Skip to content

Commit b2084b7

Browse files
committed
feat: runes balances service
1 parent 59d7d69 commit b2084b7

17 files changed

+307
-166
lines changed

apps/mobile/src/features/balances/bitcoin/runes-balance.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { TokenIcon } from '@/components/widgets/tokens/token-icon';
2-
import { createBitcoinAccountIdentifier } from '@/hooks/use-bitcoin-account-service-requests';
32
import { AccountId } from '@/models/domain.model';
43
import {
54
useRunesAccountBalance,
@@ -42,34 +41,31 @@ export function RunesBalance() {
4241
// TODO LEA-1726: handle balance loading & error states
4342
if (data.state !== 'success') return;
4443

45-
return data.value.accountBalances.map((balances: any) =>
46-
balances.runes.map((balance: any, index: any) => (
47-
<RunesTokenBalance
48-
key={`${balance.asset.symbol}-${index}`}
49-
symbol={balance.asset.symbol}
50-
name={balance.asset.name}
51-
availableBalance={balance.availableBalance}
52-
fiatBalance={balance.totalBalance}
53-
px="5"
54-
py="3"
55-
/>
56-
))
57-
);
44+
return data.value.runes.map((balance, index) => (
45+
<RunesTokenBalance
46+
key={`${balance.asset.symbol}-${index}`}
47+
symbol={balance.asset.symbol}
48+
name={balance.asset.runeName}
49+
availableBalance={balance.crypto.availableBalance}
50+
fiatBalance={balance.usd.totalBalance}
51+
px="5"
52+
py="3"
53+
/>
54+
));
5855
}
5956

6057
export function RunesBalanceByAccount({ fingerprint, accountIndex }: AccountId) {
61-
const account = createBitcoinAccountIdentifier(fingerprint, accountIndex, []);
62-
const data = useRunesAccountBalance(account);
58+
const data = useRunesAccountBalance(fingerprint, accountIndex);
6359

6460
// TODO LEA-1726: handle balance loading & error states
6561
if (data.state !== 'success') return;
66-
return data.value.runes.map((balance: any, index: any) => (
62+
return data.value.runes.map((balance, index) => (
6763
<RunesTokenBalance
6864
key={`${balance.asset.symbol}-${index}`}
6965
symbol={balance.asset.symbol}
70-
name={balance.asset.name}
71-
availableBalance={balance.availableBalance}
72-
fiatBalance={balance.totalBalance}
66+
name={balance.asset.spacedRuneName}
67+
availableBalance={balance.crypto.availableBalance}
68+
fiatBalance={balance.usd.availableBalance}
7369
px="5"
7470
py="3"
7571
/>

apps/mobile/src/features/balances/stacks/sip10-balance.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@ export function Sip10Balance() {
4141
// TODO LEA-1726: handle balance loading & error states
4242
if (data.state !== 'success') return;
4343

44-
return data.value.aggregateBalances.map((balance, index) => {
44+
return data.value.sip10s.map((balance, index) => {
4545
if (index >= sip10MaxDisplay) return null;
4646
return (
4747
<Sip10TokenBalance
4848
key={`${balance.asset.symbol}-${index}`}
4949
symbol={balance.asset.symbol}
5050
name={balance.asset.name}
51-
availableBalance={balance.sip10.availableBalance}
51+
availableBalance={balance.crypto.availableBalance}
5252
fiatBalance={balance.usd.totalBalance}
5353
px="5"
5454
py="3"
@@ -72,7 +72,7 @@ export function Sip10BalanceByAccount({ accountIndex, fingerprint }: Sip10Balanc
7272
key={`${balance.asset.symbol}-${index}`}
7373
symbol={balance.asset.symbol}
7474
name={balance.asset.name}
75-
availableBalance={balance.sip10.availableBalance}
75+
availableBalance={balance.crypto.availableBalance}
7676
fiatBalance={balance.usd.totalBalance}
7777
px="5"
7878
py="3"

apps/mobile/src/queries/balance/account-balance.query.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,23 @@ import { useStxAccountBalance } from '@/queries/balance/stx-balance.query';
33
import { FetchState, toFetchState } from '@/shared/fetch-state';
44

55
import { Money } from '@leather.io/models';
6-
import { BtcAccountBalance, Sip10AddressBalance, StxAddressBalance } from '@leather.io/services';
6+
import {
7+
BtcAccountBalance,
8+
RunesAccountBalance,
9+
Sip10AddressBalance,
10+
StxAddressBalance,
11+
} from '@leather.io/services';
712
import { createMoney, isDefined, sumMoney } from '@leather.io/utils';
813

914
import { useBtcAccountBalance } from './btc-balance.query';
15+
import { useRunesAccountBalance } from './runes-balance.query';
1016
import { useSip10AccountBalance } from './sip10-balance.query';
1117

1218
interface AccountBalance {
1319
btc: FetchState<BtcAccountBalance>;
1420
stx: FetchState<StxAddressBalance>;
1521
sip10: FetchState<Sip10AddressBalance>;
22+
runes: FetchState<RunesAccountBalance>;
1623
totalBalance: FetchState<Money>;
1724
}
1825

@@ -24,28 +31,33 @@ export function useAccountBalance(accountId: AccountId): AccountBalance {
2431
const btcAccountBalance = useBtcAccountBalance(fingerprint, accountIndex);
2532
const stxAccountBalance = useStxAccountBalance(fingerprint, accountIndex);
2633
const sip10AccountBalance = useSip10AccountBalance(fingerprint, accountIndex);
34+
const runesAccountBalance = useRunesAccountBalance(fingerprint, accountIndex);
2735

2836
const isLoading =
2937
btcAccountBalance.state === 'loading' &&
3038
stxAccountBalance.state === 'loading' &&
31-
sip10AccountBalance.state === 'loading';
39+
sip10AccountBalance.state === 'loading' &&
40+
runesAccountBalance.state === 'loading';
3241
const isError =
3342
btcAccountBalance.state === 'error' &&
3443
stxAccountBalance.state === 'error' &&
35-
sip10AccountBalance.state === 'error';
44+
sip10AccountBalance.state === 'error' &&
45+
runesAccountBalance.state === 'error';
3646
const accountBalance = sumMoney(
3747
[
3848
zeroMoneyUsd,
3949
btcAccountBalance.value?.usd.availableBalance,
4050
stxAccountBalance.value?.usd.availableBalance,
4151
sip10AccountBalance.value?.usd.availableBalance,
52+
runesAccountBalance.value?.usd.availableBalance,
4253
].filter(isDefined)
4354
);
4455

4556
return {
4657
btc: btcAccountBalance,
4758
stx: stxAccountBalance,
4859
sip10: sip10AccountBalance,
60+
runes: runesAccountBalance,
4961
totalBalance: toFetchState({
5062
isLoading,
5163
data: accountBalance,

apps/mobile/src/queries/balance/runes-balance.query.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
import { useTotalBitcoinAccountServiceRequests } from '@/hooks/use-bitcoin-account-service-requests';
1+
import {
2+
useBitcoinAccountServiceRequest,
3+
useTotalBitcoinAccountServiceRequests,
4+
useWalletBitcoinAccountServiceRequests,
5+
} from '@/hooks/use-bitcoin-account-service-requests';
26
import { toFetchState } from '@/shared/fetch-state';
37
import { QueryFunctionContext, useQuery } from '@tanstack/react-query';
48

59
import { BitcoinAccountIdentifier, getRunesBalancesService } from '@leather.io/services';
610

711
export function useRunesTotalBalance() {
812
// TODO LEA-1982: check with Alex once we have xpub-based runes endpoint from BIS
9-
const accounts = useTotalBitcoinAccountServiceRequests();
10-
return toFetchState(useRunesAggregateBalanceQuery(accounts.map(account => account.account)));
13+
const totalRequests = useTotalBitcoinAccountServiceRequests();
14+
return toFetchState(useRunesAggregateBalanceQuery(totalRequests.map(r => r.account)));
1115
}
1216

13-
export function useRunesAggregateBalance(accounts: BitcoinAccountIdentifier[]) {
14-
return toFetchState(useRunesAggregateBalanceQuery(accounts));
17+
export function useRunesWalletBalance(fingerprint: string) {
18+
const walletRequests = useWalletBitcoinAccountServiceRequests(fingerprint);
19+
return toFetchState(useRunesAggregateBalanceQuery(walletRequests.map(r => r.account)));
1520
}
1621

17-
export function useRunesAccountBalance(account: BitcoinAccountIdentifier) {
18-
return toFetchState(useRunesAccountBalanceQuery(account));
22+
export function useRunesAccountBalance(fingerprint: string, accountIndex: number) {
23+
const accountRequest = useBitcoinAccountServiceRequest(fingerprint, accountIndex);
24+
return toFetchState(useRunesAccountBalanceQuery(accountRequest.account));
1925
}
2026

2127
export function useRunesAggregateBalanceQuery(accounts: BitcoinAccountIdentifier[]) {

apps/mobile/src/queries/balance/total-balance.query.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ import { FetchState, toFetchState } from '@/shared/fetch-state';
44
import { Money } from '@leather.io/models';
55
import {
66
BtcAggregateBalance,
7+
RunesAggregateBalance,
78
Sip10AggregateBalance,
89
StxAggregateBalance,
910
} from '@leather.io/services';
1011
import { createMoney, isDefined, sumMoney } from '@leather.io/utils';
1112

1213
import { useBtcTotalBalance } from './btc-balance.query';
14+
import { useRunesTotalBalance } from './runes-balance.query';
1315
import { useSip10TotalBalance } from './sip10-balance.query';
1416

1517
interface TotalBalance {
1618
btc: FetchState<BtcAggregateBalance>;
1719
stx: FetchState<StxAggregateBalance>;
1820
sip10: FetchState<Sip10AggregateBalance>;
21+
runes: FetchState<RunesAggregateBalance>;
1922
totalBalance: FetchState<Money>;
2023
}
2124

@@ -25,28 +28,33 @@ export function useTotalBalance(): TotalBalance {
2528
const btcTotalBalance = useBtcTotalBalance();
2629
const stxTotalBalance = useStxTotalBalance();
2730
const sip10TotalBalance = useSip10TotalBalance();
31+
const runesTotalBalance = useRunesTotalBalance();
2832

2933
const isLoading =
3034
btcTotalBalance.state === 'loading' &&
3135
stxTotalBalance.state === 'loading' &&
32-
sip10TotalBalance.state === 'loading';
36+
sip10TotalBalance.state === 'loading' &&
37+
runesTotalBalance.state === 'loading';
3338
const isError =
3439
btcTotalBalance.state === 'error' &&
3540
stxTotalBalance.state === 'error' &&
36-
sip10TotalBalance.state === 'error';
41+
sip10TotalBalance.state === 'error' &&
42+
runesTotalBalance.state === 'error';
3743
const accountBalance = sumMoney(
3844
[
3945
zeroMoneyUsd,
4046
btcTotalBalance.value?.usd.availableBalance,
4147
stxTotalBalance.value?.usd.availableBalance,
4248
sip10TotalBalance.value?.usd.availableBalance,
49+
runesTotalBalance.value?.usd.availableBalance,
4350
].filter(isDefined)
4451
);
4552

4653
return {
4754
btc: btcTotalBalance,
4855
stx: stxTotalBalance,
4956
sip10: sip10TotalBalance,
57+
runes: runesTotalBalance,
5058
totalBalance: toFetchState({
5159
isLoading,
5260
data: accountBalance,

packages/services/src/balances/runes-balances.service.ts

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,32 @@ import { BestInSlotApiClient } from '../infrastructure/api/best-in-slot/best-in-
1212
import { MarketDataService } from '../market-data/market-data.service';
1313
import { BitcoinAccountIdentifier } from '../shared/bitcoin.types';
1414
import { baseCryptoAssetZeroBalanceUsd } from './constants';
15-
import { parseRunesOutputsBalances } from './runes-balances.utils';
15+
import { aggregateRunesAccountBalances, readRunesOutputsBalances } from './runes-balances.utils';
1616

1717
export interface RuneAssetBalance {
1818
asset: RuneCryptoAssetInfo;
1919
usd: CryptoAssetBalance;
20-
rune: CryptoAssetBalance;
20+
crypto: CryptoAssetBalance;
2121
}
2222

23-
export interface RuneAccountBalance {
24-
account: BitcoinAccountIdentifier;
23+
export interface RunesAggregateBalance {
2524
usd: CryptoAssetBalance;
2625
runes: RuneAssetBalance[];
2726
}
2827

29-
export interface RuneAggregateBalance {
30-
usd: CryptoAssetBalance;
31-
accountBalances: RuneAccountBalance[];
28+
export interface RunesAccountBalance extends RunesAggregateBalance {
29+
account: BitcoinAccountIdentifier;
3230
}
3331

3432
export interface RunesBalancesService {
3533
getRunesAccountBalance(
3634
bitcoinAccount: BitcoinAccountIdentifier,
3735
signal?: AbortSignal
38-
): Promise<RuneAccountBalance>;
36+
): Promise<RunesAccountBalance>;
3937
getRunesAggregateBalance(
4038
accounts: BitcoinAccountIdentifier[],
4139
signal?: AbortSignal
42-
): Promise<RuneAggregateBalance>;
40+
): Promise<RunesAggregateBalance>;
4341
}
4442

4543
export function createRunesBalancesService(
@@ -54,7 +52,7 @@ export function createRunesBalancesService(
5452
async function getRunesAggregateBalance(
5553
accounts: BitcoinAccountIdentifier[],
5654
signal?: AbortSignal
57-
) {
55+
): Promise<RunesAggregateBalance> {
5856
const accountBalances = await Promise.all(
5957
accounts.map(account => getRunesAccountBalance(account, signal))
6058
);
@@ -63,29 +61,35 @@ export function createRunesBalancesService(
6361
baseCryptoAssetZeroBalanceUsd,
6462
...accountBalances.map(b => b.usd),
6563
]),
66-
accountBalances,
64+
runes: aggregateRunesAccountBalances(accountBalances),
6765
};
6866
}
6967

7068
/**
7169
* Retrieve Rune balances for given account.
7270
* Includes cumulative USD-denominated balance.
7371
*/
74-
async function getRunesAccountBalance(account: BitcoinAccountIdentifier, signal?: AbortSignal) {
72+
async function getRunesAccountBalance(
73+
account: BitcoinAccountIdentifier,
74+
signal?: AbortSignal
75+
): Promise<RunesAccountBalance> {
7576
const runesOutputs = await bisApiClient.fetchRunesValidOutputs(
7677
account.taprootDescriptor,
7778
signal
7879
);
79-
const runesOutputsBalances = parseRunesOutputsBalances(runesOutputs);
80-
const balances = await Promise.all(
80+
const runesOutputsBalances = readRunesOutputsBalances(runesOutputs);
81+
const runesBalances = await Promise.all(
8182
Object.keys(runesOutputsBalances).map(runeName => {
8283
return getRuneBalance(runeName, runesOutputsBalances[runeName], signal);
8384
})
8485
);
8586
return {
8687
account,
87-
usd: aggregateBaseCryptoAssetBalances(balances.map(b => b.usd)),
88-
runes: balances,
88+
usd: aggregateBaseCryptoAssetBalances([
89+
baseCryptoAssetZeroBalanceUsd,
90+
...runesBalances.map(b => b.usd),
91+
]),
92+
runes: runesBalances,
8993
};
9094
}
9195

@@ -95,12 +99,12 @@ export function createRunesBalancesService(
9599
signal?: AbortSignal
96100
): Promise<RuneAssetBalance> {
97101
const runeInfo = await runeAssetService.getAssetInfo(runeName, signal);
98-
const totalBalance = createMoney(initBigNumber(amount), runeInfo.symbol, runeInfo.decimals);
102+
const totalBalance = createMoney(initBigNumber(amount), runeInfo.runeName, runeInfo.decimals);
99103
const runeMarketData = await marketDataService.getRuneMarketData(runeInfo, signal);
100104
return {
101105
asset: runeInfo,
102-
rune: createBaseCryptoAssetBalance(totalBalance),
103106
usd: createBaseCryptoAssetBalance(baseCurrencyAmountInQuote(totalBalance, runeMarketData)),
107+
crypto: createBaseCryptoAssetBalance(totalBalance),
104108
};
105109
}
106110
return {

0 commit comments

Comments
 (0)