diff --git a/apps/guardian-ui/src/GuardianApi.ts b/apps/guardian-ui/src/GuardianApi.ts index ac99e152e..f2d937706 100644 --- a/apps/guardian-ui/src/GuardianApi.ts +++ b/apps/guardian-ui/src/GuardianApi.ts @@ -1,6 +1,7 @@ import { JsonRpcError, JsonRpcWebsocket } from 'jsonrpc-client-websocket'; import { ConfigGenParams, ConsensusState, PeerHashMap } from './setup/types'; import { + AuditSummary, ConfigResponse, FederationStatus, ServerStatus, @@ -208,6 +209,7 @@ enum AdminRpc { inviteCode = 'invite_code', config = 'config', module = 'module', + audit = 'audit', } export enum LightningModuleRpc { @@ -221,6 +223,7 @@ export interface AdminApiInterface extends SharedApiInterface { fetchEpochCount: () => Promise; inviteCode: () => Promise; config: (connection: string) => Promise; + audit: () => Promise; moduleApiCall: (moduleId: number, rpc: ModuleRpc) => Promise; } @@ -366,6 +369,10 @@ export class GuardianApi return this.base.call(AdminRpc.config, connection); }; + audit = (): Promise => { + return this.base.call(AdminRpc.audit); + }; + moduleApiCall = (moduleId: number, rpc: ModuleRpc): Promise => { const method = `${AdminRpc.module}_${moduleId}_${rpc}`; return this.base.call_any_method(method); diff --git a/apps/guardian-ui/src/components/BalanceCard.tsx b/apps/guardian-ui/src/components/BalanceCard.tsx index c5a2d8cee..5f7b3f4ed 100644 --- a/apps/guardian-ui/src/components/BalanceCard.tsx +++ b/apps/guardian-ui/src/components/BalanceCard.tsx @@ -1,21 +1,37 @@ -import { Card, CardBody, CardHeader, Text } from '@chakra-ui/react'; +import { Card, CardBody, CardHeader, Skeleton, Text } from '@chakra-ui/react'; import { KeyValues } from '@fedimint/ui'; -import { useTranslation } from '@fedimint/utils'; -import React, { useMemo } from 'react'; +import { MSats, formatMsatsToBtc, useTranslation } from '@fedimint/utils'; +import React, { useEffect, useMemo, useState } from 'react'; +import { AuditSummary } from '../types'; +import { useAdminContext } from '../hooks'; export const BalanceCard: React.FC = () => { const { t } = useTranslation(); + const { api } = useAdminContext(); + const [auditSummary, setAuditSummary] = useState(); + + const walletBalance = auditSummary + ? auditSummary.module_summaries.wallet?.net_assets || (0 as MSats) + : undefined; + + useEffect(() => { + api.audit().then(setAuditSummary).catch(console.error); + }, [api]); const keyValues = useMemo( () => [ { key: 'bitcoin', label: t('common.bitcoin'), - // TODO: Use `audit` api to fill this in - value: '0.00000000', + value: + typeof walletBalance === 'number' ? ( + formatMsatsToBtc(walletBalance) + ) : ( + + ), }, ], - [t] + [walletBalance, t] ); return ( diff --git a/apps/guardian-ui/src/types.tsx b/apps/guardian-ui/src/types.tsx index f66b647ab..498c3d84e 100644 --- a/apps/guardian-ui/src/types.tsx +++ b/apps/guardian-ui/src/types.tsx @@ -1,3 +1,5 @@ +import type { MSats } from '@fedimint/utils'; + export enum ServerStatus { AwaitingPassword = 'AwaitingPassword', SharingConfigGenParams = 'SharingConfigGenParams', @@ -153,3 +155,12 @@ export interface InitializationState { needsAuth: boolean; serverStatus: ServerStatus; } + +export interface ModuleSummary { + net_assets: MSats; +} + +export interface AuditSummary { + net_assets: MSats; + module_summaries: Record; +} diff --git a/docker-compose.yml b/docker-compose.yml index afec8d4ac..c064ea2e5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -71,6 +71,8 @@ services: - '18184:18184' volumes: - ./fm_2/data:/data + depends_on: + - bitcoind # gatewayd_2: # image: fedimint/gatewayd:master @@ -92,6 +94,8 @@ services: - '18185:18185' volumes: - ./fm_3/data:/data + depends_on: + - bitcoind fedimintd_4: image: fedimint/fedimintd:v0.1.0-rc3 @@ -107,6 +111,8 @@ services: - '18186:18186' volumes: - ./fm_4/data:/data + depends_on: + - bitcoind bitcoind: image: btcpayserver/bitcoin:24.1 diff --git a/packages/utils/src/format.tsx b/packages/utils/src/format.tsx index 45688fb48..069dba426 100644 --- a/packages/utils/src/format.tsx +++ b/packages/utils/src/format.tsx @@ -1,3 +1,5 @@ +import { MSats } from './types'; + /** * Given a string, turn it into an "ellipsis sandwich" with the start and * end showing. If the text is shorter than it would be with an ellipsis, @@ -9,3 +11,14 @@ export function formatEllipsized(text: string, size = 6) { } return `${text.substring(0, size)}...${text.substring(text.length - size)}`; } + +/** + * Given some number of msats, return a formatted string in the format of + * 0.0000000 or 1,234,5678.9000000 + */ +export function formatMsatsToBtc(msats: MSats): string { + return Intl.NumberFormat(undefined, { + style: 'decimal', + minimumFractionDigits: 8, + }).format(msats / 1_00_000_000_000); +} diff --git a/packages/utils/src/index.tsx b/packages/utils/src/index.tsx index 923e52b9d..6c0b32134 100644 --- a/packages/utils/src/index.tsx +++ b/packages/utils/src/index.tsx @@ -1,2 +1,3 @@ export * from './i18n'; export * from './format'; +export * from './types'; diff --git a/packages/utils/src/types.ts b/packages/utils/src/types.ts new file mode 100644 index 000000000..b26108ce0 --- /dev/null +++ b/packages/utils/src/types.ts @@ -0,0 +1,5 @@ +// Opaque numeric types to avoid mixing different denominations of bitcoin +type BitcoinUnit = K & { _: T }; +export type Btc = BitcoinUnit; +export type Sats = BitcoinUnit; +export type MSats = BitcoinUnit;