Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions clients/js/src/createMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,36 @@ export type CreateMintInstructionPlanInput = {
};

type CreateMintInstructionPlanConfig = {
systemProgramAddress?: Address;
tokenProgramAddress?: Address;
systemProgram?: Address;
tokenProgram?: Address;
};

export function createMintInstructionPlan(
params: CreateMintInstructionPlanInput,
input: CreateMintInstructionPlanInput,
config?: CreateMintInstructionPlanConfig
): InstructionPlan {
return sequentialInstructionPlan([
getCreateAccountInstruction(
{
payer: params.payer,
newAccount: params.newMint,
lamports: params.mintAccountLamports ?? MINIMUM_BALANCE_FOR_MINT,
payer: input.payer,
newAccount: input.newMint,
lamports: input.mintAccountLamports ?? MINIMUM_BALANCE_FOR_MINT,
space: getMintSize(),
programAddress: config?.tokenProgramAddress ?? TOKEN_PROGRAM_ADDRESS,
programAddress: config?.tokenProgram ?? TOKEN_PROGRAM_ADDRESS,
},
{
programAddress: config?.systemProgramAddress,
programAddress: config?.systemProgram,
}
),
getInitializeMint2Instruction(
{
mint: params.newMint.address,
decimals: params.decimals,
mintAuthority: params.mintAuthority,
freezeAuthority: params.freezeAuthority,
mint: input.newMint.address,
decimals: input.decimals,
mintAuthority: input.mintAuthority,
freezeAuthority: input.freezeAuthority,
},
{
programAddress: config?.tokenProgramAddress,
programAddress: config?.tokenProgram,
}
),
]);
Expand Down
1 change: 1 addition & 0 deletions clients/js/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './generated';
export * from './createMint';
export * from './mintToATA';
98 changes: 98 additions & 0 deletions clients/js/src/mintToATA.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
InstructionPlan,
sequentialInstructionPlan,
Address,
TransactionSigner,
} from '@solana/kit';
import {
findAssociatedTokenPda,
getCreateAssociatedTokenIdempotentInstruction,
getMintToCheckedInstruction,
TOKEN_PROGRAM_ADDRESS,
} from './generated';

type MintToATAInstructionPlanInput = {
/** Funding account (must be a system account). */
payer: TransactionSigner;
/** Associated token account address to mint to.
* Will be created if it does not already exist.
* Note: Use {@link mintToATAInstructionPlanAsync} instead to derive this automatically.
* Note: Use {@link findAssociatedTokenPda} to derive the associated token account address.
*/
ata: Address;
/** Wallet address for the associated token account. */
owner: Address;
/** The token mint for the associated token account. */
mint: Address;
/** The mint's minting authority or its multisignature account. */
mintAuthority: Address | TransactionSigner;
/** The amount of new tokens to mint. */
amount: number | bigint;
/** Expected number of base 10 digits to the right of the decimal place. */
decimals: number;
multiSigners?: Array<TransactionSigner>;
};

type MintToATAInstructionPlanConfig = {
systemProgram?: Address;
tokenProgram?: Address;
associatedTokenProgram?: Address;
};

export function mintToATAInstructionPlan(
input: MintToATAInstructionPlanInput,
config?: MintToATAInstructionPlanConfig
): InstructionPlan {
return sequentialInstructionPlan([
getCreateAssociatedTokenIdempotentInstruction(
{
payer: input.payer,
ata: input.ata,
owner: input.owner,
mint: input.mint,
systemProgram: config?.systemProgram,
tokenProgram: config?.tokenProgram,
},
{
programAddress: config?.associatedTokenProgram,
}
),
// mint to this token account
getMintToCheckedInstruction(
{
mint: input.mint,
token: input.ata,
mintAuthority: input.mintAuthority,
amount: input.amount,
decimals: input.decimals,
multiSigners: input.multiSigners,
},
{
programAddress: config?.tokenProgram,
}
),
]);
}

type MintToATAInstructionPlanAsyncInput = Omit<
MintToATAInstructionPlanInput,
'ata'
>;

export async function mintToATAInstructionPlanAsync(
input: MintToATAInstructionPlanAsyncInput,
config?: MintToATAInstructionPlanConfig
): Promise<InstructionPlan> {
const [ataAddress] = await findAssociatedTokenPda({
owner: input.owner,
tokenProgram: config?.tokenProgram ?? TOKEN_PROGRAM_ADDRESS,
mint: input.mint,
});
return mintToATAInstructionPlan(
{
...input,
ata: ataAddress,
},
config
);
}
176 changes: 176 additions & 0 deletions clients/js/test/mintToATA.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { Account, generateKeyPairSigner, none } from '@solana/kit';
import test from 'ava';
import {
AccountState,
TOKEN_PROGRAM_ADDRESS,
Token,
mintToATAInstructionPlan,
mintToATAInstructionPlanAsync,
fetchToken,
findAssociatedTokenPda,
} from '../src';
import {
createDefaultSolanaClient,
createDefaultTransactionPlanExecutor,
createDefaultTransactionPlanner,
createMint,
generateKeyPairSignerWithSol,
} from './_setup';

test('it creates a new associated token account with an initial balance', async (t) => {
// Given a mint account, its mint authority, a token owner and the ATA.
const client = createDefaultSolanaClient();
const [payer, mintAuthority, owner] = await Promise.all([
generateKeyPairSignerWithSol(client),
generateKeyPairSigner(),
generateKeyPairSigner(),
]);
const decimals = 2;
const mint = await createMint(client, payer, mintAuthority.address, decimals);
const [ata] = await findAssociatedTokenPda({
mint,
owner: owner.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});

// When we mint to a token account at this address.
const instructionPlan = mintToATAInstructionPlan({
payer,
ata,
mint,
owner: owner.address,
mintAuthority,
amount: 1_000n,
decimals,
});

const transactionPlanner = createDefaultTransactionPlanner(client, payer);
const transactionPlan = await transactionPlanner(instructionPlan);
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
await transactionPlanExecutor(transactionPlan);

// Then we expect the token account to exist and have the following data.
t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
address: ata,
data: {
mint,
owner: owner.address,
amount: 1000n,
delegate: none(),
state: AccountState.Initialized,
isNative: none(),
delegatedAmount: 0n,
closeAuthority: none(),
},
});
});

test('it derives a new associated token account with an initial balance', async (t) => {
// Given a mint account, its mint authority, a token owner and the ATA.
const client = createDefaultSolanaClient();
const [payer, mintAuthority, owner] = await Promise.all([
generateKeyPairSignerWithSol(client),
generateKeyPairSigner(),
generateKeyPairSigner(),
]);
const decimals = 2;
const mint = await createMint(client, payer, mintAuthority.address, decimals);

// When we mint to a token account for the mint.
const instructionPlan = await mintToATAInstructionPlanAsync({
payer,
mint,
owner: owner.address,
mintAuthority,
amount: 1_000n,
decimals,
});

const transactionPlanner = createDefaultTransactionPlanner(client, payer);
const transactionPlan = await transactionPlanner(instructionPlan);
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
await transactionPlanExecutor(transactionPlan);

// Then we expect the token account to exist and have the following data.
const [ata] = await findAssociatedTokenPda({
mint,
owner: owner.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});

t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
address: ata,
data: {
mint,
owner: owner.address,
amount: 1000n,
delegate: none(),
state: AccountState.Initialized,
isNative: none(),
delegatedAmount: 0n,
closeAuthority: none(),
},
});
});

test('it also mints to an existing associated token account', async (t) => {
// Given a mint account, its mint authority, a token owner and the ATA.
const client = createDefaultSolanaClient();
const [payer, mintAuthority, owner] = await Promise.all([
generateKeyPairSignerWithSol(client),
generateKeyPairSigner(),
generateKeyPairSigner(),
]);
const decimals = 2;
const mint = await createMint(client, payer, mintAuthority.address, decimals);
const [ata] = await findAssociatedTokenPda({
mint,
owner: owner.address,
tokenProgram: TOKEN_PROGRAM_ADDRESS,
});

// When we create and initialize a token account at this address.
const instructionPlan = mintToATAInstructionPlan({
payer,
ata,
mint,
owner: owner.address,
mintAuthority,
amount: 1_000n,
decimals,
});

const transactionPlanner = createDefaultTransactionPlanner(client, payer);
const transactionPlan = await transactionPlanner(instructionPlan);
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
await transactionPlanExecutor(transactionPlan);

// And then we mint additional tokens to the same account.
const instructionPlan2 = mintToATAInstructionPlan({
payer,
ata,
mint,
owner: owner.address,
mintAuthority,
amount: 1_000n,
decimals,
});

const transactionPlan2 = await transactionPlanner(instructionPlan2);
await transactionPlanExecutor(transactionPlan2);

// Then we expect the token account to exist and have the following data.
t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
address: ata,
data: {
mint,
owner: owner.address,
amount: 2000n,
delegate: none(),
state: AccountState.Initialized,
isNative: none(),
delegatedAmount: 0n,
closeAuthority: none(),
},
});
});