import { getCryptoAssetsStore } from "@ledgerhq/ledger-wallet-framework/cryptoAssetsStore"; import { AccountLike } from "@ledgerhq/types-live"; import { ACCOUNT_SIZE, ExtensionType, getAccountLen, getAccountTypeOfMintType, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, } from "@solana/spl-token"; import type { PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import { TransferFeeConfigExt } from "../network/chain/account/tokenExtensions"; import type { ParsedOnChainMintWithInfo } from "../network/chain/web3"; import { PARSED_PROGRAMS } from "../network/chain/program/constants"; import { SolanaTokenAccount, SolanaTokenAccountExtensions, SolanaTokenProgram, TransferFeeCalculated, } from "../types"; export async function tokenIsListedOnLedger(currencyId: string, mint: string): Promise { const token = await getCryptoAssetsStore().findTokenByAddressInCurrency(mint, currencyId); return token?.type === "TokenCurrency"; } export function isTokenAccountFrozen(account: AccountLike): boolean { return account.type === "TokenAccount" && (account as SolanaTokenAccount)?.state === "frozen"; } export function getTokenExtensions(account: AccountLike) { return account.type === "TokenAccount" ? (account as SolanaTokenAccount)?.extensions : undefined; } export function hasProblematicExtension(extensions: SolanaTokenAccountExtensions) { return ["transferFee", "transferHook"].some(extension => extension in extensions); } export function getTokenAccountProgramId(program: SolanaTokenProgram): PublicKey { return program === PARSED_PROGRAMS.SPL_TOKEN_2022 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID; } export function isTokenProgram(program: string): boolean { return program === PARSED_PROGRAMS.SPL_TOKEN || program === PARSED_PROGRAMS.SPL_TOKEN_2022; } // Only mint extensions that add an account-side extension affect ATA size. const PARSED_MINT_EXTENSION_TO_TYPE: Record = { transferFeeConfig: ExtensionType.TransferFeeConfig, confidentialTransferMint: ExtensionType.ConfidentialTransferMint, nonTransferable: ExtensionType.NonTransferable, transferHook: ExtensionType.TransferHook, }; export function getAtaDataLengthForMint(mint: ParsedOnChainMintWithInfo): number { if (mint.onChainAcc.data.program !== PARSED_PROGRAMS.SPL_TOKEN_2022) { return ACCOUNT_SIZE; } // The SPL Associated Token Account program always initializes ImmutableOwner // on Token-2022 ATAs, so every Token-2022 ATA — even when the mint declares // no extensions — is larger than the classic 165-byte account. const accountExtensions: ExtensionType[] = [ExtensionType.ImmutableOwner]; for (const ext of mint.info.extensions ?? []) { const mintExtensionType = PARSED_MINT_EXTENSION_TO_TYPE[ext.extension]; if (mintExtensionType !== undefined) { accountExtensions.push(getAccountTypeOfMintType(mintExtensionType)); } } return getAccountLen(accountExtensions); } export function bpsToPercent(bps: number): number { return BigNumber(bps).div(100).toNumber(); } // https://spl.solana.com/token-2022/extensions#transfer-fees export function calculateToken2022TransferFees({ transferAmount, transferFeeConfigState, currentEpoch, }: { transferAmount: number; transferFeeConfigState: Pick< TransferFeeConfigExt["state"], "newerTransferFee" | "olderTransferFee" >; currentEpoch: number; }): TransferFeeCalculated { const { newerTransferFee, olderTransferFee } = transferFeeConfigState; const transferFeeConfig = currentEpoch >= newerTransferFee.epoch ? newerTransferFee : olderTransferFee; const { maximumFee, transferFeeBasisPoints } = transferFeeConfig; const feePercent = BigNumber(bpsToPercent(transferFeeBasisPoints)); let transferAmountIncludingFee = BigNumber(transferAmount) .div(BigNumber(1).minus(feePercent.div(100))) .decimalPlaces(0, BigNumber.ROUND_UP) .toNumber(); let transferFee = transferAmountIncludingFee - transferAmount; if (transferFee > maximumFee) { transferFee = maximumFee; transferAmountIncludingFee = transferAmount + maximumFee; } return { feePercent: feePercent.toNumber(), maxTransferFee: maximumFee, transferFee, feeBps: transferFeeBasisPoints, transferAmountIncludingFee, transferAmountExcludingFee: BigNumber(transferAmount) .minus(transferFee) .decimalPlaces(0, BigNumber.ROUND_UP) .toNumber(), }; }