diff --git a/packages/client/src/transactions/composers/signed_transaction_composer.ts b/packages/client/src/transactions/composers/signed_transaction_composer.ts index 173342092..ff63599e6 100644 --- a/packages/client/src/transactions/composers/signed_transaction_composer.ts +++ b/packages/client/src/transactions/composers/signed_transaction_composer.ts @@ -7,7 +7,7 @@ import { SignedTransactionOptions, TransactionOptions, } from '../../interfaces'; -import { signTransaction } from '../sign_and_send'; +import { getSignerNonce, signTransaction } from '../sign_and_send'; import { TransactionComposer } from './transaction_composer'; import { Account } from '@near-js/accounts'; @@ -74,6 +74,7 @@ export class SignedTransactionComposer extends TransactionComposer { /** * Sign and send the composed transaction + * Fetch the block hash and nonce from the provider if not already set. * @param blockReference block to use for determining hash */ async signAndSend(blockReference: BlockReference = { finality: 'final' }) { @@ -81,9 +82,15 @@ export class SignedTransactionComposer extends TransactionComposer { const { signedTransaction } = await this.toSignedTransaction({ publicKey: this.publicKey || await this.account.getSigner().getPublicKey(), blockHash: this.blockHash || (await this.account.provider.viewBlock(blockReference))?.header?.hash, + nonce: this.nonce || (await getSignerNonce({ account: this.sender, deps: { rpcProvider: this.account.provider, signer: this.account.getSigner() } })) + 1n }); const outcome = await this.account.provider.sendTransaction(signedTransaction); + if (outcome.final_execution_status !== 'NONE') { + this.nonce = signedTransaction.transaction.nonce + 1n; + } else if (!this.nonce) { + this.nonce = signedTransaction.transaction.nonce; + } return { outcome, result: getTransactionLastResult(outcome) as T, diff --git a/packages/client/src/transactions/composers/transaction_composer.ts b/packages/client/src/transactions/composers/transaction_composer.ts index ed5824248..efe84f925 100644 --- a/packages/client/src/transactions/composers/transaction_composer.ts +++ b/packages/client/src/transactions/composers/transaction_composer.ts @@ -37,6 +37,7 @@ export class TransactionComposer { /** * Validate and return the object used for Transaction instantiation * @param transaction transaction values to override composed transaction fields + * @throws an error if any required fields are missing * @private */ private buildTransactionObject(transaction?: TransactionOptions) { @@ -49,8 +50,24 @@ export class TransactionComposer { signerId: transaction?.sender || this.sender, }; - if (!tx.actions.length || !tx.blockHash || !tx.nonce || !tx.publicKey || !tx.receiverId || !tx.signerId) { - throw new Error(`invalid transaction: ${JSON.stringify(tx)}`); + const missingFields = []; + if (!tx.actions.length) missingFields.push('actions'); + if (!tx.blockHash) missingFields.push('blockHash'); + if (!tx.nonce) missingFields.push('nonce'); + if (!tx.publicKey) missingFields.push('publicKey'); + if (!tx.receiverId) missingFields.push('receiverId'); + if (!tx.signerId) missingFields.push('signerId'); + + if (missingFields.length > 0) { + const safeTxPreview = { + signerId: tx.signerId, + receiverId: tx.receiverId, + publicKey: tx.publicKey.toString(), + blockHash: tx.blockHash.toString(), + nonce: tx.nonce?.toString?.(), + actions: tx.actions?.map((a) => Object.keys(a)[0]), + }; + throw new Error(`invalid transaction: missing ${missingFields.join(', ')}\nPreciew: ${JSON.stringify(safeTxPreview, null, 2)}`); } return tx;