diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 2c604ef84e..1cda3ad3a0 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -21,6 +21,7 @@ import { ViewStateResult, AccountView, AccessKeyView, + AccessKeyViewRaw, CodeResult, AccessKeyList, AccessKeyInfoView, @@ -202,7 +203,7 @@ export class Account { const block = await this.connection.provider.block({ finality: 'final' }); const blockHash = block.header.hash; - const nonce = ++accessKey.nonce; + const nonce = accessKey.nonce.add(new BN(1)); return await signTransaction( receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId ); @@ -294,13 +295,18 @@ export class Account { } try { - const accessKey = await this.connection.provider.query({ + const rawAccessKey = await this.connection.provider.query({ request_type: 'view_access_key', account_id: this.accountId, public_key: publicKey.toString(), finality: 'optimistic' }); + // store nonce as BN to preserve precision on big number + const accessKey = { + ...rawAccessKey, + nonce: new BN(rawAccessKey.nonce), + }; // this function can be called multiple times and retrieve the same access key // this checks to see if the access key was already retrieved and cached while // the above network call was in flight. To keep nonce values in line, we return @@ -580,13 +586,8 @@ export class Account { account_id: this.accountId, finality: 'optimistic' }); - // A breaking API change introduced extra information into the - // response, so it now returns an object with a `keys` field instead - // of an array: https://github.com/nearprotocol/nearcore/pull/1789 - if (Array.isArray(response)) { - return response; - } - return response.keys; + // Replace raw nonce into a new BN + return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: new BN(key.access_key.nonce) } })); } /** diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index 4e69c6c01d..9ac7b0ffc7 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -4,6 +4,7 @@ */ import { SignedTransaction } from '../transaction'; +import BN from 'bn.js'; export interface SyncInfo { latest_block_hash: string; @@ -181,7 +182,7 @@ export interface Chunk { export interface Transaction { actions: Array; hash: string; - nonce: bigint; + nonce: BN; public_key: string; receiver_id: string; signature: string; @@ -352,10 +353,14 @@ export interface FunctionCallPermissionView { method_names: string[]; }; } -export interface AccessKeyView extends QueryResponseKind { +export interface AccessKeyViewRaw extends QueryResponseKind { nonce: number; permission: 'FullAccess' | FunctionCallPermissionView; } +export interface AccessKeyView extends QueryResponseKind { + nonce: BN; + permission: 'FullAccess' | FunctionCallPermissionView; +} export interface AccessKeyInfoView { public_key: string; diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index 97cbbe3d57..3d2f72a1f9 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -102,7 +102,7 @@ export class Signature extends Assignable { export class Transaction extends Assignable { signerId: string; publicKey: PublicKey; - nonce: number; + nonce: BN; receiverId: string; actions: Action[]; blockHash: Uint8Array; @@ -220,7 +220,7 @@ export const SCHEMA = new Map([ ]}], ]); -export function createTransaction(signerId: string, publicKey: PublicKey, receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array): Transaction { +export function createTransaction(signerId: string, publicKey: PublicKey, receiverId: string, nonce: BN | string | number, actions: Action[], blockHash: Uint8Array): Transaction { return new Transaction({ signerId, publicKey, nonce, receiverId, actions, blockHash }); } @@ -243,7 +243,7 @@ async function signTransactionObject(transaction: Transaction, signer: Signer, a } export async function signTransaction(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; -export async function signTransaction(receiverId: string, nonce: number, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; +export async function signTransaction(receiverId: string, nonce: BN, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; export async function signTransaction(...args): Promise<[Uint8Array, SignedTransaction]> { if (args[0].constructor === Transaction) { const [ transaction, signer, accountId, networkId ] = args; diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts index b103c0cd0c..c7e685d4f9 100644 --- a/packages/near-api-js/src/wallet-account.ts +++ b/packages/near-api-js/src/wallet-account.ts @@ -16,6 +16,7 @@ import { KeyPair, PublicKey } from './utils'; import { baseDecode } from 'borsh'; import { Connection } from './connection'; import { serialize } from 'borsh'; +import BN from 'bn.js'; const LOGIN_WALLET_URL_SUFFIX = '/login/'; const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; @@ -318,7 +319,7 @@ export class ConnectedWalletAccount extends Account { const publicKey = PublicKey.from(accessKey.public_key); // TODO: Cache & listen for nonce updates for given access key - const nonce = accessKey.access_key.nonce + 1; + const nonce = accessKey.access_key.nonce.add(new BN(1)); const transaction = createTransaction(this.accountId, publicKey, receiverId, nonce, actions, blockHash); await this.walletConnection.requestSignTransactions({ transactions: [transaction], diff --git a/packages/near-api-js/test/serialize.test.js b/packages/near-api-js/test/serialize.test.js index c87015650d..c726c54b24 100644 --- a/packages/near-api-js/test/serialize.test.js +++ b/packages/near-api-js/test/serialize.test.js @@ -1,5 +1,6 @@ const fs = require('fs'); +const BN = require('bn.js'); const nearApi = require('../src/index'); class Test extends nearApi.utils.enums.Assignable { @@ -111,4 +112,55 @@ describe('roundtrip test', () => { }); } } +}); + +describe('serialize and deserialize on different types of nonce', () => { + const actions = [ + nearApi.transactions.transfer(1), + ]; + const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + const targetNonce = new BN(1); + test('number typed nonce', async() => { + const transaction = nearApi.transactions.createTransaction( + 'test.near', + nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 'whatever.near', + 1, + actions, + blockHash); + const serialized = transaction.encode(); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000'); + const deserialized = nearApi.transactions.Transaction.decode(serialized); + expect(deserialized.encode()).toEqual(serialized); + expect(deserialized.nonce.toString()).toEqual(targetNonce.toString()); + + }); + test('string typed nonce', async() => { + const transaction = nearApi.transactions.createTransaction( + 'test.near', + nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 'whatever.near', + '1', + actions, + blockHash); + const serialized = transaction.encode(); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000'); + const deserialized = nearApi.transactions.Transaction.decode(serialized); + expect(deserialized.encode()).toEqual(serialized); + expect(deserialized.nonce.toString()).toEqual(targetNonce.toString()); + }); + test('BN typed nonce', async() => { + const transaction = nearApi.transactions.createTransaction( + 'test.near', + nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 'whatever.near', + new BN(1), + actions, + blockHash); + const serialized = transaction.encode(); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000'); + const deserialized = nearApi.transactions.Transaction.decode(serialized); + expect(deserialized.encode()).toEqual(serialized); + expect(deserialized.nonce.toString()).toEqual(targetNonce.toString()); + }); }); \ No newline at end of file