Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 6096ac6

Browse files
Support InitializeAccount2 instruction (#3229)
* Support InitializeAccount2 instruction * Add unit test
1 parent 3e91047 commit 6096ac6

File tree

3 files changed

+155
-10
lines changed

3 files changed

+155
-10
lines changed

token/js/src/instructions/decode.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { decodeBurnCheckedInstruction, DecodedBurnCheckedInstruction } from './b
99
import { decodeCloseAccountInstruction, DecodedCloseAccountInstruction } from './closeAccount';
1010
import { DecodedFreezeAccountInstruction, decodeFreezeAccountInstruction } from './freezeAccount';
1111
import { DecodedInitializeAccountInstruction, decodeInitializeAccountInstruction } from './initializeAccount';
12+
import { DecodedInitializeAccount2Instruction, decodeInitializeAccount2Instruction } from './initializeAccount2';
1213
import { DecodedInitializeMintInstruction, decodeInitializeMintInstruction } from './initializeMint';
1314
import { DecodedInitializeMultisigInstruction, decodeInitializeMultisigInstruction } from './initializeMultisig';
1415
import { DecodedMintToInstruction, decodeMintToInstruction } from './mintTo';
@@ -39,7 +40,7 @@ export type DecodedInstruction =
3940
| DecodedApproveCheckedInstruction
4041
| DecodedMintToCheckedInstruction
4142
| DecodedBurnCheckedInstruction
42-
// | DecodedInitializeAccount2Instruction
43+
| DecodedInitializeAccount2Instruction
4344
| DecodedSyncNativeInstruction
4445
// | DecodedInitializeAccount3Instruction
4546
// | DecodedInitializeMultisig2Instruction
@@ -72,8 +73,8 @@ export function decodeInstruction(
7273
if (type === TokenInstruction.ApproveChecked) return decodeApproveCheckedInstruction(instruction, programId);
7374
if (type === TokenInstruction.MintToChecked) return decodeMintToCheckedInstruction(instruction, programId);
7475
if (type === TokenInstruction.BurnChecked) return decodeBurnCheckedInstruction(instruction, programId);
75-
// TODO: implement
76-
if (type === TokenInstruction.InitializeAccount2) throw new TokenInvalidInstructionTypeError();
76+
if (type === TokenInstruction.InitializeAccount2)
77+
return decodeInitializeAccount2Instruction(instruction, programId);
7778
if (type === TokenInstruction.SyncNative) return decodeSyncNativeInstruction(instruction, programId);
7879
// TODO: implement
7980
if (type === TokenInstruction.InitializeAccount3) throw new TokenInvalidInstructionTypeError();
@@ -171,12 +172,12 @@ export function isBurnCheckedInstruction(decoded: DecodedInstruction): decoded i
171172
return decoded.data.instruction === TokenInstruction.BurnChecked;
172173
}
173174

174-
/** TODO: docs, implement */
175-
// export function isInitializeAccount2Instruction(
176-
// decoded: DecodedInstruction
177-
// ): decoded is DecodedInitializeAccount2Instruction {
178-
// return decoded.data.instruction === TokenInstruction.InitializeAccount2;
179-
// }
175+
/** TODO: docs */
176+
export function isInitializeAccount2Instruction(
177+
decoded: DecodedInstruction
178+
): decoded is DecodedInitializeAccount2Instruction {
179+
return decoded.data.instruction === TokenInstruction.InitializeAccount2;
180+
}
180181

181182
/** TODO: docs */
182183
export function isSyncNativeInstruction(decoded: DecodedInstruction): decoded is DecodedSyncNativeInstruction {
Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,134 @@
1-
export {}; // TODO: implement
1+
import { TokenInstruction } from './types';
2+
import { struct, u8 } from '@solana/buffer-layout';
3+
import { publicKey } from '@solana/buffer-layout-utils';
4+
import { AccountMeta, PublicKey, SYSVAR_RENT_PUBKEY, TransactionInstruction } from '@solana/web3.js';
5+
import { TOKEN_PROGRAM_ID } from '../constants';
6+
import {
7+
TokenInvalidInstructionDataError,
8+
TokenInvalidInstructionKeysError,
9+
TokenInvalidInstructionProgramError,
10+
TokenInvalidInstructionTypeError,
11+
} from '../errors';
12+
13+
export interface InitializeAccount2InstructionData {
14+
instruction: TokenInstruction.InitializeAccount2;
15+
owner: PublicKey;
16+
}
17+
18+
export const initializeAccount2InstructionData = struct<InitializeAccount2InstructionData>([
19+
u8('instruction'),
20+
publicKey('owner'),
21+
]);
22+
23+
/**
24+
* Construct an InitializeAccount2 instruction
25+
*
26+
* @param account New token account
27+
* @param mint Mint account
28+
* @param owner New account's owner/multisignature
29+
* @param programId SPL Token program account
30+
*
31+
* @return Instruction to add to a transaction
32+
*/
33+
export function createInitializeAccount2Instruction(
34+
account: PublicKey,
35+
mint: PublicKey,
36+
owner: PublicKey,
37+
programId = TOKEN_PROGRAM_ID
38+
): TransactionInstruction {
39+
const keys = [
40+
{ pubkey: account, isSigner: false, isWritable: true },
41+
{ pubkey: mint, isSigner: false, isWritable: false },
42+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
43+
];
44+
const data = Buffer.alloc(initializeAccount2InstructionData.span);
45+
initializeAccount2InstructionData.encode({ instruction: TokenInstruction.InitializeAccount2, owner }, data);
46+
return new TransactionInstruction({ keys, programId, data });
47+
}
48+
49+
/** A decoded, valid InitializeAccount2 instruction */
50+
export interface DecodedInitializeAccount2Instruction {
51+
programId: PublicKey;
52+
keys: {
53+
account: AccountMeta;
54+
mint: AccountMeta;
55+
rent: AccountMeta;
56+
};
57+
data: {
58+
instruction: TokenInstruction.InitializeAccount2;
59+
owner: PublicKey;
60+
};
61+
}
62+
63+
/**
64+
* Decode an InitializeAccount2 instruction and validate it
65+
*
66+
* @param instruction Transaction instruction to decode
67+
* @param programId SPL Token program account
68+
*
69+
* @return Decoded, valid instruction
70+
*/
71+
export function decodeInitializeAccount2Instruction(
72+
instruction: TransactionInstruction,
73+
programId = TOKEN_PROGRAM_ID
74+
): DecodedInitializeAccount2Instruction {
75+
if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError();
76+
if (instruction.data.length !== initializeAccount2InstructionData.span)
77+
throw new TokenInvalidInstructionDataError();
78+
79+
const {
80+
keys: { account, mint, rent },
81+
data,
82+
} = decodeInitializeAccount2InstructionUnchecked(instruction);
83+
if (data.instruction !== TokenInstruction.InitializeAccount2) throw new TokenInvalidInstructionTypeError();
84+
if (!account || !mint || !rent) throw new TokenInvalidInstructionKeysError();
85+
86+
// TODO: key checks?
87+
88+
return {
89+
programId,
90+
keys: {
91+
account,
92+
mint,
93+
rent,
94+
},
95+
data,
96+
};
97+
}
98+
99+
/** A decoded, non-validated InitializeAccount2 instruction */
100+
export interface DecodedInitializeAccount2InstructionUnchecked {
101+
programId: PublicKey;
102+
keys: {
103+
account: AccountMeta | undefined;
104+
mint: AccountMeta | undefined;
105+
rent: AccountMeta | undefined;
106+
};
107+
data: {
108+
instruction: number;
109+
owner: PublicKey;
110+
};
111+
}
112+
113+
/**
114+
* Decode an InitializeAccount2 instruction without validating it
115+
*
116+
* @param instruction Transaction instruction to decode
117+
*
118+
* @return Decoded, non-validated instruction
119+
*/
120+
export function decodeInitializeAccount2InstructionUnchecked({
121+
programId,
122+
keys: [account, mint, rent],
123+
data,
124+
}: TransactionInstruction): DecodedInitializeAccount2InstructionUnchecked {
125+
return {
126+
programId,
127+
keys: {
128+
account,
129+
mint,
130+
rent,
131+
},
132+
data: initializeAccount2InstructionData.decode(data),
133+
};
134+
}

token/js/test/unit/index.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
getAccountLen,
1515
ExtensionType,
1616
getAssociatedTokenAddressSync,
17+
createInitializeAccount2Instruction,
1718
} from '../../src';
1819

1920
chai.use(chaiAsPromised);
@@ -43,6 +44,16 @@ describe('spl-token instructions', () => {
4344
expect(ix.programId).to.eql(TOKEN_PROGRAM_ID);
4445
expect(ix.keys).to.have.length(1);
4546
});
47+
48+
it('InitializeAccount2', () => {
49+
const ix = createInitializeAccount2Instruction(
50+
Keypair.generate().publicKey,
51+
Keypair.generate().publicKey,
52+
Keypair.generate().publicKey
53+
);
54+
expect(ix.programId).to.eql(TOKEN_PROGRAM_ID);
55+
expect(ix.keys).to.have.length(3);
56+
});
4657
});
4758

4859
describe('spl-token-2022 instructions', () => {

0 commit comments

Comments
 (0)