Skip to content

Commit 478b0e3

Browse files
feat: pick gas fee token with enough balance (#2206)
1 parent 85ce720 commit 478b0e3

File tree

16 files changed

+338
-107
lines changed

16 files changed

+338
-107
lines changed

apps/namadillo/src/App/Common/GasFeeModal.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { useAtomValue } from "jotai";
1616
import { IoClose } from "react-icons/io5";
1717
import { twMerge } from "tailwind-merge";
1818
import { Asset, GasConfig, NamadaAsset } from "types";
19-
import { toDisplayAmount } from "utils";
2019
import { getDisplayGasFee } from "utils/gas";
2120
import { FiatCurrency } from "./FiatCurrency";
2221
import { TokenCard } from "./TokenCard";
@@ -122,17 +121,10 @@ export const GasFeeModal = ({
122121

123122
const findUserBalanceByTokenAddress = (tokenAddres: string): BigNumber => {
124123
// TODO: we need to refactor userShieldedBalances to return Balance[] type instead
125-
const balances =
126-
isShielded ?
127-
shieldedAmount.data?.map((balance) => ({
128-
minDenomAmount: balance.minDenomAmount,
129-
tokenAddress: balance.address,
130-
}))
131-
: transparentAmount.data;
124+
const balances = isShielded ? shieldedAmount.data : transparentAmount.data;
132125

133-
return new BigNumber(
134-
balances?.find((token) => token.tokenAddress === tokenAddres)
135-
?.minDenomAmount || "0"
126+
return BigNumber(
127+
balances?.find((b) => b.tokenAddress === tokenAddres)?.amount || "0"
136128
);
137129
};
138130

@@ -256,9 +248,8 @@ export const GasFeeModal = ({
256248
gasToken: item.token,
257249
});
258250

259-
const availableAmount = toDisplayAmount(
260-
asset,
261-
findUserBalanceByTokenAddress(item.token)
251+
const availableAmount = findUserBalanceByTokenAddress(
252+
item.token
262253
);
263254

264255
return {

apps/namadillo/src/atoms/accounts/atoms.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { Balance } from "@namada/indexer-client";
21
import {
32
AccountType,
43
GenDisposableSignerResponse,
@@ -7,13 +6,15 @@ import {
76
import { indexerApiAtom } from "atoms/api";
87
import { nativeTokenAddressAtom } from "atoms/chain";
98
import { shouldUpdateBalanceAtom } from "atoms/etc";
9+
import { namadaRegistryChainAssetsMapAtom } from "atoms/integrations";
1010
import { namadaExtensionConnectedAtom } from "atoms/settings";
1111
import { queryDependentFn } from "atoms/utils";
1212
import BigNumber from "bignumber.js";
1313
import { NamadaKeychain } from "hooks/useNamadaKeychain";
1414
import { atom } from "jotai";
1515
import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query";
16-
import { namadaAsset, toDisplayAmount } from "utils";
16+
import { Address } from "types";
17+
import { toDisplayAmount } from "utils";
1718
import {
1819
fetchAccountBalance,
1920
fetchAccounts,
@@ -106,9 +107,9 @@ export const accountBalanceAtom = atomWithQuery<BigNumber>((get) => {
106107
...queryDependentFn(async (): Promise<BigNumber> => {
107108
const balance = transparentBalanceQuery.data
108109
?.filter(({ tokenAddress: ta }) => ta === tokenAddress.data)
109-
.map(({ tokenAddress, minDenomAmount }) => ({
110+
.map(({ tokenAddress, amount }) => ({
110111
token: tokenAddress,
111-
amount: toDisplayAmount(namadaAsset(), new BigNumber(minDenomAmount)),
112+
amount,
112113
}))
113114
.at(0);
114115
return balance ? BigNumber(balance.amount) : BigNumber(0);
@@ -136,18 +137,35 @@ export const disposableSignerAtom = atomWithQuery<GenDisposableSignerResponse>(
136137
}
137138
);
138139

139-
export const transparentBalanceAtom = atomWithQuery<Balance[]>((get) => {
140+
export const transparentBalanceAtom = atomWithQuery<
141+
{ tokenAddress: Address; amount: BigNumber }[]
142+
>((get) => {
140143
const enablePolling = get(shouldUpdateBalanceAtom);
141144
const api = get(indexerApiAtom);
142145
const defaultAccountQuery = get(defaultAccountAtom);
146+
const assetsMapAtom = get(namadaRegistryChainAssetsMapAtom);
143147

144148
const account = defaultAccountQuery.data;
149+
const assetsMap = assetsMapAtom.data;
145150

146151
return {
147152
refetchInterval: enablePolling ? 1000 : false,
148153
queryKey: ["transparent-balance", account],
149154
...queryDependentFn(async () => {
150-
return account ? fetchAccountBalance(api, account) : [];
151-
}, [defaultAccountQuery]),
155+
if (!account || !assetsMap) {
156+
return [];
157+
}
158+
const balance = await fetchAccountBalance(api, account);
159+
160+
return balance
161+
.filter((b) => b.tokenAddress in assetsMap)
162+
.map((item) => ({
163+
tokenAddress: item.tokenAddress,
164+
amount: toDisplayAmount(
165+
assetsMap[item.tokenAddress],
166+
BigNumber(item.minDenomAmount)
167+
),
168+
}));
169+
}, [defaultAccountQuery, assetsMapAtom]),
152170
};
153171
});

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

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import { maspIndexerUrlAtom, rpcUrlAtom } from "atoms/settings";
2020
import { queryDependentFn } from "atoms/utils";
2121
import { isAxiosError } from "axios";
2222
import BigNumber from "bignumber.js";
23+
import * as E from "fp-ts/Either";
2324
import { sequenceT } from "fp-ts/lib/Apply";
2425
import { pipe } from "fp-ts/lib/function";
2526
import * as O from "fp-ts/Option";
2627
import invariant from "invariant";
28+
import * as t from "io-ts";
2729
import { atom, getDefaultStore } from "jotai";
2830
import { atomWithQuery } from "jotai-tanstack-query";
2931
import { atomWithStorage } from "jotai/utils";
@@ -109,12 +111,43 @@ export const viewingKeysAtom = atomWithQuery<
109111
};
110112
});
111113

114+
const NamadilloShieldedBalanceCodec = t.record(
115+
t.string, // Address
116+
t.array(
117+
t.type({
118+
tokenAddress: t.string,
119+
amount: t.string,
120+
})
121+
)
122+
);
123+
112124
export const storageShieldedBalanceAtom = atomWithStorage<
113-
Record<Address, { address: Address; minDenomAmount: string }[]>
114-
>("namadillo:shieldedBalance", {});
125+
Record<Address, { tokenAddress: Address; amount: string }[]>
126+
>(
127+
"namadillo:shieldedBalance",
128+
{},
129+
{
130+
// Invalidation function to ensure the stored data matches the codec
131+
getItem(key, initialValue) {
132+
const storedValue = localStorage.getItem(key);
133+
const maybeValue = NamadilloShieldedBalanceCodec.decode(
134+
JSON.parse(storedValue ?? "{}")
135+
);
136+
137+
return E.isRight(maybeValue) ? maybeValue.right : initialValue;
138+
},
139+
setItem(key, value) {
140+
localStorage.setItem(key, JSON.stringify(value));
141+
},
142+
removeItem(key) {
143+
localStorage.removeItem(key);
144+
},
145+
}
146+
);
115147

116148
export const shieldedSyncProgress = atom(0);
117149

150+
// After changing the stored data type, change the version in the key
118151
export const lastCompletedShieldedSyncAtom = atomWithStorage<
119152
Record<Address, Date | undefined>
120153
>("namadillo:last-shielded-sync", {}, undefined, { getOnInit: true });
@@ -132,11 +165,13 @@ export const shieldedBalanceAtom = atomWithQuery((get) => {
132165
const rpcUrl = get(rpcUrlAtom);
133166
const maspIndexerUrl = get(maspIndexerUrlAtom);
134167
const defaultAccount = get(defaultAccountAtom);
168+
const assetsMapAtom = get(namadaRegistryChainAssetsMapAtom);
135169

136170
const [viewingKey, allViewingKeys] = viewingKeysQuery.data ?? [];
137171
const chainTokens = chainTokensQuery.data?.map((t) => t.address);
138172
const chainId = chainParametersQuery.data?.chainId;
139173
const namTokenAddress = namTokenAddressQuery.data;
174+
const assetsMap = assetsMapAtom.data;
140175

141176
return {
142177
refetchInterval: enablePolling ? 1000 : false,
@@ -148,7 +183,8 @@ export const shieldedBalanceAtom = atomWithQuery((get) => {
148183
!chainTokens ||
149184
!chainId ||
150185
!namTokenAddress ||
151-
!rpcUrl
186+
!rpcUrl ||
187+
!assetsMap
152188
) {
153189
return [];
154190
}
@@ -168,16 +204,24 @@ export const shieldedBalanceAtom = atomWithQuery((get) => {
168204
chainTokens,
169205
chainId
170206
);
171-
172-
const shieldedBalance = response.map(([address, amount]) => ({
173-
address,
174-
minDenomAmount: amount,
175-
}));
207+
const shieldedBalance = response
208+
// Filter out unknown assets
209+
.filter(([address]) => address in assetsMap)
210+
.map(([address, amount]) => {
211+
const asset = assetsMap[address];
212+
return {
213+
tokenAddress: address,
214+
amount: toDisplayAmount(asset, BigNumber(amount)),
215+
};
216+
});
176217

177218
const storage = get(storageShieldedBalanceAtom);
178219
set(storageShieldedBalanceAtom, {
179220
...storage,
180-
[viewingKey.key]: shieldedBalance,
221+
[viewingKey.key]: shieldedBalance.map((balance) => ({
222+
...balance,
223+
amount: balance.amount.toString(),
224+
})),
181225
});
182226

183227
if (defaultAccount.data) {
@@ -198,6 +242,7 @@ export const shieldedBalanceAtom = atomWithQuery((get) => {
198242
chainTokensQuery,
199243
chainParametersQuery,
200244
namTokenAddressQuery,
245+
assetsMapAtom,
201246
]),
202247
};
203248
});
@@ -218,8 +263,10 @@ export const namadaShieldedAssetsAtom = atomWithQuery((get) => {
218263

219264
return mapNamadaAddressesToAssets({
220265
balances:
221-
shieldedBalance?.map((i) => ({ ...i, tokenAddress: i.address })) ??
222-
[],
266+
shieldedBalance?.map((i) => ({
267+
...i,
268+
amount: BigNumber(i.amount),
269+
})) ?? [],
223270
assets: Object.values(chainAssetsMap.data),
224271
});
225272
}, [viewingKeysQuery, chainTokensQuery, chainAssetsMap]),

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

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
import { Balance } from "@namada/indexer-client";
21
import BigNumber from "bignumber.js";
32
import {
43
Address,
54
NamadaAsset,
65
NamadaAssetWithAmount,
76
TokenBalance,
87
} from "types";
9-
import { isNamadaAsset, toDisplayAmount } from "utils";
8+
import { isNamadaAsset } from "utils";
109

1110
export const getTotalDollar = (list?: TokenBalance[]): BigNumber =>
1211
(list ?? []).reduce(
@@ -21,16 +20,16 @@ export const mapNamadaAddressesToAssets = ({
2120
balances,
2221
assets,
2322
}: {
24-
balances: Balance[];
23+
balances: { tokenAddress: Address; amount: BigNumber }[];
2524
assets: NamadaAsset[];
2625
}): Record<Address, NamadaAssetWithAmount> => {
2726
const map: Record<Address, NamadaAssetWithAmount> = {};
28-
balances.forEach((item) => {
29-
const asset = assets.find((asset) => asset.address === item.tokenAddress);
27+
balances.forEach(({ tokenAddress, amount }) => {
28+
const asset = assets.find((asset) => asset.address === tokenAddress);
3029

3130
if (asset) {
32-
map[item.tokenAddress] = {
33-
amount: toDisplayAmount(asset, BigNumber(item.minDenomAmount)),
31+
map[tokenAddress] = {
32+
amount,
3433
asset,
3534
};
3635
}

apps/namadillo/src/atoms/chain/atoms.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export const maspRewardsAtom = atomWithQuery((get) => {
121121
...queryDependentFn(async (): Promise<MaspAssetRewards[]> => {
122122
invariant(chainAssetsMap.data, "No chain assets map");
123123

124-
return await fetchMaspRewards(Object.values(chainAssetsMap.data));
124+
return await fetchMaspRewards(chainAssetsMap.data);
125125
}, [chainAssetsMap]),
126126
};
127127
});

apps/namadillo/src/atoms/chain/services.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ import {
44
NativeToken,
55
Parameters,
66
} from "@namada/indexer-client";
7-
import { getDenomFromIbcTrace } from "atoms/integrations";
87
import BigNumber from "bignumber.js";
9-
import { MaspAssetRewards, NamadaAsset } from "types";
10-
import { unknownAsset } from "utils/assets";
8+
import { Address, MaspAssetRewards, NamadaAsset } from "types";
119
import { getSdkInstance } from "utils/sdk";
1210

1311
export const fetchRpcUrlFromIndexer = async (
@@ -43,16 +41,16 @@ export const clearShieldedContext = async (chainId: string): Promise<void> => {
4341
};
4442

4543
export const fetchMaspRewards = async (
46-
assets: NamadaAsset[]
44+
assets: Record<Address, NamadaAsset>
4745
): Promise<MaspAssetRewards[]> => {
4846
const sdk = await getSdkInstance();
4947
const rewards = await sdk.rpc.globalShieldedRewardForTokens();
48+
5049
const existingRewards: MaspAssetRewards[] = rewards
51-
.filter((r) => r.maxRewardRate > 0)
50+
.filter((r) => r.maxRewardRate > 0 && r.address in assets)
5251
.map((r) => {
53-
const denom = getDenomFromIbcTrace(r.name);
54-
const asset =
55-
assets.find((asset) => asset.base === denom) ?? unknownAsset(denom);
52+
const asset = assets[r.address];
53+
5654
return {
5755
asset,
5856
address: r.address,

apps/namadillo/src/atoms/fees/atoms.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { isPublicKeyRevealed } from "lib/query";
1111
import isEqual from "lodash.isequal";
1212
import { Address } from "types";
1313
import { TxKind } from "types/txKind";
14-
import { isNamadaAsset, toDisplayAmount } from "utils";
14+
import { toDisplayAmount } from "utils";
1515
import { fetchGasEstimate, fetchTokensGasPrice } from "./services";
1616

1717
export type GasPriceTableItem = {
@@ -73,10 +73,7 @@ export const gasPriceTableAtom = atomWithQuery<GasPriceTable>((get) => {
7373
const baseAmount = BigNumber(minDenomAmount);
7474
return {
7575
token,
76-
gasPrice:
77-
asset && isNamadaAsset(asset) ?
78-
toDisplayAmount(asset, baseAmount)
79-
: baseAmount,
76+
gasPrice: toDisplayAmount(asset, baseAmount),
8077
};
8178
})
8279
);

apps/namadillo/src/hooks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from "./useProposalIdParam";
22
export * from "./useSdk";
3+
export * from "./useTransactionFee";

apps/namadillo/src/hooks/useOptimisticTransferUpdate.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,17 @@ export const useOptimisticTransferUpdate = () => {
1717
const [viewingKeyData] = useAtomValue(viewingKeysAtom).data ?? [];
1818
const viewingKey = viewingKeyData?.key;
1919

20-
return (token: Address, incrementBaseDenomAmount: BigNumber) => {
20+
return (token: Address, incrementAmount: BigNumber) => {
2121
if (!viewingKey) {
2222
return;
2323
}
2424
setStorageShieldedBalance((storage) => ({
2525
...storage,
2626
[viewingKey]: storage[viewingKey].map((item) =>
27-
item.address === token ?
27+
item.tokenAddress === token ?
2828
{
2929
...item,
30-
minDenomAmount: sum(item.minDenomAmount, incrementBaseDenomAmount),
30+
amount: sum(item.amount, incrementAmount),
3131
}
3232
: item
3333
),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./useTransactionFee";

0 commit comments

Comments
 (0)