Skip to content

Commit 08d7e68

Browse files
committed
fix: unify asset decoding for Namada and Cosmos (#1235)
This fixes cases where Namada addresses are decoded to the wrong chain's asset. Also: - Add NAM address to registry and remove special handling for Namada - Store addresses for all assets, replacing IBC-specific address entry - Remove unused chain lookup in mapCoinsToAssets
1 parent ed94316 commit 08d7e68

File tree

10 files changed

+224
-193
lines changed

10 files changed

+224
-193
lines changed

apps/namadillo/src/App/Ibc/ShieldAllAssetList.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import { TokenCurrency } from "App/Common/TokenCurrency";
44
import BigNumber from "bignumber.js";
55
import clsx from "clsx";
66
import { getAssetImageUrl } from "integrations/utils";
7-
import { AssetWithBalanceAndIbcInfo } from "types";
7+
import { AddressWithAssetAndBalance } from "types";
88

9-
export type SelectableAssetWithBalanceAndIbcInfo =
10-
AssetWithBalanceAndIbcInfo & {
9+
export type SelectableAddressWithAssetAndBalance =
10+
AddressWithAssetAndBalance & {
1111
checked: boolean;
1212
};
1313

1414
type ShieldAllAssetListProps = {
15-
assets: SelectableAssetWithBalanceAndIbcInfo[];
15+
assets: SelectableAddressWithAssetAndBalance[];
1616
onToggleAsset: (asset: Asset) => void;
1717
};
1818

@@ -24,7 +24,7 @@ export const ShieldAllAssetList = ({
2424
<ul className="max-h-[200px] dark-scrollbar -mr-2">
2525
{assets.map(
2626
(
27-
assetWithBalance: SelectableAssetWithBalanceAndIbcInfo,
27+
assetWithBalance: SelectableAddressWithAssetAndBalance,
2828
idx: number
2929
) => {
3030
const image = getAssetImageUrl(assetWithBalance.asset);

apps/namadillo/src/App/Ibc/ShieldAllPanel.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ import { TransferTransactionFee } from "App/Transfer/TransferTransactionFee";
1111
import { getTransactionFee } from "integrations/utils";
1212
import { useEffect, useMemo, useState } from "react";
1313
import {
14-
AssetWithBalanceAndIbcInfo,
14+
AddressWithAssetAndBalance,
1515
ChainRegistryEntry,
1616
WalletProvider,
1717
} from "types";
1818
import {
19-
SelectableAssetWithBalanceAndIbcInfo,
19+
SelectableAddressWithAssetAndBalance,
2020
ShieldAllAssetList,
2121
} from "./ShieldAllAssetList";
2222
import { ShieldAllContainer } from "./ShieldAllContainer";
@@ -26,7 +26,7 @@ type ShieldAllPanelProps = {
2626
wallet: WalletProvider;
2727
walletAddress: string;
2828
isLoading: boolean;
29-
assetList: AssetWithBalanceAndIbcInfo[];
29+
assetList: AddressWithAssetAndBalance[];
3030
onShieldAll: (assets: Asset[]) => void;
3131
};
3232

@@ -39,7 +39,7 @@ export const ShieldAllPanel = ({
3939
onShieldAll,
4040
}: ShieldAllPanelProps): JSX.Element => {
4141
const [selectableAssets, setSelectableAssets] = useState<
42-
SelectableAssetWithBalanceAndIbcInfo[]
42+
SelectableAddressWithAssetAndBalance[]
4343
>([]);
4444

4545
useEffect(() => {

apps/namadillo/src/atoms/balance/atoms.ts

Lines changed: 73 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,26 @@ import {
44
defaultAccountAtom,
55
transparentBalanceAtom,
66
} from "atoms/accounts/atoms";
7-
import { nativeTokenAddressAtom, tokenAddressesAtom } from "atoms/chain";
7+
import {
8+
chainParametersAtom,
9+
nativeTokenAddressAtom,
10+
tokenAddressesAtom,
11+
} from "atoms/chain";
812
import { shouldUpdateBalanceAtom } from "atoms/etc";
913
import { availableAssetsAtom } from "atoms/integrations";
1014
import { queryDependentFn } from "atoms/utils";
1115
import BigNumber from "bignumber.js";
1216
import { atomWithQuery } from "jotai-tanstack-query";
1317
import { namadaAsset } from "registry/namadaAsset";
14-
import { unknownAsset } from "registry/unknownAsset";
15-
import { toDisplayAmount } from "utils";
16-
import { findAssetByToken } from "./functions";
18+
import { AddressWithAssetAndBalance } from "types";
19+
import {
20+
findAssetByToken,
21+
mapNamadaAddressesToAssets,
22+
mapNamadaAssetsToTokenBalances,
23+
} from "./functions";
1724
import { fetchCoinPrices, fetchShieldedBalance } from "./services";
1825

19-
export type TokenBalance = {
20-
asset: Asset;
21-
balance: BigNumber;
26+
export type TokenBalance = AddressWithAssetAndBalance & {
2227
dollar?: BigNumber;
2328
};
2429

@@ -148,75 +153,88 @@ export const tokenPricesAtom = atomWithQuery((get) => {
148153
};
149154
});
150155

151-
export const shieldedTokensAtom = atomWithQuery<TokenBalance[]>((get) => {
152-
const shieldedBalanceQuery = get(shieldedBalanceAtom);
153-
const assetByAddressQuery = get(assetByAddressAtom);
154-
const tokenPricesQuery = get(tokenPricesAtom);
156+
export const namadaShieldedAssetsAtom = atomWithQuery((get) => {
157+
const shieldedBalances = get(shieldedBalanceAtom);
158+
const tokenAddresses = get(tokenAddressesAtom);
159+
const chainParameters = get(chainParametersAtom);
155160

156161
return {
157162
queryKey: [
158-
"shielded-tokens",
159-
shieldedBalanceQuery.data,
160-
assetByAddressQuery.data,
161-
tokenPricesQuery.data,
163+
"namada-shielded-assets",
164+
shieldedBalances.data,
165+
tokenAddresses.data,
166+
chainParameters.data!.chainId,
162167
],
163168
...queryDependentFn(
164169
async () =>
165-
shieldedBalanceQuery.data?.map(({ address, amount }) =>
166-
formatTokenBalance(
167-
address,
168-
amount,
169-
assetByAddressQuery.data,
170-
tokenPricesQuery.data
171-
)
172-
) ?? [],
173-
[shieldedBalanceQuery, tokenPricesQuery, assetByAddressQuery]
170+
await mapNamadaAddressesToAssets(
171+
shieldedBalances.data!,
172+
tokenAddresses.data!,
173+
chainParameters.data!.chainId
174+
),
175+
[shieldedBalances, tokenAddresses, chainParameters]
174176
),
175177
};
176178
});
177179

178-
export const transparentTokensAtom = atomWithQuery<TokenBalance[]>((get) => {
179-
const transparentBalanceQuery = get(transparentBalanceAtom);
180-
const assetByAddressQuery = get(assetByAddressAtom);
181-
const tokenPricesQuery = get(tokenPricesAtom);
180+
export const namadaTransparentAssetsAtom = atomWithQuery((get) => {
181+
const transparentBalances = get(transparentBalanceAtom);
182+
const tokenAddresses = get(tokenAddressesAtom);
183+
const chainParameters = get(chainParametersAtom);
182184

183185
return {
184186
queryKey: [
185-
"transparent-tokens",
186-
transparentBalanceQuery.data,
187-
assetByAddressQuery.data,
188-
tokenPricesQuery.data,
187+
"namada-transparent-assets",
188+
transparentBalances.data,
189+
tokenAddresses.data,
190+
chainParameters.data!.chainId,
189191
],
190192
...queryDependentFn(
191193
async () =>
192-
transparentBalanceQuery.data?.map(({ address, amount }) =>
193-
formatTokenBalance(
194-
address,
195-
amount,
196-
assetByAddressQuery.data,
197-
tokenPricesQuery.data
198-
)
199-
) ?? [],
200-
[transparentBalanceQuery, tokenPricesQuery, assetByAddressQuery]
194+
await mapNamadaAddressesToAssets(
195+
transparentBalances.data!,
196+
tokenAddresses.data!,
197+
chainParameters.data!.chainId
198+
),
199+
[transparentBalances, tokenAddresses, chainParameters]
201200
),
202201
};
203202
});
204203

205-
const formatTokenBalance = (
206-
address: string,
207-
amount: BigNumber,
208-
assetsByAddress?: Record<string, Asset>,
209-
tokenPrices?: Record<string, BigNumber>
210-
): TokenBalance => {
211-
const asset = assetsByAddress?.[address] ?? unknownAsset;
212-
const balance = toDisplayAmount(asset, amount);
204+
export const shieldedTokensAtom = atomWithQuery<TokenBalance[]>((get) => {
205+
const shieldedAssets = get(namadaShieldedAssetsAtom);
206+
const tokenPrices = get(tokenPricesAtom);
207+
208+
return {
209+
queryKey: ["shielded-tokens", shieldedAssets.data, tokenPrices.data],
210+
...queryDependentFn(
211+
() =>
212+
Promise.resolve(
213+
mapNamadaAssetsToTokenBalances(
214+
shieldedAssets.data!,
215+
tokenPrices.data!
216+
)
217+
),
218+
[shieldedAssets, tokenPrices]
219+
),
220+
};
221+
});
213222

214-
const tokenPrice = tokenPrices?.[address];
215-
const dollar = tokenPrice ? balance.multipliedBy(tokenPrice) : undefined;
223+
export const transparentTokensAtom = atomWithQuery<TokenBalance[]>((get) => {
224+
const transparentAssets = get(namadaTransparentAssetsAtom);
225+
const tokenPrices = get(tokenPricesAtom);
216226

217227
return {
218-
asset,
219-
balance,
220-
dollar,
228+
queryKey: ["transparent-tokens", transparentAssets.data, tokenPrices.data],
229+
...queryDependentFn(
230+
() =>
231+
Promise.resolve(
232+
mapNamadaAssetsToTokenBalances(
233+
transparentAssets.data!,
234+
tokenPrices.data!
235+
)
236+
),
237+
[transparentAssets, tokenPrices]
238+
),
221239
};
222-
};
240+
});

apps/namadillo/src/atoms/balance/functions.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { IbcToken, NativeToken } from "@anomaorg/namada-indexer-client";
22
import { Asset, AssetList } from "@chain-registry/types";
3+
import { mapCoinsToAssets } from "atoms/integrations";
34
import BigNumber from "bignumber.js";
5+
import { DenomTrace } from "cosmjs-types/ibc/applications/transfer/v1/transfer";
46
import { namadaAsset } from "registry/namadaAsset";
7+
import { AddressWithAssetAndBalanceMap } from "types";
58
import { TokenBalance } from "./atoms";
69

710
// TODO upgrade this function to be as smart as possible
@@ -56,3 +59,59 @@ export const getTotalDollar = (list?: TokenBalance[]): BigNumber | undefined =>
5659
export const getTotalNam = (list?: TokenBalance[]): BigNumber =>
5760
list?.find((i) => i.asset.base === namadaAsset.base)?.balance ??
5861
new BigNumber(0);
62+
63+
const tnamAddressToDenomTrace = (
64+
address: string,
65+
tokenAddresses: (NativeToken | IbcToken)[]
66+
): DenomTrace | undefined => {
67+
const token = tokenAddresses.find((entry) => entry.address === address);
68+
const trace = token && "trace" in token ? token.trace : undefined;
69+
70+
// If no trace, the token is NAM, but return undefined because we only care
71+
// about IBC tokens here
72+
if (typeof trace === "undefined") {
73+
return undefined;
74+
}
75+
76+
const separatorIndex = trace.lastIndexOf("/");
77+
78+
if (separatorIndex === -1) {
79+
return undefined;
80+
}
81+
82+
return {
83+
path: trace.substring(0, separatorIndex),
84+
baseDenom: trace.substring(separatorIndex + 1),
85+
};
86+
};
87+
88+
export const mapNamadaAddressesToAssets = async (
89+
balances: { address: string; amount: BigNumber }[],
90+
tokenAddresses: (NativeToken | IbcToken)[],
91+
chainName: string
92+
): Promise<AddressWithAssetAndBalanceMap> => {
93+
const coins = balances.map(({ address, amount }) => ({
94+
denom: address,
95+
amount: amount.toString(), // TODO: don't convert back to string
96+
}));
97+
98+
return await mapCoinsToAssets(coins, chainName, (tnamAddress) =>
99+
Promise.resolve(tnamAddressToDenomTrace(tnamAddress, tokenAddresses))
100+
);
101+
};
102+
103+
export const mapNamadaAssetsToTokenBalances = (
104+
assets: AddressWithAssetAndBalanceMap,
105+
tokenPrices: Record<string, BigNumber>
106+
): TokenBalance[] => {
107+
return Object.values(assets).map((assetEntry) => {
108+
const tokenPrice = tokenPrices[assetEntry.address];
109+
const dollar =
110+
tokenPrice ? assetEntry.balance.multipliedBy(tokenPrice) : undefined;
111+
112+
return {
113+
...assetEntry,
114+
dollar,
115+
};
116+
});
117+
};

apps/namadillo/src/atoms/integrations/atoms.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import { atom } from "jotai";
66
import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query";
77
import { atomFamily, atomWithStorage } from "jotai/utils";
88
import { ChainId, ChainRegistryEntry } from "types";
9-
import { getKnownChains, mapCoinsToAssets } from "./functions";
9+
import {
10+
getKnownChains,
11+
ibcAddressToDenomTrace,
12+
mapCoinsToAssets,
13+
} from "./functions";
1014
import {
1115
IbcTransferParams,
1216
queryAndStoreRpc,
@@ -60,7 +64,11 @@ export const assetBalanceAtomFamily = atomFamily(
6064
...queryDependentFn(async () => {
6165
return await queryAndStoreRpc(chain!, async (rpc: string) => {
6266
const assetsBalances = await queryAssetBalances(walletAddress!, rpc);
63-
return await mapCoinsToAssets(assetsBalances, chain!.chain_name, rpc);
67+
return await mapCoinsToAssets(
68+
assetsBalances,
69+
chain!.chain_name,
70+
ibcAddressToDenomTrace(rpc)
71+
);
6472
});
6573
}, [!!walletAddress, !!chain]),
6674
}));

0 commit comments

Comments
 (0)