From d270015938f6897706ce48c450f787a5cefdc035 Mon Sep 17 00:00:00 2001 From: Ethan Tuttle Date: Wed, 30 Aug 2023 15:44:29 -0400 Subject: [PATCH 1/3] feat: audit api --- apps/guardian-ui/src/GuardianApi.ts | 7 +++++++ apps/guardian-ui/src/admin/FederationAdmin.tsx | 4 +++- apps/guardian-ui/src/types.tsx | 10 ++++++++++ docker-compose.yml | 6 ++++++ 4 files changed, 26 insertions(+), 1 deletion(-) 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/admin/FederationAdmin.tsx b/apps/guardian-ui/src/admin/FederationAdmin.tsx index dba50199e..5904495f9 100644 --- a/apps/guardian-ui/src/admin/FederationAdmin.tsx +++ b/apps/guardian-ui/src/admin/FederationAdmin.tsx @@ -3,7 +3,7 @@ import { Flex, Box, Icon, Text, useTheme, Heading } from '@chakra-ui/react'; import { CopyInput } from '@fedimint/ui'; import { useTranslation } from '@fedimint/utils'; import { useAdminContext } from '../hooks'; -import { ConfigResponse, StatusResponse } from '../types'; +import { AuditSummary, ConfigResponse, StatusResponse } from '../types'; import { GatewaysCard } from '../components/GatewaysCard'; import { ReactComponent as CopyIcon } from '../assets/svgs/copy.svg'; import { GuardiansCard } from '../components/GuardiansCard'; @@ -16,6 +16,7 @@ export const FederationAdmin: React.FC = () => { const { api } = useAdminContext(); const [status, setStatus] = useState(); const [inviteCode, setInviteCode] = useState(''); + const [auditSummary, setAuditSummary] = useState(); const [config, setConfig] = useState(); const { t } = useTranslation(); @@ -23,6 +24,7 @@ export const FederationAdmin: React.FC = () => { // TODO: poll server status api.status().then(setStatus).catch(console.error); api.inviteCode().then(setInviteCode).catch(console.error); + api.audit().then(setAuditSummary).catch(console.error); }, [api]); useEffect(() => { diff --git a/apps/guardian-ui/src/types.tsx b/apps/guardian-ui/src/types.tsx index f66b647ab..a75d68314 100644 --- a/apps/guardian-ui/src/types.tsx +++ b/apps/guardian-ui/src/types.tsx @@ -153,3 +153,13 @@ export interface InitializationState { needsAuth: boolean; serverStatus: ServerStatus; } + +export interface ModuleSummary { + net_assets: bigint; +} + +export interface AuditSummary { + // 9,007,199,254,740,991 milli sats is the upper limit, or ~9007 bitcoin. Should be good? + net_assets: bigint; + 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 From f16d519096c2f44a750652eb83bd9558ffe50153 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Thu, 31 Aug 2023 16:39:11 -0500 Subject: [PATCH 2/3] feat(utils): add bitcoin unit types and formatMsatsToBtc util function --- packages/utils/src/format.tsx | 13 +++++++++++++ packages/utils/src/index.tsx | 1 + packages/utils/src/types.ts | 5 +++++ 3 files changed, 19 insertions(+) create mode 100644 packages/utils/src/types.ts 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; From d7a07bd6f4bc2a8da0613ca73a3e8b1dfaeecacc Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Thu, 31 Aug 2023 16:40:30 -0500 Subject: [PATCH 3/3] feat(guardian-ui): hook up audit api to BalanceCard, adjust response types --- .../guardian-ui/src/admin/FederationAdmin.tsx | 4 +-- .../src/components/BalanceCard.tsx | 28 +++++++++++++++---- apps/guardian-ui/src/types.tsx | 9 +++--- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/apps/guardian-ui/src/admin/FederationAdmin.tsx b/apps/guardian-ui/src/admin/FederationAdmin.tsx index 5904495f9..dba50199e 100644 --- a/apps/guardian-ui/src/admin/FederationAdmin.tsx +++ b/apps/guardian-ui/src/admin/FederationAdmin.tsx @@ -3,7 +3,7 @@ import { Flex, Box, Icon, Text, useTheme, Heading } from '@chakra-ui/react'; import { CopyInput } from '@fedimint/ui'; import { useTranslation } from '@fedimint/utils'; import { useAdminContext } from '../hooks'; -import { AuditSummary, ConfigResponse, StatusResponse } from '../types'; +import { ConfigResponse, StatusResponse } from '../types'; import { GatewaysCard } from '../components/GatewaysCard'; import { ReactComponent as CopyIcon } from '../assets/svgs/copy.svg'; import { GuardiansCard } from '../components/GuardiansCard'; @@ -16,7 +16,6 @@ export const FederationAdmin: React.FC = () => { const { api } = useAdminContext(); const [status, setStatus] = useState(); const [inviteCode, setInviteCode] = useState(''); - const [auditSummary, setAuditSummary] = useState(); const [config, setConfig] = useState(); const { t } = useTranslation(); @@ -24,7 +23,6 @@ export const FederationAdmin: React.FC = () => { // TODO: poll server status api.status().then(setStatus).catch(console.error); api.inviteCode().then(setInviteCode).catch(console.error); - api.audit().then(setAuditSummary).catch(console.error); }, [api]); useEffect(() => { 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 a75d68314..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', @@ -155,11 +157,10 @@ export interface InitializationState { } export interface ModuleSummary { - net_assets: bigint; + net_assets: MSats; } export interface AuditSummary { - // 9,007,199,254,740,991 milli sats is the upper limit, or ~9007 bitcoin. Should be good? - net_assets: bigint; - module_summaries: Record; + net_assets: MSats; + module_summaries: Record; }