Skip to content

Commit 599539e

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

File tree

4 files changed

+204
-0
lines changed

4 files changed

+204
-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
16+
// Hardcoded to avoid requiring an RPC request each time
17+
const minimumBalanceForMint = 1461600;
18+
19+
export type GetOrCreateMintInstructionPlanInput = {
20+
/** Funding account (must be a system account). */
21+
payer: TransactionSigner;
22+
/** New mint account to create. */
23+
newMint: TransactionSigner;
24+
/**
25+
* Optional override for the minimum balance required for rent exemption of the token mint account.
26+
* @default 1461600
27+
* */
28+
minimumBalanceForMintOverride?: number;
29+
/** Number of base 10 digits to the right of the decimal place. */
30+
decimals: number;
31+
/** The authority/multisignature to mint tokens. */
32+
mintAuthority: Address;
33+
/** The optional freeze authority/multisignature of the mint. */
34+
freezeAuthority?: OptionOrNullable<Address>;
35+
};
36+
37+
type GetOrCreateMintInstructionPlanConfig = {
38+
systemProgramAddress?: Address;
39+
tokenProgramAddress?: Address;
40+
};
41+
42+
export function getCreateMintInstructionPlan(
43+
params: GetOrCreateMintInstructionPlanInput,
44+
config?: GetOrCreateMintInstructionPlanConfig
45+
): InstructionPlan {
46+
return sequentialInstructionPlan([
47+
getCreateAccountInstruction(
48+
{
49+
payer: params.payer,
50+
newAccount: params.newMint,
51+
lamports: params.minimumBalanceForMintOverride ?? 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: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,25 @@ import {
55
Commitment,
66
Rpc,
77
RpcSubscriptions,
8+
Signature,
89
SolanaRpcApi,
910
SolanaRpcSubscriptionsApi,
1011
TransactionMessageWithBlockhashLifetime,
1112
TransactionMessageWithFeePayer,
13+
TransactionPlan,
14+
TransactionPlanExecutor,
15+
TransactionPlanResult,
16+
TransactionPlanner,
1217
TransactionSigner,
1318
airdropFactory,
1419
appendTransactionMessageInstructions,
1520
assertIsSendableTransaction,
21+
assertIsTransactionWithBlockhashLifetime,
1622
createSolanaRpc,
1723
createSolanaRpcSubscriptions,
1824
createTransactionMessage,
25+
createTransactionPlanExecutor,
26+
createTransactionPlanner,
1927
generateKeyPairSigner,
2028
getSignatureFromTransaction,
2129
lamports,
@@ -83,12 +91,59 @@ export const signAndSendTransaction = async (
8391
await signTransactionMessageWithSigners(transactionMessage);
8492
const signature = getSignatureFromTransaction(signedTransaction);
8593
assertIsSendableTransaction(signedTransaction);
94+
assertIsTransactionWithBlockhashLifetime(signedTransaction);
8695
await sendAndConfirmTransactionFactory(client)(signedTransaction, {
8796
commitment,
8897
});
8998
return signature;
9099
};
91100

101+
export const createDefaultTransactionPlanner = (
102+
client: Client,
103+
feePayer: TransactionSigner
104+
): TransactionPlanner => {
105+
return createTransactionPlanner({
106+
createTransactionMessage: async () => {
107+
const { value: latestBlockhash } = await client.rpc
108+
.getLatestBlockhash()
109+
.send();
110+
111+
return pipe(
112+
createTransactionMessage({ version: 0 }),
113+
(tx) => setTransactionMessageFeePayerSigner(feePayer, tx),
114+
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx)
115+
);
116+
},
117+
});
118+
};
119+
120+
export const createDefaultTransactionPlanExecutor = (
121+
client: Client,
122+
commitment: Commitment = 'confirmed'
123+
): TransactionPlanExecutor => {
124+
return createTransactionPlanExecutor({
125+
executeTransactionMessage: async (transactionMessage) => {
126+
const signedTransaction =
127+
await signTransactionMessageWithSigners(transactionMessage);
128+
assertIsSendableTransaction(signedTransaction);
129+
assertIsTransactionWithBlockhashLifetime(signedTransaction);
130+
await sendAndConfirmTransactionFactory(client)(signedTransaction, {
131+
commitment,
132+
});
133+
return { transaction: signedTransaction };
134+
},
135+
});
136+
};
137+
138+
export const getSignatureFromTransactionPlanResult = (
139+
result: TransactionPlanResult
140+
): Signature => {
141+
if (result.kind === 'single' && result.status.kind === 'successful') {
142+
return getSignatureFromTransaction(result.status.transaction);
143+
}
144+
throw new Error('Unable to get signature from transaction plan result');
145+
};
146+
92147
export const getBalance = async (client: Client, address: Address) =>
93148
(await client.rpc.getBalance(address, { commitment: 'confirmed' }).send())
94149
.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, getCreateMintInstructionPlan } 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 = getCreateMintInstructionPlan({
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 = getCreateMintInstructionPlan({
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)