diff --git a/web3.js/src/transaction.js b/web3.js/src/transaction.js index 3bb62b496c..4610c5fbf3 100644 --- a/web3.js/src/transaction.js +++ b/web3.js/src/transaction.js @@ -96,9 +96,22 @@ type SignaturePubkeyPair = {| */ type TransactionCtorFields = {| recentBlockhash?: Blockhash | null, + nonceInfo?: NonceInformation | null, signatures?: Array, |}; +/** + * NonceInformation to be used to build a Transaction. + * + * @typedef {Object} NonceInformation + * @property {nonce} The current Nonce blockhash + * @property {nonceInstruction} The NonceAdvance Instruction + */ +type NonceInformation = {| + nonce: Blockhash, + nonceInstruction: TransactionInstruction, +|}; + /** * Transaction class */ @@ -129,6 +142,12 @@ export class Transaction { */ recentBlockhash: Blockhash | null; + /** + * Optional Nonce information. If populated, transaction will use a durable + * Nonce hash instead of a recentBlockhash. Must be populated by the caller + */ + nonceInfo: NonceInformation | null; + /** * Construct an empty Transaction */ @@ -164,6 +183,11 @@ export class Transaction { * @private */ _getSignData(): Buffer { + const {nonceInfo} = this; + if (nonceInfo) { + this.recentBlockhash = nonceInfo.nonce; + this.instructions.unshift(nonceInfo.nonceInstruction); + } const {recentBlockhash} = this; if (!recentBlockhash) { throw new Error('Transaction recentBlockhash required'); diff --git a/web3.js/test/transaction.test.js b/web3.js/test/transaction.test.js index aa5894e501..e3f99678f3 100644 --- a/web3.js/test/transaction.test.js +++ b/web3.js/test/transaction.test.js @@ -4,6 +4,7 @@ import nacl from 'tweetnacl'; import {Account} from '../src/account'; import {PublicKey} from '../src/publickey'; import {Transaction} from '../src/transaction'; +import {StakeProgram} from '../src/stake-program'; import {SystemProgram} from '../src/system-program'; test('signPartial', () => { @@ -78,6 +79,54 @@ test('dedup signatures', () => { orgTransaction.sign(account1); }); +test('use nonce', () => { + const account1 = new Account(); + const account2 = new Account(); + const nonceAccount = new Account(); + const nonce = account2.publicKey.toBase58(); // Fake Nonce hash + + const nonceInfo = { + nonce, + nonceInstruction: SystemProgram.nonceAdvance( + nonceAccount.publicKey, + account1.publicKey, + ), + }; + + const transferTransaction = new Transaction({nonceInfo}).add( + SystemProgram.transfer(account1.publicKey, account2.publicKey, 123), + ); + transferTransaction.sign(account1); + + let expectedData = Buffer.alloc(4); + expectedData.writeInt32LE(4, 0); + + expect(transferTransaction.instructions).toHaveLength(2); + expect(transferTransaction.instructions[0].programId).toEqual( + SystemProgram.programId, + ); + expect(transferTransaction.instructions[0].data).toEqual(expectedData); + expect(transferTransaction.recentBlockhash).toEqual(nonce); + + const stakeAccount = new Account(); + const voteAccount = new Account(); + const stakeTransaction = new Transaction({nonceInfo}).add( + StakeProgram.delegate( + stakeAccount.publicKey, + account1.publicKey, + voteAccount.publicKey, + ), + ); + stakeTransaction.sign(account1); + + expect(stakeTransaction.instructions).toHaveLength(2); + expect(stakeTransaction.instructions[0].programId).toEqual( + SystemProgram.programId, + ); + expect(stakeTransaction.instructions[0].data).toEqual(expectedData); + expect(stakeTransaction.recentBlockhash).toEqual(nonce); +}); + test('parse wire format and serialize', () => { const keypair = nacl.sign.keyPair.fromSeed( Uint8Array.from(Array(32).fill(8)),