@saberhq/token-utils
Version:
Token-related math and transaction utilities for Solana.
302 lines (285 loc) • 6.83 kB
text/typescript
import type {
AugmentedProvider,
Provider,
PublicKey,
TransactionEnvelope,
} from "@saberhq/solana-contrib";
import { SolanaAugmentedProvider } from "@saberhq/solana-contrib";
import type { MintInfo } from "@solana/spl-token";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import type { Signer } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";
import { getATAAddress, getATAAddresses } from "./ata.js";
import { createMintInstructions, DEFAULT_TOKEN_DECIMALS } from "./common.js";
import type { TokenAmount, TokenInfo } from "./index.js";
import { SPLToken } from "./index.js";
import { getOrCreateATA, getOrCreateATAs } from "./instructions/ata.js";
import type { TokenAccountData } from "./layout.js";
import { deserializeAccount, deserializeMint } from "./layout.js";
import { Token } from "./token.js";
/**
* Augmented provider with token utilities.
*/
export class TokenAugmentedProvider
extends SolanaAugmentedProvider
implements AugmentedProvider
{
constructor(provider: Provider) {
super(provider);
}
/**
* Creates a transaction to create a {@link Token}.
*/
async createTokenTX({
mintKP = Keypair.generate(),
authority = this.walletKey,
decimals = DEFAULT_TOKEN_DECIMALS,
}: {
mintKP?: Signer;
authority?: PublicKey;
decimals?: number;
} = {}): Promise<{ token: Token; tx: TransactionEnvelope }> {
const instructions = await createMintInstructions(
this.provider,
authority,
mintKP.publicKey,
decimals,
);
return {
token: Token.fromMint(mintKP.publicKey, decimals),
tx: this.newTX(instructions, [mintKP]),
};
}
/**
* Transfers tokens from the provider's ATA to a `TokenAccount`.
*/
async transferTo({
amount,
source,
destination,
}: {
amount: TokenAmount;
source?: PublicKey;
destination: PublicKey;
}): Promise<TransactionEnvelope> {
const txEnv = this.newTX();
if (!source) {
const sourceATA = await this.getOrCreateATA({
mint: amount.token.mintAccount,
});
txEnv.append(sourceATA.instruction);
source = sourceATA.address;
}
txEnv.append(
SPLToken.createTransferInstruction(
TOKEN_PROGRAM_ID,
source,
destination,
this.walletKey,
[],
amount.toU64(),
),
);
return txEnv;
}
/**
* Transfers tokens to a recipient's ATA.
*/
async transfer({
amount,
source,
to,
}: {
amount: TokenAmount;
source?: PublicKey;
/**
* Recipient of the tokens. This should not be a token account.
*/
to: PublicKey;
}): Promise<TransactionEnvelope> {
const toATA = await this.getOrCreateATA({
mint: amount.token.mintAccount,
owner: to,
});
const txEnv = await this.transferTo({
amount,
source,
destination: toATA.address,
});
txEnv.prepend(toATA.instruction);
return txEnv;
}
/**
* Creates a {@link Token}.
*/
async createToken({
mintKP = Keypair.generate(),
authority = this.walletKey,
decimals = DEFAULT_TOKEN_DECIMALS,
}: {
mintKP?: Signer;
authority?: PublicKey;
decimals?: number;
} = {}): Promise<Token> {
const { token, tx } = await this.createTokenTX({
mintKP,
authority,
decimals,
});
await tx.confirm();
return token;
}
/**
* Gets an ATA address.
* @returns
*/
async getATAAddress({
mint,
owner = this.walletKey,
}: {
mint: PublicKey;
owner?: PublicKey;
}) {
return await getATAAddress({ mint, owner });
}
/**
* Gets an ATA address.
* @returns
*/
async getATAAddresses<K extends string>({
mints,
owner = this.walletKey,
}: {
mints: {
[mint in K]: PublicKey;
};
owner?: PublicKey;
}) {
return await getATAAddresses({ mints, owner });
}
/**
* Gets an ATA, creating it if it doesn't exist.
* @returns
*/
async getOrCreateATA({
mint,
owner = this.walletKey,
}: {
mint: PublicKey;
owner?: PublicKey;
}) {
return await getOrCreateATA({ provider: this.provider, mint, owner });
}
/**
* Get or create multiple ATAs.
* @returns
*/
async getOrCreateATAs<K extends string>({
mints,
owner = this.walletKey,
}: {
mints: {
[mint in K]: PublicKey;
};
owner?: PublicKey;
}) {
return await getOrCreateATAs({ provider: this.provider, mints, owner });
}
/**
* Loads a token from the blockchain, only if the decimals are not provided.
* @param mint
* @returns
*/
async loadToken(
mint: PublicKey,
info: Partial<Omit<TokenInfo, "address">> = {},
): Promise<Token | null> {
return Token.load(this.provider.connection, mint, info);
}
/**
* Mints tokens to a token account.
* @param mint
* @returns
*/
mintToAccount({
amount,
destination,
}: {
amount: TokenAmount;
destination: PublicKey;
}): TransactionEnvelope {
return this.newTX([
SPLToken.createMintToInstruction(
TOKEN_PROGRAM_ID,
amount.token.mintAccount,
destination,
this.walletKey,
[],
amount.toU64(),
),
]);
}
/**
* Mints tokens to the ATA of the `to` account.
* @param amount The amount of tokens to mint.
* @param to The owner of the ATA that may be created.
* @returns
*/
async mintTo({
amount,
to = this.walletKey,
}: {
amount: TokenAmount;
to?: PublicKey;
}): Promise<TransactionEnvelope> {
const toATA = await this.getOrCreateATA({
mint: amount.token.mintAccount,
owner: to,
});
const txEnv = this.mintToAccount({
amount,
destination: toATA.address,
});
txEnv.prepend(toATA.instruction);
return txEnv;
}
/**
* Fetches a mint.
* @param address
* @returns
*/
async fetchMint(address: PublicKey): Promise<MintInfo | null> {
const accountInfo = await this.getAccountInfo(address);
if (accountInfo === null) {
return null;
}
return deserializeMint(accountInfo.accountInfo.data);
}
/**
* Fetches a token account.
* @param address
* @returns
*/
async fetchTokenAccount(
address: PublicKey,
): Promise<TokenAccountData | null> {
const tokenAccountInfo = await this.getAccountInfo(address);
if (tokenAccountInfo === null) {
return null;
}
return deserializeAccount(tokenAccountInfo.accountInfo.data);
}
/**
* Fetches an ATA.
* @param mint
* @param owner
* @returns
*/
async fetchATA(
mint: PublicKey,
owner: PublicKey = this.walletKey,
): Promise<TokenAccountData | null> {
const taAddress = await getATAAddress({ mint, owner });
return await this.fetchTokenAccount(taAddress);
}
}