diff --git a/web3.js/module.d.ts b/web3.js/module.d.ts index 3a12dff437..7c531a5504 100644 --- a/web3.js/module.d.ts +++ b/web3.js/module.d.ts @@ -606,6 +606,15 @@ declare module '@solana/web3.js' { lamports: number; }; + export type CreateNonceAccountWithSeedParams = { + fromPubkey: PublicKey; + noncePubkey: PublicKey; + authorizedPubkey: PublicKey; + lamports: number; + basePubkey: PublicKey; + seed: string; + }; + export type InitializeNonceParams = { noncePubkey: PublicKey; authorizedPubkey: PublicKey; @@ -638,7 +647,9 @@ declare module '@solana/web3.js' { static createAccountWithSeed( params: CreateAccountWithSeedParams, ): Transaction; - static createNonceAccount(params: CreateNonceAccountParams): Transaction; + static createNonceAccount( + params: CreateNonceAccountParams | CreateNonceAccountWithSeedParams, + ): Transaction; static nonceAdvance(params: AdvanceNonceParams): TransactionInstruction; static nonceWithdraw(params: WithdrawNonceParams): Transaction; static nonceAuthorize(params: AuthorizeNonceParams): Transaction; diff --git a/web3.js/module.flow.js b/web3.js/module.flow.js index d06b7646e8..4fe07793b2 100644 --- a/web3.js/module.flow.js +++ b/web3.js/module.flow.js @@ -621,6 +621,15 @@ declare module '@solana/web3.js' { lamports: number, |}; + declare export type CreateNonceAccountWithSeedParams = {| + fromPubkey: PublicKey, + noncePubkey: PublicKey, + authorizedPubkey: PublicKey, + lamports: number, + basePubkey: PublicKey, + seed: string, + |}; + declare export type InitializeNonceParams = {| noncePubkey: PublicKey, authorizedPubkey: PublicKey, @@ -653,7 +662,9 @@ declare module '@solana/web3.js' { static createAccountWithSeed( params: CreateAccountWithSeedParams, ): Transaction; - static createNonceAccount(params: CreateNonceAccountParams): Transaction; + static createNonceAccount( + params: CreateNonceAccountParams | CreateNonceAccountWithSeedParams, + ): Transaction; static nonceAdvance(params: AdvanceNonceParams): TransactionInstruction; static nonceWithdraw(params: WithdrawNonceParams): Transaction; static nonceAuthorize(params: AuthorizeNonceParams): Transaction; diff --git a/web3.js/src/system-program.js b/web3.js/src/system-program.js index 1b10a4a7e6..c64c3b8800 100644 --- a/web3.js/src/system-program.js +++ b/web3.js/src/system-program.js @@ -73,9 +73,11 @@ export type CreateAccountWithSeedParams = {| /** * Create nonce account system transaction params - * @typedef {Object} AssignParams + * @typedef {Object} CreateNonceAccountParams * @property {PublicKey} fromPubkey - * @property {PublicKey} programId + * @property {PublicKey} noncePubkey + * @property {PublicKey} authorizedPubkey + * @property {number} lamports */ export type CreateNonceAccountParams = {| fromPubkey: PublicKey, @@ -84,6 +86,25 @@ export type CreateNonceAccountParams = {| 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 @@ -517,14 +538,29 @@ export class SystemProgram { /** * Generate a Transaction that creates a new Nonce account */ - static createNonceAccount(params: CreateNonceAccountParams): Transaction { - let transaction = SystemProgram.createAccount({ - fromPubkey: params.fromPubkey, - newAccountPubkey: params.noncePubkey, - lamports: params.lamports, - space: NONCE_ACCOUNT_LENGTH, - programId: this.programId, - }); + static createNonceAccount( + params: CreateNonceAccountParams | CreateNonceAccountWithSeedParams, + ): Transaction { + let transaction; + if (params.basePubkey && params.seed) { + transaction = 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 = SystemProgram.createAccount({ + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: this.programId, + }); + } const initParams = { noncePubkey: params.noncePubkey, diff --git a/web3.js/test/nonce.test.js b/web3.js/test/nonce.test.js index 12214ea2ee..b182c50695 100644 --- a/web3.js/test/nonce.test.js +++ b/web3.js/test/nonce.test.js @@ -2,7 +2,7 @@ import bs58 from 'bs58'; -import {Account, Connection, SystemProgram} from '../src'; +import {Account, Connection, SystemProgram, PublicKey} from '../src'; import {NONCE_ACCOUNT_LENGTH} from '../src/nonce-account'; import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch'; import {mockGetRecentBlockhash} from './mockrpc/get-recent-blockhash'; @@ -13,6 +13,17 @@ if (!mockRpcEnabled) { jest.setTimeout(30000); } +const expectedData = (authorizedPubkey: PublicKey): string => { + const expectedData = Buffer.alloc(NONCE_ACCOUNT_LENGTH); + expectedData.writeInt32LE(0, 0); // Version, 4 bytes + expectedData.writeInt32LE(1, 4); // State, 4 bytes + authorizedPubkey.toBuffer().copy(expectedData, 8); // authorizedPubkey, 32 bytes + const mockNonce = new Account(); + mockNonce.publicKey.toBuffer().copy(expectedData, 40); // Hash, 32 bytes + expectedData.writeUInt16LE(5000, 72); // feeCalculator, 8 bytes + return bs58.encode(expectedData); +}; + test('create and query nonce account', async () => { const from = new Account(); const nonceAccount = new Account(); @@ -32,7 +43,6 @@ test('create and query nonce account', async () => { const minimumAmount = await connection.getMinimumBalanceForRentExemption( NONCE_ACCOUNT_LENGTH, - 'recent', ); mockRpc.push([ @@ -95,14 +105,6 @@ test('create and query nonce account', async () => { }); await connection.sendTransaction(transaction, from, nonceAccount); - const expectedData = Buffer.alloc(NONCE_ACCOUNT_LENGTH); - expectedData.writeInt32LE(0, 0); // Version, 4 bytes - expectedData.writeInt32LE(1, 4); // State, 4 bytes - from.publicKey.toBuffer().copy(expectedData, 8); // authorizedPubkey, 32 bytes - const mockNonce = new Account(); - mockNonce.publicKey.toBuffer().copy(expectedData, 40); // Hash, 32 bytes - expectedData.writeUInt16LE(5000, 72); // feeCalculator, 8 bytes - mockRpc.push([ url, { @@ -118,17 +120,133 @@ test('create and query nonce account', async () => { value: { owner: '11111111111111111111111111111111', lamports: minimumAmount, - data: bs58.encode(expectedData), + data: expectedData(from.publicKey), executable: false, }, }, }, ]); // - const nonceAccountData = await connection.getNonce( - nonceAccount.publicKey, - 'recent', - ); + const nonceAccountData = await connection.getNonce(nonceAccount.publicKey); + if (nonceAccountData === null) { + expect(nonceAccountData).not.toBeNull(); + return; + } + expect(nonceAccountData.authorizedPubkey).toEqual(from.publicKey); + expect(bs58.decode(nonceAccountData.nonce).length).toBeGreaterThan(30); +}); + +test('create and query nonce account with seed', async () => { + const from = new Account(); + const seed = 'seed'; + const noncePubkey = await PublicKey.createWithSeed( + from.publicKey, + seed, + SystemProgram.programId, + ); + const connection = new Connection(url, 'recent'); + + mockRpc.push([ + url, + { + method: 'getMinimumBalanceForRentExemption', + params: [NONCE_ACCOUNT_LENGTH, {commitment: 'recent'}], + }, + { + error: null, + result: 50, + }, + ]); + + const minimumAmount = await connection.getMinimumBalanceForRentExemption( + NONCE_ACCOUNT_LENGTH, + ); + + mockRpc.push([ + url, + { + method: 'requestAirdrop', + params: [ + from.publicKey.toBase58(), + minimumAmount * 2, + {commitment: 'recent'}, + ], + }, + { + error: null, + result: + '1WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', + }, + ]); + + await connection.requestAirdrop(from.publicKey, minimumAmount * 2); + + mockRpc.push([ + url, + { + method: 'getBalance', + params: [from.publicKey.toBase58(), {commitment: 'recent'}], + }, + { + error: null, + result: { + context: { + slot: 11, + }, + value: minimumAmount * 2, + }, + }, + ]); + + const balance = await connection.getBalance(from.publicKey); + expect(balance).toBe(minimumAmount * 2); + + mockGetRecentBlockhash('recent'); + mockRpc.push([ + url, + { + method: 'sendTransaction', + }, + { + error: null, + result: + '3WE5w4B7v59x6qjyC4FbG2FEKYKQfvsJwqSxNVmtMjT8TQ31hsZieDHcSgqzxiAoTL56n2w5TncjqEKjLhtF4Vk', + }, + ]); + + const transaction = SystemProgram.createNonceAccount({ + fromPubkey: from.publicKey, + noncePubkey: noncePubkey, + basePubkey: from.publicKey, + seed, + authorizedPubkey: from.publicKey, + lamports: minimumAmount, + }); + await connection.sendTransaction(transaction, from); + + mockRpc.push([ + url, + { + method: 'getAccountInfo', + params: [noncePubkey.toBase58(), {commitment: 'recent'}], + }, + { + error: null, + result: { + context: { + slot: 11, + }, + value: { + owner: '11111111111111111111111111111111', + lamports: minimumAmount, + data: expectedData(from.publicKey), + executable: false, + }, + }, + }, + ]); + // + const nonceAccountData = await connection.getNonce(noncePubkey); if (nonceAccountData === null) { expect(nonceAccountData).not.toBeNull(); return; diff --git a/web3.js/test/system-program.test.js b/web3.js/test/system-program.test.js index f30024d831..8aafca0620 100644 --- a/web3.js/test/system-program.test.js +++ b/web3.js/test/system-program.test.js @@ -112,6 +112,43 @@ test('createNonceAccount', () => { ); }); +test('createNonceAccount with seed', () => { + const fromPubkey = new Account().publicKey; + const params = { + fromPubkey, + noncePubkey: new Account().publicKey, + authorizedPubkey: fromPubkey, + basePubkey: fromPubkey, + seed: 'hi there', + lamports: 123, + }; + + const transaction = SystemProgram.createNonceAccount(params); + expect(transaction.instructions).toHaveLength(2); + const [createInstruction, initInstruction] = transaction.instructions; + + const createParams = { + fromPubkey: params.fromPubkey, + newAccountPubkey: params.noncePubkey, + basePubkey: fromPubkey, + seed: 'hi there', + lamports: params.lamports, + space: NONCE_ACCOUNT_LENGTH, + programId: SystemProgram.programId, + }; + expect(createParams).toEqual( + SystemInstruction.decodeCreateWithSeed(createInstruction), + ); + + const initParams = { + noncePubkey: params.noncePubkey, + authorizedPubkey: fromPubkey, + }; + expect(initParams).toEqual( + SystemInstruction.decodeNonceInitialize(initInstruction), + ); +}); + test('nonceAdvance', () => { const params = { noncePubkey: new Account().publicKey,