From d439492e1a1b8abaf0642d5bcbc7c72316397a4f Mon Sep 17 00:00:00 2001 From: Danny Cho Date: Mon, 22 Aug 2022 10:14:26 +1200 Subject: [PATCH 1/4] feat: wrapped nonce from accesskey with BN --- packages/near-api-js/src/account.ts | 18 +++++++++++++----- packages/near-api-js/src/providers/provider.ts | 9 +++++++-- packages/near-api-js/src/transaction.ts | 6 +++--- packages/near-api-js/src/wallet-account.ts | 3 ++- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 2c604ef84e..1fe92573bc 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,15 @@ export class Account { account_id: this.accountId, finality: 'optimistic' }); + // Replace raw nonce into a new BN + const newResponse = { keys: response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: new BN(key.access_key.nonce) } }))}; // 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; + if (Array.isArray(newResponse)) { + return newResponse; } - return response.keys; + return newResponse.keys; } /** 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], From 8a8f205a4e4f3ae4172011fe83a4cba712251824 Mon Sep 17 00:00:00 2001 From: Danny Cho Date: Mon, 22 Aug 2022 10:24:14 +1200 Subject: [PATCH 2/4] test: added new tests against nonce types --- packages/near-api-js/test/serialize.test.js | 52 +++++++++++++++++++++ 1 file changed, 52 insertions(+) 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 From a89f33e9b0cf5e1703c9270247cd507cb6a86095 Mon Sep 17 00:00:00 2001 From: Danny Cho Date: Mon, 22 Aug 2022 10:26:37 +1200 Subject: [PATCH 3/4] fix: lint fix --- packages/near-api-js/src/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 1fe92573bc..2bca4add8b 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -306,7 +306,7 @@ export class Account { 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 From 4f77940945cdb0313585a79c3b93d0fe6e53220a Mon Sep 17 00:00:00 2001 From: Danny Cho Date: Thu, 1 Sep 2022 17:20:02 +1200 Subject: [PATCH 4/4] refactor: clean up unused code --- packages/near-api-js/src/account.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 2bca4add8b..1cda3ad3a0 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -587,14 +587,7 @@ export class Account { finality: 'optimistic' }); // Replace raw nonce into a new BN - const newResponse = { keys: response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: new BN(key.access_key.nonce) } }))}; - // 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(newResponse)) { - return newResponse; - } - return newResponse.keys; + return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: new BN(key.access_key.nonce) } })); } /**