import * as BufferLayout from 'buffer-layout'; import {encodeData, decodeData, InstructionType} from './instruction'; import * as Layout from './layout'; import {NONCE_ACCOUNT_LENGTH} from './nonce-account'; import {PublicKey} from './publickey'; import {SYSVAR_RECENT_BLOCKHASHES_PUBKEY, SYSVAR_RENT_PUBKEY} from './sysvar'; import {Transaction, TransactionInstruction} from './transaction'; import {toBuffer} from './util/to-buffer'; /** * Create account system transaction params * @typedef {Object} CreateAccountParams * @property {PublicKey} fromPubkey * @property {PublicKey} newAccountPubkey * @property {number} lamports * @property {number} space * @property {PublicKey} programId */ export type CreateAccountParams = { fromPubkey: PublicKey; newAccountPubkey: PublicKey; lamports: number; space: number; programId: PublicKey; }; /** * Transfer system transaction params * @typedef {Object} TransferParams * @property {PublicKey} fromPubkey * @property {PublicKey} toPubkey * @property {number} lamports */ export type TransferParams = { fromPubkey: PublicKey; toPubkey: PublicKey; lamports: number; }; /** * Assign system transaction params * @typedef {Object} AssignParams * @property {PublicKey} accountPubkey * @property {PublicKey} programId */ export type AssignParams = { accountPubkey: PublicKey; programId: PublicKey; }; /** * Create account with seed system transaction params * @typedef {Object} CreateAccountWithSeedParams * @property {PublicKey} fromPubkey * @property {PublicKey} newAccountPubkey * @property {PublicKey} basePubkey * @property {string} seed * @property {number} lamports * @property {number} space * @property {PublicKey} programId */ export type CreateAccountWithSeedParams = { fromPubkey: PublicKey; newAccountPubkey: PublicKey; basePubkey: PublicKey; seed: string; lamports: number; space: number; programId: PublicKey; }; /** * Create nonce account system transaction params * @typedef {Object} CreateNonceAccountParams * @property {PublicKey} fromPubkey * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey * @property {number} lamports */ export type CreateNonceAccountParams = { fromPubkey: PublicKey; noncePubkey: PublicKey; authorizedPubkey: PublicKey; lamports: number; }; /** * Create nonce account with seed system transaction params * @typedef {Object} CreateNonceAccountWithSeedParams * @property {PublicKey} fromPubkey * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey * @property {PublicKey} basePubkey * @property {string} seed * @property {number} lamports */ export type CreateNonceAccountWithSeedParams = { fromPubkey: PublicKey; noncePubkey: PublicKey; authorizedPubkey: PublicKey; lamports: number; basePubkey: PublicKey; seed: string; }; /** * Initialize nonce account system instruction params * @typedef {Object} InitializeNonceParams * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey */ export type InitializeNonceParams = { noncePubkey: PublicKey; authorizedPubkey: PublicKey; }; /** * Advance nonce account system instruction params * @typedef {Object} AdvanceNonceParams * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey */ export type AdvanceNonceParams = { noncePubkey: PublicKey; authorizedPubkey: PublicKey; }; /** * Withdraw nonce account system transaction params * @typedef {Object} WithdrawNonceParams * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey * @property {PublicKey} toPubkey * @property {number} lamports */ export type WithdrawNonceParams = { noncePubkey: PublicKey; authorizedPubkey: PublicKey; toPubkey: PublicKey; lamports: number; }; /** * Authorize nonce account system transaction params * @typedef {Object} AuthorizeNonceParams * @property {PublicKey} noncePubkey * @property {PublicKey} authorizedPubkey * @property {PublicKey} newAuthorizedPubkey */ export type AuthorizeNonceParams = { noncePubkey: PublicKey; authorizedPubkey: PublicKey; newAuthorizedPubkey: PublicKey; }; /** * Allocate account system transaction params * @typedef {Object} AllocateParams * @property {PublicKey} accountPubkey * @property {number} space */ export type AllocateParams = { accountPubkey: PublicKey; space: number; }; /** * Allocate account with seed system transaction params * @typedef {Object} AllocateWithSeedParams * @property {PublicKey} accountPubkey * @property {PublicKey} basePubkey * @property {string} seed * @property {number} space * @property {PublicKey} programId */ export type AllocateWithSeedParams = { accountPubkey: PublicKey; basePubkey: PublicKey; seed: string; space: number; programId: PublicKey; }; /** * Assign account with seed system transaction params * @typedef {Object} AssignWithSeedParams * @property {PublicKey} accountPubkey * @property {PublicKey} basePubkey * @property {string} seed * @property {PublicKey} programId */ export type AssignWithSeedParams = { accountPubkey: PublicKey; basePubkey: PublicKey; seed: string; programId: PublicKey; }; /** * Transfer with seed system transaction params * @typedef {Object} TransferWithSeedParams * @property {PublicKey} fromPubkey * @property {PublicKey} basePubkey * @property {PublicKey} toPubkey * @property {number} lamports * @property {string} seed * @property {PublicKey} programId */ export type TransferWithSeedParams = { fromPubkey: PublicKey; basePubkey: PublicKey; toPubkey: PublicKey; lamports: number; seed: string; programId: PublicKey; }; /** * System Instruction class */ export class SystemInstruction { /** * @internal */ constructor() {} /** * Decode a system instruction and retrieve the instruction type. */ static decodeInstructionType( instruction: TransactionInstruction, ): SystemInstructionType { this.checkProgramId(instruction.programId); const instructionTypeLayout = BufferLayout.u32('instruction'); const typeIndex = instructionTypeLayout.decode(instruction.data); let type: SystemInstructionType | undefined; for (const [ixType, layout] of Object.entries(SYSTEM_INSTRUCTION_LAYOUTS)) { if (layout.index == typeIndex) { type = ixType as SystemInstructionType; break; } } if (!type) { throw new Error('Instruction type incorrect; not a SystemInstruction'); } return type; } /** * Decode a create account system instruction and retrieve the instruction params. */ static decodeCreateAccount( instruction: TransactionInstruction, ): CreateAccountParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 2); const {lamports, space, programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.Create, instruction.data, ); return { fromPubkey: instruction.keys[0].pubkey, newAccountPubkey: instruction.keys[1].pubkey, lamports, space, programId: new PublicKey(programId), }; } /** * Decode a transfer system instruction and retrieve the instruction params. */ static decodeTransfer(instruction: TransactionInstruction): TransferParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 2); const {lamports} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.Transfer, instruction.data, ); return { fromPubkey: instruction.keys[0].pubkey, toPubkey: instruction.keys[1].pubkey, lamports, }; } /** * Decode a transfer with seed system instruction and retrieve the instruction params. */ static decodeTransferWithSeed( instruction: TransactionInstruction, ): TransferWithSeedParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 3); const {lamports, seed, programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed, instruction.data, ); return { fromPubkey: instruction.keys[0].pubkey, basePubkey: instruction.keys[1].pubkey, toPubkey: instruction.keys[2].pubkey, lamports, seed, programId: new PublicKey(programId), }; } /** * Decode an allocate system instruction and retrieve the instruction params. */ static decodeAllocate(instruction: TransactionInstruction): AllocateParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 1); const {space} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.Allocate, instruction.data, ); return { accountPubkey: instruction.keys[0].pubkey, space, }; } /** * Decode an allocate with seed system instruction and retrieve the instruction params. */ static decodeAllocateWithSeed( instruction: TransactionInstruction, ): AllocateWithSeedParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 1); const {base, seed, space, programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed, instruction.data, ); return { accountPubkey: instruction.keys[0].pubkey, basePubkey: new PublicKey(base), seed, space, programId: new PublicKey(programId), }; } /** * Decode an assign system instruction and retrieve the instruction params. */ static decodeAssign(instruction: TransactionInstruction): AssignParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 1); const {programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.Assign, instruction.data, ); return { accountPubkey: instruction.keys[0].pubkey, programId: new PublicKey(programId), }; } /** * Decode an assign with seed system instruction and retrieve the instruction params. */ static decodeAssignWithSeed( instruction: TransactionInstruction, ): AssignWithSeedParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 1); const {base, seed, programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed, instruction.data, ); return { accountPubkey: instruction.keys[0].pubkey, basePubkey: new PublicKey(base), seed, programId: new PublicKey(programId), }; } /** * Decode a create account with seed system instruction and retrieve the instruction params. */ static decodeCreateWithSeed( instruction: TransactionInstruction, ): CreateAccountWithSeedParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 2); const {base, seed, lamports, space, programId} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed, instruction.data, ); return { fromPubkey: instruction.keys[0].pubkey, newAccountPubkey: instruction.keys[1].pubkey, basePubkey: new PublicKey(base), seed, lamports, space, programId: new PublicKey(programId), }; } /** * Decode a nonce initialize system instruction and retrieve the instruction params. */ static decodeNonceInitialize( instruction: TransactionInstruction, ): InitializeNonceParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 3); const {authorized} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount, instruction.data, ); return { noncePubkey: instruction.keys[0].pubkey, authorizedPubkey: new PublicKey(authorized), }; } /** * Decode a nonce advance system instruction and retrieve the instruction params. */ static decodeNonceAdvance( instruction: TransactionInstruction, ): AdvanceNonceParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 3); decodeData( SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount, instruction.data, ); return { noncePubkey: instruction.keys[0].pubkey, authorizedPubkey: instruction.keys[2].pubkey, }; } /** * Decode a nonce withdraw system instruction and retrieve the instruction params. */ static decodeNonceWithdraw( instruction: TransactionInstruction, ): WithdrawNonceParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 5); const {lamports} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount, instruction.data, ); return { noncePubkey: instruction.keys[0].pubkey, toPubkey: instruction.keys[1].pubkey, authorizedPubkey: instruction.keys[4].pubkey, lamports, }; } /** * Decode a nonce authorize system instruction and retrieve the instruction params. */ static decodeNonceAuthorize( instruction: TransactionInstruction, ): AuthorizeNonceParams { this.checkProgramId(instruction.programId); this.checkKeyLength(instruction.keys, 2); const {authorized} = decodeData( SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount, instruction.data, ); return { noncePubkey: instruction.keys[0].pubkey, authorizedPubkey: instruction.keys[1].pubkey, newAuthorizedPubkey: new PublicKey(authorized), }; } /** * @internal */ static checkProgramId(programId: PublicKey) { if (!programId.equals(SystemProgram.programId)) { throw new Error('invalid instruction; programId is not SystemProgram'); } } /** * @internal */ static checkKeyLength(keys: Array, expectedLength: number) { if (keys.length < expectedLength) { throw new Error( `invalid instruction; found ${keys.length} keys, expected at least ${expectedLength}`, ); } } } /** * An enumeration of valid SystemInstructionType's */ export type SystemInstructionType = | 'AdvanceNonceAccount' | 'Allocate' | 'AllocateWithSeed' | 'Assign' | 'AssignWithSeed' | 'AuthorizeNonceAccount' | 'Create' | 'CreateWithSeed' | 'InitializeNonceAccount' | 'Transfer' | 'TransferWithSeed' | 'WithdrawNonceAccount'; /** * An enumeration of valid system InstructionType's * @internal */ export const SYSTEM_INSTRUCTION_LAYOUTS: { [type in SystemInstructionType]: InstructionType; } = Object.freeze({ Create: { index: 0, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), Layout.publicKey('programId'), ]), }, Assign: { index: 1, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('programId'), ]), }, Transfer: { index: 2, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), ]), }, CreateWithSeed: { index: 3, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('base'), Layout.rustString('seed'), BufferLayout.ns64('lamports'), BufferLayout.ns64('space'), Layout.publicKey('programId'), ]), }, AdvanceNonceAccount: { index: 4, layout: BufferLayout.struct([BufferLayout.u32('instruction')]), }, WithdrawNonceAccount: { index: 5, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), ]), }, InitializeNonceAccount: { index: 6, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('authorized'), ]), }, AuthorizeNonceAccount: { index: 7, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('authorized'), ]), }, Allocate: { index: 8, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.ns64('space'), ]), }, AllocateWithSeed: { index: 9, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('base'), Layout.rustString('seed'), BufferLayout.ns64('space'), Layout.publicKey('programId'), ]), }, AssignWithSeed: { index: 10, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), Layout.publicKey('base'), Layout.rustString('seed'), Layout.publicKey('programId'), ]), }, TransferWithSeed: { index: 11, layout: BufferLayout.struct([ BufferLayout.u32('instruction'), BufferLayout.ns64('lamports'), Layout.rustString('seed'), Layout.publicKey('programId'), ]), }, }); /** * Factory class for transactions to interact with the System program */ export class SystemProgram { /** * @internal */ constructor() {} /** * Public key that identifies the System program */ static programId: PublicKey = new PublicKey( '11111111111111111111111111111111', ); /** * Generate a transaction instruction that creates a new account */ static createAccount(params: CreateAccountParams): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.Create; const data = encodeData(type, { lamports: params.lamports, space: params.space, programId: toBuffer(params.programId.toBytes()), }); return new TransactionInstruction({ keys: [ {pubkey: params.fromPubkey, isSigner: true, isWritable: true}, {pubkey: params.newAccountPubkey, isSigner: true, isWritable: true}, ], programId: this.programId, data, }); } /** * Generate a transaction instruction that transfers lamports from one account to another */ static transfer( params: TransferParams | TransferWithSeedParams, ): TransactionInstruction { let data; let keys; if ('basePubkey' in params) { const type = SYSTEM_INSTRUCTION_LAYOUTS.TransferWithSeed; data = encodeData(type, { lamports: params.lamports, seed: params.seed, programId: toBuffer(params.programId.toBytes()), }); keys = [ {pubkey: params.fromPubkey, isSigner: false, isWritable: true}, {pubkey: params.basePubkey, isSigner: true, isWritable: false}, {pubkey: params.toPubkey, isSigner: false, isWritable: true}, ]; } else { const type = SYSTEM_INSTRUCTION_LAYOUTS.Transfer; data = encodeData(type, {lamports: params.lamports}); keys = [ {pubkey: params.fromPubkey, isSigner: true, isWritable: true}, {pubkey: params.toPubkey, isSigner: false, isWritable: true}, ]; } return new TransactionInstruction({ keys, programId: this.programId, data, }); } /** * Generate a transaction instruction that assigns an account to a program */ static assign( params: AssignParams | AssignWithSeedParams, ): TransactionInstruction { let data; let keys; if ('basePubkey' in params) { const type = SYSTEM_INSTRUCTION_LAYOUTS.AssignWithSeed; data = encodeData(type, { base: toBuffer(params.basePubkey.toBytes()), seed: params.seed, programId: toBuffer(params.programId.toBytes()), }); keys = [ {pubkey: params.accountPubkey, isSigner: false, isWritable: true}, {pubkey: params.basePubkey, isSigner: true, isWritable: false}, ]; } else { const type = SYSTEM_INSTRUCTION_LAYOUTS.Assign; data = encodeData(type, { programId: toBuffer(params.programId.toBytes()), }); keys = [{pubkey: params.accountPubkey, isSigner: true, isWritable: true}]; } return new TransactionInstruction({ keys, programId: this.programId, data, }); } /** * Generate a transaction instruction that creates a new account at * an address generated with `from`, a seed, and programId */ static createAccountWithSeed( params: CreateAccountWithSeedParams, ): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.CreateWithSeed; const data = encodeData(type, { base: toBuffer(params.basePubkey.toBytes()), seed: params.seed, lamports: params.lamports, space: params.space, programId: toBuffer(params.programId.toBytes()), }); let keys = [ {pubkey: params.fromPubkey, isSigner: true, isWritable: true}, {pubkey: params.newAccountPubkey, isSigner: false, isWritable: true}, ]; if (params.basePubkey != params.fromPubkey) { keys.push({pubkey: params.basePubkey, isSigner: true, isWritable: false}); } return new TransactionInstruction({ keys, programId: this.programId, data, }); } /** * Generate a transaction that creates a new Nonce account */ static createNonceAccount( params: CreateNonceAccountParams | CreateNonceAccountWithSeedParams, ): Transaction { const transaction = new Transaction(); if ('basePubkey' in params && 'seed' in params) { transaction.add( SystemProgram.createAccountWithSeed({ fromPubkey: params.fromPubkey, newAccountPubkey: params.noncePubkey, basePubkey: params.basePubkey, seed: params.seed, lamports: params.lamports, space: NONCE_ACCOUNT_LENGTH, programId: this.programId, }), ); } else { transaction.add( SystemProgram.createAccount({ fromPubkey: params.fromPubkey, newAccountPubkey: params.noncePubkey, lamports: params.lamports, space: NONCE_ACCOUNT_LENGTH, programId: this.programId, }), ); } const initParams = { noncePubkey: params.noncePubkey, authorizedPubkey: params.authorizedPubkey, }; transaction.add(this.nonceInitialize(initParams)); return transaction; } /** * Generate an instruction to initialize a Nonce account */ static nonceInitialize( params: InitializeNonceParams, ): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.InitializeNonceAccount; const data = encodeData(type, { authorized: toBuffer(params.authorizedPubkey.toBytes()), }); const instructionData = { keys: [ {pubkey: params.noncePubkey, isSigner: false, isWritable: true}, { pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, isSigner: false, isWritable: false, }, {pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false}, ], programId: this.programId, data, }; return new TransactionInstruction(instructionData); } /** * Generate an instruction to advance the nonce in a Nonce account */ static nonceAdvance(params: AdvanceNonceParams): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.AdvanceNonceAccount; const data = encodeData(type); const instructionData = { keys: [ {pubkey: params.noncePubkey, isSigner: false, isWritable: true}, { pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, isSigner: false, isWritable: false, }, {pubkey: params.authorizedPubkey, isSigner: true, isWritable: false}, ], programId: this.programId, data, }; return new TransactionInstruction(instructionData); } /** * Generate a transaction instruction that withdraws lamports from a Nonce account */ static nonceWithdraw(params: WithdrawNonceParams): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.WithdrawNonceAccount; const data = encodeData(type, {lamports: params.lamports}); return new TransactionInstruction({ keys: [ {pubkey: params.noncePubkey, isSigner: false, isWritable: true}, {pubkey: params.toPubkey, isSigner: false, isWritable: true}, { pubkey: SYSVAR_RECENT_BLOCKHASHES_PUBKEY, isSigner: false, isWritable: false, }, { pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false, }, {pubkey: params.authorizedPubkey, isSigner: true, isWritable: false}, ], programId: this.programId, data, }); } /** * Generate a transaction instruction that authorizes a new PublicKey as the authority * on a Nonce account. */ static nonceAuthorize(params: AuthorizeNonceParams): TransactionInstruction { const type = SYSTEM_INSTRUCTION_LAYOUTS.AuthorizeNonceAccount; const data = encodeData(type, { authorized: toBuffer(params.newAuthorizedPubkey.toBytes()), }); return new TransactionInstruction({ keys: [ {pubkey: params.noncePubkey, isSigner: false, isWritable: true}, {pubkey: params.authorizedPubkey, isSigner: true, isWritable: false}, ], programId: this.programId, data, }); } /** * Generate a transaction instruction that allocates space in an account without funding */ static allocate( params: AllocateParams | AllocateWithSeedParams, ): TransactionInstruction { let data; let keys; if ('basePubkey' in params) { const type = SYSTEM_INSTRUCTION_LAYOUTS.AllocateWithSeed; data = encodeData(type, { base: toBuffer(params.basePubkey.toBytes()), seed: params.seed, space: params.space, programId: toBuffer(params.programId.toBytes()), }); keys = [ {pubkey: params.accountPubkey, isSigner: false, isWritable: true}, {pubkey: params.basePubkey, isSigner: true, isWritable: false}, ]; } else { const type = SYSTEM_INSTRUCTION_LAYOUTS.Allocate; data = encodeData(type, { space: params.space, }); keys = [{pubkey: params.accountPubkey, isSigner: true, isWritable: true}]; } return new TransactionInstruction({ keys, programId: this.programId, data, }); } }