Skip to content

Commit 004b844

Browse files
committed
Add an instruction plan for creating a mint
1 parent 81ba155 commit 004b844

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

clients/js/src/createMint.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { getCreateAccountInstruction } from '@solana-program/system';
2+
import {
3+
Address,
4+
InstructionPlan,
5+
OptionOrNullable,
6+
sequentialInstructionPlan,
7+
TransactionSigner,
8+
} from '@solana/kit';
9+
import {
10+
getInitializeMint2Instruction,
11+
getMintSize,
12+
TOKEN_PROGRAM_ADDRESS,
13+
} from './generated';
14+
15+
// RPC `getMinimumBalanceForRentExemption` for 82 bytes, which is token mint size
16+
// Hardcoded to avoid requiring an RPC request each time
17+
const minimumBalanceForMint = 1461600;
18+
19+
export type CreateMintInstructionPlanInput = {
20+
/** Funding account (must be a system account). */
21+
payer: TransactionSigner;
22+
/** New mint account to create. */
23+
newMint: TransactionSigner;
24+
/** Number of base 10 digits to the right of the decimal place. */
25+
decimals: number;
26+
/** The authority/multisignature to mint tokens. */
27+
mintAuthority: Address;
28+
/** The optional freeze authority/multisignature of the mint. */
29+
freezeAuthority?: OptionOrNullable<Address>;
30+
/**
31+
* Optional override for the amount of Lamports to fund the mint account with.
32+
* @default 1461600
33+
* */
34+
mintAccountLamports?: number;
35+
};
36+
37+
type CreateMintInstructionPlanConfig = {
38+
systemProgramAddress?: Address;
39+
tokenProgramAddress?: Address;
40+
};
41+
42+
export function createMintInstructionPlan(
43+
params: CreateMintInstructionPlanInput,
44+
config?: CreateMintInstructionPlanConfig
45+
): InstructionPlan {
46+
return sequentialInstructionPlan([
47+
getCreateAccountInstruction(
48+
{
49+
payer: params.payer,
50+
newAccount: params.newMint,
51+
lamports: params.mintAccountLamports ?? minimumBalanceForMint,
52+
space: getMintSize(),
53+
programAddress: config?.tokenProgramAddress ?? TOKEN_PROGRAM_ADDRESS,
54+
},
55+
{
56+
programAddress: config?.systemProgramAddress,
57+
}
58+
),
59+
getInitializeMint2Instruction(
60+
{
61+
mint: params.newMint.address,
62+
decimals: params.decimals,
63+
mintAuthority: params.mintAuthority,
64+
freezeAuthority: params.freezeAuthority,
65+
},
66+
{
67+
programAddress: config?.tokenProgramAddress,
68+
}
69+
),
70+
]);
71+
}

clients/js/src/index.ts

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

clients/js/test/_setup.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ import {
99
SolanaRpcSubscriptionsApi,
1010
TransactionMessageWithBlockhashLifetime,
1111
TransactionMessageWithFeePayer,
12+
TransactionPlanExecutor,
13+
TransactionPlanner,
1214
TransactionSigner,
1315
airdropFactory,
1416
appendTransactionMessageInstructions,
1517
assertIsSendableTransaction,
18+
assertIsTransactionWithBlockhashLifetime,
1619
createSolanaRpc,
1720
createSolanaRpcSubscriptions,
1821
createTransactionMessage,
22+
createTransactionPlanExecutor,
23+
createTransactionPlanner,
1924
generateKeyPairSigner,
2025
getSignatureFromTransaction,
2126
lamports,
@@ -83,12 +88,50 @@ export const signAndSendTransaction = async (
8388
await signTransactionMessageWithSigners(transactionMessage);
8489
const signature = getSignatureFromTransaction(signedTransaction);
8590
assertIsSendableTransaction(signedTransaction);
91+
assertIsTransactionWithBlockhashLifetime(signedTransaction);
8692
await sendAndConfirmTransactionFactory(client)(signedTransaction, {
8793
commitment,
8894
});
8995
return signature;
9096
};
9197

98+
export const createDefaultTransactionPlanner = (
99+
client: Client,
100+
feePayer: TransactionSigner
101+
): TransactionPlanner => {
102+
return createTransactionPlanner({
103+
createTransactionMessage: async () => {
104+
const { value: latestBlockhash } = await client.rpc
105+
.getLatestBlockhash()
106+
.send();
107+
108+
return pipe(
109+
createTransactionMessage({ version: 0 }),
110+
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
111+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
112+
);
113+
},
114+
});
115+
};
116+
117+
export const createDefaultTransactionPlanExecutor = (
118+
client: Client,
119+
commitment: Commitment = 'confirmed'
120+
): TransactionPlanExecutor => {
121+
return createTransactionPlanExecutor({
122+
executeTransactionMessage: async (transactionMessage) => {
123+
const signedTransaction =
124+
await signTransactionMessageWithSigners(transactionMessage);
125+
assertIsSendableTransaction(signedTransaction);
126+
assertIsTransactionWithBlockhashLifetime(signedTransaction);
127+
await sendAndConfirmTransactionFactory(client)(signedTransaction, {
128+
commitment,
129+
});
130+
return { transaction: signedTransaction };
131+
},
132+
});
133+
};
134+
92135
export const getBalance = async (client: Client, address: Address) =>
93136
(await client.rpc.getBalance(address, { commitment: 'confirmed' }).send())
94137
.value;

clients/js/test/createMint.test.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { generateKeyPairSigner, Account, some, none } from '@solana/kit';
2+
import test from 'ava';
3+
import { fetchMint, Mint, createMintInstructionPlan } from '../src';
4+
import {
5+
createDefaultSolanaClient,
6+
generateKeyPairSignerWithSol,
7+
createDefaultTransactionPlanner,
8+
createDefaultTransactionPlanExecutor,
9+
} from './_setup';
10+
11+
test('it creates and initializes a new mint account', async (t) => {
12+
// Given an authority and a mint account.
13+
const client = createDefaultSolanaClient();
14+
const authority = await generateKeyPairSignerWithSol(client);
15+
const mint = await generateKeyPairSigner();
16+
17+
// When we create and initialize a mint account at this address.
18+
const instructionPlan = createMintInstructionPlan({
19+
payer: authority,
20+
newMint: mint,
21+
decimals: 2,
22+
mintAuthority: authority.address,
23+
});
24+
25+
const transactionPlanner = createDefaultTransactionPlanner(client, authority);
26+
const transactionPlan = await transactionPlanner(instructionPlan);
27+
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
28+
await transactionPlanExecutor(transactionPlan);
29+
30+
// Then we expect the mint account to exist and have the following data.
31+
const mintAccount = await fetchMint(client.rpc, mint.address);
32+
t.like(mintAccount, <Account<Mint>>{
33+
address: mint.address,
34+
data: {
35+
mintAuthority: some(authority.address),
36+
supply: 0n,
37+
decimals: 2,
38+
isInitialized: true,
39+
freezeAuthority: none(),
40+
},
41+
});
42+
});
43+
44+
test('it creates a new mint account with a freeze authority', async (t) => {
45+
// Given an authority and a mint account.
46+
const client = createDefaultSolanaClient();
47+
const [payer, mintAuthority, freezeAuthority, mint] = await Promise.all([
48+
generateKeyPairSignerWithSol(client),
49+
generateKeyPairSigner(),
50+
generateKeyPairSigner(),
51+
generateKeyPairSigner(),
52+
]);
53+
54+
// When we create and initialize a mint account at this address.
55+
const instructionPlan = createMintInstructionPlan({
56+
payer: payer,
57+
newMint: mint,
58+
decimals: 2,
59+
mintAuthority: mintAuthority.address,
60+
freezeAuthority: freezeAuthority.address,
61+
});
62+
63+
const transactionPlanner = createDefaultTransactionPlanner(client, payer);
64+
const transactionPlan = await transactionPlanner(instructionPlan);
65+
const transactionPlanExecutor = createDefaultTransactionPlanExecutor(client);
66+
await transactionPlanExecutor(transactionPlan);
67+
68+
// Then we expect the mint account to exist and have the following data.
69+
const mintAccount = await fetchMint(client.rpc, mint.address);
70+
t.like(mintAccount, <Account<Mint>>{
71+
address: mint.address,
72+
data: {
73+
mintAuthority: some(mintAuthority.address),
74+
freezeAuthority: some(freezeAuthority.address),
75+
},
76+
});
77+
});

0 commit comments

Comments
 (0)