Skip to content

Commit 99c0195

Browse files
committed
Add an instruction plan to mint to an ATA
1 parent 62944d6 commit 99c0195

File tree

4 files changed

+287
-13
lines changed

4 files changed

+287
-13
lines changed

clients/js/src/createMint.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -35,36 +35,36 @@ export type CreateMintInstructionPlanInput = {
3535
};
3636

3737
type CreateMintInstructionPlanConfig = {
38-
systemProgramAddress?: Address;
39-
tokenProgramAddress?: Address;
38+
systemProgram?: Address;
39+
tokenProgram?: Address;
4040
};
4141

4242
export function createMintInstructionPlan(
43-
params: CreateMintInstructionPlanInput,
43+
input: CreateMintInstructionPlanInput,
4444
config?: CreateMintInstructionPlanConfig
4545
): InstructionPlan {
4646
return sequentialInstructionPlan([
4747
getCreateAccountInstruction(
4848
{
49-
payer: params.payer,
50-
newAccount: params.newMint,
51-
lamports: params.mintAccountLamports ?? MINIMUM_BALANCE_FOR_MINT,
49+
payer: input.payer,
50+
newAccount: input.newMint,
51+
lamports: input.mintAccountLamports ?? MINIMUM_BALANCE_FOR_MINT,
5252
space: getMintSize(),
53-
programAddress: config?.tokenProgramAddress ?? TOKEN_PROGRAM_ADDRESS,
53+
programAddress: config?.tokenProgram ?? TOKEN_PROGRAM_ADDRESS,
5454
},
5555
{
56-
programAddress: config?.systemProgramAddress,
56+
programAddress: config?.systemProgram,
5757
}
5858
),
5959
getInitializeMint2Instruction(
6060
{
61-
mint: params.newMint.address,
62-
decimals: params.decimals,
63-
mintAuthority: params.mintAuthority,
64-
freezeAuthority: params.freezeAuthority,
61+
mint: input.newMint.address,
62+
decimals: input.decimals,
63+
mintAuthority: input.mintAuthority,
64+
freezeAuthority: input.freezeAuthority,
6565
},
6666
{
67-
programAddress: config?.tokenProgramAddress,
67+
programAddress: config?.tokenProgram,
6868
}
6969
),
7070
]);

clients/js/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export * from './generated';
22
export * from './createMint';
3+
export * from './mintToATA';

clients/js/src/mintToATA.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
InstructionPlan,
3+
sequentialInstructionPlan,
4+
Address,
5+
TransactionSigner,
6+
} from '@solana/kit';
7+
import {
8+
findAssociatedTokenPda,
9+
getCreateAssociatedTokenIdempotentInstruction,
10+
getMintToCheckedInstruction,
11+
TOKEN_PROGRAM_ADDRESS,
12+
} from './generated';
13+
14+
type MintToATAInstructionPlanInput = {
15+
/** Funding account (must be a system account). */
16+
payer: TransactionSigner;
17+
/** Associated token account address to mint to.
18+
* Will be created if it does not already exist.
19+
* Note: Use {@link mintToATAInstructionPlanAsync} instead to derive this automatically.
20+
* Note: Use {@link findAssociatedTokenPda} to derive the associated token account address.
21+
*/
22+
ata: Address;
23+
/** Wallet address for the new associated token account. */
24+
owner: Address;
25+
/** The token mint for the new associated token account. */
26+
mint: Address;
27+
/** The mint's minting authority or its multisignature account. */
28+
mintAuthority: Address | TransactionSigner;
29+
/** The amount of new tokens to mint. */
30+
amount: number | bigint;
31+
/** Expected number of base 10 digits to the right of the decimal place. */
32+
decimals: number;
33+
multiSigners?: Array<TransactionSigner>;
34+
};
35+
36+
type MintToATAInstructionPlanConfig = {
37+
systemProgram?: Address;
38+
tokenProgram?: Address;
39+
};
40+
41+
export function mintToATAInstructionPlan(
42+
input: MintToATAInstructionPlanInput,
43+
config?: MintToATAInstructionPlanConfig
44+
): InstructionPlan {
45+
return sequentialInstructionPlan([
46+
getCreateAssociatedTokenIdempotentInstruction(
47+
{
48+
payer: input.payer,
49+
ata: input.ata,
50+
owner: input.owner,
51+
mint: input.mint,
52+
systemProgram: config?.systemProgram,
53+
tokenProgram: config?.tokenProgram,
54+
},
55+
{
56+
programAddress: config?.tokenProgram,
57+
}
58+
),
59+
// mint to this token account
60+
getMintToCheckedInstruction(
61+
{
62+
mint: input.mint,
63+
token: input.ata,
64+
mintAuthority: input.mintAuthority,
65+
amount: input.amount,
66+
decimals: input.decimals,
67+
multiSigners: input.multiSigners,
68+
},
69+
{
70+
programAddress: config?.tokenProgram,
71+
}
72+
),
73+
]);
74+
}
75+
76+
type MintToATAInstructionPlanAsyncInput = Omit<
77+
MintToATAInstructionPlanInput,
78+
'ata'
79+
>;
80+
81+
export async function mintToATAInstructionPlanAsync(
82+
input: MintToATAInstructionPlanAsyncInput,
83+
config?: MintToATAInstructionPlanConfig
84+
): Promise<InstructionPlan> {
85+
const [ataAddress] = await findAssociatedTokenPda({
86+
owner: input.owner,
87+
tokenProgram: config?.tokenProgram ?? TOKEN_PROGRAM_ADDRESS,
88+
mint: input.mint,
89+
});
90+
return mintToATAInstructionPlan(
91+
{
92+
...input,
93+
ata: ataAddress,
94+
},
95+
config
96+
);
97+
}

clients/js/test/mintToATA.test.ts

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { Account, generateKeyPairSigner, none } from '@solana/kit';
2+
import test from 'ava';
3+
import {
4+
AccountState,
5+
TOKEN_PROGRAM_ADDRESS,
6+
Token,
7+
mintToATAInstructionPlan,
8+
mintToATAInstructionPlanAsync,
9+
fetchToken,
10+
findAssociatedTokenPda,
11+
} from '../src';
12+
import {
13+
createDefaultSolanaClient,
14+
createDefaultTransactionPlanExecutor,
15+
createDefaultTransactionPlanner,
16+
createMint,
17+
generateKeyPairSignerWithSol,
18+
} from './_setup';
19+
20+
test('it creates a new associated token account with an initial balance', async (t) => {
21+
// Given a mint account, its mint authority, a token owner and the ATA.
22+
const client = createDefaultSolanaClient();
23+
const [payer, mintAuthority, owner] = await Promise.all([
24+
generateKeyPairSignerWithSol(client),
25+
generateKeyPairSigner(),
26+
generateKeyPairSigner(),
27+
]);
28+
const decimals = 2;
29+
const mint = await createMint(client, payer, mintAuthority.address, decimals);
30+
const [ata] = await findAssociatedTokenPda({
31+
mint,
32+
owner: owner.address,
33+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
34+
});
35+
36+
// When we mint to a token account at this address.
37+
const instructionPlan = mintToATAInstructionPlan({
38+
payer,
39+
ata,
40+
mint,
41+
owner: owner.address,
42+
mintAuthority,
43+
amount: 1_000n,
44+
decimals,
45+
});
46+
47+
const transactionPlanner = createDefaultTransactionPlanner(client, payer);
48+
const transactionPlan = await transactionPlanner(instructionPlan);
49+
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
50+
await transactionPlanExecutor(transactionPlan);
51+
52+
// Then we expect the token account to exist and have the following data.
53+
t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
54+
address: ata,
55+
data: {
56+
mint,
57+
owner: owner.address,
58+
amount: 1000n,
59+
delegate: none(),
60+
state: AccountState.Initialized,
61+
isNative: none(),
62+
delegatedAmount: 0n,
63+
closeAuthority: none(),
64+
},
65+
});
66+
});
67+
68+
test('it derives a new associated token account with an initial balance', async (t) => {
69+
// Given a mint account, its mint authority, a token owner and the ATA.
70+
const client = createDefaultSolanaClient();
71+
const [payer, mintAuthority, owner] = await Promise.all([
72+
generateKeyPairSignerWithSol(client),
73+
generateKeyPairSigner(),
74+
generateKeyPairSigner(),
75+
]);
76+
const decimals = 2;
77+
const mint = await createMint(client, payer, mintAuthority.address, decimals);
78+
79+
// When we mint to a token account for the mint.
80+
const instructionPlan = await mintToATAInstructionPlanAsync({
81+
payer,
82+
mint,
83+
owner: owner.address,
84+
mintAuthority,
85+
amount: 1_000n,
86+
decimals,
87+
});
88+
89+
const transactionPlanner = createDefaultTransactionPlanner(client, payer);
90+
const transactionPlan = await transactionPlanner(instructionPlan);
91+
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
92+
await transactionPlanExecutor(transactionPlan);
93+
94+
// Then we expect the token account to exist and have the following data.
95+
const [ata] = await findAssociatedTokenPda({
96+
mint,
97+
owner: owner.address,
98+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
99+
});
100+
101+
t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
102+
address: ata,
103+
data: {
104+
mint,
105+
owner: owner.address,
106+
amount: 1000n,
107+
delegate: none(),
108+
state: AccountState.Initialized,
109+
isNative: none(),
110+
delegatedAmount: 0n,
111+
closeAuthority: none(),
112+
},
113+
});
114+
});
115+
116+
test('it also mints to an existing associated token account', async (t) => {
117+
// Given a mint account, its mint authority, a token owner and the ATA.
118+
const client = createDefaultSolanaClient();
119+
const [payer, mintAuthority, owner] = await Promise.all([
120+
generateKeyPairSignerWithSol(client),
121+
generateKeyPairSigner(),
122+
generateKeyPairSigner(),
123+
]);
124+
const decimals = 2;
125+
const mint = await createMint(client, payer, mintAuthority.address, decimals);
126+
const [ata] = await findAssociatedTokenPda({
127+
mint,
128+
owner: owner.address,
129+
tokenProgram: TOKEN_PROGRAM_ADDRESS,
130+
});
131+
132+
// When we create and initialize a token account at this address.
133+
const instructionPlan = mintToATAInstructionPlan({
134+
payer,
135+
ata,
136+
mint,
137+
owner: owner.address,
138+
mintAuthority,
139+
amount: 1_000n,
140+
decimals,
141+
});
142+
143+
const transactionPlanner = createDefaultTransactionPlanner(client, payer);
144+
const transactionPlan = await transactionPlanner(instructionPlan);
145+
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
146+
await transactionPlanExecutor(transactionPlan);
147+
148+
// And then we mint additional tokens to the same account.
149+
const instructionPlan2 = mintToATAInstructionPlan({
150+
payer,
151+
ata,
152+
mint,
153+
owner: owner.address,
154+
mintAuthority,
155+
amount: 1_000n,
156+
decimals,
157+
});
158+
159+
const transactionPlan2 = await transactionPlanner(instructionPlan2);
160+
await transactionPlanExecutor(transactionPlan2);
161+
162+
// Then we expect the token account to exist and have the following data.
163+
t.like(await fetchToken(client.rpc, ata), <Account<Token>>{
164+
address: ata,
165+
data: {
166+
mint,
167+
owner: owner.address,
168+
amount: 2000n,
169+
delegate: none(),
170+
state: AccountState.Initialized,
171+
isNative: none(),
172+
delegatedAmount: 0n,
173+
closeAuthority: none(),
174+
},
175+
});
176+
});

0 commit comments

Comments
 (0)