UNPKG

@solana/spl-token

Version:
197 lines (184 loc) 6.65 kB
import { struct, u32, u8 } from '@solana/buffer-layout'; import { publicKey, u64 } from '@solana/buffer-layout-utils'; import type { AccountInfo, Commitment, Connection, PublicKey } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from '../constants.js'; import { TokenAccountNotFoundError, TokenInvalidAccountError, TokenInvalidAccountOwnerError, TokenInvalidAccountSizeError, } from '../errors.js'; import { ACCOUNT_TYPE_SIZE, AccountType } from '../extensions/accountType.js'; import type { ExtensionType } from '../extensions/extensionType.js'; import { getAccountLen } from '../extensions/extensionType.js'; import { MULTISIG_SIZE } from './multisig.js'; /** Information about a token account */ export interface Account { /** Address of the account */ address: PublicKey; /** Mint associated with the account */ mint: PublicKey; /** Owner of the account */ owner: PublicKey; /** Number of tokens the account holds */ amount: bigint; /** Authority that can transfer tokens from the account */ delegate: PublicKey | null; /** Number of tokens the delegate is authorized to transfer */ delegatedAmount: bigint; /** True if the account is initialized */ isInitialized: boolean; /** True if the account is frozen */ isFrozen: boolean; /** True if the account is a native token account */ isNative: boolean; /** * If the account is a native token account, it must be rent-exempt. The rent-exempt reserve is the amount that must * remain in the balance until the account is closed. */ rentExemptReserve: bigint | null; /** Optional authority to close the account */ closeAuthority: PublicKey | null; tlvData: Buffer; } /** Token account state as stored by the program */ export enum AccountState { Uninitialized = 0, Initialized = 1, Frozen = 2, } /** Token account as stored by the program */ export interface RawAccount { mint: PublicKey; owner: PublicKey; amount: bigint; delegateOption: 1 | 0; delegate: PublicKey; state: AccountState; isNativeOption: 1 | 0; isNative: bigint; delegatedAmount: bigint; closeAuthorityOption: 1 | 0; closeAuthority: PublicKey; } /** Buffer layout for de/serializing a token account */ export const AccountLayout = struct<RawAccount>([ publicKey('mint'), publicKey('owner'), u64('amount'), u32('delegateOption'), publicKey('delegate'), u8('state'), u32('isNativeOption'), u64('isNative'), u64('delegatedAmount'), u32('closeAuthorityOption'), publicKey('closeAuthority'), ]); /** Byte length of a token account */ export const ACCOUNT_SIZE = AccountLayout.span; /** * Retrieve information about a token account * * @param connection Connection to use * @param address Token account * @param commitment Desired level of commitment for querying the state * @param programId SPL Token program account * * @return Token account information */ export async function getAccount( connection: Connection, address: PublicKey, commitment?: Commitment, programId = TOKEN_PROGRAM_ID, ): Promise<Account> { const info = await connection.getAccountInfo(address, commitment); return unpackAccount(address, info, programId); } /** * Retrieve information about multiple token accounts in a single RPC call * * @param connection Connection to use * @param addresses Token accounts * @param commitment Desired level of commitment for querying the state * @param programId SPL Token program account * * @return Token account information */ export async function getMultipleAccounts( connection: Connection, addresses: PublicKey[], commitment?: Commitment, programId = TOKEN_PROGRAM_ID, ): Promise<Account[]> { const infos = await connection.getMultipleAccountsInfo(addresses, commitment); return addresses.map((address, i) => unpackAccount(address, infos[i], programId)); } /** Get the minimum lamport balance for a base token account to be rent exempt * * @param connection Connection to use * @param commitment Desired level of commitment for querying the state * * @return Amount of lamports required */ export async function getMinimumBalanceForRentExemptAccount( connection: Connection, commitment?: Commitment, ): Promise<number> { return await getMinimumBalanceForRentExemptAccountWithExtensions(connection, [], commitment); } /** Get the minimum lamport balance for a rent-exempt token account with extensions * * @param connection Connection to use * @param commitment Desired level of commitment for querying the state * * @return Amount of lamports required */ export async function getMinimumBalanceForRentExemptAccountWithExtensions( connection: Connection, extensions: ExtensionType[], commitment?: Commitment, ): Promise<number> { const accountLen = getAccountLen(extensions); return await connection.getMinimumBalanceForRentExemption(accountLen, commitment); } /** * Unpack a token account * * @param address Token account * @param info Token account data * @param programId SPL Token program account * * @return Unpacked token account */ export function unpackAccount( address: PublicKey, info: AccountInfo<Buffer> | null, programId = TOKEN_PROGRAM_ID, ): Account { if (!info) throw new TokenAccountNotFoundError(); if (!info.owner.equals(programId)) throw new TokenInvalidAccountOwnerError(); if (info.data.length < ACCOUNT_SIZE) throw new TokenInvalidAccountSizeError(); const rawAccount = AccountLayout.decode(info.data.slice(0, ACCOUNT_SIZE)); let tlvData = Buffer.alloc(0); if (info.data.length > ACCOUNT_SIZE) { if (info.data.length === MULTISIG_SIZE) throw new TokenInvalidAccountSizeError(); if (info.data[ACCOUNT_SIZE] != AccountType.Account) throw new TokenInvalidAccountError(); tlvData = info.data.slice(ACCOUNT_SIZE + ACCOUNT_TYPE_SIZE); } return { address, mint: rawAccount.mint, owner: rawAccount.owner, amount: rawAccount.amount, delegate: rawAccount.delegateOption ? rawAccount.delegate : null, delegatedAmount: rawAccount.delegatedAmount, isInitialized: rawAccount.state !== AccountState.Uninitialized, isFrozen: rawAccount.state === AccountState.Frozen, isNative: !!rawAccount.isNativeOption, rentExemptReserve: rawAccount.isNativeOption ? rawAccount.isNative : null, closeAuthority: rawAccount.closeAuthorityOption ? rawAccount.closeAuthority : null, tlvData, }; }