diff --git a/apps/namadillo/src/atoms/accounts/atoms.ts b/apps/namadillo/src/atoms/accounts/atoms.ts index c4c0af8816..3b0f386693 100644 --- a/apps/namadillo/src/atoms/accounts/atoms.ts +++ b/apps/namadillo/src/atoms/accounts/atoms.ts @@ -11,12 +11,13 @@ import { queryDependentFn } from "atoms/utils"; import BigNumber from "bignumber.js"; import { NamadaKeychain } from "hooks/useNamadaKeychain"; import { atomWithMutation, atomWithQuery } from "jotai-tanstack-query"; +import { getDisposableKeypair } from "lib/accounts"; import { fetchAccountBalance, fetchAccounts, fetchDefaultAccount, fetchNamAccountBalance, -} from "./services"; +} from "services/accounts"; export const accountsAtom = atomWithQuery((get) => { const isExtensionConnected = get(namadaExtensionConnectedAtom); @@ -70,12 +71,11 @@ export const allDefaultAccountsAtom = atomWithQuery((get) => { }); export const updateDefaultAccountAtom = atomWithMutation(() => { - const namadaPromise = new NamadaKeychain().get(); return { - mutationFn: (address: string) => - namadaPromise.then((injectedNamada) => - injectedNamada?.updateDefaultAccount(address) - ), + mutationFn: async (address: string) => { + const namada = await new NamadaKeychain().get(); + namada?.updateDefaultAccount(address); + }, }; }); @@ -107,13 +107,7 @@ export const disposableSignerAtom = atomWithQuery( enabled: false, queryKey: ["disposable-signer", isExtensionConnected], queryFn: async () => { - const namada = await new NamadaKeychain().get(); - const res = await namada?.genDisposableKeypair(); - if (!res) { - throw new Error("Failed to generate disposable signer"); - } - - return res; + return await getDisposableKeypair(); }, }; } diff --git a/apps/namadillo/src/atoms/balance/atoms.ts b/apps/namadillo/src/atoms/balance/atoms.ts index f4f387591f..7a73b57193 100644 --- a/apps/namadillo/src/atoms/balance/atoms.ts +++ b/apps/namadillo/src/atoms/balance/atoms.ts @@ -1,6 +1,6 @@ import { DefaultApi } from "@namada/indexer-client"; import { SdkEvents } from "@namada/sdk/web"; -import { Account, AccountType, DatedViewingKey } from "@namada/types"; +import { Account, DatedViewingKey } from "@namada/types"; import { accountsAtom, defaultAccountAtom, @@ -18,8 +18,10 @@ import { maspIndexerUrlAtom, rpcUrlAtom } from "atoms/settings"; import { queryDependentFn } from "atoms/utils"; import { isAxiosError } from "axios"; import BigNumber from "bignumber.js"; +import invariant from "invariant"; import { atomWithQuery } from "jotai-tanstack-query"; -import { AddressWithAsset } from "types"; +import { filterShieldedAccounts, findAccountByAlias } from "lib/accounts"; +import { TokenBalance } from "types"; import { mapNamadaAddressesToAssets, mapNamadaAssetsToTokenBalances, @@ -31,11 +33,6 @@ import { ShieldedSyncEmitter, } from "./services"; -export type TokenBalance = AddressWithAsset & { - amount: BigNumber; - dollar?: BigNumber; -}; - /** Gets the viewing key and its birthday timestamp if it's a generated key */ @@ -43,14 +40,10 @@ const toDatedKeypair = async ( api: DefaultApi, { viewingKey, source, timestamp }: Account ): Promise => { - if (typeof viewingKey === "undefined") { - throw new Error("Viewing key not found"); - } - if (typeof timestamp === "undefined") { - throw new Error("Timestamp not found"); - } - let height = 0; + invariant(typeof viewingKey !== "undefined", "Viewing key not found"); + invariant(typeof timestamp !== "undefined", "Timestamp not found"); + let height = 0; if (source === "generated") { try { height = await fetchBlockHeightByTimestamp(api, timestamp); @@ -80,21 +73,18 @@ export const viewingKeysAtom = atomWithQuery< return { queryKey: ["viewing-keys", accountsQuery.data, defaultAccountQuery.data], ...queryDependentFn(async () => { - const shieldedAccounts = accountsQuery.data!.filter( - (a) => a.type === AccountType.ShieldedKeys - ); - const defaultShieldedAccount = shieldedAccounts.find( - (a) => a.alias === defaultAccountQuery.data?.alias + const shieldedAccounts = filterShieldedAccounts(accountsQuery.data!); + const defaultShieldedAccount = findAccountByAlias( + shieldedAccounts, + defaultAccountQuery.data?.alias ); - if (!defaultShieldedAccount) { - throw new Error("Default shielded account not found"); - } - + invariant(defaultShieldedAccount, "Default shielded account not found"); const defaultViewingKey = await toDatedKeypair( api, defaultShieldedAccount ); + const viewingKeys = await Promise.all( shieldedAccounts.map(toDatedKeypair.bind(null, api)) ); diff --git a/apps/namadillo/src/atoms/balance/functions.ts b/apps/namadillo/src/atoms/balance/functions.ts index 79883f7101..e94c411520 100644 --- a/apps/namadillo/src/atoms/balance/functions.ts +++ b/apps/namadillo/src/atoms/balance/functions.ts @@ -1,58 +1,8 @@ import { IbcToken, NativeToken } from "@namada/indexer-client"; import { mapCoinsToAssets } from "atoms/integrations"; import BigNumber from "bignumber.js"; -import { DenomTrace } from "cosmjs-types/ibc/applications/transfer/v1/transfer"; -import { AddressWithAssetAndAmountMap } from "types"; -import { isNamadaAsset } from "utils"; -import { TokenBalance } from "./atoms"; - -/** - * Sum the dollar amount of a list of tokens - * @param tokens - * @returns The total of dollars, or `undefined` if at least one token has `dollar: undefined` - */ -export const sumDollars = (tokens: TokenBalance[]): BigNumber | undefined => { - let sum = new BigNumber(0); - for (let i = 0; i < tokens.length; i++) { - const { dollar } = tokens[i]; - if (!dollar) { - return undefined; - } - sum = sum.plus(dollar); - } - return sum; -}; - -export const getTotalDollar = (list?: TokenBalance[]): BigNumber | undefined => - sumDollars(list ?? []); - -export const getTotalNam = (list?: TokenBalance[]): BigNumber => - list?.find((i) => isNamadaAsset(i.asset))?.amount ?? new BigNumber(0); - -const tnamAddressToDenomTrace = ( - address: string, - chainTokens: (NativeToken | IbcToken)[] -): DenomTrace | undefined => { - const token = chainTokens.find((entry) => entry.address === address); - const trace = token && "trace" in token ? token.trace : undefined; - - // If no trace, the token is NAM, but return undefined because we only care - // about IBC tokens here - if (typeof trace === "undefined") { - return undefined; - } - - const separatorIndex = trace.lastIndexOf("/"); - - if (separatorIndex === -1) { - return undefined; - } - - return { - path: trace.substring(0, separatorIndex), - baseDenom: trace.substring(separatorIndex + 1), - }; -}; +import { tnamAddressToDenomTrace } from "lib/tokens"; +import { AddressWithAssetAndAmountMap, TokenBalance } from "types"; export const mapNamadaAddressesToAssets = async ( balances: { address: string; minDenomAmount: BigNumber }[], diff --git a/apps/namadillo/src/lib/accounts.ts b/apps/namadillo/src/lib/accounts.ts new file mode 100644 index 0000000000..8cc112f677 --- /dev/null +++ b/apps/namadillo/src/lib/accounts.ts @@ -0,0 +1,40 @@ +import { + Account, + AccountType, + GenDisposableSignerResponse, +} from "@namada/types"; +import { NamadaKeychain } from "hooks/useNamadaKeychain"; + +export const getDefaultTransparentAccount = async (): Promise< + Account | undefined +> => { + const namada = await new NamadaKeychain().get(); + return await namada?.defaultAccount(); +}; + +export const getDisposableKeypair = async (): Promise< + GenDisposableSignerResponse | never +> => { + const namada = await new NamadaKeychain().get(); + const response = await namada?.genDisposableKeypair(); + if (!response) { + throw new Error("Failed to generate disposable signer"); + } + return response; +}; + +// TODO: ?? should it be approved accounts here? +export const getKeychainAccounts = async (): Promise => { + const namada = await new NamadaKeychain().get(); + const result = await namada?.accounts(); + return result || []; +}; + +export const findAccountByAlias = ( + accounts: Account[], + alias: string | undefined +): Account | undefined => accounts.find((a) => a.alias === alias); + +export const filterShieldedAccounts = ( + accounts: readonly Account[] +): Account[] => accounts.filter((a) => a.type === AccountType.ShieldedKeys); diff --git a/apps/namadillo/src/lib/tokens.ts b/apps/namadillo/src/lib/tokens.ts new file mode 100644 index 0000000000..da4922bc3e --- /dev/null +++ b/apps/namadillo/src/lib/tokens.ts @@ -0,0 +1,68 @@ +import { Balance, IbcToken, NativeToken } from "@namada/indexer-client"; +import BigNumber from "bignumber.js"; +import { DenomTrace } from "cosmjs-types/ibc/applications/transfer/v1/transfer"; +import { Address, TokenBalance } from "types"; +import { isNamadaAsset, namadaAsset, toDisplayAmount } from "utils"; + +export const getNamTokenAmount = ( + balances: Balance[], + namTokenAddress: Address +): BigNumber => { + const balance = balances + .filter(({ tokenAddress: ta }) => ta === namTokenAddress) + .map(({ tokenAddress, minDenomAmount }) => { + return { + token: tokenAddress, + amount: toDisplayAmount(namadaAsset(), new BigNumber(minDenomAmount)), + }; + }) + .at(0); + return balance ? BigNumber(balance.amount) : BigNumber(0); +}; + +/** + * Sum the dollar amount of a list of tokens + * @param tokens + * @returns The total of dollars, or `undefined` if at least one token has `dollar: undefined` + */ +export const sumDollars = (tokens: TokenBalance[]): BigNumber | undefined => { + let sum = new BigNumber(0); + for (let i = 0; i < tokens.length; i++) { + const { dollar } = tokens[i]; + if (!dollar) { + return undefined; + } + sum = sum.plus(dollar); + } + return sum; +}; + +export const getTotalDollar = (list?: TokenBalance[]): BigNumber | undefined => + sumDollars(list ?? []); + +export const getTotalNam = (list?: TokenBalance[]): BigNumber => + list?.find((i) => isNamadaAsset(i.asset))?.amount ?? new BigNumber(0); + +export const tnamAddressToDenomTrace = ( + address: string, + chainTokens: (NativeToken | IbcToken)[] +): DenomTrace | undefined => { + const token = chainTokens.find((entry) => entry.address === address); + const trace = token && "trace" in token ? token.trace : undefined; + + // If no trace, the token is NAM, but return undefined because we only care + // about IBC tokens here + if (typeof trace === "undefined") { + return undefined; + } + + const separatorIndex = trace.lastIndexOf("/"); + if (separatorIndex === -1) { + return undefined; + } + + return { + path: trace.substring(0, separatorIndex), + baseDenom: trace.substring(separatorIndex + 1), + }; +}; diff --git a/apps/namadillo/src/atoms/accounts/services.ts b/apps/namadillo/src/services/accounts.ts similarity index 54% rename from apps/namadillo/src/atoms/accounts/services.ts rename to apps/namadillo/src/services/accounts.ts index d7372131de..201ca56b58 100644 --- a/apps/namadillo/src/atoms/accounts/services.ts +++ b/apps/namadillo/src/services/accounts.ts @@ -1,18 +1,18 @@ import { Balance, DefaultApi } from "@namada/indexer-client"; import { Account } from "@namada/types"; import BigNumber from "bignumber.js"; -import { NamadaKeychain } from "hooks/useNamadaKeychain"; -import { namadaAsset, toDisplayAmount } from "utils"; +import { + getDefaultTransparentAccount, + getKeychainAccounts, +} from "lib/accounts"; +import { getNamTokenAmount } from "lib/tokens"; export const fetchAccounts = async (): Promise => { - const namada = await new NamadaKeychain().get(); - const result = await namada?.accounts(); - return result || []; + return await getKeychainAccounts(); }; export const fetchDefaultAccount = async (): Promise => { - const namada = await new NamadaKeychain().get(); - return await namada?.defaultAccount(); + return await getDefaultTransparentAccount(); }; export const fetchNamAccountBalance = async ( @@ -22,18 +22,7 @@ export const fetchNamAccountBalance = async ( ): Promise => { if (!account) return BigNumber(0); const balancesResponse = await api.apiV1AccountAddressGet(account.address); - - const balance = balancesResponse.data - .filter(({ tokenAddress: ta }) => ta === tokenAddress) - .map(({ tokenAddress, minDenomAmount }) => { - return { - token: tokenAddress, - amount: toDisplayAmount(namadaAsset(), new BigNumber(minDenomAmount)), - }; - }) - .at(0); - - return balance ? BigNumber(balance.amount) : BigNumber(0); + return getNamTokenAmount(balancesResponse.data, tokenAddress); }; export const fetchAccountBalance = async ( diff --git a/apps/namadillo/src/types.ts b/apps/namadillo/src/types.ts index 6387b47b5d..2c54d89cd2 100644 --- a/apps/namadillo/src/types.ts +++ b/apps/namadillo/src/types.ts @@ -213,6 +213,11 @@ export type AddressWithAssetAndAmountMap = Record< AddressWithAssetAndAmount >; +export type TokenBalance = AddressWithAsset & { + amount: BigNumber; + dollar?: BigNumber; +}; + export enum TransferStep { Sign = "sign", ZkProof = "zk-proof",