Skip to content

Commit

Permalink
feat: first step - refactoring accounts and tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrorezende committed Dec 30, 2024
1 parent bfcf1c7 commit 2fbd8b1
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 107 deletions.
20 changes: 7 additions & 13 deletions apps/namadillo/src/atoms/accounts/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<readonly Account[]>((get) => {
const isExtensionConnected = get(namadaExtensionConnectedAtom);
Expand Down Expand Up @@ -70,12 +71,11 @@ export const allDefaultAccountsAtom = atomWithQuery<Account[]>((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);
},
};
});

Expand Down Expand Up @@ -107,13 +107,7 @@ export const disposableSignerAtom = atomWithQuery<GenDisposableSignerResponse>(
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();
},
};
}
Expand Down
36 changes: 13 additions & 23 deletions apps/namadillo/src/atoms/balance/atoms.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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,
Expand All @@ -31,26 +33,17 @@ 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
*/
const toDatedKeypair = async (
api: DefaultApi,
{ viewingKey, source, timestamp }: Account
): Promise<DatedViewingKey> => {
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);
Expand Down Expand Up @@ -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))
);
Expand Down
54 changes: 2 additions & 52 deletions apps/namadillo/src/atoms/balance/functions.ts
Original file line number Diff line number Diff line change
@@ -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 }[],
Expand Down
40 changes: 40 additions & 0 deletions apps/namadillo/src/lib/accounts.ts
Original file line number Diff line number Diff line change
@@ -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<readonly Account[]> => {
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);
68 changes: 68 additions & 0 deletions apps/namadillo/src/lib/tokens.ts
Original file line number Diff line number Diff line change
@@ -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),
};
};
Original file line number Diff line number Diff line change
@@ -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<readonly Account[]> => {
const namada = await new NamadaKeychain().get();
const result = await namada?.accounts();
return result || [];
return await getKeychainAccounts();
};

export const fetchDefaultAccount = async (): Promise<Account | undefined> => {
const namada = await new NamadaKeychain().get();
return await namada?.defaultAccount();
return await getDefaultTransparentAccount();
};

export const fetchNamAccountBalance = async (
Expand All @@ -22,18 +22,7 @@ export const fetchNamAccountBalance = async (
): Promise<BigNumber> => {
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 (
Expand Down
5 changes: 5 additions & 0 deletions apps/namadillo/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 2fbd8b1

Please sign in to comment.