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

Commit ad97543

Browse files
Support InitializeAccount3 in token js (#3233)
1 parent 6096ac6 commit ad97543

File tree

3 files changed

+150
-9
lines changed

3 files changed

+150
-9
lines changed

token/js/src/instructions/decode.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { decodeCloseAccountInstruction, DecodedCloseAccountInstruction } from '.
1010
import { DecodedFreezeAccountInstruction, decodeFreezeAccountInstruction } from './freezeAccount';
1111
import { DecodedInitializeAccountInstruction, decodeInitializeAccountInstruction } from './initializeAccount';
1212
import { DecodedInitializeAccount2Instruction, decodeInitializeAccount2Instruction } from './initializeAccount2';
13+
import { DecodedInitializeAccount3Instruction, decodeInitializeAccount3Instruction } from './initializeAccount3';
1314
import { DecodedInitializeMintInstruction, decodeInitializeMintInstruction } from './initializeMint';
1415
import { DecodedInitializeMultisigInstruction, decodeInitializeMultisigInstruction } from './initializeMultisig';
1516
import { DecodedMintToInstruction, decodeMintToInstruction } from './mintTo';
@@ -42,7 +43,7 @@ export type DecodedInstruction =
4243
| DecodedBurnCheckedInstruction
4344
| DecodedInitializeAccount2Instruction
4445
| DecodedSyncNativeInstruction
45-
// | DecodedInitializeAccount3Instruction
46+
| DecodedInitializeAccount3Instruction
4647
// | DecodedInitializeMultisig2Instruction
4748
// | DecodedInitializeMint2Instruction
4849
// TODO: implement ^ and remove `never`
@@ -77,7 +78,8 @@ export function decodeInstruction(
7778
return decodeInitializeAccount2Instruction(instruction, programId);
7879
if (type === TokenInstruction.SyncNative) return decodeSyncNativeInstruction(instruction, programId);
7980
// TODO: implement
80-
if (type === TokenInstruction.InitializeAccount3) throw new TokenInvalidInstructionTypeError();
81+
if (type === TokenInstruction.InitializeAccount3)
82+
return decodeInitializeAccount3Instruction(instruction, programId);
8183
// TODO: implement
8284
if (type === TokenInstruction.InitializeMultisig2) throw new TokenInvalidInstructionTypeError();
8385
// TODO: implement
@@ -184,12 +186,12 @@ export function isSyncNativeInstruction(decoded: DecodedInstruction): decoded is
184186
return decoded.data.instruction === TokenInstruction.SyncNative;
185187
}
186188

187-
/** TODO: docs, implement */
188-
// export function isInitializeAccount3Instruction(
189-
// decoded: DecodedInstruction
190-
// ): decoded is DecodedInitializeAccount3Instruction {
191-
// return decoded.data.instruction === TokenInstruction.InitializeAccount3;
192-
// }
189+
/** TODO: docs */
190+
export function isInitializeAccount3Instruction(
191+
decoded: DecodedInstruction
192+
): decoded is DecodedInitializeAccount3Instruction {
193+
return decoded.data.instruction === TokenInstruction.InitializeAccount3;
194+
}
193195

194196
/** TODO: docs, implement */
195197
// export function isInitializeMultisig2Instruction(
Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,129 @@
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 InitializeAccount3InstructionData {
14+
instruction: TokenInstruction.InitializeAccount3;
15+
owner: PublicKey;
16+
}
17+
18+
export const initializeAccount3InstructionData = struct<InitializeAccount3InstructionData>([
19+
u8('instruction'),
20+
publicKey('owner'),
21+
]);
22+
23+
/**
24+
* Construct an InitializeAccount3 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 createInitializeAccount3Instruction(
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+
];
43+
const data = Buffer.alloc(initializeAccount3InstructionData.span);
44+
initializeAccount3InstructionData.encode({ instruction: TokenInstruction.InitializeAccount3, owner }, data);
45+
return new TransactionInstruction({ keys, programId, data });
46+
}
47+
48+
/** A decoded, valid InitializeAccount3 instruction */
49+
export interface DecodedInitializeAccount3Instruction {
50+
programId: PublicKey;
51+
keys: {
52+
account: AccountMeta;
53+
mint: AccountMeta;
54+
};
55+
data: {
56+
instruction: TokenInstruction.InitializeAccount3;
57+
owner: PublicKey;
58+
};
59+
}
60+
61+
/**
62+
* Decode an InitializeAccount3 instruction and validate it
63+
*
64+
* @param instruction Transaction instruction to decode
65+
* @param programId SPL Token program account
66+
*
67+
* @return Decoded, valid instruction
68+
*/
69+
export function decodeInitializeAccount3Instruction(
70+
instruction: TransactionInstruction,
71+
programId = TOKEN_PROGRAM_ID
72+
): DecodedInitializeAccount3Instruction {
73+
if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError();
74+
if (instruction.data.length !== initializeAccount3InstructionData.span)
75+
throw new TokenInvalidInstructionDataError();
76+
77+
const {
78+
keys: { account, mint },
79+
data,
80+
} = decodeInitializeAccount3InstructionUnchecked(instruction);
81+
if (data.instruction !== TokenInstruction.InitializeAccount3) throw new TokenInvalidInstructionTypeError();
82+
if (!account || !mint) throw new TokenInvalidInstructionKeysError();
83+
84+
// TODO: key checks?
85+
86+
return {
87+
programId,
88+
keys: {
89+
account,
90+
mint,
91+
},
92+
data,
93+
};
94+
}
95+
96+
/** A decoded, non-validated InitializeAccount3 instruction */
97+
export interface DecodedInitializeAccount3InstructionUnchecked {
98+
programId: PublicKey;
99+
keys: {
100+
account: AccountMeta | undefined;
101+
mint: AccountMeta | undefined;
102+
};
103+
data: {
104+
instruction: number;
105+
owner: PublicKey;
106+
};
107+
}
108+
109+
/**
110+
* Decode an InitializeAccount3 instruction without validating it
111+
*
112+
* @param instruction Transaction instruction to decode
113+
*
114+
* @return Decoded, non-validated instruction
115+
*/
116+
export function decodeInitializeAccount3InstructionUnchecked({
117+
programId,
118+
keys: [account, mint],
119+
data,
120+
}: TransactionInstruction): DecodedInitializeAccount3InstructionUnchecked {
121+
return {
122+
programId,
123+
keys: {
124+
account,
125+
mint,
126+
},
127+
data: initializeAccount3InstructionData.decode(data),
128+
};
129+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ExtensionType,
1616
getAssociatedTokenAddressSync,
1717
createInitializeAccount2Instruction,
18+
createInitializeAccount3Instruction,
1819
} from '../../src';
1920

2021
chai.use(chaiAsPromised);
@@ -54,6 +55,16 @@ describe('spl-token instructions', () => {
5455
expect(ix.programId).to.eql(TOKEN_PROGRAM_ID);
5556
expect(ix.keys).to.have.length(3);
5657
});
58+
59+
it('InitializeAccount3', () => {
60+
const ix = createInitializeAccount3Instruction(
61+
Keypair.generate().publicKey,
62+
Keypair.generate().publicKey,
63+
Keypair.generate().publicKey
64+
);
65+
expect(ix.programId).to.eql(TOKEN_PROGRAM_ID);
66+
expect(ix.keys).to.have.length(2);
67+
});
5768
});
5869

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

0 commit comments

Comments
 (0)