-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Add address balance to address pages (#1118)
* feat: Add ChornicleService for nova. Add endpoint to fetch address balance. * feat: Add AddressBalance hook and component. Include AddressBalance in Ed25519AddressView. * feat: Add AddressBalance.scss * feat: Hook the addressBalance hook in addressState hooks. Render AddressBalance in AddressView components. * feat: Return state and state setter from address state hooks and destructure in AddressViews * fix: Improve a css class naming * feat: Improve available balance JSdoc in IAddressBalanceResponse --------- Co-authored-by: Begoña Alvarez <[email protected]>
- Loading branch information
1 parent
788ea46
commit 7093e7b
Showing
21 changed files
with
506 additions
and
37 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
api/src/models/api/nova/chronicle/IAddressBalanceRequest.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export interface IAddressBalanceRequest { | ||
/** | ||
* The network to search on. | ||
*/ | ||
network: string; | ||
|
||
/** | ||
* The bech32 address to get the balance for. | ||
*/ | ||
address: string; | ||
} |
18 changes: 18 additions & 0 deletions
18
api/src/models/api/nova/chronicle/IAddressBalanceResponse.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { IResponse } from "../../IResponse"; | ||
|
||
export interface IAddressBalanceResponse extends IResponse { | ||
/** | ||
* The total balance (including Expiration, Timelock and StorageDepositReturn outputs) | ||
*/ | ||
totalBalance?: number; | ||
|
||
/** | ||
* The balance of all spendable outputs by the address at this time. | ||
*/ | ||
availableBalance?: number; | ||
|
||
/** | ||
* The ledger index at which this balance data was valid. | ||
*/ | ||
ledgerIndex?: number; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { ServiceFactory } from "../../../../../factories/serviceFactory"; | ||
import { IAddressBalanceRequest } from "../../../../../models/api/nova/chronicle/IAddressBalanceRequest"; | ||
import { IAddressBalanceResponse } from "../../../../../models/api/nova/chronicle/IAddressBalanceResponse"; | ||
import { IConfiguration } from "../../../../../models/configuration/IConfiguration"; | ||
import { NOVA } from "../../../../../models/db/protocolVersion"; | ||
import { NetworkService } from "../../../../../services/networkService"; | ||
import { ChronicleService } from "../../../../../services/nova/chronicleService"; | ||
import { ValidationHelper } from "../../../../../utils/validationHelper"; | ||
|
||
/** | ||
* Fetch the address balance from chronicle nova. | ||
* @param _ The configuration. | ||
* @param request The request. | ||
* @returns The response. | ||
*/ | ||
export async function get(_: IConfiguration, request: IAddressBalanceRequest): Promise<IAddressBalanceResponse> { | ||
const networkService = ServiceFactory.get<NetworkService>("network"); | ||
const networks = networkService.networkNames(); | ||
ValidationHelper.oneOf(request.network, networks, "network"); | ||
|
||
const networkConfig = networkService.get(request.network); | ||
|
||
if (networkConfig.protocolVersion !== NOVA) { | ||
return {}; | ||
} | ||
|
||
if (!networkConfig.permaNodeEndpoint) { | ||
return {}; | ||
} | ||
|
||
const chronicleService = ServiceFactory.get<ChronicleService>(`chronicle-${networkConfig.network}`); | ||
|
||
return chronicleService.addressBalance(request.address); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import logger from "../../logger"; | ||
import { IAddressBalanceResponse } from "../../models/api/nova/chronicle/IAddressBalanceResponse"; | ||
import { INetwork } from "../../models/db/INetwork"; | ||
import { FetchHelper } from "../../utils/fetchHelper"; | ||
|
||
const CHRONICLE_ENDPOINTS = { | ||
balance: "/api/explorer/v3/balance/", | ||
}; | ||
|
||
export class ChronicleService { | ||
/** | ||
* The endpoint for performing communications. | ||
*/ | ||
private readonly chronicleEndpoint: string; | ||
|
||
/** | ||
* The network config in context. | ||
*/ | ||
private readonly networkConfig: INetwork; | ||
|
||
constructor(config: INetwork) { | ||
this.networkConfig = config; | ||
this.chronicleEndpoint = config.permaNodeEndpoint; | ||
} | ||
|
||
/** | ||
* Get the current address balance info. | ||
* @param address The address to fetch the balance for. | ||
* @returns The address balance response. | ||
*/ | ||
public async addressBalance(address: string): Promise<IAddressBalanceResponse | undefined> { | ||
try { | ||
return await FetchHelper.json<never, IAddressBalanceResponse>( | ||
this.chronicleEndpoint, | ||
`${CHRONICLE_ENDPOINTS.balance}${address}`, | ||
"get", | ||
); | ||
} catch (error) { | ||
const network = this.networkConfig.network; | ||
logger.warn(`[ChronicleService (Nova)] Failed fetching address balance for ${address} on ${network}. Cause: ${error}`); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
77 changes: 77 additions & 0 deletions
77
client/src/app/components/nova/address/AddressBalance.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
@import "../../../../scss/media-queries"; | ||
|
||
.balance-wrapper { | ||
margin-top: 40px; | ||
|
||
.icon { | ||
margin-right: 16px; | ||
} | ||
|
||
.balance-wrapper--inner { | ||
display: flex; | ||
|
||
.balance { | ||
display: flex; | ||
flex-direction: row; | ||
|
||
.icon { | ||
align-self: center; | ||
|
||
@include tablet-down { | ||
display: none; | ||
} | ||
} | ||
|
||
&:not(:last-child) { | ||
margin-right: 40px; | ||
|
||
@include tablet-down { | ||
margin-right: 0px; | ||
} | ||
} | ||
|
||
.balance-value { | ||
display: flex; | ||
flex-direction: column; | ||
|
||
@include tablet-down { | ||
flex-direction: row; | ||
} | ||
|
||
.balance-value--inline { | ||
@include tablet-down { | ||
margin-left: 5px; | ||
} | ||
} | ||
} | ||
|
||
.balance-base-token, | ||
.balance-fiat { | ||
color: #b0bfd9; | ||
font-size: 18px; | ||
} | ||
|
||
.balance-heading { | ||
height: 20px; | ||
|
||
.material-icons { | ||
font-size: 18px; | ||
color: #b0bfd9; | ||
padding-left: 5px; | ||
} | ||
} | ||
} | ||
} | ||
|
||
@include tablet-down { | ||
.balance-wrapper--inner { | ||
flex-direction: column; | ||
|
||
.balance { | ||
&:not(:first-child) { | ||
margin-top: 26px; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import React, { useState } from "react"; | ||
import { useNetworkInfoNova } from "~/helpers/nova/networkInfo"; | ||
import { formatAmount } from "~helpers/stardust/valueFormatHelper"; | ||
import CopyButton from "../../CopyButton"; | ||
import Icon from "../../Icon"; | ||
import Tooltip from "../../Tooltip"; | ||
import "./AddressBalance.scss"; | ||
|
||
interface AddressBalanceProps { | ||
/** | ||
* The totalBalance amount from chronicle (representing trivial + conditional balance). | ||
*/ | ||
readonly totalBalance: number; | ||
/** | ||
* The trivially unlockable portion of the balance, fetched from chronicle. | ||
*/ | ||
readonly availableBalance: number | null; | ||
/** | ||
* The storage rent balance. | ||
*/ | ||
readonly storageDeposit: number | null; | ||
} | ||
|
||
const CONDITIONAL_BALANCE_INFO = | ||
"These funds reside within outputs with additional unlock conditions which might be potentially un-lockable"; | ||
|
||
const AddressBalance: React.FC<AddressBalanceProps> = ({ totalBalance, availableBalance, storageDeposit }) => { | ||
const { tokenInfo } = useNetworkInfoNova((s) => s.networkInfo); | ||
const [formatBalanceFull, setFormatBalanceFull] = useState(false); | ||
const [formatConditionalBalanceFull, setFormatConditionalBalanceFull] = useState(false); | ||
const [formatStorageBalanceFull, setFormatStorageBalanceFull] = useState(false); | ||
|
||
const buildBalanceView = ( | ||
label: string, | ||
isFormatFull: boolean, | ||
setIsFormatFull: React.Dispatch<React.SetStateAction<boolean>>, | ||
showInfo: boolean, | ||
showWallet: boolean, | ||
amount: number | null, | ||
) => ( | ||
<div className="balance"> | ||
{showWallet && <Icon icon="wallet" boxed />} | ||
<div> | ||
<div className="row middle balance-heading"> | ||
<div className="label">{label}</div> | ||
{showInfo && ( | ||
<Tooltip tooltipContent={CONDITIONAL_BALANCE_INFO}> | ||
<span className="material-icons">info</span> | ||
</Tooltip> | ||
)} | ||
</div> | ||
<div className="value featured"> | ||
{amount !== null && amount > 0 ? ( | ||
<div className="balance-value middle"> | ||
<div className="row middle"> | ||
<span onClick={() => setIsFormatFull(!isFormatFull)} className="balance-base-token pointer margin-r-5"> | ||
{formatAmount(amount, tokenInfo, isFormatFull)} | ||
</span> | ||
<CopyButton copy={String(amount)} /> | ||
</div> | ||
</div> | ||
) : ( | ||
<span className="margin-r-5">0</span> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
|
||
const conditionalBalance = availableBalance === null ? undefined : totalBalance - availableBalance; | ||
const shouldShowExtendedBalance = conditionalBalance !== undefined && availableBalance !== undefined; | ||
|
||
return ( | ||
<div className="row middle balance-wrapper"> | ||
<div className="balance-wrapper--inner"> | ||
{buildBalanceView( | ||
"Available Balance", | ||
formatBalanceFull, | ||
setFormatBalanceFull, | ||
false, | ||
true, | ||
shouldShowExtendedBalance ? availableBalance : totalBalance, | ||
)} | ||
{shouldShowExtendedBalance && | ||
buildBalanceView( | ||
"Conditionally Locked Balance", | ||
formatConditionalBalanceFull, | ||
setFormatConditionalBalanceFull, | ||
true, | ||
false, | ||
conditionalBalance, | ||
)} | ||
{buildBalanceView("Storage Deposit", formatStorageBalanceFull, setFormatStorageBalanceFull, false, false, storageDeposit)} | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export default AddressBalance; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.