diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index 2f81acbbc2..8bb19c3ca6 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -220,6 +220,14 @@ declare module '@solana/web3.js' { amount: number, ): Transaction; static assign(from: PublicKey, programId: PublicKey): Transaction; + static createAccountWithSeed( + from: PublicKey, + newAccount: PublicKey, + seed: string, + lamports: number, + space: number, + programId: PublicKey, + ): Transaction; } // === src/validator-info.js === diff --git a/web3.js/src/layout.js b/web3.js/src/layout.js index 6c3e06b709..667b4ca7ca 100644 --- a/web3.js/src/layout.js +++ b/web3.js/src/layout.js @@ -43,5 +43,25 @@ export const rustString = (property: string = 'string') => { return _encode(data, buffer, offset); }; + rsl.alloc = str => { + return ( + BufferLayout.u32().span + + BufferLayout.u32().span + + Buffer.from(str, 'utf8').length + ); + }; + return rsl; }; + +export function getAlloc(type: Object, fields: Object): number { + let alloc = 0; + type.layout.fields.forEach(item => { + if (item.span >= 0) { + alloc += item.span; + } else if (typeof item.alloc === 'function') { + alloc += item.alloc(fields[item.property]); + } + }); + return alloc; +} diff --git a/web3.js/src/system-program.js b/web3.js/src/system-program.js index e1b923b8bb..fad0fcdd2c 100644 --- a/web3.js/src/system-program.js +++ b/web3.js/src/system-program.js @@ -66,6 +66,7 @@ export class SystemInstruction extends TransactionInstruction { get fromPublicKey(): PublicKey | null { if ( this.type == SystemInstructionLayout.Create || + this.type == SystemInstructionLayout.CreateWithSeed || this.type == SystemInstructionLayout.Transfer ) { return this.keys[0].pubkey; @@ -80,6 +81,7 @@ export class SystemInstruction extends TransactionInstruction { get toPublicKey(): PublicKey | null { if ( this.type == SystemInstructionLayout.Create || + this.type == SystemInstructionLayout.CreateWithSeed || this.type == SystemInstructionLayout.Transfer ) { return this.keys[1].pubkey; @@ -95,7 +97,10 @@ export class SystemInstruction extends TransactionInstruction { const data = this.type.layout.decode(this.data); if (this.type == SystemInstructionLayout.Transfer) { return data.amount; - } else if (this.type == SystemInstructionLayout.Create) { + } else if ( + this.type == SystemInstructionLayout.Create || + this.type == SystemInstructionLayout.CreateWithSeed + ) { return data.lamports; } return null; @@ -139,13 +144,25 @@ const SystemInstructionLayout = Object.freeze({ BufferLayout.ns64('amount'), ]), }, + CreateWithSeed: { + index: 3, + layout: BufferLayout.struct([ + BufferLayout.u32('instruction'), + Layout.rustString('seed'), + BufferLayout.ns64('lamports'), + BufferLayout.ns64('space'), + Layout.publicKey('programId'), + ]), + }, }); /** * Populate a buffer of instruction data using the SystemInstructionType */ function encodeData(type: SystemInstructionType, fields: Object): Buffer { - const data = Buffer.alloc(type.layout.span); + const allocLength = + type.layout.span >= 0 ? type.layout.span : Layout.getAlloc(type, fields); + const data = Buffer.alloc(allocLength); const layoutFields = Object.assign({instruction: type.index}, fields); type.layout.encode(layoutFields, data); return data; @@ -221,4 +238,34 @@ export class SystemProgram { data, }); } + + /** + * Generate a Transaction that creates a new account at + * an address generated with `from`, a seed, and programId + */ + static createAccountWithSeed( + from: PublicKey, + newAccount: PublicKey, + seed: string, + lamports: number, + space: number, + programId: PublicKey, + ): Transaction { + const type = SystemInstructionLayout.CreateWithSeed; + const data = encodeData(type, { + seed, + lamports, + space, + programId: programId.toBuffer(), + }); + + return new Transaction().add({ + keys: [ + {pubkey: from, isSigner: true, isWritable: true}, + {pubkey: newAccount, isSigner: false, isWritable: true}, + ], + programId: SystemProgram.programId, + data, + }); + } } diff --git a/web3.js/test/system-program.test.js b/web3.js/test/system-program.test.js index 8965628dd3..c690b68782 100644 --- a/web3.js/test/system-program.test.js +++ b/web3.js/test/system-program.test.js @@ -50,6 +50,25 @@ test('assign', () => { // TODO: Validate transaction contents more }); +test('createAccountWithSeed', () => { + const from = new Account(); + const newAccount = new Account(); + let transaction; + + transaction = SystemProgram.createAccountWithSeed( + from.publicKey, + newAccount.publicKey, + 'hi there', + 123, + BudgetProgram.space, + BudgetProgram.programId, + ); + + expect(transaction.keys).toHaveLength(2); + expect(transaction.programId).toEqual(SystemProgram.programId); + // TODO: Validate transaction contents more +}); + test('SystemInstruction create', () => { const from = new Account(); const to = new Account(); @@ -104,6 +123,30 @@ test('SystemInstruction assign', () => { expect(systemInstruction.programId).toEqual(SystemProgram.programId); }); +test('SystemInstruction createWithSeed', () => { + const from = new Account(); + const to = new Account(); + const program = new Account(); + const amount = 42; + const space = 100; + const recentBlockhash = 'EETubP5AKHgjPAhzPAFcb8BAY1hMH639CWCFTqi3hq1k'; // Arbitrary known recentBlockhash + const create = SystemProgram.createAccountWithSeed( + from.publicKey, + to.publicKey, + 'hi there', + amount, + space, + program.publicKey, + ); + const transaction = new Transaction({recentBlockhash}).add(create); + + const systemInstruction = SystemInstruction.from(transaction.instructions[0]); + expect(systemInstruction.fromPublicKey).toEqual(from.publicKey); + expect(systemInstruction.toPublicKey).toEqual(to.publicKey); + expect(systemInstruction.amount).toEqual(amount); + expect(systemInstruction.programId).toEqual(SystemProgram.programId); +}); + test('non-SystemInstruction error', () => { const from = new Account(); const program = new Account();