diff --git a/clients/js-legacy/src/instructions/decode.ts b/clients/js-legacy/src/instructions/decode.ts index 31ef2828b..cf6d52b85 100644 --- a/clients/js-legacy/src/instructions/decode.ts +++ b/clients/js-legacy/src/instructions/decode.ts @@ -28,6 +28,8 @@ import type { DecodedInitializeMint2Instruction } from './initializeMint2.js'; import { decodeInitializeMint2Instruction } from './initializeMint2.js'; import type { DecodedInitializeMultisigInstruction } from './initializeMultisig.js'; import { decodeInitializeMultisigInstruction } from './initializeMultisig.js'; +import type { DecodedInitializeMultisig2Instruction } from './initializeMultisig2.js'; +import { decodeInitializeMultisig2Instruction } from './initializeMultisig2.js'; import type { DecodedMintToInstruction } from './mintTo.js'; import { decodeMintToInstruction } from './mintTo.js'; import type { DecodedMintToCheckedInstruction } from './mintToChecked.js'; @@ -48,7 +50,7 @@ import { TokenInstruction } from './types.js'; import type { DecodedUiAmountToAmountInstruction } from './uiAmountToAmount.js'; import { decodeUiAmountToAmountInstruction } from './uiAmountToAmount.js'; -/** TODO: docs */ +/** Union type for all decoded instructions */ export type DecodedInstruction = | DecodedInitializeMintInstruction | DecodedInitializeAccountInstruction @@ -69,14 +71,12 @@ export type DecodedInstruction = | DecodedInitializeAccount2Instruction | DecodedSyncNativeInstruction | DecodedInitializeAccount3Instruction + | DecodedInitializeMultisig2Instruction | DecodedInitializeMint2Instruction | DecodedAmountToUiAmountInstruction - | DecodedUiAmountToAmountInstruction - // | DecodedInitializeMultisig2Instruction - // TODO: implement ^ and remove `never` - | never; + | DecodedUiAmountToAmountInstruction; -/** TODO: docs */ +/** Decode and validate any token instruction */ export function decodeInstruction( instruction: TransactionInstruction, programId = TOKEN_PROGRAM_ID, @@ -106,11 +106,11 @@ export function decodeInstruction( if (type === TokenInstruction.SyncNative) return decodeSyncNativeInstruction(instruction, programId); if (type === TokenInstruction.InitializeAccount3) return decodeInitializeAccount3Instruction(instruction, programId); + if (type === TokenInstruction.InitializeMultisig2) + return decodeInitializeMultisig2Instruction(instruction, programId); if (type === TokenInstruction.InitializeMint2) return decodeInitializeMint2Instruction(instruction, programId); if (type === TokenInstruction.AmountToUiAmount) return decodeAmountToUiAmountInstruction(instruction, programId); if (type === TokenInstruction.UiAmountToAmount) return decodeUiAmountToAmountInstruction(instruction, programId); - // TODO: implement - if (type === TokenInstruction.InitializeMultisig2) throw new TokenInvalidInstructionTypeError(); throw new TokenInvalidInstructionTypeError(); } @@ -213,21 +213,21 @@ export function isSyncNativeInstruction(decoded: DecodedInstruction): decoded is return decoded.data.instruction === TokenInstruction.SyncNative; } -/** TODO: docs */ +/** Type guard to check if instruction is InitializeAccount3 */ export function isInitializeAccount3Instruction( decoded: DecodedInstruction, ): decoded is DecodedInitializeAccount3Instruction { return decoded.data.instruction === TokenInstruction.InitializeAccount3; } -/** TODO: docs, implement */ -// export function isInitializeMultisig2Instruction( -// decoded: DecodedInstruction -// ): decoded is DecodedInitializeMultisig2Instruction { -// return decoded.data.instruction === TokenInstruction.InitializeMultisig2; -// } +/** Type guard to check if instruction is InitializeMultisig2 */ +export function isInitializeMultisig2Instruction( + decoded: DecodedInstruction, +): decoded is DecodedInitializeMultisig2Instruction { + return decoded.data.instruction === TokenInstruction.InitializeMultisig2; +} -/** TODO: docs */ +/** Type guard to check if instruction is InitializeMint2 */ export function isInitializeMint2Instruction( decoded: DecodedInstruction, ): decoded is DecodedInitializeMint2Instruction { diff --git a/clients/js-legacy/src/instructions/initializeMultisig2.ts b/clients/js-legacy/src/instructions/initializeMultisig2.ts index 434426c17..f4742e259 100644 --- a/clients/js-legacy/src/instructions/initializeMultisig2.ts +++ b/clients/js-legacy/src/instructions/initializeMultisig2.ts @@ -1 +1,141 @@ -export {}; // TODO: implement +import { struct, u8 } from '@solana/buffer-layout'; +import type { AccountMeta, Signer } from '@solana/web3.js'; +import { PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { TOKEN_PROGRAM_ID } from '../constants.js'; +import { + TokenInvalidInstructionDataError, + TokenInvalidInstructionKeysError, + TokenInvalidInstructionProgramError, + TokenInvalidInstructionTypeError, +} from '../errors.js'; +import { TokenInstruction } from './types.js'; + +/** InitializeMultisig2 instruction data */ +export interface InitializeMultisig2InstructionData { + instruction: TokenInstruction.InitializeMultisig2; + m: number; +} + +/** InitializeMultisig2 instruction layout */ +export const initializeMultisig2InstructionData = struct([ + u8('instruction'), + u8('m'), +]); + +/** + * Construct an InitializeMultisig2 instruction + * + * @param account Multisig account + * @param signers Full set of signers + * @param m Number of required signatures + * @param programId SPL Token program account + * + * @return Instruction to add to a transaction + */ +export function createInitializeMultisig2Instruction( + account: PublicKey, + signers: (Signer | PublicKey)[], + m: number, + programId = TOKEN_PROGRAM_ID, +): TransactionInstruction { + const keys = [{ pubkey: account, isSigner: false, isWritable: true }]; + for (const signer of signers) { + keys.push({ + pubkey: signer instanceof PublicKey ? signer : signer.publicKey, + isSigner: false, + isWritable: false, + }); + } + + const data = Buffer.alloc(initializeMultisig2InstructionData.span); + initializeMultisig2InstructionData.encode( + { + instruction: TokenInstruction.InitializeMultisig2, + m, + }, + data, + ); + + return new TransactionInstruction({ keys, programId, data }); +} + +/** A decoded, valid InitializeMultisig2 instruction */ +export interface DecodedInitializeMultisig2Instruction { + programId: PublicKey; + keys: { + account: AccountMeta; + signers: AccountMeta[]; + }; + data: { + instruction: TokenInstruction.InitializeMultisig2; + m: number; + }; +} + +/** + * Decode an InitializeMultisig2 instruction and validate it + * + * @param instruction Transaction instruction to decode + * @param programId SPL Token program account + * + * @return Decoded, valid instruction + */ +export function decodeInitializeMultisig2Instruction( + instruction: TransactionInstruction, + programId = TOKEN_PROGRAM_ID, +): DecodedInitializeMultisig2Instruction { + if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError(); + if (instruction.data.length !== initializeMultisig2InstructionData.span) + throw new TokenInvalidInstructionDataError(); + + const { + keys: { account, signers }, + data, + } = decodeInitializeMultisig2InstructionUnchecked(instruction); + if (data.instruction !== TokenInstruction.InitializeMultisig2) throw new TokenInvalidInstructionTypeError(); + if (!account || !signers.length) throw new TokenInvalidInstructionKeysError(); + + return { + programId, + keys: { + account, + signers, + }, + data, + }; +} + +/** A decoded, non-validated InitializeMultisig2 instruction */ +export interface DecodedInitializeMultisig2InstructionUnchecked { + programId: PublicKey; + keys: { + account: AccountMeta | undefined; + signers: AccountMeta[]; + }; + data: { + instruction: number; + m: number; + }; +} + +/** + * Decode an InitializeMultisig2 instruction without validating it + * + * @param instruction Transaction instruction to decode + * + * @return Decoded, non-validated instruction + */ +export function decodeInitializeMultisig2InstructionUnchecked({ + programId, + keys: [account, ...signers], + data, +}: TransactionInstruction): DecodedInitializeMultisig2InstructionUnchecked { + return { + programId, + keys: { + account, + signers, + }, + data: initializeMultisig2InstructionData.decode(data), + }; +}