From 5089f6f6436fc987fda75ab6604ef5f73de5eacd Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:21:50 -0700 Subject: [PATCH 01/84] refactor: create core package --- packages/core/package.json | 22 ++++++++++++++++++++++ packages/core/tsconfig.json | 28 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 packages/core/package.json create mode 100644 packages/core/tsconfig.json diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..d3b6e4b15 --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,22 @@ +{ + "name": "@near-js/core", + "version": "0.0.1", + "description": "Core dependencies for the NEAR JavaScript API", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bn.js": "5.2.1", + "borsh": "^0.7.0", + "js-sha256": "^0.9.0", + "tweetnacl": "^1.0.1" + }, + "devDependencies": { + "@types/node": "^18.7.14" + } +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} From f11e1e8a972531377cda540c1ee7bbb2dac10d3b Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:32:01 -0700 Subject: [PATCH 02/84] refactor: add @near-js/core as a dependency --- packages/near-api-js/package.json | 3 ++- pnpm-lock.yaml | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 095e81c4d..1eee8335b 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -11,6 +11,7 @@ "browser": "lib/browser-index.js", "types": "lib/index.d.ts", "dependencies": { + "@near-js/core": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", @@ -79,4 +80,4 @@ "browser-exports.js" ], "author": "NEAR Inc" -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e9f92bc1e..b456dc2f5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,8 +40,24 @@ importers: homedir: 0.6.0 near-api-js: link:../near-api-js + packages/core: + specifiers: + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + js-sha256: ^0.9.0 + tweetnacl: ^1.0.1 + dependencies: + bn.js: 5.2.1 + borsh: 0.7.0 + js-sha256: 0.9.0 + tweetnacl: 1.0.3 + devDependencies: + '@types/node': 18.7.14 + packages/near-api-js: specifiers: + '@near-js/core': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 '@types/node': ^18.7.14 @@ -73,6 +89,7 @@ importers: tweetnacl: ^1.0.1 uglifyify: ^5.0.1 dependencies: + '@near-js/core': link:../core ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 From de0e6db1f72127cf3ee990edd294d5b737227426 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:32:46 -0700 Subject: [PATCH 03/84] refactor: core exports --- packages/core/src/index.ts | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 packages/core/src/index.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 000000000..8699c7d1b --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,3 @@ +export { KeyPair, KeyPairEd25519, KeyType, Signature, PublicKey } from './key_pair'; +export { InMemoryKeyStore, KeyStore } from './key_store'; +export { InMemorySigner, Signer } from './signer'; From b9a966d8d08faeb3a131d461d420d36909c14a68 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:33:20 -0700 Subject: [PATCH 04/84] refactor: move signer to core --- packages/core/src/signer/in_memory_signer.ts | 80 ++++++++++++++ packages/core/src/signer/index.ts | 2 + packages/core/src/signer/signer.ts | 27 +++++ packages/near-api-js/src/signer.ts | 106 +------------------ 4 files changed, 110 insertions(+), 105 deletions(-) create mode 100644 packages/core/src/signer/in_memory_signer.ts create mode 100644 packages/core/src/signer/index.ts create mode 100644 packages/core/src/signer/signer.ts diff --git a/packages/core/src/signer/in_memory_signer.ts b/packages/core/src/signer/in_memory_signer.ts new file mode 100644 index 000000000..c39a2b509 --- /dev/null +++ b/packages/core/src/signer/in_memory_signer.ts @@ -0,0 +1,80 @@ +import sha256 from 'js-sha256'; + +import { Signer } from './signer'; +import { KeyPair, PublicKey, Signature } from '../key_pair'; +import { InMemoryKeyStore, KeyStore } from '../key_store'; + +/** + * Signs using in memory key store. + */ +export class InMemorySigner extends Signer { + readonly keyStore: KeyStore; + + constructor(keyStore: KeyStore) { + super(); + this.keyStore = keyStore; + } + + /** + * Creates a single account Signer instance with account, network and keyPair provided. + * + * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). + * + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account to assign the key pair to + * @param keyPair The keyPair to use for signing + */ + static async fromKeyPair(networkId: string, accountId: string, keyPair: KeyPair): Promise { + const keyStore = new InMemoryKeyStore(); + await keyStore.setKey(networkId, accountId, keyPair); + return new InMemorySigner(keyStore); + } + + /** + * Creates a public key for the account given + * @param accountId The NEAR account to assign a public key to + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @returns {Promise} + */ + async createKey(accountId: string, networkId: string): Promise { + const keyPair = KeyPair.fromRandom('ed25519'); + await this.keyStore.setKey(networkId, accountId, keyPair); + return keyPair.getPublicKey(); + } + + /** + * Gets the existing public key for a given account + * @param accountId The NEAR account to assign a public key to + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @returns {Promise} Returns the public key or null if not found + */ + async getPublicKey(accountId?: string, networkId?: string): Promise { + const keyPair = await this.keyStore.getKey(networkId, accountId); + if (keyPair === null) { + return null; + } + return keyPair.getPublicKey(); + } + + /** + * @param message A message to be signed, typically a serialized transaction + * @param accountId the NEAR account signing the message + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @returns {Promise} + */ + async signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise { + const hash = new Uint8Array(sha256.sha256.array(message)); + if (!accountId) { + throw new Error('InMemorySigner requires provided account id'); + } + const keyPair = await this.keyStore.getKey(networkId, accountId); + if (keyPair === null) { + throw new Error(`Key for ${accountId} not found in ${networkId}`); + } + return keyPair.sign(hash); + } + + toString(): string { + return `InMemorySigner(${this.keyStore})`; + } +} diff --git a/packages/core/src/signer/index.ts b/packages/core/src/signer/index.ts new file mode 100644 index 000000000..9a5ebfb0b --- /dev/null +++ b/packages/core/src/signer/index.ts @@ -0,0 +1,2 @@ +export { InMemorySigner } from './in_memory_signer'; +export { Signer } from './signer'; diff --git a/packages/core/src/signer/signer.ts b/packages/core/src/signer/signer.ts new file mode 100644 index 000000000..172fa0d39 --- /dev/null +++ b/packages/core/src/signer/signer.ts @@ -0,0 +1,27 @@ +import { Signature, PublicKey } from '../key_pair'; + +/** + * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. + */ +export abstract class Signer { + + /** + * Creates new key and returns public key. + */ + abstract createKey(accountId: string, networkId?: string): Promise; + + /** + * Returns public key for given account / network. + * @param accountId accountId to retrieve from. + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + abstract getPublicKey(accountId?: string, networkId?: string): Promise; + + /** + * Signs given message, by first hashing with sha256. + * @param message message to sign. + * @param accountId accountId to use for signing. + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + abstract signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; +} diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index 7b9d3f4d9..c9f83f0ba 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1,105 +1 @@ -import sha256 from 'js-sha256'; -import { Signature, KeyPair, PublicKey } from './utils/key_pair'; -import { KeyStore } from './key_stores/keystore'; -import { InMemoryKeyStore } from './key_stores/in_memory_key_store'; - -/** - * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. - */ -export abstract class Signer { - - /** - * Creates new key and returns public key. - */ - abstract createKey(accountId: string, networkId?: string): Promise; - - /** - * Returns public key for given account / network. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - abstract getPublicKey(accountId?: string, networkId?: string): Promise; - - /** - * Signs given message, by first hashing with sha256. - * @param message message to sign. - * @param accountId accountId to use for signing. - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - abstract signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; -} - -/** - * Signs using in memory key store. - */ -export class InMemorySigner extends Signer { - readonly keyStore: KeyStore; - - constructor(keyStore: KeyStore) { - super(); - this.keyStore = keyStore; - } - - /** - * Creates a single account Signer instance with account, network and keyPair provided. - * - * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). - * - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account to assign the key pair to - * @param keyPair The keyPair to use for signing - */ - static async fromKeyPair(networkId: string, accountId: string, keyPair: KeyPair): Promise { - const keyStore = new InMemoryKeyStore(); - await keyStore.setKey(networkId, accountId, keyPair); - return new InMemorySigner(keyStore); - } - - /** - * Creates a public key for the account given - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async createKey(accountId: string, networkId: string): Promise { - const keyPair = KeyPair.fromRandom('ed25519'); - await this.keyStore.setKey(networkId, accountId, keyPair); - return keyPair.getPublicKey(); - } - - /** - * Gets the existing public key for a given account - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} Returns the public key or null if not found - */ - async getPublicKey(accountId?: string, networkId?: string): Promise { - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - return null; - } - return keyPair.getPublicKey(); - } - - /** - * @param message A message to be signed, typically a serialized transaction - * @param accountId the NEAR account signing the message - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise { - const hash = new Uint8Array(sha256.sha256.array(message)); - if (!accountId) { - throw new Error('InMemorySigner requires provided account id'); - } - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - throw new Error(`Key for ${accountId} not found in ${networkId}`); - } - return keyPair.sign(hash); - } - - toString(): string { - return `InMemorySigner(${this.keyStore})`; - } -} +export { InMemorySigner, Signer } from '@near-js/core'; From d8fdcce821ba7f29ccbdc4b3395bf371c9ecf867 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:34:24 -0700 Subject: [PATCH 05/84] refactor: move keystore code --- .../core/src/key_store/in_memory_key_store.ts | 112 +++++++++++++++++ packages/core/src/key_store/index.ts | 2 + packages/core/src/key_store/keystore.ts | 16 +++ .../src/key_stores/in_memory_key_store.ts | 113 +----------------- .../near-api-js/src/key_stores/keystore.ts | 17 +-- 5 files changed, 132 insertions(+), 128 deletions(-) create mode 100644 packages/core/src/key_store/in_memory_key_store.ts create mode 100644 packages/core/src/key_store/index.ts create mode 100644 packages/core/src/key_store/keystore.ts diff --git a/packages/core/src/key_store/in_memory_key_store.ts b/packages/core/src/key_store/in_memory_key_store.ts new file mode 100644 index 000000000..873dbe81e --- /dev/null +++ b/packages/core/src/key_store/in_memory_key_store.ts @@ -0,0 +1,112 @@ +import { KeyPair } from '../key_pair'; +import { KeyStore } from './keystore'; + +/** + * Simple in-memory keystore for mainly for testing purposes. + * + * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) + * @example + * ```js + * import { connect, keyStores, utils } from 'near-api-js'; + * + * const privateKey = '.......'; + * const keyPair = utils.KeyPair.fromString(privateKey); + * + * const keyStore = new keyStores.InMemoryKeyStore(); + * keyStore.setKey('testnet', 'example-account.testnet', keyPair); + * + * const config = { + * keyStore, // instance of InMemoryKeyStore + * networkId: 'testnet', + * nodeUrl: 'https://rpc.testnet.near.org', + * walletUrl: 'https://wallet.testnet.near.org', + * helperUrl: 'https://helper.testnet.near.org', + * explorerUrl: 'https://explorer.testnet.near.org' + * }; + * + * // inside an async function + * const near = await connect(config) + * ``` + */ +export class InMemoryKeyStore extends KeyStore { + /** @hidden */ + private keys: { [key: string]: string }; + + constructor() { + super(); + this.keys = {}; + } + + /** + * Stores a {@link utils/key_pair!KeyPair} in in-memory storage item + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @param keyPair The key pair to store in local storage + */ + async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { + this.keys[`${accountId}:${networkId}`] = keyPair.toString(); + } + + /** + * Gets a {@link utils/key_pair!KeyPair} from in-memory storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @returns {Promise} + */ + async getKey(networkId: string, accountId: string): Promise { + const value = this.keys[`${accountId}:${networkId}`]; + if (!value) { + return null; + } + return KeyPair.fromString(value); + } + + /** + * Removes a {@link utils/key_pair!KeyPair} from in-memory storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + */ + async removeKey(networkId: string, accountId: string): Promise { + delete this.keys[`${accountId}:${networkId}`]; + } + + /** + * Removes all {@link utils/key_pair!KeyPair} from in-memory storage + */ + async clear(): Promise { + this.keys = {}; + } + + /** + * Get the network(s) from in-memory storage + * @returns {Promise} + */ + async getNetworks(): Promise { + const result = new Set(); + Object.keys(this.keys).forEach((key) => { + const parts = key.split(':'); + result.add(parts[1]); + }); + return Array.from(result.values()); + } + + /** + * Gets the account(s) from in-memory storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + async getAccounts(networkId: string): Promise { + const result = new Array(); + Object.keys(this.keys).forEach((key) => { + const parts = key.split(':'); + if (parts[parts.length - 1] === networkId) { + result.push(parts.slice(0, parts.length - 1).join(':')); + } + }); + return result; + } + + /** @hidden */ + toString(): string { + return 'InMemoryKeyStore'; + } +} diff --git a/packages/core/src/key_store/index.ts b/packages/core/src/key_store/index.ts new file mode 100644 index 000000000..54e11ea87 --- /dev/null +++ b/packages/core/src/key_store/index.ts @@ -0,0 +1,2 @@ +export { InMemoryKeyStore } from './in_memory_key_store'; +export { KeyStore } from './keystore'; diff --git a/packages/core/src/key_store/keystore.ts b/packages/core/src/key_store/keystore.ts new file mode 100644 index 000000000..12447c7e3 --- /dev/null +++ b/packages/core/src/key_store/keystore.ts @@ -0,0 +1,16 @@ +import { KeyPair } from '../key_pair'; + +/** + * KeyStores are passed to {@link near!Near} via {@link near!NearConfig} + * and are used by the {@link signer!InMemorySigner} to sign transactions. + * + * @see {@link connect} + */ +export abstract class KeyStore { + abstract setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise; + abstract getKey(networkId: string, accountId: string): Promise; + abstract removeKey(networkId: string, accountId: string): Promise; + abstract clear(): Promise; + abstract getNetworks(): Promise; + abstract getAccounts(networkId: string): Promise; +} diff --git a/packages/near-api-js/src/key_stores/in_memory_key_store.ts b/packages/near-api-js/src/key_stores/in_memory_key_store.ts index 2660e3bfe..d22720928 100644 --- a/packages/near-api-js/src/key_stores/in_memory_key_store.ts +++ b/packages/near-api-js/src/key_stores/in_memory_key_store.ts @@ -1,112 +1 @@ -import { KeyStore } from './keystore'; -import { KeyPair } from '../utils/key_pair'; - -/** - * Simple in-memory keystore for mainly for testing purposes. - * - * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) - * @example - * ```js - * import { connect, keyStores, utils } from 'near-api-js'; - * - * const privateKey = '.......'; - * const keyPair = utils.KeyPair.fromString(privateKey); - * - * const keyStore = new keyStores.InMemoryKeyStore(); - * keyStore.setKey('testnet', 'example-account.testnet', keyPair); - * - * const config = { - * keyStore, // instance of InMemoryKeyStore - * networkId: 'testnet', - * nodeUrl: 'https://rpc.testnet.near.org', - * walletUrl: 'https://wallet.testnet.near.org', - * helperUrl: 'https://helper.testnet.near.org', - * explorerUrl: 'https://explorer.testnet.near.org' - * }; - * - * // inside an async function - * const near = await connect(config) - * ``` - */ -export class InMemoryKeyStore extends KeyStore { - /** @hidden */ - private keys: { [key: string]: string }; - - constructor() { - super(); - this.keys = {}; - } - - /** - * Stores a {@link utils/key_pair!KeyPair} in in-memory storage item - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @param keyPair The key pair to store in local storage - */ - async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { - this.keys[`${accountId}:${networkId}`] = keyPair.toString(); - } - - /** - * Gets a {@link utils/key_pair!KeyPair} from in-memory storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @returns {Promise} - */ - async getKey(networkId: string, accountId: string): Promise { - const value = this.keys[`${accountId}:${networkId}`]; - if (!value) { - return null; - } - return KeyPair.fromString(value); - } - - /** - * Removes a {@link utils/key_pair!KeyPair} from in-memory storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - */ - async removeKey(networkId: string, accountId: string): Promise { - delete this.keys[`${accountId}:${networkId}`]; - } - - /** - * Removes all {@link utils/key_pair!KeyPair} from in-memory storage - */ - async clear(): Promise { - this.keys = {}; - } - - /** - * Get the network(s) from in-memory storage - * @returns {Promise} - */ - async getNetworks(): Promise { - const result = new Set(); - Object.keys(this.keys).forEach((key) => { - const parts = key.split(':'); - result.add(parts[1]); - }); - return Array.from(result.values()); - } - - /** - * Gets the account(s) from in-memory storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - async getAccounts(networkId: string): Promise { - const result = new Array(); - Object.keys(this.keys).forEach((key) => { - const parts = key.split(':'); - if (parts[parts.length - 1] === networkId) { - result.push(parts.slice(0, parts.length - 1).join(':')); - } - }); - return result; - } - - /** @hidden */ - toString(): string { - return 'InMemoryKeyStore'; - } -} +export { InMemoryKeyStore } from '@near-js/core'; diff --git a/packages/near-api-js/src/key_stores/keystore.ts b/packages/near-api-js/src/key_stores/keystore.ts index 289ef0508..87c95b7b5 100644 --- a/packages/near-api-js/src/key_stores/keystore.ts +++ b/packages/near-api-js/src/key_stores/keystore.ts @@ -1,16 +1 @@ -import { KeyPair } from '../utils/key_pair'; - -/** - * KeyStores are passed to {@link near!Near} via {@link near!NearConfig} - * and are used by the {@link signer!InMemorySigner} to sign transactions. - * - * @see {@link connect} - */ -export abstract class KeyStore { - abstract setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise; - abstract getKey(networkId: string, accountId: string): Promise; - abstract removeKey(networkId: string, accountId: string): Promise; - abstract clear(): Promise; - abstract getNetworks(): Promise; - abstract getAccounts(networkId: string): Promise; -} +export { KeyStore } from '@near-js/core'; From 0d77c35a50dff2ea2b25e469b359f43cd5aa2e6a Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 13:45:02 -0700 Subject: [PATCH 06/84] refactor: move key pair code --- packages/core/src/key_pair/constants.ts | 4 + packages/core/src/key_pair/index.ts | 4 + packages/core/src/key_pair/key_pair.ts | 95 +++++++++++ .../core/src/key_pair/key_pair_ed25519.ts | 59 +++++++ packages/core/src/key_pair/public_key.ts | 63 +++++++ packages/near-api-js/src/utils/key_pair.ts | 156 +----------------- 6 files changed, 232 insertions(+), 149 deletions(-) create mode 100644 packages/core/src/key_pair/constants.ts create mode 100644 packages/core/src/key_pair/index.ts create mode 100644 packages/core/src/key_pair/key_pair.ts create mode 100644 packages/core/src/key_pair/key_pair_ed25519.ts create mode 100644 packages/core/src/key_pair/public_key.ts diff --git a/packages/core/src/key_pair/constants.ts b/packages/core/src/key_pair/constants.ts new file mode 100644 index 000000000..d61ef34fa --- /dev/null +++ b/packages/core/src/key_pair/constants.ts @@ -0,0 +1,4 @@ +/** All supported key types */ +export enum KeyType { + ED25519 = 0, +} diff --git a/packages/core/src/key_pair/index.ts b/packages/core/src/key_pair/index.ts new file mode 100644 index 000000000..e2d24b432 --- /dev/null +++ b/packages/core/src/key_pair/index.ts @@ -0,0 +1,4 @@ +export { KeyType } from './constants'; +export { KeyPair, Signature } from './key_pair'; +export { KeyPairEd25519 } from './key_pair_ed25519'; +export { PublicKey } from './public_key'; diff --git a/packages/core/src/key_pair/key_pair.ts b/packages/core/src/key_pair/key_pair.ts new file mode 100644 index 000000000..a1e520ea5 --- /dev/null +++ b/packages/core/src/key_pair/key_pair.ts @@ -0,0 +1,95 @@ +import { baseEncode, baseDecode } from 'borsh'; +import nacl from 'tweetnacl'; + +import { KeyType } from './constants'; +import { PublicKey } from './public_key'; + +export interface Signature { + signature: Uint8Array; + publicKey: PublicKey; +} + +export abstract class KeyPair { + abstract sign(message: Uint8Array): Signature; + abstract verify(message: Uint8Array, signature: Uint8Array): boolean; + abstract toString(): string; + abstract getPublicKey(): PublicKey; + + /** + * @param curve Name of elliptical curve, case-insensitive + * @returns Random KeyPair based on the curve + */ + static fromRandom(curve: string): KeyPair { + switch (curve.toUpperCase()) { + case 'ED25519': return KeyPairEd25519.fromRandom(); + default: throw new Error(`Unknown curve ${curve}`); + } + } + + static fromString(encodedKey: string): KeyPair { + const parts = encodedKey.split(':'); + if (parts.length === 1) { + return new KeyPairEd25519(parts[0]); + } else if (parts.length === 2) { + switch (parts[0].toUpperCase()) { + case 'ED25519': return new KeyPairEd25519(parts[1]); + default: throw new Error(`Unknown curve: ${parts[0]}`); + } + } else { + throw new Error('Invalid encoded key format, must be :'); + } + } +} + +/** + * This class provides key pair functionality for Ed25519 curve: + * generating key pairs, encoding key pairs, signing and verifying. + */ +export class KeyPairEd25519 extends KeyPair { + readonly publicKey: PublicKey; + readonly secretKey: string; + + /** + * Construct an instance of key pair given a secret key. + * It's generally assumed that these are encoded in base58. + * @param {string} secretKey + */ + constructor(secretKey: string) { + super(); + const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); + this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); + this.secretKey = secretKey; + } + + /** + * Generate a new random keypair. + * @example + * const keyRandom = KeyPair.fromRandom(); + * keyRandom.publicKey + * // returns [PUBLIC_KEY] + * + * keyRandom.secretKey + * // returns [SECRET_KEY] + */ + static fromRandom() { + const newKeyPair = nacl.sign.keyPair(); + return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); + } + + sign(message: Uint8Array): Signature { + const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); + return { signature, publicKey: this.publicKey }; + } + + verify(message: Uint8Array, signature: Uint8Array): boolean { + return this.publicKey.verify(message, signature); + } + + toString(): string { + return `ed25519:${this.secretKey}`; + } + + getPublicKey(): PublicKey { + return this.publicKey; + } +} diff --git a/packages/core/src/key_pair/key_pair_ed25519.ts b/packages/core/src/key_pair/key_pair_ed25519.ts new file mode 100644 index 000000000..ee41381e2 --- /dev/null +++ b/packages/core/src/key_pair/key_pair_ed25519.ts @@ -0,0 +1,59 @@ +import { baseEncode, baseDecode } from 'borsh'; +import nacl from 'tweetnacl'; + +import { KeyType } from './constants'; +import { KeyPair, Signature } from './key_pair'; +import { PublicKey } from './public_key'; + +/** + * This class provides key pair functionality for Ed25519 curve: + * generating key pairs, encoding key pairs, signing and verifying. + */ +export class KeyPairEd25519 extends KeyPair { + readonly publicKey: PublicKey; + readonly secretKey: string; + + /** + * Construct an instance of key pair given a secret key. + * It's generally assumed that these are encoded in base58. + * @param {string} secretKey + */ + constructor(secretKey: string) { + super(); + const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); + this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); + this.secretKey = secretKey; + } + + /** + * Generate a new random keypair. + * @example + * const keyRandom = KeyPair.fromRandom(); + * keyRandom.publicKey + * // returns [PUBLIC_KEY] + * + * keyRandom.secretKey + * // returns [SECRET_KEY] + */ + static fromRandom() { + const newKeyPair = nacl.sign.keyPair(); + return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); + } + + sign(message: Uint8Array): Signature { + const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); + return { signature, publicKey: this.publicKey }; + } + + verify(message: Uint8Array, signature: Uint8Array): boolean { + return this.publicKey.verify(message, signature); + } + + toString(): string { + return `ed25519:${this.secretKey}`; + } + + getPublicKey(): PublicKey { + return this.publicKey; + } +} diff --git a/packages/core/src/key_pair/public_key.ts b/packages/core/src/key_pair/public_key.ts new file mode 100644 index 000000000..18736c3ec --- /dev/null +++ b/packages/core/src/key_pair/public_key.ts @@ -0,0 +1,63 @@ +import { baseEncode, baseDecode } from 'borsh'; +import nacl from 'tweetnacl'; + +import { KeyType } from './constants'; + +abstract class Assignable { + constructor(properties: any) { + Object.keys(properties).map((key: any) => { + (this as any)[key] = properties[key]; + }); + } +} + +function key_type_to_str(keyType: KeyType): string { + switch (keyType) { + case KeyType.ED25519: return 'ed25519'; + default: throw new Error(`Unknown key type ${keyType}`); + } +} + +function str_to_key_type(keyType: string): KeyType { + switch (keyType.toLowerCase()) { + case 'ed25519': return KeyType.ED25519; + default: throw new Error(`Unknown key type ${keyType}`); + } +} + +/** + * PublicKey representation that has type and bytes of the key. + */ +export class PublicKey extends Assignable { + keyType: KeyType; + data: Uint8Array; + + static from(value: string | PublicKey): PublicKey { + if (typeof value === 'string') { + return PublicKey.fromString(value); + } + return value; + } + + static fromString(encodedKey: string): PublicKey { + const parts = encodedKey.split(':'); + if (parts.length === 1) { + return new PublicKey({ keyType: KeyType.ED25519, data: baseDecode(parts[0]) }); + } else if (parts.length === 2) { + return new PublicKey({ keyType: str_to_key_type(parts[0]), data: baseDecode(parts[1]) }); + } else { + throw new Error('Invalid encoded key format, must be :'); + } + } + + toString(): string { + return `${key_type_to_str(this.keyType)}:${baseEncode(this.data)}`; + } + + verify(message: Uint8Array, signature: Uint8Array): boolean { + switch (this.keyType) { + case KeyType.ED25519: return nacl.sign.detached.verify(message, signature, this.data); + default: throw new Error(`Unknown key type ${this.keyType}`); + } + } +} diff --git a/packages/near-api-js/src/utils/key_pair.ts b/packages/near-api-js/src/utils/key_pair.ts index ee7daa463..cfe8c95b0 100644 --- a/packages/near-api-js/src/utils/key_pair.ts +++ b/packages/near-api-js/src/utils/key_pair.ts @@ -1,151 +1,9 @@ -import nacl from 'tweetnacl'; -import { base_encode, base_decode } from './serialize'; -import { Assignable } from './enums'; +export { + KeyPair, + KeyPairEd25519, + KeyType, + PublicKey, + Signature, +} from '@near-js/core'; export type Arrayish = string | ArrayLike; - -export interface Signature { - signature: Uint8Array; - publicKey: PublicKey; -} - -/** All supported key types */ -export enum KeyType { - ED25519 = 0, -} - -function key_type_to_str(keyType: KeyType): string { - switch (keyType) { - case KeyType.ED25519: return 'ed25519'; - default: throw new Error(`Unknown key type ${keyType}`); - } -} - -function str_to_key_type(keyType: string): KeyType { - switch (keyType.toLowerCase()) { - case 'ed25519': return KeyType.ED25519; - default: throw new Error(`Unknown key type ${keyType}`); - } -} - -/** - * PublicKey representation that has type and bytes of the key. - */ -export class PublicKey extends Assignable { - keyType: KeyType; - data: Uint8Array; - - static from(value: string | PublicKey): PublicKey { - if (typeof value === 'string') { - return PublicKey.fromString(value); - } - return value; - } - - static fromString(encodedKey: string): PublicKey { - const parts = encodedKey.split(':'); - if (parts.length === 1) { - return new PublicKey({ keyType: KeyType.ED25519, data: base_decode(parts[0]) }); - } else if (parts.length === 2) { - return new PublicKey({ keyType: str_to_key_type(parts[0]), data: base_decode(parts[1]) }); - } else { - throw new Error('Invalid encoded key format, must be :'); - } - } - - toString(): string { - return `${key_type_to_str(this.keyType)}:${base_encode(this.data)}`; - } - - verify(message: Uint8Array, signature: Uint8Array): boolean { - switch (this.keyType) { - case KeyType.ED25519: return nacl.sign.detached.verify(message, signature, this.data); - default: throw new Error(`Unknown key type ${this.keyType}`); - } - } -} - -export abstract class KeyPair { - abstract sign(message: Uint8Array): Signature; - abstract verify(message: Uint8Array, signature: Uint8Array): boolean; - abstract toString(): string; - abstract getPublicKey(): PublicKey; - - /** - * @param curve Name of elliptical curve, case-insensitive - * @returns Random KeyPair based on the curve - */ - static fromRandom(curve: string): KeyPair { - switch (curve.toUpperCase()) { - case 'ED25519': return KeyPairEd25519.fromRandom(); - default: throw new Error(`Unknown curve ${curve}`); - } - } - - static fromString(encodedKey: string): KeyPair { - const parts = encodedKey.split(':'); - if (parts.length === 1) { - return new KeyPairEd25519(parts[0]); - } else if (parts.length === 2) { - switch (parts[0].toUpperCase()) { - case 'ED25519': return new KeyPairEd25519(parts[1]); - default: throw new Error(`Unknown curve: ${parts[0]}`); - } - } else { - throw new Error('Invalid encoded key format, must be :'); - } - } -} - -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -export class KeyPairEd25519 extends KeyPair { - readonly publicKey: PublicKey; - readonly secretKey: string; - - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey: string) { - super(); - const keyPair = nacl.sign.keyPair.fromSecretKey(base_decode(secretKey)); - this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); - this.secretKey = secretKey; - } - - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom() { - const newKeyPair = nacl.sign.keyPair(); - return new KeyPairEd25519(base_encode(newKeyPair.secretKey)); - } - - sign(message: Uint8Array): Signature { - const signature = nacl.sign.detached(message, base_decode(this.secretKey)); - return { signature, publicKey: this.publicKey }; - } - - verify(message: Uint8Array, signature: Uint8Array): boolean { - return this.publicKey.verify(message, signature); - } - - toString(): string { - return `ed25519:${this.secretKey}`; - } - - getPublicKey(): PublicKey { - return this.publicKey; - } -} From ee89bf915bc98334d4abadc0b43e0d29d905dd25 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 17:17:01 -0700 Subject: [PATCH 07/84] refactor: common, non-exported location for Assignable --- packages/core/src/key_pair/public_key.ts | 9 +-------- packages/core/src/types.ts | 7 +++++++ 2 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 packages/core/src/types.ts diff --git a/packages/core/src/key_pair/public_key.ts b/packages/core/src/key_pair/public_key.ts index 18736c3ec..f200f3c1a 100644 --- a/packages/core/src/key_pair/public_key.ts +++ b/packages/core/src/key_pair/public_key.ts @@ -1,16 +1,9 @@ import { baseEncode, baseDecode } from 'borsh'; import nacl from 'tweetnacl'; +import { Assignable } from '../types'; import { KeyType } from './constants'; -abstract class Assignable { - constructor(properties: any) { - Object.keys(properties).map((key: any) => { - (this as any)[key] = properties[key]; - }); - } -} - function key_type_to_str(keyType: KeyType): string { switch (keyType) { case KeyType.ED25519: return 'ed25519'; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 000000000..f73132943 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,7 @@ +export abstract class Assignable { + constructor(properties: any) { + Object.keys(properties).map((key: any) => { + (this as any)[key] = properties[key]; + }); + } +} From 25dfba24358e678f18455e04dc14ee411b9bc80f Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 17:18:37 -0700 Subject: [PATCH 08/84] refactor: move transaction code --- .../core/src/transaction/action_creators.ts | 78 +++++ packages/core/src/transaction/actions.ts | 61 ++++ packages/core/src/transaction/index.ts | 31 ++ packages/core/src/transaction/schema.ts | 93 ++++++ packages/core/src/transaction/sign.ts | 40 +++ packages/core/src/transaction/transaction.ts | 46 +++ packages/near-api-js/src/transaction.ts | 289 ++---------------- 7 files changed, 381 insertions(+), 257 deletions(-) create mode 100644 packages/core/src/transaction/action_creators.ts create mode 100644 packages/core/src/transaction/actions.ts create mode 100644 packages/core/src/transaction/index.ts create mode 100644 packages/core/src/transaction/schema.ts create mode 100644 packages/core/src/transaction/sign.ts create mode 100644 packages/core/src/transaction/transaction.ts diff --git a/packages/core/src/transaction/action_creators.ts b/packages/core/src/transaction/action_creators.ts new file mode 100644 index 000000000..7fba1685a --- /dev/null +++ b/packages/core/src/transaction/action_creators.ts @@ -0,0 +1,78 @@ +import BN from 'bn.js'; + +import { PublicKey } from '../key_pair'; +import { + AccessKey, + AccessKeyPermission, + Action, + AddKey, + CreateAccount, + DeleteAccount, + DeleteKey, + DeployContract, + FullAccessPermission, + FunctionCall, + FunctionCallPermission, + Stake, + Transfer, +} from './actions'; + +export function fullAccessKey(): AccessKey { + return new AccessKey({ permission: new AccessKeyPermission({fullAccess: new FullAccessPermission({})}) }); +} + +export function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey { + return new AccessKey({ permission: new AccessKeyPermission({functionCall: new FunctionCallPermission({receiverId, allowance, methodNames})})}); +} + +export function createAccount(): Action { + return new Action({createAccount: new CreateAccount({}) }); +} + +export function deployContract(code: Uint8Array): Action { + return new Action({ deployContract: new DeployContract({code}) }); +} + +export function stringifyJsonOrBytes(args: any): Buffer { + const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; + const serializedArgs = isUint8Array ? args : Buffer.from(JSON.stringify(args)); + return serializedArgs; +} + +/** + * Constructs {@link Action} instance representing contract method call. + * + * @param methodName the name of the method to call + * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically + * or `Uint8Array` instance which represents bytes passed as is. + * @param gas max amount of gas that method call can use + * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call + * @param stringify Convert input arguments into bytes array. + * @param jsContract Is contract from JS SDK, skips stringification of arguments. + */ +export function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN, stringify = stringifyJsonOrBytes, jsContract = false): Action { + if(jsContract){ + return new Action({ functionCall: new FunctionCall({ methodName, args, gas, deposit }) }); + } + return new Action({ functionCall: new FunctionCall({ methodName, args: stringify(args), gas, deposit }) }); +} + +export function transfer(deposit: BN): Action { + return new Action({transfer: new Transfer({ deposit }) }); +} + +export function stake(stake: BN, publicKey: PublicKey): Action { + return new Action({stake: new Stake({ stake, publicKey }) }); +} + +export function addKey(publicKey: PublicKey, accessKey: AccessKey): Action { + return new Action({addKey: new AddKey({ publicKey, accessKey}) }); +} + +export function deleteKey(publicKey: PublicKey): Action { + return new Action({deleteKey: new DeleteKey({ publicKey }) }); +} + +export function deleteAccount(beneficiaryId: string): Action { + return new Action({deleteAccount: new DeleteAccount({ beneficiaryId }) }); +} diff --git a/packages/core/src/transaction/actions.ts b/packages/core/src/transaction/actions.ts new file mode 100644 index 000000000..0f7a123aa --- /dev/null +++ b/packages/core/src/transaction/actions.ts @@ -0,0 +1,61 @@ +import BN from 'bn.js'; + +import { Assignable } from '../types'; +import { PublicKey } from '../key_pair'; + +abstract class Enum { + enum: string; + + constructor(properties: any) { + if (Object.keys(properties).length !== 1) { + throw new Error('Enum can only take single value'); + } + Object.keys(properties).map((key: string) => { + (this as any)[key] = properties[key]; + this.enum = key; + }); + } +} + +export class FunctionCallPermission extends Assignable { + allowance?: BN; + receiverId: string; + methodNames: string[]; +} + +export class FullAccessPermission extends Assignable {} + +export class AccessKeyPermission extends Enum { + functionCall: FunctionCallPermission; + fullAccess: FullAccessPermission; +} + +export class AccessKey extends Assignable { + permission: AccessKeyPermission; +} + +export class IAction extends Assignable {} + +export class CreateAccount extends IAction {} +export class DeployContract extends IAction { code: Uint8Array; } +export class FunctionCall extends IAction { methodName: string; args: Uint8Array; gas: BN; deposit: BN; } +export class Transfer extends IAction { deposit: BN; } +export class Stake extends IAction { stake: BN; publicKey: PublicKey; } +export class AddKey extends IAction { publicKey: PublicKey; accessKey: AccessKey; } +export class DeleteKey extends IAction { publicKey: PublicKey; } +export class DeleteAccount extends IAction { beneficiaryId: string; } + +/** + * Contains a list of the valid transaction Actions available with this API + * @see {@link https://nomicon.io/RuntimeSpec/Actions.html | Actions Spec} + */ +export class Action extends Enum { + createAccount: CreateAccount; + deployContract: DeployContract; + functionCall: FunctionCall; + transfer: Transfer; + stake: Stake; + addKey: AddKey; + deleteKey: DeleteKey; + deleteAccount: DeleteAccount; +} diff --git a/packages/core/src/transaction/index.ts b/packages/core/src/transaction/index.ts new file mode 100644 index 000000000..7c0875e27 --- /dev/null +++ b/packages/core/src/transaction/index.ts @@ -0,0 +1,31 @@ +export { + addKey, + createAccount, + deleteAccount, + deleteKey, + deployContract, + fullAccessKey, + functionCall, + functionCallAccessKey, + stake, + stringifyJsonOrBytes, + transfer, +} from './action_creators'; +export { + Action, + AccessKey, + AccessKeyPermission, + AddKey, + CreateAccount, + DeleteAccount, + DeleteKey, + DeployContract, + FullAccessPermission, + FunctionCall, + FunctionCallPermission, + Stake, + Transfer, +} from './actions'; +export { SCHEMA } from './schema'; +export { signTransaction } from './sign'; +export { createTransaction, Signature as TransactionSignature, SignedTransaction, Transaction } from './transaction'; diff --git a/packages/core/src/transaction/schema.ts b/packages/core/src/transaction/schema.ts new file mode 100644 index 000000000..c2be95be7 --- /dev/null +++ b/packages/core/src/transaction/schema.ts @@ -0,0 +1,93 @@ +import { PublicKey } from '../key_pair'; +import { + Action, + AccessKey, + AccessKeyPermission, + AddKey, + CreateAccount, + DeleteAccount, + DeleteKey, + DeployContract, + FullAccessPermission, + FunctionCall, + FunctionCallPermission, + Stake, + Transfer, +} from './actions'; +import { Signature, SignedTransaction, Transaction } from './transaction'; + +type Class = new (...args: any[]) => T; + +export const SCHEMA = new Map([ + [Signature, {kind: 'struct', fields: [ + ['keyType', 'u8'], + ['data', [64]] + ]}], + [SignedTransaction, {kind: 'struct', fields: [ + ['transaction', Transaction], + ['signature', Signature] + ]}], + [Transaction, { kind: 'struct', fields: [ + ['signerId', 'string'], + ['publicKey', PublicKey], + ['nonce', 'u64'], + ['receiverId', 'string'], + ['blockHash', [32]], + ['actions', [Action]] + ]}], + [PublicKey, { kind: 'struct', fields: [ + ['keyType', 'u8'], + ['data', [32]] + ]}], + [AccessKey, { kind: 'struct', fields: [ + ['nonce', 'u64'], + ['permission', AccessKeyPermission], + ]}], + [AccessKeyPermission, {kind: 'enum', field: 'enum', values: [ + ['functionCall', FunctionCallPermission], + ['fullAccess', FullAccessPermission], + ]}], + [FunctionCallPermission, {kind: 'struct', fields: [ + ['allowance', {kind: 'option', type: 'u128'}], + ['receiverId', 'string'], + ['methodNames', ['string']], + ]}], + [FullAccessPermission, {kind: 'struct', fields: []}], + [Action, {kind: 'enum', field: 'enum', values: [ + ['createAccount', CreateAccount], + ['deployContract', DeployContract], + ['functionCall', FunctionCall], + ['transfer', Transfer], + ['stake', Stake], + ['addKey', AddKey], + ['deleteKey', DeleteKey], + ['deleteAccount', DeleteAccount], + ]}], + [CreateAccount, { kind: 'struct', fields: [] }], + [DeployContract, { kind: 'struct', fields: [ + ['code', ['u8']] + ]}], + [FunctionCall, { kind: 'struct', fields: [ + ['methodName', 'string'], + ['args', ['u8']], + ['gas', 'u64'], + ['deposit', 'u128'] + ]}], + [Transfer, { kind: 'struct', fields: [ + ['deposit', 'u128'] + ]}], + [Stake, { kind: 'struct', fields: [ + ['stake', 'u128'], + ['publicKey', PublicKey] + ]}], + [AddKey, { kind: 'struct', fields: [ + ['publicKey', PublicKey], + ['accessKey', AccessKey] + ]}], + [DeleteKey, { kind: 'struct', fields: [ + ['publicKey', PublicKey] + ]}], + [DeleteAccount, { kind: 'struct', fields: [ + ['beneficiaryId', 'string'] + ]}], +]); diff --git a/packages/core/src/transaction/sign.ts b/packages/core/src/transaction/sign.ts new file mode 100644 index 000000000..a1c745be6 --- /dev/null +++ b/packages/core/src/transaction/sign.ts @@ -0,0 +1,40 @@ +import sha256 from 'js-sha256'; +import BN from 'bn.js'; +import { serialize } from 'borsh'; + +import { Action } from './actions'; +import { SCHEMA } from './schema'; +import { Signer } from '../signer'; +import { createTransaction, Signature, SignedTransaction, Transaction } from './transaction'; + +/** + * Signs a given transaction from an account with given keys, applied to the given network + * @param transaction The Transaction object to sign + * @param signer The {Signer} object that assists with signing keys + * @param accountId The human-readable NEAR account name + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ +async function signTransactionObject(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]> { + const message = serialize(SCHEMA, transaction); + const hash = new Uint8Array(sha256.sha256.array(message)); + const signature = await signer.signMessage(message, accountId, networkId); + const signedTx = new SignedTransaction({ + transaction, + signature: new Signature({ keyType: transaction.publicKey.keyType, data: signature.signature }) + }); + return [hash, signedTx]; +} + +export async function signTransaction(transaction: Transaction, 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; + return signTransactionObject(transaction, signer, accountId, networkId); + } else { + const [ receiverId, nonce, actions, blockHash, signer, accountId, networkId ] = args; + const publicKey = await signer.getPublicKey(accountId, networkId); + const transaction = createTransaction(accountId, publicKey, receiverId, nonce, actions, blockHash); + return signTransactionObject(transaction, signer, accountId, networkId); + } +} diff --git a/packages/core/src/transaction/transaction.ts b/packages/core/src/transaction/transaction.ts new file mode 100644 index 000000000..57f6f8c11 --- /dev/null +++ b/packages/core/src/transaction/transaction.ts @@ -0,0 +1,46 @@ +import BN from 'bn.js'; +import { serialize, deserialize } from 'borsh'; + +import { KeyType, PublicKey } from '../key_pair'; +import { Assignable } from '../types'; +import { Action } from './actions'; +import { SCHEMA } from './schema'; + +export class Signature extends Assignable { + keyType: KeyType; + data: Uint8Array; +} + +export class Transaction extends Assignable { + signerId: string; + publicKey: PublicKey; + nonce: BN; + receiverId: string; + actions: Action[]; + blockHash: Uint8Array; + + encode(): Uint8Array { + return serialize(SCHEMA, this); + } + + static decode(bytes: Buffer): Transaction { + return deserialize(SCHEMA, Transaction, bytes); + } +} + +export class SignedTransaction extends Assignable { + transaction: Transaction; + signature: Signature; + + encode(): Uint8Array { + return serialize(SCHEMA, this); + } + + static decode(bytes: Buffer): SignedTransaction { + return deserialize(SCHEMA, SignedTransaction, bytes); + } +} + +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 }); +} diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index 3d2f72a1f..490458906 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -1,257 +1,32 @@ -import sha256 from 'js-sha256'; -import BN from 'bn.js'; - -import { Enum, Assignable } from './utils/enums'; -import { serialize, deserialize } from 'borsh'; -import { KeyType, PublicKey } from './utils/key_pair'; -import { Signer } from './signer'; - -export class FunctionCallPermission extends Assignable { - allowance?: BN; - receiverId: string; - methodNames: string[]; -} - -export class FullAccessPermission extends Assignable {} - -export class AccessKeyPermission extends Enum { - functionCall: FunctionCallPermission; - fullAccess: FullAccessPermission; -} - -export class AccessKey extends Assignable { - permission: AccessKeyPermission; -} - -export function fullAccessKey(): AccessKey { - return new AccessKey({ permission: new AccessKeyPermission({fullAccess: new FullAccessPermission({})}) }); -} - -export function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey { - return new AccessKey({ permission: new AccessKeyPermission({functionCall: new FunctionCallPermission({receiverId, allowance, methodNames})})}); -} - -export class IAction extends Assignable {} - -export class CreateAccount extends IAction {} -export class DeployContract extends IAction { code: Uint8Array; } -export class FunctionCall extends IAction { methodName: string; args: Uint8Array; gas: BN; deposit: BN; } -export class Transfer extends IAction { deposit: BN; } -export class Stake extends IAction { stake: BN; publicKey: PublicKey; } -export class AddKey extends IAction { publicKey: PublicKey; accessKey: AccessKey; } -export class DeleteKey extends IAction { publicKey: PublicKey; } -export class DeleteAccount extends IAction { beneficiaryId: string; } - -export function createAccount(): Action { - return new Action({createAccount: new CreateAccount({}) }); -} - -export function deployContract(code: Uint8Array): Action { - return new Action({ deployContract: new DeployContract({code}) }); -} - -export function stringifyJsonOrBytes(args: any): Buffer { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; - const serializedArgs = isUint8Array ? args : Buffer.from(JSON.stringify(args)); - return serializedArgs; -} - -/** - * Constructs {@link Action} instance representing contract method call. - * - * @param methodName the name of the method to call - * @param args arguments to pass to method. Can be either plain JS object which gets serialized as JSON automatically - * or `Uint8Array` instance which represents bytes passed as is. - * @param gas max amount of gas that method call can use - * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call - * @param stringify Convert input arguments into bytes array. - * @param jsContract Is contract from JS SDK, skips stringification of arguments. - */ -export function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN, stringify = stringifyJsonOrBytes, jsContract = false): Action { - if(jsContract){ - return new Action({ functionCall: new FunctionCall({ methodName, args, gas, deposit }) }); - } - return new Action({ functionCall: new FunctionCall({ methodName, args: stringify(args), gas, deposit }) }); -} - -export function transfer(deposit: BN): Action { - return new Action({transfer: new Transfer({ deposit }) }); -} - -export function stake(stake: BN, publicKey: PublicKey): Action { - return new Action({stake: new Stake({ stake, publicKey }) }); -} - -export function addKey(publicKey: PublicKey, accessKey: AccessKey): Action { - return new Action({addKey: new AddKey({ publicKey, accessKey}) }); -} - -export function deleteKey(publicKey: PublicKey): Action { - return new Action({deleteKey: new DeleteKey({ publicKey }) }); -} - -export function deleteAccount(beneficiaryId: string): Action { - return new Action({deleteAccount: new DeleteAccount({ beneficiaryId }) }); -} - -export class Signature extends Assignable { - keyType: KeyType; - data: Uint8Array; -} - -export class Transaction extends Assignable { - signerId: string; - publicKey: PublicKey; - nonce: BN; - receiverId: string; - actions: Action[]; - blockHash: Uint8Array; - - encode(): Uint8Array { - return serialize(SCHEMA, this); - } - - static decode(bytes: Buffer): Transaction { - return deserialize(SCHEMA, Transaction, bytes); - } -} - -export class SignedTransaction extends Assignable { - transaction: Transaction; - signature: Signature; - - encode(): Uint8Array { - return serialize(SCHEMA, this); - } - - static decode(bytes: Buffer): SignedTransaction { - return deserialize(SCHEMA, SignedTransaction, bytes); - } -} - -/** - * Contains a list of the valid transaction Actions available with this API - * @see {@link https://nomicon.io/RuntimeSpec/Actions.html | Actions Spec} - */ -export class Action extends Enum { - createAccount: CreateAccount; - deployContract: DeployContract; - functionCall: FunctionCall; - transfer: Transfer; - stake: Stake; - addKey: AddKey; - deleteKey: DeleteKey; - deleteAccount: DeleteAccount; -} - -type Class = new (...args: any[]) => T; - -export const SCHEMA = new Map([ - [Signature, {kind: 'struct', fields: [ - ['keyType', 'u8'], - ['data', [64]] - ]}], - [SignedTransaction, {kind: 'struct', fields: [ - ['transaction', Transaction], - ['signature', Signature] - ]}], - [Transaction, { kind: 'struct', fields: [ - ['signerId', 'string'], - ['publicKey', PublicKey], - ['nonce', 'u64'], - ['receiverId', 'string'], - ['blockHash', [32]], - ['actions', [Action]] - ]}], - [PublicKey, { kind: 'struct', fields: [ - ['keyType', 'u8'], - ['data', [32]] - ]}], - [AccessKey, { kind: 'struct', fields: [ - ['nonce', 'u64'], - ['permission', AccessKeyPermission], - ]}], - [AccessKeyPermission, {kind: 'enum', field: 'enum', values: [ - ['functionCall', FunctionCallPermission], - ['fullAccess', FullAccessPermission], - ]}], - [FunctionCallPermission, {kind: 'struct', fields: [ - ['allowance', {kind: 'option', type: 'u128'}], - ['receiverId', 'string'], - ['methodNames', ['string']], - ]}], - [FullAccessPermission, {kind: 'struct', fields: []}], - [Action, {kind: 'enum', field: 'enum', values: [ - ['createAccount', CreateAccount], - ['deployContract', DeployContract], - ['functionCall', FunctionCall], - ['transfer', Transfer], - ['stake', Stake], - ['addKey', AddKey], - ['deleteKey', DeleteKey], - ['deleteAccount', DeleteAccount], - ]}], - [CreateAccount, { kind: 'struct', fields: [] }], - [DeployContract, { kind: 'struct', fields: [ - ['code', ['u8']] - ]}], - [FunctionCall, { kind: 'struct', fields: [ - ['methodName', 'string'], - ['args', ['u8']], - ['gas', 'u64'], - ['deposit', 'u128'] - ]}], - [Transfer, { kind: 'struct', fields: [ - ['deposit', 'u128'] - ]}], - [Stake, { kind: 'struct', fields: [ - ['stake', 'u128'], - ['publicKey', PublicKey] - ]}], - [AddKey, { kind: 'struct', fields: [ - ['publicKey', PublicKey], - ['accessKey', AccessKey] - ]}], - [DeleteKey, { kind: 'struct', fields: [ - ['publicKey', PublicKey] - ]}], - [DeleteAccount, { kind: 'struct', fields: [ - ['beneficiaryId', 'string'] - ]}], -]); - -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 }); -} - -/** - * Signs a given transaction from an account with given keys, applied to the given network - * @param transaction The Transaction object to sign - * @param signer The {Signer} object that assists with signing keys - * @param accountId The human-readable NEAR account name - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ -async function signTransactionObject(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]> { - const message = serialize(SCHEMA, transaction); - const hash = new Uint8Array(sha256.sha256.array(message)); - const signature = await signer.signMessage(message, accountId, networkId); - const signedTx = new SignedTransaction({ - transaction, - signature: new Signature({ keyType: transaction.publicKey.keyType, data: signature.signature }) - }); - return [hash, signedTx]; -} - -export async function signTransaction(transaction: Transaction, 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; - return signTransactionObject(transaction, signer, accountId, networkId); - } else { - const [ receiverId, nonce, actions, blockHash, signer, accountId, networkId ] = args; - const publicKey = await signer.getPublicKey(accountId, networkId); - const transaction = createTransaction(accountId, publicKey, receiverId, nonce, actions, blockHash); - return signTransactionObject(transaction, signer, accountId, networkId); - } -} +export { + addKey, + createAccount, + deleteAccount, + deleteKey, + deployContract, + fullAccessKey, + functionCall, + functionCallAccessKey, + stake, + stringifyJsonOrBytes, + transfer, + Action, + AccessKey, + AccessKeyPermission, + AddKey, + CreateAccount, + DeleteAccount, + DeleteKey, + DeployContract, + FullAccessPermission, + FunctionCall, + FunctionCallPermission, + Stake, + Transfer, + SCHEMA, + createTransaction, + signTransaction, + TransactionSignature as Signature, + SignedTransaction, + Transaction +} from '@near-js/core'; From 01826bf3f618f7b4cbe5b3c45651073895bd4e84 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 4 Nov 2022 17:19:09 -0700 Subject: [PATCH 09/84] refactor: export all --- packages/core/src/index.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 8699c7d1b..7bbc29158 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ -export { KeyPair, KeyPairEd25519, KeyType, Signature, PublicKey } from './key_pair'; -export { InMemoryKeyStore, KeyStore } from './key_store'; -export { InMemorySigner, Signer } from './signer'; +export * from './key_pair'; +export * from './key_store'; +export * from './signer'; +export * from './transaction'; From 3e542c2c61c96c9e8670e81e0ab785724851fdad Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 14 Nov 2022 15:30:21 -0800 Subject: [PATCH 10/84] refactor: break up and move provider logic to core --- packages/core/src/index.ts | 1 + packages/core/src/provider/index.ts | 75 +++ packages/core/src/provider/light_client.ts | 34 ++ packages/core/src/provider/protocol.ts | 191 +++++++ packages/core/src/provider/provider.ts | 49 ++ packages/core/src/provider/request.ts | 48 ++ packages/core/src/provider/response.ts | 127 +++++ packages/core/src/provider/utils.ts | 14 + packages/core/src/provider/validator.ts | 44 ++ packages/core/src/transaction/index.ts | 4 +- packages/core/src/transaction/schema.ts | 42 +- packages/core/src/transaction/sign.ts | 4 +- packages/core/src/transaction/transaction.ts | 41 +- .../near-api-js/src/providers/provider.ts | 523 +++--------------- 14 files changed, 699 insertions(+), 498 deletions(-) create mode 100644 packages/core/src/provider/index.ts create mode 100644 packages/core/src/provider/light_client.ts create mode 100644 packages/core/src/provider/protocol.ts create mode 100644 packages/core/src/provider/provider.ts create mode 100644 packages/core/src/provider/request.ts create mode 100644 packages/core/src/provider/response.ts create mode 100644 packages/core/src/provider/utils.ts create mode 100644 packages/core/src/provider/validator.ts diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 7bbc29158..1c8a85ffa 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,4 +1,5 @@ export * from './key_pair'; export * from './key_store'; +export * from './provider'; export * from './signer'; export * from './transaction'; diff --git a/packages/core/src/provider/index.ts b/packages/core/src/provider/index.ts new file mode 100644 index 000000000..5fc01bd34 --- /dev/null +++ b/packages/core/src/provider/index.ts @@ -0,0 +1,75 @@ + +export { + IdType, + LightClientBlockLiteView, + LightClientProof, + LightClientProofRequest, +} from './light_client'; +export { + AccessKeyWithPublicKey, + BlockHash, + BlockChange, + BlockChangeResult, + BlockHeader, + BlockHeaderInnerLiteView, + BlockHeight, + BlockId, + BlockReference, + BlockResult, + BlockShardId, + ChangeResult, + Chunk, + ChunkHash, + ChunkHeader, + ChunkId, + ChunkResult, + Finality, + GasPrice, + MerkleNode, + MerklePath, + NearProtocolConfig, + NearProtocolRuntimeConfig, + NodeStatusResult, + ShardId, + SyncInfo, + TotalWeight, + Transaction as ProviderTransaction, +} from './protocol'; +export { Provider } from './provider'; +export { + CallFunctionRequest, + RpcQueryRequest, + ViewAccessKeyListRequest, + ViewAccessKeyRequest, + ViewAccountRequest, + ViewCodeRequest, + ViewStateRequest, +} from './request'; +export { + AccessKeyInfoView, + AccessKeyList, + AccessKeyView, + AccessKeyViewRaw, + AccountView, + CodeResult, + ContractCodeView, + ExecutionError, + ExecutionOutcome, + ExecutionOutcomeWithId, + ExecutionOutcomeWithIdView, + ExecutionStatus, + ExecutionStatusBasic, + FinalExecutionOutcome, + FinalExecutionStatus, + FinalExecutionStatusBasic, + FunctionCallPermissionView, + QueryResponseKind, + ViewStateResult, +} from './response'; +export { getTransactionLastResult } from './utils' +export { + CurrentEpochValidatorInfo, + EpochValidatorInfo, + NextEpochValidatorInfo, + ValidatorStakeView, +} from './validator'; diff --git a/packages/core/src/provider/light_client.ts b/packages/core/src/provider/light_client.ts new file mode 100644 index 000000000..d69235c19 --- /dev/null +++ b/packages/core/src/provider/light_client.ts @@ -0,0 +1,34 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +import { BlockHeaderInnerLiteView, MerklePath } from './protocol'; +import { ExecutionOutcomeWithIdView } from './response'; + +export interface LightClientBlockLiteView { + prev_block_hash: string; + inner_rest_hash: string; + inner_lite: BlockHeaderInnerLiteView; +} + +export interface LightClientProof { + outcome_proof: ExecutionOutcomeWithIdView; + outcome_root_proof: MerklePath; + block_header_lite: LightClientBlockLiteView; + block_proof: MerklePath; +} + +export enum IdType { + Transaction = 'transaction', + Receipt = 'receipt', +} + +export interface LightClientProofRequest { + type: IdType; + light_client_head: string; + transaction_hash?: string; + sender_id?: string; + receipt_id?: string; + receiver_id?: string; +} diff --git a/packages/core/src/provider/protocol.ts b/packages/core/src/provider/protocol.ts new file mode 100644 index 000000000..8be6a6493 --- /dev/null +++ b/packages/core/src/provider/protocol.ts @@ -0,0 +1,191 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +import BN from 'bn.js'; + +export interface SyncInfo { + latest_block_hash: string; + latest_block_height: number; + latest_block_time: string; + latest_state_root: string; + syncing: boolean; +} + +interface Version { + version: string; + build: string; +} + +export interface NodeStatusResult { + chain_id: string; + rpc_addr: string; + sync_info: SyncInfo; + validators: string[]; + version: Version; +} + +export type BlockHash = string; +export type BlockHeight = number; +export type BlockId = BlockHash | BlockHeight; + +export type Finality = 'optimistic' | 'near-final' | 'final' + +export type BlockReference = { blockId: BlockId } | { finality: Finality } | { sync_checkpoint: 'genesis' | 'earliest_available' } + +export interface TotalWeight { + num: number; +} + +export interface BlockHeader { + height: number; + epoch_id: string; + next_epoch_id: string; + hash: string; + prev_hash: string; + prev_state_root: string; + chunk_receipts_root: string; + chunk_headers_root: string; + chunk_tx_root: string; + outcome_root: string; + chunks_included: number; + challenges_root: string; + timestamp: number; + timestamp_nanosec: string; + random_value: string; + validator_proposals: any[]; + chunk_mask: boolean[]; + gas_price: string; + rent_paid: string; + validator_reward: string; + total_supply: string; + challenges_result: any[]; + last_final_block: string; + last_ds_final_block: string; + next_bp_hash: string; + block_merkle_root: string; + approvals: string[]; + signature: string; + latest_protocol_version: number; +} + +export type ChunkHash = string; +export type ShardId = number; +export type BlockShardId = [BlockId, ShardId]; +export type ChunkId = ChunkHash | BlockShardId; + +export interface ChunkHeader { + balance_burnt: string; + chunk_hash: ChunkHash; + encoded_length: number; + encoded_merkle_root: string; + gas_limit: number; + gas_used: number; + height_created: number; + height_included: number; + outcome_root: string; + outgoing_receipts_root: string; + prev_block_hash: string; + prev_state_root: string; + rent_paid: string; + shard_id: number; + signature: string; + tx_root: string; + validator_proposals: any[]; + validator_reward: string; +} + +export interface ChunkResult { + author: string; + header: ChunkHeader; + receipts: any[]; + transactions: Transaction[]; +} + +export interface Chunk { + chunk_hash: string; + prev_block_hash: string; + outcome_root: string; + prev_state_root: string; + encoded_merkle_root: string; + encoded_length: number; + height_created: number; + height_included: number; + shard_id: number; + gas_used: number; + gas_limit: number; + rent_paid: string; + validator_reward: string; + balance_burnt: string; + outgoing_receipts_root: string; + tx_root: string; + validator_proposals: any[]; + signature: string; +} + +export interface Transaction { + actions: Array; + hash: string; + nonce: BN; + public_key: string; + receiver_id: string; + signature: string; + signer_id: string; +} + +export interface BlockResult { + author: string; + header: BlockHeader; + chunks: Chunk[]; +} + +export interface BlockChange { + type: string; + account_id: string; +} + +export interface BlockChangeResult { + block_hash: string; + changes: BlockChange[]; +} + +export interface ChangeResult { + block_hash: string; + changes: any[]; +} + +export interface NearProtocolConfig { + runtime_config: NearProtocolRuntimeConfig; +} + +export interface NearProtocolRuntimeConfig { + storage_amount_per_byte: string; +} + +export interface MerkleNode { + hash: string; + direction: string; +} + +export type MerklePath = MerkleNode[]; + +export interface BlockHeaderInnerLiteView { + height: number; + epoch_id: string; + next_epoch_id: string; + prev_state_root: string; + outcome_root: string; + timestamp: number; + next_bp_hash: string; + block_merkle_root: string; +} + +export interface GasPrice { + gas_price: string; +} + +export interface AccessKeyWithPublicKey { + account_id: string; + public_key: string; +} diff --git a/packages/core/src/provider/provider.ts b/packages/core/src/provider/provider.ts new file mode 100644 index 000000000..4b7361c6c --- /dev/null +++ b/packages/core/src/provider/provider.ts @@ -0,0 +1,49 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +import { LightClientProof, LightClientProofRequest } from './light_client'; +import { + AccessKeyWithPublicKey, + BlockChangeResult, + BlockId, + BlockReference, + BlockResult, + ChangeResult, + ChunkId, + ChunkResult, + GasPrice, + NearProtocolConfig, + NodeStatusResult, +} from './protocol'; +import { RpcQueryRequest } from './request'; +import { FinalExecutionOutcome, QueryResponseKind } from './response'; +import { SignedTransaction } from '../transaction'; +import { EpochValidatorInfo } from "./validator"; + +/** @hidden */ +export abstract class Provider { + abstract status(): Promise; + + abstract sendTransaction(signedTransaction: SignedTransaction): Promise; + abstract sendTransactionAsync(signedTransaction: SignedTransaction): Promise; + abstract txStatus(txHash: Uint8Array | string, accountId: string): Promise; + abstract txStatusReceipts(txHash: Uint8Array | string, accountId: string): Promise; + abstract query(params: RpcQueryRequest): Promise; + abstract query(path: string, data: string): Promise; + // TODO: BlockQuery type? + abstract block(blockQuery: BlockId | BlockReference): Promise; + abstract blockChanges(blockQuery: BlockId | BlockReference): Promise; + abstract chunk(chunkId: ChunkId): Promise; + // TODO: Use BlockQuery? + abstract validators(blockId: BlockId): Promise; + abstract experimental_protocolConfig(blockReference: BlockReference): Promise; + abstract lightClientProof(request: LightClientProofRequest): Promise; + abstract gasPrice(blockId: BlockId): Promise; + abstract accessKeyChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + abstract singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], BlockQuery: BlockId | BlockReference): Promise; + abstract accountChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + abstract contractStateChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference, keyPrefix: string): Promise; + abstract contractCodeChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; +} diff --git a/packages/core/src/provider/request.ts b/packages/core/src/provider/request.ts new file mode 100644 index 000000000..387fabfb8 --- /dev/null +++ b/packages/core/src/provider/request.ts @@ -0,0 +1,48 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +import { BlockReference } from './protocol'; + +export interface ViewAccountRequest { + request_type: 'view_account'; + account_id: string; +} + +export interface ViewCodeRequest { + request_type: 'view_code'; + account_id: string; +} + +export interface ViewStateRequest { + request_type: 'view_state'; + account_id: string; + prefix_base64: string; +} + +export interface ViewAccessKeyRequest { + request_type: 'view_access_key'; + account_id: string; + public_key: string; +} + +export interface ViewAccessKeyListRequest { + request_type: 'view_access_key_list'; + account_id: string; +} + +export interface CallFunctionRequest { + request_type: 'call_function'; + account_id: string; + method_name: string; + args_base64: string; +} + +export type RpcQueryRequest = (ViewAccountRequest | + ViewCodeRequest | + ViewStateRequest | + ViewAccountRequest | + ViewAccessKeyRequest | + ViewAccessKeyListRequest | + CallFunctionRequest) & BlockReference diff --git a/packages/core/src/provider/response.ts b/packages/core/src/provider/response.ts new file mode 100644 index 000000000..dffb59531 --- /dev/null +++ b/packages/core/src/provider/response.ts @@ -0,0 +1,127 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +import BN from 'bn.js'; + +import { BlockHash, BlockHeight, MerklePath } from './protocol'; + +export enum ExecutionStatusBasic { + Unknown = 'Unknown', + Pending = 'Pending', + Failure = 'Failure', +} + +export interface ExecutionStatus { + SuccessValue?: string; + SuccessReceiptId?: string; + Failure?: ExecutionError; +} + +export enum FinalExecutionStatusBasic { + NotStarted = 'NotStarted', + Started = 'Started', + Failure = 'Failure', +} + +export interface ExecutionError { + error_message: string; + error_type: string; +} + +export interface FinalExecutionStatus { + SuccessValue?: string; + Failure?: ExecutionError; +} + +export interface ExecutionOutcomeWithId { + id: string; + outcome: ExecutionOutcome; +} + +export interface ExecutionOutcome { + logs: string[]; + receipt_ids: string[]; + gas_burnt: number; + status: ExecutionStatus | ExecutionStatusBasic; +} + +export interface ExecutionOutcomeWithIdView { + proof: MerklePath; + block_hash: string; + id: string; + outcome: ExecutionOutcome; +} + +export interface FinalExecutionOutcome { + status: FinalExecutionStatus | FinalExecutionStatusBasic; + transaction: any; + transaction_outcome: ExecutionOutcomeWithId; + receipts_outcome: ExecutionOutcomeWithId[]; +} + +export interface QueryResponseKind { + block_height: BlockHeight; + block_hash: BlockHash; +} + +export interface AccountView extends QueryResponseKind { + amount: string; + locked: string; + code_hash: string; + storage_usage: number; + storage_paid_at: BlockHeight; +} + +interface StateItem { + key: string; + value: string; + proof: string[]; +} + +export interface ViewStateResult extends QueryResponseKind { + values: StateItem[]; + proof: string[]; +} + +export interface CodeResult extends QueryResponseKind { + result: number[]; + logs: string[]; +} + +export interface ContractCodeView extends QueryResponseKind { + code_base64: string; + hash: string; +} + +export interface FunctionCallPermissionView { + FunctionCall: { + allowance: string; + receiver_id: string; + method_names: string[]; + }; +} + +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; + access_key: AccessKeyView; +} + +export interface AccessKeyList extends QueryResponseKind { + keys: AccessKeyInfoView[]; +} + +export interface AccessKeyInfoView { + public_key: string; + access_key: AccessKeyView; +} diff --git a/packages/core/src/provider/utils.ts b/packages/core/src/provider/utils.ts new file mode 100644 index 000000000..7b328d7a7 --- /dev/null +++ b/packages/core/src/provider/utils.ts @@ -0,0 +1,14 @@ +import { FinalExecutionOutcome } from './response'; + +/** @hidden */ +export function getTransactionLastResult(txResult: FinalExecutionOutcome): any { + if (typeof txResult.status === 'object' && typeof txResult.status.SuccessValue === 'string') { + const value = Buffer.from(txResult.status.SuccessValue, 'base64').toString(); + try { + return JSON.parse(value); + } catch (e) { + return value; + } + } + return null; +} diff --git a/packages/core/src/provider/validator.ts b/packages/core/src/provider/validator.ts new file mode 100644 index 000000000..09b269e52 --- /dev/null +++ b/packages/core/src/provider/validator.ts @@ -0,0 +1,44 @@ +/** + * NEAR RPC API request types and responses + * @module + */ + +export interface CurrentEpochValidatorInfo { + account_id: string; + public_key: string; + is_slashed: boolean; + stake: string; + shards: number[]; + num_produced_blocks: number; + num_expected_blocks: number; +} + +export interface NextEpochValidatorInfo { + account_id: string; + public_key: string; + stake: string; + shards: number[]; +} + +export interface ValidatorStakeView { + account_id: string; + public_key: string; + stake: string; +} + +export interface EpochValidatorInfo { + // Validators for the current epoch. + next_validators: NextEpochValidatorInfo[]; + // Validators for the next epoch. + current_validators: CurrentEpochValidatorInfo[]; + // Fishermen for the current epoch. + next_fisherman: ValidatorStakeView[]; + // Fishermen for the next epoch. + current_fisherman: ValidatorStakeView[]; + // Proposals in the current epoch. + current_proposals: ValidatorStakeView[]; + // Kickout in the previous epoch. + prev_epoch_kickout: ValidatorStakeView[]; + // Epoch start height. + epoch_start_height: number; +} diff --git a/packages/core/src/transaction/index.ts b/packages/core/src/transaction/index.ts index 7c0875e27..fcedfa8c6 100644 --- a/packages/core/src/transaction/index.ts +++ b/packages/core/src/transaction/index.ts @@ -26,6 +26,6 @@ export { Stake, Transfer, } from './actions'; -export { SCHEMA } from './schema'; +export { SCHEMA, Signature as TransactionSignature, SignedTransaction, Transaction } from './schema'; export { signTransaction } from './sign'; -export { createTransaction, Signature as TransactionSignature, SignedTransaction, Transaction } from './transaction'; +export { createTransaction } from './transaction'; diff --git a/packages/core/src/transaction/schema.ts b/packages/core/src/transaction/schema.ts index c2be95be7..2199639a5 100644 --- a/packages/core/src/transaction/schema.ts +++ b/packages/core/src/transaction/schema.ts @@ -1,4 +1,8 @@ -import { PublicKey } from '../key_pair'; +import BN from 'bn.js'; +import { deserialize, serialize } from 'borsh'; + +import { KeyType, PublicKey } from '../key_pair'; +import { Assignable } from '../types'; import { Action, AccessKey, @@ -14,7 +18,41 @@ import { Stake, Transfer, } from './actions'; -import { Signature, SignedTransaction, Transaction } from './transaction'; + +export class Signature extends Assignable { + keyType: KeyType; + data: Uint8Array; +} + +export class Transaction extends Assignable { + signerId: string; + publicKey: PublicKey; + nonce: BN; + receiverId: string; + actions: Action[]; + blockHash: Uint8Array; + + encode(): Uint8Array { + return serialize(SCHEMA, this); + } + + static decode(bytes: Buffer): Transaction { + return deserialize(SCHEMA, Transaction, bytes); + } +} + +export class SignedTransaction extends Assignable { + transaction: Transaction; + signature: Signature; + + encode(): Uint8Array { + return serialize(SCHEMA, this); + } + + static decode(bytes: Buffer): SignedTransaction { + return deserialize(SCHEMA, SignedTransaction, bytes); + } +} type Class = new (...args: any[]) => T; diff --git a/packages/core/src/transaction/sign.ts b/packages/core/src/transaction/sign.ts index a1c745be6..2c4c349f7 100644 --- a/packages/core/src/transaction/sign.ts +++ b/packages/core/src/transaction/sign.ts @@ -3,9 +3,9 @@ import BN from 'bn.js'; import { serialize } from 'borsh'; import { Action } from './actions'; -import { SCHEMA } from './schema'; +import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema'; import { Signer } from '../signer'; -import { createTransaction, Signature, SignedTransaction, Transaction } from './transaction'; +import { createTransaction } from './transaction'; /** * Signs a given transaction from an account with given keys, applied to the given network diff --git a/packages/core/src/transaction/transaction.ts b/packages/core/src/transaction/transaction.ts index 57f6f8c11..77cc472d7 100644 --- a/packages/core/src/transaction/transaction.ts +++ b/packages/core/src/transaction/transaction.ts @@ -1,45 +1,8 @@ import BN from 'bn.js'; -import { serialize, deserialize } from 'borsh'; -import { KeyType, PublicKey } from '../key_pair'; -import { Assignable } from '../types'; +import { PublicKey } from '../key_pair'; import { Action } from './actions'; -import { SCHEMA } from './schema'; - -export class Signature extends Assignable { - keyType: KeyType; - data: Uint8Array; -} - -export class Transaction extends Assignable { - signerId: string; - publicKey: PublicKey; - nonce: BN; - receiverId: string; - actions: Action[]; - blockHash: Uint8Array; - - encode(): Uint8Array { - return serialize(SCHEMA, this); - } - - static decode(bytes: Buffer): Transaction { - return deserialize(SCHEMA, Transaction, bytes); - } -} - -export class SignedTransaction extends Assignable { - transaction: Transaction; - signature: Signature; - - encode(): Uint8Array { - return serialize(SCHEMA, this); - } - - static decode(bytes: Buffer): SignedTransaction { - return deserialize(SCHEMA, SignedTransaction, bytes); - } -} +import { Transaction } from './schema'; 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 }); diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index 9ac7b0ffc..49e8c9e47 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -1,454 +1,71 @@ -/** - * NEAR RPC API request types and responses - * @module - */ -import { SignedTransaction } from '../transaction'; -import BN from 'bn.js'; - -export interface SyncInfo { - latest_block_hash: string; - latest_block_height: number; - latest_block_time: string; - latest_state_root: string; - syncing: boolean; -} - -interface Version { - version: string; - build: string; -} - -export interface NodeStatusResult { - chain_id: string; - rpc_addr: string; - sync_info: SyncInfo; - validators: string[]; - version: Version; -} - -type BlockHash = string; -type BlockHeight = number; -export type BlockId = BlockHash | BlockHeight; - -export type Finality = 'optimistic' | 'near-final' | 'final' - -export type BlockReference = { blockId: BlockId } | { finality: Finality } | { sync_checkpoint: 'genesis' | 'earliest_available' } - -export enum ExecutionStatusBasic { - Unknown = 'Unknown', - Pending = 'Pending', - Failure = 'Failure', -} - -export interface ExecutionStatus { - SuccessValue?: string; - SuccessReceiptId?: string; - Failure?: ExecutionError; -} - -export enum FinalExecutionStatusBasic { - NotStarted = 'NotStarted', - Started = 'Started', - Failure = 'Failure', -} - -export interface ExecutionError { - error_message: string; - error_type: string; -} - -export interface FinalExecutionStatus { - SuccessValue?: string; - Failure?: ExecutionError; -} - -export interface ExecutionOutcomeWithId { - id: string; - outcome: ExecutionOutcome; -} - -export interface ExecutionOutcome { - logs: string[]; - receipt_ids: string[]; - gas_burnt: number; - status: ExecutionStatus | ExecutionStatusBasic; -} - -export interface ExecutionOutcomeWithIdView { - proof: MerklePath; - block_hash: string; - id: string; - outcome: ExecutionOutcome; -} - -export interface FinalExecutionOutcome { - status: FinalExecutionStatus | FinalExecutionStatusBasic; - transaction: any; - transaction_outcome: ExecutionOutcomeWithId; - receipts_outcome: ExecutionOutcomeWithId[]; -} - -export interface TotalWeight { - num: number; -} - -export interface BlockHeader { - height: number; - epoch_id: string; - next_epoch_id: string; - hash: string; - prev_hash: string; - prev_state_root: string; - chunk_receipts_root: string; - chunk_headers_root: string; - chunk_tx_root: string; - outcome_root: string; - chunks_included: number; - challenges_root: string; - timestamp: number; - timestamp_nanosec: string; - random_value: string; - validator_proposals: any[]; - chunk_mask: boolean[]; - gas_price: string; - rent_paid: string; - validator_reward: string; - total_supply: string; - challenges_result: any[]; - last_final_block: string; - last_ds_final_block: string; - next_bp_hash: string; - block_merkle_root: string; - approvals: string[]; - signature: string; - latest_protocol_version: number; -} - -export type ChunkHash = string; -export type ShardId = number; -export type BlockShardId = [BlockId, ShardId]; -export type ChunkId = ChunkHash | BlockShardId; - -export interface ChunkHeader { - balance_burnt: string; - chunk_hash: ChunkHash; - encoded_length: number; - encoded_merkle_root: string; - gas_limit: number; - gas_used: number; - height_created: number; - height_included: number; - outcome_root: string; - outgoing_receipts_root: string; - prev_block_hash: string; - prev_state_root: string; - rent_paid: string; - shard_id: number; - signature: string; - tx_root: string; - validator_proposals: any[]; - validator_reward: string; -} - -export interface ChunkResult { - author: string; - header: ChunkHeader; - receipts: any[]; - transactions: Transaction[]; -} - -export interface Chunk { - chunk_hash: string; - prev_block_hash: string; - outcome_root: string; - prev_state_root: string; - encoded_merkle_root: string; - encoded_length: number; - height_created: number; - height_included: number; - shard_id: number; - gas_used: number; - gas_limit: number; - rent_paid: string; - validator_reward: string; - balance_burnt: string; - outgoing_receipts_root: string; - tx_root: string; - validator_proposals: any[]; - signature: string; -} - -export interface Transaction { - actions: Array; - hash: string; - nonce: BN; - public_key: string; - receiver_id: string; - signature: string; - signer_id: string; -} - -export interface BlockResult { - author: string; - header: BlockHeader; - chunks: Chunk[]; -} - -export interface BlockChange { - type: string; - account_id: string; -} - -export interface BlockChangeResult { - block_hash: string; - changes: BlockChange[]; -} - -export interface ChangeResult { - block_hash: string; - changes: any[]; -} - -export interface CurrentEpochValidatorInfo { - account_id: string; - public_key: string; - is_slashed: boolean; - stake: string; - shards: number[]; - num_produced_blocks: number; - num_expected_blocks: number; -} - -export interface NextEpochValidatorInfo { - account_id: string; - public_key: string; - stake: string; - shards: number[]; -} - -export interface ValidatorStakeView { - account_id: string; - public_key: string; - stake: string; -} - -export interface NearProtocolConfig { - runtime_config: NearProtocolRuntimeConfig; -} - -export interface NearProtocolRuntimeConfig { - storage_amount_per_byte: string; -} - -export interface EpochValidatorInfo { - // Validators for the current epoch. - next_validators: NextEpochValidatorInfo[]; - // Validators for the next epoch. - current_validators: CurrentEpochValidatorInfo[]; - // Fishermen for the current epoch. - next_fisherman: ValidatorStakeView[]; - // Fishermen for the next epoch. - current_fisherman: ValidatorStakeView[]; - // Proposals in the current epoch. - current_proposals: ValidatorStakeView[]; - // Kickout in the previous epoch. - prev_epoch_kickout: ValidatorStakeView[]; - // Epoch start height. - epoch_start_height: number; -} - -export interface MerkleNode { - hash: string; - direction: string; -} - -export type MerklePath = MerkleNode[]; - -export interface BlockHeaderInnerLiteView { - height: number; - epoch_id: string; - next_epoch_id: string; - prev_state_root: string; - outcome_root: string; - timestamp: number; - next_bp_hash: string; - block_merkle_root: string; -} - -export interface LightClientBlockLiteView { - prev_block_hash: string; - inner_rest_hash: string; - inner_lite: BlockHeaderInnerLiteView; -} - -export interface LightClientProof { - outcome_proof: ExecutionOutcomeWithIdView; - outcome_root_proof: MerklePath; - block_header_lite: LightClientBlockLiteView; - block_proof: MerklePath; -} - -export enum IdType { - Transaction = 'transaction', - Receipt = 'receipt', -} - -export interface LightClientProofRequest { - type: IdType; - light_client_head: string; - transaction_hash?: string; - sender_id?: string; - receipt_id?: string; - receiver_id?: string; -} - -export interface GasPrice { - gas_price: string; -} - -export interface AccessKeyWithPublicKey { - account_id: string; - public_key: string; -} - -export interface QueryResponseKind { - block_height: BlockHeight; - block_hash: BlockHash; -} - -export interface AccountView extends QueryResponseKind { - amount: string; - locked: string; - code_hash: string; - storage_usage: number; - storage_paid_at: BlockHeight; -} - -interface StateItem { - key: string; - value: string; - proof: string[]; -} - -export interface ViewStateResult extends QueryResponseKind { - values: StateItem[]; - proof: string[]; -} - -export interface CodeResult extends QueryResponseKind { - result: number[]; - logs: string[]; -} - -export interface ContractCodeView extends QueryResponseKind { - code_base64: string; - hash: string; -} - -export interface FunctionCallPermissionView { - FunctionCall: { - allowance: string; - receiver_id: string; - method_names: string[]; - }; -} -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; - access_key: AccessKeyView; -} - -export interface AccessKeyList extends QueryResponseKind { - keys: AccessKeyInfoView[]; -} - -export interface ViewAccountRequest { - request_type: 'view_account'; - account_id: string; -} - -export interface ViewCodeRequest { - request_type: 'view_code'; - account_id: string; -} - -export interface ViewStateRequest { - request_type: 'view_state'; - account_id: string; - prefix_base64: string; -} - -export interface ViewAccessKeyRequest { - request_type: 'view_access_key'; - account_id: string; - public_key: string; -} - -export interface ViewAccessKeyListRequest { - request_type: 'view_access_key_list'; - account_id: string; -} - -export interface CallFunctionRequest { - request_type: 'call_function'; - account_id: string; - method_name: string; - args_base64: string; -} - -export type RpcQueryRequest = (ViewAccountRequest | - ViewCodeRequest | - ViewStateRequest | - ViewAccountRequest | - ViewAccessKeyRequest | - ViewAccessKeyListRequest | - CallFunctionRequest) & BlockReference - - -/** @hidden */ -export abstract class Provider { - abstract status(): Promise; - - abstract sendTransaction(signedTransaction: SignedTransaction): Promise; - abstract sendTransactionAsync(signedTransaction: SignedTransaction): Promise; - abstract txStatus(txHash: Uint8Array | string, accountId: string): Promise; - abstract txStatusReceipts(txHash: Uint8Array | string, accountId: string): Promise; - abstract query(params: RpcQueryRequest): Promise; - abstract query(path: string, data: string): Promise; - // TODO: BlockQuery type? - abstract block(blockQuery: BlockId | BlockReference): Promise; - abstract blockChanges(blockQuery: BlockId | BlockReference): Promise; - abstract chunk(chunkId: ChunkId): Promise; - // TODO: Use BlockQuery? - abstract validators(blockId: BlockId): Promise; - abstract experimental_protocolConfig(blockReference: BlockReference): Promise; - abstract lightClientProof(request: LightClientProofRequest): Promise; - abstract gasPrice(blockId: BlockId): Promise; - abstract accessKeyChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; - abstract singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], BlockQuery: BlockId | BlockReference): Promise; - abstract accountChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; - abstract contractStateChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference, keyPrefix: string): Promise; - abstract contractCodeChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; -} - -/** @hidden */ -export function getTransactionLastResult(txResult: FinalExecutionOutcome): any { - if (typeof txResult.status === 'object' && typeof txResult.status.SuccessValue === 'string') { - const value = Buffer.from(txResult.status.SuccessValue, 'base64').toString(); - try { - return JSON.parse(value); - } catch (e) { - return value; - } - } - return null; -} +export { + IdType, + LightClientBlockLiteView, + LightClientProof, + LightClientProofRequest, + + AccessKeyWithPublicKey, + BlockHash, + BlockChange, + BlockChangeResult, + BlockHeader, + BlockHeaderInnerLiteView, + BlockHeight, + BlockId, + BlockReference, + BlockResult, + BlockShardId, + ChangeResult, + Chunk, + ChunkHash, + ChunkHeader, + ChunkId, + ChunkResult, + Finality, + GasPrice, + MerkleNode, + MerklePath, + NearProtocolConfig, + NearProtocolRuntimeConfig, + NodeStatusResult, + ShardId, + SyncInfo, + TotalWeight, + ProviderTransaction as Transaction, + Provider, + + CallFunctionRequest, + RpcQueryRequest, + ViewAccessKeyListRequest, + ViewAccessKeyRequest, + ViewAccountRequest, + ViewCodeRequest, + ViewStateRequest, + + AccessKeyInfoView, + AccessKeyList, + AccessKeyView, + AccessKeyViewRaw, + AccountView, + CodeResult, + ContractCodeView, + ExecutionError, + ExecutionOutcome, + ExecutionOutcomeWithId, + ExecutionOutcomeWithIdView, + ExecutionStatus, + ExecutionStatusBasic, + FinalExecutionOutcome, + FinalExecutionStatus, + FinalExecutionStatusBasic, + FunctionCallPermissionView, + QueryResponseKind, + ViewStateResult, + getTransactionLastResult, + + CurrentEpochValidatorInfo, + EpochValidatorInfo, + NextEpochValidatorInfo, + ValidatorStakeView, +} from '@near-js/core'; From 2e38bc3d275d0abc40e737d38fa76067279e4f1d Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 14 Nov 2022 15:34:04 -0800 Subject: [PATCH 11/84] refactor: move format methods to core --- packages/core/src/format.ts | 107 +++++++++++++++++++++ packages/core/src/index.ts | 1 + packages/near-api-js/src/utils/format.ts | 113 ++--------------------- 3 files changed, 114 insertions(+), 107 deletions(-) create mode 100644 packages/core/src/format.ts diff --git a/packages/core/src/format.ts b/packages/core/src/format.ts new file mode 100644 index 000000000..9e68d0b1b --- /dev/null +++ b/packages/core/src/format.ts @@ -0,0 +1,107 @@ +import BN from 'bn.js'; + +/** + * Exponent for calculating how many indivisible units are there in one NEAR. See {@link NEAR_NOMINATION}. + */ +export const NEAR_NOMINATION_EXP = 24; + +/** + * Number of indivisible units in one NEAR. Derived from {@link NEAR_NOMINATION_EXP}. + */ +export const NEAR_NOMINATION = new BN('10', 10).pow(new BN(NEAR_NOMINATION_EXP, 10)); + +// Pre-calculate offests used for rounding to different number of digits +const ROUNDING_OFFSETS: BN[] = []; +const BN10 = new BN(10); +for (let i = 0, offset = new BN(5); i < NEAR_NOMINATION_EXP; i++, offset = offset.mul(BN10)) { + ROUNDING_OFFSETS[i] = offset; +} + +/** + * Convert account balance value from internal indivisible units to NEAR. 1 NEAR is defined by {@link NEAR_NOMINATION}. + * Effectively this divides given amount by {@link NEAR_NOMINATION}. + * + * @param balance decimal string representing balance in smallest non-divisible NEAR units (as specified by {@link NEAR_NOMINATION}) + * @param fracDigits number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits. + * @returns Value in Ⓝ + */ +export function formatNearAmount(balance: string, fracDigits: number = NEAR_NOMINATION_EXP): string { + const balanceBN = new BN(balance, 10); + if (fracDigits !== NEAR_NOMINATION_EXP) { + // Adjust balance for rounding at given number of digits + const roundingExp = NEAR_NOMINATION_EXP - fracDigits - 1; + if (roundingExp > 0) { + balanceBN.iadd(ROUNDING_OFFSETS[roundingExp]); + } + } + + balance = balanceBN.toString(); + const wholeStr = balance.substring(0, balance.length - NEAR_NOMINATION_EXP) || '0'; + const fractionStr = balance.substring(balance.length - NEAR_NOMINATION_EXP) + .padStart(NEAR_NOMINATION_EXP, '0').substring(0, fracDigits); + + return trimTrailingZeroes(`${formatWithCommas(wholeStr)}.${fractionStr}`); +} + +/** + * Convert human readable NEAR amount to internal indivisible units. + * Effectively this multiplies given amount by {@link NEAR_NOMINATION}. + * + * @param amt decimal string (potentially fractional) denominated in NEAR. + * @returns The parsed yoctoⓃ amount or null if no amount was passed in + */ +export function parseNearAmount(amt?: string): string | null { + if (!amt) { return null; } + amt = cleanupAmount(amt); + const split = amt.split('.'); + const wholePart = split[0]; + const fracPart = split[1] || ''; + if (split.length > 2 || fracPart.length > NEAR_NOMINATION_EXP) { + throw new Error(`Cannot parse '${amt}' as NEAR amount`); + } + return trimLeadingZeroes(wholePart + fracPart.padEnd(NEAR_NOMINATION_EXP, '0')); +} + +/** + * Removes commas from the input + * @param amount A value or amount that may contain commas + * @returns string The cleaned value + */ +function cleanupAmount(amount: string): string { + return amount.replace(/,/g, '').trim(); +} + +/** + * Removes .000… from an input + * @param value A value that may contain trailing zeroes in the decimals place + * @returns string The value without the trailing zeros + */ +function trimTrailingZeroes(value: string): string { + return value.replace(/\.?0*$/, ''); +} + +/** + * Removes leading zeroes from an input + * @param value A value that may contain leading zeroes + * @returns string The value without the leading zeroes + */ +function trimLeadingZeroes(value: string): string { + value = value.replace(/^0+/, ''); + if (value === '') { + return '0'; + } + return value; +} + +/** + * Returns a human-readable value with commas + * @param value A value that may not contain commas + * @returns string A value with commas + */ +function formatWithCommas(value: string): string { + const pattern = /(-?\d+)(\d{3})/; + while (pattern.test(value)) { + value = value.replace(pattern, '$1,$2'); + } + return value; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1c8a85ffa..baf0b4e4f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ +export * from './format'; export * from './key_pair'; export * from './key_store'; export * from './provider'; diff --git a/packages/near-api-js/src/utils/format.ts b/packages/near-api-js/src/utils/format.ts index 9e68d0b1b..26ab737c3 100644 --- a/packages/near-api-js/src/utils/format.ts +++ b/packages/near-api-js/src/utils/format.ts @@ -1,107 +1,6 @@ -import BN from 'bn.js'; - -/** - * Exponent for calculating how many indivisible units are there in one NEAR. See {@link NEAR_NOMINATION}. - */ -export const NEAR_NOMINATION_EXP = 24; - -/** - * Number of indivisible units in one NEAR. Derived from {@link NEAR_NOMINATION_EXP}. - */ -export const NEAR_NOMINATION = new BN('10', 10).pow(new BN(NEAR_NOMINATION_EXP, 10)); - -// Pre-calculate offests used for rounding to different number of digits -const ROUNDING_OFFSETS: BN[] = []; -const BN10 = new BN(10); -for (let i = 0, offset = new BN(5); i < NEAR_NOMINATION_EXP; i++, offset = offset.mul(BN10)) { - ROUNDING_OFFSETS[i] = offset; -} - -/** - * Convert account balance value from internal indivisible units to NEAR. 1 NEAR is defined by {@link NEAR_NOMINATION}. - * Effectively this divides given amount by {@link NEAR_NOMINATION}. - * - * @param balance decimal string representing balance in smallest non-divisible NEAR units (as specified by {@link NEAR_NOMINATION}) - * @param fracDigits number of fractional digits to preserve in formatted string. Balance is rounded to match given number of digits. - * @returns Value in Ⓝ - */ -export function formatNearAmount(balance: string, fracDigits: number = NEAR_NOMINATION_EXP): string { - const balanceBN = new BN(balance, 10); - if (fracDigits !== NEAR_NOMINATION_EXP) { - // Adjust balance for rounding at given number of digits - const roundingExp = NEAR_NOMINATION_EXP - fracDigits - 1; - if (roundingExp > 0) { - balanceBN.iadd(ROUNDING_OFFSETS[roundingExp]); - } - } - - balance = balanceBN.toString(); - const wholeStr = balance.substring(0, balance.length - NEAR_NOMINATION_EXP) || '0'; - const fractionStr = balance.substring(balance.length - NEAR_NOMINATION_EXP) - .padStart(NEAR_NOMINATION_EXP, '0').substring(0, fracDigits); - - return trimTrailingZeroes(`${formatWithCommas(wholeStr)}.${fractionStr}`); -} - -/** - * Convert human readable NEAR amount to internal indivisible units. - * Effectively this multiplies given amount by {@link NEAR_NOMINATION}. - * - * @param amt decimal string (potentially fractional) denominated in NEAR. - * @returns The parsed yoctoⓃ amount or null if no amount was passed in - */ -export function parseNearAmount(amt?: string): string | null { - if (!amt) { return null; } - amt = cleanupAmount(amt); - const split = amt.split('.'); - const wholePart = split[0]; - const fracPart = split[1] || ''; - if (split.length > 2 || fracPart.length > NEAR_NOMINATION_EXP) { - throw new Error(`Cannot parse '${amt}' as NEAR amount`); - } - return trimLeadingZeroes(wholePart + fracPart.padEnd(NEAR_NOMINATION_EXP, '0')); -} - -/** - * Removes commas from the input - * @param amount A value or amount that may contain commas - * @returns string The cleaned value - */ -function cleanupAmount(amount: string): string { - return amount.replace(/,/g, '').trim(); -} - -/** - * Removes .000… from an input - * @param value A value that may contain trailing zeroes in the decimals place - * @returns string The value without the trailing zeros - */ -function trimTrailingZeroes(value: string): string { - return value.replace(/\.?0*$/, ''); -} - -/** - * Removes leading zeroes from an input - * @param value A value that may contain leading zeroes - * @returns string The value without the leading zeroes - */ -function trimLeadingZeroes(value: string): string { - value = value.replace(/^0+/, ''); - if (value === '') { - return '0'; - } - return value; -} - -/** - * Returns a human-readable value with commas - * @param value A value that may not contain commas - * @returns string A value with commas - */ -function formatWithCommas(value: string): string { - const pattern = /(-?\d+)(\d{3})/; - while (pattern.test(value)) { - value = value.replace(pattern, '$1,$2'); - } - return value; -} +export { + NEAR_NOMINATION, + NEAR_NOMINATION_EXP, + formatNearAmount, + parseNearAmount, +} from '@near-js/core'; From 302fc3b40eba1e76ee1be89442af9acbc2dda4ca Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 14 Nov 2022 15:36:56 -0800 Subject: [PATCH 12/84] refactor: move error modules to core --- packages/core/package.json | 1 + packages/core/src/errors/error_messages.json | 66 ++ packages/core/src/errors/errors.ts | 34 + packages/core/src/errors/index.ts | 14 + .../core/src/errors/rpc_error_schema.json | 869 ++++++++++++++++++ packages/core/src/errors/rpc_errors.ts | 120 +++ packages/core/src/index.ts | 1 + packages/near-api-js/src/utils/errors.ts | 67 +- packages/near-api-js/src/utils/rpc_errors.ts | 127 +-- 9 files changed, 1119 insertions(+), 180 deletions(-) create mode 100644 packages/core/src/errors/error_messages.json create mode 100644 packages/core/src/errors/errors.ts create mode 100644 packages/core/src/errors/index.ts create mode 100644 packages/core/src/errors/rpc_error_schema.json create mode 100644 packages/core/src/errors/rpc_errors.ts diff --git a/packages/core/package.json b/packages/core/package.json index d3b6e4b15..c5709e426 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -14,6 +14,7 @@ "bn.js": "5.2.1", "borsh": "^0.7.0", "js-sha256": "^0.9.0", + "mustache": "^4.0.0", "tweetnacl": "^1.0.1" }, "devDependencies": { diff --git a/packages/core/src/errors/error_messages.json b/packages/core/src/errors/error_messages.json new file mode 100644 index 000000000..085313959 --- /dev/null +++ b/packages/core/src/errors/error_messages.json @@ -0,0 +1,66 @@ +{ + "GasLimitExceeded": "Exceeded the maximum amount of gas allowed to burn per contract", + "MethodEmptyName": "Method name is empty", + "WasmerCompileError": "Wasmer compilation error: {{msg}}", + "GuestPanic": "Smart contract panicked: {{panic_msg}}", + "Memory": "Error creating Wasm memory", + "GasExceeded": "Exceeded the prepaid gas", + "MethodUTF8Error": "Method name is not valid UTF8 string", + "BadUTF16": "String encoding is bad UTF-16 sequence", + "WasmTrap": "WebAssembly trap: {{msg}}", + "GasInstrumentation": "Gas instrumentation failed or contract has denied instructions.", + "InvalidPromiseIndex": "{{promise_idx}} does not correspond to existing promises", + "InvalidPromiseResultIndex": "Accessed invalid promise result index: {{result_idx}}", + "Deserialization": "Error happened while deserializing the module", + "MethodNotFound": "Contract method is not found", + "InvalidRegisterId": "Accessed invalid register id: {{register_id}}", + "InvalidReceiptIndex": "VM Logic returned an invalid receipt index: {{receipt_index}}", + "EmptyMethodName": "Method name is empty in contract call", + "CannotReturnJointPromise": "Returning joint promise is currently prohibited", + "StackHeightInstrumentation": "Stack instrumentation failed", + "CodeDoesNotExist": "Cannot find contract code for account {{account_id}}", + "MethodInvalidSignature": "Invalid method signature", + "IntegerOverflow": "Integer overflow happened during contract execution", + "MemoryAccessViolation": "MemoryAccessViolation", + "InvalidIteratorIndex": "Iterator index {{iterator_index}} does not exist", + "IteratorWasInvalidated": "Iterator {{iterator_index}} was invalidated after its creation by performing a mutable operation on trie", + "InvalidAccountId": "VM Logic returned an invalid account id", + "Serialization": "Error happened while serializing the module", + "CannotAppendActionToJointPromise": "Actions can only be appended to non-joint promise.", + "InternalMemoryDeclared": "Internal memory declaration has been found in the module", + "Instantiate": "Error happened during instantiation", + "ProhibitedInView": "{{method_name}} is not allowed in view calls", + "InvalidMethodName": "VM Logic returned an invalid method name", + "BadUTF8": "String encoding is bad UTF-8 sequence", + "BalanceExceeded": "Exceeded the account balance", + "LinkError": "Wasm contract link error: {{msg}}", + "InvalidPublicKey": "VM Logic provided an invalid public key", + "ActorNoPermission": "Actor {{actor_id}} doesn't have permission to account {{account_id}} to complete the action", + "LackBalanceForState": "The account {{account_id}} wouldn't have enough balance to cover storage, required to have {{amount}} yoctoNEAR more", + "ReceiverMismatch": "Wrong AccessKey used for transaction: transaction is sent to receiver_id={{tx_receiver}}, but is signed with function call access key that restricted to only use with receiver_id={{ak_receiver}}. Either change receiver_id in your transaction or switch to use a FullAccessKey.", + "CostOverflow": "Transaction gas or balance cost is too high", + "InvalidSignature": "Transaction is not signed with the given public key", + "AccessKeyNotFound": "Signer \"{{account_id}}\" doesn't have access key with the given public_key {{public_key}}", + "NotEnoughBalance": "Sender {{signer_id}} does not have enough balance {{#formatNear}}{{balance}}{{/formatNear}} for operation costing {{#formatNear}}{{cost}}{{/formatNear}}", + "NotEnoughAllowance": "Access Key {account_id}:{public_key} does not have enough balance {{#formatNear}}{{allowance}}{{/formatNear}} for transaction costing {{#formatNear}}{{cost}}{{/formatNear}}", + "Expired": "Transaction has expired", + "DeleteAccountStaking": "Account {{account_id}} is staking and can not be deleted", + "SignerDoesNotExist": "Signer {{signer_id}} does not exist", + "TriesToStake": "Account {{account_id}} tried to stake {{#formatNear}}{{stake}}{{/formatNear}}, but has staked {{#formatNear}}{{locked}}{{/formatNear}} and only has {{#formatNear}}{{balance}}{{/formatNear}}", + "AddKeyAlreadyExists": "The public key {{public_key}} is already used for an existing access key", + "InvalidSigner": "Invalid signer account ID {{signer_id}} according to requirements", + "CreateAccountNotAllowed": "The new account_id {{account_id}} can't be created by {{predecessor_id}}", + "RequiresFullAccess": "The transaction contains more then one action, but it was signed with an access key which allows transaction to apply only one specific action. To apply more then one actions TX must be signed with a full access key", + "TriesToUnstake": "Account {{account_id}} is not yet staked, but tried to unstake", + "InvalidNonce": "Transaction nonce {{tx_nonce}} must be larger than nonce of the used access key {{ak_nonce}}", + "AccountAlreadyExists": "Can't create a new account {{account_id}}, because it already exists", + "InvalidChain": "Transaction parent block hash doesn't belong to the current chain", + "AccountDoesNotExist": "Can't complete the action because account {{account_id}} doesn't exist", + "MethodNameMismatch": "Transaction method name {{method_name}} isn't allowed by the access key", + "DeleteAccountHasRent": "Account {{account_id}} can't be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover the rent", + "DeleteAccountHasEnoughBalance": "Account {{account_id}} can't be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover it's storage", + "InvalidReceiver": "Invalid receiver account ID {{receiver_id}} according to requirements", + "DeleteKeyDoesNotExist": "Account {{account_id}} tries to remove an access key that doesn't exist", + "Timeout": "Timeout exceeded", + "Closed": "Connection closed" +} diff --git a/packages/core/src/errors/errors.ts b/packages/core/src/errors/errors.ts new file mode 100644 index 000000000..bb46224b0 --- /dev/null +++ b/packages/core/src/errors/errors.ts @@ -0,0 +1,34 @@ +export class PositionalArgsError extends Error { + constructor() { + super('Contract method calls expect named arguments wrapped in object, e.g. { argName1: argValue1, argName2: argValue2 }'); + } +} + +export class ArgumentTypeError extends Error { + constructor(argName: string, argType: string, argValue: any) { + super(`Expected ${argType} for '${argName}' argument, but got '${JSON.stringify(argValue)}'`); + } +} + +export class TypedError extends Error { + type: string; + context?: ErrorContext; + constructor(message?: string, type?: string, context?: ErrorContext) { + super(message); + this.type = type || 'UntypedError'; + this.context = context; + } +} + +export class ErrorContext { + transactionHash?: string; + constructor(transactionHash?: string) { + this.transactionHash = transactionHash; + } +} + +export function logWarning(...args: any[]): void { + if (!process.env['NEAR_NO_LOGS']){ + console.warn(...args); + } +} \ No newline at end of file diff --git a/packages/core/src/errors/index.ts b/packages/core/src/errors/index.ts new file mode 100644 index 000000000..3889242d0 --- /dev/null +++ b/packages/core/src/errors/index.ts @@ -0,0 +1,14 @@ +export { + ArgumentTypeError, + ErrorContext, + PositionalArgsError, + TypedError, + logWarning, +} from './errors'; +export { + ServerError, + formatError, + getErrorTypeFromErrorMessage, + parseResultError, + parseRpcError, +} from './rpc_errors'; diff --git a/packages/core/src/errors/rpc_error_schema.json b/packages/core/src/errors/rpc_error_schema.json new file mode 100644 index 000000000..e5ac6cec6 --- /dev/null +++ b/packages/core/src/errors/rpc_error_schema.json @@ -0,0 +1,869 @@ +{ + "schema": { + "BadUTF16": { + "name": "BadUTF16", + "subtypes": [], + "props": {} + }, + "BadUTF8": { + "name": "BadUTF8", + "subtypes": [], + "props": {} + }, + "BalanceExceeded": { + "name": "BalanceExceeded", + "subtypes": [], + "props": {} + }, + "BreakpointTrap": { + "name": "BreakpointTrap", + "subtypes": [], + "props": {} + }, + "CacheError": { + "name": "CacheError", + "subtypes": [ + "ReadError", + "WriteError", + "DeserializationError", + "SerializationError" + ], + "props": {} + }, + "CallIndirectOOB": { + "name": "CallIndirectOOB", + "subtypes": [], + "props": {} + }, + "CannotAppendActionToJointPromise": { + "name": "CannotAppendActionToJointPromise", + "subtypes": [], + "props": {} + }, + "CannotReturnJointPromise": { + "name": "CannotReturnJointPromise", + "subtypes": [], + "props": {} + }, + "CodeDoesNotExist": { + "name": "CodeDoesNotExist", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "CompilationError": { + "name": "CompilationError", + "subtypes": [ + "CodeDoesNotExist", + "PrepareError", + "WasmerCompileError" + ], + "props": {} + }, + "ContractSizeExceeded": { + "name": "ContractSizeExceeded", + "subtypes": [], + "props": { + "limit": "", + "size": "" + } + }, + "Deprecated": { + "name": "Deprecated", + "subtypes": [], + "props": { + "method_name": "" + } + }, + "Deserialization": { + "name": "Deserialization", + "subtypes": [], + "props": {} + }, + "DeserializationError": { + "name": "DeserializationError", + "subtypes": [], + "props": {} + }, + "EmptyMethodName": { + "name": "EmptyMethodName", + "subtypes": [], + "props": {} + }, + "FunctionCallError": { + "name": "FunctionCallError", + "subtypes": [ + "CompilationError", + "LinkError", + "MethodResolveError", + "WasmTrap", + "WasmUnknownError", + "HostError", + "EvmError" + ], + "props": {} + }, + "GasExceeded": { + "name": "GasExceeded", + "subtypes": [], + "props": {} + }, + "GasInstrumentation": { + "name": "GasInstrumentation", + "subtypes": [], + "props": {} + }, + "GasLimitExceeded": { + "name": "GasLimitExceeded", + "subtypes": [], + "props": {} + }, + "GenericTrap": { + "name": "GenericTrap", + "subtypes": [], + "props": {} + }, + "GuestPanic": { + "name": "GuestPanic", + "subtypes": [], + "props": { + "panic_msg": "" + } + }, + "HostError": { + "name": "HostError", + "subtypes": [ + "BadUTF16", + "BadUTF8", + "GasExceeded", + "GasLimitExceeded", + "BalanceExceeded", + "EmptyMethodName", + "GuestPanic", + "IntegerOverflow", + "InvalidPromiseIndex", + "CannotAppendActionToJointPromise", + "CannotReturnJointPromise", + "InvalidPromiseResultIndex", + "InvalidRegisterId", + "IteratorWasInvalidated", + "MemoryAccessViolation", + "InvalidReceiptIndex", + "InvalidIteratorIndex", + "InvalidAccountId", + "InvalidMethodName", + "InvalidPublicKey", + "ProhibitedInView", + "NumberOfLogsExceeded", + "KeyLengthExceeded", + "ValueLengthExceeded", + "TotalLogLengthExceeded", + "NumberPromisesExceeded", + "NumberInputDataDependenciesExceeded", + "ReturnedValueLengthExceeded", + "ContractSizeExceeded", + "Deprecated" + ], + "props": {} + }, + "IllegalArithmetic": { + "name": "IllegalArithmetic", + "subtypes": [], + "props": {} + }, + "IncorrectCallIndirectSignature": { + "name": "IncorrectCallIndirectSignature", + "subtypes": [], + "props": {} + }, + "Instantiate": { + "name": "Instantiate", + "subtypes": [], + "props": {} + }, + "IntegerOverflow": { + "name": "IntegerOverflow", + "subtypes": [], + "props": {} + }, + "InternalMemoryDeclared": { + "name": "InternalMemoryDeclared", + "subtypes": [], + "props": {} + }, + "InvalidAccountId": { + "name": "InvalidAccountId", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "InvalidIteratorIndex": { + "name": "InvalidIteratorIndex", + "subtypes": [], + "props": { + "iterator_index": "" + } + }, + "InvalidMethodName": { + "name": "InvalidMethodName", + "subtypes": [], + "props": {} + }, + "InvalidPromiseIndex": { + "name": "InvalidPromiseIndex", + "subtypes": [], + "props": { + "promise_idx": "" + } + }, + "InvalidPromiseResultIndex": { + "name": "InvalidPromiseResultIndex", + "subtypes": [], + "props": { + "result_idx": "" + } + }, + "InvalidPublicKey": { + "name": "InvalidPublicKey", + "subtypes": [], + "props": {} + }, + "InvalidReceiptIndex": { + "name": "InvalidReceiptIndex", + "subtypes": [], + "props": { + "receipt_index": "" + } + }, + "InvalidRegisterId": { + "name": "InvalidRegisterId", + "subtypes": [], + "props": { + "register_id": "" + } + }, + "IteratorWasInvalidated": { + "name": "IteratorWasInvalidated", + "subtypes": [], + "props": { + "iterator_index": "" + } + }, + "KeyLengthExceeded": { + "name": "KeyLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "LinkError": { + "name": "LinkError", + "subtypes": [], + "props": { + "msg": "" + } + }, + "Memory": { + "name": "Memory", + "subtypes": [], + "props": {} + }, + "MemoryAccessViolation": { + "name": "MemoryAccessViolation", + "subtypes": [], + "props": {} + }, + "MemoryOutOfBounds": { + "name": "MemoryOutOfBounds", + "subtypes": [], + "props": {} + }, + "MethodEmptyName": { + "name": "MethodEmptyName", + "subtypes": [], + "props": {} + }, + "MethodInvalidSignature": { + "name": "MethodInvalidSignature", + "subtypes": [], + "props": {} + }, + "MethodNotFound": { + "name": "MethodNotFound", + "subtypes": [], + "props": {} + }, + "MethodResolveError": { + "name": "MethodResolveError", + "subtypes": [ + "MethodEmptyName", + "MethodUTF8Error", + "MethodNotFound", + "MethodInvalidSignature" + ], + "props": {} + }, + "MethodUTF8Error": { + "name": "MethodUTF8Error", + "subtypes": [], + "props": {} + }, + "MisalignedAtomicAccess": { + "name": "MisalignedAtomicAccess", + "subtypes": [], + "props": {} + }, + "NumberInputDataDependenciesExceeded": { + "name": "NumberInputDataDependenciesExceeded", + "subtypes": [], + "props": { + "limit": "", + "number_of_input_data_dependencies": "" + } + }, + "NumberOfLogsExceeded": { + "name": "NumberOfLogsExceeded", + "subtypes": [], + "props": { + "limit": "" + } + }, + "NumberPromisesExceeded": { + "name": "NumberPromisesExceeded", + "subtypes": [], + "props": { + "limit": "", + "number_of_promises": "" + } + }, + "PrepareError": { + "name": "PrepareError", + "subtypes": [ + "Serialization", + "Deserialization", + "InternalMemoryDeclared", + "GasInstrumentation", + "StackHeightInstrumentation", + "Instantiate", + "Memory" + ], + "props": {} + }, + "ProhibitedInView": { + "name": "ProhibitedInView", + "subtypes": [], + "props": { + "method_name": "" + } + }, + "ReadError": { + "name": "ReadError", + "subtypes": [], + "props": {} + }, + "ReturnedValueLengthExceeded": { + "name": "ReturnedValueLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "Serialization": { + "name": "Serialization", + "subtypes": [], + "props": {} + }, + "SerializationError": { + "name": "SerializationError", + "subtypes": [], + "props": { + "hash": "" + } + }, + "StackHeightInstrumentation": { + "name": "StackHeightInstrumentation", + "subtypes": [], + "props": {} + }, + "StackOverflow": { + "name": "StackOverflow", + "subtypes": [], + "props": {} + }, + "TotalLogLengthExceeded": { + "name": "TotalLogLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "Unreachable": { + "name": "Unreachable", + "subtypes": [], + "props": {} + }, + "ValueLengthExceeded": { + "name": "ValueLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "WasmTrap": { + "name": "WasmTrap", + "subtypes": [ + "Unreachable", + "IncorrectCallIndirectSignature", + "MemoryOutOfBounds", + "CallIndirectOOB", + "IllegalArithmetic", + "MisalignedAtomicAccess", + "BreakpointTrap", + "StackOverflow", + "GenericTrap" + ], + "props": {} + }, + "WasmUnknownError": { + "name": "WasmUnknownError", + "subtypes": [], + "props": {} + }, + "WasmerCompileError": { + "name": "WasmerCompileError", + "subtypes": [], + "props": { + "msg": "" + } + }, + "WriteError": { + "name": "WriteError", + "subtypes": [], + "props": {} + }, + "AccessKeyNotFound": { + "name": "AccessKeyNotFound", + "subtypes": [], + "props": { + "account_id": "", + "public_key": "" + } + }, + "AccountAlreadyExists": { + "name": "AccountAlreadyExists", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "AccountDoesNotExist": { + "name": "AccountDoesNotExist", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "ActionError": { + "name": "ActionError", + "subtypes": [ + "AccountAlreadyExists", + "AccountDoesNotExist", + "CreateAccountOnlyByRegistrar", + "CreateAccountNotAllowed", + "ActorNoPermission", + "DeleteKeyDoesNotExist", + "AddKeyAlreadyExists", + "DeleteAccountStaking", + "LackBalanceForState", + "TriesToUnstake", + "TriesToStake", + "InsufficientStake", + "FunctionCallError", + "NewReceiptValidationError", + "OnlyImplicitAccountCreationAllowed" + ], + "props": { + "index": "" + } + }, + "ActionsValidationError": { + "name": "ActionsValidationError", + "subtypes": [ + "DeleteActionMustBeFinal", + "TotalPrepaidGasExceeded", + "TotalNumberOfActionsExceeded", + "AddKeyMethodNamesNumberOfBytesExceeded", + "AddKeyMethodNameLengthExceeded", + "IntegerOverflow", + "InvalidAccountId", + "ContractSizeExceeded", + "FunctionCallMethodNameLengthExceeded", + "FunctionCallArgumentsLengthExceeded", + "UnsuitableStakingKey", + "FunctionCallZeroAttachedGas" + ], + "props": {} + }, + "ActorNoPermission": { + "name": "ActorNoPermission", + "subtypes": [], + "props": { + "account_id": "", + "actor_id": "" + } + }, + "AddKeyAlreadyExists": { + "name": "AddKeyAlreadyExists", + "subtypes": [], + "props": { + "account_id": "", + "public_key": "" + } + }, + "AddKeyMethodNameLengthExceeded": { + "name": "AddKeyMethodNameLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "AddKeyMethodNamesNumberOfBytesExceeded": { + "name": "AddKeyMethodNamesNumberOfBytesExceeded", + "subtypes": [], + "props": { + "limit": "", + "total_number_of_bytes": "" + } + }, + "BalanceMismatchError": { + "name": "BalanceMismatchError", + "subtypes": [], + "props": { + "final_accounts_balance": "", + "final_postponed_receipts_balance": "", + "incoming_receipts_balance": "", + "incoming_validator_rewards": "", + "initial_accounts_balance": "", + "initial_postponed_receipts_balance": "", + "new_delayed_receipts_balance": "", + "other_burnt_amount": "", + "outgoing_receipts_balance": "", + "processed_delayed_receipts_balance": "", + "slashed_burnt_amount": "", + "tx_burnt_amount": "" + } + }, + "CostOverflow": { + "name": "CostOverflow", + "subtypes": [], + "props": {} + }, + "CreateAccountNotAllowed": { + "name": "CreateAccountNotAllowed", + "subtypes": [], + "props": { + "account_id": "", + "predecessor_id": "" + } + }, + "CreateAccountOnlyByRegistrar": { + "name": "CreateAccountOnlyByRegistrar", + "subtypes": [], + "props": { + "account_id": "", + "predecessor_id": "", + "registrar_account_id": "" + } + }, + "DeleteAccountStaking": { + "name": "DeleteAccountStaking", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "DeleteActionMustBeFinal": { + "name": "DeleteActionMustBeFinal", + "subtypes": [], + "props": {} + }, + "DeleteKeyDoesNotExist": { + "name": "DeleteKeyDoesNotExist", + "subtypes": [], + "props": { + "account_id": "", + "public_key": "" + } + }, + "DepositWithFunctionCall": { + "name": "DepositWithFunctionCall", + "subtypes": [], + "props": {} + }, + "Expired": { + "name": "Expired", + "subtypes": [], + "props": {} + }, + "FunctionCallArgumentsLengthExceeded": { + "name": "FunctionCallArgumentsLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "FunctionCallMethodNameLengthExceeded": { + "name": "FunctionCallMethodNameLengthExceeded", + "subtypes": [], + "props": { + "length": "", + "limit": "" + } + }, + "FunctionCallZeroAttachedGas": { + "name": "FunctionCallZeroAttachedGas", + "subtypes": [], + "props": {} + }, + "InsufficientStake": { + "name": "InsufficientStake", + "subtypes": [], + "props": { + "account_id": "", + "minimum_stake": "", + "stake": "" + } + }, + "InvalidAccessKeyError": { + "name": "InvalidAccessKeyError", + "subtypes": [ + "AccessKeyNotFound", + "ReceiverMismatch", + "MethodNameMismatch", + "RequiresFullAccess", + "NotEnoughAllowance", + "DepositWithFunctionCall" + ], + "props": {} + }, + "InvalidChain": { + "name": "InvalidChain", + "subtypes": [], + "props": {} + }, + "InvalidDataReceiverId": { + "name": "InvalidDataReceiverId", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "InvalidNonce": { + "name": "InvalidNonce", + "subtypes": [], + "props": { + "ak_nonce": "", + "tx_nonce": "" + } + }, + "InvalidPredecessorId": { + "name": "InvalidPredecessorId", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "InvalidReceiverId": { + "name": "InvalidReceiverId", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "InvalidSignature": { + "name": "InvalidSignature", + "subtypes": [], + "props": {} + }, + "InvalidSignerId": { + "name": "InvalidSignerId", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "InvalidTxError": { + "name": "InvalidTxError", + "subtypes": [ + "InvalidAccessKeyError", + "InvalidSignerId", + "SignerDoesNotExist", + "InvalidNonce", + "InvalidReceiverId", + "InvalidSignature", + "NotEnoughBalance", + "LackBalanceForState", + "CostOverflow", + "InvalidChain", + "Expired", + "ActionsValidation" + ], + "props": {} + }, + "LackBalanceForState": { + "name": "LackBalanceForState", + "subtypes": [], + "props": { + "account_id": "", + "amount": "" + } + }, + "MethodNameMismatch": { + "name": "MethodNameMismatch", + "subtypes": [], + "props": { + "method_name": "" + } + }, + "NotEnoughAllowance": { + "name": "NotEnoughAllowance", + "subtypes": [], + "props": { + "account_id": "", + "allowance": "", + "cost": "", + "public_key": "" + } + }, + "NotEnoughBalance": { + "name": "NotEnoughBalance", + "subtypes": [], + "props": { + "balance": "", + "cost": "", + "signer_id": "" + } + }, + "OnlyImplicitAccountCreationAllowed": { + "name": "OnlyImplicitAccountCreationAllowed", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "ReceiptValidationError": { + "name": "ReceiptValidationError", + "subtypes": [ + "InvalidPredecessorId", + "InvalidReceiverId", + "InvalidSignerId", + "InvalidDataReceiverId", + "ReturnedValueLengthExceeded", + "NumberInputDataDependenciesExceeded", + "ActionsValidation" + ], + "props": {} + }, + "ReceiverMismatch": { + "name": "ReceiverMismatch", + "subtypes": [], + "props": { + "ak_receiver": "", + "tx_receiver": "" + } + }, + "RequiresFullAccess": { + "name": "RequiresFullAccess", + "subtypes": [], + "props": {} + }, + "SignerDoesNotExist": { + "name": "SignerDoesNotExist", + "subtypes": [], + "props": { + "signer_id": "" + } + }, + "TotalNumberOfActionsExceeded": { + "name": "TotalNumberOfActionsExceeded", + "subtypes": [], + "props": { + "limit": "", + "total_number_of_actions": "" + } + }, + "TotalPrepaidGasExceeded": { + "name": "TotalPrepaidGasExceeded", + "subtypes": [], + "props": { + "limit": "", + "total_prepaid_gas": "" + } + }, + "TriesToStake": { + "name": "TriesToStake", + "subtypes": [], + "props": { + "account_id": "", + "balance": "", + "locked": "", + "stake": "" + } + }, + "TriesToUnstake": { + "name": "TriesToUnstake", + "subtypes": [], + "props": { + "account_id": "" + } + }, + "TxExecutionError": { + "name": "TxExecutionError", + "subtypes": [ + "ActionError", + "InvalidTxError" + ], + "props": {} + }, + "UnsuitableStakingKey": { + "name": "UnsuitableStakingKey", + "subtypes": [], + "props": { + "public_key": "" + } + }, + "Closed": { + "name": "Closed", + "subtypes": [], + "props": {} + }, + "InternalError": { + "name": "InternalError", + "subtypes": [], + "props": {} + }, + "ServerError": { + "name": "ServerError", + "subtypes": [ + "TxExecutionError", + "Timeout", + "Closed", + "InternalError" + ], + "props": {} + }, + "Timeout": { + "name": "Timeout", + "subtypes": [], + "props": {} + } + } +} \ No newline at end of file diff --git a/packages/core/src/errors/rpc_errors.ts b/packages/core/src/errors/rpc_errors.ts new file mode 100644 index 000000000..27477f368 --- /dev/null +++ b/packages/core/src/errors/rpc_errors.ts @@ -0,0 +1,120 @@ +import Mustache from 'mustache'; + +import { formatNearAmount } from '../format'; +import { TypedError } from './errors' +import messages from './error_messages.json'; +import schema from './rpc_error_schema.json'; + +const mustacheHelpers = { + formatNear: () => (n, render) => formatNearAmount(render(n)) +}; + +export class ServerError extends TypedError { +} + +class ServerTransactionError extends ServerError { + public transaction_outcome: any; +} + +export function parseRpcError(errorObj: Record): ServerError { + const result = {}; + const errorClassName = walkSubtype(errorObj, schema.schema, result, ''); + // NOTE: This assumes that all errors extend TypedError + const error = new ServerError(formatError(errorClassName, result), errorClassName); + Object.assign(error, result); + return error; +} + +export function parseResultError(result: any): ServerTransactionError { + const server_error = parseRpcError(result.status.Failure); + const server_tx_error = new ServerTransactionError(); + Object.assign(server_tx_error, server_error); + server_tx_error.type = server_error.type; + server_tx_error.message = server_error.message; + server_tx_error.transaction_outcome = result.transaction_outcome; + return server_tx_error; +} + +export function formatError(errorClassName: string, errorData): string { + if (typeof messages[errorClassName] === 'string') { + return Mustache.render(messages[errorClassName], { + ...errorData, + ...mustacheHelpers + }); + } + return JSON.stringify(errorData); +} + +/** + * Walks through defined schema returning error(s) recursively + * @param errorObj The error to be parsed + * @param schema A defined schema in JSON mapping to the RPC errors + * @param result An object used in recursion or called directly + * @param typeName The human-readable error type name as defined in the JSON mapping + */ +function walkSubtype(errorObj, schema, result, typeName) { + let error; + let type; + let errorTypeName; + for (const errorName in schema) { + if (isString(errorObj[errorName])) { + // Return early if error type is in a schema + return errorObj[errorName]; + } + if (isObject(errorObj[errorName])) { + error = errorObj[errorName]; + type = schema[errorName]; + errorTypeName = errorName; + } else if (isObject(errorObj.kind) && isObject(errorObj.kind[errorName])) { + error = errorObj.kind[errorName]; + type = schema[errorName]; + errorTypeName = errorName; + } else { + continue; + } + } + if (error && type) { + for (const prop of Object.keys(type.props)) { + result[prop] = error[prop]; + } + return walkSubtype(error, schema, result, errorTypeName); + } else { + // TODO: is this the right thing to do? + result.kind = errorObj; + return typeName; + } +} + +export function getErrorTypeFromErrorMessage(errorMessage, errorType) { + // This function should be removed when JSON RPC starts returning typed errors. + switch (true) { + case /^account .*? does not exist while viewing$/.test(errorMessage): + return 'AccountDoesNotExist'; + case /^Account .*? doesn't exist$/.test(errorMessage): + return 'AccountDoesNotExist'; + case /^access key .*? does not exist while viewing$/.test(errorMessage): + return 'AccessKeyDoesNotExist'; + case /wasm execution failed with error: FunctionCallError\(CompilationError\(CodeDoesNotExist/.test(errorMessage): + return 'CodeDoesNotExist'; + case /Transaction nonce \d+ must be larger than nonce of the used access key \d+/.test(errorMessage): + return 'InvalidNonce'; + default: + return errorType; + } +} + +/** + * Helper function determining if the argument is an object + * @param n Value to check + */ +function isObject(n) { + return Object.prototype.toString.call(n) === '[object Object]'; +} + +/** + * Helper function determining if the argument is a string + * @param n Value to check + */ +function isString(n) { + return Object.prototype.toString.call(n) === '[object String]'; +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index baf0b4e4f..2c85a97f2 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ +export * from './errors'; export * from './format'; export * from './key_pair'; export * from './key_store'; diff --git a/packages/near-api-js/src/utils/errors.ts b/packages/near-api-js/src/utils/errors.ts index e0af04521..df8dda749 100644 --- a/packages/near-api-js/src/utils/errors.ts +++ b/packages/near-api-js/src/utils/errors.ts @@ -1,60 +1,7 @@ -import { ErrorObject } from 'ajv'; - -export class PositionalArgsError extends Error { - constructor() { - super('Contract method calls expect named arguments wrapped in object, e.g. { argName1: argValue1, argName2: argValue2 }'); - } -} - -export class ArgumentTypeError extends Error { - constructor(argName: string, argType: string, argValue: any) { - super(`Expected ${argType} for '${argName}' argument, but got '${JSON.stringify(argValue)}'`); - } -} - -export class TypedError extends Error { - type: string; - context?: ErrorContext; - constructor(message?: string, type?: string, context?: ErrorContext) { - super(message); - this.type = type || 'UntypedError'; - this.context = context; - } -} - -export class ErrorContext { - transactionHash?: string; - constructor(transactionHash?: string) { - this.transactionHash = transactionHash; - } -} - -export function logWarning(...args: any[]): void { - if (!process.env['NEAR_NO_LOGS']) { - console.warn(...args); - } -} - -export class UnsupportedSerializationError extends Error { - constructor(methodName: string, serializationType: string) { - super(`Contract method '${methodName}' is using an unsupported serialization type ${serializationType}`); - } -} - -export class UnknownArgumentError extends Error { - constructor(actualArgName: string, expectedArgNames: string[]) { - super(`Unrecognized argument '${actualArgName}', expected '${JSON.stringify(expectedArgNames)}'`); - } -} - -export class ArgumentSchemaError extends Error { - constructor(argName: string, errors: ErrorObject[]) { - super(`Argument '${argName}' does not conform to the specified ABI schema: '${JSON.stringify(errors)}'`); - } -} - -export class ConflictingOptions extends Error { - constructor() { - super('Conflicting contract method options have been passed. You can either specify ABI or a list of view/call methods.'); - } -} +export { + ArgumentTypeError, + ErrorContext, + PositionalArgsError, + TypedError, + logWarning, +} from '@near-js/core'; diff --git a/packages/near-api-js/src/utils/rpc_errors.ts b/packages/near-api-js/src/utils/rpc_errors.ts index 01e5621a5..e1018419c 100644 --- a/packages/near-api-js/src/utils/rpc_errors.ts +++ b/packages/near-api-js/src/utils/rpc_errors.ts @@ -1,120 +1,7 @@ - -import Mustache from 'mustache'; -import schema from '../generated/rpc_error_schema.json'; -import messages from '../res/error_messages.json'; -import { utils } from '../common-index'; -import { TypedError } from '../utils/errors'; - -const mustacheHelpers = { - formatNear: () => (n, render) => utils.format.formatNearAmount(render(n)) -}; - -export class ServerError extends TypedError { -} - -class ServerTransactionError extends ServerError { - public transaction_outcome: any; -} - -export function parseRpcError(errorObj: Record): ServerError { - const result = {}; - const errorClassName = walkSubtype(errorObj, schema.schema, result, ''); - // NOTE: This assumes that all errors extend TypedError - const error = new ServerError(formatError(errorClassName, result), errorClassName); - Object.assign(error, result); - return error; -} - -export function parseResultError(result: any): ServerTransactionError { - const server_error = parseRpcError(result.status.Failure); - const server_tx_error = new ServerTransactionError(); - Object.assign(server_tx_error, server_error); - server_tx_error.type = server_error.type; - server_tx_error.message = server_error.message; - server_tx_error.transaction_outcome = result.transaction_outcome; - return server_tx_error; -} - -export function formatError(errorClassName: string, errorData): string { - if (typeof messages[errorClassName] === 'string') { - return Mustache.render(messages[errorClassName], { - ...errorData, - ...mustacheHelpers - }); - } - return JSON.stringify(errorData); -} - -/** - * Walks through defined schema returning error(s) recursively - * @param errorObj The error to be parsed - * @param schema A defined schema in JSON mapping to the RPC errors - * @param result An object used in recursion or called directly - * @param typeName The human-readable error type name as defined in the JSON mapping - */ -function walkSubtype(errorObj, schema, result, typeName) { - let error; - let type; - let errorTypeName; - for (const errorName in schema) { - if (isString(errorObj[errorName])) { - // Return early if error type is in a schema - return errorObj[errorName]; - } - if (isObject(errorObj[errorName])) { - error = errorObj[errorName]; - type = schema[errorName]; - errorTypeName = errorName; - } else if (isObject(errorObj.kind) && isObject(errorObj.kind[errorName])) { - error = errorObj.kind[errorName]; - type = schema[errorName]; - errorTypeName = errorName; - } else { - continue; - } - } - if (error && type) { - for (const prop of Object.keys(type.props)) { - result[prop] = error[prop]; - } - return walkSubtype(error, schema, result, errorTypeName); - } else { - // TODO: is this the right thing to do? - result.kind = errorObj; - return typeName; - } -} - -export function getErrorTypeFromErrorMessage(errorMessage, errorType) { - // This function should be removed when JSON RPC starts returning typed errors. - switch (true) { - case /^account .*? does not exist while viewing$/.test(errorMessage): - return 'AccountDoesNotExist'; - case /^Account .*? doesn't exist$/.test(errorMessage): - return 'AccountDoesNotExist'; - case /^access key .*? does not exist while viewing$/.test(errorMessage): - return 'AccessKeyDoesNotExist'; - case /wasm execution failed with error: FunctionCallError\(CompilationError\(CodeDoesNotExist/.test(errorMessage): - return 'CodeDoesNotExist'; - case /Transaction nonce \d+ must be larger than nonce of the used access key \d+/.test(errorMessage): - return 'InvalidNonce'; - default: - return errorType; - } -} - -/** - * Helper function determining if the argument is an object - * @param n Value to check - */ -function isObject(n) { - return Object.prototype.toString.call(n) === '[object Object]'; -} - -/** - * Helper function determining if the argument is a string - * @param n Value to check - */ -function isString(n) { - return Object.prototype.toString.call(n) === '[object String]'; -} +export { + ServerError, + parseRpcError, + parseResultError, + formatError, + getErrorTypeFromErrorMessage, +} from '@near-js/core'; From 7c4e186ff61f615be3f1001a51cab75f2d6d2c24 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 14 Nov 2022 15:39:02 -0800 Subject: [PATCH 13/84] refactor: json-rpc-provider package --- packages/json-rpc-provider/package.json | 27 ++ .../src/exponential-backoff.ts | 23 ++ packages/json-rpc-provider/src/fetch.ts | 21 + packages/json-rpc-provider/src/index.ts | 3 + .../src/json-rpc-provider.ts | 377 +++++++++++++++++ packages/json-rpc-provider/src/web.ts | 59 +++ packages/json-rpc-provider/tsconfig.json | 28 ++ packages/near-api-js/package.json | 1 + .../src/providers/json-rpc-provider.ts | 379 +----------------- .../src/utils/exponential-backoff.ts | 24 +- packages/near-api-js/src/utils/web.ts | 56 +-- pnpm-lock.yaml | 26 ++ 12 files changed, 570 insertions(+), 454 deletions(-) create mode 100644 packages/json-rpc-provider/package.json create mode 100644 packages/json-rpc-provider/src/exponential-backoff.ts create mode 100644 packages/json-rpc-provider/src/fetch.ts create mode 100644 packages/json-rpc-provider/src/index.ts create mode 100644 packages/json-rpc-provider/src/json-rpc-provider.ts create mode 100644 packages/json-rpc-provider/src/web.ts create mode 100644 packages/json-rpc-provider/tsconfig.json diff --git a/packages/json-rpc-provider/package.json b/packages/json-rpc-provider/package.json new file mode 100644 index 000000000..248f29e2f --- /dev/null +++ b/packages/json-rpc-provider/package.json @@ -0,0 +1,27 @@ +{ + "name": "@near-js/json-rpc-provider", + "version": "0.0.1", + "description": "Client for communicating with the NEAR RPC using JSON-RPC", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/core": "workspace:*", + "bn.js": "5.2.1", + "borsh": "^0.7.0", + "http-errors": "^1.7.2", + "js-sha256": "^0.9.0", + "tweetnacl": "^1.0.1" + }, + "devDependencies": { + "@types/node": "^18.7.14" + }, + "optionalDependencies": { + "node-fetch": "^2.6.1" + } +} diff --git a/packages/json-rpc-provider/src/exponential-backoff.ts b/packages/json-rpc-provider/src/exponential-backoff.ts new file mode 100644 index 000000000..d9e55e83d --- /dev/null +++ b/packages/json-rpc-provider/src/exponential-backoff.ts @@ -0,0 +1,23 @@ +export async function exponentialBackoff(startWaitTime, retryNumber, waitBackoff, getResult) { + // TODO: jitter? + + let waitTime = startWaitTime; + for (let i = 0; i < retryNumber; i++) { + const result = await getResult(); + if (result) { + return result; + } + + await sleep(waitTime); + waitTime *= waitBackoff; + } + + return null; +} + +// Sleep given number of millis. +function sleep(millis: number): Promise { + return new Promise(resolve => setTimeout(resolve, millis)); +} + + diff --git a/packages/json-rpc-provider/src/fetch.ts b/packages/json-rpc-provider/src/fetch.ts new file mode 100644 index 000000000..bd9660e06 --- /dev/null +++ b/packages/json-rpc-provider/src/fetch.ts @@ -0,0 +1,21 @@ +import fetch from 'node-fetch'; +import http from 'http'; +import https from 'https'; + +const httpAgent = new http.Agent({ keepAlive: true }); +const httpsAgent = new https.Agent({ keepAlive: true }); + +function agent(_parsedURL) { + if (_parsedURL.protocol === 'http:') { + return httpAgent; + } else { + return httpsAgent; + } +} + +export default function (resource, init) { + return fetch(resource, { + agent: agent(new URL(resource.toString())), + ...init, + }); +} diff --git a/packages/json-rpc-provider/src/index.ts b/packages/json-rpc-provider/src/index.ts new file mode 100644 index 000000000..ce7dabaae --- /dev/null +++ b/packages/json-rpc-provider/src/index.ts @@ -0,0 +1,3 @@ +export { exponentialBackoff } from './exponential-backoff'; +export { JsonRpcProvider } from './json-rpc-provider'; +export { fetchJson } from './web'; diff --git a/packages/json-rpc-provider/src/json-rpc-provider.ts b/packages/json-rpc-provider/src/json-rpc-provider.ts new file mode 100644 index 000000000..ae7e8a84d --- /dev/null +++ b/packages/json-rpc-provider/src/json-rpc-provider.ts @@ -0,0 +1,377 @@ +/** + * @module + * @description + * This module contains the {@link JsonRpcProvider} client class + * which can be used to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). + * @see {@link providers/provider | providers} for a list of request and response types + */ +import { + AccessKeyWithPublicKey, + BlockId, + BlockReference, + BlockResult, + BlockChangeResult, + ChangeResult, + ChunkId, + ChunkResult, + EpochValidatorInfo, + FinalExecutionOutcome, + GasPrice, + LightClientProof, + LightClientProofRequest, + NearProtocolConfig, + NodeStatusResult, + Provider, + QueryResponseKind, + SignedTransaction, + TypedError, + getErrorTypeFromErrorMessage, + parseRpcError, +} from '@near-js/core'; +import { baseEncode } from 'borsh'; + +import { exponentialBackoff } from './exponential-backoff'; +import { ConnectionInfo, fetchJson } from './web'; + +/** @hidden */ +// Default number of retries before giving up on a request. +const REQUEST_RETRY_NUMBER = 12; + +// Default wait until next retry in millis. +const REQUEST_RETRY_WAIT = 500; + +// Exponential back off for waiting to retry. +const REQUEST_RETRY_WAIT_BACKOFF = 1.5; + +/// Keep ids unique across all connections. +let _nextId = 123; + +/** + * Client class to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). + * @see [https://github.com/near/nearcore/tree/master/chain/jsonrpc](https://github.com/near/nearcore/tree/master/chain/jsonrpc) + */ +export class JsonRpcProvider extends Provider { + /** @hidden */ + readonly connection: ConnectionInfo; + + /** + * @param connectionInfo Connection info + */ + constructor(connectionInfo: ConnectionInfo) { + super(); + this.connection = connectionInfo || { url: '' }; + } + + /** + * Gets the RPC's status + * @see [https://docs.near.org/docs/develop/front-end/rpc#general-validator-status](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) + */ + async status(): Promise { + return this.sendJsonRpc('status', []); + } + + /** + * Sends a signed transaction to the RPC and waits until transaction is fully complete + * @see [https://docs.near.org/docs/develop/front-end/rpc#send-transaction-await](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) + * + * @param signedTransaction The signed transaction being sent + */ + async sendTransaction(signedTransaction: SignedTransaction): Promise { + const bytes = signedTransaction.encode(); + return this.sendJsonRpc('broadcast_tx_commit', [Buffer.from(bytes).toString('base64')]); + } + + /** + * Sends a signed transaction to the RPC and immediately returns transaction hash + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#send-transaction-async) + * @param signedTransaction The signed transaction being sent + * @returns {Promise} + */ + async sendTransactionAsync(signedTransaction: SignedTransaction): Promise { + const bytes = signedTransaction.encode(); + return this.sendJsonRpc('broadcast_tx_async', [Buffer.from(bytes).toString('base64')]); + } + + /** + * Gets a transaction's status from the RPC + * @see [https://docs.near.org/docs/develop/front-end/rpc#transaction-status](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) + * + * @param txHash A transaction hash as either a Uint8Array or a base58 encoded string + * @param accountId The NEAR account that signed the transaction + */ + async txStatus(txHash: Uint8Array | string, accountId: string): Promise { + if (typeof txHash === 'string') { + return this.txStatusString(txHash, accountId); + } else { + return this.txStatusUint8Array(txHash, accountId); + } + } + + private async txStatusUint8Array(txHash: Uint8Array, accountId: string): Promise { + return this.sendJsonRpc('tx', [baseEncode(txHash), accountId]); + } + + private async txStatusString(txHash: string, accountId: string): Promise { + return this.sendJsonRpc('tx', [txHash, accountId]); + } + + /** + * Gets a transaction's status from the RPC with receipts + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#transaction-status-with-receipts) + * @param txHash The hash of the transaction + * @param accountId The NEAR account that signed the transaction + * @returns {Promise} + */ + async txStatusReceipts(txHash: Uint8Array | string, accountId: string): Promise { + if (typeof txHash === 'string') { + return this.sendJsonRpc('EXPERIMENTAL_tx_status', [txHash, accountId]); + } + else { + return this.sendJsonRpc('EXPERIMENTAL_tx_status', [baseEncode(txHash), accountId]); + } + } + + /** + * Query the RPC by passing an {@link providers/provider!RpcQueryRequest} + * @see [https://docs.near.org/api/rpc/contracts](https://docs.near.org/api/rpc/contracts) + * + * @typeParam T the shape of the returned query response + */ + async query(...args: any[]): Promise { + let result; + if (args.length === 1) { + const { block_id, blockId, ...otherParams } = args[0]; + result = await this.sendJsonRpc('query', { ...otherParams, block_id: block_id || blockId }); + } else { + const [path, data] = args; + result = await this.sendJsonRpc('query', [path, data]); + } + if (result && result.error) { + throw new TypedError( + `Querying failed: ${result.error}.\n${JSON.stringify(result, null, 2)}`, + getErrorTypeFromErrorMessage(result.error, result.error.name) + ); + } + return result; + } + + /** + * Query for block info from the RPC + * pass block_id OR finality as blockQuery, not both + * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) + * + * @param blockQuery {@link providers/provider!BlockReference} (passing a {@link providers/provider!BlockId} is deprecated) + */ + async block(blockQuery: BlockId | BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('block', { block_id: blockId, finality }); + } + + /** + * Query changes in block from the RPC + * pass block_id OR finality as blockQuery, not both + * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) + */ + async blockChanges(blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes_in_block', { block_id: blockId, finality }); + } + + /** + * Queries for details about a specific chunk appending details of receipts and transactions to the same chunk data provided by a block + * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) + * + * @param chunkId Hash of a chunk ID or shard ID + */ + async chunk(chunkId: ChunkId): Promise { + return this.sendJsonRpc('chunk', [chunkId]); + } + + /** + * Query validators of the epoch defined by the given block id. + * @see [https://docs.near.org/api/rpc/network#validation-status](https://docs.near.org/api/rpc/network#validation-status) + * + * @param blockId Block hash or height, or null for latest. + */ + async validators(blockId: BlockId | null): Promise { + return this.sendJsonRpc('validators', [blockId]); + } + + /** + * Gets the protocol config at a block from RPC + * + * @param blockReference specifies the block to get the protocol config for + */ + async experimental_protocolConfig(blockReference: BlockReference | { sync_checkpoint: 'genesis' }): Promise { + const { blockId, ...otherParams } = blockReference as any; + return await this.sendJsonRpc('EXPERIMENTAL_protocol_config', {...otherParams, block_id: blockId}); + } + + /** + * Gets a light client execution proof for verifying execution outcomes + * @see [https://github.com/nearprotocol/NEPs/blob/master/specs/ChainSpec/LightClient.md#light-client-proof](https://github.com/nearprotocol/NEPs/blob/master/specs/ChainSpec/LightClient.md#light-client-proof) + */ + async lightClientProof(request: LightClientProofRequest): Promise { + return await this.sendJsonRpc('EXPERIMENTAL_light_client_proof', request); + } + + /** + * Gets access key changes for a given array of accountIds + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-access-key-changes-all) + * @returns {Promise} + */ + async accessKeyChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes', { + changes_type: 'all_access_key_changes', + account_ids: accountIdArray, + block_id: blockId, + finality + }); + } + + /** + * Gets single access key changes for a given array of access keys + * pass block_id OR finality as blockQuery, not both + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-access-key-changes-single) + * @returns {Promise} + */ + async singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes', { + changes_type: 'single_access_key_changes', + keys: accessKeyArray, + block_id: blockId, + finality + }); + } + + /** + * Gets account changes for a given array of accountIds + * pass block_id OR finality as blockQuery, not both + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-account-changes) + * @returns {Promise} + */ + async accountChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes', { + changes_type: 'account_changes', + account_ids: accountIdArray, + block_id: blockId, + finality + }); + } + + /** + * Gets contract state changes for a given array of accountIds + * pass block_id OR finality as blockQuery, not both + * Note: If you pass a keyPrefix it must be base64 encoded + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-contract-state-changes) + * @returns {Promise} + */ + async contractStateChanges(accountIdArray: string[], blockQuery: BlockReference, keyPrefix = ''): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes', { + changes_type: 'data_changes', + account_ids: accountIdArray, + key_prefix_base64: keyPrefix, + block_id: blockId, + finality + }); + } + + /** + * Gets contract code changes for a given array of accountIds + * pass block_id OR finality as blockQuery, not both + * Note: Change is returned in a base64 encoded WASM file + * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-contract-code-changes) + * @returns {Promise} + */ + async contractCodeChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('EXPERIMENTAL_changes', { + changes_type: 'contract_code_changes', + account_ids: accountIdArray, + block_id: blockId, + finality + }); + } + + /** + * Returns gas price for a specific block_height or block_hash. + * @see [https://docs.near.org/api/rpc/gas](https://docs.near.org/api/rpc/gas) + * + * @param blockId Block hash or height, or null for latest. + */ + async gasPrice(blockId: BlockId | null): Promise { + return await this.sendJsonRpc('gas_price', [blockId]); + } + + /** + * Directly call the RPC specifying the method and params + * + * @param method RPC method + * @param params Parameters to the method + */ + async sendJsonRpc(method: string, params: object): Promise { + const response = await exponentialBackoff(REQUEST_RETRY_WAIT, REQUEST_RETRY_NUMBER, REQUEST_RETRY_WAIT_BACKOFF, async () => { + try { + const request = { + method, + params, + id: (_nextId++), + jsonrpc: '2.0' + }; + const response = await fetchJson(this.connection, JSON.stringify(request)); + if (response.error) { + if (typeof response.error.data === 'object') { + if (typeof response.error.data.error_message === 'string' && typeof response.error.data.error_type === 'string') { + // if error data has error_message and error_type properties, we consider that node returned an error in the old format + throw new TypedError(response.error.data.error_message, response.error.data.error_type); + } + + throw parseRpcError(response.error.data); + } else { + const errorMessage = `[${response.error.code}] ${response.error.message}: ${response.error.data}`; + // NOTE: All this hackery is happening because structured errors not implemented + // TODO: Fix when https://github.com/nearprotocol/nearcore/issues/1839 gets resolved + if (response.error.data === 'Timeout' || errorMessage.includes('Timeout error') + || errorMessage.includes('query has timed out')) { + throw new TypedError(errorMessage, 'TimeoutError'); + } + + throw new TypedError(errorMessage, getErrorTypeFromErrorMessage(response.error.data, response.error.name)); + } + } + // Success when response.error is not exist + return response; + } catch (error) { + if (error.type === 'TimeoutError') { + if (!process.env['NEAR_NO_LOGS']) { + console.warn(`Retrying request to ${method} as it has timed out`, params); + } + return null; + } + + throw error; + } + }); + const { result } = response; + // From jsonrpc spec: + // result + // This member is REQUIRED on success. + // This member MUST NOT exist if there was an error invoking the method. + if (typeof result === 'undefined') { + throw new TypedError( + `Exceeded ${REQUEST_RETRY_NUMBER} attempts for request to ${method}.`, 'RetriesExceeded'); + } + return result; + } +} diff --git a/packages/json-rpc-provider/src/web.ts b/packages/json-rpc-provider/src/web.ts new file mode 100644 index 000000000..4ba6dd32c --- /dev/null +++ b/packages/json-rpc-provider/src/web.ts @@ -0,0 +1,59 @@ +import { + TypedError, +} from '@near-js/core'; +import createError from 'http-errors'; + +import { exponentialBackoff } from './exponential-backoff'; +import nodeFetch from './fetch'; + +const START_WAIT_TIME_MS = 1000; +const BACKOFF_MULTIPLIER = 1.5; +const RETRY_NUMBER = 10; + +export interface ConnectionInfo { + url: string; + user?: string; + password?: string; + allowInsecure?: boolean; + timeout?: number; + headers?: { [key: string]: string | number }; +} + +const logWarning = (...args) => !process.env['NEAR_NO_LOGS'] && console.warn(...args); + +export async function fetchJson(connectionInfoOrUrl: string | ConnectionInfo, json?: string): Promise { + let connectionInfo: ConnectionInfo = { url: null }; + if (typeof (connectionInfoOrUrl) === 'string') { + connectionInfo.url = connectionInfoOrUrl; + } else { + connectionInfo = connectionInfoOrUrl as ConnectionInfo; + } + + const response = await exponentialBackoff(START_WAIT_TIME_MS, RETRY_NUMBER, BACKOFF_MULTIPLIER, async () => { + try { + const response = await (global.fetch || nodeFetch)(connectionInfo.url, { + method: json ? 'POST' : 'GET', + body: json ? json : undefined, + headers: { ...connectionInfo.headers, 'Content-Type': 'application/json' } + }); + if (!response.ok) { + if (response.status === 503) { + logWarning(`Retrying HTTP request for ${connectionInfo.url} as it's not available now`); + return null; + } + throw createError(response.status, await response.text()); + } + return response; + } catch (error) { + if (error.toString().includes('FetchError') || error.toString().includes('Failed to fetch')) { + logWarning(`Retrying HTTP request for ${connectionInfo.url} because of error: ${error}`); + return null; + } + throw error; + } + }); + if (!response) { + throw new TypedError(`Exceeded ${RETRY_NUMBER} attempts for ${connectionInfo.url}.`, 'RetriesExceeded'); + } + return await response.json(); +} diff --git a/packages/json-rpc-provider/tsconfig.json b/packages/json-rpc-provider/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/json-rpc-provider/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 1eee8335b..f39500d15 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,6 +12,7 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/core": "workspace:*", + "@near-js/json-rpc-provider": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", diff --git a/packages/near-api-js/src/providers/json-rpc-provider.ts b/packages/near-api-js/src/providers/json-rpc-provider.ts index 960543e6e..4134f12ef 100644 --- a/packages/near-api-js/src/providers/json-rpc-provider.ts +++ b/packages/near-api-js/src/providers/json-rpc-provider.ts @@ -1,377 +1,2 @@ -/** - * @module - * @description - * This module contains the {@link JsonRpcProvider} client class - * which can be used to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). - * @see {@link providers/provider | providers} for a list of request and response types - */ -import { - AccessKeyWithPublicKey, - Provider, - FinalExecutionOutcome, - NodeStatusResult, - BlockId, - BlockReference, - BlockResult, - BlockChangeResult, - ChangeResult, - ChunkId, - ChunkResult, - EpochValidatorInfo, - NearProtocolConfig, - LightClientProof, - LightClientProofRequest, - GasPrice, - QueryResponseKind -} from './provider'; -import { ConnectionInfo, fetchJson } from '../utils/web'; -import { TypedError, ErrorContext } from '../utils/errors'; -import { baseEncode } from 'borsh'; -import exponentialBackoff from '../utils/exponential-backoff'; -import { parseRpcError, getErrorTypeFromErrorMessage } from '../utils/rpc_errors'; -import { SignedTransaction } from '../transaction'; - -/** @hidden */ -export { TypedError, ErrorContext }; - -// Default number of retries before giving up on a request. -const REQUEST_RETRY_NUMBER = 12; - -// Default wait until next retry in millis. -const REQUEST_RETRY_WAIT = 500; - -// Exponential back off for waiting to retry. -const REQUEST_RETRY_WAIT_BACKOFF = 1.5; - -/// Keep ids unique across all connections. -let _nextId = 123; - -/** - * Client class to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). - * @see [https://github.com/near/nearcore/tree/master/chain/jsonrpc](https://github.com/near/nearcore/tree/master/chain/jsonrpc) - */ -export class JsonRpcProvider extends Provider { - /** @hidden */ - readonly connection: ConnectionInfo; - - /** - * @param connectionInfo Connection info - */ - constructor(connectionInfo: ConnectionInfo) { - super(); - this.connection = connectionInfo || { url: '' }; - } - - /** - * Gets the RPC's status - * @see [https://docs.near.org/docs/develop/front-end/rpc#general-validator-status](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) - */ - async status(): Promise { - return this.sendJsonRpc('status', []); - } - - /** - * Sends a signed transaction to the RPC and waits until transaction is fully complete - * @see [https://docs.near.org/docs/develop/front-end/rpc#send-transaction-await](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) - * - * @param signedTransaction The signed transaction being sent - */ - async sendTransaction(signedTransaction: SignedTransaction): Promise { - const bytes = signedTransaction.encode(); - return this.sendJsonRpc('broadcast_tx_commit', [Buffer.from(bytes).toString('base64')]); - } - - /** - * Sends a signed transaction to the RPC and immediately returns transaction hash - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#send-transaction-async) - * @param signedTransaction The signed transaction being sent - * @returns {Promise} - */ - async sendTransactionAsync(signedTransaction: SignedTransaction): Promise { - const bytes = signedTransaction.encode(); - return this.sendJsonRpc('broadcast_tx_async', [Buffer.from(bytes).toString('base64')]); - } - - /** - * Gets a transaction's status from the RPC - * @see [https://docs.near.org/docs/develop/front-end/rpc#transaction-status](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) - * - * @param txHash A transaction hash as either a Uint8Array or a base58 encoded string - * @param accountId The NEAR account that signed the transaction - */ - async txStatus(txHash: Uint8Array | string, accountId: string): Promise { - if (typeof txHash === 'string') { - return this.txStatusString(txHash, accountId); - } else { - return this.txStatusUint8Array(txHash, accountId); - } - } - - private async txStatusUint8Array(txHash: Uint8Array, accountId: string): Promise { - return this.sendJsonRpc('tx', [baseEncode(txHash), accountId]); - } - - private async txStatusString(txHash: string, accountId: string): Promise { - return this.sendJsonRpc('tx', [txHash, accountId]); - } - - /** - * Gets a transaction's status from the RPC with receipts - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#transaction-status-with-receipts) - * @param txHash The hash of the transaction - * @param accountId The NEAR account that signed the transaction - * @returns {Promise} - */ - async txStatusReceipts(txHash: Uint8Array | string, accountId: string): Promise { - if (typeof txHash === 'string') { - return this.sendJsonRpc('EXPERIMENTAL_tx_status', [txHash, accountId]); - } - else { - return this.sendJsonRpc('EXPERIMENTAL_tx_status', [baseEncode(txHash), accountId]); - } - } - - /** - * Query the RPC by passing an {@link providers/provider!RpcQueryRequest} - * @see [https://docs.near.org/api/rpc/contracts](https://docs.near.org/api/rpc/contracts) - * - * @typeParam T the shape of the returned query response - */ - async query(...args: any[]): Promise { - let result; - if (args.length === 1) { - const { block_id, blockId, ...otherParams } = args[0]; - result = await this.sendJsonRpc('query', { ...otherParams, block_id: block_id || blockId }); - } else { - const [path, data] = args; - result = await this.sendJsonRpc('query', [path, data]); - } - if (result && result.error) { - throw new TypedError( - `Querying failed: ${result.error}.\n${JSON.stringify(result, null, 2)}`, - getErrorTypeFromErrorMessage(result.error, result.error.name) - ); - } - return result; - } - - /** - * Query for block info from the RPC - * pass block_id OR finality as blockQuery, not both - * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) - * - * @param blockQuery {@link providers/provider!BlockReference} (passing a {@link providers/provider!BlockId} is deprecated) - */ - async block(blockQuery: BlockId | BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('block', { block_id: blockId, finality }); - } - - /** - * Query changes in block from the RPC - * pass block_id OR finality as blockQuery, not both - * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) - */ - async blockChanges(blockQuery: BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes_in_block', { block_id: blockId, finality }); - } - - /** - * Queries for details about a specific chunk appending details of receipts and transactions to the same chunk data provided by a block - * @see [https://docs.near.org/api/rpc/block-chunk](https://docs.near.org/api/rpc/block-chunk) - * - * @param chunkId Hash of a chunk ID or shard ID - */ - async chunk(chunkId: ChunkId): Promise { - return this.sendJsonRpc('chunk', [chunkId]); - } - - /** - * Query validators of the epoch defined by the given block id. - * @see [https://docs.near.org/api/rpc/network#validation-status](https://docs.near.org/api/rpc/network#validation-status) - * - * @param blockId Block hash or height, or null for latest. - */ - async validators(blockId: BlockId | null): Promise { - return this.sendJsonRpc('validators', [blockId]); - } - - /** - * Gets the protocol config at a block from RPC - * - * @param blockReference specifies the block to get the protocol config for - */ - async experimental_protocolConfig(blockReference: BlockReference | { sync_checkpoint: 'genesis' }): Promise { - const { blockId, ...otherParams } = blockReference as any; - return await this.sendJsonRpc('EXPERIMENTAL_protocol_config', {...otherParams, block_id: blockId}); - } - - /** - * Gets a light client execution proof for verifying execution outcomes - * @see [https://github.com/nearprotocol/NEPs/blob/master/specs/ChainSpec/LightClient.md#light-client-proof](https://github.com/nearprotocol/NEPs/blob/master/specs/ChainSpec/LightClient.md#light-client-proof) - */ - async lightClientProof(request: LightClientProofRequest): Promise { - return await this.sendJsonRpc('EXPERIMENTAL_light_client_proof', request); - } - - /** - * Gets access key changes for a given array of accountIds - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-access-key-changes-all) - * @returns {Promise} - */ - async accessKeyChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes', { - changes_type: 'all_access_key_changes', - account_ids: accountIdArray, - block_id: blockId, - finality - }); - } - - /** - * Gets single access key changes for a given array of access keys - * pass block_id OR finality as blockQuery, not both - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-access-key-changes-single) - * @returns {Promise} - */ - async singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], blockQuery: BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes', { - changes_type: 'single_access_key_changes', - keys: accessKeyArray, - block_id: blockId, - finality - }); - } - - /** - * Gets account changes for a given array of accountIds - * pass block_id OR finality as blockQuery, not both - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-account-changes) - * @returns {Promise} - */ - async accountChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes', { - changes_type: 'account_changes', - account_ids: accountIdArray, - block_id: blockId, - finality - }); - } - - /** - * Gets contract state changes for a given array of accountIds - * pass block_id OR finality as blockQuery, not both - * Note: If you pass a keyPrefix it must be base64 encoded - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-contract-state-changes) - * @returns {Promise} - */ - async contractStateChanges(accountIdArray: string[], blockQuery: BlockReference, keyPrefix = ''): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes', { - changes_type: 'data_changes', - account_ids: accountIdArray, - key_prefix_base64: keyPrefix, - block_id: blockId, - finality - }); - } - - /** - * Gets contract code changes for a given array of accountIds - * pass block_id OR finality as blockQuery, not both - * Note: Change is returned in a base64 encoded WASM file - * See [docs for more info](https://docs.near.org/docs/develop/front-end/rpc#view-contract-code-changes) - * @returns {Promise} - */ - async contractCodeChanges(accountIdArray: string[], blockQuery: BlockReference): Promise { - const { finality } = blockQuery as any; - const { blockId } = blockQuery as any; - return this.sendJsonRpc('EXPERIMENTAL_changes', { - changes_type: 'contract_code_changes', - account_ids: accountIdArray, - block_id: blockId, - finality - }); - } - - /** - * Returns gas price for a specific block_height or block_hash. - * @see [https://docs.near.org/api/rpc/gas](https://docs.near.org/api/rpc/gas) - * - * @param blockId Block hash or height, or null for latest. - */ - async gasPrice(blockId: BlockId | null): Promise { - return await this.sendJsonRpc('gas_price', [blockId]); - } - - /** - * Directly call the RPC specifying the method and params - * - * @param method RPC method - * @param params Parameters to the method - */ - async sendJsonRpc(method: string, params: object): Promise { - const response = await exponentialBackoff(REQUEST_RETRY_WAIT, REQUEST_RETRY_NUMBER, REQUEST_RETRY_WAIT_BACKOFF, async () => { - try { - const request = { - method, - params, - id: (_nextId++), - jsonrpc: '2.0' - }; - const response = await fetchJson(this.connection, JSON.stringify(request)); - if (response.error) { - if (typeof response.error.data === 'object') { - if (typeof response.error.data.error_message === 'string' && typeof response.error.data.error_type === 'string') { - // if error data has error_message and error_type properties, we consider that node returned an error in the old format - throw new TypedError(response.error.data.error_message, response.error.data.error_type); - } - - throw parseRpcError(response.error.data); - } else { - const errorMessage = `[${response.error.code}] ${response.error.message}: ${response.error.data}`; - // NOTE: All this hackery is happening because structured errors not implemented - // TODO: Fix when https://github.com/nearprotocol/nearcore/issues/1839 gets resolved - if (response.error.data === 'Timeout' || errorMessage.includes('Timeout error') - || errorMessage.includes('query has timed out')) { - throw new TypedError(errorMessage, 'TimeoutError'); - } - - throw new TypedError(errorMessage, getErrorTypeFromErrorMessage(response.error.data, response.error.name)); - } - } - // Success when response.error is not exist - return response; - } catch (error) { - if (error.type === 'TimeoutError') { - if (!process.env['NEAR_NO_LOGS']) { - console.warn(`Retrying request to ${method} as it has timed out`, params); - } - return null; - } - - throw error; - } - }); - const { result } = response; - // From jsonrpc spec: - // result - // This member is REQUIRED on success. - // This member MUST NOT exist if there was an error invoking the method. - if (typeof result === 'undefined') { - throw new TypedError( - `Exceeded ${REQUEST_RETRY_NUMBER} attempts for request to ${method}.`, 'RetriesExceeded'); - } - return result; - } -} +export { ErrorContext, TypedError } from '@near-js/core'; +export { JsonRpcProvider } from '@near-js/json-rpc-provider'; diff --git a/packages/near-api-js/src/utils/exponential-backoff.ts b/packages/near-api-js/src/utils/exponential-backoff.ts index 369311ec2..f1af78ad1 100644 --- a/packages/near-api-js/src/utils/exponential-backoff.ts +++ b/packages/near-api-js/src/utils/exponential-backoff.ts @@ -1,23 +1,3 @@ -export default async function exponentialBackoff(startWaitTime, retryNumber, waitBackoff, getResult) { - // TODO: jitter? - - let waitTime = startWaitTime; - for (let i = 0; i < retryNumber; i++) { - const result = await getResult(); - if (result) { - return result; - } - - await sleep(waitTime); - waitTime *= waitBackoff; - } - - return null; -} - -// Sleep given number of millis. -function sleep(millis: number): Promise { - return new Promise(resolve => setTimeout(resolve, millis)); -} - +import { exponentialBackoff } from '@near-js/json-rpc-provider'; +export default exponentialBackoff; diff --git a/packages/near-api-js/src/utils/web.ts b/packages/near-api-js/src/utils/web.ts index 8e8dfe6a1..6895d152a 100644 --- a/packages/near-api-js/src/utils/web.ts +++ b/packages/near-api-js/src/utils/web.ts @@ -1,55 +1 @@ -import createError from 'http-errors'; - -import exponentialBackoff from './exponential-backoff'; -import { TypedError } from '../providers'; -import { logWarning } from './errors'; - -const START_WAIT_TIME_MS = 1000; -const BACKOFF_MULTIPLIER = 1.5; -const RETRY_NUMBER = 10; - -export interface ConnectionInfo { - url: string; - user?: string; - password?: string; - allowInsecure?: boolean; - timeout?: number; - headers?: { [key: string]: string | number }; -} - -export async function fetchJson(connectionInfoOrUrl: string | ConnectionInfo, json?: string): Promise { - let connectionInfo: ConnectionInfo = { url: null }; - if (typeof (connectionInfoOrUrl) === 'string') { - connectionInfo.url = connectionInfoOrUrl; - } else { - connectionInfo = connectionInfoOrUrl as ConnectionInfo; - } - - const response = await exponentialBackoff(START_WAIT_TIME_MS, RETRY_NUMBER, BACKOFF_MULTIPLIER, async () => { - try { - const response = await fetch(connectionInfo.url, { - method: json ? 'POST' : 'GET', - body: json ? json : undefined, - headers: { ...connectionInfo.headers, 'Content-Type': 'application/json' } - }); - if (!response.ok) { - if (response.status === 503) { - logWarning(`Retrying HTTP request for ${connectionInfo.url} as it's not available now`); - return null; - } - throw createError(response.status, await response.text()); - } - return response; - } catch (error) { - if (error.toString().includes('FetchError') || error.toString().includes('Failed to fetch')) { - logWarning(`Retrying HTTP request for ${connectionInfo.url} because of error: ${error}`); - return null; - } - throw error; - } - }); - if (!response) { - throw new TypedError(`Exceeded ${RETRY_NUMBER} attempts for ${connectionInfo.url}.`, 'RetriesExceeded'); - } - return await response.json(); -} +export { fetchJson } from '@near-js/json-rpc-provider'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b456dc2f5..696394e73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,18 +46,43 @@ importers: bn.js: 5.2.1 borsh: ^0.7.0 js-sha256: ^0.9.0 + mustache: ^4.0.0 tweetnacl: ^1.0.1 dependencies: bn.js: 5.2.1 borsh: 0.7.0 js-sha256: 0.9.0 + mustache: 4.2.0 tweetnacl: 1.0.3 devDependencies: '@types/node': 18.7.14 + packages/json-rpc-provider: + specifiers: + '@near-js/core': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + http-errors: ^1.7.2 + js-sha256: ^0.9.0 + node-fetch: ^2.6.1 + tweetnacl: ^1.0.1 + dependencies: + '@near-js/core': link:../core + bn.js: 5.2.1 + borsh: 0.7.0 + http-errors: 1.8.1 + js-sha256: 0.9.0 + tweetnacl: 1.0.3 + optionalDependencies: + node-fetch: 2.6.7 + devDependencies: + '@types/node': 18.7.14 + packages/near-api-js: specifiers: '@near-js/core': workspace:* + '@near-js/json-rpc-provider': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 '@types/node': ^18.7.14 @@ -90,6 +115,7 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/core': link:../core + '@near-js/json-rpc-provider': link:../json-rpc-provider ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 From 490b29a07debc335c51043735342c102da3479e5 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 17 Nov 2022 20:27:27 -0800 Subject: [PATCH 14/84] refactor: rename module after sole exported function --- .../src/transaction/{transaction.ts => create_transaction.ts} | 0 packages/core/src/transaction/index.ts | 2 +- packages/core/src/transaction/sign.ts | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename packages/core/src/transaction/{transaction.ts => create_transaction.ts} (100%) diff --git a/packages/core/src/transaction/transaction.ts b/packages/core/src/transaction/create_transaction.ts similarity index 100% rename from packages/core/src/transaction/transaction.ts rename to packages/core/src/transaction/create_transaction.ts diff --git a/packages/core/src/transaction/index.ts b/packages/core/src/transaction/index.ts index fcedfa8c6..e3877d87f 100644 --- a/packages/core/src/transaction/index.ts +++ b/packages/core/src/transaction/index.ts @@ -26,6 +26,6 @@ export { Stake, Transfer, } from './actions'; +export { createTransaction } from './create_transaction'; export { SCHEMA, Signature as TransactionSignature, SignedTransaction, Transaction } from './schema'; export { signTransaction } from './sign'; -export { createTransaction } from './transaction'; diff --git a/packages/core/src/transaction/sign.ts b/packages/core/src/transaction/sign.ts index 2c4c349f7..4e10197f6 100644 --- a/packages/core/src/transaction/sign.ts +++ b/packages/core/src/transaction/sign.ts @@ -2,10 +2,10 @@ import sha256 from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; +import { Signer } from '../signer'; import { Action } from './actions'; +import { createTransaction } from './create_transaction'; import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema'; -import { Signer } from '../signer'; -import { createTransaction } from './transaction'; /** * Signs a given transaction from an account with given keys, applied to the given network From e0c971e5d696bf7d4408fd084ff9484e2b1884f4 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 09:54:54 -0800 Subject: [PATCH 15/84] fix: remove vestigial prepare command to fix circular failure --- packages/near-api-js/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index f39500d15..1e0182e90 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -64,7 +64,6 @@ "prefuzz": "pnpm build", "fuzz": "jsfuzz test/fuzz/borsh-roundtrip.js test/fuzz/corpus/", "clean": "pnpm rimraf lib", - "prepare": "pnpm build", "bundlewatch": "bundlewatch" }, "bundlewatch": { From 904adf84b52bf168ba733017d23b7f345b293ffe Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 10:41:34 -0800 Subject: [PATCH 16/84] refactor: move Provider abstract class to json-rpc-provider package --- packages/core/src/provider/index.ts | 1 - packages/json-rpc-provider/src/index.ts | 1 + .../json-rpc-provider/src/json-rpc-provider.ts | 2 +- .../provider => json-rpc-provider/src}/provider.ts | 14 ++++++++------ packages/near-api-js/src/providers/provider.ts | 4 +++- 5 files changed, 13 insertions(+), 9 deletions(-) rename packages/{core/src/provider => json-rpc-provider/src}/provider.ts (87%) diff --git a/packages/core/src/provider/index.ts b/packages/core/src/provider/index.ts index 5fc01bd34..f46deee4a 100644 --- a/packages/core/src/provider/index.ts +++ b/packages/core/src/provider/index.ts @@ -35,7 +35,6 @@ export { TotalWeight, Transaction as ProviderTransaction, } from './protocol'; -export { Provider } from './provider'; export { CallFunctionRequest, RpcQueryRequest, diff --git a/packages/json-rpc-provider/src/index.ts b/packages/json-rpc-provider/src/index.ts index ce7dabaae..6eb5740d2 100644 --- a/packages/json-rpc-provider/src/index.ts +++ b/packages/json-rpc-provider/src/index.ts @@ -1,3 +1,4 @@ export { exponentialBackoff } from './exponential-backoff'; export { JsonRpcProvider } from './json-rpc-provider'; +export { Provider } from './provider'; export { fetchJson } from './web'; diff --git a/packages/json-rpc-provider/src/json-rpc-provider.ts b/packages/json-rpc-provider/src/json-rpc-provider.ts index ae7e8a84d..5f184235e 100644 --- a/packages/json-rpc-provider/src/json-rpc-provider.ts +++ b/packages/json-rpc-provider/src/json-rpc-provider.ts @@ -21,7 +21,6 @@ import { LightClientProofRequest, NearProtocolConfig, NodeStatusResult, - Provider, QueryResponseKind, SignedTransaction, TypedError, @@ -31,6 +30,7 @@ import { import { baseEncode } from 'borsh'; import { exponentialBackoff } from './exponential-backoff'; +import { Provider } from './provider'; import { ConnectionInfo, fetchJson } from './web'; /** @hidden */ diff --git a/packages/core/src/provider/provider.ts b/packages/json-rpc-provider/src/provider.ts similarity index 87% rename from packages/core/src/provider/provider.ts rename to packages/json-rpc-provider/src/provider.ts index 4b7361c6c..88e112737 100644 --- a/packages/core/src/provider/provider.ts +++ b/packages/json-rpc-provider/src/provider.ts @@ -3,7 +3,6 @@ * @module */ -import { LightClientProof, LightClientProofRequest } from './light_client'; import { AccessKeyWithPublicKey, BlockChangeResult, @@ -13,14 +12,17 @@ import { ChangeResult, ChunkId, ChunkResult, + FinalExecutionOutcome, GasPrice, + LightClientProof, + LightClientProofRequest, NearProtocolConfig, NodeStatusResult, -} from './protocol'; -import { RpcQueryRequest } from './request'; -import { FinalExecutionOutcome, QueryResponseKind } from './response'; -import { SignedTransaction } from '../transaction'; -import { EpochValidatorInfo } from "./validator"; + QueryResponseKind, + RpcQueryRequest, + SignedTransaction, + EpochValidatorInfo, +} from '@near-js/core'; /** @hidden */ export abstract class Provider { diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index 49e8c9e47..d5eb127a3 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -33,7 +33,6 @@ export { SyncInfo, TotalWeight, ProviderTransaction as Transaction, - Provider, CallFunctionRequest, RpcQueryRequest, @@ -69,3 +68,6 @@ export { NextEpochValidatorInfo, ValidatorStakeView, } from '@near-js/core'; +export { + Provider, +} from '@near-js/json-rpc-provider'; From 812aae4d7c23173838cee7557f07616c1566b803 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 10:50:42 -0800 Subject: [PATCH 17/84] refactor: rename package to represent class of implementations, not the single one --- packages/near-api-js/package.json | 2 +- packages/near-api-js/src/providers/json-rpc-provider.ts | 2 +- packages/near-api-js/src/providers/provider.ts | 2 +- packages/near-api-js/src/utils/exponential-backoff.ts | 2 +- packages/near-api-js/src/utils/web.ts | 2 +- packages/{json-rpc-provider => providers}/package.json | 4 ++-- .../src/exponential-backoff.ts | 0 packages/{json-rpc-provider => providers}/src/fetch.ts | 0 packages/{json-rpc-provider => providers}/src/index.ts | 0 .../src/json-rpc-provider.ts | 0 packages/{json-rpc-provider => providers}/src/provider.ts | 0 packages/{json-rpc-provider => providers}/src/web.ts | 0 packages/{json-rpc-provider => providers}/tsconfig.json | 0 pnpm-lock.yaml | 6 +++--- 14 files changed, 10 insertions(+), 10 deletions(-) rename packages/{json-rpc-provider => providers}/package.json (80%) rename packages/{json-rpc-provider => providers}/src/exponential-backoff.ts (100%) rename packages/{json-rpc-provider => providers}/src/fetch.ts (100%) rename packages/{json-rpc-provider => providers}/src/index.ts (100%) rename packages/{json-rpc-provider => providers}/src/json-rpc-provider.ts (100%) rename packages/{json-rpc-provider => providers}/src/provider.ts (100%) rename packages/{json-rpc-provider => providers}/src/web.ts (100%) rename packages/{json-rpc-provider => providers}/tsconfig.json (100%) diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 1e0182e90..815977b91 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,7 +12,7 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/core": "workspace:*", - "@near-js/json-rpc-provider": "workspace:*", + "@near-js/providers": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", diff --git a/packages/near-api-js/src/providers/json-rpc-provider.ts b/packages/near-api-js/src/providers/json-rpc-provider.ts index 4134f12ef..c689c609c 100644 --- a/packages/near-api-js/src/providers/json-rpc-provider.ts +++ b/packages/near-api-js/src/providers/json-rpc-provider.ts @@ -1,2 +1,2 @@ export { ErrorContext, TypedError } from '@near-js/core'; -export { JsonRpcProvider } from '@near-js/json-rpc-provider'; +export { JsonRpcProvider } from '@near-js/providers'; diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index d5eb127a3..8509ec6f9 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -70,4 +70,4 @@ export { } from '@near-js/core'; export { Provider, -} from '@near-js/json-rpc-provider'; +} from '@near-js/providers'; diff --git a/packages/near-api-js/src/utils/exponential-backoff.ts b/packages/near-api-js/src/utils/exponential-backoff.ts index f1af78ad1..45565cc44 100644 --- a/packages/near-api-js/src/utils/exponential-backoff.ts +++ b/packages/near-api-js/src/utils/exponential-backoff.ts @@ -1,3 +1,3 @@ -import { exponentialBackoff } from '@near-js/json-rpc-provider'; +import { exponentialBackoff } from '@near-js/providers'; export default exponentialBackoff; diff --git a/packages/near-api-js/src/utils/web.ts b/packages/near-api-js/src/utils/web.ts index 6895d152a..ba8d09f15 100644 --- a/packages/near-api-js/src/utils/web.ts +++ b/packages/near-api-js/src/utils/web.ts @@ -1 +1 @@ -export { fetchJson } from '@near-js/json-rpc-provider'; +export { fetchJson } from '@near-js/providers'; diff --git a/packages/json-rpc-provider/package.json b/packages/providers/package.json similarity index 80% rename from packages/json-rpc-provider/package.json rename to packages/providers/package.json index 248f29e2f..5e6635345 100644 --- a/packages/json-rpc-provider/package.json +++ b/packages/providers/package.json @@ -1,7 +1,7 @@ { - "name": "@near-js/json-rpc-provider", + "name": "@near-js/providers", "version": "0.0.1", - "description": "Client for communicating with the NEAR RPC using JSON-RPC", + "description": "Library of implementations for interfacing with the NEAR blockchain", "main": "lib/index.js", "scripts": { "build": "pnpm compile", diff --git a/packages/json-rpc-provider/src/exponential-backoff.ts b/packages/providers/src/exponential-backoff.ts similarity index 100% rename from packages/json-rpc-provider/src/exponential-backoff.ts rename to packages/providers/src/exponential-backoff.ts diff --git a/packages/json-rpc-provider/src/fetch.ts b/packages/providers/src/fetch.ts similarity index 100% rename from packages/json-rpc-provider/src/fetch.ts rename to packages/providers/src/fetch.ts diff --git a/packages/json-rpc-provider/src/index.ts b/packages/providers/src/index.ts similarity index 100% rename from packages/json-rpc-provider/src/index.ts rename to packages/providers/src/index.ts diff --git a/packages/json-rpc-provider/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts similarity index 100% rename from packages/json-rpc-provider/src/json-rpc-provider.ts rename to packages/providers/src/json-rpc-provider.ts diff --git a/packages/json-rpc-provider/src/provider.ts b/packages/providers/src/provider.ts similarity index 100% rename from packages/json-rpc-provider/src/provider.ts rename to packages/providers/src/provider.ts diff --git a/packages/json-rpc-provider/src/web.ts b/packages/providers/src/web.ts similarity index 100% rename from packages/json-rpc-provider/src/web.ts rename to packages/providers/src/web.ts diff --git a/packages/json-rpc-provider/tsconfig.json b/packages/providers/tsconfig.json similarity index 100% rename from packages/json-rpc-provider/tsconfig.json rename to packages/providers/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 696394e73..b50818cb2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,7 +57,7 @@ importers: devDependencies: '@types/node': 18.7.14 - packages/json-rpc-provider: + packages/providers: specifiers: '@near-js/core': workspace:* '@types/node': ^18.7.14 @@ -82,7 +82,7 @@ importers: packages/near-api-js: specifiers: '@near-js/core': workspace:* - '@near-js/json-rpc-provider': workspace:* + '@near-js/providers': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 '@types/node': ^18.7.14 @@ -115,7 +115,7 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/core': link:../core - '@near-js/json-rpc-provider': link:../json-rpc-provider + '@near-js/providers': link:../providers ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 From 4348177e5f3a62c8fd985cf59c1225846e664ff5 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 14:43:16 -0800 Subject: [PATCH 18/84] refactor: separate transactions package --- packages/core/src/index.ts | 2 +- packages/core/src/transaction/index.ts | 31 --------- packages/near-api-js/package.json | 1 + packages/near-api-js/src/transaction.ts | 6 +- packages/providers/package.json | 1 + packages/providers/src/json-rpc-provider.ts | 4 +- packages/providers/src/provider.ts | 4 +- packages/transactions/package.json | 24 +++++++ .../src}/action_creators.ts | 41 ++++++++---- .../src}/actions.ts | 4 +- .../src}/create_transaction.ts | 2 +- packages/transactions/src/index.ts | 5 ++ .../src}/schema.ts | 3 +- .../transaction => transactions/src}/sign.ts | 2 +- packages/transactions/tsconfig.json | 28 ++++++++ pnpm-lock.yaml | 67 +++++++++++++------ 16 files changed, 146 insertions(+), 79 deletions(-) delete mode 100644 packages/core/src/transaction/index.ts create mode 100644 packages/transactions/package.json rename packages/{core/src/transaction => transactions/src}/action_creators.ts (63%) rename packages/{core/src/transaction => transactions/src}/actions.ts (95%) rename packages/{core/src/transaction => transactions/src}/create_transaction.ts (89%) create mode 100644 packages/transactions/src/index.ts rename packages/{core/src/transaction => transactions/src}/schema.ts (97%) rename packages/{core/src/transaction => transactions/src}/sign.ts (98%) create mode 100644 packages/transactions/tsconfig.json diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2c85a97f2..fb8db9585 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,4 +4,4 @@ export * from './key_pair'; export * from './key_store'; export * from './provider'; export * from './signer'; -export * from './transaction'; +export * from './types'; diff --git a/packages/core/src/transaction/index.ts b/packages/core/src/transaction/index.ts deleted file mode 100644 index e3877d87f..000000000 --- a/packages/core/src/transaction/index.ts +++ /dev/null @@ -1,31 +0,0 @@ -export { - addKey, - createAccount, - deleteAccount, - deleteKey, - deployContract, - fullAccessKey, - functionCall, - functionCallAccessKey, - stake, - stringifyJsonOrBytes, - transfer, -} from './action_creators'; -export { - Action, - AccessKey, - AccessKeyPermission, - AddKey, - CreateAccount, - DeleteAccount, - DeleteKey, - DeployContract, - FullAccessPermission, - FunctionCall, - FunctionCallPermission, - Stake, - Transfer, -} from './actions'; -export { createTransaction } from './create_transaction'; -export { SCHEMA, Signature as TransactionSignature, SignedTransaction, Transaction } from './schema'; -export { signTransaction } from './sign'; diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 815977b91..fc2e8a817 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -13,6 +13,7 @@ "dependencies": { "@near-js/core": "workspace:*", "@near-js/providers": "workspace:*", + "@near-js/transactions": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index 490458906..424be5ea8 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -26,7 +26,7 @@ export { SCHEMA, createTransaction, signTransaction, - TransactionSignature as Signature, + Signature, SignedTransaction, - Transaction -} from '@near-js/core'; + Transaction, +} from '@near-js/transactions'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 5e6635345..99046536f 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -12,6 +12,7 @@ "license": "ISC", "dependencies": { "@near-js/core": "workspace:*", + "@near-js/transactions": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "http-errors": "^1.7.2", diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 5f184235e..5d29598b3 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -22,11 +22,13 @@ import { NearProtocolConfig, NodeStatusResult, QueryResponseKind, - SignedTransaction, TypedError, getErrorTypeFromErrorMessage, parseRpcError, } from '@near-js/core'; +import { + SignedTransaction, +} from '@near-js/transactions'; import { baseEncode } from 'borsh'; import { exponentialBackoff } from './exponential-backoff'; diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 88e112737..47d47defc 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -20,9 +20,11 @@ import { NodeStatusResult, QueryResponseKind, RpcQueryRequest, - SignedTransaction, EpochValidatorInfo, } from '@near-js/core'; +import { + SignedTransaction, +} from '@near-js/transactions'; /** @hidden */ export abstract class Provider { diff --git a/packages/transactions/package.json b/packages/transactions/package.json new file mode 100644 index 000000000..e083e028d --- /dev/null +++ b/packages/transactions/package.json @@ -0,0 +1,24 @@ +{ + "name": "@near-js/transactions", + "version": "0.0.1", + "description": "Functions and data types for transactions on NEAR", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/core": "workspace:*", + "bn.js": "5.2.1", + "borsh": "^0.7.0", + "js-sha256": "^0.9.0", + "mustache": "^4.0.0", + "tweetnacl": "^1.0.1" + }, + "devDependencies": { + "@types/node": "^18.7.14" + } +} diff --git a/packages/core/src/transaction/action_creators.ts b/packages/transactions/src/action_creators.ts similarity index 63% rename from packages/core/src/transaction/action_creators.ts rename to packages/transactions/src/action_creators.ts index 7fba1685a..4fdffa80d 100644 --- a/packages/core/src/transaction/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -1,6 +1,6 @@ +import { PublicKey } from '@near-js/core'; import BN from 'bn.js'; -import { PublicKey } from '../key_pair'; import { AccessKey, AccessKeyPermission, @@ -18,25 +18,32 @@ import { } from './actions'; export function fullAccessKey(): AccessKey { - return new AccessKey({ permission: new AccessKeyPermission({fullAccess: new FullAccessPermission({})}) }); + return new AccessKey({ + permission: new AccessKeyPermission({ + fullAccess: new FullAccessPermission({}), + }) + }); } export function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey { - return new AccessKey({ permission: new AccessKeyPermission({functionCall: new FunctionCallPermission({receiverId, allowance, methodNames})})}); + return new AccessKey({ + permission: new AccessKeyPermission({ + functionCall: new FunctionCallPermission({ receiverId, allowance, methodNames }), + }) + }); } export function createAccount(): Action { - return new Action({createAccount: new CreateAccount({}) }); + return new Action({ createAccount: new CreateAccount({}) }); } export function deployContract(code: Uint8Array): Action { - return new Action({ deployContract: new DeployContract({code}) }); + return new Action({ deployContract: new DeployContract({ code }) }); } export function stringifyJsonOrBytes(args: any): Buffer { const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; - const serializedArgs = isUint8Array ? args : Buffer.from(JSON.stringify(args)); - return serializedArgs; + return isUint8Array ? args : Buffer.from(JSON.stringify(args)); } /** @@ -54,25 +61,33 @@ export function functionCall(methodName: string, args: Uint8Array | object, gas: if(jsContract){ return new Action({ functionCall: new FunctionCall({ methodName, args, gas, deposit }) }); } - return new Action({ functionCall: new FunctionCall({ methodName, args: stringify(args), gas, deposit }) }); + + return new Action({ + functionCall: new FunctionCall({ + methodName, + args: stringify(args), + gas, + deposit, + }), + }); } export function transfer(deposit: BN): Action { - return new Action({transfer: new Transfer({ deposit }) }); + return new Action({ transfer: new Transfer({ deposit }) }); } export function stake(stake: BN, publicKey: PublicKey): Action { - return new Action({stake: new Stake({ stake, publicKey }) }); + return new Action({ stake: new Stake({ stake, publicKey }) }); } export function addKey(publicKey: PublicKey, accessKey: AccessKey): Action { - return new Action({addKey: new AddKey({ publicKey, accessKey}) }); + return new Action({ addKey: new AddKey({ publicKey, accessKey}) }); } export function deleteKey(publicKey: PublicKey): Action { - return new Action({deleteKey: new DeleteKey({ publicKey }) }); + return new Action({ deleteKey: new DeleteKey({ publicKey }) }); } export function deleteAccount(beneficiaryId: string): Action { - return new Action({deleteAccount: new DeleteAccount({ beneficiaryId }) }); + return new Action({ deleteAccount: new DeleteAccount({ beneficiaryId }) }); } diff --git a/packages/core/src/transaction/actions.ts b/packages/transactions/src/actions.ts similarity index 95% rename from packages/core/src/transaction/actions.ts rename to packages/transactions/src/actions.ts index 0f7a123aa..fe423ede9 100644 --- a/packages/core/src/transaction/actions.ts +++ b/packages/transactions/src/actions.ts @@ -1,8 +1,6 @@ +import { Assignable, PublicKey } from '@near-js/core'; import BN from 'bn.js'; -import { Assignable } from '../types'; -import { PublicKey } from '../key_pair'; - abstract class Enum { enum: string; diff --git a/packages/core/src/transaction/create_transaction.ts b/packages/transactions/src/create_transaction.ts similarity index 89% rename from packages/core/src/transaction/create_transaction.ts rename to packages/transactions/src/create_transaction.ts index 77cc472d7..833108a38 100644 --- a/packages/core/src/transaction/create_transaction.ts +++ b/packages/transactions/src/create_transaction.ts @@ -1,6 +1,6 @@ +import { PublicKey } from '@near-js/core'; import BN from 'bn.js'; -import { PublicKey } from '../key_pair'; import { Action } from './actions'; import { Transaction } from './schema'; diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts new file mode 100644 index 000000000..08e1eecc2 --- /dev/null +++ b/packages/transactions/src/index.ts @@ -0,0 +1,5 @@ +export * from './action_creators'; +export * from './actions'; +export * from './create_transaction'; +export * from './schema'; +export * from './sign'; diff --git a/packages/core/src/transaction/schema.ts b/packages/transactions/src/schema.ts similarity index 97% rename from packages/core/src/transaction/schema.ts rename to packages/transactions/src/schema.ts index 2199639a5..4bc3f05e9 100644 --- a/packages/core/src/transaction/schema.ts +++ b/packages/transactions/src/schema.ts @@ -1,8 +1,7 @@ +import { Assignable, KeyType, PublicKey } from '@near-js/core'; import BN from 'bn.js'; import { deserialize, serialize } from 'borsh'; -import { KeyType, PublicKey } from '../key_pair'; -import { Assignable } from '../types'; import { Action, AccessKey, diff --git a/packages/core/src/transaction/sign.ts b/packages/transactions/src/sign.ts similarity index 98% rename from packages/core/src/transaction/sign.ts rename to packages/transactions/src/sign.ts index 4e10197f6..24d2ec2a9 100644 --- a/packages/core/src/transaction/sign.ts +++ b/packages/transactions/src/sign.ts @@ -1,8 +1,8 @@ +import { Signer } from '@near-js/core'; import sha256 from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; -import { Signer } from '../signer'; import { Action } from './actions'; import { createTransaction } from './create_transaction'; import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema'; diff --git a/packages/transactions/tsconfig.json b/packages/transactions/tsconfig.json new file mode 100644 index 000000000..237474a66 --- /dev/null +++ b/packages/transactions/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext", + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts", + ], +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b50818cb2..3959fc80a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,32 +57,11 @@ importers: devDependencies: '@types/node': 18.7.14 - packages/providers: - specifiers: - '@near-js/core': workspace:* - '@types/node': ^18.7.14 - bn.js: 5.2.1 - borsh: ^0.7.0 - http-errors: ^1.7.2 - js-sha256: ^0.9.0 - node-fetch: ^2.6.1 - tweetnacl: ^1.0.1 - dependencies: - '@near-js/core': link:../core - bn.js: 5.2.1 - borsh: 0.7.0 - http-errors: 1.8.1 - js-sha256: 0.9.0 - tweetnacl: 1.0.3 - optionalDependencies: - node-fetch: 2.6.7 - devDependencies: - '@types/node': 18.7.14 - packages/near-api-js: specifiers: '@near-js/core': workspace:* '@near-js/providers': workspace:* + '@near-js/transactions': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 '@types/node': ^18.7.14 @@ -116,6 +95,7 @@ importers: dependencies: '@near-js/core': link:../core '@near-js/providers': link:../providers + '@near-js/transactions': link:../transactions ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 @@ -148,6 +128,49 @@ importers: ts-jest: 26.5.6_jest@26.6.3 uglifyify: 5.0.2 + packages/providers: + specifiers: + '@near-js/core': workspace:* + '@near-js/transactions': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + http-errors: ^1.7.2 + js-sha256: ^0.9.0 + node-fetch: ^2.6.1 + tweetnacl: ^1.0.1 + dependencies: + '@near-js/core': link:../core + '@near-js/transactions': link:../transactions + bn.js: 5.2.1 + borsh: 0.7.0 + http-errors: 1.8.1 + js-sha256: 0.9.0 + tweetnacl: 1.0.3 + optionalDependencies: + node-fetch: 2.6.7 + devDependencies: + '@types/node': 18.7.14 + + packages/transactions: + specifiers: + '@near-js/core': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + js-sha256: ^0.9.0 + mustache: ^4.0.0 + tweetnacl: ^1.0.1 + dependencies: + '@near-js/core': link:../core + bn.js: 5.2.1 + borsh: 0.7.0 + js-sha256: 0.9.0 + mustache: 4.2.0 + tweetnacl: 1.0.3 + devDependencies: + '@types/node': 18.7.14 + packages: /@ampproject/remapping/2.2.0: From ed9c45f80ee4e66e4b459f78b89a91a297b456a6 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:09:41 -0800 Subject: [PATCH 19/84] refactor: move MergeKeyStore into core --- packages/core/src/key_store/index.ts | 1 + .../core/src/key_store/merge_key_store.ts | 134 +++++++++++++++++ .../src/key_stores/merge_key_store.ts | 135 +----------------- 3 files changed, 136 insertions(+), 134 deletions(-) create mode 100644 packages/core/src/key_store/merge_key_store.ts diff --git a/packages/core/src/key_store/index.ts b/packages/core/src/key_store/index.ts index 54e11ea87..f095dee7f 100644 --- a/packages/core/src/key_store/index.ts +++ b/packages/core/src/key_store/index.ts @@ -1,2 +1,3 @@ export { InMemoryKeyStore } from './in_memory_key_store'; export { KeyStore } from './keystore'; +export { MergeKeyStore } from './merge_key_store'; diff --git a/packages/core/src/key_store/merge_key_store.ts b/packages/core/src/key_store/merge_key_store.ts new file mode 100644 index 000000000..c2796976b --- /dev/null +++ b/packages/core/src/key_store/merge_key_store.ts @@ -0,0 +1,134 @@ +import { KeyPair } from '../key_pair'; +import { KeyStore } from './keystore'; + +/** + * Keystore which can be used to merge multiple key stores into one virtual key store. + * + * @example + * ```js + * const { homedir } = require('os'); + * import { connect, keyStores, utils } from 'near-api-js'; + * + * const privateKey = '.......'; + * const keyPair = utils.KeyPair.fromString(privateKey); + * + * const inMemoryKeyStore = new keyStores.InMemoryKeyStore(); + * inMemoryKeyStore.setKey('testnet', 'example-account.testnet', keyPair); + * + * const fileSystemKeyStore = new keyStores.UnencryptedFileSystemKeyStore(`${homedir()}/.near-credentials`); + * + * const keyStore = new MergeKeyStore([ + * inMemoryKeyStore, + * fileSystemKeyStore + * ]); + * const config = { + * keyStore, // instance of MergeKeyStore + * networkId: 'testnet', + * nodeUrl: 'https://rpc.testnet.near.org', + * walletUrl: 'https://wallet.testnet.near.org', + * helperUrl: 'https://helper.testnet.near.org', + * explorerUrl: 'https://explorer.testnet.near.org' + * }; + * + * // inside an async function + * const near = await connect(config) + * ``` + */ + +interface MergeKeyStoreOptions { + writeKeyStoreIndex: number; +} + +export class MergeKeyStore extends KeyStore { + private options: MergeKeyStoreOptions; + keyStores: KeyStore[]; + + /** + * @param keyStores read calls are attempted from start to end of array + * @param options.writeKeyStoreIndex the keystore index that will receive all write calls + */ + constructor(keyStores: KeyStore[], options: MergeKeyStoreOptions = { writeKeyStoreIndex: 0 }) { + super(); + this.options = options; + this.keyStores = keyStores; + } + + /** + * Store a {@link utils/key_pair!KeyPair} to the first index of a key store array + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @param keyPair The key pair to store in local storage + */ + async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { + await this.keyStores[this.options.writeKeyStoreIndex].setKey(networkId, accountId, keyPair); + } + + /** + * Gets a {@link utils/key_pair!KeyPair} from the array of key stores + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @returns {Promise} + */ + async getKey(networkId: string, accountId: string): Promise { + for (const keyStore of this.keyStores) { + const keyPair = await keyStore.getKey(networkId, accountId); + if (keyPair) { + return keyPair; + } + } + return null; + } + + /** + * Removes a {@link utils/key_pair!KeyPair} from the array of key stores + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + */ + async removeKey(networkId: string, accountId: string): Promise { + for (const keyStore of this.keyStores) { + await keyStore.removeKey(networkId, accountId); + } + } + + /** + * Removes all items from each key store + */ + async clear(): Promise { + for (const keyStore of this.keyStores) { + await keyStore.clear(); + } + } + + /** + * Get the network(s) from the array of key stores + * @returns {Promise} + */ + async getNetworks(): Promise { + const result = new Set(); + for (const keyStore of this.keyStores) { + for (const network of await keyStore.getNetworks()) { + result.add(network); + } + } + return Array.from(result); + } + + /** + * Gets the account(s) from the array of key stores + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + async getAccounts(networkId: string): Promise { + const result = new Set(); + for (const keyStore of this.keyStores) { + for (const account of await keyStore.getAccounts(networkId)) { + result.add(account); + } + } + return Array.from(result); + } + + /** @hidden */ + toString(): string { + return `MergeKeyStore(${this.keyStores.join(', ')})`; + } +} \ No newline at end of file diff --git a/packages/near-api-js/src/key_stores/merge_key_store.ts b/packages/near-api-js/src/key_stores/merge_key_store.ts index 603f7ed74..41c7172eb 100644 --- a/packages/near-api-js/src/key_stores/merge_key_store.ts +++ b/packages/near-api-js/src/key_stores/merge_key_store.ts @@ -1,134 +1 @@ -import { KeyStore } from './keystore'; -import { KeyPair } from '../utils/key_pair'; - -/** - * Keystore which can be used to merge multiple key stores into one virtual key store. - * - * @example - * ```js - * const { homedir } = require('os'); - * import { connect, keyStores, utils } from 'near-api-js'; - * - * const privateKey = '.......'; - * const keyPair = utils.KeyPair.fromString(privateKey); - * - * const inMemoryKeyStore = new keyStores.InMemoryKeyStore(); - * inMemoryKeyStore.setKey('testnet', 'example-account.testnet', keyPair); - * - * const fileSystemKeyStore = new keyStores.UnencryptedFileSystemKeyStore(`${homedir()}/.near-credentials`); - * - * const keyStore = new MergeKeyStore([ - * inMemoryKeyStore, - * fileSystemKeyStore - * ]); - * const config = { - * keyStore, // instance of MergeKeyStore - * networkId: 'testnet', - * nodeUrl: 'https://rpc.testnet.near.org', - * walletUrl: 'https://wallet.testnet.near.org', - * helperUrl: 'https://helper.testnet.near.org', - * explorerUrl: 'https://explorer.testnet.near.org' - * }; - * - * // inside an async function - * const near = await connect(config) - * ``` - */ - -interface MergeKeyStoreOptions { - writeKeyStoreIndex: number; -} - -export class MergeKeyStore extends KeyStore { - private options: MergeKeyStoreOptions; - keyStores: KeyStore[]; - - /** - * @param keyStores read calls are attempted from start to end of array - * @param options.writeKeyStoreIndex the keystore index that will receive all write calls - */ - constructor(keyStores: KeyStore[], options: MergeKeyStoreOptions = { writeKeyStoreIndex: 0 }) { - super(); - this.options = options; - this.keyStores = keyStores; - } - - /** - * Store a {@link utils/key_pair!KeyPair} to the first index of a key store array - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @param keyPair The key pair to store in local storage - */ - async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { - await this.keyStores[this.options.writeKeyStoreIndex].setKey(networkId, accountId, keyPair); - } - - /** - * Gets a {@link utils/key_pair!KeyPair} from the array of key stores - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @returns {Promise} - */ - async getKey(networkId: string, accountId: string): Promise { - for (const keyStore of this.keyStores) { - const keyPair = await keyStore.getKey(networkId, accountId); - if (keyPair) { - return keyPair; - } - } - return null; - } - - /** - * Removes a {@link utils/key_pair!KeyPair} from the array of key stores - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - */ - async removeKey(networkId: string, accountId: string): Promise { - for (const keyStore of this.keyStores) { - await keyStore.removeKey(networkId, accountId); - } - } - - /** - * Removes all items from each key store - */ - async clear(): Promise { - for (const keyStore of this.keyStores) { - await keyStore.clear(); - } - } - - /** - * Get the network(s) from the array of key stores - * @returns {Promise} - */ - async getNetworks(): Promise { - const result = new Set(); - for (const keyStore of this.keyStores) { - for (const network of await keyStore.getNetworks()) { - result.add(network); - } - } - return Array.from(result); - } - - /** - * Gets the account(s) from the array of key stores - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - async getAccounts(networkId: string): Promise { - const result = new Set(); - for (const keyStore of this.keyStores) { - for (const account of await keyStore.getAccounts(networkId)) { - result.add(account); - } - } - return Array.from(result); - } - - /** @hidden */ - toString(): string { - return `MergeKeyStore(${this.keyStores.join(', ')})`; - } -} \ No newline at end of file +export { MergeKeyStore } from '@near-js/core'; From b2d2bee2f423eb415f06e84308aafe37828c8ab6 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:13:55 -0800 Subject: [PATCH 20/84] refactor: move tests from near-api-js to core --- packages/core/jest.config.js | 5 ++++ packages/core/package.json | 7 ++++-- packages/core/test/.eslintrc.yml | 7 ++++++ .../test/utils => core/test}/format.test.js | 10 ++++---- .../test/key_pair.test.js | 23 ++++++++++--------- .../utils => core/test}/rpc-errors.test.js | 9 ++------ packages/core/test/signer.test.js | 7 ++++++ packages/near-api-js/test/signer.test.js | 7 ------ pnpm-lock.yaml | 4 ++++ 9 files changed, 46 insertions(+), 33 deletions(-) create mode 100644 packages/core/jest.config.js create mode 100644 packages/core/test/.eslintrc.yml rename packages/{near-api-js/test/utils => core/test}/format.test.js (90%) rename packages/{near-api-js => core}/test/key_pair.test.js (52%) rename packages/{near-api-js/test/utils => core/test}/rpc-errors.test.js (96%) create mode 100644 packages/core/test/signer.test.js delete mode 100644 packages/near-api-js/test/signer.test.js diff --git a/packages/core/jest.config.js b/packages/core/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/core/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/core/package.json b/packages/core/package.json index c5709e426..79f6b33f0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -5,7 +5,8 @@ "main": "lib/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json" + "compile": "tsc -p tsconfig.json", + "test": "jest test" }, "keywords": [], "author": "", @@ -18,6 +19,8 @@ "tweetnacl": "^1.0.1" }, "devDependencies": { - "@types/node": "^18.7.14" + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" } } diff --git a/packages/core/test/.eslintrc.yml b/packages/core/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/core/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/near-api-js/test/utils/format.test.js b/packages/core/test/format.test.js similarity index 90% rename from packages/near-api-js/test/utils/format.test.js rename to packages/core/test/format.test.js index 009df1f8d..fcd642d5b 100644 --- a/packages/near-api-js/test/utils/format.test.js +++ b/packages/core/test/format.test.js @@ -1,6 +1,4 @@ -// Unit tests for simple util code - -const nearApi = require('../../src/index'); +const { formatNearAmount, parseNearAmount } = require('../lib'); jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; @@ -28,7 +26,7 @@ test.each` ${'1000100000000000000000000000000'} | ${undefined} | ${'1,000,100'} ${'910000000000000000000000'} | ${0} | ${'1'} `('formatNearAmount($balance, $fracDigits) returns $expected', ({ balance, fracDigits, expected }) => { - expect(nearApi.utils.format.formatNearAmount(balance, fracDigits)).toEqual(expected); + expect(formatNearAmount(balance, fracDigits)).toEqual(expected); }); test.each` @@ -49,12 +47,12 @@ test.each` ${'000000.000001'} | ${'1000000000000000000'} ${'1,000,000.1'} | ${'1000000100000000000000000000000'} `('parseNearAmount($amt) returns $expected', ({ amt, expected }) => { - expect(nearApi.utils.format.parseNearAmount(amt)).toStrictEqual(expected); + expect(parseNearAmount(amt)).toStrictEqual(expected); }); test('parseNearAmount fails when parsing values with ≥25 decimal places', () => { expect(() => { - nearApi.utils.format.parseNearAmount('0.0000080990999998370878871'); + parseNearAmount('0.0000080990999998370878871'); }).toThrowError( 'Cannot parse \'0.0000080990999998370878871\' as NEAR amount' ); diff --git a/packages/near-api-js/test/key_pair.test.js b/packages/core/test/key_pair.test.js similarity index 52% rename from packages/near-api-js/test/key_pair.test.js rename to packages/core/test/key_pair.test.js index e7def3ef3..b9796559b 100644 --- a/packages/near-api-js/test/key_pair.test.js +++ b/packages/core/test/key_pair.test.js @@ -1,41 +1,42 @@ - -const nearApi = require('../src/index'); +const { baseEncode } = require('borsh'); const { sha256 } = require('js-sha256'); +const { KeyPair, KeyPairEd25519, PublicKey } = require('../lib'); + test('test sign and verify', async () => { - const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('26x56YPzPDro5t2smQfGcYAPy3j7R2jB2NUb7xKbAGK23B6x4WNQPh3twb6oDksFov5X8ts5CtntUNbpQpAKFdbR'); + const keyPair = new KeyPairEd25519('26x56YPzPDro5t2smQfGcYAPy3j7R2jB2NUb7xKbAGK23B6x4WNQPh3twb6oDksFov5X8ts5CtntUNbpQpAKFdbR'); expect(keyPair.publicKey.toString()).toEqual('ed25519:AYWv9RAN1hpSQA4p1DLhCNnpnNXwxhfH9qeHN8B4nJ59'); const message = new Uint8Array(sha256.array('message')); const signature = keyPair.sign(message); - expect(nearApi.utils.serialize.base_encode(signature.signature)).toEqual('26gFr4xth7W9K7HPWAxq3BLsua8oTy378mC1MYFiEXHBBpeBjP8WmJEJo8XTBowetvqbRshcQEtBUdwQcAqDyP8T'); + expect(baseEncode(signature.signature)).toEqual('26gFr4xth7W9K7HPWAxq3BLsua8oTy378mC1MYFiEXHBBpeBjP8WmJEJo8XTBowetvqbRshcQEtBUdwQcAqDyP8T'); }); test('test sign and verify with random', async () => { - const keyPair = nearApi.utils.key_pair.KeyPairEd25519.fromRandom(); + const keyPair = KeyPairEd25519.fromRandom(); const message = new Uint8Array(sha256.array('message')); const signature = keyPair.sign(message); expect(keyPair.verify(message, signature.signature)).toBeTruthy(); }); test('test sign and verify with public key', async () => { - const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); + const keyPair = new KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); const message = new Uint8Array(sha256.array('message')); const signature = keyPair.sign(message); - const publicKey = nearApi.utils.key_pair.PublicKey.from('ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW'); + const publicKey = PublicKey.from('ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW'); expect(publicKey.verify(message, signature.signature)).toBeTruthy(); }); test('test from secret', async () => { - const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); + const keyPair = new KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); expect(keyPair.publicKey.toString()).toEqual('ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW'); }); test('convert to string', async () => { - const keyPair = nearApi.utils.key_pair.KeyPairEd25519.fromRandom(); - const newKeyPair = nearApi.utils.key_pair.KeyPair.fromString(keyPair.toString()); + const keyPair = KeyPairEd25519.fromRandom(); + const newKeyPair = KeyPair.fromString(keyPair.toString()); expect(newKeyPair.secretKey).toEqual(keyPair.secretKey); const keyString = 'ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'; - const keyPair2 = nearApi.utils.key_pair.KeyPair.fromString(keyString); + const keyPair2 = KeyPair.fromString(keyString); expect(keyPair2.toString()).toEqual(keyString); }); diff --git a/packages/near-api-js/test/utils/rpc-errors.test.js b/packages/core/test/rpc-errors.test.js similarity index 96% rename from packages/near-api-js/test/utils/rpc-errors.test.js rename to packages/core/test/rpc-errors.test.js index a176edfe3..203932452 100644 --- a/packages/near-api-js/test/utils/rpc-errors.test.js +++ b/packages/core/test/rpc-errors.test.js @@ -1,10 +1,5 @@ -const nearApi = require('../../src/index'); -const { ServerError } = require('../../src/utils/rpc_errors'); -const { - parseRpcError, - formatError, - getErrorTypeFromErrorMessage -} = nearApi.utils.rpc_errors; +const { formatError, getErrorTypeFromErrorMessage, parseRpcError, ServerError } = require('../lib'); + describe('rpc-errors', () => { test('test AccountAlreadyExists error', async () => { let rpc_error = { diff --git a/packages/core/test/signer.test.js b/packages/core/test/signer.test.js new file mode 100644 index 000000000..f8862fe4f --- /dev/null +++ b/packages/core/test/signer.test.js @@ -0,0 +1,7 @@ +const { InMemoryKeyStore, InMemorySigner } = require('../lib'); + +test('test no key', async() => { + const signer = new InMemorySigner(new InMemoryKeyStore()); + await expect(signer.signMessage('message', 'user', 'network')) + .rejects.toThrow(/Key for user not found in network/); +}); diff --git a/packages/near-api-js/test/signer.test.js b/packages/near-api-js/test/signer.test.js deleted file mode 100644 index 9de563bdb..000000000 --- a/packages/near-api-js/test/signer.test.js +++ /dev/null @@ -1,7 +0,0 @@ - -const nearApi = require('../src/index'); - -test('test no key', async() => { - const signer = new nearApi.InMemorySigner(new nearApi.keyStores.InMemoryKeyStore()); - await expect(signer.signMessage('message', 'user', 'network')).rejects.toThrow(/Key for user not found in network/); -}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3959fc80a..d0bd0e552 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,10 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + jest: ^26.0.1 js-sha256: ^0.9.0 mustache: ^4.0.0 + ts-jest: ^26.5.6 tweetnacl: ^1.0.1 dependencies: bn.js: 5.2.1 @@ -56,6 +58,8 @@ importers: tweetnacl: 1.0.3 devDependencies: '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 packages/near-api-js: specifiers: From a0e6b544acdd4ea022bfffce96b6f4d57690738b Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:26:37 -0800 Subject: [PATCH 21/84] refactor: move tests from near-api-js to providers --- packages/providers/jest.config.js | 5 +++++ packages/providers/package.json | 7 +++++-- packages/providers/test/.eslintrc.yml | 7 +++++++ .../{near-api-js/test/utils => providers/test}/web.test.js | 7 +++---- pnpm-lock.yaml | 4 ++++ 5 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 packages/providers/jest.config.js create mode 100644 packages/providers/test/.eslintrc.yml rename packages/{near-api-js/test/utils => providers/test}/web.test.js (74%) diff --git a/packages/providers/jest.config.js b/packages/providers/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/providers/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/providers/package.json b/packages/providers/package.json index 99046536f..9273b1d04 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -5,7 +5,8 @@ "main": "lib/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json" + "compile": "tsc -p tsconfig.json", + "test": "jest test" }, "keywords": [], "author": "", @@ -20,7 +21,9 @@ "tweetnacl": "^1.0.1" }, "devDependencies": { - "@types/node": "^18.7.14" + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" }, "optionalDependencies": { "node-fetch": "^2.6.1" diff --git a/packages/providers/test/.eslintrc.yml b/packages/providers/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/providers/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/near-api-js/test/utils/web.test.js b/packages/providers/test/web.test.js similarity index 74% rename from packages/near-api-js/test/utils/web.test.js rename to packages/providers/test/web.test.js index 880e9bd66..064c1b02c 100644 --- a/packages/near-api-js/test/utils/web.test.js +++ b/packages/providers/test/web.test.js @@ -1,5 +1,4 @@ -const nearApi = require('../../src/index'); -const { web } = nearApi.utils; +const { fetchJson } = require('../lib'); describe('web', () => { test('string parameter in fetchJson', async () => { @@ -10,7 +9,7 @@ describe('web', () => { 'method': 'status', 'params': [] }; - const result = await web.fetchJson(RPC_URL, JSON.stringify(statusRequest)); + const result = await fetchJson(RPC_URL, JSON.stringify(statusRequest)); expect(result.result.chain_id).toBe('testnet'); }); test('object parameter in fetchJson', async () => { @@ -21,7 +20,7 @@ describe('web', () => { 'method': 'status', 'params': [] }; - const result = await web.fetchJson(connection, JSON.stringify(statusRequest)); + const result = await fetchJson(connection, JSON.stringify(statusRequest)); expect(result.result.chain_id).toBe('testnet'); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0bd0e552..6fd887230 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,8 +140,10 @@ importers: bn.js: 5.2.1 borsh: ^0.7.0 http-errors: ^1.7.2 + jest: ^26.0.1 js-sha256: ^0.9.0 node-fetch: ^2.6.1 + ts-jest: ^26.5.6 tweetnacl: ^1.0.1 dependencies: '@near-js/core': link:../core @@ -155,6 +157,8 @@ importers: node-fetch: 2.6.7 devDependencies: '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 packages/transactions: specifiers: From 2481903a5d141fbf5c9ff992f17caf226d3b49a2 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:27:37 -0800 Subject: [PATCH 22/84] refactor: rename web to fetch_json --- packages/providers/src/{web.ts => fetch_json.ts} | 0 packages/providers/src/index.ts | 2 +- packages/providers/src/json-rpc-provider.ts | 2 +- packages/providers/test/{web.test.js => fetch_json.test.js} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename packages/providers/src/{web.ts => fetch_json.ts} (100%) rename packages/providers/test/{web.test.js => fetch_json.test.js} (96%) diff --git a/packages/providers/src/web.ts b/packages/providers/src/fetch_json.ts similarity index 100% rename from packages/providers/src/web.ts rename to packages/providers/src/fetch_json.ts diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts index 6eb5740d2..8c6eda449 100644 --- a/packages/providers/src/index.ts +++ b/packages/providers/src/index.ts @@ -1,4 +1,4 @@ export { exponentialBackoff } from './exponential-backoff'; export { JsonRpcProvider } from './json-rpc-provider'; export { Provider } from './provider'; -export { fetchJson } from './web'; +export { fetchJson } from './fetch_json'; diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 5d29598b3..85d1e33c9 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -33,7 +33,7 @@ import { baseEncode } from 'borsh'; import { exponentialBackoff } from './exponential-backoff'; import { Provider } from './provider'; -import { ConnectionInfo, fetchJson } from './web'; +import { ConnectionInfo, fetchJson } from './fetch_json'; /** @hidden */ // Default number of retries before giving up on a request. diff --git a/packages/providers/test/web.test.js b/packages/providers/test/fetch_json.test.js similarity index 96% rename from packages/providers/test/web.test.js rename to packages/providers/test/fetch_json.test.js index 064c1b02c..6711d53e2 100644 --- a/packages/providers/test/web.test.js +++ b/packages/providers/test/fetch_json.test.js @@ -1,6 +1,6 @@ const { fetchJson } = require('../lib'); -describe('web', () => { +describe('fetchJson', () => { test('string parameter in fetchJson', async () => { const RPC_URL = 'https://rpc.testnet.near.org'; const statusRequest = { From a234431423c4d1fc8013e376436a5241e5c43d5b Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:42:29 -0800 Subject: [PATCH 23/84] refactor: rename core to client-core --- packages/{core => client-core}/jest.config.js | 0 packages/{core => client-core}/package.json | 4 +-- .../src/errors/error_messages.json | 0 .../src/errors/errors.ts | 0 .../{core => client-core}/src/errors/index.ts | 0 .../src/errors/rpc_error_schema.json | 0 .../src/errors/rpc_errors.ts | 0 packages/{core => client-core}/src/format.ts | 0 packages/{core => client-core}/src/index.ts | 0 .../src/key_pair/constants.ts | 0 .../src/key_pair/index.ts | 0 .../src/key_pair/key_pair.ts | 0 .../src/key_pair/key_pair_ed25519.ts | 0 .../src/key_pair/public_key.ts | 0 .../src/key_store/in_memory_key_store.ts | 0 .../src/key_store/index.ts | 0 .../src/key_store/keystore.ts | 0 .../src/key_store/merge_key_store.ts | 0 .../src/provider/index.ts | 0 .../src/provider/light_client.ts | 0 .../src/provider/protocol.ts | 0 .../src/provider/request.ts | 0 .../src/provider/response.ts | 0 .../src/provider/utils.ts | 0 .../src/provider/validator.ts | 0 .../src/signer/in_memory_signer.ts | 0 .../{core => client-core}/src/signer/index.ts | 0 .../src/signer/signer.ts | 0 packages/{core => client-core}/src/types.ts | 0 .../{core => client-core}/test/.eslintrc.yml | 0 .../{core => client-core}/test/format.test.js | 0 .../test/key_pair.test.js | 0 .../test/rpc-errors.test.js | 0 .../{core => client-core}/test/signer.test.js | 0 packages/{core => client-core}/tsconfig.json | 0 packages/near-api-js/package.json | 2 +- .../src/key_stores/in_memory_key_store.ts | 2 +- .../near-api-js/src/key_stores/keystore.ts | 2 +- .../src/key_stores/merge_key_store.ts | 2 +- .../src/providers/json-rpc-provider.ts | 2 +- .../near-api-js/src/providers/provider.ts | 2 +- packages/near-api-js/src/signer.ts | 2 +- packages/near-api-js/src/utils/errors.ts | 2 +- packages/near-api-js/src/utils/format.ts | 2 +- packages/near-api-js/src/utils/key_pair.ts | 2 +- packages/near-api-js/src/utils/rpc_errors.ts | 2 +- packages/providers/package.json | 2 +- packages/providers/src/fetch_json.ts | 2 +- packages/providers/src/json-rpc-provider.ts | 2 +- packages/providers/src/provider.ts | 2 +- packages/transactions/package.json | 2 +- packages/transactions/src/action_creators.ts | 2 +- packages/transactions/src/actions.ts | 2 +- .../transactions/src/create_transaction.ts | 2 +- packages/transactions/src/schema.ts | 2 +- packages/transactions/src/sign.ts | 2 +- pnpm-lock.yaml | 34 +++++++++---------- 57 files changed, 40 insertions(+), 40 deletions(-) rename packages/{core => client-core}/jest.config.js (100%) rename packages/{core => client-core}/package.json (81%) rename packages/{core => client-core}/src/errors/error_messages.json (100%) rename packages/{core => client-core}/src/errors/errors.ts (100%) rename packages/{core => client-core}/src/errors/index.ts (100%) rename packages/{core => client-core}/src/errors/rpc_error_schema.json (100%) rename packages/{core => client-core}/src/errors/rpc_errors.ts (100%) rename packages/{core => client-core}/src/format.ts (100%) rename packages/{core => client-core}/src/index.ts (100%) rename packages/{core => client-core}/src/key_pair/constants.ts (100%) rename packages/{core => client-core}/src/key_pair/index.ts (100%) rename packages/{core => client-core}/src/key_pair/key_pair.ts (100%) rename packages/{core => client-core}/src/key_pair/key_pair_ed25519.ts (100%) rename packages/{core => client-core}/src/key_pair/public_key.ts (100%) rename packages/{core => client-core}/src/key_store/in_memory_key_store.ts (100%) rename packages/{core => client-core}/src/key_store/index.ts (100%) rename packages/{core => client-core}/src/key_store/keystore.ts (100%) rename packages/{core => client-core}/src/key_store/merge_key_store.ts (100%) rename packages/{core => client-core}/src/provider/index.ts (100%) rename packages/{core => client-core}/src/provider/light_client.ts (100%) rename packages/{core => client-core}/src/provider/protocol.ts (100%) rename packages/{core => client-core}/src/provider/request.ts (100%) rename packages/{core => client-core}/src/provider/response.ts (100%) rename packages/{core => client-core}/src/provider/utils.ts (100%) rename packages/{core => client-core}/src/provider/validator.ts (100%) rename packages/{core => client-core}/src/signer/in_memory_signer.ts (100%) rename packages/{core => client-core}/src/signer/index.ts (100%) rename packages/{core => client-core}/src/signer/signer.ts (100%) rename packages/{core => client-core}/src/types.ts (100%) rename packages/{core => client-core}/test/.eslintrc.yml (100%) rename packages/{core => client-core}/test/format.test.js (100%) rename packages/{core => client-core}/test/key_pair.test.js (100%) rename packages/{core => client-core}/test/rpc-errors.test.js (100%) rename packages/{core => client-core}/test/signer.test.js (100%) rename packages/{core => client-core}/tsconfig.json (100%) diff --git a/packages/core/jest.config.js b/packages/client-core/jest.config.js similarity index 100% rename from packages/core/jest.config.js rename to packages/client-core/jest.config.js diff --git a/packages/core/package.json b/packages/client-core/package.json similarity index 81% rename from packages/core/package.json rename to packages/client-core/package.json index 79f6b33f0..8063f748b 100644 --- a/packages/core/package.json +++ b/packages/client-core/package.json @@ -1,7 +1,7 @@ { - "name": "@near-js/core", + "name": "@near-js/client-core", "version": "0.0.1", - "description": "Core dependencies for the NEAR JavaScript API", + "description": "Core dependencies for the NEAR API JavaScript client", "main": "lib/index.js", "scripts": { "build": "pnpm compile", diff --git a/packages/core/src/errors/error_messages.json b/packages/client-core/src/errors/error_messages.json similarity index 100% rename from packages/core/src/errors/error_messages.json rename to packages/client-core/src/errors/error_messages.json diff --git a/packages/core/src/errors/errors.ts b/packages/client-core/src/errors/errors.ts similarity index 100% rename from packages/core/src/errors/errors.ts rename to packages/client-core/src/errors/errors.ts diff --git a/packages/core/src/errors/index.ts b/packages/client-core/src/errors/index.ts similarity index 100% rename from packages/core/src/errors/index.ts rename to packages/client-core/src/errors/index.ts diff --git a/packages/core/src/errors/rpc_error_schema.json b/packages/client-core/src/errors/rpc_error_schema.json similarity index 100% rename from packages/core/src/errors/rpc_error_schema.json rename to packages/client-core/src/errors/rpc_error_schema.json diff --git a/packages/core/src/errors/rpc_errors.ts b/packages/client-core/src/errors/rpc_errors.ts similarity index 100% rename from packages/core/src/errors/rpc_errors.ts rename to packages/client-core/src/errors/rpc_errors.ts diff --git a/packages/core/src/format.ts b/packages/client-core/src/format.ts similarity index 100% rename from packages/core/src/format.ts rename to packages/client-core/src/format.ts diff --git a/packages/core/src/index.ts b/packages/client-core/src/index.ts similarity index 100% rename from packages/core/src/index.ts rename to packages/client-core/src/index.ts diff --git a/packages/core/src/key_pair/constants.ts b/packages/client-core/src/key_pair/constants.ts similarity index 100% rename from packages/core/src/key_pair/constants.ts rename to packages/client-core/src/key_pair/constants.ts diff --git a/packages/core/src/key_pair/index.ts b/packages/client-core/src/key_pair/index.ts similarity index 100% rename from packages/core/src/key_pair/index.ts rename to packages/client-core/src/key_pair/index.ts diff --git a/packages/core/src/key_pair/key_pair.ts b/packages/client-core/src/key_pair/key_pair.ts similarity index 100% rename from packages/core/src/key_pair/key_pair.ts rename to packages/client-core/src/key_pair/key_pair.ts diff --git a/packages/core/src/key_pair/key_pair_ed25519.ts b/packages/client-core/src/key_pair/key_pair_ed25519.ts similarity index 100% rename from packages/core/src/key_pair/key_pair_ed25519.ts rename to packages/client-core/src/key_pair/key_pair_ed25519.ts diff --git a/packages/core/src/key_pair/public_key.ts b/packages/client-core/src/key_pair/public_key.ts similarity index 100% rename from packages/core/src/key_pair/public_key.ts rename to packages/client-core/src/key_pair/public_key.ts diff --git a/packages/core/src/key_store/in_memory_key_store.ts b/packages/client-core/src/key_store/in_memory_key_store.ts similarity index 100% rename from packages/core/src/key_store/in_memory_key_store.ts rename to packages/client-core/src/key_store/in_memory_key_store.ts diff --git a/packages/core/src/key_store/index.ts b/packages/client-core/src/key_store/index.ts similarity index 100% rename from packages/core/src/key_store/index.ts rename to packages/client-core/src/key_store/index.ts diff --git a/packages/core/src/key_store/keystore.ts b/packages/client-core/src/key_store/keystore.ts similarity index 100% rename from packages/core/src/key_store/keystore.ts rename to packages/client-core/src/key_store/keystore.ts diff --git a/packages/core/src/key_store/merge_key_store.ts b/packages/client-core/src/key_store/merge_key_store.ts similarity index 100% rename from packages/core/src/key_store/merge_key_store.ts rename to packages/client-core/src/key_store/merge_key_store.ts diff --git a/packages/core/src/provider/index.ts b/packages/client-core/src/provider/index.ts similarity index 100% rename from packages/core/src/provider/index.ts rename to packages/client-core/src/provider/index.ts diff --git a/packages/core/src/provider/light_client.ts b/packages/client-core/src/provider/light_client.ts similarity index 100% rename from packages/core/src/provider/light_client.ts rename to packages/client-core/src/provider/light_client.ts diff --git a/packages/core/src/provider/protocol.ts b/packages/client-core/src/provider/protocol.ts similarity index 100% rename from packages/core/src/provider/protocol.ts rename to packages/client-core/src/provider/protocol.ts diff --git a/packages/core/src/provider/request.ts b/packages/client-core/src/provider/request.ts similarity index 100% rename from packages/core/src/provider/request.ts rename to packages/client-core/src/provider/request.ts diff --git a/packages/core/src/provider/response.ts b/packages/client-core/src/provider/response.ts similarity index 100% rename from packages/core/src/provider/response.ts rename to packages/client-core/src/provider/response.ts diff --git a/packages/core/src/provider/utils.ts b/packages/client-core/src/provider/utils.ts similarity index 100% rename from packages/core/src/provider/utils.ts rename to packages/client-core/src/provider/utils.ts diff --git a/packages/core/src/provider/validator.ts b/packages/client-core/src/provider/validator.ts similarity index 100% rename from packages/core/src/provider/validator.ts rename to packages/client-core/src/provider/validator.ts diff --git a/packages/core/src/signer/in_memory_signer.ts b/packages/client-core/src/signer/in_memory_signer.ts similarity index 100% rename from packages/core/src/signer/in_memory_signer.ts rename to packages/client-core/src/signer/in_memory_signer.ts diff --git a/packages/core/src/signer/index.ts b/packages/client-core/src/signer/index.ts similarity index 100% rename from packages/core/src/signer/index.ts rename to packages/client-core/src/signer/index.ts diff --git a/packages/core/src/signer/signer.ts b/packages/client-core/src/signer/signer.ts similarity index 100% rename from packages/core/src/signer/signer.ts rename to packages/client-core/src/signer/signer.ts diff --git a/packages/core/src/types.ts b/packages/client-core/src/types.ts similarity index 100% rename from packages/core/src/types.ts rename to packages/client-core/src/types.ts diff --git a/packages/core/test/.eslintrc.yml b/packages/client-core/test/.eslintrc.yml similarity index 100% rename from packages/core/test/.eslintrc.yml rename to packages/client-core/test/.eslintrc.yml diff --git a/packages/core/test/format.test.js b/packages/client-core/test/format.test.js similarity index 100% rename from packages/core/test/format.test.js rename to packages/client-core/test/format.test.js diff --git a/packages/core/test/key_pair.test.js b/packages/client-core/test/key_pair.test.js similarity index 100% rename from packages/core/test/key_pair.test.js rename to packages/client-core/test/key_pair.test.js diff --git a/packages/core/test/rpc-errors.test.js b/packages/client-core/test/rpc-errors.test.js similarity index 100% rename from packages/core/test/rpc-errors.test.js rename to packages/client-core/test/rpc-errors.test.js diff --git a/packages/core/test/signer.test.js b/packages/client-core/test/signer.test.js similarity index 100% rename from packages/core/test/signer.test.js rename to packages/client-core/test/signer.test.js diff --git a/packages/core/tsconfig.json b/packages/client-core/tsconfig.json similarity index 100% rename from packages/core/tsconfig.json rename to packages/client-core/tsconfig.json diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index fc2e8a817..c68738e1c 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -11,7 +11,7 @@ "browser": "lib/browser-index.js", "types": "lib/index.d.ts", "dependencies": { - "@near-js/core": "workspace:*", + "@near-js/client-core": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", "ajv": "^8.11.2", diff --git a/packages/near-api-js/src/key_stores/in_memory_key_store.ts b/packages/near-api-js/src/key_stores/in_memory_key_store.ts index d22720928..08aa74249 100644 --- a/packages/near-api-js/src/key_stores/in_memory_key_store.ts +++ b/packages/near-api-js/src/key_stores/in_memory_key_store.ts @@ -1 +1 @@ -export { InMemoryKeyStore } from '@near-js/core'; +export { InMemoryKeyStore } from '@near-js/client-core'; diff --git a/packages/near-api-js/src/key_stores/keystore.ts b/packages/near-api-js/src/key_stores/keystore.ts index 87c95b7b5..6eab9833c 100644 --- a/packages/near-api-js/src/key_stores/keystore.ts +++ b/packages/near-api-js/src/key_stores/keystore.ts @@ -1 +1 @@ -export { KeyStore } from '@near-js/core'; +export { KeyStore } from '@near-js/client-core'; diff --git a/packages/near-api-js/src/key_stores/merge_key_store.ts b/packages/near-api-js/src/key_stores/merge_key_store.ts index 41c7172eb..0ffca4319 100644 --- a/packages/near-api-js/src/key_stores/merge_key_store.ts +++ b/packages/near-api-js/src/key_stores/merge_key_store.ts @@ -1 +1 @@ -export { MergeKeyStore } from '@near-js/core'; +export { MergeKeyStore } from '@near-js/client-core'; diff --git a/packages/near-api-js/src/providers/json-rpc-provider.ts b/packages/near-api-js/src/providers/json-rpc-provider.ts index c689c609c..4bddefc01 100644 --- a/packages/near-api-js/src/providers/json-rpc-provider.ts +++ b/packages/near-api-js/src/providers/json-rpc-provider.ts @@ -1,2 +1,2 @@ -export { ErrorContext, TypedError } from '@near-js/core'; +export { ErrorContext, TypedError } from '@near-js/client-core'; export { JsonRpcProvider } from '@near-js/providers'; diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index 8509ec6f9..dc4d9d62d 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -67,7 +67,7 @@ export { EpochValidatorInfo, NextEpochValidatorInfo, ValidatorStakeView, -} from '@near-js/core'; +} from '@near-js/client-core'; export { Provider, } from '@near-js/providers'; diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index c9f83f0ba..74e867942 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1 +1 @@ -export { InMemorySigner, Signer } from '@near-js/core'; +export { InMemorySigner, Signer } from '@near-js/client-core'; diff --git a/packages/near-api-js/src/utils/errors.ts b/packages/near-api-js/src/utils/errors.ts index df8dda749..7a2c75a7a 100644 --- a/packages/near-api-js/src/utils/errors.ts +++ b/packages/near-api-js/src/utils/errors.ts @@ -4,4 +4,4 @@ export { PositionalArgsError, TypedError, logWarning, -} from '@near-js/core'; +} from '@near-js/client-core'; diff --git a/packages/near-api-js/src/utils/format.ts b/packages/near-api-js/src/utils/format.ts index 26ab737c3..271ddedde 100644 --- a/packages/near-api-js/src/utils/format.ts +++ b/packages/near-api-js/src/utils/format.ts @@ -3,4 +3,4 @@ export { NEAR_NOMINATION_EXP, formatNearAmount, parseNearAmount, -} from '@near-js/core'; +} from '@near-js/client-core'; diff --git a/packages/near-api-js/src/utils/key_pair.ts b/packages/near-api-js/src/utils/key_pair.ts index cfe8c95b0..99280c3dd 100644 --- a/packages/near-api-js/src/utils/key_pair.ts +++ b/packages/near-api-js/src/utils/key_pair.ts @@ -4,6 +4,6 @@ export { KeyType, PublicKey, Signature, -} from '@near-js/core'; +} from '@near-js/client-core'; export type Arrayish = string | ArrayLike; diff --git a/packages/near-api-js/src/utils/rpc_errors.ts b/packages/near-api-js/src/utils/rpc_errors.ts index e1018419c..c38d7dacd 100644 --- a/packages/near-api-js/src/utils/rpc_errors.ts +++ b/packages/near-api-js/src/utils/rpc_errors.ts @@ -4,4 +4,4 @@ export { parseResultError, formatError, getErrorTypeFromErrorMessage, -} from '@near-js/core'; +} from '@near-js/client-core'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 9273b1d04..f4824d3cf 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -12,7 +12,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/core": "workspace:*", + "@near-js/client-core": "workspace:*", "@near-js/transactions": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index 4ba6dd32c..f4deb8182 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -1,6 +1,6 @@ import { TypedError, -} from '@near-js/core'; +} from '@near-js/client-core'; import createError from 'http-errors'; import { exponentialBackoff } from './exponential-backoff'; diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 85d1e33c9..701084457 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -25,7 +25,7 @@ import { TypedError, getErrorTypeFromErrorMessage, parseRpcError, -} from '@near-js/core'; +} from '@near-js/client-core'; import { SignedTransaction, } from '@near-js/transactions'; diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 47d47defc..cbd74e700 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -21,7 +21,7 @@ import { QueryResponseKind, RpcQueryRequest, EpochValidatorInfo, -} from '@near-js/core'; +} from '@near-js/client-core'; import { SignedTransaction, } from '@near-js/transactions'; diff --git a/packages/transactions/package.json b/packages/transactions/package.json index e083e028d..584ebae90 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -11,7 +11,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/core": "workspace:*", + "@near-js/client-core": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "js-sha256": "^0.9.0", diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index 4fdffa80d..cf0e0cc02 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/core'; +import { PublicKey } from '@near-js/client-core'; import BN from 'bn.js'; import { diff --git a/packages/transactions/src/actions.ts b/packages/transactions/src/actions.ts index fe423ede9..3b46a86fa 100644 --- a/packages/transactions/src/actions.ts +++ b/packages/transactions/src/actions.ts @@ -1,4 +1,4 @@ -import { Assignable, PublicKey } from '@near-js/core'; +import { Assignable, PublicKey } from '@near-js/client-core'; import BN from 'bn.js'; abstract class Enum { diff --git a/packages/transactions/src/create_transaction.ts b/packages/transactions/src/create_transaction.ts index 833108a38..9bc535562 100644 --- a/packages/transactions/src/create_transaction.ts +++ b/packages/transactions/src/create_transaction.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/core'; +import { PublicKey } from '@near-js/client-core'; import BN from 'bn.js'; import { Action } from './actions'; diff --git a/packages/transactions/src/schema.ts b/packages/transactions/src/schema.ts index 4bc3f05e9..42c828417 100644 --- a/packages/transactions/src/schema.ts +++ b/packages/transactions/src/schema.ts @@ -1,4 +1,4 @@ -import { Assignable, KeyType, PublicKey } from '@near-js/core'; +import { Assignable, KeyType, PublicKey } from '@near-js/client-core'; import BN from 'bn.js'; import { deserialize, serialize } from 'borsh'; diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts index 24d2ec2a9..e3d3becd6 100644 --- a/packages/transactions/src/sign.ts +++ b/packages/transactions/src/sign.ts @@ -1,4 +1,4 @@ -import { Signer } from '@near-js/core'; +import { Signer } from '@near-js/client-core'; import sha256 from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6fd887230..d25081d04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,17 +30,7 @@ importers: turbo: 1.6.3 typescript: 4.9.4 - packages/cookbook: - specifiers: - chalk: ^4.1.1 - homedir: ^0.6.0 - near-api-js: workspace:* - dependencies: - chalk: 4.1.2 - homedir: 0.6.0 - near-api-js: link:../near-api-js - - packages/core: + packages/client-core: specifiers: '@types/node': ^18.7.14 bn.js: 5.2.1 @@ -61,9 +51,19 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 + packages/cookbook: + specifiers: + chalk: ^4.1.1 + homedir: ^0.6.0 + near-api-js: workspace:* + dependencies: + chalk: 4.1.2 + homedir: 0.6.0 + near-api-js: link:../near-api-js + packages/near-api-js: specifiers: - '@near-js/core': workspace:* + '@near-js/client-core': workspace:* '@near-js/providers': workspace:* '@near-js/transactions': workspace:* '@types/bn.js': ^5.1.0 @@ -97,7 +97,7 @@ importers: tweetnacl: ^1.0.1 uglifyify: ^5.0.1 dependencies: - '@near-js/core': link:../core + '@near-js/client-core': link:../client-core '@near-js/providers': link:../providers '@near-js/transactions': link:../transactions ajv: 8.11.2 @@ -134,7 +134,7 @@ importers: packages/providers: specifiers: - '@near-js/core': workspace:* + '@near-js/client-core': workspace:* '@near-js/transactions': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 @@ -146,7 +146,7 @@ importers: ts-jest: ^26.5.6 tweetnacl: ^1.0.1 dependencies: - '@near-js/core': link:../core + '@near-js/client-core': link:../client-core '@near-js/transactions': link:../transactions bn.js: 5.2.1 borsh: 0.7.0 @@ -162,7 +162,7 @@ importers: packages/transactions: specifiers: - '@near-js/core': workspace:* + '@near-js/client-core': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -170,7 +170,7 @@ importers: mustache: ^4.0.0 tweetnacl: ^1.0.1 dependencies: - '@near-js/core': link:../core + '@near-js/client-core': link:../client-core bn.js: 5.2.1 borsh: 0.7.0 js-sha256: 0.9.0 From 4cdfe9f54c1104101c494a92e768f430a51b8078 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 15:58:20 -0800 Subject: [PATCH 24/84] refactor: client-node package with UnencryptedFileSystemKeyStore --- packages/client-node/package.json | 20 ++ packages/client-node/src/index.ts | 1 + packages/client-node/src/key_store/index.ts | 1 + .../unencrypted_file_system_keystore.ts | 167 ++++++++++++++++ packages/client-node/tsconfig.json | 28 +++ packages/near-api-js/package.json | 1 + packages/near-api-js/src/connect.ts | 2 +- .../unencrypted_file_system_keystore.ts | 179 +----------------- pnpm-lock.yaml | 11 ++ 9 files changed, 231 insertions(+), 179 deletions(-) create mode 100644 packages/client-node/package.json create mode 100644 packages/client-node/src/index.ts create mode 100644 packages/client-node/src/key_store/index.ts create mode 100644 packages/client-node/src/key_store/unencrypted_file_system_keystore.ts create mode 100644 packages/client-node/tsconfig.json diff --git a/packages/client-node/package.json b/packages/client-node/package.json new file mode 100644 index 000000000..36b2664d3 --- /dev/null +++ b/packages/client-node/package.json @@ -0,0 +1,20 @@ +{ + "name": "@near-js/client-node", + "version": "0.0.1", + "description": "Dependencies for the NEAR API JavaScript client in NodeJS", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/client-core": "workspace:*" + }, + "devDependencies": { + "@types/node": "^18.7.14" + } +} diff --git a/packages/client-node/src/index.ts b/packages/client-node/src/index.ts new file mode 100644 index 000000000..9998b34a0 --- /dev/null +++ b/packages/client-node/src/index.ts @@ -0,0 +1 @@ +export * from './key_store'; diff --git a/packages/client-node/src/key_store/index.ts b/packages/client-node/src/key_store/index.ts new file mode 100644 index 000000000..92da05d1f --- /dev/null +++ b/packages/client-node/src/key_store/index.ts @@ -0,0 +1 @@ +export { readKeyFile, UnencryptedFileSystemKeyStore } from './unencrypted_file_system_keystore'; diff --git a/packages/client-node/src/key_store/unencrypted_file_system_keystore.ts b/packages/client-node/src/key_store/unencrypted_file_system_keystore.ts new file mode 100644 index 000000000..3b3c0cce3 --- /dev/null +++ b/packages/client-node/src/key_store/unencrypted_file_system_keystore.ts @@ -0,0 +1,167 @@ +import { KeyPair, KeyStore } from '@near-js/client-core'; +import fs from 'fs'; +import path from 'path'; +import { promisify } from 'util'; + +const exists = promisify(fs.exists); +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); +const unlink = promisify(fs.unlink); +const readdir = promisify(fs.readdir); +const mkdir = promisify(fs.mkdir); + +/** + * Format of the account stored on disk. + */ +interface AccountInfo { + account_id: string; + public_key: string; + private_key: string; +} + +/** @hidden */ +async function loadJsonFile(filename: string): Promise { + const content = await readFile(filename); + return JSON.parse(content.toString()); +} + +async function ensureDir(dir: string): Promise { + try { + await mkdir(dir, { recursive: true }); + } catch (err) { + if (err.code !== 'EEXIST') { throw err; } + } +} + +/** @hidden */ +export async function readKeyFile(filename: string): Promise<[string, KeyPair]> { + const accountInfo = await loadJsonFile(filename); + // The private key might be in private_key or secret_key field. + let privateKey = accountInfo.private_key; + if (!privateKey && accountInfo.secret_key) { + privateKey = accountInfo.secret_key; + } + return [accountInfo.account_id, KeyPair.fromString(privateKey)]; +} + +/** + * This class is used to store keys on the file system. + * + * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) + * @example + * ```js + * const { homedir } = require('os'); + * const { connect, keyStores } = require('near-api-js'); + * + * const keyStore = new keyStores.UnencryptedFileSystemKeyStore(`${homedir()}/.near-credentials`); + * const config = { + * keyStore, // instance of UnencryptedFileSystemKeyStore + * networkId: 'testnet', + * nodeUrl: 'https://rpc.testnet.near.org', + * walletUrl: 'https://wallet.testnet.near.org', + * helperUrl: 'https://helper.testnet.near.org', + * explorerUrl: 'https://explorer.testnet.near.org' + * }; + * + * // inside an async function + * const near = await connect(config) + * ``` + */ +export class UnencryptedFileSystemKeyStore extends KeyStore { + /** @hidden */ + readonly keyDir: string; + + /** + * @param keyDir base directory for key storage. Keys will be stored in `keyDir/networkId/accountId.json` + */ + constructor(keyDir: string) { + super(); + this.keyDir = path.resolve(keyDir); + } + + /** + * Store a {@link utils/key_pair!KeyPair} in an unencrypted file + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @param keyPair The key pair to store in local storage + */ + async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { + await ensureDir(`${this.keyDir}/${networkId}`); + const content: AccountInfo = { account_id: accountId, public_key: keyPair.getPublicKey().toString(), private_key: keyPair.toString() }; + await writeFile(this.getKeyFilePath(networkId, accountId), JSON.stringify(content), { mode: 0o600 }); + } + + /** + * Gets a {@link utils/key_pair!KeyPair} from an unencrypted file + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @returns {Promise} + */ + async getKey(networkId: string, accountId: string): Promise { + // Find key / account id. + if (!await exists(this.getKeyFilePath(networkId, accountId))) { + return null; + } + const accountKeyPair = await readKeyFile(this.getKeyFilePath(networkId, accountId)); + return accountKeyPair[1]; + } + + /** + * Deletes an unencrypted file holding a {@link utils/key_pair!KeyPair} + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + */ + async removeKey(networkId: string, accountId: string): Promise { + if (await exists(this.getKeyFilePath(networkId, accountId))) { + await unlink(this.getKeyFilePath(networkId, accountId)); + } + } + + /** + * Deletes all unencrypted files from the `keyDir` path. + */ + async clear(): Promise { + for (const network of await this.getNetworks()) { + for (const account of await this.getAccounts(network)) { + await this.removeKey(network, account); + } + } + } + + /** @hidden */ + private getKeyFilePath(networkId: string, accountId: string): string { + return `${this.keyDir}/${networkId}/${accountId}.json`; + } + + /** + * Get the network(s) from files in `keyDir` + * @returns {Promise} + */ + async getNetworks(): Promise { + const files: string[] = await readdir(this.keyDir); + const result = new Array(); + files.forEach((item) => { + result.push(item); + }); + return result; + } + + /** + * Gets the account(s) files in `keyDir/networkId` + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + async getAccounts(networkId: string): Promise { + if (!await exists(`${this.keyDir}/${networkId}`)) { + return []; + } + const files: string[] = await readdir(`${this.keyDir}/${networkId}`); + return files + .filter(file => file.endsWith('.json')) + .map(file => file.replace(/.json$/, '')); + } + + /** @hidden */ + toString(): string { + return `UnencryptedFileSystemKeyStore(${this.keyDir})`; + } +} diff --git a/packages/client-node/tsconfig.json b/packages/client-node/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/client-node/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index c68738e1c..48da847f4 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,6 +12,7 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/client-core": "workspace:*", + "@near-js/client-node": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", "ajv": "^8.11.2", diff --git a/packages/near-api-js/src/connect.ts b/packages/near-api-js/src/connect.ts index 8b95c54d8..0b8fc9932 100644 --- a/packages/near-api-js/src/connect.ts +++ b/packages/near-api-js/src/connect.ts @@ -21,7 +21,7 @@ * ``` * @module connect */ -import { readKeyFile } from './key_stores/unencrypted_file_system_keystore'; +import { readKeyFile } from '@near-js/client-node'; import { InMemoryKeyStore, MergeKeyStore } from './key_stores'; import { Near, NearConfig } from './near'; import fetch from './utils/setup-node-fetch'; diff --git a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts index 2ac80cd0b..a79f4ff0d 100644 --- a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts +++ b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts @@ -1,178 +1 @@ -import fs from 'fs'; -import path from 'path'; -import { promisify as _promisify } from 'util'; - -import { KeyPair } from '../utils/key_pair'; -import { KeyStore } from './keystore'; - -const promisify = (fn: any) => { - if (!fn) { - return () => { - throw new Error('Trying to use unimplemented function. `fs` module not available in web build?'); - }; - } - return _promisify(fn); -}; - -const exists = promisify(fs.exists); -const readFile = promisify(fs.readFile); -const writeFile = promisify(fs.writeFile); -const unlink = promisify(fs.unlink); -const readdir = promisify(fs.readdir); -const mkdir = promisify(fs.mkdir); - -/** - * Format of the account stored on disk. - */ -interface AccountInfo { - account_id: string; - public_key: string; - private_key: string; -} - -/** @hidden */ -export async function loadJsonFile(filename: string): Promise { - const content = await readFile(filename); - return JSON.parse(content.toString()); -} - -async function ensureDir(dir: string): Promise { - try { - await mkdir(dir, { recursive: true }); - } catch (err) { - if (err.code !== 'EEXIST') { throw err; } - } -} - -/** @hidden */ -export async function readKeyFile(filename: string): Promise<[string, KeyPair]> { - const accountInfo = await loadJsonFile(filename); - // The private key might be in private_key or secret_key field. - let privateKey = accountInfo.private_key; - if (!privateKey && accountInfo.secret_key) { - privateKey = accountInfo.secret_key; - } - return [accountInfo.account_id, KeyPair.fromString(privateKey)]; -} - -/** - * This class is used to store keys on the file system. - * - * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) - * @example - * ```js - * const { homedir } = require('os'); - * const { connect, keyStores } = require('near-api-js'); - * - * const keyStore = new keyStores.UnencryptedFileSystemKeyStore(`${homedir()}/.near-credentials`); - * const config = { - * keyStore, // instance of UnencryptedFileSystemKeyStore - * networkId: 'testnet', - * nodeUrl: 'https://rpc.testnet.near.org', - * walletUrl: 'https://wallet.testnet.near.org', - * helperUrl: 'https://helper.testnet.near.org', - * explorerUrl: 'https://explorer.testnet.near.org' - * }; - * - * // inside an async function - * const near = await connect(config) - * ``` - */ -export class UnencryptedFileSystemKeyStore extends KeyStore { - /** @hidden */ - readonly keyDir: string; - - /** - * @param keyDir base directory for key storage. Keys will be stored in `keyDir/networkId/accountId.json` - */ - constructor(keyDir: string) { - super(); - this.keyDir = path.resolve(keyDir); - } - - /** - * Store a {@link utils/key_pair!KeyPair} in an unencrypted file - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @param keyPair The key pair to store in local storage - */ - async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { - await ensureDir(`${this.keyDir}/${networkId}`); - const content: AccountInfo = { account_id: accountId, public_key: keyPair.getPublicKey().toString(), private_key: keyPair.toString() }; - await writeFile(this.getKeyFilePath(networkId, accountId), JSON.stringify(content), { mode: 0o600 }); - } - - /** - * Gets a {@link utils/key_pair!KeyPair} from an unencrypted file - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @returns {Promise} - */ - async getKey(networkId: string, accountId: string): Promise { - // Find key / account id. - if (!await exists(this.getKeyFilePath(networkId, accountId))) { - return null; - } - const accountKeyPair = await readKeyFile(this.getKeyFilePath(networkId, accountId)); - return accountKeyPair[1]; - } - - /** - * Deletes an unencrypted file holding a {@link utils/key_pair!KeyPair} - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - */ - async removeKey(networkId: string, accountId: string): Promise { - if (await exists(this.getKeyFilePath(networkId, accountId))) { - await unlink(this.getKeyFilePath(networkId, accountId)); - } - } - - /** - * Deletes all unencrypted files from the `keyDir` path. - */ - async clear(): Promise { - for (const network of await this.getNetworks()) { - for (const account of await this.getAccounts(network)) { - await this.removeKey(network, account); - } - } - } - - /** @hidden */ - private getKeyFilePath(networkId: string, accountId: string): string { - return `${this.keyDir}/${networkId}/${accountId}.json`; - } - - /** - * Get the network(s) from files in `keyDir` - * @returns {Promise} - */ - async getNetworks(): Promise { - const files: string[] = await readdir(this.keyDir); - const result = new Array(); - files.forEach((item) => { - result.push(item); - }); - return result; - } - - /** - * Gets the account(s) files in `keyDir/networkId` - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - async getAccounts(networkId: string): Promise { - if (!await exists(`${this.keyDir}/${networkId}`)) { - return []; - } - const files: string[] = await readdir(`${this.keyDir}/${networkId}`); - return files - .filter(file => file.endsWith('.json')) - .map(file => file.replace(/.json$/, '')); - } - - /** @hidden */ - toString(): string { - return `UnencryptedFileSystemKeyStore(${this.keyDir})`; - } -} +export { UnencryptedFileSystemKeyStore } from '@near-js/client-node'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d25081d04..e87a095c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,15 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 + packages/client-node: + specifiers: + '@near-js/client-core': workspace:* + '@types/node': ^18.7.14 + dependencies: + '@near-js/client-core': link:../client-core + devDependencies: + '@types/node': 18.7.14 + packages/cookbook: specifiers: chalk: ^4.1.1 @@ -64,6 +73,7 @@ importers: packages/near-api-js: specifiers: '@near-js/client-core': workspace:* + '@near-js/client-node': workspace:* '@near-js/providers': workspace:* '@near-js/transactions': workspace:* '@types/bn.js': ^5.1.0 @@ -98,6 +108,7 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/client-core': link:../client-core + '@near-js/client-node': link:../client-node '@near-js/providers': link:../providers '@near-js/transactions': link:../transactions ajv: 8.11.2 From 9c76a1daab2101c8106e3e7678a9608a1d46406c Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 16:12:23 -0800 Subject: [PATCH 25/84] refactor: client-browser package with localstorage keystore, polyfills, and browserify bundling --- packages/client-browser/browser-exports.js | 2 + packages/client-browser/package.json | 23 +++ packages/client-browser/src/index.ts | 3 + .../browser_local_storage_key_store.ts | 136 +++++++++++++++++ .../client-browser/src/key_store/index.ts | 1 + packages/client-browser/tsconfig.json | 29 ++++ packages/near-api-js/package.json | 1 + .../browser_local_storage_key_store.ts | 138 +----------------- pnpm-lock.yaml | 15 ++ 9 files changed, 211 insertions(+), 137 deletions(-) create mode 100644 packages/client-browser/browser-exports.js create mode 100644 packages/client-browser/package.json create mode 100644 packages/client-browser/src/index.ts create mode 100644 packages/client-browser/src/key_store/browser_local_storage_key_store.ts create mode 100644 packages/client-browser/src/key_store/index.ts create mode 100644 packages/client-browser/tsconfig.json diff --git a/packages/client-browser/browser-exports.js b/packages/client-browser/browser-exports.js new file mode 100644 index 000000000..15c716a7b --- /dev/null +++ b/packages/client-browser/browser-exports.js @@ -0,0 +1,2 @@ +window.nearApi = require('./lib'); +window.Buffer = Buffer; diff --git a/packages/client-browser/package.json b/packages/client-browser/package.json new file mode 100644 index 000000000..9ac17fe4d --- /dev/null +++ b/packages/client-browser/package.json @@ -0,0 +1,23 @@ +{ + "name": "@near-js/client-browser", + "version": "0.0.1", + "description": "Dependencies for the NEAR API JavaScript client in the browser", + "main": "lib/index.js", + "scripts": { + "browserify": "browserify browser-exports.js -o dist/near-api-js.js && browserify browser-exports.js -i node-fetch -g uglifyify -o dist/near-api-js.min.js", + "build": "pnpm compile && pnpm browserify", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/client-core": "workspace:*", + "error-polyfill": "^0.1.3" + }, + "devDependencies": { + "browserify": "^16.2.3", + "uglifyify": "^5.0.1" + } +} diff --git a/packages/client-browser/src/index.ts b/packages/client-browser/src/index.ts new file mode 100644 index 000000000..3a688749b --- /dev/null +++ b/packages/client-browser/src/index.ts @@ -0,0 +1,3 @@ +import 'error-polyfill'; + +export * from './key_store'; diff --git a/packages/client-browser/src/key_store/browser_local_storage_key_store.ts b/packages/client-browser/src/key_store/browser_local_storage_key_store.ts new file mode 100644 index 000000000..8aa0f574b --- /dev/null +++ b/packages/client-browser/src/key_store/browser_local_storage_key_store.ts @@ -0,0 +1,136 @@ +import { KeyPair, KeyStore } from '@near-js/client-core'; + +const LOCAL_STORAGE_KEY_PREFIX = 'near-api-js:keystore:'; + +/** + * This class is used to store keys in the browsers local storage. + * + * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) + * @example + * ```js + * import { connect, keyStores } from 'near-api-js'; + * + * const keyStore = new keyStores.BrowserLocalStorageKeyStore(); + * const config = { + * keyStore, // instance of BrowserLocalStorageKeyStore + * networkId: 'testnet', + * nodeUrl: 'https://rpc.testnet.near.org', + * walletUrl: 'https://wallet.testnet.near.org', + * helperUrl: 'https://helper.testnet.near.org', + * explorerUrl: 'https://explorer.testnet.near.org' + * }; + * + * // inside an async function + * const near = await connect(config) + * ``` + */ +export class BrowserLocalStorageKeyStore extends KeyStore { + /** @hidden */ + private localStorage: any; + /** @hidden */ + private prefix: string; + + /** + * @param localStorage defaults to window.localStorage + * @param prefix defaults to `near-api-js:keystore:` + */ + constructor(localStorage: any = window.localStorage, prefix = LOCAL_STORAGE_KEY_PREFIX) { + super(); + this.localStorage = localStorage; + this.prefix = prefix; + } + + /** + * Stores a {@link utils/key_pair!KeyPair} in local storage. + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @param keyPair The key pair to store in local storage + */ + async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { + this.localStorage.setItem(this.storageKeyForSecretKey(networkId, accountId), keyPair.toString()); + } + + /** + * Gets a {@link utils/key_pair!KeyPair} from local storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + * @returns {Promise} + */ + async getKey(networkId: string, accountId: string): Promise { + const value = this.localStorage.getItem(this.storageKeyForSecretKey(networkId, accountId)); + if (!value) { + return null; + } + return KeyPair.fromString(value); + } + + /** + * Removes a {@link utils/key_pair!KeyPair} from local storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the key pair + */ + async removeKey(networkId: string, accountId: string): Promise { + this.localStorage.removeItem(this.storageKeyForSecretKey(networkId, accountId)); + } + + /** + * Removes all items that start with `prefix` from local storage + */ + async clear(): Promise { + for (const key of this.storageKeys()) { + if (key.startsWith(this.prefix)) { + this.localStorage.removeItem(key); + } + } + } + + /** + * Get the network(s) from local storage + * @returns {Promise} + */ + async getNetworks(): Promise { + const result = new Set(); + for (const key of this.storageKeys()) { + if (key.startsWith(this.prefix)) { + const parts = key.substring(this.prefix.length).split(':'); + result.add(parts[1]); + } + } + return Array.from(result.values()); + } + + /** + * Gets the account(s) from local storage + * @param networkId The targeted network. (ex. default, betanet, etc…) + */ + async getAccounts(networkId: string): Promise { + const result = new Array(); + for (const key of this.storageKeys()) { + if (key.startsWith(this.prefix)) { + const parts = key.substring(this.prefix.length).split(':'); + if (parts[1] === networkId) { + result.push(parts[0]); + } + } + } + return result; + } + + /** + * @hidden + * Helper function to retrieve a local storage key + * @param networkId The targeted network. (ex. default, betanet, etc…) + * @param accountId The NEAR account tied to the storage keythat's sought + * @returns {string} An example might be: `near-api-js:keystore:near-friend:default` + */ + private storageKeyForSecretKey(networkId: string, accountId: string): string { + return `${this.prefix}${accountId}:${networkId}`; + } + + /** @hidden */ + private *storageKeys(): IterableIterator { + for (let i = 0; i < this.localStorage.length; i++) { + yield this.localStorage.key(i); + } + } +} diff --git a/packages/client-browser/src/key_store/index.ts b/packages/client-browser/src/key_store/index.ts new file mode 100644 index 000000000..2efe7c05b --- /dev/null +++ b/packages/client-browser/src/key_store/index.ts @@ -0,0 +1 @@ +export { BrowserLocalStorageKeyStore } from './browser_local_storage_key_store'; diff --git a/packages/client-browser/tsconfig.json b/packages/client-browser/tsconfig.json new file mode 100644 index 000000000..a77f4f8d3 --- /dev/null +++ b/packages/client-browser/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext", + "dom" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 48da847f4..7b3990055 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -11,6 +11,7 @@ "browser": "lib/browser-index.js", "types": "lib/index.d.ts", "dependencies": { + "@near-js/client-browser": "workspace:*", "@near-js/client-core": "workspace:*", "@near-js/client-node": "workspace:*", "@near-js/providers": "workspace:*", diff --git a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts index 03ba676f6..aaa83c050 100644 --- a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts +++ b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts @@ -1,137 +1 @@ -import { KeyStore } from './keystore'; -import { KeyPair } from '../utils/key_pair'; - -const LOCAL_STORAGE_KEY_PREFIX = 'near-api-js:keystore:'; - -/** - * This class is used to store keys in the browsers local storage. - * - * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store](https://docs.near.org/docs/develop/front-end/naj-quick-reference#key-store) - * @example - * ```js - * import { connect, keyStores } from 'near-api-js'; - * - * const keyStore = new keyStores.BrowserLocalStorageKeyStore(); - * const config = { - * keyStore, // instance of BrowserLocalStorageKeyStore - * networkId: 'testnet', - * nodeUrl: 'https://rpc.testnet.near.org', - * walletUrl: 'https://wallet.testnet.near.org', - * helperUrl: 'https://helper.testnet.near.org', - * explorerUrl: 'https://explorer.testnet.near.org' - * }; - * - * // inside an async function - * const near = await connect(config) - * ``` - */ -export class BrowserLocalStorageKeyStore extends KeyStore { - /** @hidden */ - private localStorage: any; - /** @hidden */ - private prefix: string; - - /** - * @param localStorage defaults to window.localStorage - * @param prefix defaults to `near-api-js:keystore:` - */ - constructor(localStorage: any = window.localStorage, prefix = LOCAL_STORAGE_KEY_PREFIX) { - super(); - this.localStorage = localStorage; - this.prefix = prefix; - } - - /** - * Stores a {@link utils/key_pair!KeyPair} in local storage. - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @param keyPair The key pair to store in local storage - */ - async setKey(networkId: string, accountId: string, keyPair: KeyPair): Promise { - this.localStorage.setItem(this.storageKeyForSecretKey(networkId, accountId), keyPair.toString()); - } - - /** - * Gets a {@link utils/key_pair!KeyPair} from local storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - * @returns {Promise} - */ - async getKey(networkId: string, accountId: string): Promise { - const value = this.localStorage.getItem(this.storageKeyForSecretKey(networkId, accountId)); - if (!value) { - return null; - } - return KeyPair.fromString(value); - } - - /** - * Removes a {@link utils/key_pair!KeyPair} from local storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the key pair - */ - async removeKey(networkId: string, accountId: string): Promise { - this.localStorage.removeItem(this.storageKeyForSecretKey(networkId, accountId)); - } - - /** - * Removes all items that start with `prefix` from local storage - */ - async clear(): Promise { - for (const key of this.storageKeys()) { - if (key.startsWith(this.prefix)) { - this.localStorage.removeItem(key); - } - } - } - - /** - * Get the network(s) from local storage - * @returns {Promise} - */ - async getNetworks(): Promise { - const result = new Set(); - for (const key of this.storageKeys()) { - if (key.startsWith(this.prefix)) { - const parts = key.substring(this.prefix.length).split(':'); - result.add(parts[1]); - } - } - return Array.from(result.values()); - } - - /** - * Gets the account(s) from local storage - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - async getAccounts(networkId: string): Promise { - const result = new Array(); - for (const key of this.storageKeys()) { - if (key.startsWith(this.prefix)) { - const parts = key.substring(this.prefix.length).split(':'); - if (parts[1] === networkId) { - result.push(parts[0]); - } - } - } - return result; - } - - /** - * @hidden - * Helper function to retrieve a local storage key - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account tied to the storage keythat's sought - * @returns {string} An example might be: `near-api-js:keystore:near-friend:default` - */ - private storageKeyForSecretKey(networkId: string, accountId: string): string { - return `${this.prefix}${accountId}:${networkId}`; - } - - /** @hidden */ - private *storageKeys(): IterableIterator { - for (let i = 0; i < this.localStorage.length; i++) { - yield this.localStorage.key(i); - } - } -} +export { BrowserLocalStorageKeyStore } from '@near-js/client-browser'; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e87a095c6..4d3485234 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,19 @@ importers: turbo: 1.6.3 typescript: 4.9.4 + packages/client-browser: + specifiers: + '@near-js/client-core': workspace:* + browserify: ^16.2.3 + error-polyfill: ^0.1.3 + uglifyify: ^5.0.1 + dependencies: + '@near-js/client-core': link:../client-core + error-polyfill: 0.1.3 + devDependencies: + browserify: 16.5.2 + uglifyify: 5.0.2 + packages/client-core: specifiers: '@types/node': ^18.7.14 @@ -72,6 +85,7 @@ importers: packages/near-api-js: specifiers: + '@near-js/client-browser': workspace:* '@near-js/client-core': workspace:* '@near-js/client-node': workspace:* '@near-js/providers': workspace:* @@ -107,6 +121,7 @@ importers: tweetnacl: ^1.0.1 uglifyify: ^5.0.1 dependencies: + '@near-js/client-browser': link:../client-browser '@near-js/client-core': link:../client-core '@near-js/client-node': link:../client-node '@near-js/providers': link:../providers From 979f7451570956e54383443884cdfa9b1cf007ae Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 18 Nov 2022 16:26:47 -0800 Subject: [PATCH 26/84] refactor: fix duplicate export --- packages/client-core/src/key_pair/key_pair.ts | 58 +------------------ 1 file changed, 1 insertion(+), 57 deletions(-) diff --git a/packages/client-core/src/key_pair/key_pair.ts b/packages/client-core/src/key_pair/key_pair.ts index a1e520ea5..27a7e6e8b 100644 --- a/packages/client-core/src/key_pair/key_pair.ts +++ b/packages/client-core/src/key_pair/key_pair.ts @@ -1,7 +1,4 @@ -import { baseEncode, baseDecode } from 'borsh'; -import nacl from 'tweetnacl'; - -import { KeyType } from './constants'; +import { KeyPairEd25519 } from './key_pair_ed25519'; import { PublicKey } from './public_key'; export interface Signature { @@ -40,56 +37,3 @@ export abstract class KeyPair { } } } - -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -export class KeyPairEd25519 extends KeyPair { - readonly publicKey: PublicKey; - readonly secretKey: string; - - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey: string) { - super(); - const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); - this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); - this.secretKey = secretKey; - } - - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom() { - const newKeyPair = nacl.sign.keyPair(); - return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); - } - - sign(message: Uint8Array): Signature { - const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); - return { signature, publicKey: this.publicKey }; - } - - verify(message: Uint8Array, signature: Uint8Array): boolean { - return this.publicKey.verify(message, signature); - } - - toString(): string { - return `ed25519:${this.secretKey}`; - } - - getPublicKey(): PublicKey { - return this.publicKey; - } -} From 3d6df96b1ed58a7aedd4e2112f311e3eeffc10f7 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 1 Dec 2022 16:26:36 -0800 Subject: [PATCH 27/84] feat: accounts package --- packages/accounts/jest.config.js | 5 + packages/accounts/package.json | 26 + packages/accounts/src/account.ts | 656 ++++++++++++++++++ packages/accounts/src/account_multisig.ts | 501 ++++++++++++++ packages/accounts/src/connection.ts | 56 ++ packages/accounts/src/index.ts | 22 + packages/accounts/test/.eslintrc.yml | 7 + packages/accounts/tsconfig.json | 28 + packages/client-core/src/constants.ts | 9 + packages/client-core/src/index.ts | 2 + packages/client-core/src/logging.ts | 69 ++ packages/near-api-js/package.json | 1 + packages/near-api-js/src/account.ts | 661 +------------------ packages/near-api-js/src/account_multisig.ts | 515 +-------------- packages/near-api-js/src/connection.ts | 57 +- packages/near-api-js/src/constants.ts | 10 +- packages/near-api-js/src/utils/logging.ts | 70 +- pnpm-lock.yaml | 24 + 18 files changed, 1430 insertions(+), 1289 deletions(-) create mode 100644 packages/accounts/jest.config.js create mode 100644 packages/accounts/package.json create mode 100644 packages/accounts/src/account.ts create mode 100644 packages/accounts/src/account_multisig.ts create mode 100644 packages/accounts/src/connection.ts create mode 100644 packages/accounts/src/index.ts create mode 100644 packages/accounts/test/.eslintrc.yml create mode 100644 packages/accounts/tsconfig.json create mode 100644 packages/client-core/src/constants.ts create mode 100644 packages/client-core/src/logging.ts diff --git a/packages/accounts/jest.config.js b/packages/accounts/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/accounts/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/accounts/package.json b/packages/accounts/package.json new file mode 100644 index 000000000..d7db23248 --- /dev/null +++ b/packages/accounts/package.json @@ -0,0 +1,26 @@ +{ + "name": "@near-js/accounts", + "version": "0.0.1", + "description": "Classes encapsulating account-specific functionality", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/client-core": "workspace:*", + "@near-js/providers": "workspace:*", + "@near-js/transactions": "workspace:*", + "bn.js": "5.2.1", + "borsh": "^0.7.0" + }, + "devDependencies": { + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" + } +} diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts new file mode 100644 index 000000000..f6315c4b4 --- /dev/null +++ b/packages/accounts/src/account.ts @@ -0,0 +1,656 @@ +import BN from 'bn.js'; + +import { exponentialBackoff } from '@near-js/providers'; +import { + transfer, + createAccount, + signTransaction, + deployContract, + addKey, + functionCall, + fullAccessKey, + functionCallAccessKey, + deleteKey, + stake, + deleteAccount, + Action, + SignedTransaction, + stringifyJsonOrBytes +} from '@near-js/transactions'; +import { + PublicKey, + logWarning, + PositionalArgsError, + parseResultError, + DEFAULT_FUNCTION_CALL_GAS, + printTxOutcomeLogs, + printTxOutcomeLogsAndFailures, + FinalExecutionOutcome, + TypedError, + ErrorContext, + ViewStateResult, + AccountView, + AccessKeyView, + AccessKeyViewRaw, + CodeResult, + AccessKeyList, + AccessKeyInfoView, + FunctionCallPermissionView, + BlockReference, +} from '@near-js/client-core'; +import { baseDecode, baseEncode } from 'borsh'; + +import { Connection } from './connection'; + +// Default number of retries with different nonce before giving up on a transaction. +const TX_NONCE_RETRY_NUMBER = 12; + +// Default wait until next retry in millis. +const TX_NONCE_RETRY_WAIT = 500; + +// Exponential back off for waiting to retry. +const TX_NONCE_RETRY_WAIT_BACKOFF = 1.5; + +export interface AccountBalance { + total: string; + stateStaked: string; + staked: string; + available: string; +} + +export interface AccountAuthorizedApp { + contractId: string; + amount: string; + publicKey: string; +} + +/** + * Options used to initiate sining and sending transactions + */ +export interface SignAndSendTransactionOptions { + receiverId: string; + actions: Action[]; + /** + * Metadata to send the NEAR Wallet if using it to sign transactions. + * @see {@link RequestSignTransactionsOptions} + */ + walletMeta?: string; + /** + * Callback url to send the NEAR Wallet if using it to sign transactions. + * @see {@link RequestSignTransactionsOptions} + */ + walletCallbackUrl?: string; + returnError?: boolean; +} + +/** + * Options used to initiate a function call (especially a change function call) + * @see {@link account!Account#viewFunction} to initiate a view function call + */ +export interface FunctionCallOptions { + /** The NEAR account id where the contract is deployed */ + contractId: string; + /** The name of the method to invoke */ + methodName: string; + /** + * named arguments to pass the method `{ messageText: 'my message' }` + */ + args?: object; + /** max amount of gas that method call can use */ + gas?: BN; + /** amount of NEAR (in yoctoNEAR) to send together with the call */ + attachedDeposit?: BN; + /** + * Convert input arguments into bytes array. + */ + stringify?: (input: any) => Buffer; + /** + * Is contract from JS SDK, automatically encodes args from JS SDK to binary. + */ + jsContract?: boolean; +} + +export interface ChangeFunctionCallOptions extends FunctionCallOptions { + /** + * Metadata to send the NEAR Wallet if using it to sign transactions. + * @see {@link RequestSignTransactionsOptions} + */ + walletMeta?: string; + /** + * Callback url to send the NEAR Wallet if using it to sign transactions. + * @see {@link RequestSignTransactionsOptions} + */ + walletCallbackUrl?: string; +} +export interface ViewFunctionCallOptions extends FunctionCallOptions { + parse?: (response: Uint8Array) => any; + blockQuery?: BlockReference; +} + +interface StakedBalance { + validatorId: string; + amount?: string; + error?: string; +} + +interface ActiveDelegatedStakeBalance { + stakedValidators: StakedBalance[]; + failedValidators: StakedBalance[]; + total: BN | string; +} + +function parseJsonFromRawResponse(response: Uint8Array): any { + return JSON.parse(Buffer.from(response).toString()); +} + +function bytesJsonStringify(input: any): Buffer { + return Buffer.from(JSON.stringify(input)); +} + +/** + * This class provides common account related RPC calls including signing transactions with a {@link utils/key_pair!KeyPair}. + * + * @hint Use {@link walletAccount!WalletConnection} in the browser to redirect to [NEAR Wallet](https://wallet.near.org/) for Account/key management using the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. + * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) + * @see [Account Spec](https://nomicon.io/DataStructures/Account.html) + */ +export class Account { + readonly connection: Connection; + readonly accountId: string; + + constructor(connection: Connection, accountId: string) { + this.connection = connection; + this.accountId = accountId; + } + + /** + * Returns basic NEAR account information via the `view_account` RPC query method + * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + */ + async state(): Promise { + return this.connection.provider.query({ + request_type: 'view_account', + account_id: this.accountId, + finality: 'optimistic' + }); + } + + /** + * Create a signed transaction which can be broadcast to the network + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the transaction + * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} + */ + protected async signTransaction(receiverId: string, actions: Action[]): Promise<[Uint8Array, SignedTransaction]> { + const accessKeyInfo = await this.findAccessKey(receiverId, actions); + if (!accessKeyInfo) { + throw new TypedError(`Can not sign transactions for account ${this.accountId} on network ${this.connection.networkId}, no matching key pair exists for this account`, 'KeyNotFound'); + } + const { accessKey } = accessKeyInfo; + + const block = await this.connection.provider.block({ finality: 'final' }); + const blockHash = block.header.hash; + + const nonce = accessKey.nonce.add(new BN(1)); + return await signTransaction( + receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId + ); + } + + /** + * Sign a transaction to preform a list of actions and broadcast it using the RPC API. + * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} + */ + async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise { + let txHash, signedTx; + // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) + const result = await exponentialBackoff(TX_NONCE_RETRY_WAIT, TX_NONCE_RETRY_NUMBER, TX_NONCE_RETRY_WAIT_BACKOFF, async () => { + [txHash, signedTx] = await this.signTransaction(receiverId, actions); + const publicKey = signedTx.transaction.publicKey; + + try { + return await this.connection.provider.sendTransaction(signedTx); + } catch (error) { + if (error.type === 'InvalidNonce') { + logWarning(`Retrying transaction ${receiverId}:${baseEncode(txHash)} with new nonce.`); + delete this.accessKeyByPublicKeyCache[publicKey.toString()]; + return null; + } + if (error.type === 'Expired') { + logWarning(`Retrying transaction ${receiverId}:${baseEncode(txHash)} due to expired block hash`); + return null; + } + + error.context = new ErrorContext(baseEncode(txHash)); + throw error; + } + }); + if (!result) { + // TODO: This should have different code actually, as means "transaction not submitted for sure" + throw new TypedError('nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.', 'RetriesExceeded'); + } + + printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result }); + + // Should be falsy if result.status.Failure is null + if (!returnError && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { + // if error data has error_message and error_type properties, we consider that node returned an error in the old format + if (result.status.Failure.error_message && result.status.Failure.error_type) { + throw new TypedError( + `Transaction ${result.transaction_outcome.id} failed. ${result.status.Failure.error_message}`, + result.status.Failure.error_type); + } else { + throw parseResultError(result); + } + } + // TODO: if Tx is Unknown or Started. + return result; + } + + /** @hidden */ + accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; + + /** + * Finds the {@link providers/provider!AccessKeyView} associated with the accounts {@link utils/key_pair!PublicKey} stored in the {@link key_stores/keystore!KeyStore}. + * + * @todo Find matching access key based on transaction (i.e. receiverId and actions) + * + * @param receiverId currently unused (see todo) + * @param actions currently unused (see todo) + * @returns `{ publicKey PublicKey; accessKey: AccessKeyView }` + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async findAccessKey(receiverId: string, actions: Action[]): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { + // TODO: Find matching access key based on transaction (i.e. receiverId and actions) + const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); + if (!publicKey) { + throw new TypedError(`no matching key pair found in ${this.connection.signer}`, 'PublicKeyNotFound'); + } + + const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()]; + if (cachedAccessKey !== undefined) { + return { publicKey, accessKey: cachedAccessKey }; + } + + try { + 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 + // the cached access key. + if (this.accessKeyByPublicKeyCache[publicKey.toString()]) { + return { publicKey, accessKey: this.accessKeyByPublicKeyCache[publicKey.toString()] }; + } + + this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; + return { publicKey, accessKey }; + } catch (e) { + if (e.type == 'AccessKeyDoesNotExist') { + return null; + } + + throw e; + } + } + + /** + * Create a new account and deploy a contract to it + * + * @param contractId NEAR account where the contract is deployed + * @param publicKey The public key to add to the created contract account + * @param data The compiled contract code + * @param amount of NEAR to transfer to the created contract account. Transfer enough to pay for storage https://docs.near.org/docs/concepts/storage-staking + */ + async createAndDeployContract(contractId: string, publicKey: string | PublicKey, data: Uint8Array, amount: BN): Promise { + const accessKey = fullAccessKey(); + await this.signAndSendTransaction({ + receiverId: contractId, + actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey), deployContract(data)] + }); + const contractAccount = new Account(this.connection, contractId); + return contractAccount; + } + + /** + * @param receiverId NEAR account receiving Ⓝ + * @param amount Amount to send in yoctoⓃ + */ + async sendMoney(receiverId: string, amount: BN): Promise { + return this.signAndSendTransaction({ + receiverId, + actions: [transfer(amount)] + }); + } + + /** + * @param newAccountId NEAR account name to be created + * @param publicKey A public key created from the masterAccount + */ + async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: BN): Promise { + const accessKey = fullAccessKey(); + return this.signAndSendTransaction({ + receiverId: newAccountId, + actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey)] + }); + } + + /** + * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted + */ + async deleteAccount(beneficiaryId: string) { + if (!process.env['NEAR_NO_LOGS']) { + console.log('Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting.'); + } + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deleteAccount(beneficiaryId)] + }); + } + + /** + * @param data The compiled contract code + */ + async deployContract(data: Uint8Array): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deployContract(data)] + }); + } + + /** @hidden */ + private encodeJSContractArgs(contractId: string, method: string, args) { + return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]); + } + + /** + * Execute function call + * @returns {Promise} + */ + async functionCall({ contractId, methodName, args = {}, gas = DEFAULT_FUNCTION_CALL_GAS, attachedDeposit, walletMeta, walletCallbackUrl, stringify, jsContract }: ChangeFunctionCallOptions): Promise { + this.validateArgs(args); + let functionCallArgs; + + if(jsContract){ + const encodedArgs = this.encodeJSContractArgs( contractId, methodName, JSON.stringify(args) ); + functionCallArgs = ['call_js_contract', encodedArgs, gas, attachedDeposit, null, true ]; + } else{ + const stringifyArg = stringify === undefined ? stringifyJsonOrBytes : stringify; + functionCallArgs = [methodName, args, gas, attachedDeposit, stringifyArg, false]; + } + + return this.signAndSendTransaction({ + receiverId: jsContract ? this.connection.jsvmAccountId: contractId, + // eslint-disable-next-line prefer-spread + actions: [functionCall.apply(void 0, functionCallArgs)], + walletMeta, + walletCallbackUrl + }); + } + + /** + * @see [https://docs.near.org/concepts/basics/accounts/access-keys](https://docs.near.org/concepts/basics/accounts/access-keys) + * @todo expand this API to support more options. + * @param publicKey A public key to be associated with the contract + * @param contractId NEAR account where the contract is deployed + * @param methodNames The method names on the contract that should be allowed to be called. Pass null for no method names and '' or [] for any method names. + * @param amount Payment in yoctoⓃ that is sent to the contract during this function call + */ + async addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: BN): Promise { + if (!methodNames) { + methodNames = []; + } + if (!Array.isArray(methodNames)) { + methodNames = [methodNames]; + } + let accessKey; + if (!contractId) { + accessKey = fullAccessKey(); + } else { + accessKey = functionCallAccessKey(contractId, methodNames, amount); + } + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [addKey(PublicKey.from(publicKey), accessKey)] + }); + } + + /** + * @param publicKey The public key to be deleted + * @returns {Promise} + */ + async deleteKey(publicKey: string | PublicKey): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deleteKey(PublicKey.from(publicKey))] + }); + } + + /** + * @see [https://near-nodes.io/validator/staking-and-delegation](https://near-nodes.io/validator/staking-and-delegation) + * + * @param publicKey The public key for the account that's staking + * @param amount The account to stake in yoctoⓃ + */ + async stake(publicKey: string | PublicKey, amount: BN): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [stake(amount, PublicKey.from(publicKey))] + }); + } + + /** @hidden */ + private validateArgs(args: any) { + const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; + if (isUint8Array) { + return; + } + + if (Array.isArray(args) || typeof args !== 'object') { + throw new PositionalArgsError(); + } + } + + /** + * Invoke a contract view function using the RPC API. + * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) + * + * @param viewFunctionCallOptions.contractId NEAR account where the contract is deployed + * @param viewFunctionCallOptions.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code + * @param viewFunctionCallOptions.args Any arguments to the view contract method, wrapped in JSON + * @param viewFunctionCallOptions.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json. + * @param viewFunctionCallOptions.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. + * @param viewFunctionCallOptions.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. + * @param viewFunctionCallOptions.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). + * @returns {Promise} + */ + + async viewFunction({ + contractId, + methodName, + args = {}, + parse = parseJsonFromRawResponse, + stringify = bytesJsonStringify, + jsContract = false, + blockQuery = { finality: 'optimistic' } + }: ViewFunctionCallOptions): Promise { + let encodedArgs; + + this.validateArgs(args); + + if(jsContract){ + encodedArgs = this.encodeJSContractArgs(contractId, methodName, Object.keys(args).length > 0 ? JSON.stringify(args): ''); + } else{ + encodedArgs = stringify(args); + } + + const result = await this.connection.provider.query({ + request_type: 'call_function', + ...blockQuery, + account_id: jsContract ? this.connection.jsvmAccountId : contractId, + method_name: jsContract ? 'view_js_contract' : methodName, + args_base64: encodedArgs.toString('base64') + }); + + if (result.logs) { + printTxOutcomeLogs({ contractId, logs: result.logs }); + } + + return result.result && result.result.length > 0 && parse(Buffer.from(result.result)); + } + + /** + * Returns the state (key value pairs) of this account's contract based on the key prefix. + * Pass an empty string for prefix if you would like to return the entire state. + * @see [https://docs.near.org/api/rpc/contracts#view-contract-state](https://docs.near.org/api/rpc/contracts#view-contract-state) + * + * @param prefix allows to filter which keys should be returned. Empty prefix means all keys. String prefix is utf-8 encoded. + * @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). + */ + async viewState(prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' } ): Promise> { + const { values } = await this.connection.provider.query({ + request_type: 'view_state', + ...blockQuery, + account_id: this.accountId, + prefix_base64: Buffer.from(prefix).toString('base64') + }); + + return values.map(({ key, value }) => ({ + key: Buffer.from(key, 'base64'), + value: Buffer.from(value, 'base64') + })); + } + + /** + * Get all access keys for the account + * @see [https://docs.near.org/api/rpc/access-keys#view-access-key-list](https://docs.near.org/api/rpc/access-keys#view-access-key-list) + */ + async getAccessKeys(): Promise { + const response = await this.connection.provider.query({ + request_type: 'view_access_key_list', + account_id: this.accountId, + finality: 'optimistic' + }); + // 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) } })); + } + + /** + * Returns a list of authorized apps + * @todo update the response value to return all the different keys, not just app keys. + */ + async getAccountDetails(): Promise<{ authorizedApps: AccountAuthorizedApp[] }> { + // TODO: update the response value to return all the different keys, not just app keys. + // Also if we need this function, or getAccessKeys is good enough. + const accessKeys = await this.getAccessKeys(); + const authorizedApps = accessKeys + .filter(item => item.access_key.permission !== 'FullAccess') + .map(item => { + const perm = (item.access_key.permission as FunctionCallPermissionView); + return { + contractId: perm.FunctionCall.receiver_id, + amount: perm.FunctionCall.allowance, + publicKey: item.public_key, + }; + }); + return { authorizedApps }; + } + + /** + * Returns calculated account balance + */ + async getAccountBalance(): Promise { + const protocolConfig = await this.connection.provider.experimental_protocolConfig({ finality: 'final' }); + const state = await this.state(); + + const costPerByte = new BN(protocolConfig.runtime_config.storage_amount_per_byte); + const stateStaked = new BN(state.storage_usage).mul(costPerByte); + const staked = new BN(state.locked); + const totalBalance = new BN(state.amount).add(staked); + const availableBalance = totalBalance.sub(BN.max(staked, stateStaked)); + + return { + total: totalBalance.toString(), + stateStaked: stateStaked.toString(), + staked: staked.toString(), + available: availableBalance.toString() + }; + } + + /** + * Returns the NEAR tokens balance and validators of a given account that is delegated to the staking pools that are part of the validators set in the current epoch. + * + * NOTE: If the tokens are delegated to a staking pool that is currently on pause or does not have enough tokens to participate in validation, they won't be accounted for. + * @returns {Promise} + */ + async getActiveDelegatedStakeBalance(): Promise { + const block = await this.connection.provider.block({ finality: 'final' }); + const blockHash = block.header.hash; + const epochId = block.header.epoch_id; + const { current_validators, next_validators, current_proposals } = await this.connection.provider.validators(epochId); + const pools:Set = new Set(); + [...current_validators, ...next_validators, ...current_proposals] + .forEach((validator) => pools.add(validator.account_id)); + + const uniquePools = [...pools]; + const promises = uniquePools + .map((validator) => ( + this.viewFunction({ + contractId: validator, + methodName: 'get_account_total_balance', + args: { account_id: this.accountId }, + blockQuery: { blockId: blockHash } + }) + )); + + const results = await Promise.allSettled(promises); + + const hasTimeoutError = results.some((result) => { + if (result.status === 'rejected' && result.reason.type === 'TimeoutError') { + return true; + } + return false; + }); + + // When RPC is down and return timeout error, throw error + if (hasTimeoutError) { + throw new Error('Failed to get delegated stake balance'); + } + const summary = results.reduce((result, state, index) => { + const validatorId = uniquePools[index]; + if (state.status === 'fulfilled') { + const currentBN = new BN(state.value); + if (!currentBN.isZero()) { + return { + ...result, + stakedValidators: [...result.stakedValidators, { validatorId, amount: currentBN.toString() }], + total: result.total.add(currentBN), + }; + } + } + if (state.status === 'rejected') { + return { + ...result, + failedValidators: [...result.failedValidators, { validatorId, error: state.reason }], + }; + } + return result; + }, + { stakedValidators: [], failedValidators: [], total: new BN(0) }); + + return { + ...summary, + total: summary.total.toString(), + }; + } +} diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts new file mode 100644 index 000000000..5f7f4a39f --- /dev/null +++ b/packages/accounts/src/account_multisig.ts @@ -0,0 +1,501 @@ +'use strict'; + +import { parseNearAmount, PublicKey, FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/client-core'; +import { fetchJson } from '@near-js/providers'; +import { Action, addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from '@near-js/transactions'; +import BN from 'bn.js'; + +import { Account, SignAndSendTransactionOptions } from './account'; +import { Connection } from './connection'; + +export const MULTISIG_STORAGE_KEY = '__multisigRequest'; +export const MULTISIG_ALLOWANCE = new BN(parseNearAmount('1')); +// TODO: Different gas value for different requests (can reduce gas usage dramatically) +export const MULTISIG_GAS = new BN('100000000000000'); +export const MULTISIG_DEPOSIT = new BN('0'); +export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; +export const MULTISIG_CONFIRM_METHODS = ['confirm']; + +type sendCodeFunction = () => Promise; +type getCodeFunction = (method: any) => Promise; +type verifyCodeFunction = (securityCode: any) => Promise; + +export enum MultisigDeleteRequestRejectionError { + CANNOT_DESERIALIZE_STATE = 'Cannot deserialize the contract state', + MULTISIG_NOT_INITIALIZED = 'Smart contract panicked: Multisig contract should be initialized before usage', + NO_SUCH_REQUEST = 'Smart contract panicked: panicked at \'No such request: either wrong number or already confirmed\'', + REQUEST_COOLDOWN_ERROR = 'Request cannot be deleted immediately after creation.', + METHOD_NOT_FOUND = 'Contract method is not found' +} + +export enum MultisigStateStatus { + INVALID_STATE, + STATE_NOT_INITIALIZED, + VALID_STATE, + UNKNOWN_STATE +} + +enum MultisigCodeStatus { + INVALID_CODE, + VALID_CODE, + UNKNOWN_CODE +} + +// in memory request cache for node w/o localStorage +const storageFallback = { + [MULTISIG_STORAGE_KEY]: null +}; + +export class AccountMultisig extends Account { + public storage: any; + public onAddRequestResult: (any) => any; + + constructor(connection: Connection, accountId: string, options: any) { + super(connection, accountId); + this.storage = options.storage; + this.onAddRequestResult = options.onAddRequestResult; + } + + async signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise { + return super.signAndSendTransaction({ receiverId, actions }); + } + + async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { + const { accountId } = this; + + const args = Buffer.from(JSON.stringify({ + request: { + receiver_id: receiverId, + actions: convertActions(actions, accountId, receiverId) + } + })); + + let result; + try { + result = await super.signAndSendTransaction({ + receiverId: accountId, + actions: [ + functionCall('add_request_and_confirm', args, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }); + } catch (e) { + if (e.toString().includes('Account has too many active requests. Confirm or delete some')) { + await this.deleteUnconfirmedRequests(); + return await this.signAndSendTransaction({ receiverId, actions }); + } + throw e; + } + + // TODO: Are following even needed? Seems like it throws on error already + if (!result.status) { + throw new Error('Request failed'); + } + const status: any = { ...result.status }; + if (!status.SuccessValue || typeof status.SuccessValue !== 'string') { + throw new Error('Request failed'); + } + + this.setRequest({ + accountId, + actions, + requestId: parseInt(Buffer.from(status.SuccessValue, 'base64').toString('ascii'), 10) + }); + + if (this.onAddRequestResult) { + await this.onAddRequestResult(result); + } + + // NOTE there is no await on purpose to avoid blocking for 2fa + this.deleteUnconfirmedRequests(); + + return result; + } + + /* + * This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state + * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. + * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. + */ + async checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }> { + const u32_max = 4_294_967_295; + const validCodeStatusIfNoDeploy = contractBytes ? MultisigCodeStatus.UNKNOWN_CODE : MultisigCodeStatus.VALID_CODE; + + try { + if(contractBytes) { + await super.signAndSendTransaction({ + receiverId: this.accountId, actions: [ + deployContract(contractBytes), + functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }); + } else { + await this.deleteRequest(u32_max); + } + + return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; + } catch (e) { + if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e && e.kind && e.kind.ExecutionError)) { + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; + } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e && e.kind && e.kind.ExecutionError)) { + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; + } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e && e.kind && e.kind.ExecutionError)) { + return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; + } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e && e.message)) { + // not reachable if transaction included a deploy + return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; + } + throw e; + } + } + + deleteRequest(request_id) { + return super.signAndSendTransaction({ + receiverId: this.accountId, + actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)] + }); + } + + async deleteAllRequests() { + const request_ids = await this.getRequestIds(); + if(request_ids.length) { + await Promise.all(request_ids.map((id) => this.deleteRequest(id))); + } + } + + async deleteUnconfirmedRequests () { + // TODO: Delete in batch, don't delete unexpired + // TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically) + const request_ids = await this.getRequestIds(); + const { requestId } = this.getRequest(); + for (const requestIdToDelete of request_ids) { + if (requestIdToDelete == requestId) { + continue; + } + try { + await super.signAndSendTransaction({ + receiverId: this.accountId, + actions: [functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)] + }); + } catch (e) { + console.warn('Attempt to delete an earlier request before 15 minutes failed. Will try again.'); + } + } + } + + // helpers + + async getRequestIds(): Promise { + // TODO: Read requests from state to allow filtering by expiration time + // TODO: https://github.com/near/core-contracts/blob/305d1db4f4f2cf5ce4c1ef3479f7544957381f11/multisig/src/lib.rs#L84 + return this.viewFunction({ + contractId: this.accountId, + methodName: 'list_request_ids', + }); + } + + getRequest() { + if (this.storage) { + return JSON.parse(this.storage.getItem(MULTISIG_STORAGE_KEY) || '{}'); + } + return storageFallback[MULTISIG_STORAGE_KEY]; + } + + setRequest(data) { + if (this.storage) { + return this.storage.setItem(MULTISIG_STORAGE_KEY, JSON.stringify(data)); + } + storageFallback[MULTISIG_STORAGE_KEY] = data; + } +} + +export class Account2FA extends AccountMultisig { + /******************************** + Account2FA has options object where you can provide callbacks for: + - sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper + - getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code) + - onResult: the tx result after it's been confirmed by NEAR Contract Helper + ********************************/ + public sendCode: sendCodeFunction; + public getCode: getCodeFunction; + public verifyCode: verifyCodeFunction; + public onConfirmResult: (any) => any; + public helperUrl = 'https://helper.testnet.near.org'; + + constructor(connection: Connection, accountId: string, options: any) { + super(connection, accountId, options); + this.helperUrl = options.helperUrl || this.helperUrl; + this.storage = options.storage; + this.sendCode = options.sendCode || this.sendCodeDefault; + this.getCode = options.getCode || this.getCodeDefault; + this.verifyCode = options.verifyCode || this.verifyCodeDefault; + this.onConfirmResult = options.onConfirmResult; + } + + /** + * Sign a transaction to preform a list of actions and broadcast it using the RPC API. + * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} + */ + async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { + await super.signAndSendTransaction({ receiverId, actions }); + // TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction? + await this.sendCode(); + const result = await this.promptAndVerify(); + if (this.onConfirmResult) { + await this.onConfirmResult(result); + } + return result; + } + + // default helpers for CH deployments of multisig + + async deployMultisig(contractBytes: Uint8Array) { + const { accountId } = this; + + const seedOrLedgerKey = (await this.getRecoveryMethods()).data + .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) + .map((rm) => rm.publicKey); + + const fak2lak = (await this.getAccessKeys()) + .filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key)) + .map((ak) => ak.public_key) + .map(toPK); + + const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + + const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); + + const actions = [ + ...fak2lak.map((pk) => deleteKey(pk)), + ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), + addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), + deployContract(contractBytes), + ]; + const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)); + console.log('deploying multisig contract for', accountId); + + const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); + switch (multisigStateStatus) { + case MultisigStateStatus.STATE_NOT_INITIALIZED: + return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID_STATE: + return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + default: + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + } + + async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) { + let cleanupActions = []; + if(cleanupContractBytes) { + await this.deleteAllRequests().catch(e => e); + cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes); + } + const keyConversionActions = await this.get2faDisableKeyConversionActions(); + + const actions = [ + ...cleanupActions, + ...keyConversionActions, + deployContract(contractBytes) + ]; + + const accessKeyInfo = await this.findAccessKey(this.accountId, actions); + + if(accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') { + throw new TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound'); + } + + return this.signAndSendTransactionWithAccount(this.accountId, actions); + } + + async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) { + const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { + return []; + } + throw cause == 'TOO_LARGE_CONTRACT_STATE' + ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') + : error; + }); + + const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); + return currentAccountState.length ? [ + deployContract(cleanupContractBytes), + functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0')) + ] : []; + } + + async get2faDisableKeyConversionActions() { + const { accountId } = this; + const accessKeys = await this.getAccessKeys(); + const lak2fak = accessKeys + .filter(({ access_key }) => access_key.permission !== 'FullAccess') + .filter(({ access_key }) => { + const perm = (access_key.permission as FunctionCallPermissionView).FunctionCall; + return perm.receiver_id === accountId && + perm.method_names.length === 4 && + perm.method_names.includes('add_request_and_confirm'); + }); + const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + return [ + deleteKey(confirmOnlyKey), + ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), + ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), fullAccessKey())) + ]; + } + + /** + * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) + * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} + * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} + */ + async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { + const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); + if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + + let deleteAllRequestsError; + await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); + + const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => { + if(e.type === 'ContractHasExistingState') { + throw deleteAllRequestsError || e; + } + throw e; + }); + + const actions = [ + ...cleanupActions, + ...(await this.get2faDisableKeyConversionActions()), + deployContract(contractBytes), + ]; + console.log('disabling 2fa for', this.accountId); + return await this.signAndSendTransaction({ + receiverId: this.accountId, + actions + }); + } + + async sendCodeDefault() { + const { accountId } = this; + const { requestId } = this.getRequest(); + const method = await this.get2faMethod(); + await this.postSignedJson('/2fa/send', { + accountId, + method, + requestId, + }); + return requestId; + } + + async getCodeDefault(): Promise { + throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".'); + } + + async promptAndVerify() { + const method = await this.get2faMethod(); + const securityCode = await this.getCode(method); + try { + const result = await this.verifyCode(securityCode); + + // TODO: Parse error from result for real (like in normal account.signAndSendTransaction) + return result; + } catch (e) { + console.warn('Error validating security code:', e); + if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) { + return await this.promptAndVerify(); + } + + throw e; + } + } + + async verifyCodeDefault(securityCode: string) { + const { accountId } = this; + const request = this.getRequest(); + if (!request) { + throw new Error('no request pending'); + } + const { requestId } = request; + return await this.postSignedJson('/2fa/verify', { + accountId, + securityCode, + requestId + }); + } + + async getRecoveryMethods() { + const { accountId } = this; + return { + accountId, + data: await this.postSignedJson('/account/recoveryMethods', { accountId }) + }; + } + + async get2faMethod() { + let { data } = await this.getRecoveryMethods(); + if (data && data.length) { + data = data.find((m) => m.kind.indexOf('2fa-') === 0); + } + if (!data) return null; + const { kind, detail } = data; + return { kind, detail }; + } + + async signatureFor() { + const { accountId } = this; + const block = await this.connection.provider.block({ finality: 'final' }); + const blockNumber = block.header.height.toString(); + const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId); + const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); + return { blockNumber, blockNumberSignature }; + } + + async postSignedJson(path, body) { + return await fetchJson(this.helperUrl + path, JSON.stringify({ + ...body, + ...(await this.signatureFor()) + })); + } +} + +// helpers +const toPK = (pk) => PublicKey.from(pk); +const convertPKForContract = (pk) => pk.toString().replace('ed25519:', ''); + +const convertActions = (actions, accountId, receiverId) => actions.map((a) => { + const type = a.enum; + const { gas, publicKey, methodName, args, deposit, accessKey, code } = a[type]; + const action = { + type: type[0].toUpperCase() + type.substr(1), + gas: (gas && gas.toString()) || undefined, + public_key: (publicKey && convertPKForContract(publicKey)) || undefined, + method_name: methodName, + args: (args && Buffer.from(args).toString('base64')) || undefined, + code: (code && Buffer.from(code).toString('base64')) || undefined, + amount: (deposit && deposit.toString()) || undefined, + deposit: (deposit && deposit.toString()) || '0', + permission: undefined, + }; + if (accessKey) { + if (receiverId === accountId && accessKey.permission.enum !== 'fullAccess') { + action.permission = { + receiver_id: accountId, + allowance: MULTISIG_ALLOWANCE.toString(), + method_names: MULTISIG_CHANGE_METHODS, + }; + } + if (accessKey.permission.enum === 'functionCall') { + const { receiverId: receiver_id, methodNames: method_names, allowance } = accessKey.permission.functionCall; + action.permission = { + receiver_id, + allowance: (allowance && allowance.toString()) || undefined, + method_names + }; + } + } + return action; +}); diff --git a/packages/accounts/src/connection.ts b/packages/accounts/src/connection.ts new file mode 100644 index 000000000..eb26b1ce4 --- /dev/null +++ b/packages/accounts/src/connection.ts @@ -0,0 +1,56 @@ +import { Signer, InMemorySigner } from '@near-js/client-core'; +import { Provider, JsonRpcProvider } from '@near-js/providers'; + +/** + * @param config Contains connection info details + * @returns {Provider} + */ +function getProvider(config: any): Provider { + switch (config.type) { + case undefined: + return config; + case 'JsonRpcProvider': return new JsonRpcProvider({ ...config.args }); + default: throw new Error(`Unknown provider type ${config.type}`); + } +} + +/** + * @param config Contains connection info details + * @returns {Signer} + */ +function getSigner(config: any): Signer { + switch (config.type) { + case undefined: + return config; + case 'InMemorySigner': { + return new InMemorySigner(config.keyStore); + } + default: throw new Error(`Unknown signer type ${config.type}`); + } +} + +/** + * Connects an account to a given network via a given provider + */ +export class Connection { + readonly networkId: string; + readonly provider: Provider; + readonly signer: Signer; + readonly jsvmAccountId: string; + + constructor(networkId: string, provider: Provider, signer: Signer, jsvmAccountId: string) { + this.networkId = networkId; + this.provider = provider; + this.signer = signer; + this.jsvmAccountId = jsvmAccountId; + } + + /** + * @param config Contains connection info details + */ + static fromConfig(config: any): Connection { + const provider = getProvider(config.provider); + const signer = getSigner(config.signer); + return new Connection(config.networkId, provider, signer, config.jsvmAccountId); + } +} diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts new file mode 100644 index 000000000..cd64ff436 --- /dev/null +++ b/packages/accounts/src/index.ts @@ -0,0 +1,22 @@ +export { + Account, + AccountBalance, + AccountAuthorizedApp, + SignAndSendTransactionOptions, + FunctionCallOptions, + ChangeFunctionCallOptions, + ViewFunctionCallOptions, +} from './account'; +export { + Account2FA, + AccountMultisig, + MULTISIG_STORAGE_KEY, + MULTISIG_ALLOWANCE, + MULTISIG_GAS, + MULTISIG_DEPOSIT, + MULTISIG_CHANGE_METHODS, + MULTISIG_CONFIRM_METHODS, + MultisigDeleteRequestRejectionError, + MultisigStateStatus, +} from './account_multisig'; +export { Connection } from './connection'; diff --git a/packages/accounts/test/.eslintrc.yml b/packages/accounts/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/accounts/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/accounts/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/client-core/src/constants.ts b/packages/client-core/src/constants.ts new file mode 100644 index 000000000..63a7c421c --- /dev/null +++ b/packages/client-core/src/constants.ts @@ -0,0 +1,9 @@ +import BN from 'bn.js'; + +// Default amount of gas to be sent with the function calls. Used to pay for the fees +// incurred while running the contract execution. The unused amount will be refunded back to +// the originator. +// Due to protocol changes that charge upfront for the maximum possible gas price inflation due to +// full blocks, the price of max_prepaid_gas is decreased to `300 * 10**12`. +// For discussion see https://github.com/nearprotocol/NEPs/issues/67 +export const DEFAULT_FUNCTION_CALL_GAS = new BN('30000000000000'); \ No newline at end of file diff --git a/packages/client-core/src/index.ts b/packages/client-core/src/index.ts index fb8db9585..0797f3238 100644 --- a/packages/client-core/src/index.ts +++ b/packages/client-core/src/index.ts @@ -1,7 +1,9 @@ +export * from './constants'; export * from './errors'; export * from './format'; export * from './key_pair'; export * from './key_store'; +export * from './logging'; export * from './provider'; export * from './signer'; export * from './types'; diff --git a/packages/client-core/src/logging.ts b/packages/client-core/src/logging.ts new file mode 100644 index 000000000..1c56f4985 --- /dev/null +++ b/packages/client-core/src/logging.ts @@ -0,0 +1,69 @@ +import { parseRpcError } from './errors'; +import { FinalExecutionOutcome } from './provider'; + +const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; + +/** + * Parse and print details from a query execution response + * @param params + * @param params.contractId ID of the account/contract which made the query + * @param params.outcome the query execution response + */ +export function printTxOutcomeLogsAndFailures({ + contractId, + outcome, +}: { contractId: string, outcome: FinalExecutionOutcome }) { + if (SUPPRESS_LOGGING) { + return; + } + + const flatLogs = [outcome.transaction_outcome, ...outcome.receipts_outcome] + .reduce((acc, it) => { + const isFailure = typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object'; + if (it.outcome.logs.length || isFailure) { + return acc.concat({ + receiptIds: it.outcome.receipt_ids, + logs: it.outcome.logs, + failure: typeof it.outcome.status === 'object' && it.outcome.status.Failure !== undefined + ? parseRpcError(it.outcome.status.Failure) + : null + }); + } else { + return acc; + } + }, []); + + for (const result of flatLogs) { + console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`); + printTxOutcomeLogs({ + contractId, + logs: result.logs, + prefix: '\t', + }); + + if (result.failure) { + console.warn(`\tFailure [${contractId}]: ${result.failure}`); + } + } +} + +/** + * Format and print log output from a query execution response + * @param params + * @param params.contractId ID of the account/contract which made the query + * @param params.logs log output from a query execution response + * @param params.prefix string to append to the beginning of each log + */ +export function printTxOutcomeLogs({ + contractId, + logs, + prefix = '', +}: { contractId: string, logs: string[], prefix?: string }) { + if (SUPPRESS_LOGGING) { + return; + } + + for (const log of logs) { + console.log(`${prefix}Log [${contractId}]: ${log}`); + } +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 7b3990055..67f635239 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -11,6 +11,7 @@ "browser": "lib/browser-index.js", "types": "lib/index.d.ts", "dependencies": { + "@near-js/accounts": "workspace:*", "@near-js/client-browser": "workspace:*", "@near-js/client-core": "workspace:*", "@near-js/client-node": "workspace:*", diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index afffd2913..d1883e140 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -1,652 +1,9 @@ -import BN from 'bn.js'; - -import { - transfer, - createAccount, - signTransaction, - deployContract, - addKey, - functionCall, - fullAccessKey, - functionCallAccessKey, - deleteKey, - stake, - deleteAccount, - Action, - SignedTransaction, - stringifyJsonOrBytes -} from './transaction'; -import { FinalExecutionOutcome, TypedError, ErrorContext } from './providers'; -import { - ViewStateResult, - AccountView, - AccessKeyView, - AccessKeyViewRaw, - CodeResult, - AccessKeyList, - AccessKeyInfoView, - FunctionCallPermissionView, - BlockReference -} from './providers/provider'; -import { Connection } from './connection'; -import { baseDecode, baseEncode } from 'borsh'; -import { PublicKey } from './utils/key_pair'; -import { logWarning, PositionalArgsError } from './utils/errors'; -import { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from './utils/logging'; -import { parseResultError } from './utils/rpc_errors'; -import { DEFAULT_FUNCTION_CALL_GAS } from './constants'; - -import exponentialBackoff from './utils/exponential-backoff'; - -// Default number of retries with different nonce before giving up on a transaction. -const TX_NONCE_RETRY_NUMBER = 12; - -// Default wait until next retry in millis. -const TX_NONCE_RETRY_WAIT = 500; - -// Exponential back off for waiting to retry. -const TX_NONCE_RETRY_WAIT_BACKOFF = 1.5; - -export interface AccountBalance { - total: string; - stateStaked: string; - staked: string; - available: string; -} - -export interface AccountAuthorizedApp { - contractId: string; - amount: string; - publicKey: string; -} - -/** - * Options used to initiate sining and sending transactions - */ -export interface SignAndSendTransactionOptions { - receiverId: string; - actions: Action[]; - /** - * Metadata to send the NEAR Wallet if using it to sign transactions. - * @see {@link RequestSignTransactionsOptions} - */ - walletMeta?: string; - /** - * Callback url to send the NEAR Wallet if using it to sign transactions. - * @see {@link RequestSignTransactionsOptions} - */ - walletCallbackUrl?: string; - returnError?: boolean; -} - -/** - * Options used to initiate a function call (especially a change function call) - * @see {@link account!Account#viewFunction} to initiate a view function call - */ -export interface FunctionCallOptions { - /** The NEAR account id where the contract is deployed */ - contractId: string; - /** The name of the method to invoke */ - methodName: string; - /** - * named arguments to pass the method `{ messageText: 'my message' }` - */ - args?: object; - /** max amount of gas that method call can use */ - gas?: BN; - /** amount of NEAR (in yoctoNEAR) to send together with the call */ - attachedDeposit?: BN; - /** - * Convert input arguments into bytes array. - */ - stringify?: (input: any) => Buffer; - /** - * Is contract from JS SDK, automatically encodes args from JS SDK to binary. - */ - jsContract?: boolean; -} - -export interface ChangeFunctionCallOptions extends FunctionCallOptions { - /** - * Metadata to send the NEAR Wallet if using it to sign transactions. - * @see {@link RequestSignTransactionsOptions} - */ - walletMeta?: string; - /** - * Callback url to send the NEAR Wallet if using it to sign transactions. - * @see {@link RequestSignTransactionsOptions} - */ - walletCallbackUrl?: string; -} -export interface ViewFunctionCallOptions extends FunctionCallOptions { - parse?: (response: Uint8Array) => any; - blockQuery?: BlockReference; -} - -interface StakedBalance { - validatorId: string; - amount?: string; - error?: string; -} - -interface ActiveDelegatedStakeBalance { - stakedValidators: StakedBalance[]; - failedValidators: StakedBalance[]; - total: BN | string; -} - -function parseJsonFromRawResponse(response: Uint8Array): any { - return JSON.parse(Buffer.from(response).toString()); -} - -function bytesJsonStringify(input: any): Buffer { - return Buffer.from(JSON.stringify(input)); -} - -/** - * This class provides common account related RPC calls including signing transactions with a {@link utils/key_pair!KeyPair}. - * - * @hint Use {@link walletAccount!WalletConnection} in the browser to redirect to [NEAR Wallet](https://wallet.near.org/) for Account/key management using the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. - * @see [https://docs.near.org/docs/develop/front-end/naj-quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) - * @see [Account Spec](https://nomicon.io/DataStructures/Account.html) - */ -export class Account { - readonly connection: Connection; - readonly accountId: string; - - constructor(connection: Connection, accountId: string) { - this.connection = connection; - this.accountId = accountId; - } - - /** - * Returns basic NEAR account information via the `view_account` RPC query method - * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) - */ - async state(): Promise { - return this.connection.provider.query({ - request_type: 'view_account', - account_id: this.accountId, - finality: 'optimistic' - }); - } - - /** - * Create a signed transaction which can be broadcast to the network - * @param receiverId NEAR account receiving the transaction - * @param actions list of actions to perform as part of the transaction - * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} - */ - protected async signTransaction(receiverId: string, actions: Action[]): Promise<[Uint8Array, SignedTransaction]> { - const accessKeyInfo = await this.findAccessKey(receiverId, actions); - if (!accessKeyInfo) { - throw new TypedError(`Can not sign transactions for account ${this.accountId} on network ${this.connection.networkId}, no matching key pair exists for this account`, 'KeyNotFound'); - } - const { accessKey } = accessKeyInfo; - - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = block.header.hash; - - const nonce = accessKey.nonce.add(new BN(1)); - return await signTransaction( - receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId - ); - } - - /** - * Sign a transaction to preform a list of actions and broadcast it using the RPC API. - * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} - */ - async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise { - let txHash, signedTx; - // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) - const result = await exponentialBackoff(TX_NONCE_RETRY_WAIT, TX_NONCE_RETRY_NUMBER, TX_NONCE_RETRY_WAIT_BACKOFF, async () => { - [txHash, signedTx] = await this.signTransaction(receiverId, actions); - const publicKey = signedTx.transaction.publicKey; - - try { - return await this.connection.provider.sendTransaction(signedTx); - } catch (error) { - if (error.type === 'InvalidNonce') { - logWarning(`Retrying transaction ${receiverId}:${baseEncode(txHash)} with new nonce.`); - delete this.accessKeyByPublicKeyCache[publicKey.toString()]; - return null; - } - if (error.type === 'Expired') { - logWarning(`Retrying transaction ${receiverId}:${baseEncode(txHash)} due to expired block hash`); - return null; - } - - error.context = new ErrorContext(baseEncode(txHash)); - throw error; - } - }); - if (!result) { - // TODO: This should have different code actually, as means "transaction not submitted for sure" - throw new TypedError('nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.', 'RetriesExceeded'); - } - - printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result }); - - // Should be falsy if result.status.Failure is null - if (!returnError && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { - // if error data has error_message and error_type properties, we consider that node returned an error in the old format - if (result.status.Failure.error_message && result.status.Failure.error_type) { - throw new TypedError( - `Transaction ${result.transaction_outcome.id} failed. ${result.status.Failure.error_message}`, - result.status.Failure.error_type); - } else { - throw parseResultError(result); - } - } - // TODO: if Tx is Unknown or Started. - return result; - } - - /** @hidden */ - accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; - - /** - * Finds the {@link providers/provider!AccessKeyView} associated with the accounts {@link utils/key_pair!PublicKey} stored in the {@link key_stores/keystore!KeyStore}. - * - * @todo Find matching access key based on transaction (i.e. receiverId and actions) - * - * @param receiverId currently unused (see todo) - * @param actions currently unused (see todo) - * @returns `{ publicKey PublicKey; accessKey: AccessKeyView }` - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async findAccessKey(receiverId: string, actions: Action[]): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { - // TODO: Find matching access key based on transaction (i.e. receiverId and actions) - const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); - if (!publicKey) { - throw new TypedError(`no matching key pair found in ${this.connection.signer}`, 'PublicKeyNotFound'); - } - - const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()]; - if (cachedAccessKey !== undefined) { - return { publicKey, accessKey: cachedAccessKey }; - } - - try { - 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 - // the cached access key. - if (this.accessKeyByPublicKeyCache[publicKey.toString()]) { - return { publicKey, accessKey: this.accessKeyByPublicKeyCache[publicKey.toString()] }; - } - - this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; - return { publicKey, accessKey }; - } catch (e) { - if (e.type == 'AccessKeyDoesNotExist') { - return null; - } - - throw e; - } - } - - /** - * Create a new account and deploy a contract to it - * - * @param contractId NEAR account where the contract is deployed - * @param publicKey The public key to add to the created contract account - * @param data The compiled contract code - * @param amount of NEAR to transfer to the created contract account. Transfer enough to pay for storage https://docs.near.org/docs/concepts/storage-staking - */ - async createAndDeployContract(contractId: string, publicKey: string | PublicKey, data: Uint8Array, amount: BN): Promise { - const accessKey = fullAccessKey(); - await this.signAndSendTransaction({ - receiverId: contractId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey), deployContract(data)] - }); - const contractAccount = new Account(this.connection, contractId); - return contractAccount; - } - - /** - * @param receiverId NEAR account receiving Ⓝ - * @param amount Amount to send in yoctoⓃ - */ - async sendMoney(receiverId: string, amount: BN): Promise { - return this.signAndSendTransaction({ - receiverId, - actions: [transfer(amount)] - }); - } - - /** - * @param newAccountId NEAR account name to be created - * @param publicKey A public key created from the masterAccount - */ - async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: BN): Promise { - const accessKey = fullAccessKey(); - return this.signAndSendTransaction({ - receiverId: newAccountId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey)] - }); - } - - /** - * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted - */ - async deleteAccount(beneficiaryId: string) { - if (!process.env['NEAR_NO_LOGS']) { - console.log('Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting.'); - } - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [deleteAccount(beneficiaryId)] - }); - } - - /** - * @param data The compiled contract code - */ - async deployContract(data: Uint8Array): Promise { - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [deployContract(data)] - }); - } - - /** @hidden */ - private encodeJSContractArgs(contractId: string, method: string, args) { - return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]); - } - - /** - * Execute function call - * @returns {Promise} - */ - async functionCall({ contractId, methodName, args = {}, gas = DEFAULT_FUNCTION_CALL_GAS, attachedDeposit, walletMeta, walletCallbackUrl, stringify, jsContract }: ChangeFunctionCallOptions): Promise { - this.validateArgs(args); - let functionCallArgs; - - if(jsContract){ - const encodedArgs = this.encodeJSContractArgs( contractId, methodName, JSON.stringify(args) ); - functionCallArgs = ['call_js_contract', encodedArgs, gas, attachedDeposit, null, true ]; - } else{ - const stringifyArg = stringify === undefined ? stringifyJsonOrBytes : stringify; - functionCallArgs = [methodName, args, gas, attachedDeposit, stringifyArg, false]; - } - - return this.signAndSendTransaction({ - receiverId: jsContract ? this.connection.jsvmAccountId: contractId, - // eslint-disable-next-line prefer-spread - actions: [functionCall.apply(void 0, functionCallArgs)], - walletMeta, - walletCallbackUrl - }); - } - - /** - * @see [https://docs.near.org/concepts/basics/accounts/access-keys](https://docs.near.org/concepts/basics/accounts/access-keys) - * @todo expand this API to support more options. - * @param publicKey A public key to be associated with the contract - * @param contractId NEAR account where the contract is deployed - * @param methodNames The method names on the contract that should be allowed to be called. Pass null for no method names and '' or [] for any method names. - * @param amount Payment in yoctoⓃ that is sent to the contract during this function call - */ - async addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: BN): Promise { - if (!methodNames) { - methodNames = []; - } - if (!Array.isArray(methodNames)) { - methodNames = [methodNames]; - } - let accessKey; - if (!contractId) { - accessKey = fullAccessKey(); - } else { - accessKey = functionCallAccessKey(contractId, methodNames, amount); - } - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [addKey(PublicKey.from(publicKey), accessKey)] - }); - } - - /** - * @param publicKey The public key to be deleted - * @returns {Promise} - */ - async deleteKey(publicKey: string | PublicKey): Promise { - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [deleteKey(PublicKey.from(publicKey))] - }); - } - - /** - * @see [https://near-nodes.io/validator/staking-and-delegation](https://near-nodes.io/validator/staking-and-delegation) - * - * @param publicKey The public key for the account that's staking - * @param amount The account to stake in yoctoⓃ - */ - async stake(publicKey: string | PublicKey, amount: BN): Promise { - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [stake(amount, PublicKey.from(publicKey))] - }); - } - - /** @hidden */ - private validateArgs(args: any) { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; - if (isUint8Array) { - return; - } - - if (Array.isArray(args) || typeof args !== 'object') { - throw new PositionalArgsError(); - } - } - - /** - * Invoke a contract view function using the RPC API. - * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) - * - * @param viewFunctionCallOptions.contractId NEAR account where the contract is deployed - * @param viewFunctionCallOptions.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code - * @param viewFunctionCallOptions.args Any arguments to the view contract method, wrapped in JSON - * @param viewFunctionCallOptions.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json. - * @param viewFunctionCallOptions.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param viewFunctionCallOptions.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. - * @param viewFunctionCallOptions.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). - * @returns {Promise} - */ - - async viewFunction({ - contractId, - methodName, - args = {}, - parse = parseJsonFromRawResponse, - stringify = bytesJsonStringify, - jsContract = false, - blockQuery = { finality: 'optimistic' } - }: ViewFunctionCallOptions): Promise { - let encodedArgs; - - this.validateArgs(args); - - if(jsContract){ - encodedArgs = this.encodeJSContractArgs(contractId, methodName, Object.keys(args).length > 0 ? JSON.stringify(args): ''); - } else{ - encodedArgs = stringify(args); - } - - const result = await this.connection.provider.query({ - request_type: 'call_function', - ...blockQuery, - account_id: jsContract ? this.connection.jsvmAccountId : contractId, - method_name: jsContract ? 'view_js_contract' : methodName, - args_base64: encodedArgs.toString('base64') - }); - - if (result.logs) { - printTxOutcomeLogs({ contractId, logs: result.logs }); - } - - return result.result && result.result.length > 0 && parse(Buffer.from(result.result)); - } - - /** - * Returns the state (key value pairs) of this account's contract based on the key prefix. - * Pass an empty string for prefix if you would like to return the entire state. - * @see [https://docs.near.org/api/rpc/contracts#view-contract-state](https://docs.near.org/api/rpc/contracts#view-contract-state) - * - * @param prefix allows to filter which keys should be returned. Empty prefix means all keys. String prefix is utf-8 encoded. - * @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). - */ - async viewState(prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' } ): Promise> { - const { values } = await this.connection.provider.query({ - request_type: 'view_state', - ...blockQuery, - account_id: this.accountId, - prefix_base64: Buffer.from(prefix).toString('base64') - }); - - return values.map(({ key, value }) => ({ - key: Buffer.from(key, 'base64'), - value: Buffer.from(value, 'base64') - })); - } - - /** - * Get all access keys for the account - * @see [https://docs.near.org/api/rpc/access-keys#view-access-key-list](https://docs.near.org/api/rpc/access-keys#view-access-key-list) - */ - async getAccessKeys(): Promise { - const response = await this.connection.provider.query({ - request_type: 'view_access_key_list', - account_id: this.accountId, - finality: 'optimistic' - }); - // 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) } })); - } - - /** - * Returns a list of authorized apps - * @todo update the response value to return all the different keys, not just app keys. - */ - async getAccountDetails(): Promise<{ authorizedApps: AccountAuthorizedApp[] }> { - // TODO: update the response value to return all the different keys, not just app keys. - // Also if we need this function, or getAccessKeys is good enough. - const accessKeys = await this.getAccessKeys(); - const authorizedApps = accessKeys - .filter(item => item.access_key.permission !== 'FullAccess') - .map(item => { - const perm = (item.access_key.permission as FunctionCallPermissionView); - return { - contractId: perm.FunctionCall.receiver_id, - amount: perm.FunctionCall.allowance, - publicKey: item.public_key, - }; - }); - return { authorizedApps }; - } - - /** - * Returns calculated account balance - */ - async getAccountBalance(): Promise { - const protocolConfig = await this.connection.provider.experimental_protocolConfig({ finality: 'final' }); - const state = await this.state(); - - const costPerByte = new BN(protocolConfig.runtime_config.storage_amount_per_byte); - const stateStaked = new BN(state.storage_usage).mul(costPerByte); - const staked = new BN(state.locked); - const totalBalance = new BN(state.amount).add(staked); - const availableBalance = totalBalance.sub(BN.max(staked, stateStaked)); - - return { - total: totalBalance.toString(), - stateStaked: stateStaked.toString(), - staked: staked.toString(), - available: availableBalance.toString() - }; - } - - /** - * Returns the NEAR tokens balance and validators of a given account that is delegated to the staking pools that are part of the validators set in the current epoch. - * - * NOTE: If the tokens are delegated to a staking pool that is currently on pause or does not have enough tokens to participate in validation, they won't be accounted for. - * @returns {Promise} - */ - async getActiveDelegatedStakeBalance(): Promise { - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = block.header.hash; - const epochId = block.header.epoch_id; - const { current_validators, next_validators, current_proposals } = await this.connection.provider.validators(epochId); - const pools:Set = new Set(); - [...current_validators, ...next_validators, ...current_proposals] - .forEach((validator) => pools.add(validator.account_id)); - - const uniquePools = [...pools]; - const promises = uniquePools - .map((validator) => ( - this.viewFunction({ - contractId: validator, - methodName: 'get_account_total_balance', - args: { account_id: this.accountId }, - blockQuery: { blockId: blockHash } - }) - )); - - const results = await Promise.allSettled(promises); - - const hasTimeoutError = results.some((result) => { - if (result.status === 'rejected' && result.reason.type === 'TimeoutError') { - return true; - } - return false; - }); - - // When RPC is down and return timeout error, throw error - if (hasTimeoutError) { - throw new Error('Failed to get delegated stake balance'); - } - const summary = results.reduce((result, state, index) => { - const validatorId = uniquePools[index]; - if (state.status === 'fulfilled') { - const currentBN = new BN(state.value); - if (!currentBN.isZero()) { - return { - ...result, - stakedValidators: [...result.stakedValidators, { validatorId, amount: currentBN.toString() }], - total: result.total.add(currentBN), - }; - } - } - if (state.status === 'rejected') { - return { - ...result, - failedValidators: [...result.failedValidators, { validatorId, error: state.reason }], - }; - } - return result; - }, - { stakedValidators: [], failedValidators: [], total: new BN(0) }); - - return { - ...summary, - total: summary.total.toString(), - }; - } -} +export { + Account, + AccountBalance, + AccountAuthorizedApp, + SignAndSendTransactionOptions, + FunctionCallOptions, + ChangeFunctionCallOptions, + ViewFunctionCallOptions, +} from '@near-js/accounts'; diff --git a/packages/near-api-js/src/account_multisig.ts b/packages/near-api-js/src/account_multisig.ts index 621d8dedb..8671cf595 100644 --- a/packages/near-api-js/src/account_multisig.ts +++ b/packages/near-api-js/src/account_multisig.ts @@ -1,503 +1,12 @@ -'use strict'; - -import BN from 'bn.js'; -import { Account, SignAndSendTransactionOptions } from './account'; -import { Connection } from './connection'; -import { parseNearAmount } from './utils/format'; -import { PublicKey } from './utils/key_pair'; -import { Action, addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from './transaction'; -import { FinalExecutionOutcome, TypedError } from './providers'; -import { fetchJson } from './utils/web'; -import { FunctionCallPermissionView } from './providers/provider'; - -export const MULTISIG_STORAGE_KEY = '__multisigRequest'; -export const MULTISIG_ALLOWANCE = new BN(parseNearAmount('1')); -// TODO: Different gas value for different requests (can reduce gas usage dramatically) -export const MULTISIG_GAS = new BN('100000000000000'); -export const MULTISIG_DEPOSIT = new BN('0'); -export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; -export const MULTISIG_CONFIRM_METHODS = ['confirm']; - -type sendCodeFunction = () => Promise; -type getCodeFunction = (method: any) => Promise; -type verifyCodeFunction = (securityCode: any) => Promise; - -export enum MultisigDeleteRequestRejectionError { - CANNOT_DESERIALIZE_STATE = 'Cannot deserialize the contract state', - MULTISIG_NOT_INITIALIZED = 'Smart contract panicked: Multisig contract should be initialized before usage', - NO_SUCH_REQUEST = 'Smart contract panicked: panicked at \'No such request: either wrong number or already confirmed\'', - REQUEST_COOLDOWN_ERROR = 'Request cannot be deleted immediately after creation.', - METHOD_NOT_FOUND = 'Contract method is not found' -} - -export enum MultisigStateStatus { - INVALID_STATE, - STATE_NOT_INITIALIZED, - VALID_STATE, - UNKNOWN_STATE -} - -enum MultisigCodeStatus { - INVALID_CODE, - VALID_CODE, - UNKNOWN_CODE -} - -// in memory request cache for node w/o localStorage -const storageFallback = { - [MULTISIG_STORAGE_KEY]: null -}; - -export class AccountMultisig extends Account { - public storage: any; - public onAddRequestResult: (any) => any; - - constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId); - this.storage = options.storage; - this.onAddRequestResult = options.onAddRequestResult; - } - - async signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise { - return super.signAndSendTransaction({ receiverId, actions }); - } - - async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { - const { accountId } = this; - - const args = Buffer.from(JSON.stringify({ - request: { - receiver_id: receiverId, - actions: convertActions(actions, accountId, receiverId) - } - })); - - let result; - try { - result = await super.signAndSendTransaction({ - receiverId: accountId, - actions: [ - functionCall('add_request_and_confirm', args, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }); - } catch (e) { - if (e.toString().includes('Account has too many active requests. Confirm or delete some')) { - await this.deleteUnconfirmedRequests(); - return await this.signAndSendTransaction({ receiverId, actions }); - } - throw e; - } - - // TODO: Are following even needed? Seems like it throws on error already - if (!result.status) { - throw new Error('Request failed'); - } - const status: any = { ...result.status }; - if (!status.SuccessValue || typeof status.SuccessValue !== 'string') { - throw new Error('Request failed'); - } - - this.setRequest({ - accountId, - actions, - requestId: parseInt(Buffer.from(status.SuccessValue, 'base64').toString('ascii'), 10) - }); - - if (this.onAddRequestResult) { - await this.onAddRequestResult(result); - } - - // NOTE there is no await on purpose to avoid blocking for 2fa - this.deleteUnconfirmedRequests(); - - return result; - } - - /* - * This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state - * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. - * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. - */ - async checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }> { - const u32_max = 4_294_967_295; - const validCodeStatusIfNoDeploy = contractBytes ? MultisigCodeStatus.UNKNOWN_CODE : MultisigCodeStatus.VALID_CODE; - - try { - if(contractBytes) { - await super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - deployContract(contractBytes), - functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }); - } else { - await this.deleteRequest(u32_max); - } - - return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; - } catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e && e.message)) { - // not reachable if transaction included a deploy - return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; - } - throw e; - } - } - - deleteRequest(request_id) { - return super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }); - } - - async deleteAllRequests() { - const request_ids = await this.getRequestIds(); - if(request_ids.length) { - await Promise.all(request_ids.map((id) => this.deleteRequest(id))); - } - } - - async deleteUnconfirmedRequests () { - // TODO: Delete in batch, don't delete unexpired - // TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically) - const request_ids = await this.getRequestIds(); - const { requestId } = this.getRequest(); - for (const requestIdToDelete of request_ids) { - if (requestIdToDelete == requestId) { - continue; - } - try { - await super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }); - } catch (e) { - console.warn('Attempt to delete an earlier request before 15 minutes failed. Will try again.'); - } - } - } - - // helpers - - async getRequestIds(): Promise { - // TODO: Read requests from state to allow filtering by expiration time - // TODO: https://github.com/near/core-contracts/blob/305d1db4f4f2cf5ce4c1ef3479f7544957381f11/multisig/src/lib.rs#L84 - return this.viewFunction({ - contractId: this.accountId, - methodName: 'list_request_ids', - }); - } - - getRequest() { - if (this.storage) { - return JSON.parse(this.storage.getItem(MULTISIG_STORAGE_KEY) || '{}'); - } - return storageFallback[MULTISIG_STORAGE_KEY]; - } - - setRequest(data) { - if (this.storage) { - return this.storage.setItem(MULTISIG_STORAGE_KEY, JSON.stringify(data)); - } - storageFallback[MULTISIG_STORAGE_KEY] = data; - } -} - -export class Account2FA extends AccountMultisig { - /******************************** - Account2FA has options object where you can provide callbacks for: - - sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper - - getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code) - - onResult: the tx result after it's been confirmed by NEAR Contract Helper - ********************************/ - public sendCode: sendCodeFunction; - public getCode: getCodeFunction; - public verifyCode: verifyCodeFunction; - public onConfirmResult: (any) => any; - public helperUrl = 'https://helper.testnet.near.org'; - - constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId, options); - this.helperUrl = options.helperUrl || this.helperUrl; - this.storage = options.storage; - this.sendCode = options.sendCode || this.sendCodeDefault; - this.getCode = options.getCode || this.getCodeDefault; - this.verifyCode = options.verifyCode || this.verifyCodeDefault; - this.onConfirmResult = options.onConfirmResult; - } - - /** - * Sign a transaction to preform a list of actions and broadcast it using the RPC API. - * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} - */ - async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { - await super.signAndSendTransaction({ receiverId, actions }); - // TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction? - await this.sendCode(); - const result = await this.promptAndVerify(); - if (this.onConfirmResult) { - await this.onConfirmResult(result); - } - return result; - } - - // default helpers for CH deployments of multisig - - async deployMultisig(contractBytes: Uint8Array) { - const { accountId } = this; - - const seedOrLedgerKey = (await this.getRecoveryMethods()).data - .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) - .map((rm) => rm.publicKey); - - const fak2lak = (await this.getAccessKeys()) - .filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key)) - .map((ak) => ak.public_key) - .map(toPK); - - const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - - const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); - - const actions = [ - ...fak2lak.map((pk) => deleteKey(pk)), - ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), - addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), - deployContract(contractBytes), - ]; - const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)); - console.log('deploying multisig contract for', accountId); - - const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); - switch (multisigStateStatus) { - case MultisigStateStatus.STATE_NOT_INITIALIZED: - return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID_STATE: - return await super.signAndSendTransactionWithAccount(accountId, actions); - case MultisigStateStatus.INVALID_STATE: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); - default: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - } - - async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) { - let cleanupActions = []; - if(cleanupContractBytes) { - await this.deleteAllRequests().catch(e => e); - cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes); - } - const keyConversionActions = await this.get2faDisableKeyConversionActions(); - - const actions = [ - ...cleanupActions, - ...keyConversionActions, - deployContract(contractBytes) - ]; - - const accessKeyInfo = await this.findAccessKey(this.accountId, actions); - - if(accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') { - throw new TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound'); - } - - return this.signAndSendTransactionWithAccount(this.accountId, actions); - } - - async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) { - const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if (cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' - ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') - : error; - }); - - const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); - return currentAccountState.length ? [ - deployContract(cleanupContractBytes), - functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0')) - ] : []; - } - - async get2faDisableKeyConversionActions() { - const { accountId } = this; - const accessKeys = await this.getAccessKeys(); - const lak2fak = accessKeys - .filter(({ access_key }) => access_key.permission !== 'FullAccess') - .filter(({ access_key }) => { - const perm = (access_key.permission as FunctionCallPermissionView).FunctionCall; - return perm.receiver_id === accountId && - perm.method_names.length === 4 && - perm.method_names.includes('add_request_and_confirm'); - }); - const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - return [ - deleteKey(confirmOnlyKey), - ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), - ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), fullAccessKey())) - ]; - } - - /** - * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) - * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} - * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} - */ - async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { - const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); - if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - - let deleteAllRequestsError; - await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); - - const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => { - if(e.type === 'ContractHasExistingState') { - throw deleteAllRequestsError || e; - } - throw e; - }); - - const actions = [ - ...cleanupActions, - ...(await this.get2faDisableKeyConversionActions()), - deployContract(contractBytes), - ]; - console.log('disabling 2fa for', this.accountId); - return await this.signAndSendTransaction({ - receiverId: this.accountId, - actions - }); - } - - async sendCodeDefault() { - const { accountId } = this; - const { requestId } = this.getRequest(); - const method = await this.get2faMethod(); - await this.postSignedJson('/2fa/send', { - accountId, - method, - requestId, - }); - return requestId; - } - - async getCodeDefault(): Promise { - throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".'); - } - - async promptAndVerify() { - const method = await this.get2faMethod(); - const securityCode = await this.getCode(method); - try { - const result = await this.verifyCode(securityCode); - - // TODO: Parse error from result for real (like in normal account.signAndSendTransaction) - return result; - } catch (e) { - console.warn('Error validating security code:', e); - if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) { - return await this.promptAndVerify(); - } - - throw e; - } - } - - async verifyCodeDefault(securityCode: string) { - const { accountId } = this; - const request = this.getRequest(); - if (!request) { - throw new Error('no request pending'); - } - const { requestId } = request; - return await this.postSignedJson('/2fa/verify', { - accountId, - securityCode, - requestId - }); - } - - async getRecoveryMethods() { - const { accountId } = this; - return { - accountId, - data: await this.postSignedJson('/account/recoveryMethods', { accountId }) - }; - } - - async get2faMethod() { - let { data } = await this.getRecoveryMethods(); - if (data && data.length) { - data = data.find((m) => m.kind.indexOf('2fa-') === 0); - } - if (!data) return null; - const { kind, detail } = data; - return { kind, detail }; - } - - async signatureFor() { - const { accountId } = this; - const block = await this.connection.provider.block({ finality: 'final' }); - const blockNumber = block.header.height.toString(); - const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId); - const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); - return { blockNumber, blockNumberSignature }; - } - - async postSignedJson(path, body) { - return await fetchJson(this.helperUrl + path, JSON.stringify({ - ...body, - ...(await this.signatureFor()) - })); - } -} - -// helpers -const toPK = (pk) => PublicKey.from(pk); -const convertPKForContract = (pk) => pk.toString().replace('ed25519:', ''); - -const convertActions = (actions, accountId, receiverId) => actions.map((a) => { - const type = a.enum; - const { gas, publicKey, methodName, args, deposit, accessKey, code } = a[type]; - const action = { - type: type[0].toUpperCase() + type.substr(1), - gas: (gas && gas.toString()) || undefined, - public_key: (publicKey && convertPKForContract(publicKey)) || undefined, - method_name: methodName, - args: (args && Buffer.from(args).toString('base64')) || undefined, - code: (code && Buffer.from(code).toString('base64')) || undefined, - amount: (deposit && deposit.toString()) || undefined, - deposit: (deposit && deposit.toString()) || '0', - permission: undefined, - }; - if (accessKey) { - if (receiverId === accountId && accessKey.permission.enum !== 'fullAccess') { - action.permission = { - receiver_id: accountId, - allowance: MULTISIG_ALLOWANCE.toString(), - method_names: MULTISIG_CHANGE_METHODS, - }; - } - if (accessKey.permission.enum === 'functionCall') { - const { receiverId: receiver_id, methodNames: method_names, allowance } = accessKey.permission.functionCall; - action.permission = { - receiver_id, - allowance: (allowance && allowance.toString()) || undefined, - method_names - }; - } - } - return action; -}); +export { + Account2FA, + AccountMultisig, + MULTISIG_STORAGE_KEY, + MULTISIG_ALLOWANCE, + MULTISIG_GAS, + MULTISIG_DEPOSIT, + MULTISIG_CHANGE_METHODS, + MULTISIG_CONFIRM_METHODS, + MultisigDeleteRequestRejectionError, + MultisigStateStatus, +} from '@near-js/accounts'; diff --git a/packages/near-api-js/src/connection.ts b/packages/near-api-js/src/connection.ts index 80e7d9a98..ab8ff5589 100644 --- a/packages/near-api-js/src/connection.ts +++ b/packages/near-api-js/src/connection.ts @@ -1,56 +1 @@ -import { Provider, JsonRpcProvider } from './providers'; -import { Signer, InMemorySigner } from './signer'; - -/** - * @param config Contains connection info details - * @returns {Provider} - */ -function getProvider(config: any): Provider { - switch (config.type) { - case undefined: - return config; - case 'JsonRpcProvider': return new JsonRpcProvider({ ...config.args }); - default: throw new Error(`Unknown provider type ${config.type}`); - } -} - -/** - * @param config Contains connection info details - * @returns {Signer} - */ -function getSigner(config: any): Signer { - switch (config.type) { - case undefined: - return config; - case 'InMemorySigner': { - return new InMemorySigner(config.keyStore); - } - default: throw new Error(`Unknown signer type ${config.type}`); - } -} - -/** - * Connects an account to a given network via a given provider - */ -export class Connection { - readonly networkId: string; - readonly provider: Provider; - readonly signer: Signer; - readonly jsvmAccountId: string; - - constructor(networkId: string, provider: Provider, signer: Signer, jsvmAccountId: string) { - this.networkId = networkId; - this.provider = provider; - this.signer = signer; - this.jsvmAccountId = jsvmAccountId; - } - - /** - * @param config Contains connection info details - */ - static fromConfig(config: any): Connection { - const provider = getProvider(config.provider); - const signer = getSigner(config.signer); - return new Connection(config.networkId, provider, signer, config.jsvmAccountId); - } -} +export { Connection } from '@near-js/accounts'; \ No newline at end of file diff --git a/packages/near-api-js/src/constants.ts b/packages/near-api-js/src/constants.ts index 63a7c421c..c93ee9b45 100644 --- a/packages/near-api-js/src/constants.ts +++ b/packages/near-api-js/src/constants.ts @@ -1,9 +1 @@ -import BN from 'bn.js'; - -// Default amount of gas to be sent with the function calls. Used to pay for the fees -// incurred while running the contract execution. The unused amount will be refunded back to -// the originator. -// Due to protocol changes that charge upfront for the maximum possible gas price inflation due to -// full blocks, the price of max_prepaid_gas is decreased to `300 * 10**12`. -// For discussion see https://github.com/nearprotocol/NEPs/issues/67 -export const DEFAULT_FUNCTION_CALL_GAS = new BN('30000000000000'); \ No newline at end of file +export { DEFAULT_FUNCTION_CALL_GAS } from '@near-js/client-core'; diff --git a/packages/near-api-js/src/utils/logging.ts b/packages/near-api-js/src/utils/logging.ts index 8e1e6c1a8..47125d113 100644 --- a/packages/near-api-js/src/utils/logging.ts +++ b/packages/near-api-js/src/utils/logging.ts @@ -1,69 +1 @@ -import { FinalExecutionOutcome } from '../providers'; -import { parseRpcError } from './rpc_errors'; - -const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; - -/** - * Parse and print details from a query execution response - * @param params - * @param params.contractId ID of the account/contract which made the query - * @param params.outcome the query execution response - */ -export function printTxOutcomeLogsAndFailures({ - contractId, - outcome, -}: { contractId: string, outcome: FinalExecutionOutcome }) { - if (SUPPRESS_LOGGING) { - return; - } - - const flatLogs = [outcome.transaction_outcome, ...outcome.receipts_outcome] - .reduce((acc, it) => { - const isFailure = typeof it.outcome.status === 'object' && typeof it.outcome.status.Failure === 'object'; - if (it.outcome.logs.length || isFailure) { - return acc.concat({ - receiptIds: it.outcome.receipt_ids, - logs: it.outcome.logs, - failure: typeof it.outcome.status === 'object' && it.outcome.status.Failure !== undefined - ? parseRpcError(it.outcome.status.Failure) - : null - }); - } else { - return acc; - } - }, []); - - for (const result of flatLogs) { - console.log(`Receipt${result.receiptIds.length > 1 ? 's' : ''}: ${result.receiptIds.join(', ')}`); - printTxOutcomeLogs({ - contractId, - logs: result.logs, - prefix: '\t', - }); - - if (result.failure) { - console.warn(`\tFailure [${contractId}]: ${result.failure}`); - } - } -} - -/** - * Format and print log output from a query execution response - * @param params - * @param params.contractId ID of the account/contract which made the query - * @param params.logs log output from a query execution response - * @param params.prefix string to append to the beginning of each log - */ -export function printTxOutcomeLogs({ - contractId, - logs, - prefix = '', -}: { contractId: string, logs: string[], prefix?: string }) { - if (SUPPRESS_LOGGING) { - return; - } - - for (const log of logs) { - console.log(`${prefix}Log [${contractId}]: ${log}`); - } -} +export { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from '@near-js/client-core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d3485234..233a0cb02 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,27 @@ importers: turbo: 1.6.3 typescript: 4.9.4 + packages/accounts: + specifiers: + '@near-js/client-core': workspace:* + '@near-js/providers': workspace:* + '@near-js/transactions': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + jest: ^26.0.1 + ts-jest: ^26.5.6 + dependencies: + '@near-js/client-core': link:../client-core + '@near-js/providers': link:../providers + '@near-js/transactions': link:../transactions + bn.js: 5.2.1 + borsh: 0.7.0 + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + packages/client-browser: specifiers: '@near-js/client-core': workspace:* @@ -85,6 +106,7 @@ importers: packages/near-api-js: specifiers: + '@near-js/accounts': workspace:* '@near-js/client-browser': workspace:* '@near-js/client-core': workspace:* '@near-js/client-node': workspace:* @@ -121,6 +143,7 @@ importers: tweetnacl: ^1.0.1 uglifyify: ^5.0.1 dependencies: + '@near-js/accounts': link:../accounts '@near-js/client-browser': link:../client-browser '@near-js/client-core': link:../client-core '@near-js/client-node': link:../client-node @@ -5725,6 +5748,7 @@ packages: /node-fetch/2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} + requiresBuild: true peerDependencies: encoding: ^0.1.0 peerDependenciesMeta: From af6429938ac0a9812610cda1b7049554c9012965 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 1 Dec 2022 16:53:01 -0800 Subject: [PATCH 28/84] feat: split up Account2FA and AccountMultisig --- packages/accounts/src/account_2fa.ts | 278 ++++++++++++++++++++ packages/accounts/src/account_multisig.ts | 297 +--------------------- packages/accounts/src/constants.ts | 10 + packages/accounts/src/index.ts | 10 +- packages/accounts/src/types.ts | 14 + 5 files changed, 318 insertions(+), 291 deletions(-) create mode 100644 packages/accounts/src/account_2fa.ts create mode 100644 packages/accounts/src/constants.ts create mode 100644 packages/accounts/src/types.ts diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts new file mode 100644 index 000000000..5ef0ca828 --- /dev/null +++ b/packages/accounts/src/account_2fa.ts @@ -0,0 +1,278 @@ +'use strict'; + +import { PublicKey, FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/client-core'; +import { fetchJson } from '@near-js/providers'; +import { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from '@near-js/transactions'; +import BN from 'bn.js'; + +import { SignAndSendTransactionOptions } from './account'; +import { AccountMultisig } from './account_multisig'; +import { Connection } from './connection'; +import { + MULTISIG_CHANGE_METHODS, + MULTISIG_CONFIRM_METHODS, + MULTISIG_DEPOSIT, + MULTISIG_GAS, +} from './constants'; +import { MultisigStateStatus } from './types'; + +type sendCodeFunction = () => Promise; +type getCodeFunction = (method: any) => Promise; +type verifyCodeFunction = (securityCode: any) => Promise; + +export class Account2FA extends AccountMultisig { + /******************************** + Account2FA has options object where you can provide callbacks for: + - sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper + - getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code) + - onResult: the tx result after it's been confirmed by NEAR Contract Helper + ********************************/ + public sendCode: sendCodeFunction; + public getCode: getCodeFunction; + public verifyCode: verifyCodeFunction; + public onConfirmResult: (any) => any; + public helperUrl = 'https://helper.testnet.near.org'; + + constructor(connection: Connection, accountId: string, options: any) { + super(connection, accountId, options); + this.helperUrl = options.helperUrl || this.helperUrl; + this.storage = options.storage; + this.sendCode = options.sendCode || this.sendCodeDefault; + this.getCode = options.getCode || this.getCodeDefault; + this.verifyCode = options.verifyCode || this.verifyCodeDefault; + this.onConfirmResult = options.onConfirmResult; + } + + /** + * Sign a transaction to preform a list of actions and broadcast it using the RPC API. + * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} + */ + async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { + await super.signAndSendTransaction({ receiverId, actions }); + // TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction? + await this.sendCode(); + const result = await this.promptAndVerify(); + if (this.onConfirmResult) { + await this.onConfirmResult(result); + } + return result; + } + + // default helpers for CH deployments of multisig + + async deployMultisig(contractBytes: Uint8Array) { + const { accountId } = this; + + const seedOrLedgerKey = (await this.getRecoveryMethods()).data + .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) + .map((rm) => rm.publicKey); + + const fak2lak = (await this.getAccessKeys()) + .filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key)) + .map((ak) => ak.public_key) + .map(toPK); + + const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + + const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); + + const actions = [ + ...fak2lak.map((pk) => deleteKey(pk)), + ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), + addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), + deployContract(contractBytes), + ]; + const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)); + console.log('deploying multisig contract for', accountId); + + const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); + switch (multisigStateStatus) { + case MultisigStateStatus.STATE_NOT_INITIALIZED: + return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); + case MultisigStateStatus.VALID_STATE: + return await super.signAndSendTransactionWithAccount(accountId, actions); + case MultisigStateStatus.INVALID_STATE: + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + default: + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + } + + async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) { + let cleanupActions = []; + if(cleanupContractBytes) { + await this.deleteAllRequests().catch(e => e); + cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes); + } + const keyConversionActions = await this.get2faDisableKeyConversionActions(); + + const actions = [ + ...cleanupActions, + ...keyConversionActions, + deployContract(contractBytes) + ]; + + const accessKeyInfo = await this.findAccessKey(this.accountId, actions); + + if(accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') { + throw new TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound'); + } + + return this.signAndSendTransactionWithAccount(this.accountId, actions); + } + + async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) { + const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => { + const cause = error.cause && error.cause.name; + if (cause == 'NO_CONTRACT_CODE') { + return []; + } + throw cause == 'TOO_LARGE_CONTRACT_STATE' + ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') + : error; + }); + + const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); + return currentAccountState.length ? [ + deployContract(cleanupContractBytes), + functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0')) + ] : []; + } + + async get2faDisableKeyConversionActions() { + const { accountId } = this; + const accessKeys = await this.getAccessKeys(); + const lak2fak = accessKeys + .filter(({ access_key }) => access_key.permission !== 'FullAccess') + .filter(({ access_key }) => { + const perm = (access_key.permission as FunctionCallPermissionView).FunctionCall; + return perm.receiver_id === accountId && + perm.method_names.length === 4 && + perm.method_names.includes('add_request_and_confirm'); + }); + const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); + return [ + deleteKey(confirmOnlyKey), + ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), + ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), fullAccessKey())) + ]; + } + + /** + * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) + * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} + * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} + */ + async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { + const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); + if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + } + + let deleteAllRequestsError; + await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); + + const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => { + if(e.type === 'ContractHasExistingState') { + throw deleteAllRequestsError || e; + } + throw e; + }); + + const actions = [ + ...cleanupActions, + ...(await this.get2faDisableKeyConversionActions()), + deployContract(contractBytes), + ]; + console.log('disabling 2fa for', this.accountId); + return await this.signAndSendTransaction({ + receiverId: this.accountId, + actions + }); + } + + async sendCodeDefault() { + const { accountId } = this; + const { requestId } = this.getRequest(); + const method = await this.get2faMethod(); + await this.postSignedJson('/2fa/send', { + accountId, + method, + requestId, + }); + return requestId; + } + + async getCodeDefault(): Promise { + throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".'); + } + + async promptAndVerify() { + const method = await this.get2faMethod(); + const securityCode = await this.getCode(method); + try { + const result = await this.verifyCode(securityCode); + + // TODO: Parse error from result for real (like in normal account.signAndSendTransaction) + return result; + } catch (e) { + console.warn('Error validating security code:', e); + if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) { + return await this.promptAndVerify(); + } + + throw e; + } + } + + async verifyCodeDefault(securityCode: string) { + const { accountId } = this; + const request = this.getRequest(); + if (!request) { + throw new Error('no request pending'); + } + const { requestId } = request; + return await this.postSignedJson('/2fa/verify', { + accountId, + securityCode, + requestId + }); + } + + async getRecoveryMethods() { + const { accountId } = this; + return { + accountId, + data: await this.postSignedJson('/account/recoveryMethods', { accountId }) + }; + } + + async get2faMethod() { + let { data } = await this.getRecoveryMethods(); + if (data && data.length) { + data = data.find((m) => m.kind.indexOf('2fa-') === 0); + } + if (!data) return null; + const { kind, detail } = data; + return { kind, detail }; + } + + async signatureFor() { + const { accountId } = this; + const block = await this.connection.provider.block({ finality: 'final' }); + const blockNumber = block.header.height.toString(); + const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId); + const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); + return { blockNumber, blockNumberSignature }; + } + + async postSignedJson(path, body) { + return await fetchJson(this.helperUrl + path, JSON.stringify({ + ...body, + ...(await this.signatureFor()) + })); + } +} + +// helpers +const toPK = (pk) => PublicKey.from(pk); diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index 5f7f4a39f..3ef4b18bf 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -1,39 +1,18 @@ 'use strict'; -import { parseNearAmount, PublicKey, FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/client-core'; -import { fetchJson } from '@near-js/providers'; -import { Action, addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from '@near-js/transactions'; -import BN from 'bn.js'; +import { FinalExecutionOutcome } from '@near-js/client-core'; +import { Action, deployContract, functionCall } from '@near-js/transactions'; import { Account, SignAndSendTransactionOptions } from './account'; import { Connection } from './connection'; - -export const MULTISIG_STORAGE_KEY = '__multisigRequest'; -export const MULTISIG_ALLOWANCE = new BN(parseNearAmount('1')); -// TODO: Different gas value for different requests (can reduce gas usage dramatically) -export const MULTISIG_GAS = new BN('100000000000000'); -export const MULTISIG_DEPOSIT = new BN('0'); -export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; -export const MULTISIG_CONFIRM_METHODS = ['confirm']; - -type sendCodeFunction = () => Promise; -type getCodeFunction = (method: any) => Promise; -type verifyCodeFunction = (securityCode: any) => Promise; - -export enum MultisigDeleteRequestRejectionError { - CANNOT_DESERIALIZE_STATE = 'Cannot deserialize the contract state', - MULTISIG_NOT_INITIALIZED = 'Smart contract panicked: Multisig contract should be initialized before usage', - NO_SUCH_REQUEST = 'Smart contract panicked: panicked at \'No such request: either wrong number or already confirmed\'', - REQUEST_COOLDOWN_ERROR = 'Request cannot be deleted immediately after creation.', - METHOD_NOT_FOUND = 'Contract method is not found' -} - -export enum MultisigStateStatus { - INVALID_STATE, - STATE_NOT_INITIALIZED, - VALID_STATE, - UNKNOWN_STATE -} +import { + MULTISIG_ALLOWANCE, + MULTISIG_CHANGE_METHODS, + MULTISIG_DEPOSIT, + MULTISIG_GAS, + MULTISIG_STORAGE_KEY, +} from './constants'; +import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types'; enum MultisigCodeStatus { INVALID_CODE, @@ -208,262 +187,6 @@ export class AccountMultisig extends Account { } } -export class Account2FA extends AccountMultisig { - /******************************** - Account2FA has options object where you can provide callbacks for: - - sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper - - getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code) - - onResult: the tx result after it's been confirmed by NEAR Contract Helper - ********************************/ - public sendCode: sendCodeFunction; - public getCode: getCodeFunction; - public verifyCode: verifyCodeFunction; - public onConfirmResult: (any) => any; - public helperUrl = 'https://helper.testnet.near.org'; - - constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId, options); - this.helperUrl = options.helperUrl || this.helperUrl; - this.storage = options.storage; - this.sendCode = options.sendCode || this.sendCodeDefault; - this.getCode = options.getCode || this.getCodeDefault; - this.verifyCode = options.verifyCode || this.verifyCodeDefault; - this.onConfirmResult = options.onConfirmResult; - } - - /** - * Sign a transaction to preform a list of actions and broadcast it using the RPC API. - * @see {@link providers/json-rpc-provider!JsonRpcProvider#sendTransaction | JsonRpcProvider.sendTransaction} - */ - async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { - await super.signAndSendTransaction({ receiverId, actions }); - // TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction? - await this.sendCode(); - const result = await this.promptAndVerify(); - if (this.onConfirmResult) { - await this.onConfirmResult(result); - } - return result; - } - - // default helpers for CH deployments of multisig - - async deployMultisig(contractBytes: Uint8Array) { - const { accountId } = this; - - const seedOrLedgerKey = (await this.getRecoveryMethods()).data - .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) - .map((rm) => rm.publicKey); - - const fak2lak = (await this.getAccessKeys()) - .filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key)) - .map((ak) => ak.public_key) - .map(toPK); - - const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - - const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); - - const actions = [ - ...fak2lak.map((pk) => deleteKey(pk)), - ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), - addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), - deployContract(contractBytes), - ]; - const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)); - console.log('deploying multisig contract for', accountId); - - const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); - switch (multisigStateStatus) { - case MultisigStateStatus.STATE_NOT_INITIALIZED: - return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID_STATE: - return await super.signAndSendTransactionWithAccount(accountId, actions); - case MultisigStateStatus.INVALID_STATE: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); - default: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - } - - async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) { - let cleanupActions = []; - if(cleanupContractBytes) { - await this.deleteAllRequests().catch(e => e); - cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes); - } - const keyConversionActions = await this.get2faDisableKeyConversionActions(); - - const actions = [ - ...cleanupActions, - ...keyConversionActions, - deployContract(contractBytes) - ]; - - const accessKeyInfo = await this.findAccessKey(this.accountId, actions); - - if(accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') { - throw new TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound'); - } - - return this.signAndSendTransactionWithAccount(this.accountId, actions); - } - - async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) { - const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if (cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' - ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') - : error; - }); - - const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); - return currentAccountState.length ? [ - deployContract(cleanupContractBytes), - functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0')) - ] : []; - } - - async get2faDisableKeyConversionActions() { - const { accountId } = this; - const accessKeys = await this.getAccessKeys(); - const lak2fak = accessKeys - .filter(({ access_key }) => access_key.permission !== 'FullAccess') - .filter(({ access_key }) => { - const perm = (access_key.permission as FunctionCallPermissionView).FunctionCall; - return perm.receiver_id === accountId && - perm.method_names.length === 4 && - perm.method_names.includes('add_request_and_confirm'); - }); - const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - return [ - deleteKey(confirmOnlyKey), - ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), - ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), fullAccessKey())) - ]; - } - - /** - * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) - * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} - * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-cleanup/res/state_cleanup.wasm?raw=true} - */ - async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { - const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); - if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - - let deleteAllRequestsError; - await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); - - const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => { - if(e.type === 'ContractHasExistingState') { - throw deleteAllRequestsError || e; - } - throw e; - }); - - const actions = [ - ...cleanupActions, - ...(await this.get2faDisableKeyConversionActions()), - deployContract(contractBytes), - ]; - console.log('disabling 2fa for', this.accountId); - return await this.signAndSendTransaction({ - receiverId: this.accountId, - actions - }); - } - - async sendCodeDefault() { - const { accountId } = this; - const { requestId } = this.getRequest(); - const method = await this.get2faMethod(); - await this.postSignedJson('/2fa/send', { - accountId, - method, - requestId, - }); - return requestId; - } - - async getCodeDefault(): Promise { - throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".'); - } - - async promptAndVerify() { - const method = await this.get2faMethod(); - const securityCode = await this.getCode(method); - try { - const result = await this.verifyCode(securityCode); - - // TODO: Parse error from result for real (like in normal account.signAndSendTransaction) - return result; - } catch (e) { - console.warn('Error validating security code:', e); - if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) { - return await this.promptAndVerify(); - } - - throw e; - } - } - - async verifyCodeDefault(securityCode: string) { - const { accountId } = this; - const request = this.getRequest(); - if (!request) { - throw new Error('no request pending'); - } - const { requestId } = request; - return await this.postSignedJson('/2fa/verify', { - accountId, - securityCode, - requestId - }); - } - - async getRecoveryMethods() { - const { accountId } = this; - return { - accountId, - data: await this.postSignedJson('/account/recoveryMethods', { accountId }) - }; - } - - async get2faMethod() { - let { data } = await this.getRecoveryMethods(); - if (data && data.length) { - data = data.find((m) => m.kind.indexOf('2fa-') === 0); - } - if (!data) return null; - const { kind, detail } = data; - return { kind, detail }; - } - - async signatureFor() { - const { accountId } = this; - const block = await this.connection.provider.block({ finality: 'final' }); - const blockNumber = block.header.height.toString(); - const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId); - const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); - return { blockNumber, blockNumberSignature }; - } - - async postSignedJson(path, body) { - return await fetchJson(this.helperUrl + path, JSON.stringify({ - ...body, - ...(await this.signatureFor()) - })); - } -} - -// helpers -const toPK = (pk) => PublicKey.from(pk); const convertPKForContract = (pk) => pk.toString().replace('ed25519:', ''); const convertActions = (actions, accountId, receiverId) => actions.map((a) => { diff --git a/packages/accounts/src/constants.ts b/packages/accounts/src/constants.ts new file mode 100644 index 000000000..f53662bf5 --- /dev/null +++ b/packages/accounts/src/constants.ts @@ -0,0 +1,10 @@ +import { parseNearAmount } from '@near-js/client-core'; +import BN from 'bn.js'; + +export const MULTISIG_STORAGE_KEY = '__multisigRequest'; +export const MULTISIG_ALLOWANCE = new BN(parseNearAmount('1')); +// TODO: Different gas value for different requests (can reduce gas usage dramatically) +export const MULTISIG_GAS = new BN('100000000000000'); +export const MULTISIG_DEPOSIT = new BN('0'); +export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm']; +export const MULTISIG_CONFIRM_METHODS = ['confirm']; diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index cd64ff436..47d924068 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -7,16 +7,18 @@ export { ChangeFunctionCallOptions, ViewFunctionCallOptions, } from './account'; +export { Account2FA } from './account_2fa'; +export { AccountMultisig } from './account_multisig'; +export { Connection } from './connection'; export { - Account2FA, - AccountMultisig, MULTISIG_STORAGE_KEY, MULTISIG_ALLOWANCE, MULTISIG_GAS, MULTISIG_DEPOSIT, MULTISIG_CHANGE_METHODS, MULTISIG_CONFIRM_METHODS, +} from './constants'; +export { MultisigDeleteRequestRejectionError, MultisigStateStatus, -} from './account_multisig'; -export { Connection } from './connection'; +} from './types'; diff --git a/packages/accounts/src/types.ts b/packages/accounts/src/types.ts new file mode 100644 index 000000000..9e016e5d6 --- /dev/null +++ b/packages/accounts/src/types.ts @@ -0,0 +1,14 @@ +export enum MultisigDeleteRequestRejectionError { + CANNOT_DESERIALIZE_STATE = 'Cannot deserialize the contract state', + MULTISIG_NOT_INITIALIZED = 'Smart contract panicked: Multisig contract should be initialized before usage', + NO_SUCH_REQUEST = 'Smart contract panicked: panicked at \'No such request: either wrong number or already confirmed\'', + REQUEST_COOLDOWN_ERROR = 'Request cannot be deleted immediately after creation.', + METHOD_NOT_FOUND = 'Contract method is not found' +} + +export enum MultisigStateStatus { + INVALID_STATE, + STATE_NOT_INITIALIZED, + VALID_STATE, + UNKNOWN_STATE +} From a5ed04c49215bda12fef7443e147458d95ad1532 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 1 Dec 2022 17:26:20 -0800 Subject: [PATCH 29/84] feat: move account_creator to accounts package --- packages/accounts/src/account_creator.ts | 56 +++++++++++++++++++++ packages/accounts/src/index.ts | 5 ++ packages/near-api-js/src/account_creator.ts | 56 +-------------------- 3 files changed, 62 insertions(+), 55 deletions(-) create mode 100644 packages/accounts/src/account_creator.ts diff --git a/packages/accounts/src/account_creator.ts b/packages/accounts/src/account_creator.ts new file mode 100644 index 000000000..b1a8187d0 --- /dev/null +++ b/packages/accounts/src/account_creator.ts @@ -0,0 +1,56 @@ +import { PublicKey } from '@near-js/client-core'; +import { fetchJson } from '@near-js/providers'; +import BN from 'bn.js'; + +import { Connection } from './connection'; +import { Account } from './account'; + +/** + * Account creator provides an interface for implementations to actually create accounts + */ +export abstract class AccountCreator { + abstract createAccount(newAccountId: string, publicKey: PublicKey): Promise; +} + +export class LocalAccountCreator extends AccountCreator { + readonly masterAccount: Account; + readonly initialBalance: BN; + + constructor(masterAccount: Account, initialBalance: BN) { + super(); + this.masterAccount = masterAccount; + this.initialBalance = initialBalance; + } + + /** + * Creates an account using a masterAccount, meaning the new account is created from an existing account + * @param newAccountId The name of the NEAR account to be created + * @param publicKey The public key from the masterAccount used to create this account + * @returns {Promise} + */ + async createAccount(newAccountId: string, publicKey: PublicKey): Promise { + await this.masterAccount.createAccount(newAccountId, publicKey, this.initialBalance); + } +} + +export class UrlAccountCreator extends AccountCreator { + readonly connection: Connection; + readonly helperUrl: string; + + constructor(connection: Connection, helperUrl: string) { + super(); + this.connection = connection; + this.helperUrl = helperUrl; + } + + /** + * Creates an account using a helperUrl + * This is [hosted here](https://helper.nearprotocol.com) or set up locally with the [near-contract-helper](https://github.com/nearprotocol/near-contract-helper) repository + * @param newAccountId The name of the NEAR account to be created + * @param publicKey The public key from the masterAccount used to create this account + * @returns {Promise} + */ + async createAccount(newAccountId: string, publicKey: PublicKey): Promise { + await fetchJson(`${this.helperUrl}/account`, JSON.stringify({ newAccountId, newAccountPublicKey: publicKey.toString() })); + } +} diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 47d924068..4cf984b0f 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -8,6 +8,11 @@ export { ViewFunctionCallOptions, } from './account'; export { Account2FA } from './account_2fa'; +export { + AccountCreator, + LocalAccountCreator, + UrlAccountCreator, +} from './account_creator'; export { AccountMultisig } from './account_multisig'; export { Connection } from './connection'; export { diff --git a/packages/near-api-js/src/account_creator.ts b/packages/near-api-js/src/account_creator.ts index a886d83ba..fa0db8fa7 100644 --- a/packages/near-api-js/src/account_creator.ts +++ b/packages/near-api-js/src/account_creator.ts @@ -1,55 +1 @@ -import BN from 'bn.js'; -import { Connection } from './connection'; -import { Account } from './account'; -import { fetchJson } from './utils/web'; -import { PublicKey } from './utils/key_pair'; - -/** - * Account creator provides an interface for implementations to actually create accounts - */ -export abstract class AccountCreator { - abstract createAccount(newAccountId: string, publicKey: PublicKey): Promise; -} - -export class LocalAccountCreator extends AccountCreator { - readonly masterAccount: Account; - readonly initialBalance: BN; - - constructor(masterAccount: Account, initialBalance: BN) { - super(); - this.masterAccount = masterAccount; - this.initialBalance = initialBalance; - } - - /** - * Creates an account using a masterAccount, meaning the new account is created from an existing account - * @param newAccountId The name of the NEAR account to be created - * @param publicKey The public key from the masterAccount used to create this account - * @returns {Promise} - */ - async createAccount(newAccountId: string, publicKey: PublicKey): Promise { - await this.masterAccount.createAccount(newAccountId, publicKey, this.initialBalance); - } -} - -export class UrlAccountCreator extends AccountCreator { - readonly connection: Connection; - readonly helperUrl: string; - - constructor(connection: Connection, helperUrl: string) { - super(); - this.connection = connection; - this.helperUrl = helperUrl; - } - - /** - * Creates an account using a helperUrl - * This is [hosted here](https://helper.nearprotocol.com) or set up locally with the [near-contract-helper](https://github.com/nearprotocol/near-contract-helper) repository - * @param newAccountId The name of the NEAR account to be created - * @param publicKey The public key from the masterAccount used to create this account - * @returns {Promise} - */ - async createAccount(newAccountId: string, publicKey: PublicKey): Promise { - await fetchJson(`${this.helperUrl}/account`, JSON.stringify({ newAccountId, newAccountPublicKey: publicKey.toString() })); - } -} +export { AccountCreator, LocalAccountCreator, UrlAccountCreator } from '@near-js/accounts'; From c7f1d88618994df1277169c6627898bd0927d763 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 1 Dec 2022 17:27:39 -0800 Subject: [PATCH 30/84] feat: connected-wallet package --- packages/connected-wallet/package.json | 24 + packages/connected-wallet/src/index.ts | 2 + packages/connected-wallet/src/near.ts | 135 ++++++ .../connected-wallet/src/wallet-account.ts | 411 ++++++++++++++++++ packages/connected-wallet/tsconfig.json | 29 ++ packages/near-api-js/package.json | 1 + packages/near-api-js/src/near.ts | 130 +----- packages/near-api-js/src/wallet-account.ts | 405 +---------------- pnpm-lock.yaml | 19 + 9 files changed, 623 insertions(+), 533 deletions(-) create mode 100644 packages/connected-wallet/package.json create mode 100644 packages/connected-wallet/src/index.ts create mode 100644 packages/connected-wallet/src/near.ts create mode 100644 packages/connected-wallet/src/wallet-account.ts create mode 100644 packages/connected-wallet/tsconfig.json diff --git a/packages/connected-wallet/package.json b/packages/connected-wallet/package.json new file mode 100644 index 000000000..edddaa782 --- /dev/null +++ b/packages/connected-wallet/package.json @@ -0,0 +1,24 @@ +{ + "name": "@near-js/connected-wallet", + "version": "0.0.1", + "description": "Dependencies for the NEAR API JavaScript client in the browser", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/accounts": "workspace:*", + "@near-js/client-core": "workspace:*", + "@near-js/transactions": "workspace:*", + "bn.js": "5.2.1", + "borsh": "^0.7.0" + }, + "devDependencies": { + "@types/node": "^18.7.14" + } +} diff --git a/packages/connected-wallet/src/index.ts b/packages/connected-wallet/src/index.ts new file mode 100644 index 000000000..1172cd5ad --- /dev/null +++ b/packages/connected-wallet/src/index.ts @@ -0,0 +1,2 @@ +export { Near, NearConfig } from './near'; +export { ConnectedWalletAccount, WalletConnection } from './wallet-account'; \ No newline at end of file diff --git a/packages/connected-wallet/src/near.ts b/packages/connected-wallet/src/near.ts new file mode 100644 index 000000000..f22ab8d78 --- /dev/null +++ b/packages/connected-wallet/src/near.ts @@ -0,0 +1,135 @@ +/** + * This module contains the main class developers will use to interact with NEAR. + * The {@link Near} class is used to interact with {@link account!Account | Accounts} through the {@link providers/json-rpc-provider!JsonRpcProvider}. + * It is configured via the {@link NearConfig}. + * + * @see [https://docs.near.org/tools/near-api-js/quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) + * + * @module near + */ +import { + Account, + AccountCreator, + Connection, + LocalAccountCreator, + UrlAccountCreator, +} from '@near-js/accounts'; +import { + KeyStore, + PublicKey, + Signer, +} from '@near-js/client-core'; +import BN from 'bn.js'; + +export interface NearConfig { + /** Holds {@link utils/key_pair!KeyPair | KeyPairs} for signing transactions */ + keyStore?: KeyStore; + + /** @hidden */ + signer?: Signer; + + /** + * [NEAR Contract Helper](https://github.com/near/near-contract-helper) url used to create accounts if no master account is provided + * @see {@link account_creator!UrlAccountCreator} + */ + helperUrl?: string; + + /** + * The balance transferred from the {@link masterAccount} to a created account + * @see {@link account_creator!LocalAccountCreator} + */ + initialBalance?: string; + + /** + * The account to use when creating new accounts + * @see {@link account_creator!LocalAccountCreator} + */ + masterAccount?: string; + + /** + * {@link utils/key_pair!KeyPair | KeyPairs} are stored in a {@link key_stores/keystore!KeyStore} under the `networkId` namespace. + */ + networkId: string; + + /** + * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. + * @see {@link providers/json-rpc-provider!JsonRpcProvider} + */ + nodeUrl: string; + + /** + * NEAR RPC API headers. Can be used to pass API KEY and other parameters. + * @see {@link providers/json-rpc-provider!JsonRpcProvider} + */ + headers?: { [key: string]: string | number }; + + /** + * NEAR wallet url used to redirect users to their wallet in browser applications. + * @see [https://wallet.near.org/](https://wallet.near.org/) + */ + walletUrl?: string; + + /** + * JVSM account ID for NEAR JS SDK + */ + jsvmAccountId?: string; +} + +/** + * This is the main class developers should use to interact with NEAR. + * @example + * ```js + * const near = new Near(config); + * ``` + */ +export class Near { + readonly config: NearConfig; + readonly connection: Connection; + readonly accountCreator: AccountCreator; + + constructor(config: NearConfig) { + this.config = config; + this.connection = Connection.fromConfig({ + networkId: config.networkId, + provider: { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, + signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore }, + jsvmAccountId: config.jsvmAccountId || `jsvm.${config.networkId}` + }); + + if (config.masterAccount) { + // TODO: figure out better way of specifiying initial balance. + // Hardcoded number below must be enough to pay the gas cost to dev-deploy with near-shell for multiple times + const initialBalance = config.initialBalance ? new BN(config.initialBalance) : new BN('500000000000000000000000000'); + this.accountCreator = new LocalAccountCreator(new Account(this.connection, config.masterAccount), initialBalance); + } else if (config.helperUrl) { + this.accountCreator = new UrlAccountCreator(this.connection, config.helperUrl); + } else { + this.accountCreator = null; + } + } + + /** + * @param accountId near accountId used to interact with the network. + */ + async account(accountId: string): Promise { + const account = new Account(this.connection, accountId); + return account; + } + + /** + * Create an account using the {@link account_creator!AccountCreator}. Either: + * * using a masterAccount with {@link account_creator!LocalAccountCreator} + * * using the helperUrl with {@link account_creator!UrlAccountCreator} + * @see {@link NearConfig.masterAccount} and {@link NearConfig.helperUrl} + * + * @param accountId + * @param publicKey + */ + async createAccount(accountId: string, publicKey: PublicKey): Promise { + if (!this.accountCreator) { + throw new Error('Must specify account creator, either via masterAccount or helperUrl configuration settings.'); + } + await this.accountCreator.createAccount(accountId, publicKey); + return new Account(this.connection, accountId); + } +} diff --git a/packages/connected-wallet/src/wallet-account.ts b/packages/connected-wallet/src/wallet-account.ts new file mode 100644 index 000000000..31fd155e6 --- /dev/null +++ b/packages/connected-wallet/src/wallet-account.ts @@ -0,0 +1,411 @@ +/** + * The classes in this module are used in conjunction with the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. + * This module exposes two classes: + * * {@link WalletConnection} which redirects users to [NEAR Wallet](https://wallet.near.org/) for key management. + * * {@link ConnectedWalletAccount} is an {@link account!Account} implementation that uses {@link WalletConnection} to get keys + * + * @module walletAccount + */ +import { + Account, + Connection, + SignAndSendTransactionOptions, +} from '@near-js/accounts'; +import { + FinalExecutionOutcome, + InMemorySigner, + KeyPair, + KeyStore, + PublicKey, +} from '@near-js/client-core'; +import { + Transaction, Action, SCHEMA, createTransaction +} from '@near-js/transactions'; +import BN from 'bn.js'; +import { baseDecode, serialize } from 'borsh'; + +import { Near } from './near'; + +const LOGIN_WALLET_URL_SUFFIX = '/login/'; +const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; +const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; +const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) + +interface SignInOptions { + contractId?: string; + methodNames?: string[]; + // TODO: Replace following with single callbackUrl + successUrl?: string; + failureUrl?: string; +} + +/** + * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application + */ +interface RequestSignTransactionsOptions { + /** list of transactions to sign */ + transactions: Transaction[]; + /** url NEAR Wallet will redirect to after transaction signing is complete */ + callbackUrl?: string; + /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ + meta?: string; +} + +/** + * This class is used in conjunction with the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. + * It redirects users to [NEAR Wallet](https://wallet.near.org) for key management. + * This class is not intended for use outside the browser. Without `window` (i.e. in server contexts), it will instantiate but will throw a clear error when used. + * + * @see [https://docs.near.org/tools/near-api-js/quick-reference#wallet](https://docs.near.org/tools/near-api-js/quick-reference#wallet) + * @example + * ```js + * // create new WalletConnection instance + * const wallet = new WalletConnection(near, 'my-app'); + * + * // If not signed in redirect to the NEAR wallet to sign in + * // keys will be stored in the BrowserLocalStorageKeyStore + * if(!wallet.isSignedIn()) return wallet.requestSignIn() + * ``` + */ +export class WalletConnection { + /** @hidden */ + _walletBaseUrl: string; + + /** @hidden */ + _authDataKey: string; + + /** @hidden */ + _keyStore: KeyStore; + + /** @hidden */ + _authData: { accountId?: string; allKeys?: string[] }; + + /** @hidden */ + _networkId: string; + + /** @hidden */ + // _near: Near; + _near: Near; + + /** @hidden */ + _connectedAccount: ConnectedWalletAccount; + + /** @hidden */ + _completeSignInPromise: Promise; + + constructor(near: Near, appKeyPrefix: string | null) { + if(typeof window === 'undefined') { + return new Proxy(this, { + get(target, property) { + if(property === 'isSignedIn') { + return () => false; + } + if(property === 'getAccountId') { + return () => ''; + } + if(target[property] && typeof target[property] === 'function') { + return () => { + throw new Error('No window found in context, please ensure you are using WalletConnection on the browser'); + }; + } + return target[property]; + } + }); + } + this._near = near; + const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; + const authData = JSON.parse(window.localStorage.getItem(authDataKey)); + this._networkId = near.config.networkId; + this._walletBaseUrl = near.config.walletUrl; + appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; + this._keyStore = (near.connection.signer as InMemorySigner).keyStore; + this._authData = authData || { allKeys: [] }; + this._authDataKey = authDataKey; + if (!this.isSignedIn()) { + this._completeSignInPromise = this._completeSignInWithAccessKey(); + } + } + + /** + * Returns true, if this WalletConnection is authorized with the wallet. + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.isSignedIn(); + * ``` + */ + isSignedIn() { + return !!this._authData.accountId; + } + + /** + * Returns promise of completing signing in after redirecting from wallet + * @example + * ```js + * // on login callback page + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.isSignedIn(); // false + * await wallet.isSignedInAsync(); // true + * ``` + */ + async isSignedInAsync() { + if (!this._completeSignInPromise) { + return this.isSignedIn(); + } + + await this._completeSignInPromise; + return this.isSignedIn(); + } + + /** + * Returns authorized Account ID. + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * wallet.getAccountId(); + * ``` + */ + getAccountId() { + return this._authData.accountId || ''; + } + + /** + * Redirects current page to the wallet authentication page. + * @param options An optional options object + * @param options.contractId The NEAR account where the contract is deployed + * @param options.successUrl URL to redirect upon success. Default: current url + * @param options.failureUrl URL to redirect upon failure. Default: current url + * + * @example + * ```js + * const wallet = new WalletConnection(near, 'my-app'); + * // redirects to the NEAR Wallet + * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); + * ``` + */ + async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) { + const currentUrl = new URL(window.location.href); + const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); + newUrl.searchParams.set('success_url', successUrl || currentUrl.href); + newUrl.searchParams.set('failure_url', failureUrl || currentUrl.href); + if (contractId) { + /* Throws exception if contract account does not exist */ + const contractAccount = await this._near.account(contractId); + await contractAccount.state(); + + newUrl.searchParams.set('contract_id', contractId); + const accessKey = KeyPair.fromRandom('ed25519'); + newUrl.searchParams.set('public_key', accessKey.getPublicKey().toString()); + await this._keyStore.setKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), accessKey); + } + + if (methodNames) { + methodNames.forEach(methodName => { + newUrl.searchParams.append('methodNames', methodName); + }); + } + + window.location.assign(newUrl.toString()); + } + + /** + * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet. + */ + async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise { + const currentUrl = new URL(window.location.href); + const newUrl = new URL('sign', this._walletBaseUrl); + + newUrl.searchParams.set('transactions', transactions + .map(transaction => serialize(SCHEMA, transaction)) + .map(serialized => Buffer.from(serialized).toString('base64')) + .join(',')); + newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); + if (meta) newUrl.searchParams.set('meta', meta); + + window.location.assign(newUrl.toString()); + } + + /** + * @hidden + * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. + */ + async _completeSignInWithAccessKey() { + const currentUrl = new URL(window.location.href); + const publicKey = currentUrl.searchParams.get('public_key') || ''; + const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); + const accountId = currentUrl.searchParams.get('account_id') || ''; + // TODO: Handle errors during login + if (accountId) { + const authData = { + accountId, + allKeys + }; + window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); + if (publicKey) { + await this._moveKeyFromTempToPermanent(accountId, publicKey); + } + this._authData = authData; + } + currentUrl.searchParams.delete('public_key'); + currentUrl.searchParams.delete('all_keys'); + currentUrl.searchParams.delete('account_id'); + currentUrl.searchParams.delete('meta'); + currentUrl.searchParams.delete('transactionHashes'); + + window.history.replaceState({}, document.title, currentUrl.toString()); + } + + /** + * @hidden + * @param accountId The NEAR account owning the given public key + * @param publicKey The public key being set to the key store + */ + async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { + const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + await this._keyStore.setKey(this._networkId, accountId, keyPair); + await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); + } + + /** + * Sign out from the current account + * @example + * walletConnection.signOut(); + */ + signOut() { + this._authData = {}; + window.localStorage.removeItem(this._authDataKey); + } + + /** + * Returns the current connected wallet account + */ + account() { + if (!this._connectedAccount) { + this._connectedAccount = new ConnectedWalletAccount(this, this._near.connection, this._authData.accountId); + } + return this._connectedAccount; + } +} + +/** + * {@link account!Account} implementation which redirects to wallet using {@link WalletConnection} when no local key is available. + */ +export class ConnectedWalletAccount extends Account { + walletConnection: WalletConnection; + + constructor(walletConnection: WalletConnection, connection: Connection, accountId: string) { + super(connection, accountId); + this.walletConnection = walletConnection; + } + + // Overriding Account methods + + /** + * Sign a transaction by redirecting to the NEAR Wallet + * @see {@link WalletConnection.requestSignTransactions} + */ + async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { + const localKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); + let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); + if (!accessKey) { + throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); + } + + if (localKey && localKey.toString() === accessKey.public_key) { + try { + return await super.signAndSendTransaction({ receiverId, actions }); + } catch (e) { + if (e.type === 'NotEnoughAllowance') { + accessKey = await this.accessKeyForTransaction(receiverId, actions); + } else { + throw e; + } + } + } + + const block = await this.connection.provider.block({ finality: 'final' }); + const blockHash = baseDecode(block.header.hash); + + const publicKey = PublicKey.from(accessKey.public_key); + // TODO: Cache & listen for nonce updates for given access key + 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], + meta: walletMeta, + callbackUrl: walletCallbackUrl + }); + + return new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('Failed to redirect to sign transaction')); + }, 1000); + }); + + // TODO: Aggregate multiple transaction request with "debounce". + // TODO: Introduce TrasactionQueue which also can be used to watch for status? + } + + /** + * Check if given access key allows the function call or method attempted in transaction + * @param accessKey Array of \{access_key: AccessKey, public_key: PublicKey\} items + * @param receiverId The NEAR account attempting to have access + * @param actions The action(s) needed to be checked for access + */ + async accessKeyMatchesTransaction(accessKey, receiverId: string, actions: Action[]): Promise { + const { access_key: { permission } } = accessKey; + if (permission === 'FullAccess') { + return true; + } + + if (permission.FunctionCall) { + const { receiver_id: allowedReceiverId, method_names: allowedMethods } = permission.FunctionCall; + /******************************** + Accept multisig access keys and let wallets attempt to signAndSendTransaction + If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 + ********************************/ + if (allowedReceiverId === this.accountId && allowedMethods.includes(MULTISIG_HAS_METHOD)) { + return true; + } + if (allowedReceiverId === receiverId) { + if (actions.length !== 1) { + return false; + } + const [{ functionCall }] = actions; + return functionCall && + (!functionCall.deposit || functionCall.deposit.toString() === '0') && // TODO: Should support charging amount smaller than allowance? + (allowedMethods.length === 0 || allowedMethods.includes(functionCall.methodName)); + // TODO: Handle cases when allowance doesn't have enough to pay for gas + } + } + // TODO: Support other permissions than FunctionCall + + return false; + } + + /** + * Helper function returning the access key (if it exists) to the receiver that grants the designated permission + * @param receiverId The NEAR account seeking the access key for a transaction + * @param actions The action(s) sought to gain access to + * @param localKey A local public key provided to check for access + */ + async accessKeyForTransaction(receiverId: string, actions: Action[], localKey?: PublicKey): Promise { + const accessKeys = await this.getAccessKeys(); + + if (localKey) { + const accessKey = accessKeys.find(key => key.public_key.toString() === localKey.toString()); + if (accessKey && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { + return accessKey; + } + } + + const walletKeys = this.walletConnection._authData.allKeys; + for (const accessKey of accessKeys) { + if (walletKeys.indexOf(accessKey.public_key) !== -1 && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { + return accessKey; + } + } + + return null; + } +} diff --git a/packages/connected-wallet/tsconfig.json b/packages/connected-wallet/tsconfig.json new file mode 100644 index 000000000..a77f4f8d3 --- /dev/null +++ b/packages/connected-wallet/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext", + "dom" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 67f635239..b9c083b79 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -15,6 +15,7 @@ "@near-js/client-browser": "workspace:*", "@near-js/client-core": "workspace:*", "@near-js/client-node": "workspace:*", + "@near-js/connected-wallet": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", "ajv": "^8.11.2", diff --git a/packages/near-api-js/src/near.ts b/packages/near-api-js/src/near.ts index 7ebd5cb8a..269687170 100644 --- a/packages/near-api-js/src/near.ts +++ b/packages/near-api-js/src/near.ts @@ -1,129 +1 @@ -/** - * This module contains the main class developers will use to interact with NEAR. - * The {@link Near} class is used to interact with {@link account!Account | Accounts} through the {@link providers/json-rpc-provider!JsonRpcProvider}. - * It is configured via the {@link NearConfig}. - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) - * - * @module near - */ -import BN from 'bn.js'; -import { Account } from './account'; -import { Connection } from './connection'; -import { Signer } from './signer'; -import { PublicKey } from './utils/key_pair'; -import { AccountCreator, LocalAccountCreator, UrlAccountCreator } from './account_creator'; -import { KeyStore } from './key_stores'; - -export interface NearConfig { - /** Holds {@link utils/key_pair!KeyPair | KeyPairs} for signing transactions */ - keyStore?: KeyStore; - - /** @hidden */ - signer?: Signer; - - /** - * [NEAR Contract Helper](https://github.com/near/near-contract-helper) url used to create accounts if no master account is provided - * @see {@link account_creator!UrlAccountCreator} - */ - helperUrl?: string; - - /** - * The balance transferred from the {@link masterAccount} to a created account - * @see {@link account_creator!LocalAccountCreator} - */ - initialBalance?: string; - - /** - * The account to use when creating new accounts - * @see {@link account_creator!LocalAccountCreator} - */ - masterAccount?: string; - - /** - * {@link utils/key_pair!KeyPair | KeyPairs} are stored in a {@link key_stores/keystore!KeyStore} under the `networkId` namespace. - */ - networkId: string; - - /** - * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. - * @see {@link providers/json-rpc-provider!JsonRpcProvider} - */ - nodeUrl: string; - - /** - * NEAR RPC API headers. Can be used to pass API KEY and other parameters. - * @see {@link providers/json-rpc-provider!JsonRpcProvider} - */ - headers?: { [key: string]: string | number }; - - /** - * NEAR wallet url used to redirect users to their wallet in browser applications. - * @see [https://wallet.near.org/](https://wallet.near.org/) - */ - walletUrl?: string; - - /** - * JVSM account ID for NEAR JS SDK - */ - jsvmAccountId?: string; -} - -/** - * This is the main class developers should use to interact with NEAR. - * @example - * ```js - * const near = new Near(config); - * ``` - */ -export class Near { - readonly config: any; - readonly connection: Connection; - readonly accountCreator: AccountCreator; - - constructor(config: NearConfig) { - this.config = config; - this.connection = Connection.fromConfig({ - networkId: config.networkId, - provider: { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, - signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore }, - jsvmAccountId: config.jsvmAccountId || `jsvm.${config.networkId}` - }); - - if (config.masterAccount) { - // TODO: figure out better way of specifiying initial balance. - // Hardcoded number below must be enough to pay the gas cost to dev-deploy with near-shell for multiple times - const initialBalance = config.initialBalance ? new BN(config.initialBalance) : new BN('500000000000000000000000000'); - this.accountCreator = new LocalAccountCreator(new Account(this.connection, config.masterAccount), initialBalance); - } else if (config.helperUrl) { - this.accountCreator = new UrlAccountCreator(this.connection, config.helperUrl); - } else { - this.accountCreator = null; - } - } - - /** - * @param accountId near accountId used to interact with the network. - */ - async account(accountId: string): Promise { - const account = new Account(this.connection, accountId); - return account; - } - - /** - * Create an account using the {@link account_creator!AccountCreator}. Either: - * * using a masterAccount with {@link account_creator!LocalAccountCreator} - * * using the helperUrl with {@link account_creator!UrlAccountCreator} - * @see {@link NearConfig.masterAccount} and {@link NearConfig.helperUrl} - * - * @param accountId - * @param publicKey - */ - async createAccount(accountId: string, publicKey: PublicKey): Promise { - if (!this.accountCreator) { - throw new Error('Must specify account creator, either via masterAccount or helperUrl configuration settings.'); - } - await this.accountCreator.createAccount(accountId, publicKey); - return new Account(this.connection, accountId); - } -} +export { Near, NearConfig } from '@near-js/connected-wallet'; diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts index 3c6cf4e6b..2154cb499 100644 --- a/packages/near-api-js/src/wallet-account.ts +++ b/packages/near-api-js/src/wallet-account.ts @@ -1,404 +1 @@ -/** - * The classes in this module are used in conjunction with the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. - * This module exposes two classes: - * * {@link WalletConnection} which redirects users to [NEAR Wallet](https://wallet.near.org/) for key management. - * * {@link ConnectedWalletAccount} is an {@link account!Account} implementation that uses {@link WalletConnection} to get keys - * - * @module walletAccount - */ -import { Account, SignAndSendTransactionOptions } from './account'; -import { Near } from './near'; -import { KeyStore } from './key_stores'; -import { FinalExecutionOutcome } from './providers'; -import { InMemorySigner } from './signer'; -import { Transaction, Action, SCHEMA, createTransaction } from './transaction'; -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'; -const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; -const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) - -interface SignInOptions { - contractId?: string; - methodNames?: string[]; - // TODO: Replace following with single callbackUrl - successUrl?: string; - failureUrl?: string; -} - -/** - * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application - */ -interface RequestSignTransactionsOptions { - /** list of transactions to sign */ - transactions: Transaction[]; - /** url NEAR Wallet will redirect to after transaction signing is complete */ - callbackUrl?: string; - /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ - meta?: string; -} - -/** - * This class is used in conjunction with the {@link key_stores/browser_local_storage_key_store!BrowserLocalStorageKeyStore}. - * It redirects users to [NEAR Wallet](https://wallet.near.org) for key management. - * This class is not intended for use outside the browser. Without `window` (i.e. in server contexts), it will instantiate but will throw a clear error when used. - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#wallet](https://docs.near.org/tools/near-api-js/quick-reference#wallet) - * @example - * ```js - * // create new WalletConnection instance - * const wallet = new WalletConnection(near, 'my-app'); - * - * // If not signed in redirect to the NEAR wallet to sign in - * // keys will be stored in the BrowserLocalStorageKeyStore - * if(!wallet.isSignedIn()) return wallet.requestSignIn() - * ``` - */ -export class WalletConnection { - /** @hidden */ - _walletBaseUrl: string; - - /** @hidden */ - _authDataKey: string; - - /** @hidden */ - _keyStore: KeyStore; - - /** @hidden */ - _authData: { accountId?: string; allKeys?: string[] }; - - /** @hidden */ - _networkId: string; - - /** @hidden */ - _near: Near; - - /** @hidden */ - _connectedAccount: ConnectedWalletAccount; - - /** @hidden */ - _completeSignInPromise: Promise; - - constructor(near: Near, appKeyPrefix: string) { - if(typeof(appKeyPrefix) != 'string') { - throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); - } - if(typeof window === 'undefined') { - return new Proxy(this, { - get(target, property) { - if(property === 'isSignedIn') { - return () => false; - } - if(property === 'getAccountId') { - return () => ''; - } - if(target[property] && typeof target[property] === 'function') { - return () => { - throw new Error('No window found in context, please ensure you are using WalletConnection on the browser'); - }; - } - return target[property]; - } - }); - } - this._near = near; - const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; - const authData = JSON.parse(window.localStorage.getItem(authDataKey)); - this._networkId = near.config.networkId; - this._walletBaseUrl = near.config.walletUrl; - this._keyStore = (near.connection.signer as InMemorySigner).keyStore; - this._authData = authData || { allKeys: [] }; - this._authDataKey = authDataKey; - if (!this.isSignedIn()) { - this._completeSignInPromise = this._completeSignInWithAccessKey(); - } - } - - /** - * Returns true, if this WalletConnection is authorized with the wallet. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); - * ``` - */ - isSignedIn() { - return !!this._authData.accountId; - } - - /** - * Returns promise of completing signing in after redirecting from wallet - * @example - * ```js - * // on login callback page - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); // false - * await wallet.isSignedInAsync(); // true - * ``` - */ - async isSignedInAsync() { - if (!this._completeSignInPromise) { - return this.isSignedIn(); - } - - await this._completeSignInPromise; - return this.isSignedIn(); - } - - /** - * Returns authorized Account ID. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.getAccountId(); - * ``` - */ - getAccountId() { - return this._authData.accountId || ''; - } - - /** - * Redirects current page to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url - * - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * // redirects to the NEAR Wallet - * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignIn({ contractId, methodNames, successUrl, failureUrl }: SignInOptions) { - const currentUrl = new URL(window.location.href); - const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); - newUrl.searchParams.set('success_url', successUrl || currentUrl.href); - newUrl.searchParams.set('failure_url', failureUrl || currentUrl.href); - if (contractId) { - /* Throws exception if contract account does not exist */ - const contractAccount = await this._near.account(contractId); - await contractAccount.state(); - - newUrl.searchParams.set('contract_id', contractId); - const accessKey = KeyPair.fromRandom('ed25519'); - newUrl.searchParams.set('public_key', accessKey.getPublicKey().toString()); - await this._keyStore.setKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), accessKey); - } - - if (methodNames) { - methodNames.forEach(methodName => { - newUrl.searchParams.append('methodNames', methodName); - }); - } - - window.location.assign(newUrl.toString()); - } - - /** - * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the NEAR wallet. - */ - async requestSignTransactions({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): Promise { - const currentUrl = new URL(window.location.href); - const newUrl = new URL('sign', this._walletBaseUrl); - - newUrl.searchParams.set('transactions', transactions - .map(transaction => serialize(SCHEMA, transaction)) - .map(serialized => Buffer.from(serialized).toString('base64')) - .join(',')); - newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); - if (meta) newUrl.searchParams.set('meta', meta); - - window.location.assign(newUrl.toString()); - } - - /** - * @hidden - * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. - */ - async _completeSignInWithAccessKey() { - const currentUrl = new URL(window.location.href); - const publicKey = currentUrl.searchParams.get('public_key') || ''; - const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); - const accountId = currentUrl.searchParams.get('account_id') || ''; - // TODO: Handle errors during login - if (accountId) { - const authData = { - accountId, - allKeys - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; - } - currentUrl.searchParams.delete('public_key'); - currentUrl.searchParams.delete('all_keys'); - currentUrl.searchParams.delete('account_id'); - currentUrl.searchParams.delete('meta'); - currentUrl.searchParams.delete('transactionHashes'); - - window.history.replaceState({}, document.title, currentUrl.toString()); - } - - /** - * @hidden - * @param accountId The NEAR account owning the given public key - * @param publicKey The public key being set to the key store - */ - async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { - const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - await this._keyStore.setKey(this._networkId, accountId, keyPair); - await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - } - - /** - * Sign out from the current account - * @example - * walletConnection.signOut(); - */ - signOut() { - this._authData = {}; - window.localStorage.removeItem(this._authDataKey); - } - - /** - * Returns the current connected wallet account - */ - account() { - if (!this._connectedAccount) { - this._connectedAccount = new ConnectedWalletAccount(this, this._near.connection, this._authData.accountId); - } - return this._connectedAccount; - } -} - -/** - * {@link account!Account} implementation which redirects to wallet using {@link WalletConnection} when no local key is available. - */ -export class ConnectedWalletAccount extends Account { - walletConnection: WalletConnection; - - constructor(walletConnection: WalletConnection, connection: Connection, accountId: string) { - super(connection, accountId); - this.walletConnection = walletConnection; - } - - // Overriding Account methods - - /** - * Sign a transaction by redirecting to the NEAR Wallet - * @see {@link WalletConnection.requestSignTransactions} - */ - async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { - const localKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); - let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); - if (!accessKey) { - throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); - } - - if (localKey && localKey.toString() === accessKey.public_key) { - try { - return await super.signAndSendTransaction({ receiverId, actions }); - } catch (e) { - if (e.type === 'NotEnoughAllowance') { - accessKey = await this.accessKeyForTransaction(receiverId, actions); - } else { - throw e; - } - } - } - - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = baseDecode(block.header.hash); - - const publicKey = PublicKey.from(accessKey.public_key); - // TODO: Cache & listen for nonce updates for given access key - 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], - meta: walletMeta, - callbackUrl: walletCallbackUrl - }); - - return new Promise((resolve, reject) => { - setTimeout(() => { - reject(new Error('Failed to redirect to sign transaction')); - }, 1000); - }); - - // TODO: Aggregate multiple transaction request with "debounce". - // TODO: Introduce TrasactionQueue which also can be used to watch for status? - } - - /** - * Check if given access key allows the function call or method attempted in transaction - * @param accessKey Array of \{access_key: AccessKey, public_key: PublicKey\} items - * @param receiverId The NEAR account attempting to have access - * @param actions The action(s) needed to be checked for access - */ - async accessKeyMatchesTransaction(accessKey, receiverId: string, actions: Action[]): Promise { - const { access_key: { permission } } = accessKey; - if (permission === 'FullAccess') { - return true; - } - - if (permission.FunctionCall) { - const { receiver_id: allowedReceiverId, method_names: allowedMethods } = permission.FunctionCall; - /******************************** - Accept multisig access keys and let wallets attempt to signAndSendTransaction - If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 - ********************************/ - if (allowedReceiverId === this.accountId && allowedMethods.includes(MULTISIG_HAS_METHOD)) { - return true; - } - if (allowedReceiverId === receiverId) { - if (actions.length !== 1) { - return false; - } - const [{ functionCall }] = actions; - return functionCall && - (!functionCall.deposit || functionCall.deposit.toString() === '0') && // TODO: Should support charging amount smaller than allowance? - (allowedMethods.length === 0 || allowedMethods.includes(functionCall.methodName)); - // TODO: Handle cases when allowance doesn't have enough to pay for gas - } - } - // TODO: Support other permissions than FunctionCall - - return false; - } - - /** - * Helper function returning the access key (if it exists) to the receiver that grants the designated permission - * @param receiverId The NEAR account seeking the access key for a transaction - * @param actions The action(s) sought to gain access to - * @param localKey A local public key provided to check for access - */ - async accessKeyForTransaction(receiverId: string, actions: Action[], localKey?: PublicKey): Promise { - const accessKeys = await this.getAccessKeys(); - - if (localKey) { - const accessKey = accessKeys.find(key => key.public_key.toString() === localKey.toString()); - if (accessKey && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - const walletKeys = this.walletConnection._authData.allKeys; - for (const accessKey of accessKeys) { - if (walletKeys.indexOf(accessKey.public_key) !== -1 && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - return null; - } -} +export { ConnectedWalletAccount, WalletConnection } from '@near-js/connected-wallet'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 233a0cb02..f4f1f1176 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -94,6 +94,23 @@ importers: devDependencies: '@types/node': 18.7.14 + packages/connected-wallet: + specifiers: + '@near-js/accounts': workspace:* + '@near-js/client-core': workspace:* + '@near-js/transactions': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + dependencies: + '@near-js/accounts': link:../accounts + '@near-js/client-core': link:../client-core + '@near-js/transactions': link:../transactions + bn.js: 5.2.1 + borsh: 0.7.0 + devDependencies: + '@types/node': 18.7.14 + packages/cookbook: specifiers: chalk: ^4.1.1 @@ -110,6 +127,7 @@ importers: '@near-js/client-browser': workspace:* '@near-js/client-core': workspace:* '@near-js/client-node': workspace:* + '@near-js/connected-wallet': workspace:* '@near-js/providers': workspace:* '@near-js/transactions': workspace:* '@types/bn.js': ^5.1.0 @@ -147,6 +165,7 @@ importers: '@near-js/client-browser': link:../client-browser '@near-js/client-core': link:../client-core '@near-js/client-node': link:../client-node + '@near-js/connected-wallet': link:../connected-wallet '@near-js/providers': link:../providers '@near-js/transactions': link:../transactions ajv: 8.11.2 From fc2a3cb38f067edffa8f61c5da618c15c32b6440 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 13:20:24 -0800 Subject: [PATCH 31/84] refactor: break out platform-specific packages by domain model --- .../package.json | 18 ++++++++++ .../src}/browser_local_storage_key_store.ts | 0 .../src}/index.ts | 0 .../tsconfig.json | 0 packages/client-browser/browser-exports.js | 2 -- packages/client-browser/package.json | 23 ------------- packages/client-browser/src/index.ts | 3 -- packages/client-node/src/index.ts | 1 - packages/near-api-js/package.json | 4 +-- packages/near-api-js/src/connect.ts | 2 +- .../browser_local_storage_key_store.ts | 2 +- .../unencrypted_file_system_keystore.ts | 2 +- .../package.json | 4 +-- .../src}/index.ts | 0 .../src}/unencrypted_file_system_keystore.ts | 0 .../tsconfig.json | 0 pnpm-lock.yaml | 33 ++++++++----------- 17 files changed, 39 insertions(+), 55 deletions(-) create mode 100644 packages/browser-keystore-localstorage/package.json rename packages/{client-browser/src/key_store => browser-keystore-localstorage/src}/browser_local_storage_key_store.ts (100%) rename packages/{client-browser/src/key_store => browser-keystore-localstorage/src}/index.ts (100%) rename packages/{client-browser => browser-keystore-localstorage}/tsconfig.json (100%) delete mode 100644 packages/client-browser/browser-exports.js delete mode 100644 packages/client-browser/package.json delete mode 100644 packages/client-browser/src/index.ts delete mode 100644 packages/client-node/src/index.ts rename packages/{client-node => node-keystore-filesystem}/package.json (71%) rename packages/{client-node/src/key_store => node-keystore-filesystem/src}/index.ts (100%) rename packages/{client-node/src/key_store => node-keystore-filesystem/src}/unencrypted_file_system_keystore.ts (100%) rename packages/{client-node => node-keystore-filesystem}/tsconfig.json (100%) diff --git a/packages/browser-keystore-localstorage/package.json b/packages/browser-keystore-localstorage/package.json new file mode 100644 index 000000000..dc5f92394 --- /dev/null +++ b/packages/browser-keystore-localstorage/package.json @@ -0,0 +1,18 @@ +{ + "name": "@near-js/browser-keystore-localstorage", + "version": "0.0.1", + "description": "KeyStore implementation for working with keys in browser LocalStorage", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/client-core": "workspace:*", + "error-polyfill": "^0.1.3" + } +} diff --git a/packages/client-browser/src/key_store/browser_local_storage_key_store.ts b/packages/browser-keystore-localstorage/src/browser_local_storage_key_store.ts similarity index 100% rename from packages/client-browser/src/key_store/browser_local_storage_key_store.ts rename to packages/browser-keystore-localstorage/src/browser_local_storage_key_store.ts diff --git a/packages/client-browser/src/key_store/index.ts b/packages/browser-keystore-localstorage/src/index.ts similarity index 100% rename from packages/client-browser/src/key_store/index.ts rename to packages/browser-keystore-localstorage/src/index.ts diff --git a/packages/client-browser/tsconfig.json b/packages/browser-keystore-localstorage/tsconfig.json similarity index 100% rename from packages/client-browser/tsconfig.json rename to packages/browser-keystore-localstorage/tsconfig.json diff --git a/packages/client-browser/browser-exports.js b/packages/client-browser/browser-exports.js deleted file mode 100644 index 15c716a7b..000000000 --- a/packages/client-browser/browser-exports.js +++ /dev/null @@ -1,2 +0,0 @@ -window.nearApi = require('./lib'); -window.Buffer = Buffer; diff --git a/packages/client-browser/package.json b/packages/client-browser/package.json deleted file mode 100644 index 9ac17fe4d..000000000 --- a/packages/client-browser/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "@near-js/client-browser", - "version": "0.0.1", - "description": "Dependencies for the NEAR API JavaScript client in the browser", - "main": "lib/index.js", - "scripts": { - "browserify": "browserify browser-exports.js -o dist/near-api-js.js && browserify browser-exports.js -i node-fetch -g uglifyify -o dist/near-api-js.min.js", - "build": "pnpm compile && pnpm browserify", - "compile": "tsc -p tsconfig.json", - "test": "jest test" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@near-js/client-core": "workspace:*", - "error-polyfill": "^0.1.3" - }, - "devDependencies": { - "browserify": "^16.2.3", - "uglifyify": "^5.0.1" - } -} diff --git a/packages/client-browser/src/index.ts b/packages/client-browser/src/index.ts deleted file mode 100644 index 3a688749b..000000000 --- a/packages/client-browser/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import 'error-polyfill'; - -export * from './key_store'; diff --git a/packages/client-node/src/index.ts b/packages/client-node/src/index.ts deleted file mode 100644 index 9998b34a0..000000000 --- a/packages/client-node/src/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './key_store'; diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index b9c083b79..7dd6aa2dc 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,10 +12,10 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/client-browser": "workspace:*", + "@near-js/browser-keystore-localstorage": "workspace:*", "@near-js/client-core": "workspace:*", - "@near-js/client-node": "workspace:*", "@near-js/connected-wallet": "workspace:*", + "@near-js/node-keystore-filesystem": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", "ajv": "^8.11.2", diff --git a/packages/near-api-js/src/connect.ts b/packages/near-api-js/src/connect.ts index 0b8fc9932..8b95c54d8 100644 --- a/packages/near-api-js/src/connect.ts +++ b/packages/near-api-js/src/connect.ts @@ -21,7 +21,7 @@ * ``` * @module connect */ -import { readKeyFile } from '@near-js/client-node'; +import { readKeyFile } from './key_stores/unencrypted_file_system_keystore'; import { InMemoryKeyStore, MergeKeyStore } from './key_stores'; import { Near, NearConfig } from './near'; import fetch from './utils/setup-node-fetch'; diff --git a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts index aaa83c050..9984404a6 100644 --- a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts +++ b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts @@ -1 +1 @@ -export { BrowserLocalStorageKeyStore } from '@near-js/client-browser'; \ No newline at end of file +export { BrowserLocalStorageKeyStore } from '@near-js/browser-keystore-localstorage'; \ No newline at end of file diff --git a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts index a79f4ff0d..f27819289 100644 --- a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts +++ b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts @@ -1 +1 @@ -export { UnencryptedFileSystemKeyStore } from '@near-js/client-node'; +export { readKeyFile, UnencryptedFileSystemKeyStore } from '@near-js/node-keystore-filesystem'; diff --git a/packages/client-node/package.json b/packages/node-keystore-filesystem/package.json similarity index 71% rename from packages/client-node/package.json rename to packages/node-keystore-filesystem/package.json index 36b2664d3..8274c36a5 100644 --- a/packages/client-node/package.json +++ b/packages/node-keystore-filesystem/package.json @@ -1,7 +1,7 @@ { - "name": "@near-js/client-node", + "name": "@near-js/node-keystore-filesystem", "version": "0.0.1", - "description": "Dependencies for the NEAR API JavaScript client in NodeJS", + "description": "KeyStore implementation for working with keys in the local filesystem", "main": "lib/index.js", "scripts": { "build": "pnpm compile", diff --git a/packages/client-node/src/key_store/index.ts b/packages/node-keystore-filesystem/src/index.ts similarity index 100% rename from packages/client-node/src/key_store/index.ts rename to packages/node-keystore-filesystem/src/index.ts diff --git a/packages/client-node/src/key_store/unencrypted_file_system_keystore.ts b/packages/node-keystore-filesystem/src/unencrypted_file_system_keystore.ts similarity index 100% rename from packages/client-node/src/key_store/unencrypted_file_system_keystore.ts rename to packages/node-keystore-filesystem/src/unencrypted_file_system_keystore.ts diff --git a/packages/client-node/tsconfig.json b/packages/node-keystore-filesystem/tsconfig.json similarity index 100% rename from packages/client-node/tsconfig.json rename to packages/node-keystore-filesystem/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f4f1f1176..44d1d3eb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,18 +51,13 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 - packages/client-browser: + packages/browser-keystore-localstorage: specifiers: '@near-js/client-core': workspace:* - browserify: ^16.2.3 error-polyfill: ^0.1.3 - uglifyify: ^5.0.1 dependencies: '@near-js/client-core': link:../client-core error-polyfill: 0.1.3 - devDependencies: - browserify: 16.5.2 - uglifyify: 5.0.2 packages/client-core: specifiers: @@ -85,15 +80,6 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 - packages/client-node: - specifiers: - '@near-js/client-core': workspace:* - '@types/node': ^18.7.14 - dependencies: - '@near-js/client-core': link:../client-core - devDependencies: - '@types/node': 18.7.14 - packages/connected-wallet: specifiers: '@near-js/accounts': workspace:* @@ -124,10 +110,10 @@ importers: packages/near-api-js: specifiers: '@near-js/accounts': workspace:* - '@near-js/client-browser': workspace:* + '@near-js/browser-keystore-localstorage': workspace:* '@near-js/client-core': workspace:* - '@near-js/client-node': workspace:* '@near-js/connected-wallet': workspace:* + '@near-js/node-keystore-filesystem': workspace:* '@near-js/providers': workspace:* '@near-js/transactions': workspace:* '@types/bn.js': ^5.1.0 @@ -162,10 +148,10 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/accounts': link:../accounts - '@near-js/client-browser': link:../client-browser + '@near-js/browser-keystore-localstorage': link:../browser-keystore-localstorage '@near-js/client-core': link:../client-core - '@near-js/client-node': link:../client-node '@near-js/connected-wallet': link:../connected-wallet + '@near-js/node-keystore-filesystem': link:../node-keystore-filesystem '@near-js/providers': link:../providers '@near-js/transactions': link:../transactions ajv: 8.11.2 @@ -200,6 +186,15 @@ importers: ts-jest: 26.5.6_jest@26.6.3 uglifyify: 5.0.2 + packages/node-keystore-filesystem: + specifiers: + '@near-js/client-core': workspace:* + '@types/node': ^18.7.14 + dependencies: + '@near-js/client-core': link:../client-core + devDependencies: + '@types/node': 18.7.14 + packages/providers: specifiers: '@near-js/client-core': workspace:* From ec417f16fe7e6f70a487eaa01b23a2538d63727a Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 14:54:01 -0800 Subject: [PATCH 32/84] feat: connected-wallet package --- packages/connected-wallet/src/index.ts | 2 +- packages/connected-wallet/src/near.ts | 2 +- .../src/{wallet-account.ts => wallet_account.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/connected-wallet/src/{wallet-account.ts => wallet_account.ts} (100%) diff --git a/packages/connected-wallet/src/index.ts b/packages/connected-wallet/src/index.ts index 1172cd5ad..ea0f840b6 100644 --- a/packages/connected-wallet/src/index.ts +++ b/packages/connected-wallet/src/index.ts @@ -1,2 +1,2 @@ export { Near, NearConfig } from './near'; -export { ConnectedWalletAccount, WalletConnection } from './wallet-account'; \ No newline at end of file +export { ConnectedWalletAccount, WalletConnection } from './wallet_account'; \ No newline at end of file diff --git a/packages/connected-wallet/src/near.ts b/packages/connected-wallet/src/near.ts index f22ab8d78..cad784558 100644 --- a/packages/connected-wallet/src/near.ts +++ b/packages/connected-wallet/src/near.ts @@ -83,7 +83,7 @@ export interface NearConfig { * ``` */ export class Near { - readonly config: NearConfig; + readonly config: any; readonly connection: Connection; readonly accountCreator: AccountCreator; diff --git a/packages/connected-wallet/src/wallet-account.ts b/packages/connected-wallet/src/wallet_account.ts similarity index 100% rename from packages/connected-wallet/src/wallet-account.ts rename to packages/connected-wallet/src/wallet_account.ts From f8c3114a31fa6fc83c9ff92cf8f23f3f987eb6c8 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 14:54:53 -0800 Subject: [PATCH 33/84] chore: remove old dependencies --- packages/near-api-js/package.json | 5 +---- pnpm-lock.yaml | 10 +--------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 7dd6aa2dc..7b9c9c4f8 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -22,15 +22,11 @@ "ajv-formats": "^2.1.1", "bn.js": "5.2.1", "borsh": "^0.7.0", - "bs58": "^4.0.0", "depd": "^2.0.0", "error-polyfill": "^0.1.3", "http-errors": "^1.7.2", - "js-sha256": "^0.9.0", - "mustache": "^4.0.0", "near-abi": "0.1.1", "node-fetch": "^2.6.1", - "text-encoding-utf-8": "^1.0.2", "tweetnacl": "^1.0.1" }, "devDependencies": { @@ -38,6 +34,7 @@ "@types/http-errors": "^1.6.1", "@types/node": "^18.7.14", "browserify": "^16.2.3", + "bs58": "^4.0.0", "bundlewatch": "^0.3.1", "concurrently": "^7.3.0", "danger": "^11.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 44d1d3eb0..be7cd0035 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,15 +134,12 @@ importers: http-errors: ^1.7.2 in-publish: ^2.0.0 jest: ^26.0.1 - js-sha256: ^0.9.0 localstorage-memory: ^1.0.3 - mustache: ^4.0.0 near-abi: 0.1.1 near-hello: ^0.5.1 node-fetch: ^2.6.1 rimraf: ^3.0.0 semver: ^7.1.1 - text-encoding-utf-8: ^1.0.2 ts-jest: ^26.5.6 tweetnacl: ^1.0.1 uglifyify: ^5.0.1 @@ -158,21 +155,18 @@ importers: ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 borsh: 0.7.0 - bs58: 4.0.1 depd: 2.0.0 error-polyfill: 0.1.3 http-errors: 1.8.1 - js-sha256: 0.9.0 - mustache: 4.2.0 near-abi: 0.1.1 node-fetch: 2.6.7 - text-encoding-utf-8: 1.0.2 tweetnacl: 1.0.3 devDependencies: '@types/bn.js': 5.1.1 '@types/http-errors': 1.8.2 '@types/node': 18.11.15 browserify: 16.5.2 + bs58: 4.0.1 bundlewatch: 0.3.3 concurrently: 7.6.0 danger: 11.2.0 @@ -2068,7 +2062,6 @@ packages: resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} dependencies: safe-buffer: 5.2.1 - dev: false /base/0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} @@ -2309,7 +2302,6 @@ packages: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} dependencies: base-x: 3.0.9 - dev: false /bser/2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} From bf1295de4f988410aca549fc72935125b49c3da1 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 14:56:00 -0800 Subject: [PATCH 34/84] refactor: break out platform-specific packages by domain model --- packages/browser-keystore-localstorage/package.json | 3 +-- pnpm-lock.yaml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/browser-keystore-localstorage/package.json b/packages/browser-keystore-localstorage/package.json index dc5f92394..6475af837 100644 --- a/packages/browser-keystore-localstorage/package.json +++ b/packages/browser-keystore-localstorage/package.json @@ -12,7 +12,6 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*", - "error-polyfill": "^0.1.3" + "@near-js/client-core": "workspace:*" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be7cd0035..c9d9e70d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,10 +54,8 @@ importers: packages/browser-keystore-localstorage: specifiers: '@near-js/client-core': workspace:* - error-polyfill: ^0.1.3 dependencies: '@near-js/client-core': link:../client-core - error-polyfill: 0.1.3 packages/client-core: specifiers: From 7ce12c56d27c68ffbcb4bc5d2d29f0e3cb0ed81d Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 15:24:52 -0800 Subject: [PATCH 35/84] refactor: move validators to core --- packages/client-core/package.json | 1 + packages/client-core/src/index.ts | 1 + packages/client-core/src/validators.ts | 95 +++++++++++++++++++++++ packages/near-api-js/src/validators.ts | 100 ++----------------------- pnpm-lock.yaml | 2 + 5 files changed, 105 insertions(+), 94 deletions(-) create mode 100644 packages/client-core/src/validators.ts diff --git a/packages/client-core/package.json b/packages/client-core/package.json index 8063f748b..bd674db6f 100644 --- a/packages/client-core/package.json +++ b/packages/client-core/package.json @@ -14,6 +14,7 @@ "dependencies": { "bn.js": "5.2.1", "borsh": "^0.7.0", + "depd": "^2.0.0", "js-sha256": "^0.9.0", "mustache": "^4.0.0", "tweetnacl": "^1.0.1" diff --git a/packages/client-core/src/index.ts b/packages/client-core/src/index.ts index 0797f3238..4c0361afe 100644 --- a/packages/client-core/src/index.ts +++ b/packages/client-core/src/index.ts @@ -7,3 +7,4 @@ export * from './logging'; export * from './provider'; export * from './signer'; export * from './types'; +export * from './validators'; diff --git a/packages/client-core/src/validators.ts b/packages/client-core/src/validators.ts new file mode 100644 index 000000000..979f92511 --- /dev/null +++ b/packages/client-core/src/validators.ts @@ -0,0 +1,95 @@ +'use strict'; + +import BN from 'bn.js'; +import depd from 'depd'; + +import { CurrentEpochValidatorInfo, NextEpochValidatorInfo } from './provider'; + +/** Finds seat price given validators stakes and number of seats. + * Calculation follow the spec: https://nomicon.io/Economics/README.html#validator-selection + * @params validators: current or next epoch validators. + * @params maxNumberOfSeats: maximum number of seats in the network. + * @params minimumStakeRatio: minimum stake ratio + * @params protocolVersion: version of the protocol from genesis config + */ +export function findSeatPrice(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], maxNumberOfSeats: number, minimumStakeRatio: number[], protocolVersion?: number): BN { + if (protocolVersion && protocolVersion < 49) { + return findSeatPriceForProtocolBefore49(validators, maxNumberOfSeats); + } + if (!minimumStakeRatio) { + const deprecate = depd('findSeatPrice(validators, maxNumberOfSeats)'); + deprecate('`use `findSeatPrice(validators, maxNumberOfSeats, minimumStakeRatio)` instead'); + minimumStakeRatio = [1, 6250]; // harcoded minimumStakeRation from 12/7/21 + } + return findSeatPriceForProtocolAfter49(validators, maxNumberOfSeats, minimumStakeRatio); +} + +function findSeatPriceForProtocolBefore49(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], numSeats: number): BN { + const stakes = validators.map(v => new BN(v.stake, 10)).sort((a, b) => a.cmp(b)); + const num = new BN(numSeats); + const stakesSum = stakes.reduce((a, b) => a.add(b)); + if (stakesSum.lt(num)) { + throw new Error('Stakes are below seats'); + } + // assert stakesSum >= numSeats + let left = new BN(1), right = stakesSum.add(new BN(1)); + while (!left.eq(right.sub(new BN(1)))) { + const mid = left.add(right).div(new BN(2)); + let found = false; + let currentSum = new BN(0); + for (let i = 0; i < stakes.length; ++i) { + currentSum = currentSum.add(stakes[i].div(mid)); + if (currentSum.gte(num)) { + left = mid; + found = true; + break; + } + } + if (!found) { + right = mid; + } + } + return left; +} + +// nearcore reference: https://github.com/near/nearcore/blob/5a8ae263ec07930cd34d0dcf5bcee250c67c02aa/chain/epoch_manager/src/validator_selection.rs#L308;L315 +function findSeatPriceForProtocolAfter49(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], maxNumberOfSeats: number, minimumStakeRatio: number[]): BN { + if (minimumStakeRatio.length != 2) { + throw Error('minimumStakeRatio should have 2 elements'); + } + const stakes = validators.map(v => new BN(v.stake, 10)).sort((a, b) => a.cmp(b)); + const stakesSum = stakes.reduce((a, b) => a.add(b)); + if (validators.length < maxNumberOfSeats) { + return stakesSum.mul(new BN(minimumStakeRatio[0])).div(new BN(minimumStakeRatio[1])); + } else { + return stakes[0].add(new BN(1)); + } +} + +export interface ChangedValidatorInfo { + current: CurrentEpochValidatorInfo; + next: NextEpochValidatorInfo; +} + +export interface EpochValidatorsDiff { + newValidators: NextEpochValidatorInfo[]; + removedValidators: CurrentEpochValidatorInfo[]; + changedValidators: ChangedValidatorInfo[]; +} + +/** Diff validators between current and next epoch. + * Returns additions, subtractions and changes to validator set. + * @params currentValidators: list of current validators. + * @params nextValidators: list of next validators. + */ +export function diffEpochValidators(currentValidators: CurrentEpochValidatorInfo[], nextValidators: NextEpochValidatorInfo[]): EpochValidatorsDiff { + const validatorsMap = new Map(); + currentValidators.forEach(v => validatorsMap.set(v.account_id, v)); + const nextValidatorsSet = new Set(nextValidators.map(v => v.account_id)); + return { + newValidators: nextValidators.filter(v => !validatorsMap.has(v.account_id)), + removedValidators: currentValidators.filter(v => !nextValidatorsSet.has(v.account_id)), + changedValidators: nextValidators.filter(v => (validatorsMap.has(v.account_id) && validatorsMap.get(v.account_id).stake != v.stake)) + .map(v => ({ current: validatorsMap.get(v.account_id), next: v })) + }; +} diff --git a/packages/near-api-js/src/validators.ts b/packages/near-api-js/src/validators.ts index f725a359b..8e39eb6b2 100644 --- a/packages/near-api-js/src/validators.ts +++ b/packages/near-api-js/src/validators.ts @@ -1,94 +1,6 @@ -'use strict'; - -import BN from 'bn.js'; -import depd from 'depd'; -import { CurrentEpochValidatorInfo, NextEpochValidatorInfo } from './providers/provider'; - -/** Finds seat price given validators stakes and number of seats. - * Calculation follow the spec: https://nomicon.io/Economics/README.html#validator-selection - * @params validators: current or next epoch validators. - * @params maxNumberOfSeats: maximum number of seats in the network. - * @params minimumStakeRatio: minimum stake ratio - * @params protocolVersion: version of the protocol from genesis config - */ -export function findSeatPrice(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], maxNumberOfSeats: number, minimumStakeRatio: number[], protocolVersion?: number): BN { - if (protocolVersion && protocolVersion < 49) { - return findSeatPriceForProtocolBefore49(validators, maxNumberOfSeats); - } - if (!minimumStakeRatio) { - const deprecate = depd('findSeatPrice(validators, maxNumberOfSeats)'); - deprecate('`use `findSeatPrice(validators, maxNumberOfSeats, minimumStakeRatio)` instead'); - minimumStakeRatio = [1, 6250]; // harcoded minimumStakeRation from 12/7/21 - } - return findSeatPriceForProtocolAfter49(validators, maxNumberOfSeats, minimumStakeRatio); -} - -function findSeatPriceForProtocolBefore49(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], numSeats: number): BN { - const stakes = validators.map(v => new BN(v.stake, 10)).sort((a, b) => a.cmp(b)); - const num = new BN(numSeats); - const stakesSum = stakes.reduce((a, b) => a.add(b)); - if (stakesSum.lt(num)) { - throw new Error('Stakes are below seats'); - } - // assert stakesSum >= numSeats - let left = new BN(1), right = stakesSum.add(new BN(1)); - while (!left.eq(right.sub(new BN(1)))) { - const mid = left.add(right).div(new BN(2)); - let found = false; - let currentSum = new BN(0); - for (let i = 0; i < stakes.length; ++i) { - currentSum = currentSum.add(stakes[i].div(mid)); - if (currentSum.gte(num)) { - left = mid; - found = true; - break; - } - } - if (!found) { - right = mid; - } - } - return left; -} - -// nearcore reference: https://github.com/near/nearcore/blob/5a8ae263ec07930cd34d0dcf5bcee250c67c02aa/chain/epoch_manager/src/validator_selection.rs#L308;L315 -function findSeatPriceForProtocolAfter49(validators: (CurrentEpochValidatorInfo | NextEpochValidatorInfo)[], maxNumberOfSeats: number, minimumStakeRatio: number[]): BN { - if (minimumStakeRatio.length != 2) { - throw Error('minimumStakeRatio should have 2 elements'); - } - const stakes = validators.map(v => new BN(v.stake, 10)).sort((a, b) => a.cmp(b)); - const stakesSum = stakes.reduce((a, b) => a.add(b)); - if (validators.length < maxNumberOfSeats) { - return stakesSum.mul(new BN(minimumStakeRatio[0])).div(new BN(minimumStakeRatio[1])); - } else { - return stakes[0].add(new BN(1)); - } -} - -export interface ChangedValidatorInfo { - current: CurrentEpochValidatorInfo; - next: NextEpochValidatorInfo; -} - -export interface EpochValidatorsDiff { - newValidators: NextEpochValidatorInfo[]; - removedValidators: CurrentEpochValidatorInfo[]; - changedValidators: ChangedValidatorInfo[]; -} - -/** Diff validators between current and next epoch. - * Returns additions, subtractions and changes to validator set. - * @params currentValidators: list of current validators. - * @params nextValidators: list of next validators. - */ -export function diffEpochValidators(currentValidators: CurrentEpochValidatorInfo[], nextValidators: NextEpochValidatorInfo[]): EpochValidatorsDiff { - const validatorsMap = new Map(); - currentValidators.forEach(v => validatorsMap.set(v.account_id, v)); - const nextValidatorsSet = new Set(nextValidators.map(v => v.account_id)); - return { - newValidators: nextValidators.filter(v => !validatorsMap.has(v.account_id)), - removedValidators: currentValidators.filter(v => !nextValidatorsSet.has(v.account_id)), - changedValidators: nextValidators.filter(v => (validatorsMap.has(v.account_id) && validatorsMap.get(v.account_id).stake != v.stake)) - .map(v => ({ current: validatorsMap.get(v.account_id), next: v })) - }; -} +export { + diffEpochValidators, + findSeatPrice, + ChangedValidatorInfo, + EpochValidatorsDiff, +} from '@near-js/client-core'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9d9e70d3..f8a7c25f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,7 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + depd: ^2.0.0 jest: ^26.0.1 js-sha256: ^0.9.0 mustache: ^4.0.0 @@ -70,6 +71,7 @@ importers: dependencies: bn.js: 5.2.1 borsh: 0.7.0 + depd: 2.0.0 js-sha256: 0.9.0 mustache: 4.2.0 tweetnacl: 1.0.3 From 3932efe569453cb48170c0ed26b153c548d12c98 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 16:05:45 -0800 Subject: [PATCH 36/84] refactor: move contract to accounts --- packages/accounts/package.json | 3 +- packages/accounts/src/contract.ts | 167 ++++++++++++++++++ packages/accounts/src/index.ts | 4 + packages/near-api-js/src/contract.ts | 246 +-------------------------- 4 files changed, 174 insertions(+), 246 deletions(-) create mode 100644 packages/accounts/src/contract.ts diff --git a/packages/accounts/package.json b/packages/accounts/package.json index d7db23248..ecb27bd02 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -16,7 +16,8 @@ "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", "bn.js": "5.2.1", - "borsh": "^0.7.0" + "borsh": "^0.7.0", + "depd": "^2.0.0" }, "devDependencies": { "@types/node": "^18.7.14", diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts new file mode 100644 index 000000000..72c63698f --- /dev/null +++ b/packages/accounts/src/contract.ts @@ -0,0 +1,167 @@ +import { getTransactionLastResult, PositionalArgsError, ArgumentTypeError } from '@near-js/client-core'; +import BN from 'bn.js'; +import depd from 'depd'; + +import { Account } from './account'; + +// Makes `function.name` return given name +function nameFunction(name: string, body: (args?: any[]) => any) { + return { + [name](...args: any[]) { + return body(...args); + } + }[name]; +} + +const isUint8Array = (x: any) => + x && x.byteLength !== undefined && x.byteLength === x.length; + +const isObject = (x: any) => + Object.prototype.toString.call(x) === '[object Object]'; + +interface ChangeMethodOptions { + args: object; + methodName: string; + gas?: BN; + amount?: BN; + meta?: string; + callbackUrl?: string; +} + +export interface ContractMethods { + /** + * Methods that change state. These methods cost gas and require a signed transaction. + * + * @see {@link account!Account.functionCall} + */ + changeMethods: string[]; + + /** + * View methods do not require a signed transaction. + * + * @see {@link account!Account#viewFunction} + */ + viewMethods: string[]; +} + +/** + * Defines a smart contract on NEAR including the change (mutable) and view (non-mutable) methods + * + * @see [https://docs.near.org/tools/near-api-js/quick-reference#contract](https://docs.near.org/tools/near-api-js/quick-reference#contract) + * @example + * ```js + * import { Contract } from 'near-api-js'; + * + * async function contractExample() { + * const methodOptions = { + * viewMethods: ['getMessageByAccountId'], + * changeMethods: ['addMessage'] + * }; + * const contract = new Contract( + * wallet.account(), + * 'contract-id.testnet', + * methodOptions + * ); + * + * // use a contract view method + * const messages = await contract.getMessages({ + * accountId: 'example-account.testnet' + * }); + * + * // use a contract change method + * await contract.addMessage({ + * meta: 'some info', + * callbackUrl: 'https://example.com/callback', + * args: { text: 'my message' }, + * amount: 1 + * }) + * } + * ``` + */ +export class Contract { + readonly account: Account; + readonly contractId: string; + + /** + * @param account NEAR account to sign change method transactions + * @param contractId NEAR account id where the contract is deployed + * @param options NEAR smart contract methods that your application will use. These will be available as `contract.methodName` + */ + constructor(account: Account, contractId: string, options: ContractMethods) { + this.account = account; + this.contractId = contractId; + const { viewMethods = [], changeMethods = [] } = options; + viewMethods.forEach((methodName) => { + Object.defineProperty(this, methodName, { + writable: false, + enumerable: true, + value: nameFunction(methodName, async (args: object = {}, options = {}, ...ignored) => { + if (ignored.length || !(isObject(args) || isUint8Array(args)) || !isObject(options)) { + throw new PositionalArgsError(); + } + return this.account.viewFunction({ + contractId: this.contractId, + methodName, + args, + ...options, + }); + }) + }); + }); + changeMethods.forEach((methodName) => { + Object.defineProperty(this, methodName, { + writable: false, + enumerable: true, + value: nameFunction(methodName, async (...args: any[]) => { + if (args.length && (args.length > 3 || !(isObject(args[0]) || isUint8Array(args[0])))) { + throw new PositionalArgsError(); + } + + if(args.length > 1 || !(args[0] && args[0].args)) { + const deprecate = depd('contract.methodName(args, gas, amount)'); + deprecate('use `contract.methodName({ args, gas?, amount?, callbackUrl?, meta? })` instead'); + return this._changeMethod({ + methodName, + args: args[0], + gas: args[1], + amount: args[2] + }); + } + + return this._changeMethod({ methodName, ...args[0] }); + }) + }); + }); + } + + private async _changeMethod({ args, methodName, gas, amount, meta, callbackUrl }: ChangeMethodOptions) { + validateBNLike({ gas, amount }); + + const rawResult = await this.account.functionCall({ + contractId: this.contractId, + methodName, + args, + gas, + attachedDeposit: amount, + walletMeta: meta, + walletCallbackUrl: callbackUrl + }); + + return getTransactionLastResult(rawResult); + } +} + +/** + * Validation on arguments being a big number from bn.js + * Throws if an argument is not in BN format or otherwise invalid + * @param argMap + */ +function validateBNLike(argMap: { [name: string]: any }) { + const bnLike = 'number, decimal string or BN'; + for (const argName of Object.keys(argMap)) { + const argValue = argMap[argName]; + if (argValue && !BN.isBN(argValue) && isNaN(argValue)) { + throw new ArgumentTypeError(argName, bnLike, argValue); + } + } +} diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index 4cf984b0f..cb1778b0b 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -23,6 +23,10 @@ export { MULTISIG_CHANGE_METHODS, MULTISIG_CONFIRM_METHODS, } from './constants'; +export { + Contract, + ContractMethods, +} from './contract'; export { MultisigDeleteRequestRejectionError, MultisigStateStatus, diff --git a/packages/near-api-js/src/contract.ts b/packages/near-api-js/src/contract.ts index 01c4b4fa2..926d26143 100644 --- a/packages/near-api-js/src/contract.ts +++ b/packages/near-api-js/src/contract.ts @@ -1,245 +1 @@ -import Ajv from 'ajv'; -import addFormats from 'ajv-formats'; -import BN from 'bn.js'; -import depd from 'depd'; -import { AbiFunction, AbiFunctionKind, AbiRoot, AbiSerializationType } from 'near-abi'; -import { Account } from './account'; -import { getTransactionLastResult } from './providers'; -import { PositionalArgsError, ArgumentTypeError, UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './utils/errors'; - -// Makes `function.name` return given name -function nameFunction(name: string, body: (args?: any[]) => any) { - return { - [name](...args: any[]) { - return body(...args); - } - }[name]; -} - -function validateArguments(args: object, abiFunction: AbiFunction, ajv: Ajv, abiRoot: AbiRoot) { - if (!isObject(args)) return; - - if (abiFunction.params && abiFunction.params.serialization_type !== AbiSerializationType.Json) { - throw new UnsupportedSerializationError(abiFunction.name, abiFunction.params.serialization_type); - } - - if (abiFunction.result && abiFunction.result.serialization_type !== AbiSerializationType.Json) { - throw new UnsupportedSerializationError(abiFunction.name, abiFunction.result.serialization_type); - } - - const params = abiFunction.params?.args || []; - for (const p of params) { - const arg = args[p.name]; - const typeSchema = p.type_schema; - typeSchema.definitions = abiRoot.body.root_schema.definitions; - const validate = ajv.compile(typeSchema); - if (!validate(arg)) { - throw new ArgumentSchemaError(p.name, validate.errors); - } - } - // Check there are no extra unknown arguments passed - for (const argName of Object.keys(args)) { - const param = params.find((p) => p.name === argName); - if (!param) { - throw new UnknownArgumentError(argName, params.map((p) => p.name)); - } - } -} - -function createAjv() { - // Strict mode is disabled for now as it complains about unknown formats. We need to - // figure out if we want to support a fixed set of formats. `uint32` and `uint64` - // are added explicitly just to reduce the amount of warnings as these are very popular - // types. - const ajv = new Ajv({ - strictSchema: false, - formats: { - uint32: true, - uint64: true - } - }); - addFormats(ajv); - return ajv; -} - -const isUint8Array = (x: any) => - x && x.byteLength !== undefined && x.byteLength === x.length; - -const isObject = (x: any) => - Object.prototype.toString.call(x) === '[object Object]'; - -interface ChangeMethodOptions { - args: object; - methodName: string; - gas?: BN; - amount?: BN; - meta?: string; - callbackUrl?: string; -} - -export interface ContractMethods { - /** - * Methods that change state. These methods cost gas and require a signed transaction. - * - * @see {@link account!Account.functionCall} - */ - changeMethods: string[]; - - /** - * View methods do not require a signed transaction. - * - * @see {@link account!Account#viewFunction} - */ - viewMethods: string[]; - - /** - * ABI defining this contract's interface. - */ - abi: AbiRoot; -} - -/** - * Defines a smart contract on NEAR including the change (mutable) and view (non-mutable) methods - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#contract](https://docs.near.org/tools/near-api-js/quick-reference#contract) - * @example - * ```js - * import { Contract } from 'near-api-js'; - * - * async function contractExample() { - * const methodOptions = { - * viewMethods: ['getMessageByAccountId'], - * changeMethods: ['addMessage'] - * }; - * const contract = new Contract( - * wallet.account(), - * 'contract-id.testnet', - * methodOptions - * ); - * - * // use a contract view method - * const messages = await contract.getMessages({ - * accountId: 'example-account.testnet' - * }); - * - * // use a contract change method - * await contract.addMessage({ - * meta: 'some info', - * callbackUrl: 'https://example.com/callback', - * args: { text: 'my message' }, - * amount: 1 - * }) - * } - * ``` - */ -export class Contract { - readonly account: Account; - readonly contractId: string; - - /** - * @param account NEAR account to sign change method transactions - * @param contractId NEAR account id where the contract is deployed - * @param options NEAR smart contract methods that your application will use. These will be available as `contract.methodName` - */ - constructor(account: Account, contractId: string, options: ContractMethods) { - this.account = account; - this.contractId = contractId; - const { viewMethods = [], changeMethods = [], abi: abiRoot } = options; - - let viewMethodsWithAbi = viewMethods.map((name) => ({ name, abi: null as AbiFunction })); - let changeMethodsWithAbi = changeMethods.map((name) => ({ name, abi: null as AbiFunction })); - if (abiRoot) { - if (viewMethodsWithAbi.length > 0 || changeMethodsWithAbi.length > 0) { - throw new ConflictingOptions(); - } - viewMethodsWithAbi = abiRoot.body.functions - .filter((m) => m.kind === AbiFunctionKind.View) - .map((m) => ({ name: m.name, abi: m })); - changeMethodsWithAbi = abiRoot.body.functions - .filter((methodAbi) => methodAbi.kind === AbiFunctionKind.Call) - .map((methodAbi) => ({ name: methodAbi.name, abi: methodAbi })); - } - - const ajv = createAjv(); - viewMethodsWithAbi.forEach(({ name, abi }) => { - Object.defineProperty(this, name, { - writable: false, - enumerable: true, - value: nameFunction(name, async (args: object = {}, options = {}, ...ignored) => { - if (ignored.length || !(isObject(args) || isUint8Array(args)) || !isObject(options)) { - throw new PositionalArgsError(); - } - - if (abi) { - validateArguments(args, abi, ajv, abiRoot); - } - - return this.account.viewFunction({ - contractId: this.contractId, - methodName: name, - args, - ...options, - }); - }) - }); - }); - changeMethodsWithAbi.forEach(({ name, abi }) => { - Object.defineProperty(this, name, { - writable: false, - enumerable: true, - value: nameFunction(name, async (...args: any[]) => { - if (args.length && (args.length > 3 || !(isObject(args[0]) || isUint8Array(args[0])))) { - throw new PositionalArgsError(); - } - - if (args.length > 1 || !(args[0] && args[0].args)) { - const deprecate = depd('contract.methodName(args, gas, amount)'); - deprecate('use `contract.methodName({ args, gas?, amount?, callbackUrl?, meta? })` instead'); - args[0] = { - args: args[0], - gas: args[1], - amount: args[2] - }; - } - - if (abi) { - validateArguments(args[0].args, abi, ajv, abiRoot); - } - - return this._changeMethod({ methodName: name, ...args[0] }); - }) - }); - }); - } - - private async _changeMethod({ args, methodName, gas, amount, meta, callbackUrl }: ChangeMethodOptions) { - validateBNLike({ gas, amount }); - - const rawResult = await this.account.functionCall({ - contractId: this.contractId, - methodName, - args, - gas, - attachedDeposit: amount, - walletMeta: meta, - walletCallbackUrl: callbackUrl - }); - - return getTransactionLastResult(rawResult); - } -} - -/** - * Validation on arguments being a big number from bn.js - * Throws if an argument is not in BN format or otherwise invalid - * @param argMap - */ -function validateBNLike(argMap: { [name: string]: any }) { - const bnLike = 'number, decimal string or BN'; - for (const argName of Object.keys(argMap)) { - const argValue = argMap[argName]; - if (argValue && !BN.isBN(argValue) && isNaN(argValue)) { - throw new ArgumentTypeError(argName, bnLike, argValue); - } - } -} +export { Contract, ContractMethods } from '@near-js/accounts'; From 17c6157ae89c48aef7089299d129622d78c3fca2 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 2 Dec 2022 16:29:18 -0800 Subject: [PATCH 37/84] refactor: rename connected-wallet to wallet-account --- packages/near-api-js/package.json | 2 +- packages/near-api-js/src/near.ts | 2 +- packages/near-api-js/src/wallet-account.ts | 2 +- .../package.json | 2 +- .../src/index.ts | 0 .../src/near.ts | 0 .../src/wallet_account.ts | 0 .../tsconfig.json | 0 pnpm-lock.yaml | 40 ++++++++++--------- 9 files changed, 25 insertions(+), 23 deletions(-) rename packages/{connected-wallet => wallet-account}/package.json (93%) rename packages/{connected-wallet => wallet-account}/src/index.ts (100%) rename packages/{connected-wallet => wallet-account}/src/near.ts (100%) rename packages/{connected-wallet => wallet-account}/src/wallet_account.ts (100%) rename packages/{connected-wallet => wallet-account}/tsconfig.json (100%) diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 7b9c9c4f8..ccf7c3c63 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -14,7 +14,7 @@ "@near-js/accounts": "workspace:*", "@near-js/browser-keystore-localstorage": "workspace:*", "@near-js/client-core": "workspace:*", - "@near-js/connected-wallet": "workspace:*", + "@near-js/wallet-account": "workspace:*", "@near-js/node-keystore-filesystem": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/transactions": "workspace:*", diff --git a/packages/near-api-js/src/near.ts b/packages/near-api-js/src/near.ts index 269687170..c75aa3c7d 100644 --- a/packages/near-api-js/src/near.ts +++ b/packages/near-api-js/src/near.ts @@ -1 +1 @@ -export { Near, NearConfig } from '@near-js/connected-wallet'; +export { Near, NearConfig } from '@near-js/wallet-account'; diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts index 2154cb499..172cdfa31 100644 --- a/packages/near-api-js/src/wallet-account.ts +++ b/packages/near-api-js/src/wallet-account.ts @@ -1 +1 @@ -export { ConnectedWalletAccount, WalletConnection } from '@near-js/connected-wallet'; +export { ConnectedWalletAccount, WalletConnection } from '@near-js/wallet-account'; diff --git a/packages/connected-wallet/package.json b/packages/wallet-account/package.json similarity index 93% rename from packages/connected-wallet/package.json rename to packages/wallet-account/package.json index edddaa782..9d0d921dc 100644 --- a/packages/connected-wallet/package.json +++ b/packages/wallet-account/package.json @@ -1,5 +1,5 @@ { - "name": "@near-js/connected-wallet", + "name": "@near-js/wallet-account", "version": "0.0.1", "description": "Dependencies for the NEAR API JavaScript client in the browser", "main": "lib/index.js", diff --git a/packages/connected-wallet/src/index.ts b/packages/wallet-account/src/index.ts similarity index 100% rename from packages/connected-wallet/src/index.ts rename to packages/wallet-account/src/index.ts diff --git a/packages/connected-wallet/src/near.ts b/packages/wallet-account/src/near.ts similarity index 100% rename from packages/connected-wallet/src/near.ts rename to packages/wallet-account/src/near.ts diff --git a/packages/connected-wallet/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts similarity index 100% rename from packages/connected-wallet/src/wallet_account.ts rename to packages/wallet-account/src/wallet_account.ts diff --git a/packages/connected-wallet/tsconfig.json b/packages/wallet-account/tsconfig.json similarity index 100% rename from packages/connected-wallet/tsconfig.json rename to packages/wallet-account/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8a7c25f6..3171f2083 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,7 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + depd: ^2.0.0 jest: ^26.0.1 ts-jest: ^26.5.6 dependencies: @@ -46,6 +47,7 @@ importers: '@near-js/transactions': link:../transactions bn.js: 5.2.1 borsh: 0.7.0 + depd: 2.0.0 devDependencies: '@types/node': 18.7.14 jest: 26.6.3 @@ -80,23 +82,6 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 - packages/connected-wallet: - specifiers: - '@near-js/accounts': workspace:* - '@near-js/client-core': workspace:* - '@near-js/transactions': workspace:* - '@types/node': ^18.7.14 - bn.js: 5.2.1 - borsh: ^0.7.0 - dependencies: - '@near-js/accounts': link:../accounts - '@near-js/client-core': link:../client-core - '@near-js/transactions': link:../transactions - bn.js: 5.2.1 - borsh: 0.7.0 - devDependencies: - '@types/node': 18.7.14 - packages/cookbook: specifiers: chalk: ^4.1.1 @@ -112,10 +97,10 @@ importers: '@near-js/accounts': workspace:* '@near-js/browser-keystore-localstorage': workspace:* '@near-js/client-core': workspace:* - '@near-js/connected-wallet': workspace:* '@near-js/node-keystore-filesystem': workspace:* '@near-js/providers': workspace:* '@near-js/transactions': workspace:* + '@near-js/wallet-account': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 '@types/node': ^18.7.14 @@ -147,10 +132,10 @@ importers: '@near-js/accounts': link:../accounts '@near-js/browser-keystore-localstorage': link:../browser-keystore-localstorage '@near-js/client-core': link:../client-core - '@near-js/connected-wallet': link:../connected-wallet '@near-js/node-keystore-filesystem': link:../node-keystore-filesystem '@near-js/providers': link:../providers '@near-js/transactions': link:../transactions + '@near-js/wallet-account': link:../wallet-account ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 @@ -236,6 +221,23 @@ importers: devDependencies: '@types/node': 18.7.14 + packages/wallet-account: + specifiers: + '@near-js/accounts': workspace:* + '@near-js/client-core': workspace:* + '@near-js/transactions': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + dependencies: + '@near-js/accounts': link:../accounts + '@near-js/client-core': link:../client-core + '@near-js/transactions': link:../transactions + bn.js: 5.2.1 + borsh: 0.7.0 + devDependencies: + '@types/node': 18.7.14 + packages: /@ampproject/remapping/2.2.0: From 394b401f3560740ac7c5b17f5b02a10a96171a51 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 16 Dec 2022 16:15:46 -0800 Subject: [PATCH 38/84] refactor: fix circular dependency --- packages/client-core/src/key_pair/index.ts | 3 +- packages/client-core/src/key_pair/key_pair.ts | 58 +++++++++++++++++- .../src/key_pair/key_pair_ed25519.ts | 59 ------------------- 3 files changed, 58 insertions(+), 62 deletions(-) delete mode 100644 packages/client-core/src/key_pair/key_pair_ed25519.ts diff --git a/packages/client-core/src/key_pair/index.ts b/packages/client-core/src/key_pair/index.ts index e2d24b432..6894f79c0 100644 --- a/packages/client-core/src/key_pair/index.ts +++ b/packages/client-core/src/key_pair/index.ts @@ -1,4 +1,3 @@ export { KeyType } from './constants'; -export { KeyPair, Signature } from './key_pair'; -export { KeyPairEd25519 } from './key_pair_ed25519'; +export { KeyPair, KeyPairEd25519, Signature } from './key_pair'; export { PublicKey } from './public_key'; diff --git a/packages/client-core/src/key_pair/key_pair.ts b/packages/client-core/src/key_pair/key_pair.ts index 27a7e6e8b..a1e520ea5 100644 --- a/packages/client-core/src/key_pair/key_pair.ts +++ b/packages/client-core/src/key_pair/key_pair.ts @@ -1,4 +1,7 @@ -import { KeyPairEd25519 } from './key_pair_ed25519'; +import { baseEncode, baseDecode } from 'borsh'; +import nacl from 'tweetnacl'; + +import { KeyType } from './constants'; import { PublicKey } from './public_key'; export interface Signature { @@ -37,3 +40,56 @@ export abstract class KeyPair { } } } + +/** + * This class provides key pair functionality for Ed25519 curve: + * generating key pairs, encoding key pairs, signing and verifying. + */ +export class KeyPairEd25519 extends KeyPair { + readonly publicKey: PublicKey; + readonly secretKey: string; + + /** + * Construct an instance of key pair given a secret key. + * It's generally assumed that these are encoded in base58. + * @param {string} secretKey + */ + constructor(secretKey: string) { + super(); + const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); + this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); + this.secretKey = secretKey; + } + + /** + * Generate a new random keypair. + * @example + * const keyRandom = KeyPair.fromRandom(); + * keyRandom.publicKey + * // returns [PUBLIC_KEY] + * + * keyRandom.secretKey + * // returns [SECRET_KEY] + */ + static fromRandom() { + const newKeyPair = nacl.sign.keyPair(); + return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); + } + + sign(message: Uint8Array): Signature { + const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); + return { signature, publicKey: this.publicKey }; + } + + verify(message: Uint8Array, signature: Uint8Array): boolean { + return this.publicKey.verify(message, signature); + } + + toString(): string { + return `ed25519:${this.secretKey}`; + } + + getPublicKey(): PublicKey { + return this.publicKey; + } +} diff --git a/packages/client-core/src/key_pair/key_pair_ed25519.ts b/packages/client-core/src/key_pair/key_pair_ed25519.ts deleted file mode 100644 index ee41381e2..000000000 --- a/packages/client-core/src/key_pair/key_pair_ed25519.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { baseEncode, baseDecode } from 'borsh'; -import nacl from 'tweetnacl'; - -import { KeyType } from './constants'; -import { KeyPair, Signature } from './key_pair'; -import { PublicKey } from './public_key'; - -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -export class KeyPairEd25519 extends KeyPair { - readonly publicKey: PublicKey; - readonly secretKey: string; - - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey: string) { - super(); - const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); - this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); - this.secretKey = secretKey; - } - - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom() { - const newKeyPair = nacl.sign.keyPair(); - return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); - } - - sign(message: Uint8Array): Signature { - const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); - return { signature, publicKey: this.publicKey }; - } - - verify(message: Uint8Array, signature: Uint8Array): boolean { - return this.publicKey.verify(message, signature); - } - - toString(): string { - return `ed25519:${this.secretKey}`; - } - - getPublicKey(): PublicKey { - return this.publicKey; - } -} From 26b4ae1aefd75f14e505aad49f53ed224c4c3091 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 16 Dec 2022 16:16:53 -0800 Subject: [PATCH 39/84] refactor: moving tests --- packages/accounts/package.json | 1 + .../test/account.access_key.test.js | 13 +-- .../test/account.test.js | 14 +-- .../test/account_multisig.test.js | 20 ++-- packages/accounts/test/config.js | 44 ++++++++ packages/accounts/test/test-utils.js | 101 ++++++++++++++++++ packages/accounts/test/wasm/multisig.wasm | Bin 0 -> 356105 bytes .../package.json | 6 +- .../test/.eslintrc.yml | 7 ++ .../node-keystore-filesystem/package.json | 6 +- .../test/.eslintrc.yml | 7 ++ packages/wallet-account/jest.config.js | 5 + packages/wallet-account/package.json | 5 +- packages/wallet-account/test/.eslintrc.yml | 7 ++ .../test/wallet_account.test.js} | 76 ++++++------- pnpm-lock.yaml | 17 +++ 16 files changed, 263 insertions(+), 66 deletions(-) rename packages/{near-api-js => accounts}/test/account.access_key.test.js (90%) rename packages/{near-api-js => accounts}/test/account.test.js (98%) rename packages/{near-api-js => accounts}/test/account_multisig.test.js (95%) create mode 100644 packages/accounts/test/config.js create mode 100644 packages/accounts/test/test-utils.js create mode 100755 packages/accounts/test/wasm/multisig.wasm create mode 100644 packages/browser-keystore-localstorage/test/.eslintrc.yml create mode 100644 packages/node-keystore-filesystem/test/.eslintrc.yml create mode 100644 packages/wallet-account/jest.config.js create mode 100644 packages/wallet-account/test/.eslintrc.yml rename packages/{near-api-js/test/wallet-account.test.js => wallet-account/test/wallet_account.test.js} (87%) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index ecb27bd02..9570ea87c 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -22,6 +22,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", + "near-hello": "^0.5.1", "ts-jest": "^26.5.6" } } diff --git a/packages/near-api-js/test/account.access_key.test.js b/packages/accounts/test/account.access_key.test.js similarity index 90% rename from packages/near-api-js/test/account.access_key.test.js rename to packages/accounts/test/account.access_key.test.js index 9a44f9f73..c1bc4a430 100644 --- a/packages/near-api-js/test/account.access_key.test.js +++ b/packages/accounts/test/account.access_key.test.js @@ -1,4 +1,5 @@ -const nearApi = require('../src/index'); +const { KeyPair } = require('@near-js/client-core'); + const testUtils = require('./test-utils'); let nearjs; @@ -19,7 +20,7 @@ beforeEach(async () => { }); test('make function call using access key', async() => { - const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', '2000000000000000000000000'); // Override in the key store the workingAccount key to the given access key. @@ -30,7 +31,7 @@ test('make function call using access key', async() => { }); test('remove access key no longer works', async() => { - const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); let publicKey = keyPair.getPublicKey(); await workingAccount.addKey(publicKey, contractId, '', 400000); await workingAccount.deleteKey(publicKey); @@ -46,11 +47,11 @@ test('remove access key no longer works', async() => { }); test('view account details after adding access keys', async() => { - const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', 1000000000); const contract2 = await testUtils.deployContract(workingAccount, testUtils.generateUniqueString('test_contract2')); - const keyPair2 = nearApi.utils.KeyPair.fromRandom('ed25519'); + const keyPair2 = KeyPair.fromRandom('ed25519'); await workingAccount.addKey(keyPair2.getPublicKey(), contract2.contractId, '', 2000000000); const details = await workingAccount.getAccountDetails(); @@ -71,7 +72,7 @@ test('view account details after adding access keys', async() => { }); test('loading account after adding a full key', async() => { - const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); // wallet calls this with an empty string for contract id and method await workingAccount.addKey(keyPair.getPublicKey(), '', ''); diff --git a/packages/near-api-js/test/account.test.js b/packages/accounts/test/account.test.js similarity index 98% rename from packages/near-api-js/test/account.test.js rename to packages/accounts/test/account.test.js index f69cc5ca3..9c21f51e7 100644 --- a/packages/near-api-js/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -1,10 +1,10 @@ +const { getTransactionLastResult, TypedError } = require('@near-js/client-core'); +const { transfer } = require('@near-js/transactions'); +const BN = require('bn.js'); +const fs = require('fs'); -const { Account, Contract, providers } = require('../src/index'); +const { Account, Contract } = require('../lib'); const testUtils = require('./test-utils'); -const { TypedError } = require('../src/utils/errors'); -const fs = require('fs'); -const BN = require('bn.js'); -const { transfer } = require('../src/transaction'); let nearjs; let workingAccount; @@ -166,7 +166,7 @@ describe('with deploy contract', () => { methodName: 'setValue', args: { value: setCallValue } }); - expect(providers.getTransactionLastResult(result2)).toEqual(setCallValue); + expect(getTransactionLastResult(result2)).toEqual(setCallValue); expect(await workingAccount.viewFunction({ contractId, methodName: 'getValue' @@ -181,7 +181,7 @@ describe('with deploy contract', () => { args: { value: setCallValue } }); - const contractAccount = await nearjs.account(contractId); + const contractAccount = new Account(nearjs.connection, contractId); const state = (await contractAccount.viewState('')).map(({ key, value }) => [key.toString('utf-8'), value.toString('utf-8')]); expect(state).toEqual([['name', setCallValue]]); }); diff --git a/packages/near-api-js/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js similarity index 95% rename from packages/near-api-js/test/account_multisig.test.js rename to packages/accounts/test/account_multisig.test.js index ff638c4bc..eac9fd551 100644 --- a/packages/near-api-js/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -1,22 +1,16 @@ /* global BigInt */ -const nearApi = require('../src/index'); -const fs = require('fs'); +const { InMemorySigner, KeyPair, parseNearAmount } = require('@near-js/client-core'); +const { functionCall, transfer } = require('@near-js/transactions'); const BN = require('bn.js'); -const testUtils = require('./test-utils'); +const fs = require('fs'); const semver = require('semver'); -const { transfer } = require('../src/transaction'); + +const { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } = require('../lib'); +const testUtils = require('./test-utils'); let nearjs; let startFromVersion; -const { - KeyPair, - transactions: { functionCall }, - InMemorySigner, - multisig: { Account2FA, MULTISIG_GAS, MULTISIG_DEPOSIT }, - utils: { format: { parseNearAmount } } -} = nearApi; - jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) => ({ publicKey, kind: 'phone' })) => { @@ -49,7 +43,7 @@ const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) = account2fa.getRecoveryMethods = () => ({ data: keys.map(keyMapping) }); - await account2fa.deployMultisig([...fs.readFileSync('./test/data/multisig.wasm')]); + await account2fa.deployMultisig([...fs.readFileSync('./test/wasm/multisig.wasm')]); return account2fa; }; diff --git a/packages/accounts/test/config.js b/packages/accounts/test/config.js new file mode 100644 index 000000000..4af00c1c6 --- /dev/null +++ b/packages/accounts/test/config.js @@ -0,0 +1,44 @@ +module.exports = function getConfig(env) { + switch (env) { + case 'production': + case 'mainnet': + return { + networkId: 'mainnet', + nodeUrl: 'https://rpc.mainnet.near.org', + walletUrl: 'https://wallet.near.org', + helperUrl: 'https://helper.mainnet.near.org', + }; + case 'development': + case 'testnet': + return { + networkId: 'default', + nodeUrl: 'https://rpc.testnet.near.org', + walletUrl: 'https://wallet.testnet.near.org', + helperUrl: 'https://helper.testnet.near.org', + masterAccount: 'test.near', + }; + case 'betanet': + return { + networkId: 'betanet', + nodeUrl: 'https://rpc.betanet.near.org', + walletUrl: 'https://wallet.betanet.near.org', + helperUrl: 'https://helper.betanet.near.org', + }; + case 'local': + return { + networkId: 'local', + nodeUrl: 'http://localhost:3030', + keyPath: `${process.env.HOME}/.near/validator_key.json`, + walletUrl: 'http://localhost:4000/wallet', + }; + case 'test': + case 'ci': + return { + networkId: 'shared-test', + nodeUrl: 'https://rpc.ci-testnet.near.org', + masterAccount: 'test.near', + }; + default: + throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); + } +}; diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js new file mode 100644 index 000000000..884efbc08 --- /dev/null +++ b/packages/accounts/test/test-utils.js @@ -0,0 +1,101 @@ +const { InMemoryKeyStore, KeyPair } = require('@near-js/client-core'); +const BN = require('bn.js'); +const fs = require('fs').promises; + +const { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } = require('../lib'); + +const networkId = 'unittest'; + +const HELLO_WASM_PATH = process.env.HELLO_WASM_PATH || 'node_modules/near-hello/dist/main.wasm'; +const HELLO_WASM_BALANCE = new BN('10000000000000000000000000'); +const HELLO_WASM_METHODS = { + viewMethods: ['getValue', 'getLastResult'], + changeMethods: ['setValue', 'callPromise'] +}; +const MULTISIG_WASM_PATH = process.env.MULTISIG_WASM_PATH || './test/wasm/multisig.wasm'; +// Length of a random account. Set to 40 because in the protocol minimal allowed top-level account length should be at +// least 32. +const RANDOM_ACCOUNT_LENGTH = 40; + +async function setUpTestConnection() { + const keyStore = new InMemoryKeyStore(); + const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test'), { + networkId, + keyStore + }); + + if (config.masterAccount) { + // full accessKey on ci-testnet, dedicated rpc for tests. + await keyStore.setKey(networkId, config.masterAccount, KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw')); + } + + const connection = Connection.fromConfig({ + networkId: config.networkId, + provider: { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, + signer: { type: 'InMemorySigner', keyStore: config.keyStore }, + }); + + return { + accountCreator: new LocalAccountCreator(new Account(connection, config.masterAccount), new BN('500000000000000000000000000')), + connection, + }; +} + +// Generate some unique string of length at least RANDOM_ACCOUNT_LENGTH with a given prefix using the alice nonce. +function generateUniqueString(prefix) { + let result = `${prefix}-${Date.now()}-${Math.round(Math.random() * 1000000)}`; + let add_symbols = Math.max(RANDOM_ACCOUNT_LENGTH - result.length, 1); + for (let i = add_symbols; i > 0; --i) result += '0'; + return result; +} + +async function createAccount({ accountCreator, connection }) { + const newAccountName = generateUniqueString('test'); + const newPublicKey = await connection.signer.createKey(newAccountName, networkId); + await accountCreator.createAccount(newAccountName, newPublicKey); + return new Account(connection, newAccountName); +} + +async function createAccountMultisig({ accountCreator, connection }, options) { + const newAccountName = generateUniqueString('test'); + const newPublicKey = await connection.signer.createKey(newAccountName, networkId); + await accountCreator.createAccount(newAccountName, newPublicKey); + // add a confirm key for multisig (contract helper sim) + + try { + const confirmKeyPair = KeyPair.fromRandom('ed25519'); + const { publicKey } = confirmKeyPair; + const accountMultisig = new AccountMultisig(connection, newAccountName, options); + accountMultisig.useConfirmKey = async () => { + await connection.signer.setKey(networkId, options.masterAccount, confirmKeyPair); + }; + accountMultisig.getRecoveryMethods = () => ({ data: [] }); + accountMultisig.postSignedJson = async (path) => { + switch (path) { + case '/2fa/getAccessKey': return { publicKey }; + } + }; + await accountMultisig.deployMultisig(new Uint8Array([...(await fs.readFile(MULTISIG_WASM_PATH))])); + return accountMultisig; + } catch (e) { + console.log(e); + } +} + +async function deployContract(workingAccount, contractId) { + const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const data = [...(await fs.readFile(HELLO_WASM_PATH))]; + await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); + return new Contract(workingAccount, contractId, HELLO_WASM_METHODS); +} + +module.exports = { + setUpTestConnection, + networkId, + generateUniqueString, + createAccount, + createAccountMultisig, + deployContract, + HELLO_WASM_PATH, + HELLO_WASM_BALANCE, +}; diff --git a/packages/accounts/test/wasm/multisig.wasm b/packages/accounts/test/wasm/multisig.wasm new file mode 100755 index 0000000000000000000000000000000000000000..c904c5b7842091e6eed8cd0d7822c705091a1613 GIT binary patch literal 356105 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�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 { keyStore.clear(); nearFake = { @@ -29,7 +33,7 @@ beforeEach(() => { }, connection: { networkId: 'networkId', - signer: new nearApi.InMemorySigner(keyStore) + signer: new InMemorySigner(keyStore) }, account() { return { @@ -50,7 +54,7 @@ beforeEach(() => { replaceState: (state, title, url) => history.push([state, title, url]) } }); - walletConnection = new nearApi.WalletConnection(nearFake, ''); + walletConnection = new WalletConnection(nearFake, ''); }); it('not signed in by default', () => { @@ -77,7 +81,7 @@ describe('fails gracefully on the server side (without window)', () => { }); it('does not throw on instantiation', () => { - expect(() => new nearApi.WalletConnection(nearFake, '')).not.toThrowError(); + expect(() => new WalletConnection(nearFake, '')).not.toThrowError(); }); it('throws if non string appKeyPrefix in server context', () => { @@ -88,22 +92,22 @@ describe('fails gracefully on the server side (without window)', () => { }); it('returns an empty string as accountId', () => { - const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + const serverWalletConnection = new WalletConnection(nearFake, ''); expect(serverWalletConnection.getAccountId()).toEqual(''); }); it('returns false as isSignedIn', () => { - const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + const serverWalletConnection = new WalletConnection(nearFake, ''); expect(serverWalletConnection.isSignedIn()).toEqual(false); }); it('throws explicit error when calling other methods on the instance', () => { - const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + const serverWalletConnection = new WalletConnection(nearFake, ''); expect(() => serverWalletConnection.requestSignIn('signInContract', 'signInTitle', 'http://example.com/success', 'http://example.com/fail')).toThrow(/please ensure you are using WalletConnection on the browser/); }); it('can access other props on the instance', () => { - const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + const serverWalletConnection = new WalletConnection(nearFake, ''); expect(serverWalletConnection['randomValue']).toEqual(undefined); }); }); @@ -161,7 +165,7 @@ it('can request sign in with methodNames', async () => { }); it('can complete sign in', async () => { - const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); global.window.location.href = `http://example.com/location?account_id=near.account&public_key=${keyPair.publicKey}`; await keyStore.setKey('networkId', 'pending_key' + keyPair.publicKey, keyPair); @@ -175,11 +179,11 @@ it('can complete sign in', async () => { }); it('Promise until complete sign in', async () => { - const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); global.window.location.href = `http://example.com/location?account_id=near2.account&public_key=${keyPair.publicKey}`; await keyStore.setKey('networkId', 'pending_key' + keyPair.publicKey, keyPair); - const newWalletConn = new nearApi.WalletConnection(nearFake, 'promise_on_complete_signin'); + const newWalletConn = new WalletConnection(nearFake, 'promise_on_complete_signin'); expect(newWalletConn.isSignedIn()).toEqual(false); expect(await newWalletConn.isSignedInAsync()).toEqual(true); @@ -191,14 +195,14 @@ it('Promise until complete sign in', async () => { }); const BLOCK_HASH = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; -const blockHash = nearApi.utils.serialize.base_decode(BLOCK_HASH); +const blockHash = baseDecode(BLOCK_HASH); function createTransferTx() { const actions = [ - nearApi.transactions.transfer(1), + transfer(1), ]; - return nearApi.transactions.createTransaction( + return createTransaction( 'test.near', - nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), 'whatever.near', 1, actions, @@ -251,9 +255,9 @@ function parseTransactionsFromUrl(urlToParse, callbackUrl = 'http://example.com/ } }); const transactions = parsedUrl.query.transactions.split(',') - .map(txBase64 => nearApi.utils.serialize.deserialize( - nearApi.transactions.SCHEMA, - nearApi.transactions.Transaction, + .map(txBase64 => deserialize( + SCHEMA, + Transaction, Buffer.from(txBase64, 'base64'))); return transactions; } @@ -298,7 +302,7 @@ function setupWalletConnectionForSigning({ allKeys, accountAccessKeys }) { } describe('requests transaction signing automatically when there is no local key', () => { - const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + const keyPair = KeyPair.fromRandom('ed25519'); let transactions; beforeEach(() => { setupWalletConnectionForSigning({ @@ -317,7 +321,7 @@ describe('requests transaction signing automatically when there is no local key' try { await walletConnection.account().signAndSendTransaction({ receiverId: 'receiver.near', - actions: [nearApi.transactions.transfer(1)], + actions: [transfer(1)], walletCallbackUrl: 'http://callback.com/callback' }); fail('expected to throw'); @@ -347,8 +351,8 @@ describe('requests transaction signing automatically when there is no local key' describe('requests transaction signing automatically when function call has attached deposit', () => { beforeEach(async() => { - const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); - const walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + const localKeyPair = KeyPair.fromRandom('ed25519'); + const walletKeyPair = KeyPair.fromRandom('ed25519'); setupWalletConnectionForSigning({ allKeys: [ walletKeyPair.publicKey.toString() ], accountAccessKeys: [{ @@ -378,7 +382,7 @@ describe('requests transaction signing automatically when function call has atta try { await walletConnection.account().signAndSendTransaction({ receiverId: 'receiver.near', - actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))], + actions: [functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))], walletCallbackUrl: 'http://example.com/after', walletMeta: 'someStuff' }); @@ -394,8 +398,8 @@ describe('requests transaction signing automatically when function call has atta describe('requests transaction signing with 2fa access key', () => { beforeEach(async () => { - let localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); - let walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + let localKeyPair = KeyPair.fromRandom('ed25519'); + let walletKeyPair = KeyPair.fromRandom('ed25519'); setupWalletConnectionForSigning({ allKeys: [ walletKeyPair.publicKey.toString() ], accountAccessKeys: [{ @@ -419,7 +423,7 @@ describe('requests transaction signing with 2fa access key', () => { try { const res = await walletConnection.account().signAndSendTransaction({ receiverId: 'receiver.near', - actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] + actions: [functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] }); // multisig access key is accepted res is object representing transaction, populated upon wallet redirect to app @@ -433,8 +437,8 @@ describe('requests transaction signing with 2fa access key', () => { describe('fails requests transaction signing without 2fa access key', () => { beforeEach(async () => { - const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); - const walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + const localKeyPair = KeyPair.fromRandom('ed25519'); + const walletKeyPair = KeyPair.fromRandom('ed25519'); setupWalletConnectionForSigning({ allKeys: [ walletKeyPair.publicKey.toString() ], accountAccessKeys: [{ @@ -458,7 +462,7 @@ describe('fails requests transaction signing without 2fa access key', () => { return expect( walletConnection.account().signAndSendTransaction({ receiverId: 'receiver.near', - actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] + actions: [functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] }) ).rejects.toThrow('Cannot find matching key for transaction sent to receiver.near'); }); @@ -466,7 +470,7 @@ describe('fails requests transaction signing without 2fa access key', () => { describe('can sign transaction locally when function call has no attached deposit', () => { beforeEach(async () => { - const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + const localKeyPair = KeyPair.fromRandom('ed25519'); setupWalletConnectionForSigning({ allKeys: [ /* no keys in wallet needed */ ], accountAccessKeys: [{ @@ -487,9 +491,9 @@ describe('can sign transaction locally when function call has no attached deposi }); it.each([ - nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('0')), - nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1')), - nearApi.transactions.functionCall('someMethod', new Uint8Array()) + functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('0')), + functionCall('someMethod', new Uint8Array(), new BN('1')), + functionCall('someMethod', new Uint8Array()) ])('V2', async (functionCall) => { await walletConnection.account().signAndSendTransaction({ receiverId: 'receiver.near', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3171f2083..02358ddd3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ importers: borsh: ^0.7.0 depd: ^2.0.0 jest: ^26.0.1 + near-hello: ^0.5.1 ts-jest: ^26.5.6 dependencies: '@near-js/client-core': link:../client-core @@ -51,13 +52,19 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 + near-hello: 0.5.1 ts-jest: 26.5.6_jest@26.6.3 packages/browser-keystore-localstorage: specifiers: '@near-js/client-core': workspace:* + jest: ^26.0.1 + ts-jest: ^26.5.6 dependencies: '@near-js/client-core': link:../client-core + devDependencies: + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 packages/client-core: specifiers: @@ -169,10 +176,14 @@ importers: specifiers: '@near-js/client-core': workspace:* '@types/node': ^18.7.14 + jest: ^26.0.1 + ts-jest: ^26.5.6 dependencies: '@near-js/client-core': link:../client-core devDependencies: '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 packages/providers: specifiers: @@ -229,6 +240,9 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + jest: ^26.0.1 + localstorage-memory: ^1.0.3 + ts-jest: ^26.5.6 dependencies: '@near-js/accounts': link:../accounts '@near-js/client-core': link:../client-core @@ -237,6 +251,9 @@ importers: borsh: 0.7.0 devDependencies: '@types/node': 18.7.14 + jest: 26.6.3 + localstorage-memory: 1.0.3 + ts-jest: 26.5.6_jest@26.6.3 packages: From 052a99c02921d1341ac5093d858f0e7a715bd22b Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 2 Jan 2023 22:10:06 -0800 Subject: [PATCH 40/84] refactor: split up providers tests between providers and accounts packages --- packages/accounts/package.json | 1 + .../test/providers.test.js | 163 +----------------- packages/accounts/test/test-utils.js | 24 +++ packages/providers/test/providers.test.js | 160 +++++++++++++++++ pnpm-lock.yaml | 2 + 5 files changed, 193 insertions(+), 157 deletions(-) rename packages/{near-api-js => accounts}/test/providers.test.js (53%) create mode 100644 packages/providers/test/providers.test.js diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 9570ea87c..1bc5e878c 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -21,6 +21,7 @@ }, "devDependencies": { "@types/node": "^18.7.14", + "bs58": "^4.0.0", "jest": "^26.0.1", "near-hello": "^0.5.1", "ts-jest": "^26.5.6" diff --git a/packages/near-api-js/test/providers.test.js b/packages/accounts/test/providers.test.js similarity index 53% rename from packages/near-api-js/test/providers.test.js rename to packages/accounts/test/providers.test.js index 4a60b1b70..335d04dc2 100644 --- a/packages/near-api-js/test/providers.test.js +++ b/packages/accounts/test/providers.test.js @@ -1,66 +1,17 @@ -const nearApi = require('../src/index'); -const testUtils = require('./test-utils'); +const { JsonRpcProvider } = require('@near-js/providers'); const BN = require('bn.js'); const base58 = require('bs58'); +const testUtils = require('./test-utils'); + jest.setTimeout(20000); const withProvider = (fn) => { const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test')); - const provider = new nearApi.providers.JsonRpcProvider(config.nodeUrl); + const provider = new JsonRpcProvider(config.nodeUrl); return () => fn(provider); }; -test('json rpc fetch node status', withProvider(async (provider) => { - let response = await provider.status(); - expect(response.chain_id).toBeTruthy(); -})); - -test('json rpc fetch block info', withProvider(async (provider) => { - let stat = await provider.status(); - let height = stat.sync_info.latest_block_height - 1; - let response = await provider.block({ blockId: height }); - expect(response.header.height).toEqual(height); - - let sameBlock = await provider.block({ blockId: response.header.hash }); - expect(sameBlock.header.height).toEqual(height); - - let optimisticBlock = await provider.block({ finality: 'optimistic' }); - expect(optimisticBlock.header.height - height).toBeLessThan(5); - - let nearFinalBlock = await provider.block({ finality: 'near-final' }); - expect(nearFinalBlock.header.height - height).toBeLessThan(5); - - let finalBlock = await provider.block({ finality: 'final' }); - expect(finalBlock.header.height - height).toBeLessThan(5); -})); - -test('json rpc fetch block changes', withProvider(async (provider) => { - let stat = await provider.status(); - let height = stat.sync_info.latest_block_height - 1; - let response = await provider.blockChanges({ blockId: height }); - - expect(response).toMatchObject({ - block_hash: expect.any(String), - changes: expect.any(Array) - }); -})); - -test('json rpc fetch chunk info', withProvider(async (provider) => { - let stat = await provider.status(); - let height = stat.sync_info.latest_block_height - 1; - let response = await provider.chunk([height, 0]); - expect(response.header.shard_id).toEqual(0); - let sameChunk = await provider.chunk(response.header.chunk_hash); - expect(sameChunk.header.chunk_hash).toEqual(response.header.chunk_hash); - expect(sameChunk.header.shard_id).toEqual(0); -})); - -test('json rpc fetch validators info', withProvider(async (provider) => { - let validators = await provider.validators(null); - expect(validators.current_validators.length).toBeGreaterThanOrEqual(1); -})); - test('txStatus with string hash and buffer hash', withProvider(async(provider) => { const near = await testUtils.setUpTestConnection(); const sender = await testUtils.createAccount(near); @@ -86,27 +37,6 @@ test('txStatusReciept with string hash and buffer hash', withProvider(async(prov expect(responseWithUint8Array).toMatchObject(reciepts); })); -test('json rpc query with block_id', withProvider(async(provider) => { - const stat = await provider.status(); - let block_id = stat.sync_info.latest_block_height - 1; - - const response = await provider.query({ - block_id, - request_type: 'view_account', - account_id: 'test.near' - }); - - expect(response).toEqual({ - block_height: expect.any(Number), - block_hash: expect.any(String), - amount: expect.any(String), - locked: expect.any(String), - code_hash: '11111111111111111111111111111111', - storage_usage: 182, - storage_paid_at: 0, - }); -})); - test('json rpc query account', withProvider(async (provider) => { const near = await testUtils.setUpTestConnection(); const account = await testUtils.createAccount(near); @@ -119,7 +49,7 @@ test('json rpc query view_state', withProvider(async (provider) => { const account = await testUtils.createAccount(near); const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); - await contract.setValue({ args: { value: 'hello' } }); + await contract.setValue({ args: { value: 'hello' }}); return testUtils.waitFor(async() => { const response = await provider.query({ @@ -139,24 +69,6 @@ test('json rpc query view_state', withProvider(async (provider) => { }); })); -test('json rpc query view_account', withProvider(async (provider) => { - const response = await provider.query({ - request_type: 'view_account', - finality: 'final', - account_id: 'test.near' - }); - - expect(response).toEqual({ - block_height: expect.any(Number), - block_hash: expect.any(String), - amount: expect.any(String), - locked: expect.any(String), - code_hash: '11111111111111111111111111111111', - storage_usage: 182, - storage_paid_at: 0, - }); -})); - test('json rpc query view_code', withProvider(async (provider) => { const near = await testUtils.setUpTestConnection(); const account = await testUtils.createAccount(near); @@ -183,7 +95,7 @@ test('json rpc query call_function', withProvider(async (provider) => { const account = await testUtils.createAccount(near); const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); - await contract.setValue({ args: { value: 'hello' } }); + await contract.setValue({ args: { value: 'hello' }}); return testUtils.waitFor(async() => { const response = await provider.query({ @@ -210,30 +122,6 @@ test('json rpc query call_function', withProvider(async (provider) => { }); })); -test('final tx result', async() => { - const result = { - status: { SuccessValue: 'e30=' }, - transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, - receipts: [ - { id: '11112', outcome: { status: { SuccessValue: 'e30=' }, logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, - { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } - ] - }; - expect(nearApi.providers.getTransactionLastResult(result)).toEqual({}); -}); - -test('final tx result with null', async() => { - const result = { - status: 'Failure', - transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, - receipts: [ - { id: '11112', outcome: { status: 'Failure', logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, - { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } - ] - }; - expect(nearApi.providers.getTransactionLastResult(result)).toEqual(null); -}); - test('json rpc light client proof', async() => { const near = await testUtils.setUpTestConnection(); const workingAccount = await testUtils.createAccount(near); @@ -294,42 +182,3 @@ test('json rpc light client proof', async() => { await expect(provider.lightClientProof(lightClientRequest)).rejects.toThrow(/.+ block .+ is ahead of head block .+/); }); - -test('json rpc fetch protocol config', withProvider(async (provider) => { - const status = await provider.status(); - const blockHeight = status.sync_info.latest_block_height; - const blockHash = status.sync_info.latest_block_hash; - for (const blockReference of [{ sync_checkpoint: 'genesis' }, { blockId: blockHeight }, { blockId: blockHash }, { finality: 'final' }, { finality: 'optimistic' }]) { - const response = await provider.experimental_protocolConfig(blockReference); - expect('chain_id' in response).toBe(true); - expect('genesis_height' in response).toBe(true); - expect('runtime_config' in response).toBe(true); - expect('storage_amount_per_byte' in response.runtime_config).toBe(true); - } -})); - -test('json rpc gas price', withProvider(async (provider) => { - let status = await provider.status(); - let positiveIntegerRegex = /^[+]?\d+([.]\d+)?$/; - - let response1 = await provider.gasPrice(status.sync_info.latest_block_height); - expect(response1.gas_price).toMatch(positiveIntegerRegex); - - let response2 = await provider.gasPrice(status.sync_info.latest_block_hash); - expect(response2.gas_price).toMatch(positiveIntegerRegex); - - let response3 = await provider.gasPrice(); - expect(response3.gas_price).toMatch(positiveIntegerRegex); -})); - -test('JsonRpc connection object exist without connectionInfo provided', async () => { - const provider = new nearApi.providers.JsonRpcProvider(); - expect(provider.connection).toStrictEqual({ url: '' }); -}); - -test('near json rpc fetch node status', async () => { - const config = require('./config')(process.env.NODE_ENV || 'test'); - const near = await nearApi.connect(config); - let response = await near.connection.provider.status(); - expect(response.chain_id).toBeTruthy(); -}); \ No newline at end of file diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 884efbc08..ebb88d24a 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -89,6 +89,28 @@ async function deployContract(workingAccount, contractId) { return new Contract(workingAccount, contractId, HELLO_WASM_METHODS); } +function sleep(time) { + return new Promise(function (resolve) { + setTimeout(resolve, time); + }); +} + +function waitFor(fn) { + const _waitFor = async (count = 10) => { + try { + return await fn(); + } catch (e) { + if (count > 0) { + await sleep(500); + return _waitFor(count - 1); + } + else throw e; + } + }; + + return _waitFor(); +} + module.exports = { setUpTestConnection, networkId, @@ -98,4 +120,6 @@ module.exports = { deployContract, HELLO_WASM_PATH, HELLO_WASM_BALANCE, + sleep, + waitFor, }; diff --git a/packages/providers/test/providers.test.js b/packages/providers/test/providers.test.js new file mode 100644 index 000000000..b88cf2221 --- /dev/null +++ b/packages/providers/test/providers.test.js @@ -0,0 +1,160 @@ +const { getTransactionLastResult } = require('@near-js/client-core'); + +const { JsonRpcProvider } = require('../lib'); + +jest.setTimeout(20000); + +const withProvider = (fn) => { + return () => fn(new JsonRpcProvider({ url: 'https://rpc.ci-testnet.near.org' })); +}; + +test('json rpc fetch node status', withProvider(async (provider) => { + let response = await provider.status(); + expect(response.chain_id).toBeTruthy(); +})); + +test('json rpc fetch block info', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.block({ blockId: height }); + expect(response.header.height).toEqual(height); + + let sameBlock = await provider.block({ blockId: response.header.hash }); + expect(sameBlock.header.height).toEqual(height); + + let optimisticBlock = await provider.block({ finality: 'optimistic' }); + expect(optimisticBlock.header.height - height).toBeLessThan(5); + + let nearFinalBlock = await provider.block({ finality: 'near-final' }); + expect(nearFinalBlock.header.height - height).toBeLessThan(5); + + let finalBlock = await provider.block({ finality: 'final' }); + expect(finalBlock.header.height - height).toBeLessThan(5); +})); + +test('json rpc fetch block changes', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.blockChanges({ blockId: height }); + + expect(response).toMatchObject({ + block_hash: expect.any(String), + changes: expect.any(Array) + }); +})); + +test('json rpc fetch chunk info', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.chunk([height, 0]); + expect(response.header.shard_id).toEqual(0); + let sameChunk = await provider.chunk(response.header.chunk_hash); + expect(sameChunk.header.chunk_hash).toEqual(response.header.chunk_hash); + expect(sameChunk.header.shard_id).toEqual(0); +})); + +test('json rpc fetch validators info', withProvider(async (provider) => { + let validators = await provider.validators(null); + expect(validators.current_validators.length).toBeGreaterThanOrEqual(1); +})); + +test('json rpc query with block_id', withProvider(async(provider) => { + const stat = await provider.status(); + let block_id = stat.sync_info.latest_block_height - 1; + + const response = await provider.query({ + block_id, + request_type: 'view_account', + account_id: 'test.near' + }); + + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + amount: expect.any(String), + locked: expect.any(String), + code_hash: '11111111111111111111111111111111', + storage_usage: 182, + storage_paid_at: 0, + }); +})); + +test('json rpc query view_account', withProvider(async (provider) => { + const response = await provider.query({ + request_type: 'view_account', + finality: 'final', + account_id: 'test.near' + }); + + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + amount: expect.any(String), + locked: expect.any(String), + code_hash: '11111111111111111111111111111111', + storage_usage: 182, + storage_paid_at: 0, + }); +})); + +test('final tx result', async() => { + const result = { + status: { SuccessValue: 'e30=' }, + transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, + receipts: [ + { id: '11112', outcome: { status: { SuccessValue: 'e30=' }, logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, + { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } + ] + }; + expect(getTransactionLastResult(result)).toEqual({}); +}); + +test('final tx result with null', async() => { + const result = { + status: 'Failure', + transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, + receipts: [ + { id: '11112', outcome: { status: 'Failure', logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, + { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } + ] + }; + expect(getTransactionLastResult(result)).toEqual(null); +}); + +test('json rpc fetch protocol config', withProvider(async (provider) => { + const status = await provider.status(); + const blockHeight = status.sync_info.latest_block_height; + const blockHash = status.sync_info.latest_block_hash; + for (const blockReference of [{ sync_checkpoint: 'genesis' }, { blockId: blockHeight }, { blockId: blockHash }, { finality: 'final' }, { finality: 'optimistic' }]) { + const response = await provider.experimental_protocolConfig(blockReference); + expect('chain_id' in response).toBe(true); + expect('genesis_height' in response).toBe(true); + expect('runtime_config' in response).toBe(true); + expect('storage_amount_per_byte' in response.runtime_config).toBe(true); + } +})); + +test('json rpc gas price', withProvider(async (provider) => { + let status = await provider.status(); + let positiveIntegerRegex = /^[+]?\d+([.]\d+)?$/; + + let response1 = await provider.gasPrice(status.sync_info.latest_block_height); + expect(response1.gas_price).toMatch(positiveIntegerRegex); + + let response2 = await provider.gasPrice(status.sync_info.latest_block_hash); + expect(response2.gas_price).toMatch(positiveIntegerRegex); + + let response3 = await provider.gasPrice(); + expect(response3.gas_price).toMatch(positiveIntegerRegex); +})); + +test('JsonRpc connection object exist without connectionInfo provided', async () => { + const provider = new JsonRpcProvider(); + expect(provider.connection).toStrictEqual({ url: '' }); +}); + +test('near json rpc fetch node status', async () => { + const provider = new JsonRpcProvider({ url: 'https://rpc.ci-testnet.near.org' }); + let response = await provider.status(); + expect(response.chain_id).toBeTruthy(); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02358ddd3..3f246fc77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,7 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + bs58: ^4.0.0 depd: ^2.0.0 jest: ^26.0.1 near-hello: ^0.5.1 @@ -51,6 +52,7 @@ importers: depd: 2.0.0 devDependencies: '@types/node': 18.7.14 + bs58: 4.0.1 jest: 26.6.3 near-hello: 0.5.1 ts-jest: 26.5.6_jest@26.6.3 From 97e06f25b49208f55542c32ecec84acf6116b99c Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 2 Jan 2023 22:12:33 -0800 Subject: [PATCH 41/84] refactor: remove unused/moved methods --- packages/near-api-js/test/test-utils.js | 57 ------------------------- 1 file changed, 57 deletions(-) diff --git a/packages/near-api-js/test/test-utils.js b/packages/near-api-js/test/test-utils.js index 5ff2ebbe9..e882bf610 100644 --- a/packages/near-api-js/test/test-utils.js +++ b/packages/near-api-js/test/test-utils.js @@ -11,7 +11,6 @@ const HELLO_WASM_METHODS = { viewMethods: ['getValue', 'getLastResult'], changeMethods: ['setValue', 'callPromise'] }; -const MULTISIG_WASM_PATH = process.env.MULTISIG_WASM_PATH || './test/wasm/multisig.wasm'; // Length of a random account. Set to 40 because in the protocol minimal allowed top-level account length should be at // least 32. const RANDOM_ACCOUNT_LENGTH = 40; @@ -46,35 +45,6 @@ async function createAccount(near) { return account; } -async function createAccountMultisig(near, options) { - const newAccountName = generateUniqueString('test'); - const newPublicKey = await near.connection.signer.createKey(newAccountName, networkId); - await near.createAccount(newAccountName, newPublicKey); - // add a confirm key for multisig (contract helper sim) - - try { - const confirmKeyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); - const { publicKey } = confirmKeyPair; - // const account = new nearApi.Account(near.connection, newAccountName); - // await account.addKey(publicKey, account.accountId, nearApi.multisig.MULTISIG_CONFIRM_METHODS, '0') - // create multisig account instance and deploy contract - const accountMultisig = new nearApi.multisig.AccountMultisig(near.connection, newAccountName, options); - accountMultisig.useConfirmKey = async () => { - await near.connection.signer.setKey(networkId, options.masterAccount, confirmKeyPair); - }; - accountMultisig.getRecoveryMethods = () => ({ data: [] }); - accountMultisig.postSignedJson = async (path) => { - switch (path) { - case '/2fa/getAccessKey': return { publicKey }; - } - }; - await accountMultisig.deployMultisig(new Uint8Array([...(await fs.readFile(MULTISIG_WASM_PATH))])); - return accountMultisig; - } catch (e) { - console.log(e); - } -} - async function deployContract(workingAccount, contractId) { const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); const data = [...(await fs.readFile(HELLO_WASM_PATH))]; @@ -82,28 +52,6 @@ async function deployContract(workingAccount, contractId) { return new nearApi.Contract(workingAccount, contractId, HELLO_WASM_METHODS); } -function sleep(time) { - return new Promise(function (resolve) { - setTimeout(resolve, time); - }); -} - -function waitFor(fn) { - const _waitFor = async (count = 10) => { - try { - return await fn(); - } catch (e) { - if (count > 0) { - await sleep(500); - return _waitFor(count - 1); - } - else throw e; - } - }; - - return _waitFor(); -} - async function ensureDir(dirpath) { try { await fs.mkdir(dirpath, { recursive: true }); @@ -117,11 +65,6 @@ module.exports = { networkId, generateUniqueString, createAccount, - createAccountMultisig, deployContract, - sleep, - waitFor, ensureDir, - HELLO_WASM_PATH, - HELLO_WASM_BALANCE, }; From 9d14709101158df3d497b27eee90f536499d526d Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 2 Jan 2023 22:12:52 -0800 Subject: [PATCH 42/84] refactor: move contract tests --- packages/{near-api-js => accounts}/test/contract.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) rename packages/{near-api-js => accounts}/test/contract.test.js (97%) diff --git a/packages/near-api-js/test/contract.test.js b/packages/accounts/test/contract.test.js similarity index 97% rename from packages/near-api-js/test/contract.test.js rename to packages/accounts/test/contract.test.js index fbeb57790..d5901fab3 100644 --- a/packages/near-api-js/test/contract.test.js +++ b/packages/accounts/test/contract.test.js @@ -1,5 +1,6 @@ -const { Contract } = require('../src/contract'); -const { PositionalArgsError } = require('../src/utils/errors'); +const { PositionalArgsError } = require('@near-js/client-core'); + +const { Contract } = require('../lib'); const account = { viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery}) { From fcf6f73c1438fb7cb0c291d0f308dd7c902f8193 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 11:48:10 -0800 Subject: [PATCH 43/84] refactor: transactions tests --- packages/transactions/jest.config.js | 5 + packages/transactions/package.json | 4 +- packages/transactions/test/.eslintrc.yml | 7 ++ .../test/data/signed_transaction1.json | 0 .../test/data/transaction1.json | 0 .../test/serialize.test.js | 107 ++++++++++-------- .../test/transaction.test.js | 5 +- pnpm-lock.yaml | 4 + 8 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 packages/transactions/jest.config.js create mode 100644 packages/transactions/test/.eslintrc.yml rename packages/{near-api-js => transactions}/test/data/signed_transaction1.json (100%) rename packages/{near-api-js => transactions}/test/data/transaction1.json (100%) rename packages/{near-api-js => transactions}/test/serialize.test.js (61%) rename packages/{near-api-js => transactions}/test/transaction.test.js (93%) diff --git a/packages/transactions/jest.config.js b/packages/transactions/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/transactions/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 584ebae90..c30ab3e72 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -19,6 +19,8 @@ "tweetnacl": "^1.0.1" }, "devDependencies": { - "@types/node": "^18.7.14" + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" } } diff --git a/packages/transactions/test/.eslintrc.yml b/packages/transactions/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/transactions/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/near-api-js/test/data/signed_transaction1.json b/packages/transactions/test/data/signed_transaction1.json similarity index 100% rename from packages/near-api-js/test/data/signed_transaction1.json rename to packages/transactions/test/data/signed_transaction1.json diff --git a/packages/near-api-js/test/data/transaction1.json b/packages/transactions/test/data/transaction1.json similarity index 100% rename from packages/near-api-js/test/data/transaction1.json rename to packages/transactions/test/data/transaction1.json diff --git a/packages/near-api-js/test/serialize.test.js b/packages/transactions/test/serialize.test.js similarity index 61% rename from packages/near-api-js/test/serialize.test.js rename to packages/transactions/test/serialize.test.js index c726c54b2..530fa1d04 100644 --- a/packages/near-api-js/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -1,16 +1,33 @@ - +const { Assignable, InMemoryKeyStore, InMemorySigner, KeyPair, PublicKey } = require('@near-js/client-core'); const fs = require('fs'); const BN = require('bn.js'); -const nearApi = require('../src/index'); - -class Test extends nearApi.utils.enums.Assignable { +const { baseDecode, baseEncode, deserialize, serialize } = require('borsh'); + +const { + addKey, + createAccount, + createTransaction, + deleteAccount, + deleteKey, + deployContract, + functionCall, + functionCallAccessKey, + SCHEMA, + signTransaction, + SignedTransaction, + stake, + Transaction, + transfer, +} = require('../lib'); + +class Test extends Assignable { } test('serialize object', async () => { const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3]}); const schema = new Map([[Test, {kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); - let buf = nearApi.utils.serialize.serialize(schema, value); - let new_value = nearApi.utils.serialize.deserialize(schema, Test, buf); + let buf = serialize(schema, value); + let new_value = deserialize(schema, Test, buf); expect(new_value.x).toEqual(255); expect(new_value.y.toString()).toEqual('20'); expect(new_value.z).toEqual('123'); @@ -18,35 +35,35 @@ test('serialize object', async () => { }); test('serialize and sign multi-action tx', async() => { - const keyStore = new nearApi.keyStores.InMemoryKeyStore(); - const keyPair = nearApi.utils.KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); + const keyStore = new InMemoryKeyStore(); + const keyPair = KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); await keyStore.setKey('test', 'test.near', keyPair); const publicKey = keyPair.publicKey; const actions = [ - nearApi.transactions.createAccount(), - nearApi.transactions.deployContract(new Uint8Array([1, 2, 3])), - nearApi.transactions.functionCall('qqq', new Uint8Array([1, 2, 3]), 1000, 1000000), - nearApi.transactions.transfer(123), - nearApi.transactions.stake(1000000, publicKey), - nearApi.transactions.addKey(publicKey, nearApi.transactions.functionCallAccessKey('zzz', ['www'], null)), - nearApi.transactions.deleteKey(publicKey), - nearApi.transactions.deleteAccount('123') + createAccount(), + deployContract(new Uint8Array([1, 2, 3])), + functionCall('qqq', new Uint8Array([1, 2, 3]), 1000, 1000000), + transfer(123), + stake(1000000, publicKey), + addKey(publicKey, functionCallAccessKey('zzz', ['www'], null)), + deleteKey(publicKey), + deleteAccount('123') ]; - const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - let [hash, { transaction }] = await nearApi.transactions.signTransaction('123', 1, actions, blockHash, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); - expect(nearApi.utils.serialize.base_encode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); - const serialized = nearApi.utils.serialize.serialize(nearApi.transactions.SCHEMA, transaction); + const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + let [hash, { transaction }] = await signTransaction('123', 1, actions, blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); + expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); + const serialized = serialize(SCHEMA, transaction); expect(serialized.toString('hex')).toEqual('09000000746573742e6e656172000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80100000000000000030000003132330fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef608000000000103000000010203020300000071717103000000010203e80300000000000040420f00000000000000000000000000037b0000000000000000000000000000000440420f00000000000000000000000000000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef805000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef800000000000000000000030000007a7a7a010000000300000077777706000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80703000000313233'); }); function createTransferTx() { const actions = [ - nearApi.transactions.transfer(1), + transfer(1), ]; - const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - return nearApi.transactions.createTransaction( + const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + return createTransaction( 'test.near', - nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), 'whatever.near', 1, actions, @@ -59,13 +76,13 @@ test('serialize transfer tx', async() => { const serialized = transaction.encode(); expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000'); - const deserialized = nearApi.transactions.Transaction.decode(serialized); + const deserialized = Transaction.decode(serialized); expect(deserialized.encode()).toEqual(serialized); }); async function createKeyStore() { - const keyStore = new nearApi.keyStores.InMemoryKeyStore(); - const keyPair = nearApi.utils.KeyPair.fromString('ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv'); + const keyStore = new InMemoryKeyStore(); + const keyPair = KeyPair.fromString('ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv'); await keyStore.setKey('test', 'test.near', keyPair); return keyStore; } @@ -75,7 +92,7 @@ async function verifySignedTransferTx(signedTx) { const serialized = signedTx.encode(); expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01'); - const deserialized = nearApi.transactions.SignedTransaction.decode(serialized); + const deserialized = SignedTransaction.decode(serialized); expect(deserialized.encode()).toEqual(serialized); } @@ -83,7 +100,7 @@ test('serialize and sign transfer tx', async() => { const transaction = createTransferTx(); const keyStore = await createKeyStore(); - let [, signedTx] = await nearApi.transactions.signTransaction(transaction.receiverId, transaction.nonce, transaction.actions, transaction.blockHash, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); + let [, signedTx] = await signTransaction(transaction.receiverId, transaction.nonce, transaction.actions, transaction.blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); verifySignedTransferTx(signedTx); }); @@ -92,7 +109,7 @@ test('serialize and sign transfer tx object', async() => { const transaction = createTransferTx(); const keyStore = await createKeyStore(); - let [, signedTx] = await nearApi.transactions.signTransaction(transaction, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); + let [, signedTx] = await signTransaction(transaction, new InMemorySigner(keyStore), 'test.near', 'test'); verifySignedTransferTx(signedTx); }); @@ -105,9 +122,9 @@ describe('roundtrip test', () => { const testDefinition = JSON.parse(fs.readFileSync(dataDir + '/' + testFile)); test(testFile, () => { const data = Buffer.from(testDefinition.data, 'hex'); - const type = Array.from(nearApi.transactions.SCHEMA.keys()).find(key => key.name === testDefinition.type); - const deserialized = nearApi.utils.serialize.deserialize(nearApi.transactions.SCHEMA, type, data); - const serialized = nearApi.utils.serialize.serialize(nearApi.transactions.SCHEMA, deserialized); + const type = Array.from(SCHEMA.keys()).find(key => key.name === testDefinition.type); + const deserialized = deserialize(SCHEMA, type, data); + const serialized = serialize(SCHEMA, deserialized); expect(serialized).toEqual(data); }); } @@ -116,51 +133,51 @@ describe('roundtrip test', () => { describe('serialize and deserialize on different types of nonce', () => { const actions = [ - nearApi.transactions.transfer(1), + transfer(1), ]; - const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); const targetNonce = new BN(1); test('number typed nonce', async() => { - const transaction = nearApi.transactions.createTransaction( + const transaction = createTransaction( 'test.near', - nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 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); + const deserialized = 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( + const transaction = createTransaction( 'test.near', - nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 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); + const deserialized = 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( + const transaction = createTransaction( 'test.near', - nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 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); + const deserialized = Transaction.decode(serialized); expect(deserialized.encode()).toEqual(serialized); expect(deserialized.nonce.toString()).toEqual(targetNonce.toString()); }); -}); \ No newline at end of file +}); diff --git a/packages/near-api-js/test/transaction.test.js b/packages/transactions/test/transaction.test.js similarity index 93% rename from packages/near-api-js/test/transaction.test.js rename to packages/transactions/test/transaction.test.js index 1f0c05eeb..9a615850b 100644 --- a/packages/near-api-js/test/transaction.test.js +++ b/packages/transactions/test/transaction.test.js @@ -1,6 +1,7 @@ -const { functionCall } = require('../src/transaction'); const BN = require('bn.js'); +const { functionCall } = require('../lib'); + test('functionCall with already serialized args', () => { const serializedArgs = Buffer.from('{}'); const action = functionCall('methodName', serializedArgs, new BN(1), new BN(2)); @@ -25,4 +26,4 @@ test('functionCall with non-serialized args', () => { deposit: new BN(2) } }); -}); \ No newline at end of file +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f246fc77..3eac05863 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,8 +221,10 @@ importers: '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 + jest: ^26.0.1 js-sha256: ^0.9.0 mustache: ^4.0.0 + ts-jest: ^26.5.6 tweetnacl: ^1.0.1 dependencies: '@near-js/client-core': link:../client-core @@ -233,6 +235,8 @@ importers: tweetnacl: 1.0.3 devDependencies: '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 packages/wallet-account: specifiers: From d94c7ceb72fcc7402a2a54f46ad06ee73d0bf574 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 11:49:21 -0800 Subject: [PATCH 44/84] refactor: move remaining top-level tests --- .../test/promise.test.js | 1 + .../test/validator.test.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) rename packages/{near-api-js => accounts}/test/promise.test.js (99%) rename packages/{near-api-js => client-core}/test/validator.test.js (78%) diff --git a/packages/near-api-js/test/promise.test.js b/packages/accounts/test/promise.test.js similarity index 99% rename from packages/near-api-js/test/promise.test.js rename to packages/accounts/test/promise.test.js index 02e57cbd8..f0cc67cc3 100644 --- a/packages/near-api-js/test/promise.test.js +++ b/packages/accounts/test/promise.test.js @@ -1,4 +1,5 @@ const BN = require('bn.js'); + const testUtils = require('./test-utils'); let nearjs; diff --git a/packages/near-api-js/test/validator.test.js b/packages/client-core/test/validator.test.js similarity index 78% rename from packages/near-api-js/test/validator.test.js rename to packages/client-core/test/validator.test.js index 20e280fa3..6bb95cec4 100644 --- a/packages/near-api-js/test/validator.test.js +++ b/packages/client-core/test/validator.test.js @@ -1,28 +1,28 @@ - -const nearApi = require('../src/index'); const BN = require('bn.js'); +const { diffEpochValidators, findSeatPrice } = require('../lib'); + test('find seat price', async () => { - expect(nearApi.validators.findSeatPrice( + expect(findSeatPrice( [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 2, [1, 6250], 49 )).toEqual(new BN('101')); - expect(nearApi.validators.findSeatPrice( + expect(findSeatPrice( [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 3, [1, 6250] )).toEqual(new BN('101')); - expect(nearApi.validators.findSeatPrice( + expect(findSeatPrice( [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 4, [1, 6250], 49 )).toEqual(new BN('320')); - expect(nearApi.validators.findSeatPrice( + expect(findSeatPrice( [{stake: '1000'}, {stake: '1000'}, {stake: '200'}], 100, [1, 25] )).toEqual(new BN('88')); }); test('diff validators', async () => { - expect(nearApi.validators.diffEpochValidators( + expect(diffEpochValidators( [{account_id: 'x', stake: '10'}], [{ account_id: 'x', stake: '10' }] )).toEqual({newValidators: [], removedValidators: [], changedValidators: []}); - expect(nearApi.validators.diffEpochValidators( + expect(diffEpochValidators( [{ account_id: 'x', stake: '10' }, { account_id: 'y', stake: '10' }], [{ account_id: 'x', stake: '11' }, { account_id: 'z', stake: '11' }] )).toEqual({ @@ -33,4 +33,4 @@ test('diff validators', async () => { next: { account_id: 'x', stake: '11' } }] }); -}); \ No newline at end of file +}); From d6f99b7698c2277c52b16bd7d593f8c16c2a0119 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 11:49:58 -0800 Subject: [PATCH 45/84] refactor: inline test method --- .../unencrypted_file_system_keystore.test.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js b/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js index 6ab9bfd89..867741379 100644 --- a/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js +++ b/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js @@ -4,8 +4,7 @@ const rimraf = require('util').promisify(require('rimraf')); const nearApi = require('../../src/index'); const UnencryptedFileSystemKeyStore = nearApi.keyStores.UnencryptedFileSystemKeyStore; const KeyPair = nearApi.utils.KeyPairEd25519; -const { ensureDir } = require('../test-utils'); -const fs = require('fs'); +const fs = require('fs').promises; const path = require('path'); const KEYSTORE_PATH = '../test-keys'; @@ -15,7 +14,11 @@ describe('Unencrypted file system keystore', () => { beforeAll(async () => { await rimraf(KEYSTORE_PATH); - await ensureDir(KEYSTORE_PATH); + try { + await fs.mkdir(KEYSTORE_PATH, { recursive: true }); + } catch (err) { + if (err.code !== 'EEXIST') throw err; + } ctx.keyStore = new UnencryptedFileSystemKeyStore(KEYSTORE_PATH); }); @@ -29,7 +32,7 @@ describe('Unencrypted file system keystore', () => { const key1 = KeyPair.fromRandom(); await ctx.keyStore.setKey('network', 'account', key1); const keyFilePath = ctx.keyStore.getKeyFilePath('network', 'account'); - const content = fs.readFileSync(keyFilePath); + const content = await fs.readFile(keyFilePath); const accountInfo = JSON.parse(content.toString()); expect(accountInfo.public_key).toEqual(key1.getPublicKey().toString()); }); From 54163051e4af918a8f86f0c588c5633fb8c804a5 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 11:50:22 -0800 Subject: [PATCH 46/84] refactor: remove unused assets/modules --- packages/near-api-js/test/config.js | 44 ------------ packages/near-api-js/test/data/main.wasm | Bin 86 -> 0 bytes packages/near-api-js/test/data/multisig.wasm | Bin 356105 -> 0 bytes packages/near-api-js/test/test-utils.js | 70 ------------------- packages/near-api-js/test/wasm/multisig.wasm | Bin 356105 -> 0 bytes 5 files changed, 114 deletions(-) delete mode 100644 packages/near-api-js/test/config.js delete mode 100755 packages/near-api-js/test/data/main.wasm delete mode 100755 packages/near-api-js/test/data/multisig.wasm delete mode 100644 packages/near-api-js/test/test-utils.js delete mode 100755 packages/near-api-js/test/wasm/multisig.wasm diff --git a/packages/near-api-js/test/config.js b/packages/near-api-js/test/config.js deleted file mode 100644 index 4af00c1c6..000000000 --- a/packages/near-api-js/test/config.js +++ /dev/null @@ -1,44 +0,0 @@ -module.exports = function getConfig(env) { - switch (env) { - case 'production': - case 'mainnet': - return { - networkId: 'mainnet', - nodeUrl: 'https://rpc.mainnet.near.org', - walletUrl: 'https://wallet.near.org', - helperUrl: 'https://helper.mainnet.near.org', - }; - case 'development': - case 'testnet': - return { - networkId: 'default', - nodeUrl: 'https://rpc.testnet.near.org', - walletUrl: 'https://wallet.testnet.near.org', - helperUrl: 'https://helper.testnet.near.org', - masterAccount: 'test.near', - }; - case 'betanet': - return { - networkId: 'betanet', - nodeUrl: 'https://rpc.betanet.near.org', - walletUrl: 'https://wallet.betanet.near.org', - helperUrl: 'https://helper.betanet.near.org', - }; - case 'local': - return { - networkId: 'local', - nodeUrl: 'http://localhost:3030', - keyPath: `${process.env.HOME}/.near/validator_key.json`, - walletUrl: 'http://localhost:4000/wallet', - }; - case 'test': - case 'ci': - return { - networkId: 'shared-test', - nodeUrl: 'https://rpc.ci-testnet.near.org', - masterAccount: 'test.near', - }; - default: - throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); - } -}; diff --git a/packages/near-api-js/test/data/main.wasm b/packages/near-api-js/test/data/main.wasm deleted file mode 100755 index 639540286e293cbf7f61295c9ffadee0d1a5867d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86 zcmZQbEY4+QU|?WjWh`K1WMpM#WDsDJWUgm)Y-l*Zz+KOPO0%mnv*o7d<`-2mF>uAl drzDmn#;4|`Ff($;$7iG_7Q`nd7N;^Z0RaAq6wd$v diff --git a/packages/near-api-js/test/data/multisig.wasm b/packages/near-api-js/test/data/multisig.wasm deleted file mode 100755 index c904c5b7842091e6eed8cd0d7822c705091a1613..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356105 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�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 0; --i) result += '0'; - return result; -} - -async function createAccount(near) { - const newAccountName = generateUniqueString('test'); - const newPublicKey = await near.connection.signer.createKey(newAccountName, networkId); - await near.createAccount(newAccountName, newPublicKey); - const account = new nearApi.Account(near.connection, newAccountName); - return account; -} - -async function deployContract(workingAccount, contractId) { - const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); - const data = [...(await fs.readFile(HELLO_WASM_PATH))]; - await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); - return new nearApi.Contract(workingAccount, contractId, HELLO_WASM_METHODS); -} - -async function ensureDir(dirpath) { - try { - await fs.mkdir(dirpath, { recursive: true }); - } catch (err) { - if (err.code !== 'EEXIST') throw err; - } -} - -module.exports = { - setUpTestConnection, - networkId, - generateUniqueString, - createAccount, - deployContract, - ensureDir, -}; diff --git a/packages/near-api-js/test/wasm/multisig.wasm b/packages/near-api-js/test/wasm/multisig.wasm deleted file mode 100755 index c904c5b7842091e6eed8cd0d7822c705091a1613..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 356105 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�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 Date: Tue, 3 Jan 2023 14:21:01 -0800 Subject: [PATCH 47/84] fix: remove unused dependencies --- packages/providers/package.json | 4 +--- packages/transactions/package.json | 4 +--- pnpm-lock.yaml | 8 -------- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/packages/providers/package.json b/packages/providers/package.json index f4824d3cf..8a880cfb1 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -16,9 +16,7 @@ "@near-js/transactions": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", - "http-errors": "^1.7.2", - "js-sha256": "^0.9.0", - "tweetnacl": "^1.0.1" + "http-errors": "^1.7.2" }, "devDependencies": { "@types/node": "^18.7.14", diff --git a/packages/transactions/package.json b/packages/transactions/package.json index c30ab3e72..05aef07af 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -14,9 +14,7 @@ "@near-js/client-core": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", - "js-sha256": "^0.9.0", - "mustache": "^4.0.0", - "tweetnacl": "^1.0.1" + "js-sha256": "^0.9.0" }, "devDependencies": { "@types/node": "^18.7.14", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3eac05863..aa78ec987 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -196,18 +196,14 @@ importers: borsh: ^0.7.0 http-errors: ^1.7.2 jest: ^26.0.1 - js-sha256: ^0.9.0 node-fetch: ^2.6.1 ts-jest: ^26.5.6 - tweetnacl: ^1.0.1 dependencies: '@near-js/client-core': link:../client-core '@near-js/transactions': link:../transactions bn.js: 5.2.1 borsh: 0.7.0 http-errors: 1.8.1 - js-sha256: 0.9.0 - tweetnacl: 1.0.3 optionalDependencies: node-fetch: 2.6.7 devDependencies: @@ -223,16 +219,12 @@ importers: borsh: ^0.7.0 jest: ^26.0.1 js-sha256: ^0.9.0 - mustache: ^4.0.0 ts-jest: ^26.5.6 - tweetnacl: ^1.0.1 dependencies: '@near-js/client-core': link:../client-core bn.js: 5.2.1 borsh: 0.7.0 js-sha256: 0.9.0 - mustache: 4.2.0 - tweetnacl: 1.0.3 devDependencies: '@types/node': 18.7.14 jest: 26.6.3 From 1f55c419129da3b02378f6d63a83b75df61ddd10 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 14:32:38 -0800 Subject: [PATCH 48/84] refactor: split up key pair implementation --- packages/client-core/src/key_pair/index.ts | 4 +- packages/client-core/src/key_pair/key_pair.ts | 72 +------------------ .../client-core/src/key_pair/key_pair_base.ts | 13 ++++ .../src/key_pair/key_pair_ed25519.ts | 59 +++++++++++++++ 4 files changed, 78 insertions(+), 70 deletions(-) create mode 100644 packages/client-core/src/key_pair/key_pair_base.ts create mode 100644 packages/client-core/src/key_pair/key_pair_ed25519.ts diff --git a/packages/client-core/src/key_pair/index.ts b/packages/client-core/src/key_pair/index.ts index 6894f79c0..f7f05c045 100644 --- a/packages/client-core/src/key_pair/index.ts +++ b/packages/client-core/src/key_pair/index.ts @@ -1,3 +1,5 @@ export { KeyType } from './constants'; -export { KeyPair, KeyPairEd25519, Signature } from './key_pair'; +export { KeyPair } from './key_pair'; +export { Signature } from './key_pair_base'; +export { KeyPairEd25519 } from './key_pair_ed25519'; export { PublicKey } from './public_key'; diff --git a/packages/client-core/src/key_pair/key_pair.ts b/packages/client-core/src/key_pair/key_pair.ts index a1e520ea5..d02790a15 100644 --- a/packages/client-core/src/key_pair/key_pair.ts +++ b/packages/client-core/src/key_pair/key_pair.ts @@ -1,20 +1,7 @@ -import { baseEncode, baseDecode } from 'borsh'; -import nacl from 'tweetnacl'; - -import { KeyType } from './constants'; -import { PublicKey } from './public_key'; - -export interface Signature { - signature: Uint8Array; - publicKey: PublicKey; -} - -export abstract class KeyPair { - abstract sign(message: Uint8Array): Signature; - abstract verify(message: Uint8Array, signature: Uint8Array): boolean; - abstract toString(): string; - abstract getPublicKey(): PublicKey; +import { KeyPairBase } from './key_pair_base'; +import { KeyPairEd25519 } from './key_pair_ed25519'; +export abstract class KeyPair extends KeyPairBase { /** * @param curve Name of elliptical curve, case-insensitive * @returns Random KeyPair based on the curve @@ -40,56 +27,3 @@ export abstract class KeyPair { } } } - -/** - * This class provides key pair functionality for Ed25519 curve: - * generating key pairs, encoding key pairs, signing and verifying. - */ -export class KeyPairEd25519 extends KeyPair { - readonly publicKey: PublicKey; - readonly secretKey: string; - - /** - * Construct an instance of key pair given a secret key. - * It's generally assumed that these are encoded in base58. - * @param {string} secretKey - */ - constructor(secretKey: string) { - super(); - const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); - this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); - this.secretKey = secretKey; - } - - /** - * Generate a new random keypair. - * @example - * const keyRandom = KeyPair.fromRandom(); - * keyRandom.publicKey - * // returns [PUBLIC_KEY] - * - * keyRandom.secretKey - * // returns [SECRET_KEY] - */ - static fromRandom() { - const newKeyPair = nacl.sign.keyPair(); - return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); - } - - sign(message: Uint8Array): Signature { - const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); - return { signature, publicKey: this.publicKey }; - } - - verify(message: Uint8Array, signature: Uint8Array): boolean { - return this.publicKey.verify(message, signature); - } - - toString(): string { - return `ed25519:${this.secretKey}`; - } - - getPublicKey(): PublicKey { - return this.publicKey; - } -} diff --git a/packages/client-core/src/key_pair/key_pair_base.ts b/packages/client-core/src/key_pair/key_pair_base.ts new file mode 100644 index 000000000..47da6c450 --- /dev/null +++ b/packages/client-core/src/key_pair/key_pair_base.ts @@ -0,0 +1,13 @@ +import { PublicKey } from './public_key'; + +export interface Signature { + signature: Uint8Array; + publicKey: PublicKey; +} + +export abstract class KeyPairBase { + abstract sign(message: Uint8Array): Signature; + abstract verify(message: Uint8Array, signature: Uint8Array): boolean; + abstract toString(): string; + abstract getPublicKey(): PublicKey; +} diff --git a/packages/client-core/src/key_pair/key_pair_ed25519.ts b/packages/client-core/src/key_pair/key_pair_ed25519.ts new file mode 100644 index 000000000..1414aca51 --- /dev/null +++ b/packages/client-core/src/key_pair/key_pair_ed25519.ts @@ -0,0 +1,59 @@ +import { baseEncode, baseDecode } from 'borsh'; +import nacl from 'tweetnacl'; + +import { KeyType } from './constants'; +import { KeyPairBase, Signature} from './key_pair_base'; +import { PublicKey } from './public_key'; + +/** + * This class provides key pair functionality for Ed25519 curve: + * generating key pairs, encoding key pairs, signing and verifying. + */ +export class KeyPairEd25519 extends KeyPairBase { + readonly publicKey: PublicKey; + readonly secretKey: string; + + /** + * Construct an instance of key pair given a secret key. + * It's generally assumed that these are encoded in base58. + * @param {string} secretKey + */ + constructor(secretKey: string) { + super(); + const keyPair = nacl.sign.keyPair.fromSecretKey(baseDecode(secretKey)); + this.publicKey = new PublicKey({ keyType: KeyType.ED25519, data: keyPair.publicKey }); + this.secretKey = secretKey; + } + + /** + * Generate a new random keypair. + * @example + * const keyRandom = KeyPair.fromRandom(); + * keyRandom.publicKey + * // returns [PUBLIC_KEY] + * + * keyRandom.secretKey + * // returns [SECRET_KEY] + */ + static fromRandom() { + const newKeyPair = nacl.sign.keyPair(); + return new KeyPairEd25519(baseEncode(newKeyPair.secretKey)); + } + + sign(message: Uint8Array): Signature { + const signature = nacl.sign.detached(message, baseDecode(this.secretKey)); + return { signature, publicKey: this.publicKey }; + } + + verify(message: Uint8Array, signature: Uint8Array): boolean { + return this.publicKey.verify(message, signature); + } + + toString(): string { + return `ed25519:${this.secretKey}`; + } + + getPublicKey(): PublicKey { + return this.publicKey; + } +} From d3349cb900f986b3b0e5259e01747a2bfd5e8d6a Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 3 Jan 2023 17:41:56 -0800 Subject: [PATCH 49/84] refactor: split up client-core into keypairs, keystores, signers, and types packages --- packages/accounts/package.json | 4 + packages/accounts/src/account.ts | 16 +- packages/accounts/src/account_2fa.ts | 3 +- packages/accounts/src/account_creator.ts | 2 +- packages/accounts/src/account_multisig.ts | 2 +- packages/accounts/src/connection.ts | 2 +- packages/accounts/src/contract.ts | 3 +- .../accounts/test/account.access_key.test.js | 2 +- packages/accounts/test/account.test.js | 3 +- .../accounts/test/account_multisig.test.js | 4 +- packages/accounts/test/contract.test.js | 2 +- packages/accounts/test/test-utils.js | 3 +- packages/client-core/package.json | 1 + packages/client-core/src/errors/errors.ts | 29 ---- packages/client-core/src/errors/index.ts | 8 +- packages/client-core/src/errors/rpc_errors.ts | 2 +- packages/client-core/src/index.ts | 4 - packages/client-core/src/logging.ts | 3 +- .../src/{provider/utils.ts => provider.ts} | 2 +- packages/client-core/src/validators.ts | 3 +- packages/keypairs/jest.config.js | 5 + packages/keypairs/package.json | 25 +++ .../key_pair => keypairs/src}/constants.ts | 0 .../src/key_pair => keypairs/src}/index.ts | 0 .../src/key_pair => keypairs/src}/key_pair.ts | 0 .../src}/key_pair_base.ts | 0 .../src}/key_pair_ed25519.ts | 0 .../key_pair => keypairs/src}/public_key.ts | 2 +- .../test/.eslintrc.yml | 0 .../test/key_pair.test.js | 0 .../tsconfig.json | 0 .../package.json | 5 +- .../src/browser_local_storage_key_store.ts | 3 +- .../src/index.ts | 0 .../test/.eslintrc.yml | 0 .../tsconfig.json | 0 .../package.json | 5 +- .../src/index.ts | 0 .../src/unencrypted_file_system_keystore.ts | 3 +- packages/keystores-node/test/.eslintrc.yml | 7 + packages/keystores-node/tsconfig.json | 28 ++++ packages/keystores/jest.config.js | 5 + packages/keystores/package.json | 23 +++ .../src}/in_memory_key_store.ts | 2 +- .../src/key_store => keystores/src}/index.ts | 0 .../key_store => keystores/src}/keystore.ts | 2 +- .../src}/merge_key_store.ts | 2 +- packages/keystores/test/.eslintrc.yml | 7 + packages/keystores/tsconfig.json | 28 ++++ packages/near-api-js/package.json | 10 +- .../browser_local_storage_key_store.ts | 2 +- .../src/key_stores/in_memory_key_store.ts | 2 +- .../near-api-js/src/key_stores/keystore.ts | 2 +- .../src/key_stores/merge_key_store.ts | 2 +- .../unencrypted_file_system_keystore.ts | 2 +- .../src/providers/json-rpc-provider.ts | 2 +- .../near-api-js/src/providers/provider.ts | 8 +- packages/near-api-js/src/signer.ts | 2 +- packages/near-api-js/src/utils/enums.ts | 10 +- packages/near-api-js/src/utils/errors.ts | 4 +- packages/near-api-js/src/utils/key_pair.ts | 2 +- packages/near-api-js/src/utils/rpc_errors.ts | 2 +- packages/providers/package.json | 1 + packages/providers/src/fetch_json.ts | 4 +- packages/providers/src/json-rpc-provider.ts | 8 +- packages/providers/src/provider.ts | 6 +- packages/signers/jest.config.js | 5 + packages/signers/package.json | 24 +++ .../src}/in_memory_signer.ts | 4 +- .../src/signer => signers/src}/index.ts | 0 .../src/signer => signers/src}/signer.ts | 2 +- packages/signers/test/.eslintrc.yml | 7 + .../test/signer.test.js | 4 +- packages/signers/tsconfig.json | 28 ++++ packages/transactions/package.json | 3 + packages/transactions/src/action_creators.ts | 2 +- packages/transactions/src/actions.ts | 3 +- .../transactions/src/create_transaction.ts | 2 +- packages/transactions/src/schema.ts | 3 +- packages/transactions/src/sign.ts | 2 +- packages/types/package.json | 21 +++ .../src/types.ts => types/src/assignable.ts} | 0 packages/types/src/errors.ts | 28 ++++ packages/types/src/index.ts | 3 + .../src/provider/index.ts | 1 - .../src/provider/light_client.ts | 0 .../src/provider/protocol.ts | 0 .../src/provider/request.ts | 0 .../src/provider/response.ts | 0 .../src/provider/validator.ts | 0 packages/types/tsconfig.json | 28 ++++ packages/wallet-account/package.json | 4 + packages/wallet-account/src/near.ts | 8 +- packages/wallet-account/src/wallet_account.ts | 15 +- .../test/wallet_account.test.js | 4 +- pnpm-lock.yaml | 158 ++++++++++++++---- 96 files changed, 515 insertions(+), 163 deletions(-) rename packages/client-core/src/{provider/utils.ts => provider.ts} (88%) create mode 100644 packages/keypairs/jest.config.js create mode 100644 packages/keypairs/package.json rename packages/{client-core/src/key_pair => keypairs/src}/constants.ts (100%) rename packages/{client-core/src/key_pair => keypairs/src}/index.ts (100%) rename packages/{client-core/src/key_pair => keypairs/src}/key_pair.ts (100%) rename packages/{client-core/src/key_pair => keypairs/src}/key_pair_base.ts (100%) rename packages/{client-core/src/key_pair => keypairs/src}/key_pair_ed25519.ts (100%) rename packages/{client-core/src/key_pair => keypairs/src}/public_key.ts (97%) rename packages/{browser-keystore-localstorage => keypairs}/test/.eslintrc.yml (100%) rename packages/{client-core => keypairs}/test/key_pair.test.js (100%) rename packages/{node-keystore-filesystem => keypairs}/tsconfig.json (100%) rename packages/{browser-keystore-localstorage => keystores-browser}/package.json (78%) rename packages/{browser-keystore-localstorage => keystores-browser}/src/browser_local_storage_key_store.ts (98%) rename packages/{browser-keystore-localstorage => keystores-browser}/src/index.ts (100%) rename packages/{node-keystore-filesystem => keystores-browser}/test/.eslintrc.yml (100%) rename packages/{browser-keystore-localstorage => keystores-browser}/tsconfig.json (100%) rename packages/{node-keystore-filesystem => keystores-node}/package.json (79%) rename packages/{node-keystore-filesystem => keystores-node}/src/index.ts (100%) rename packages/{node-keystore-filesystem => keystores-node}/src/unencrypted_file_system_keystore.ts (98%) create mode 100644 packages/keystores-node/test/.eslintrc.yml create mode 100644 packages/keystores-node/tsconfig.json create mode 100644 packages/keystores/jest.config.js create mode 100644 packages/keystores/package.json rename packages/{client-core/src/key_store => keystores/src}/in_memory_key_store.ts (98%) rename packages/{client-core/src/key_store => keystores/src}/index.ts (100%) rename packages/{client-core/src/key_store => keystores/src}/keystore.ts (93%) rename packages/{client-core/src/key_store => keystores/src}/merge_key_store.ts (98%) create mode 100644 packages/keystores/test/.eslintrc.yml create mode 100644 packages/keystores/tsconfig.json create mode 100644 packages/signers/jest.config.js create mode 100644 packages/signers/package.json rename packages/{client-core/src/signer => signers/src}/in_memory_signer.ts (95%) rename packages/{client-core/src/signer => signers/src}/index.ts (100%) rename packages/{client-core/src/signer => signers/src}/signer.ts (94%) create mode 100644 packages/signers/test/.eslintrc.yml rename packages/{client-core => signers}/test/signer.test.js (68%) create mode 100644 packages/signers/tsconfig.json create mode 100644 packages/types/package.json rename packages/{client-core/src/types.ts => types/src/assignable.ts} (100%) create mode 100644 packages/types/src/errors.ts create mode 100644 packages/types/src/index.ts rename packages/{client-core => types}/src/provider/index.ts (96%) rename packages/{client-core => types}/src/provider/light_client.ts (100%) rename packages/{client-core => types}/src/provider/protocol.ts (100%) rename packages/{client-core => types}/src/provider/request.ts (100%) rename packages/{client-core => types}/src/provider/response.ts (100%) rename packages/{client-core => types}/src/provider/validator.ts (100%) create mode 100644 packages/types/tsconfig.json diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 1bc5e878c..1b698d36a 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -13,13 +13,17 @@ "license": "ISC", "dependencies": { "@near-js/client-core": "workspace:*", + "@near-js/keypairs": "workspace:*", "@near-js/providers": "workspace:*", + "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", + "@near-js/types": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "depd": "^2.0.0" }, "devDependencies": { + "@near-js/keystores": "workspace:*", "@types/node": "^18.7.14", "bs58": "^4.0.0", "jest": "^26.0.1", diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index f6315c4b4..0b417cf85 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -1,5 +1,12 @@ import BN from 'bn.js'; +import { + logWarning, + parseResultError, + DEFAULT_FUNCTION_CALL_GAS, + printTxOutcomeLogs, + printTxOutcomeLogsAndFailures, +} from '@near-js/client-core'; import { exponentialBackoff } from '@near-js/providers'; import { transfer, @@ -17,14 +24,9 @@ import { SignedTransaction, stringifyJsonOrBytes } from '@near-js/transactions'; +import { PublicKey } from '@near-js/keypairs'; import { - PublicKey, - logWarning, PositionalArgsError, - parseResultError, - DEFAULT_FUNCTION_CALL_GAS, - printTxOutcomeLogs, - printTxOutcomeLogsAndFailures, FinalExecutionOutcome, TypedError, ErrorContext, @@ -37,7 +39,7 @@ import { AccessKeyInfoView, FunctionCallPermissionView, BlockReference, -} from '@near-js/client-core'; +} from '@near-js/types'; import { baseDecode, baseEncode } from 'borsh'; import { Connection } from './connection'; diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index 5ef0ca828..a137d7b8d 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -1,6 +1,7 @@ 'use strict'; -import { PublicKey, FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; +import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; import { fetchJson } from '@near-js/providers'; import { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from '@near-js/transactions'; import BN from 'bn.js'; diff --git a/packages/accounts/src/account_creator.ts b/packages/accounts/src/account_creator.ts index b1a8187d0..899bd9c18 100644 --- a/packages/accounts/src/account_creator.ts +++ b/packages/accounts/src/account_creator.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; import { fetchJson } from '@near-js/providers'; import BN from 'bn.js'; diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index 3ef4b18bf..9240d24b7 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -1,7 +1,7 @@ 'use strict'; -import { FinalExecutionOutcome } from '@near-js/client-core'; import { Action, deployContract, functionCall } from '@near-js/transactions'; +import { FinalExecutionOutcome } from '@near-js/types'; import { Account, SignAndSendTransactionOptions } from './account'; import { Connection } from './connection'; diff --git a/packages/accounts/src/connection.ts b/packages/accounts/src/connection.ts index eb26b1ce4..3c27a3a45 100644 --- a/packages/accounts/src/connection.ts +++ b/packages/accounts/src/connection.ts @@ -1,4 +1,4 @@ -import { Signer, InMemorySigner } from '@near-js/client-core'; +import { Signer, InMemorySigner } from '@near-js/signers'; import { Provider, JsonRpcProvider } from '@near-js/providers'; /** diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts index 72c63698f..cb35ea79c 100644 --- a/packages/accounts/src/contract.ts +++ b/packages/accounts/src/contract.ts @@ -1,4 +1,5 @@ -import { getTransactionLastResult, PositionalArgsError, ArgumentTypeError } from '@near-js/client-core'; +import { getTransactionLastResult } from '@near-js/client-core'; +import { ArgumentTypeError, PositionalArgsError } from '@near-js/types'; import BN from 'bn.js'; import depd from 'depd'; diff --git a/packages/accounts/test/account.access_key.test.js b/packages/accounts/test/account.access_key.test.js index c1bc4a430..db7197b96 100644 --- a/packages/accounts/test/account.access_key.test.js +++ b/packages/accounts/test/account.access_key.test.js @@ -1,4 +1,4 @@ -const { KeyPair } = require('@near-js/client-core'); +const { KeyPair } = require('@near-js/keypairs'); const testUtils = require('./test-utils'); diff --git a/packages/accounts/test/account.test.js b/packages/accounts/test/account.test.js index 9c21f51e7..bf21590ca 100644 --- a/packages/accounts/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -1,5 +1,6 @@ -const { getTransactionLastResult, TypedError } = require('@near-js/client-core'); +const { getTransactionLastResult } = require('@near-js/client-core'); const { transfer } = require('@near-js/transactions'); +const { TypedError } = require('@near-js/types'); const BN = require('bn.js'); const fs = require('fs'); diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index eac9fd551..3ca20e77c 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -1,5 +1,7 @@ /* global BigInt */ -const { InMemorySigner, KeyPair, parseNearAmount } = require('@near-js/client-core'); +const { parseNearAmount } = require('@near-js/client-core'); +const { KeyPair } = require('@near-js/keypairs'); +const { InMemorySigner } = require('@near-js/signers'); const { functionCall, transfer } = require('@near-js/transactions'); const BN = require('bn.js'); const fs = require('fs'); diff --git a/packages/accounts/test/contract.test.js b/packages/accounts/test/contract.test.js index d5901fab3..d56fff804 100644 --- a/packages/accounts/test/contract.test.js +++ b/packages/accounts/test/contract.test.js @@ -1,4 +1,4 @@ -const { PositionalArgsError } = require('@near-js/client-core'); +const { PositionalArgsError } = require('@near-js/types'); const { Contract } = require('../lib'); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index ebb88d24a..332d9762b 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -1,4 +1,5 @@ -const { InMemoryKeyStore, KeyPair } = require('@near-js/client-core'); +const { KeyPair } = require('@near-js/keypairs'); +const { InMemoryKeyStore } = require('@near-js/keystores'); const BN = require('bn.js'); const fs = require('fs').promises; diff --git a/packages/client-core/package.json b/packages/client-core/package.json index bd674db6f..2e0180b98 100644 --- a/packages/client-core/package.json +++ b/packages/client-core/package.json @@ -12,6 +12,7 @@ "author": "", "license": "ISC", "dependencies": { + "@near-js/types": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "depd": "^2.0.0", diff --git a/packages/client-core/src/errors/errors.ts b/packages/client-core/src/errors/errors.ts index bb46224b0..c6bd55588 100644 --- a/packages/client-core/src/errors/errors.ts +++ b/packages/client-core/src/errors/errors.ts @@ -1,32 +1,3 @@ -export class PositionalArgsError extends Error { - constructor() { - super('Contract method calls expect named arguments wrapped in object, e.g. { argName1: argValue1, argName2: argValue2 }'); - } -} - -export class ArgumentTypeError extends Error { - constructor(argName: string, argType: string, argValue: any) { - super(`Expected ${argType} for '${argName}' argument, but got '${JSON.stringify(argValue)}'`); - } -} - -export class TypedError extends Error { - type: string; - context?: ErrorContext; - constructor(message?: string, type?: string, context?: ErrorContext) { - super(message); - this.type = type || 'UntypedError'; - this.context = context; - } -} - -export class ErrorContext { - transactionHash?: string; - constructor(transactionHash?: string) { - this.transactionHash = transactionHash; - } -} - export function logWarning(...args: any[]): void { if (!process.env['NEAR_NO_LOGS']){ console.warn(...args); diff --git a/packages/client-core/src/errors/index.ts b/packages/client-core/src/errors/index.ts index 3889242d0..427780331 100644 --- a/packages/client-core/src/errors/index.ts +++ b/packages/client-core/src/errors/index.ts @@ -1,10 +1,4 @@ -export { - ArgumentTypeError, - ErrorContext, - PositionalArgsError, - TypedError, - logWarning, -} from './errors'; +export { logWarning } from './errors'; export { ServerError, formatError, diff --git a/packages/client-core/src/errors/rpc_errors.ts b/packages/client-core/src/errors/rpc_errors.ts index 27477f368..54605bc16 100644 --- a/packages/client-core/src/errors/rpc_errors.ts +++ b/packages/client-core/src/errors/rpc_errors.ts @@ -1,7 +1,7 @@ +import { TypedError } from '@near-js/types'; import Mustache from 'mustache'; import { formatNearAmount } from '../format'; -import { TypedError } from './errors' import messages from './error_messages.json'; import schema from './rpc_error_schema.json'; diff --git a/packages/client-core/src/index.ts b/packages/client-core/src/index.ts index 4c0361afe..acf0293d2 100644 --- a/packages/client-core/src/index.ts +++ b/packages/client-core/src/index.ts @@ -1,10 +1,6 @@ export * from './constants'; export * from './errors'; export * from './format'; -export * from './key_pair'; -export * from './key_store'; export * from './logging'; export * from './provider'; -export * from './signer'; -export * from './types'; export * from './validators'; diff --git a/packages/client-core/src/logging.ts b/packages/client-core/src/logging.ts index 1c56f4985..5024a7559 100644 --- a/packages/client-core/src/logging.ts +++ b/packages/client-core/src/logging.ts @@ -1,5 +1,6 @@ +import { FinalExecutionOutcome } from '@near-js/types'; + import { parseRpcError } from './errors'; -import { FinalExecutionOutcome } from './provider'; const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; diff --git a/packages/client-core/src/provider/utils.ts b/packages/client-core/src/provider.ts similarity index 88% rename from packages/client-core/src/provider/utils.ts rename to packages/client-core/src/provider.ts index 7b328d7a7..24530eda5 100644 --- a/packages/client-core/src/provider/utils.ts +++ b/packages/client-core/src/provider.ts @@ -1,4 +1,4 @@ -import { FinalExecutionOutcome } from './response'; +import { FinalExecutionOutcome } from '@near-js/types'; /** @hidden */ export function getTransactionLastResult(txResult: FinalExecutionOutcome): any { diff --git a/packages/client-core/src/validators.ts b/packages/client-core/src/validators.ts index 979f92511..26ef82edd 100644 --- a/packages/client-core/src/validators.ts +++ b/packages/client-core/src/validators.ts @@ -1,10 +1,9 @@ 'use strict'; +import { CurrentEpochValidatorInfo, NextEpochValidatorInfo } from '@near-js/types'; import BN from 'bn.js'; import depd from 'depd'; -import { CurrentEpochValidatorInfo, NextEpochValidatorInfo } from './provider'; - /** Finds seat price given validators stakes and number of seats. * Calculation follow the spec: https://nomicon.io/Economics/README.html#validator-selection * @params validators: current or next epoch validators. diff --git a/packages/keypairs/jest.config.js b/packages/keypairs/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/keypairs/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/keypairs/package.json b/packages/keypairs/package.json new file mode 100644 index 000000000..cb4db7f15 --- /dev/null +++ b/packages/keypairs/package.json @@ -0,0 +1,25 @@ +{ + "name": "@near-js/keypairs", + "version": "0.0.1", + "description": "Abstractions around NEAR-compatible elliptical curves and cryptographic keys", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/types": "workspace:*", + "bn.js": "5.2.1", + "borsh": "^0.7.0", + "tweetnacl": "^1.0.1" + }, + "devDependencies": { + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" + } +} diff --git a/packages/client-core/src/key_pair/constants.ts b/packages/keypairs/src/constants.ts similarity index 100% rename from packages/client-core/src/key_pair/constants.ts rename to packages/keypairs/src/constants.ts diff --git a/packages/client-core/src/key_pair/index.ts b/packages/keypairs/src/index.ts similarity index 100% rename from packages/client-core/src/key_pair/index.ts rename to packages/keypairs/src/index.ts diff --git a/packages/client-core/src/key_pair/key_pair.ts b/packages/keypairs/src/key_pair.ts similarity index 100% rename from packages/client-core/src/key_pair/key_pair.ts rename to packages/keypairs/src/key_pair.ts diff --git a/packages/client-core/src/key_pair/key_pair_base.ts b/packages/keypairs/src/key_pair_base.ts similarity index 100% rename from packages/client-core/src/key_pair/key_pair_base.ts rename to packages/keypairs/src/key_pair_base.ts diff --git a/packages/client-core/src/key_pair/key_pair_ed25519.ts b/packages/keypairs/src/key_pair_ed25519.ts similarity index 100% rename from packages/client-core/src/key_pair/key_pair_ed25519.ts rename to packages/keypairs/src/key_pair_ed25519.ts diff --git a/packages/client-core/src/key_pair/public_key.ts b/packages/keypairs/src/public_key.ts similarity index 97% rename from packages/client-core/src/key_pair/public_key.ts rename to packages/keypairs/src/public_key.ts index f200f3c1a..b656394e9 100644 --- a/packages/client-core/src/key_pair/public_key.ts +++ b/packages/keypairs/src/public_key.ts @@ -1,7 +1,7 @@ +import { Assignable } from '@near-js/types'; import { baseEncode, baseDecode } from 'borsh'; import nacl from 'tweetnacl'; -import { Assignable } from '../types'; import { KeyType } from './constants'; function key_type_to_str(keyType: KeyType): string { diff --git a/packages/browser-keystore-localstorage/test/.eslintrc.yml b/packages/keypairs/test/.eslintrc.yml similarity index 100% rename from packages/browser-keystore-localstorage/test/.eslintrc.yml rename to packages/keypairs/test/.eslintrc.yml diff --git a/packages/client-core/test/key_pair.test.js b/packages/keypairs/test/key_pair.test.js similarity index 100% rename from packages/client-core/test/key_pair.test.js rename to packages/keypairs/test/key_pair.test.js diff --git a/packages/node-keystore-filesystem/tsconfig.json b/packages/keypairs/tsconfig.json similarity index 100% rename from packages/node-keystore-filesystem/tsconfig.json rename to packages/keypairs/tsconfig.json diff --git a/packages/browser-keystore-localstorage/package.json b/packages/keystores-browser/package.json similarity index 78% rename from packages/browser-keystore-localstorage/package.json rename to packages/keystores-browser/package.json index 7eea0368d..eeb8ac11d 100644 --- a/packages/browser-keystore-localstorage/package.json +++ b/packages/keystores-browser/package.json @@ -1,5 +1,5 @@ { - "name": "@near-js/browser-keystore-localstorage", + "name": "@near-js/keystores-browser", "version": "0.0.1", "description": "KeyStore implementation for working with keys in browser LocalStorage", "main": "lib/index.js", @@ -12,7 +12,8 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*" + "@near-js/keypairs": "workspace:*", + "@near-js/keystores": "workspace:*" }, "devDependencies": { "jest": "^26.0.1", diff --git a/packages/browser-keystore-localstorage/src/browser_local_storage_key_store.ts b/packages/keystores-browser/src/browser_local_storage_key_store.ts similarity index 98% rename from packages/browser-keystore-localstorage/src/browser_local_storage_key_store.ts rename to packages/keystores-browser/src/browser_local_storage_key_store.ts index 8aa0f574b..ba4cd6372 100644 --- a/packages/browser-keystore-localstorage/src/browser_local_storage_key_store.ts +++ b/packages/keystores-browser/src/browser_local_storage_key_store.ts @@ -1,4 +1,5 @@ -import { KeyPair, KeyStore } from '@near-js/client-core'; +import { KeyPair } from '@near-js/keypairs'; +import { KeyStore } from '@near-js/keystores'; const LOCAL_STORAGE_KEY_PREFIX = 'near-api-js:keystore:'; diff --git a/packages/browser-keystore-localstorage/src/index.ts b/packages/keystores-browser/src/index.ts similarity index 100% rename from packages/browser-keystore-localstorage/src/index.ts rename to packages/keystores-browser/src/index.ts diff --git a/packages/node-keystore-filesystem/test/.eslintrc.yml b/packages/keystores-browser/test/.eslintrc.yml similarity index 100% rename from packages/node-keystore-filesystem/test/.eslintrc.yml rename to packages/keystores-browser/test/.eslintrc.yml diff --git a/packages/browser-keystore-localstorage/tsconfig.json b/packages/keystores-browser/tsconfig.json similarity index 100% rename from packages/browser-keystore-localstorage/tsconfig.json rename to packages/keystores-browser/tsconfig.json diff --git a/packages/node-keystore-filesystem/package.json b/packages/keystores-node/package.json similarity index 79% rename from packages/node-keystore-filesystem/package.json rename to packages/keystores-node/package.json index 9725a2c90..4375e0b07 100644 --- a/packages/node-keystore-filesystem/package.json +++ b/packages/keystores-node/package.json @@ -1,5 +1,5 @@ { - "name": "@near-js/node-keystore-filesystem", + "name": "@near-js/keystores-node", "version": "0.0.1", "description": "KeyStore implementation for working with keys in the local filesystem", "main": "lib/index.js", @@ -12,7 +12,8 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*" + "@near-js/keypairs": "workspace:*", + "@near-js/keystores": "workspace:*" }, "devDependencies": { "@types/node": "^18.7.14", diff --git a/packages/node-keystore-filesystem/src/index.ts b/packages/keystores-node/src/index.ts similarity index 100% rename from packages/node-keystore-filesystem/src/index.ts rename to packages/keystores-node/src/index.ts diff --git a/packages/node-keystore-filesystem/src/unencrypted_file_system_keystore.ts b/packages/keystores-node/src/unencrypted_file_system_keystore.ts similarity index 98% rename from packages/node-keystore-filesystem/src/unencrypted_file_system_keystore.ts rename to packages/keystores-node/src/unencrypted_file_system_keystore.ts index 3b3c0cce3..589547d69 100644 --- a/packages/node-keystore-filesystem/src/unencrypted_file_system_keystore.ts +++ b/packages/keystores-node/src/unencrypted_file_system_keystore.ts @@ -1,4 +1,5 @@ -import { KeyPair, KeyStore } from '@near-js/client-core'; +import { KeyPair } from '@near-js/keypairs'; +import { KeyStore } from '@near-js/keystores'; import fs from 'fs'; import path from 'path'; import { promisify } from 'util'; diff --git a/packages/keystores-node/test/.eslintrc.yml b/packages/keystores-node/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/keystores-node/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/keystores-node/tsconfig.json b/packages/keystores-node/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/keystores-node/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/keystores/jest.config.js b/packages/keystores/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/keystores/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/keystores/package.json b/packages/keystores/package.json new file mode 100644 index 000000000..28e26bec6 --- /dev/null +++ b/packages/keystores/package.json @@ -0,0 +1,23 @@ +{ + "name": "@near-js/keystores", + "version": "0.0.1", + "description": "Key storage and management implementations", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test --passWithNoTests" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/keypairs": "workspace:*", + "@near-js/types": "workspace:*" + }, + "devDependencies": { + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" + } +} diff --git a/packages/client-core/src/key_store/in_memory_key_store.ts b/packages/keystores/src/in_memory_key_store.ts similarity index 98% rename from packages/client-core/src/key_store/in_memory_key_store.ts rename to packages/keystores/src/in_memory_key_store.ts index 873dbe81e..bbd4d16eb 100644 --- a/packages/client-core/src/key_store/in_memory_key_store.ts +++ b/packages/keystores/src/in_memory_key_store.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '../key_pair'; +import { KeyPair } from '@near-js/keypairs'; import { KeyStore } from './keystore'; /** diff --git a/packages/client-core/src/key_store/index.ts b/packages/keystores/src/index.ts similarity index 100% rename from packages/client-core/src/key_store/index.ts rename to packages/keystores/src/index.ts diff --git a/packages/client-core/src/key_store/keystore.ts b/packages/keystores/src/keystore.ts similarity index 93% rename from packages/client-core/src/key_store/keystore.ts rename to packages/keystores/src/keystore.ts index 12447c7e3..22fdb6de5 100644 --- a/packages/client-core/src/key_store/keystore.ts +++ b/packages/keystores/src/keystore.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '../key_pair'; +import { KeyPair } from '@near-js/keypairs'; /** * KeyStores are passed to {@link near!Near} via {@link near!NearConfig} diff --git a/packages/client-core/src/key_store/merge_key_store.ts b/packages/keystores/src/merge_key_store.ts similarity index 98% rename from packages/client-core/src/key_store/merge_key_store.ts rename to packages/keystores/src/merge_key_store.ts index c2796976b..8d6270132 100644 --- a/packages/client-core/src/key_store/merge_key_store.ts +++ b/packages/keystores/src/merge_key_store.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '../key_pair'; +import { KeyPair } from '@near-js/keypairs'; import { KeyStore } from './keystore'; /** diff --git a/packages/keystores/test/.eslintrc.yml b/packages/keystores/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/keystores/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/keystores/tsconfig.json b/packages/keystores/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/keystores/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index ccf7c3c63..451c90165 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,12 +12,16 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/browser-keystore-localstorage": "workspace:*", "@near-js/client-core": "workspace:*", - "@near-js/wallet-account": "workspace:*", - "@near-js/node-keystore-filesystem": "workspace:*", + "@near-js/keypairs": "workspace:*", + "@near-js/keystores": "workspace:*", + "@near-js/keystores-browser": "workspace:*", + "@near-js/keystores-node": "workspace:*", "@near-js/providers": "workspace:*", + "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", + "@near-js/types": "workspace:*", + "@near-js/wallet-account": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", "bn.js": "5.2.1", diff --git a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts index 9984404a6..fe4cef8c6 100644 --- a/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts +++ b/packages/near-api-js/src/key_stores/browser_local_storage_key_store.ts @@ -1 +1 @@ -export { BrowserLocalStorageKeyStore } from '@near-js/browser-keystore-localstorage'; \ No newline at end of file +export { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; \ No newline at end of file diff --git a/packages/near-api-js/src/key_stores/in_memory_key_store.ts b/packages/near-api-js/src/key_stores/in_memory_key_store.ts index 08aa74249..e89ffaa0c 100644 --- a/packages/near-api-js/src/key_stores/in_memory_key_store.ts +++ b/packages/near-api-js/src/key_stores/in_memory_key_store.ts @@ -1 +1 @@ -export { InMemoryKeyStore } from '@near-js/client-core'; +export { InMemoryKeyStore } from '@near-js/keystores'; diff --git a/packages/near-api-js/src/key_stores/keystore.ts b/packages/near-api-js/src/key_stores/keystore.ts index 6eab9833c..1d891ccb6 100644 --- a/packages/near-api-js/src/key_stores/keystore.ts +++ b/packages/near-api-js/src/key_stores/keystore.ts @@ -1 +1 @@ -export { KeyStore } from '@near-js/client-core'; +export { KeyStore } from '@near-js/keystores'; diff --git a/packages/near-api-js/src/key_stores/merge_key_store.ts b/packages/near-api-js/src/key_stores/merge_key_store.ts index 0ffca4319..c0f9e25f1 100644 --- a/packages/near-api-js/src/key_stores/merge_key_store.ts +++ b/packages/near-api-js/src/key_stores/merge_key_store.ts @@ -1 +1 @@ -export { MergeKeyStore } from '@near-js/client-core'; +export { MergeKeyStore } from '@near-js/keystores'; diff --git a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts index f27819289..c36e134a3 100644 --- a/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts +++ b/packages/near-api-js/src/key_stores/unencrypted_file_system_keystore.ts @@ -1 +1 @@ -export { readKeyFile, UnencryptedFileSystemKeyStore } from '@near-js/node-keystore-filesystem'; +export { readKeyFile, UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; diff --git a/packages/near-api-js/src/providers/json-rpc-provider.ts b/packages/near-api-js/src/providers/json-rpc-provider.ts index 4bddefc01..d18c08ebd 100644 --- a/packages/near-api-js/src/providers/json-rpc-provider.ts +++ b/packages/near-api-js/src/providers/json-rpc-provider.ts @@ -1,2 +1,2 @@ -export { ErrorContext, TypedError } from '@near-js/client-core'; +export { ErrorContext, TypedError } from '@near-js/types'; export { JsonRpcProvider } from '@near-js/providers'; diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index dc4d9d62d..c14761bc3 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -1,4 +1,6 @@ +export { getTransactionLastResult } from '@near-js/client-core'; +export { Provider } from '@near-js/providers'; export { IdType, LightClientBlockLiteView, @@ -61,13 +63,9 @@ export { FunctionCallPermissionView, QueryResponseKind, ViewStateResult, - getTransactionLastResult, CurrentEpochValidatorInfo, EpochValidatorInfo, NextEpochValidatorInfo, ValidatorStakeView, -} from '@near-js/client-core'; -export { - Provider, -} from '@near-js/providers'; +} from '@near-js/types'; diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index 74e867942..0195a0720 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1 +1 @@ -export { InMemorySigner, Signer } from '@near-js/client-core'; +export { InMemorySigner, Signer } from '@near-js/signers'; diff --git a/packages/near-api-js/src/utils/enums.ts b/packages/near-api-js/src/utils/enums.ts index bb6c98dab..d2000e929 100644 --- a/packages/near-api-js/src/utils/enums.ts +++ b/packages/near-api-js/src/utils/enums.ts @@ -1,3 +1,5 @@ +export { Assignable } from '@near-js/types'; + /** @hidden @module */ export abstract class Enum { enum: string; @@ -12,11 +14,3 @@ export abstract class Enum { }); } } - -export abstract class Assignable { - constructor(properties: any) { - Object.keys(properties).map((key: any) => { - (this as any)[key] = properties[key]; - }); - } -} diff --git a/packages/near-api-js/src/utils/errors.ts b/packages/near-api-js/src/utils/errors.ts index 7a2c75a7a..a74073183 100644 --- a/packages/near-api-js/src/utils/errors.ts +++ b/packages/near-api-js/src/utils/errors.ts @@ -1,7 +1,7 @@ +export { logWarning } from '@near-js/client-core'; export { ArgumentTypeError, ErrorContext, PositionalArgsError, TypedError, - logWarning, -} from '@near-js/client-core'; +} from '@near-js/types'; diff --git a/packages/near-api-js/src/utils/key_pair.ts b/packages/near-api-js/src/utils/key_pair.ts index 99280c3dd..82d3ad7be 100644 --- a/packages/near-api-js/src/utils/key_pair.ts +++ b/packages/near-api-js/src/utils/key_pair.ts @@ -4,6 +4,6 @@ export { KeyType, PublicKey, Signature, -} from '@near-js/client-core'; +} from '@near-js/keypairs'; export type Arrayish = string | ArrayLike; diff --git a/packages/near-api-js/src/utils/rpc_errors.ts b/packages/near-api-js/src/utils/rpc_errors.ts index c38d7dacd..248e294c8 100644 --- a/packages/near-api-js/src/utils/rpc_errors.ts +++ b/packages/near-api-js/src/utils/rpc_errors.ts @@ -1,7 +1,7 @@ export { - ServerError, parseRpcError, parseResultError, formatError, getErrorTypeFromErrorMessage, + ServerError, } from '@near-js/client-core'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 8a880cfb1..e5dbc8a73 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -14,6 +14,7 @@ "dependencies": { "@near-js/client-core": "workspace:*", "@near-js/transactions": "workspace:*", + "@near-js/types": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "http-errors": "^1.7.2" diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index f4deb8182..e80c50c19 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -1,6 +1,4 @@ -import { - TypedError, -} from '@near-js/client-core'; +import { TypedError } from '@near-js/types'; import createError from 'http-errors'; import { exponentialBackoff } from './exponential-backoff'; diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 701084457..5828b5be9 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -5,6 +5,10 @@ * which can be used to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). * @see {@link providers/provider | providers} for a list of request and response types */ +import { + getErrorTypeFromErrorMessage, + parseRpcError, +} from '@near-js/client-core'; import { AccessKeyWithPublicKey, BlockId, @@ -23,9 +27,7 @@ import { NodeStatusResult, QueryResponseKind, TypedError, - getErrorTypeFromErrorMessage, - parseRpcError, -} from '@near-js/client-core'; +} from '@near-js/types'; import { SignedTransaction, } from '@near-js/transactions'; diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index cbd74e700..15e741da7 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -3,6 +3,7 @@ * @module */ +import { SignedTransaction } from '@near-js/transactions'; import { AccessKeyWithPublicKey, BlockChangeResult, @@ -21,10 +22,7 @@ import { QueryResponseKind, RpcQueryRequest, EpochValidatorInfo, -} from '@near-js/client-core'; -import { - SignedTransaction, -} from '@near-js/transactions'; +} from '@near-js/types'; /** @hidden */ export abstract class Provider { diff --git a/packages/signers/jest.config.js b/packages/signers/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/signers/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/signers/package.json b/packages/signers/package.json new file mode 100644 index 000000000..b182ae9ed --- /dev/null +++ b/packages/signers/package.json @@ -0,0 +1,24 @@ +{ + "name": "@near-js/signers", + "version": "0.0.1", + "description": "Core dependencies for the NEAR API JavaScript client", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json", + "test": "jest test" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@near-js/keypairs": "workspace:*", + "@near-js/keystores": "workspace:*", + "js-sha256": "^0.9.0" + }, + "devDependencies": { + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" + } +} diff --git a/packages/client-core/src/signer/in_memory_signer.ts b/packages/signers/src/in_memory_signer.ts similarity index 95% rename from packages/client-core/src/signer/in_memory_signer.ts rename to packages/signers/src/in_memory_signer.ts index c39a2b509..da9c0cc0e 100644 --- a/packages/client-core/src/signer/in_memory_signer.ts +++ b/packages/signers/src/in_memory_signer.ts @@ -1,8 +1,8 @@ +import { KeyPair, PublicKey, Signature } from '@near-js/keypairs'; +import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; import sha256 from 'js-sha256'; import { Signer } from './signer'; -import { KeyPair, PublicKey, Signature } from '../key_pair'; -import { InMemoryKeyStore, KeyStore } from '../key_store'; /** * Signs using in memory key store. diff --git a/packages/client-core/src/signer/index.ts b/packages/signers/src/index.ts similarity index 100% rename from packages/client-core/src/signer/index.ts rename to packages/signers/src/index.ts diff --git a/packages/client-core/src/signer/signer.ts b/packages/signers/src/signer.ts similarity index 94% rename from packages/client-core/src/signer/signer.ts rename to packages/signers/src/signer.ts index 172fa0d39..753e86216 100644 --- a/packages/client-core/src/signer/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,4 +1,4 @@ -import { Signature, PublicKey } from '../key_pair'; +import { Signature, PublicKey } from '@near-js/keypairs'; /** * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. diff --git a/packages/signers/test/.eslintrc.yml b/packages/signers/test/.eslintrc.yml new file mode 100644 index 000000000..a74d2e539 --- /dev/null +++ b/packages/signers/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/client-core/test/signer.test.js b/packages/signers/test/signer.test.js similarity index 68% rename from packages/client-core/test/signer.test.js rename to packages/signers/test/signer.test.js index f8862fe4f..f037b8889 100644 --- a/packages/client-core/test/signer.test.js +++ b/packages/signers/test/signer.test.js @@ -1,4 +1,6 @@ -const { InMemoryKeyStore, InMemorySigner } = require('../lib'); +const { InMemoryKeyStore } = require('@near-js/keystores'); + +const { InMemorySigner } = require('../lib'); test('test no key', async() => { const signer = new InMemorySigner(new InMemoryKeyStore()); diff --git a/packages/signers/tsconfig.json b/packages/signers/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/signers/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 05aef07af..9548dfd79 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -12,6 +12,9 @@ "license": "ISC", "dependencies": { "@near-js/client-core": "workspace:*", + "@near-js/keypairs": "workspace:*", + "@near-js/signers": "workspace:*", + "@near-js/types": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "js-sha256": "^0.9.0" diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index cf0e0cc02..c8f20ca69 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; import BN from 'bn.js'; import { diff --git a/packages/transactions/src/actions.ts b/packages/transactions/src/actions.ts index 3b46a86fa..3a55a9672 100644 --- a/packages/transactions/src/actions.ts +++ b/packages/transactions/src/actions.ts @@ -1,4 +1,5 @@ -import { Assignable, PublicKey } from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; +import { Assignable } from '@near-js/types'; import BN from 'bn.js'; abstract class Enum { diff --git a/packages/transactions/src/create_transaction.ts b/packages/transactions/src/create_transaction.ts index 9bc535562..6914a2cfb 100644 --- a/packages/transactions/src/create_transaction.ts +++ b/packages/transactions/src/create_transaction.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; import BN from 'bn.js'; import { Action } from './actions'; diff --git a/packages/transactions/src/schema.ts b/packages/transactions/src/schema.ts index 42c828417..b32a4f8c1 100644 --- a/packages/transactions/src/schema.ts +++ b/packages/transactions/src/schema.ts @@ -1,4 +1,5 @@ -import { Assignable, KeyType, PublicKey } from '@near-js/client-core'; +import { KeyType, PublicKey } from '@near-js/keypairs'; +import { Assignable } from '@near-js/types'; import BN from 'bn.js'; import { deserialize, serialize } from 'borsh'; diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts index e3d3becd6..830751bf0 100644 --- a/packages/transactions/src/sign.ts +++ b/packages/transactions/src/sign.ts @@ -1,4 +1,4 @@ -import { Signer } from '@near-js/client-core'; +import { Signer } from '@near-js/signers'; import sha256 from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 000000000..7cdbe7213 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,21 @@ +{ + "name": "@near-js/types", + "version": "0.0.1", + "description": "TypeScript types for working with the Near JS API", + "main": "lib/index.js", + "scripts": { + "build": "pnpm compile", + "compile": "tsc -p tsconfig.json" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bn.js": "5.2.1" + }, + "devDependencies": { + "@types/node": "^18.7.14", + "jest": "^26.0.1", + "ts-jest": "^26.5.6" + } +} diff --git a/packages/client-core/src/types.ts b/packages/types/src/assignable.ts similarity index 100% rename from packages/client-core/src/types.ts rename to packages/types/src/assignable.ts diff --git a/packages/types/src/errors.ts b/packages/types/src/errors.ts new file mode 100644 index 000000000..822448c55 --- /dev/null +++ b/packages/types/src/errors.ts @@ -0,0 +1,28 @@ +export class PositionalArgsError extends Error { + constructor() { + super('Contract method calls expect named arguments wrapped in object, e.g. { argName1: argValue1, argName2: argValue2 }'); + } +} + +export class ArgumentTypeError extends Error { + constructor(argName: string, argType: string, argValue: any) { + super(`Expected ${argType} for '${argName}' argument, but got '${JSON.stringify(argValue)}'`); + } +} + +export class TypedError extends Error { + type: string; + context?: ErrorContext; + constructor(message?: string, type?: string, context?: ErrorContext) { + super(message); + this.type = type || 'UntypedError'; + this.context = context; + } +} + +export class ErrorContext { + transactionHash?: string; + constructor(transactionHash?: string) { + this.transactionHash = transactionHash; + } +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 000000000..392a6af8d --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,3 @@ +export * from './assignable'; +export * from './errors'; +export * from './provider'; diff --git a/packages/client-core/src/provider/index.ts b/packages/types/src/provider/index.ts similarity index 96% rename from packages/client-core/src/provider/index.ts rename to packages/types/src/provider/index.ts index f46deee4a..370447265 100644 --- a/packages/client-core/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -65,7 +65,6 @@ export { QueryResponseKind, ViewStateResult, } from './response'; -export { getTransactionLastResult } from './utils' export { CurrentEpochValidatorInfo, EpochValidatorInfo, diff --git a/packages/client-core/src/provider/light_client.ts b/packages/types/src/provider/light_client.ts similarity index 100% rename from packages/client-core/src/provider/light_client.ts rename to packages/types/src/provider/light_client.ts diff --git a/packages/client-core/src/provider/protocol.ts b/packages/types/src/provider/protocol.ts similarity index 100% rename from packages/client-core/src/provider/protocol.ts rename to packages/types/src/provider/protocol.ts diff --git a/packages/client-core/src/provider/request.ts b/packages/types/src/provider/request.ts similarity index 100% rename from packages/client-core/src/provider/request.ts rename to packages/types/src/provider/request.ts diff --git a/packages/client-core/src/provider/response.ts b/packages/types/src/provider/response.ts similarity index 100% rename from packages/client-core/src/provider/response.ts rename to packages/types/src/provider/response.ts diff --git a/packages/client-core/src/provider/validator.ts b/packages/types/src/provider/validator.ts similarity index 100% rename from packages/client-core/src/provider/validator.ts rename to packages/types/src/provider/validator.ts diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 000000000..3f44345ba --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "lib": [ + "es2015", + "esnext" + ], + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "outDir": "./lib", + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json index 01b1d7d7e..ba56fb67a 100644 --- a/packages/wallet-account/package.json +++ b/packages/wallet-account/package.json @@ -14,7 +14,11 @@ "dependencies": { "@near-js/accounts": "workspace:*", "@near-js/client-core": "workspace:*", + "@near-js/keypairs": "workspace:*", + "@near-js/keystores": "workspace:*", + "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", + "@near-js/types": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0" }, diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts index cad784558..f2f874623 100644 --- a/packages/wallet-account/src/near.ts +++ b/packages/wallet-account/src/near.ts @@ -14,11 +14,9 @@ import { LocalAccountCreator, UrlAccountCreator, } from '@near-js/accounts'; -import { - KeyStore, - PublicKey, - Signer, -} from '@near-js/client-core'; +import { PublicKey } from '@near-js/keypairs'; +import { KeyStore } from '@near-js/keystores'; +import { Signer } from '@near-js/signers'; import BN from 'bn.js'; export interface NearConfig { diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index 31fd155e6..286561a69 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -11,16 +11,11 @@ import { Connection, SignAndSendTransactionOptions, } from '@near-js/accounts'; -import { - FinalExecutionOutcome, - InMemorySigner, - KeyPair, - KeyStore, - PublicKey, -} from '@near-js/client-core'; -import { - Transaction, Action, SCHEMA, createTransaction -} from '@near-js/transactions'; +import { KeyPair, PublicKey } from '@near-js/keypairs'; +import { KeyStore } from '@near-js/keystores'; +import { InMemorySigner } from '@near-js/signers'; +import { FinalExecutionOutcome } from '@near-js/types'; +import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transactions'; import BN from 'bn.js'; import { baseDecode, serialize } from 'borsh'; diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 329ec2221..502a72193 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -1,4 +1,6 @@ -const { InMemoryKeyStore, InMemorySigner, KeyPair, PublicKey } = require('@near-js/client-core'); +const { KeyPair, PublicKey } = require('@near-js/keypairs'); +const { InMemoryKeyStore } = require('@near-js/keystores'); +const { InMemorySigner } = require('@near-js/signers'); const { createTransaction, functionCall, SCHEMA, Transaction, transfer } = require('@near-js/transactions'); const BN = require('bn.js'); const { baseDecode, deserialize } = require('borsh'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aa78ec987..ae10b8b4b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,12 @@ importers: packages/accounts: specifiers: '@near-js/client-core': workspace:* + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* '@near-js/providers': workspace:* + '@near-js/signers': workspace:* '@near-js/transactions': workspace:* + '@near-js/types': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -45,31 +49,25 @@ importers: ts-jest: ^26.5.6 dependencies: '@near-js/client-core': link:../client-core + '@near-js/keypairs': link:../keypairs '@near-js/providers': link:../providers + '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions + '@near-js/types': link:../types bn.js: 5.2.1 borsh: 0.7.0 depd: 2.0.0 devDependencies: + '@near-js/keystores': link:../keystores '@types/node': 18.7.14 bs58: 4.0.1 jest: 26.6.3 near-hello: 0.5.1 ts-jest: 26.5.6_jest@26.6.3 - packages/browser-keystore-localstorage: - specifiers: - '@near-js/client-core': workspace:* - jest: ^26.0.1 - ts-jest: ^26.5.6 - dependencies: - '@near-js/client-core': link:../client-core - devDependencies: - jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 - packages/client-core: specifiers: + '@near-js/types': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -80,6 +78,7 @@ importers: ts-jest: ^26.5.6 tweetnacl: ^1.0.1 dependencies: + '@near-js/types': link:../types bn.js: 5.2.1 borsh: 0.7.0 depd: 2.0.0 @@ -101,14 +100,80 @@ importers: homedir: 0.6.0 near-api-js: link:../near-api-js + packages/keypairs: + specifiers: + '@near-js/types': workspace:* + '@types/node': ^18.7.14 + bn.js: 5.2.1 + borsh: ^0.7.0 + jest: ^26.0.1 + ts-jest: ^26.5.6 + tweetnacl: ^1.0.1 + dependencies: + '@near-js/types': link:../types + bn.js: 5.2.1 + borsh: 0.7.0 + tweetnacl: 1.0.3 + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + + packages/keystores: + specifiers: + '@near-js/keypairs': workspace:* + '@near-js/types': workspace:* + '@types/node': ^18.7.14 + jest: ^26.0.1 + ts-jest: ^26.5.6 + dependencies: + '@near-js/keypairs': link:../keypairs + '@near-js/types': link:../types + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + + packages/keystores-browser: + specifiers: + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* + jest: ^26.0.1 + ts-jest: ^26.5.6 + dependencies: + '@near-js/keypairs': link:../keypairs + '@near-js/keystores': link:../keystores + devDependencies: + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + + packages/keystores-node: + specifiers: + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* + '@types/node': ^18.7.14 + jest: ^26.0.1 + ts-jest: ^26.5.6 + dependencies: + '@near-js/keypairs': link:../keypairs + '@near-js/keystores': link:../keystores + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + packages/near-api-js: specifiers: '@near-js/accounts': workspace:* - '@near-js/browser-keystore-localstorage': workspace:* '@near-js/client-core': workspace:* - '@near-js/node-keystore-filesystem': workspace:* + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* + '@near-js/keystores-browser': workspace:* + '@near-js/keystores-node': workspace:* '@near-js/providers': workspace:* + '@near-js/signers': workspace:* '@near-js/transactions': workspace:* + '@near-js/types': workspace:* '@near-js/wallet-account': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 @@ -139,11 +204,15 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/accounts': link:../accounts - '@near-js/browser-keystore-localstorage': link:../browser-keystore-localstorage '@near-js/client-core': link:../client-core - '@near-js/node-keystore-filesystem': link:../node-keystore-filesystem + '@near-js/keypairs': link:../keypairs + '@near-js/keystores': link:../keystores + '@near-js/keystores-browser': link:../keystores-browser + '@near-js/keystores-node': link:../keystores-node '@near-js/providers': link:../providers + '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions + '@near-js/types': link:../types '@near-js/wallet-account': link:../wallet-account ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 @@ -174,23 +243,11 @@ importers: ts-jest: 26.5.6_jest@26.6.3 uglifyify: 5.0.2 - packages/node-keystore-filesystem: - specifiers: - '@near-js/client-core': workspace:* - '@types/node': ^18.7.14 - jest: ^26.0.1 - ts-jest: ^26.5.6 - dependencies: - '@near-js/client-core': link:../client-core - devDependencies: - '@types/node': 18.7.14 - jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 - packages/providers: specifiers: '@near-js/client-core': workspace:* '@near-js/transactions': workspace:* + '@near-js/types': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -201,6 +258,7 @@ importers: dependencies: '@near-js/client-core': link:../client-core '@near-js/transactions': link:../transactions + '@near-js/types': link:../types bn.js: 5.2.1 borsh: 0.7.0 http-errors: 1.8.1 @@ -211,9 +269,29 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 + packages/signers: + specifiers: + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* + '@types/node': ^18.7.14 + jest: ^26.0.1 + js-sha256: ^0.9.0 + ts-jest: ^26.5.6 + dependencies: + '@near-js/keypairs': link:../keypairs + '@near-js/keystores': link:../keystores + js-sha256: 0.9.0 + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + packages/transactions: specifiers: '@near-js/client-core': workspace:* + '@near-js/keypairs': workspace:* + '@near-js/signers': workspace:* + '@near-js/types': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -222,6 +300,9 @@ importers: ts-jest: ^26.5.6 dependencies: '@near-js/client-core': link:../client-core + '@near-js/keypairs': link:../keypairs + '@near-js/signers': link:../signers + '@near-js/types': link:../types bn.js: 5.2.1 borsh: 0.7.0 js-sha256: 0.9.0 @@ -230,11 +311,28 @@ importers: jest: 26.6.3 ts-jest: 26.5.6_jest@26.6.3 + packages/types: + specifiers: + '@types/node': ^18.7.14 + bn.js: 5.2.1 + jest: ^26.0.1 + ts-jest: ^26.5.6 + dependencies: + bn.js: 5.2.1 + devDependencies: + '@types/node': 18.7.14 + jest: 26.6.3 + ts-jest: 26.5.6_jest@26.6.3 + packages/wallet-account: specifiers: '@near-js/accounts': workspace:* '@near-js/client-core': workspace:* + '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* + '@near-js/signers': workspace:* '@near-js/transactions': workspace:* + '@near-js/types': workspace:* '@types/node': ^18.7.14 bn.js: 5.2.1 borsh: ^0.7.0 @@ -244,7 +342,11 @@ importers: dependencies: '@near-js/accounts': link:../accounts '@near-js/client-core': link:../client-core + '@near-js/keypairs': link:../keypairs + '@near-js/keystores': link:../keystores + '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions + '@near-js/types': link:../types bn.js: 5.2.1 borsh: 0.7.0 devDependencies: From d0bdac84f100736fb63faa0cc668aad0796903bc Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 10:48:10 -0800 Subject: [PATCH 50/84] refactor: shared tsconfig --- packages/accounts/tsconfig.json | 21 +------------------- packages/client-core/tsconfig.json | 21 +------------------- packages/keypairs/tsconfig.json | 21 +------------------- packages/keystores-browser/tsconfig.json | 22 +-------------------- packages/keystores-node/tsconfig.json | 21 +------------------- packages/keystores/tsconfig.json | 21 +------------------- packages/providers/tsconfig.json | 21 +------------------- packages/signers/tsconfig.json | 21 +------------------- packages/transactions/tsconfig.json | 25 +++--------------------- packages/types/tsconfig.json | 21 +------------------- packages/wallet-account/tsconfig.json | 22 +-------------------- tsconfig.base.json | 20 +++++++++++++++++++ tsconfig.browser.json | 10 ++++++++++ tsconfig.node.json | 9 +++++++++ 14 files changed, 52 insertions(+), 224 deletions(-) create mode 100644 tsconfig.base.json create mode 100644 tsconfig.browser.json create mode 100644 tsconfig.node.json diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/client-core/tsconfig.json b/packages/client-core/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/client-core/tsconfig.json +++ b/packages/client-core/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/keypairs/tsconfig.json b/packages/keypairs/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/keypairs/tsconfig.json +++ b/packages/keypairs/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/keystores-browser/tsconfig.json b/packages/keystores-browser/tsconfig.json index a77f4f8d3..24c600084 100644 --- a/packages/keystores-browser/tsconfig.json +++ b/packages/keystores-browser/tsconfig.json @@ -1,27 +1,7 @@ { + "extends": "../../tsconfig.browser.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext", - "dom" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/keystores-node/tsconfig.json b/packages/keystores-node/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/keystores-node/tsconfig.json +++ b/packages/keystores-node/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/keystores/tsconfig.json b/packages/keystores/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/keystores/tsconfig.json +++ b/packages/keystores/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/providers/tsconfig.json b/packages/providers/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/providers/tsconfig.json +++ b/packages/providers/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/signers/tsconfig.json b/packages/signers/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/signers/tsconfig.json +++ b/packages/signers/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/transactions/tsconfig.json b/packages/transactions/tsconfig.json index 237474a66..ae42955e4 100644 --- a/packages/transactions/tsconfig.json +++ b/packages/transactions/tsconfig.json @@ -1,28 +1,9 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext", - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ - "src/index.ts", - ], + "src/index.ts" + ] } diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 3f44345ba..ae42955e4 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -1,26 +1,7 @@ { + "extends": "../../tsconfig.node.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/packages/wallet-account/tsconfig.json b/packages/wallet-account/tsconfig.json index a77f4f8d3..24c600084 100644 --- a/packages/wallet-account/tsconfig.json +++ b/packages/wallet-account/tsconfig.json @@ -1,27 +1,7 @@ { + "extends": "../../tsconfig.browser.json", "compilerOptions": { - "esModuleInterop": true, - "lib": [ - "es2015", - "esnext", - "dom" - ], - "module": "commonjs", - "target": "es2015", - "moduleResolution": "node", - "alwaysStrict": true, "outDir": "./lib", - "declaration": true, - "preserveSymlinks": true, - "preserveWatchOutput": true, - "pretty": false, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "noImplicitAny": false, - "noImplicitReturns": true, - "noUnusedLocals": true, - "experimentalDecorators": true, - "resolveJsonModule": true, }, "files": [ "src/index.ts" diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 000000000..3ae27a0e3 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "module": "commonjs", + "target": "es2015", + "moduleResolution": "node", + "alwaysStrict": true, + "declaration": true, + "preserveSymlinks": true, + "preserveWatchOutput": true, + "pretty": false, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": false, + "noImplicitReturns": true, + "noUnusedLocals": true, + "experimentalDecorators": true, + "resolveJsonModule": true, + } +} diff --git a/tsconfig.browser.json b/tsconfig.browser.json new file mode 100644 index 000000000..41a429f16 --- /dev/null +++ b/tsconfig.browser.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "lib": [ + "es2015", + "esnext", + "dom" + ], + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 000000000..b4f764428 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "lib": [ + "es2015", + "esnext" + ], + } +} From 7870cb7d4fd837d535acf3d9cc2bb95ce6be8c75 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 11:25:31 -0800 Subject: [PATCH 51/84] chore: set TS as dev dependency --- packages/accounts/package.json | 3 +- packages/client-core/package.json | 3 +- packages/keypairs/package.json | 3 +- packages/keystores-browser/package.json | 3 +- packages/keystores-node/package.json | 3 +- packages/keystores/package.json | 3 +- packages/providers/package.json | 3 +- packages/signers/package.json | 3 +- packages/transactions/package.json | 3 +- packages/types/package.json | 3 +- packages/wallet-account/package.json | 3 +- pnpm-lock.yaml | 66 ++++++++++++++++++++----- 12 files changed, 77 insertions(+), 22 deletions(-) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 1b698d36a..abdf4f7bf 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -28,6 +28,7 @@ "bs58": "^4.0.0", "jest": "^26.0.1", "near-hello": "^0.5.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/client-core/package.json b/packages/client-core/package.json index 2e0180b98..b0ddbdf75 100644 --- a/packages/client-core/package.json +++ b/packages/client-core/package.json @@ -23,6 +23,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/keypairs/package.json b/packages/keypairs/package.json index cb4db7f15..7fdaf231b 100644 --- a/packages/keypairs/package.json +++ b/packages/keypairs/package.json @@ -20,6 +20,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index eeb8ac11d..b784b4e70 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -17,6 +17,7 @@ }, "devDependencies": { "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 4375e0b07..19163c2fd 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/keystores/package.json b/packages/keystores/package.json index 28e26bec6..47edd69d0 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/providers/package.json b/packages/providers/package.json index e5dbc8a73..5833753f5 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -22,7 +22,8 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" }, "optionalDependencies": { "node-fetch": "^2.6.1" diff --git a/packages/signers/package.json b/packages/signers/package.json index b182ae9ed..4bcfdd097 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -19,6 +19,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 9548dfd79..4c7891cc6 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -22,6 +22,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/types/package.json b/packages/types/package.json index 7cdbe7213..37e414c95 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -16,6 +16,7 @@ "devDependencies": { "@types/node": "^18.7.14", "jest": "^26.0.1", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json index ba56fb67a..69f9c5b7b 100644 --- a/packages/wallet-account/package.json +++ b/packages/wallet-account/package.json @@ -26,6 +26,7 @@ "@types/node": "^18.7.14", "jest": "^26.0.1", "localstorage-memory": "^1.0.3", - "ts-jest": "^26.5.6" + "ts-jest": "^26.5.6", + "typescript": "^4.9.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ae10b8b4b..e8157fe5a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,7 @@ importers: jest: ^26.0.1 near-hello: ^0.5.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs @@ -63,7 +64,8 @@ importers: bs58: 4.0.1 jest: 26.6.3 near-hello: 0.5.1 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/client-core: specifiers: @@ -77,6 +79,7 @@ importers: mustache: ^4.0.0 ts-jest: ^26.5.6 tweetnacl: ^1.0.1 + typescript: ^4.9.4 dependencies: '@near-js/types': link:../types bn.js: 5.2.1 @@ -88,7 +91,8 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/cookbook: specifiers: @@ -109,6 +113,7 @@ importers: jest: ^26.0.1 ts-jest: ^26.5.6 tweetnacl: ^1.0.1 + typescript: ^4.9.4 dependencies: '@near-js/types': link:../types bn.js: 5.2.1 @@ -117,7 +122,8 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/keystores: specifiers: @@ -126,13 +132,15 @@ importers: '@types/node': ^18.7.14 jest: ^26.0.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/keypairs': link:../keypairs '@near-js/types': link:../types devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/keystores-browser: specifiers: @@ -140,12 +148,14 @@ importers: '@near-js/keystores': workspace:* jest: ^26.0.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores devDependencies: jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/keystores-node: specifiers: @@ -154,13 +164,15 @@ importers: '@types/node': ^18.7.14 jest: ^26.0.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/near-api-js: specifiers: @@ -255,6 +267,7 @@ importers: jest: ^26.0.1 node-fetch: ^2.6.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/client-core': link:../client-core '@near-js/transactions': link:../transactions @@ -267,7 +280,8 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/signers: specifiers: @@ -277,6 +291,7 @@ importers: jest: ^26.0.1 js-sha256: ^0.9.0 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores @@ -284,7 +299,8 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/transactions: specifiers: @@ -298,6 +314,7 @@ importers: jest: ^26.0.1 js-sha256: ^0.9.0 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs @@ -309,7 +326,8 @@ importers: devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/types: specifiers: @@ -317,12 +335,14 @@ importers: bn.js: 5.2.1 jest: ^26.0.1 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: bn.js: 5.2.1 devDependencies: '@types/node': 18.7.14 jest: 26.6.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages/wallet-account: specifiers: @@ -339,6 +359,7 @@ importers: jest: ^26.0.1 localstorage-memory: ^1.0.3 ts-jest: ^26.5.6 + typescript: ^4.9.4 dependencies: '@near-js/accounts': link:../accounts '@near-js/client-core': link:../client-core @@ -353,7 +374,8 @@ importers: '@types/node': 18.7.14 jest: 26.6.3 localstorage-memory: 1.0.3 - ts-jest: 26.5.6_jest@26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 packages: @@ -7356,6 +7378,28 @@ packages: yargs-parser: 20.2.9 dev: true + /ts-jest/26.5.6_vxa7amr3o4p5wmsiameezakoli: + resolution: {integrity: sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==} + engines: {node: '>= 10'} + hasBin: true + peerDependencies: + jest: '>=26 <27' + typescript: '>=3.8 <5.0' + dependencies: + bs-logger: 0.2.6 + buffer-from: 1.1.2 + fast-json-stable-stringify: 2.1.0 + jest: 26.6.3 + jest-util: 26.6.2 + json5: 2.2.1 + lodash: 4.17.21 + make-error: 1.3.6 + mkdirp: 1.0.4 + semver: 7.3.7 + typescript: 4.9.4 + yargs-parser: 20.2.9 + dev: true + /ts-node/10.9.1_ewcgsh5jhk3o7xvttutb4bhery: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true From 54f2704d2aab9776b77bbf47e98b06f3e27bc2b7 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 11:45:48 -0800 Subject: [PATCH 52/84] refactor: rename client-core to utils --- packages/accounts/package.json | 4 +- packages/accounts/src/account.ts | 2 +- packages/accounts/src/constants.ts | 2 +- packages/accounts/src/contract.ts | 2 +- packages/accounts/test/account.test.js | 2 +- .../accounts/test/account_multisig.test.js | 2 +- packages/keypairs/package.json | 2 +- packages/keystores-node/package.json | 2 +- packages/keystores/package.json | 2 +- packages/near-api-js/package.json | 4 +- packages/near-api-js/src/constants.ts | 2 +- .../near-api-js/src/providers/provider.ts | 2 +- packages/near-api-js/src/utils/errors.ts | 2 +- packages/near-api-js/src/utils/format.ts | 2 +- packages/near-api-js/src/utils/logging.ts | 2 +- packages/near-api-js/src/utils/rpc_errors.ts | 2 +- packages/near-api-js/src/validators.ts | 2 +- packages/providers/package.json | 4 +- packages/providers/src/json-rpc-provider.ts | 2 +- packages/providers/test/providers.test.js | 2 +- packages/signers/package.json | 2 +- packages/transactions/package.json | 4 +- packages/transactions/test/serialize.test.js | 2 +- packages/types/package.json | 2 +- .../{client-core => utils}/jest.config.js | 0 packages/{client-core => utils}/package.json | 11 +- .../{client-core => utils}/src/constants.ts | 0 .../src/errors/error_messages.json | 0 .../src/errors/errors.ts | 0 .../src/errors/index.ts | 0 .../src/errors/rpc_error_schema.json | 0 .../src/errors/rpc_errors.ts | 0 packages/{client-core => utils}/src/format.ts | 0 packages/{client-core => utils}/src/index.ts | 0 .../{client-core => utils}/src/logging.ts | 0 .../{client-core => utils}/src/provider.ts | 0 .../{client-core => utils}/src/validators.ts | 0 .../{client-core => utils}/test/.eslintrc.yml | 0 .../test/format.test.js | 0 .../test/rpc-errors.test.js | 0 .../test/validator.test.js | 0 packages/{client-core => utils}/tsconfig.json | 0 packages/wallet-account/package.json | 4 +- pnpm-lock.yaml | 152 +++++++++--------- 44 files changed, 107 insertions(+), 116 deletions(-) rename packages/{client-core => utils}/jest.config.js (100%) rename packages/{client-core => utils}/package.json (64%) rename packages/{client-core => utils}/src/constants.ts (100%) rename packages/{client-core => utils}/src/errors/error_messages.json (100%) rename packages/{client-core => utils}/src/errors/errors.ts (100%) rename packages/{client-core => utils}/src/errors/index.ts (100%) rename packages/{client-core => utils}/src/errors/rpc_error_schema.json (100%) rename packages/{client-core => utils}/src/errors/rpc_errors.ts (100%) rename packages/{client-core => utils}/src/format.ts (100%) rename packages/{client-core => utils}/src/index.ts (100%) rename packages/{client-core => utils}/src/logging.ts (100%) rename packages/{client-core => utils}/src/provider.ts (100%) rename packages/{client-core => utils}/src/validators.ts (100%) rename packages/{client-core => utils}/test/.eslintrc.yml (100%) rename packages/{client-core => utils}/test/format.test.js (100%) rename packages/{client-core => utils}/test/rpc-errors.test.js (100%) rename packages/{client-core => utils}/test/validator.test.js (100%) rename packages/{client-core => utils}/tsconfig.json (100%) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index abdf4f7bf..a43b84e81 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -12,19 +12,19 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*", "@near-js/keypairs": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", + "@near-js/utils": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "depd": "^2.0.0" }, "devDependencies": { "@near-js/keystores": "workspace:*", - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "bs58": "^4.0.0", "jest": "^26.0.1", "near-hello": "^0.5.1", diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 0b417cf85..0581d2e02 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -6,7 +6,7 @@ import { DEFAULT_FUNCTION_CALL_GAS, printTxOutcomeLogs, printTxOutcomeLogsAndFailures, -} from '@near-js/client-core'; +} from '@near-js/utils'; import { exponentialBackoff } from '@near-js/providers'; import { transfer, diff --git a/packages/accounts/src/constants.ts b/packages/accounts/src/constants.ts index f53662bf5..464afa7da 100644 --- a/packages/accounts/src/constants.ts +++ b/packages/accounts/src/constants.ts @@ -1,4 +1,4 @@ -import { parseNearAmount } from '@near-js/client-core'; +import { parseNearAmount } from '@near-js/utils'; import BN from 'bn.js'; export const MULTISIG_STORAGE_KEY = '__multisigRequest'; diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts index cb35ea79c..4ba7cb1e6 100644 --- a/packages/accounts/src/contract.ts +++ b/packages/accounts/src/contract.ts @@ -1,4 +1,4 @@ -import { getTransactionLastResult } from '@near-js/client-core'; +import { getTransactionLastResult } from '@near-js/utils'; import { ArgumentTypeError, PositionalArgsError } from '@near-js/types'; import BN from 'bn.js'; import depd from 'depd'; diff --git a/packages/accounts/test/account.test.js b/packages/accounts/test/account.test.js index bf21590ca..68fad2512 100644 --- a/packages/accounts/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -1,4 +1,4 @@ -const { getTransactionLastResult } = require('@near-js/client-core'); +const { getTransactionLastResult } = require('@near-js/utils'); const { transfer } = require('@near-js/transactions'); const { TypedError } = require('@near-js/types'); const BN = require('bn.js'); diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index 3ca20e77c..40cc160a8 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -1,5 +1,5 @@ /* global BigInt */ -const { parseNearAmount } = require('@near-js/client-core'); +const { parseNearAmount } = require('@near-js/utils'); const { KeyPair } = require('@near-js/keypairs'); const { InMemorySigner } = require('@near-js/signers'); const { functionCall, transfer } = require('@near-js/transactions'); diff --git a/packages/keypairs/package.json b/packages/keypairs/package.json index 7fdaf231b..f1e6be372 100644 --- a/packages/keypairs/package.json +++ b/packages/keypairs/package.json @@ -18,7 +18,7 @@ "tweetnacl": "^1.0.1" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 19163c2fd..1044a6709 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -16,7 +16,7 @@ "@near-js/keystores": "workspace:*" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/keystores/package.json b/packages/keystores/package.json index 47edd69d0..2a2d2af65 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -16,7 +16,7 @@ "@near-js/types": "workspace:*" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 451c90165..75f4ecfb9 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,7 +12,6 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/client-core": "workspace:*", "@near-js/keypairs": "workspace:*", "@near-js/keystores": "workspace:*", "@near-js/keystores-browser": "workspace:*", @@ -21,6 +20,7 @@ "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", + "@near-js/utils": "workspace:*", "@near-js/wallet-account": "workspace:*", "ajv": "^8.11.2", "ajv-formats": "^2.1.1", @@ -36,7 +36,7 @@ "devDependencies": { "@types/bn.js": "^5.1.0", "@types/http-errors": "^1.6.1", - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "browserify": "^16.2.3", "bs58": "^4.0.0", "bundlewatch": "^0.3.1", diff --git a/packages/near-api-js/src/constants.ts b/packages/near-api-js/src/constants.ts index c93ee9b45..bca466d86 100644 --- a/packages/near-api-js/src/constants.ts +++ b/packages/near-api-js/src/constants.ts @@ -1 +1 @@ -export { DEFAULT_FUNCTION_CALL_GAS } from '@near-js/client-core'; +export { DEFAULT_FUNCTION_CALL_GAS } from '@near-js/utils'; diff --git a/packages/near-api-js/src/providers/provider.ts b/packages/near-api-js/src/providers/provider.ts index c14761bc3..282825d47 100644 --- a/packages/near-api-js/src/providers/provider.ts +++ b/packages/near-api-js/src/providers/provider.ts @@ -1,5 +1,5 @@ -export { getTransactionLastResult } from '@near-js/client-core'; +export { getTransactionLastResult } from '@near-js/utils'; export { Provider } from '@near-js/providers'; export { IdType, diff --git a/packages/near-api-js/src/utils/errors.ts b/packages/near-api-js/src/utils/errors.ts index a74073183..dd8a24793 100644 --- a/packages/near-api-js/src/utils/errors.ts +++ b/packages/near-api-js/src/utils/errors.ts @@ -1,4 +1,4 @@ -export { logWarning } from '@near-js/client-core'; +export { logWarning } from '@near-js/utils'; export { ArgumentTypeError, ErrorContext, diff --git a/packages/near-api-js/src/utils/format.ts b/packages/near-api-js/src/utils/format.ts index 271ddedde..5495c81e6 100644 --- a/packages/near-api-js/src/utils/format.ts +++ b/packages/near-api-js/src/utils/format.ts @@ -3,4 +3,4 @@ export { NEAR_NOMINATION_EXP, formatNearAmount, parseNearAmount, -} from '@near-js/client-core'; +} from '@near-js/utils'; diff --git a/packages/near-api-js/src/utils/logging.ts b/packages/near-api-js/src/utils/logging.ts index 47125d113..b92bebd9b 100644 --- a/packages/near-api-js/src/utils/logging.ts +++ b/packages/near-api-js/src/utils/logging.ts @@ -1 +1 @@ -export { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from '@near-js/client-core'; +export { printTxOutcomeLogs, printTxOutcomeLogsAndFailures } from '@near-js/utils'; diff --git a/packages/near-api-js/src/utils/rpc_errors.ts b/packages/near-api-js/src/utils/rpc_errors.ts index 248e294c8..fc9c251da 100644 --- a/packages/near-api-js/src/utils/rpc_errors.ts +++ b/packages/near-api-js/src/utils/rpc_errors.ts @@ -4,4 +4,4 @@ export { formatError, getErrorTypeFromErrorMessage, ServerError, -} from '@near-js/client-core'; +} from '@near-js/utils'; diff --git a/packages/near-api-js/src/validators.ts b/packages/near-api-js/src/validators.ts index 8e39eb6b2..d40fb6119 100644 --- a/packages/near-api-js/src/validators.ts +++ b/packages/near-api-js/src/validators.ts @@ -3,4 +3,4 @@ export { findSeatPrice, ChangedValidatorInfo, EpochValidatorsDiff, -} from '@near-js/client-core'; +} from '@near-js/utils'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 5833753f5..24f58829f 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -12,15 +12,15 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*", "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", + "@near-js/utils": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "http-errors": "^1.7.2" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 5828b5be9..1f5b20e92 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -8,7 +8,7 @@ import { getErrorTypeFromErrorMessage, parseRpcError, -} from '@near-js/client-core'; +} from '@near-js/utils'; import { AccessKeyWithPublicKey, BlockId, diff --git a/packages/providers/test/providers.test.js b/packages/providers/test/providers.test.js index b88cf2221..ea824f697 100644 --- a/packages/providers/test/providers.test.js +++ b/packages/providers/test/providers.test.js @@ -1,4 +1,4 @@ -const { getTransactionLastResult } = require('@near-js/client-core'); +const { getTransactionLastResult } = require('@near-js/utils'); const { JsonRpcProvider } = require('../lib'); diff --git a/packages/signers/package.json b/packages/signers/package.json index 4bcfdd097..9841ac931 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -17,7 +17,7 @@ "js-sha256": "^0.9.0" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 4c7891cc6..0dea0e0e0 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -11,16 +11,16 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/client-core": "workspace:*", "@near-js/keypairs": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/types": "workspace:*", + "@near-js/utils": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0", "js-sha256": "^0.9.0" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/transactions/test/serialize.test.js b/packages/transactions/test/serialize.test.js index 530fa1d04..4df3b3dfd 100644 --- a/packages/transactions/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -1,4 +1,4 @@ -const { Assignable, InMemoryKeyStore, InMemorySigner, KeyPair, PublicKey } = require('@near-js/client-core'); +const { Assignable, InMemoryKeyStore, InMemorySigner, KeyPair, PublicKey } = require('@near-js/utils'); const fs = require('fs'); const BN = require('bn.js'); const { baseDecode, baseEncode, deserialize, serialize } = require('borsh'); diff --git a/packages/types/package.json b/packages/types/package.json index 37e414c95..217c8f974 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,7 +14,7 @@ "bn.js": "5.2.1" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/client-core/jest.config.js b/packages/utils/jest.config.js similarity index 100% rename from packages/client-core/jest.config.js rename to packages/utils/jest.config.js diff --git a/packages/client-core/package.json b/packages/utils/package.json similarity index 64% rename from packages/client-core/package.json rename to packages/utils/package.json index b0ddbdf75..711f2ca0e 100644 --- a/packages/client-core/package.json +++ b/packages/utils/package.json @@ -1,7 +1,7 @@ { - "name": "@near-js/client-core", + "name": "@near-js/utils", "version": "0.0.1", - "description": "Core dependencies for the NEAR API JavaScript client", + "description": "Common methods and constants for the NEAR API JavaScript client", "main": "lib/index.js", "scripts": { "build": "pnpm compile", @@ -14,14 +14,11 @@ "dependencies": { "@near-js/types": "workspace:*", "bn.js": "5.2.1", - "borsh": "^0.7.0", "depd": "^2.0.0", - "js-sha256": "^0.9.0", - "mustache": "^4.0.0", - "tweetnacl": "^1.0.1" + "mustache": "^4.0.0" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" diff --git a/packages/client-core/src/constants.ts b/packages/utils/src/constants.ts similarity index 100% rename from packages/client-core/src/constants.ts rename to packages/utils/src/constants.ts diff --git a/packages/client-core/src/errors/error_messages.json b/packages/utils/src/errors/error_messages.json similarity index 100% rename from packages/client-core/src/errors/error_messages.json rename to packages/utils/src/errors/error_messages.json diff --git a/packages/client-core/src/errors/errors.ts b/packages/utils/src/errors/errors.ts similarity index 100% rename from packages/client-core/src/errors/errors.ts rename to packages/utils/src/errors/errors.ts diff --git a/packages/client-core/src/errors/index.ts b/packages/utils/src/errors/index.ts similarity index 100% rename from packages/client-core/src/errors/index.ts rename to packages/utils/src/errors/index.ts diff --git a/packages/client-core/src/errors/rpc_error_schema.json b/packages/utils/src/errors/rpc_error_schema.json similarity index 100% rename from packages/client-core/src/errors/rpc_error_schema.json rename to packages/utils/src/errors/rpc_error_schema.json diff --git a/packages/client-core/src/errors/rpc_errors.ts b/packages/utils/src/errors/rpc_errors.ts similarity index 100% rename from packages/client-core/src/errors/rpc_errors.ts rename to packages/utils/src/errors/rpc_errors.ts diff --git a/packages/client-core/src/format.ts b/packages/utils/src/format.ts similarity index 100% rename from packages/client-core/src/format.ts rename to packages/utils/src/format.ts diff --git a/packages/client-core/src/index.ts b/packages/utils/src/index.ts similarity index 100% rename from packages/client-core/src/index.ts rename to packages/utils/src/index.ts diff --git a/packages/client-core/src/logging.ts b/packages/utils/src/logging.ts similarity index 100% rename from packages/client-core/src/logging.ts rename to packages/utils/src/logging.ts diff --git a/packages/client-core/src/provider.ts b/packages/utils/src/provider.ts similarity index 100% rename from packages/client-core/src/provider.ts rename to packages/utils/src/provider.ts diff --git a/packages/client-core/src/validators.ts b/packages/utils/src/validators.ts similarity index 100% rename from packages/client-core/src/validators.ts rename to packages/utils/src/validators.ts diff --git a/packages/client-core/test/.eslintrc.yml b/packages/utils/test/.eslintrc.yml similarity index 100% rename from packages/client-core/test/.eslintrc.yml rename to packages/utils/test/.eslintrc.yml diff --git a/packages/client-core/test/format.test.js b/packages/utils/test/format.test.js similarity index 100% rename from packages/client-core/test/format.test.js rename to packages/utils/test/format.test.js diff --git a/packages/client-core/test/rpc-errors.test.js b/packages/utils/test/rpc-errors.test.js similarity index 100% rename from packages/client-core/test/rpc-errors.test.js rename to packages/utils/test/rpc-errors.test.js diff --git a/packages/client-core/test/validator.test.js b/packages/utils/test/validator.test.js similarity index 100% rename from packages/client-core/test/validator.test.js rename to packages/utils/test/validator.test.js diff --git a/packages/client-core/tsconfig.json b/packages/utils/tsconfig.json similarity index 100% rename from packages/client-core/tsconfig.json rename to packages/utils/tsconfig.json diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json index 69f9c5b7b..e6d2096c0 100644 --- a/packages/wallet-account/package.json +++ b/packages/wallet-account/package.json @@ -13,17 +13,17 @@ "license": "ISC", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/client-core": "workspace:*", "@near-js/keypairs": "workspace:*", "@near-js/keystores": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", + "@near-js/utils": "workspace:*", "bn.js": "5.2.1", "borsh": "^0.7.0" }, "devDependencies": { - "@types/node": "^18.7.14", + "@types/node": "^18.11.18", "jest": "^26.0.1", "localstorage-memory": "^1.0.3", "ts-jest": "^26.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e8157fe5a..3c4794295 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,14 +32,14 @@ importers: packages/accounts: specifiers: - '@near-js/client-core': workspace:* '@near-js/keypairs': workspace:* '@near-js/keystores': workspace:* '@near-js/providers': workspace:* '@near-js/signers': workspace:* '@near-js/transactions': workspace:* '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@near-js/utils': workspace:* + '@types/node': ^18.11.18 bn.js: 5.2.1 borsh: ^0.7.0 bs58: ^4.0.0 @@ -49,51 +49,24 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs '@near-js/providers': link:../providers '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions '@near-js/types': link:../types + '@near-js/utils': link:../utils bn.js: 5.2.1 borsh: 0.7.0 depd: 2.0.0 devDependencies: '@near-js/keystores': link:../keystores - '@types/node': 18.7.14 + '@types/node': 18.11.18 bs58: 4.0.1 jest: 26.6.3 near-hello: 0.5.1 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 - packages/client-core: - specifiers: - '@near-js/types': workspace:* - '@types/node': ^18.7.14 - bn.js: 5.2.1 - borsh: ^0.7.0 - depd: ^2.0.0 - jest: ^26.0.1 - js-sha256: ^0.9.0 - mustache: ^4.0.0 - ts-jest: ^26.5.6 - tweetnacl: ^1.0.1 - typescript: ^4.9.4 - dependencies: - '@near-js/types': link:../types - bn.js: 5.2.1 - borsh: 0.7.0 - depd: 2.0.0 - js-sha256: 0.9.0 - mustache: 4.2.0 - tweetnacl: 1.0.3 - devDependencies: - '@types/node': 18.7.14 - jest: 26.6.3 - ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli - typescript: 4.9.4 - packages/cookbook: specifiers: chalk: ^4.1.1 @@ -107,7 +80,7 @@ importers: packages/keypairs: specifiers: '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 bn.js: 5.2.1 borsh: ^0.7.0 jest: ^26.0.1 @@ -120,7 +93,7 @@ importers: borsh: 0.7.0 tweetnacl: 1.0.3 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 @@ -129,7 +102,7 @@ importers: specifiers: '@near-js/keypairs': workspace:* '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 typescript: ^4.9.4 @@ -137,7 +110,7 @@ importers: '@near-js/keypairs': link:../keypairs '@near-js/types': link:../types devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 @@ -161,7 +134,7 @@ importers: specifiers: '@near-js/keypairs': workspace:* '@near-js/keystores': workspace:* - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 typescript: ^4.9.4 @@ -169,7 +142,7 @@ importers: '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 @@ -177,7 +150,6 @@ importers: packages/near-api-js: specifiers: '@near-js/accounts': workspace:* - '@near-js/client-core': workspace:* '@near-js/keypairs': workspace:* '@near-js/keystores': workspace:* '@near-js/keystores-browser': workspace:* @@ -186,10 +158,11 @@ importers: '@near-js/signers': workspace:* '@near-js/transactions': workspace:* '@near-js/types': workspace:* + '@near-js/utils': workspace:* '@near-js/wallet-account': workspace:* '@types/bn.js': ^5.1.0 '@types/http-errors': ^1.6.1 - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 ajv: ^8.11.2 ajv-formats: ^2.1.1 bn.js: 5.2.1 @@ -216,7 +189,6 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/accounts': link:../accounts - '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores '@near-js/keystores-browser': link:../keystores-browser @@ -225,6 +197,7 @@ importers: '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions '@near-js/types': link:../types + '@near-js/utils': link:../utils '@near-js/wallet-account': link:../wallet-account ajv: 8.11.2 ajv-formats: 2.1.1_ajv@8.11.2 @@ -239,7 +212,7 @@ importers: devDependencies: '@types/bn.js': 5.1.1 '@types/http-errors': 1.8.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 browserify: 16.5.2 bs58: 4.0.1 bundlewatch: 0.3.3 @@ -257,10 +230,10 @@ importers: packages/providers: specifiers: - '@near-js/client-core': workspace:* '@near-js/transactions': workspace:* '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@near-js/utils': workspace:* + '@types/node': ^18.11.18 bn.js: 5.2.1 borsh: ^0.7.0 http-errors: ^1.7.2 @@ -269,16 +242,16 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/client-core': link:../client-core '@near-js/transactions': link:../transactions '@near-js/types': link:../types + '@near-js/utils': link:../utils bn.js: 5.2.1 borsh: 0.7.0 http-errors: 1.8.1 optionalDependencies: node-fetch: 2.6.7 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 @@ -287,7 +260,7 @@ importers: specifiers: '@near-js/keypairs': workspace:* '@near-js/keystores': workspace:* - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 jest: ^26.0.1 js-sha256: ^0.9.0 ts-jest: ^26.5.6 @@ -297,18 +270,18 @@ importers: '@near-js/keystores': link:../keystores js-sha256: 0.9.0 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 packages/transactions: specifiers: - '@near-js/client-core': workspace:* '@near-js/keypairs': workspace:* '@near-js/signers': workspace:* '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@near-js/utils': workspace:* + '@types/node': ^18.11.18 bn.js: 5.2.1 borsh: ^0.7.0 jest: ^26.0.1 @@ -316,22 +289,22 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs '@near-js/signers': link:../signers '@near-js/types': link:../types + '@near-js/utils': link:../utils bn.js: 5.2.1 borsh: 0.7.0 js-sha256: 0.9.0 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 packages/types: specifiers: - '@types/node': ^18.7.14 + '@types/node': ^18.11.18 bn.js: 5.2.1 jest: ^26.0.1 ts-jest: ^26.5.6 @@ -339,7 +312,28 @@ importers: dependencies: bn.js: 5.2.1 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 + jest: 26.6.3 + ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + typescript: 4.9.4 + + packages/utils: + specifiers: + '@near-js/types': workspace:* + '@types/node': ^18.11.18 + bn.js: 5.2.1 + depd: ^2.0.0 + jest: ^26.0.1 + mustache: ^4.0.0 + ts-jest: ^26.5.6 + typescript: ^4.9.4 + dependencies: + '@near-js/types': link:../types + bn.js: 5.2.1 + depd: 2.0.0 + mustache: 4.2.0 + devDependencies: + '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli typescript: 4.9.4 @@ -347,13 +341,13 @@ importers: packages/wallet-account: specifiers: '@near-js/accounts': workspace:* - '@near-js/client-core': workspace:* '@near-js/keypairs': workspace:* '@near-js/keystores': workspace:* '@near-js/signers': workspace:* '@near-js/transactions': workspace:* '@near-js/types': workspace:* - '@types/node': ^18.7.14 + '@near-js/utils': workspace:* + '@types/node': ^18.11.18 bn.js: 5.2.1 borsh: ^0.7.0 jest: ^26.0.1 @@ -362,16 +356,16 @@ importers: typescript: ^4.9.4 dependencies: '@near-js/accounts': link:../accounts - '@near-js/client-core': link:../client-core '@near-js/keypairs': link:../keypairs '@near-js/keystores': link:../keystores '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions '@near-js/types': link:../types + '@near-js/utils': link:../utils bn.js: 5.2.1 borsh: 0.7.0 devDependencies: - '@types/node': 18.7.14 + '@types/node': 18.11.18 jest: 26.6.3 localstorage-memory: 1.0.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli @@ -1179,7 +1173,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 chalk: 4.1.2 jest-message-util: 26.6.2 jest-util: 26.6.2 @@ -1195,7 +1189,7 @@ packages: '@jest/test-result': 26.6.2 '@jest/transform': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 ansi-escapes: 4.3.2 chalk: 4.1.2 exit: 0.1.2 @@ -1232,7 +1226,7 @@ packages: dependencies: '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 jest-mock: 26.6.2 dev: true @@ -1242,7 +1236,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@sinonjs/fake-timers': 6.0.1 - '@types/node': 18.11.15 + '@types/node': 18.11.18 jest-message-util: 26.6.2 jest-mock: 26.6.2 jest-util: 26.6.2 @@ -1356,7 +1350,7 @@ packages: dependencies: '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.11.15 + '@types/node': 18.11.18 '@types/yargs': 15.0.14 chalk: 4.1.2 dev: true @@ -1636,7 +1630,7 @@ packages: /@types/bn.js/5.1.1: resolution: {integrity: sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 dev: true /@types/cacheable-request/6.0.3: @@ -1644,14 +1638,14 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 18.11.15 + '@types/node': 18.11.18 '@types/responselike': 1.0.0 dev: true /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 dev: true /@types/http-cache-semantics/4.0.1: @@ -1690,7 +1684,7 @@ packages: /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 dev: true /@types/minimist/1.2.2: @@ -1705,8 +1699,8 @@ packages: resolution: {integrity: sha512-hcU9AIQVHmPnmjRK+XUUYlILlr9pQrsqSrwov/JK1pnf3GTQowVBhx54FbvM0AU/VXGH4i3+vgXS5EguR7fysA==} dev: true - /@types/node/18.11.15: - resolution: {integrity: sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==} + /@types/node/18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} dev: true /@types/normalize-package-data/2.4.1: @@ -1724,7 +1718,7 @@ packages: /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 dev: true /@types/semver/6.2.3: @@ -4873,7 +4867,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 jest-mock: 26.6.2 jest-util: 26.6.2 jsdom: 16.7.0 @@ -4891,7 +4885,7 @@ packages: '@jest/environment': 26.6.2 '@jest/fake-timers': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 jest-mock: 26.6.2 jest-util: 26.6.2 dev: true @@ -4907,7 +4901,7 @@ packages: dependencies: '@jest/types': 26.6.2 '@types/graceful-fs': 4.1.5 - '@types/node': 18.11.15 + '@types/node': 18.11.18 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.10 @@ -4933,7 +4927,7 @@ packages: '@jest/source-map': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 chalk: 4.1.2 co: 4.6.0 expect: 26.6.2 @@ -4992,7 +4986,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 dev: true /jest-pnp-resolver/1.2.3_jest-resolve@26.6.2: @@ -5045,7 +5039,7 @@ packages: '@jest/environment': 26.6.2 '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 chalk: 4.1.2 emittery: 0.7.2 exit: 0.1.2 @@ -5113,7 +5107,7 @@ packages: resolution: {integrity: sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==} engines: {node: '>= 10.14.2'} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 graceful-fs: 4.2.10 dev: true @@ -5146,7 +5140,7 @@ packages: engines: {node: '>= 10.14.2'} dependencies: '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 chalk: 4.1.2 graceful-fs: 4.2.10 is-ci: 2.0.0 @@ -5171,7 +5165,7 @@ packages: dependencies: '@jest/test-result': 26.6.2 '@jest/types': 26.6.2 - '@types/node': 18.11.15 + '@types/node': 18.11.18 ansi-escapes: 4.3.2 chalk: 4.1.2 jest-util: 26.6.2 @@ -5182,7 +5176,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.11.15 + '@types/node': 18.11.18 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true From d5f5db407cf46118385434b25c6ff13f2376fec4 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 14:10:51 -0800 Subject: [PATCH 53/84] refactor: move keystore tests --- packages/keystores-browser/package.json | 2 +- .../test}/browser_keystore.test.js | 4 +- .../test}/keystore_common.js | 11 ++-- packages/keystores-node/package.json | 2 +- .../keystores-node/test/keystore_common.js | 54 +++++++++++++++++++ .../unencrypted_file_system_keystore.test.js | 14 +++-- packages/keystores/package.json | 2 +- .../test}/in_memory_keystore.test.js | 4 +- packages/keystores/test/keystore_common.js | 54 +++++++++++++++++++ .../test}/merge_keystore.test.js | 15 +++--- packages/near-api-js/jest.config.js | 5 -- packages/near-api-js/test/.eslintrc.yml | 7 --- 12 files changed, 129 insertions(+), 45 deletions(-) rename packages/{near-api-js/test/key_stores => keystores-browser/test}/browser_keystore.test.js (66%) rename packages/{near-api-js/test/key_stores => keystores-browser/test}/keystore_common.js (86%) create mode 100644 packages/keystores-node/test/keystore_common.js rename packages/{near-api-js/test/key_stores => keystores-node/test}/unencrypted_file_system_keystore.test.js (81%) rename packages/{near-api-js/test/key_stores => keystores/test}/in_memory_keystore.test.js (66%) create mode 100644 packages/keystores/test/keystore_common.js rename packages/{near-api-js/test/key_stores => keystores/test}/merge_keystore.test.js (73%) delete mode 100644 packages/near-api-js/jest.config.js delete mode 100644 packages/near-api-js/test/.eslintrc.yml diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index b784b4e70..7fd3d35b2 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", - "test": "jest test --passWithNoTests" + "test": "jest test" }, "keywords": [], "author": "", diff --git a/packages/near-api-js/test/key_stores/browser_keystore.test.js b/packages/keystores-browser/test/browser_keystore.test.js similarity index 66% rename from packages/near-api-js/test/key_stores/browser_keystore.test.js rename to packages/keystores-browser/test/browser_keystore.test.js index d7f7b19d2..cb71965ca 100644 --- a/packages/near-api-js/test/key_stores/browser_keystore.test.js +++ b/packages/keystores-browser/test/browser_keystore.test.js @@ -1,6 +1,4 @@ -const nearApi = require('../../src/index'); - -const BrowserLocalStorageKeyStore = nearApi.keyStores.BrowserLocalStorageKeyStore; +const { BrowserLocalStorageKeyStore } = require('../lib'); describe('Browser keystore', () => { let ctx = {}; diff --git a/packages/near-api-js/test/key_stores/keystore_common.js b/packages/keystores-browser/test/keystore_common.js similarity index 86% rename from packages/near-api-js/test/key_stores/keystore_common.js rename to packages/keystores-browser/test/keystore_common.js index 7008b950a..079315bf6 100644 --- a/packages/near-api-js/test/key_stores/keystore_common.js +++ b/packages/keystores-browser/test/keystore_common.js @@ -1,11 +1,8 @@ - -const nearApi = require('../../src/index'); - -const KeyPair = nearApi.utils.KeyPairEd25519; +const { KeyPairEd25519 } = require('@near-js/keypairs'); const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; -const KEYPAIR_SINGLE_KEY = new KeyPair('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); +const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); module.exports.shouldStoreAndRetriveKeys = ctx => { beforeEach(async () => { @@ -41,8 +38,8 @@ module.exports.shouldStoreAndRetriveKeys = ctx => { const networkId = 'twoKeyNetwork'; const accountId1 = 'acc1'; const accountId2 = 'acc2'; - const key1Expected = KeyPair.fromRandom(); - const key2Expected = KeyPair.fromRandom(); + const key1Expected = KeyPairEd25519.fromRandom(); + const key2Expected = KeyPairEd25519.fromRandom(); await ctx.keyStore.setKey(networkId, accountId1, key1Expected); await ctx.keyStore.setKey(networkId, accountId2, key2Expected); const key1 = await ctx.keyStore.getKey(networkId, accountId1); diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 1044a6709..203d66e05 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", - "test": "jest test --passWithNoTests" + "test": "jest test" }, "keywords": [], "author": "", diff --git a/packages/keystores-node/test/keystore_common.js b/packages/keystores-node/test/keystore_common.js new file mode 100644 index 000000000..079315bf6 --- /dev/null +++ b/packages/keystores-node/test/keystore_common.js @@ -0,0 +1,54 @@ +const { KeyPairEd25519 } = require('@near-js/keypairs'); + +const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; +const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; +const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); + +module.exports.shouldStoreAndRetriveKeys = ctx => { + beforeEach(async () => { + await ctx.keyStore.clear(); + await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); + }); + + test('Get all keys with empty network returns empty list', async () => { + const emptyList = await ctx.keyStore.getAccounts('emptynetwork'); + expect(emptyList).toEqual([]); + }); + + test('Get all keys with single key in keystore', async () => { + const accountIds = await ctx.keyStore.getAccounts(NETWORK_ID_SINGLE_KEY); + expect(accountIds).toEqual([ACCOUNT_ID_SINGLE_KEY]); + }); + + test('Get not-existing account', async () => { + expect(await ctx.keyStore.getKey('somenetwork', 'someaccount')).toBeNull(); + }); + + test('Get account id from a network with single key', async () => { + const key = await ctx.keyStore.getKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY); + expect(key).toEqual(KEYPAIR_SINGLE_KEY); + }); + + test('Get networks', async () => { + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY]); + }); + + test('Add two keys to network and retrieve them', async () => { + const networkId = 'twoKeyNetwork'; + const accountId1 = 'acc1'; + const accountId2 = 'acc2'; + const key1Expected = KeyPairEd25519.fromRandom(); + const key2Expected = KeyPairEd25519.fromRandom(); + await ctx.keyStore.setKey(networkId, accountId1, key1Expected); + await ctx.keyStore.setKey(networkId, accountId2, key2Expected); + const key1 = await ctx.keyStore.getKey(networkId, accountId1); + const key2 = await ctx.keyStore.getKey(networkId, accountId2); + expect(key1).toEqual(key1Expected); + expect(key2).toEqual(key2Expected); + const accountIds = await ctx.keyStore.getAccounts(networkId); + expect(accountIds).toEqual([accountId1, accountId2]); + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY, networkId]); + }); +}; diff --git a/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js similarity index 81% rename from packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js rename to packages/keystores-node/test/unencrypted_file_system_keystore.test.js index 867741379..8c0b75b56 100644 --- a/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js +++ b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js @@ -1,13 +1,11 @@ - -const rimraf = require('util').promisify(require('rimraf')); - -const nearApi = require('../../src/index'); -const UnencryptedFileSystemKeyStore = nearApi.keyStores.UnencryptedFileSystemKeyStore; -const KeyPair = nearApi.utils.KeyPairEd25519; +const { KeyPairEd25519 } = require('@near-js/keypairs'); const fs = require('fs').promises; const path = require('path'); +const rimraf = require('util').promisify(require('rimraf')); + +const { UnencryptedFileSystemKeyStore } = require('../lib'); -const KEYSTORE_PATH = '../test-keys'; +const KEYSTORE_PATH = '../../test-keys'; describe('Unencrypted file system keystore', () => { let ctx = {}; @@ -29,7 +27,7 @@ describe('Unencrypted file system keystore', () => { }); it('test public key exists', async () => { - const key1 = KeyPair.fromRandom(); + const key1 = KeyPairEd25519.fromRandom(); await ctx.keyStore.setKey('network', 'account', key1); const keyFilePath = ctx.keyStore.getKeyFilePath('network', 'account'); const content = await fs.readFile(keyFilePath); diff --git a/packages/keystores/package.json b/packages/keystores/package.json index 2a2d2af65..e8051018b 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -6,7 +6,7 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", - "test": "jest test --passWithNoTests" + "test": "jest test" }, "keywords": [], "author": "", diff --git a/packages/near-api-js/test/key_stores/in_memory_keystore.test.js b/packages/keystores/test/in_memory_keystore.test.js similarity index 66% rename from packages/near-api-js/test/key_stores/in_memory_keystore.test.js rename to packages/keystores/test/in_memory_keystore.test.js index 99affb3ff..52a0eda48 100644 --- a/packages/near-api-js/test/key_stores/in_memory_keystore.test.js +++ b/packages/keystores/test/in_memory_keystore.test.js @@ -1,6 +1,4 @@ -const nearApi = require('../../src/index'); - -const InMemoryKeyStore = nearApi.keyStores.InMemoryKeyStore; +const { InMemoryKeyStore } = require('../lib'); describe('In-memory keystore', () => { let ctx = {}; diff --git a/packages/keystores/test/keystore_common.js b/packages/keystores/test/keystore_common.js new file mode 100644 index 000000000..079315bf6 --- /dev/null +++ b/packages/keystores/test/keystore_common.js @@ -0,0 +1,54 @@ +const { KeyPairEd25519 } = require('@near-js/keypairs'); + +const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; +const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; +const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); + +module.exports.shouldStoreAndRetriveKeys = ctx => { + beforeEach(async () => { + await ctx.keyStore.clear(); + await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); + }); + + test('Get all keys with empty network returns empty list', async () => { + const emptyList = await ctx.keyStore.getAccounts('emptynetwork'); + expect(emptyList).toEqual([]); + }); + + test('Get all keys with single key in keystore', async () => { + const accountIds = await ctx.keyStore.getAccounts(NETWORK_ID_SINGLE_KEY); + expect(accountIds).toEqual([ACCOUNT_ID_SINGLE_KEY]); + }); + + test('Get not-existing account', async () => { + expect(await ctx.keyStore.getKey('somenetwork', 'someaccount')).toBeNull(); + }); + + test('Get account id from a network with single key', async () => { + const key = await ctx.keyStore.getKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY); + expect(key).toEqual(KEYPAIR_SINGLE_KEY); + }); + + test('Get networks', async () => { + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY]); + }); + + test('Add two keys to network and retrieve them', async () => { + const networkId = 'twoKeyNetwork'; + const accountId1 = 'acc1'; + const accountId2 = 'acc2'; + const key1Expected = KeyPairEd25519.fromRandom(); + const key2Expected = KeyPairEd25519.fromRandom(); + await ctx.keyStore.setKey(networkId, accountId1, key1Expected); + await ctx.keyStore.setKey(networkId, accountId2, key2Expected); + const key1 = await ctx.keyStore.getKey(networkId, accountId1); + const key2 = await ctx.keyStore.getKey(networkId, accountId2); + expect(key1).toEqual(key1Expected); + expect(key2).toEqual(key2Expected); + const accountIds = await ctx.keyStore.getAccounts(networkId); + expect(accountIds).toEqual([accountId1, accountId2]); + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY, networkId]); + }); +}; diff --git a/packages/near-api-js/test/key_stores/merge_keystore.test.js b/packages/keystores/test/merge_keystore.test.js similarity index 73% rename from packages/near-api-js/test/key_stores/merge_keystore.test.js rename to packages/keystores/test/merge_keystore.test.js index 86f8fee64..5bf31d6e1 100644 --- a/packages/near-api-js/test/key_stores/merge_keystore.test.js +++ b/packages/keystores/test/merge_keystore.test.js @@ -1,9 +1,6 @@ -const nearApi = require('../../src/index'); +const { KeyPairEd25519 } = require('@near-js/keypairs'); -const KeyPair = nearApi.utils.KeyPairEd25519; - -const MergeKeyStore = nearApi.keyStores.MergeKeyStore; -const InMemoryKeyStore = nearApi.keyStores.InMemoryKeyStore; +const { InMemoryKeyStore, MergeKeyStore } = require('../lib'); describe('Merge keystore', () => { let ctx = {}; @@ -14,21 +11,21 @@ describe('Merge keystore', () => { }); it('looks up key from fallback key store if needed', async () => { - const key1 = KeyPair.fromRandom(); + const key1 = KeyPairEd25519.fromRandom(); await ctx.stores[1].setKey('network', 'account', key1); expect(await ctx.keyStore.getKey('network', 'account')).toEqual(key1); }); it('looks up key in proper order', async () => { - const key1 = KeyPair.fromRandom(); - const key2 = KeyPair.fromRandom(); + const key1 = KeyPairEd25519.fromRandom(); + const key2 = KeyPairEd25519.fromRandom(); await ctx.stores[0].setKey('network', 'account', key1); await ctx.stores[1].setKey('network', 'account', key2); expect(await ctx.keyStore.getKey('network', 'account')).toEqual(key1); }); it('sets keys only in first key store', async () => { - const key1 = KeyPair.fromRandom(); + const key1 = KeyPairEd25519.fromRandom(); await ctx.keyStore.setKey('network', 'account', key1); expect(await ctx.stores[0].getAccounts('network')).toHaveLength(1); expect(await ctx.stores[1].getAccounts('network')).toHaveLength(0); diff --git a/packages/near-api-js/jest.config.js b/packages/near-api-js/jest.config.js deleted file mode 100644 index 749b7fcb2..000000000 --- a/packages/near-api-js/jest.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - collectCoverage: true -}; diff --git a/packages/near-api-js/test/.eslintrc.yml b/packages/near-api-js/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/near-api-js/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true From 3c3999bc4023b77704eec5f6e450032ce27ab779 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 14:11:50 -0800 Subject: [PATCH 54/84] refactor: move error script to utils --- packages/{near-api-js => utils}/fetch_error_schema.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename packages/{near-api-js => utils}/fetch_error_schema.js (80%) diff --git a/packages/near-api-js/fetch_error_schema.js b/packages/utils/fetch_error_schema.js similarity index 80% rename from packages/near-api-js/fetch_error_schema.js rename to packages/utils/fetch_error_schema.js index cdaf29b60..0882458e9 100644 --- a/packages/near-api-js/fetch_error_schema.js +++ b/packages/utils/fetch_error_schema.js @@ -3,8 +3,7 @@ const fs = require('fs'); const ERROR_SCHEMA_URL = 'https://raw.githubusercontent.com/nearprotocol/nearcore/4c1149974ccf899dbcb2253a3e27cbab86dc47be/chain/jsonrpc/res/rpc_errors_schema.json'; -const TARGET_DIR = process.argv[2] || process.cwd() + '/src/generated'; -const TARGET_SCHEMA_FILE_PATH = TARGET_DIR + '/rpc_error_schema.json'; +const TARGET_SCHEMA_FILE_PATH = `${process.argv[2] || process.cwd()}/src/errors/rpc_error_schema.json'`; https .get(ERROR_SCHEMA_URL, resp => { From c57a8269477cad6f9b9a4a57d2fdbf8d38897e15 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 4 Jan 2023 15:59:54 -0800 Subject: [PATCH 55/84] chore: linting --- .eslintrc.yml => .eslintrc.base.yml | 2 + .eslintrc.js.yml | 7 ++ .eslintrc.ts.yml | 5 ++ package.json | 8 +- packages/accounts/package.json | 4 + packages/accounts/test/.eslintrc.yml | 7 -- packages/accounts/test/config.js | 80 +++++++++---------- packages/accounts/test/test-utils.js | 2 +- packages/keypairs/package.json | 4 + packages/keypairs/test/.eslintrc.yml | 7 -- packages/keystores-browser/package.json | 4 + packages/keystores-browser/test/.eslintrc.yml | 7 -- packages/keystores-node/package.json | 4 + packages/keystores-node/test/.eslintrc.yml | 7 -- packages/keystores/package.json | 4 + packages/keystores/test/.eslintrc.yml | 7 -- packages/near-api-js/package.json | 5 +- packages/providers/package.json | 4 + packages/providers/test/.eslintrc.yml | 7 -- packages/signers/package.json | 3 + packages/signers/test/.eslintrc.yml | 7 -- packages/transactions/package.json | 7 +- packages/transactions/test/.eslintrc.yml | 7 -- packages/types/package.json | 4 +- packages/utils/package.json | 2 + packages/utils/src/errors/errors.ts | 2 +- packages/utils/test/.eslintrc.yml | 7 -- packages/wallet-account/test/.eslintrc.yml | 7 -- pnpm-lock.yaml | 2 + turbo.json | 16 ++++ 30 files changed, 119 insertions(+), 120 deletions(-) rename .eslintrc.yml => .eslintrc.base.yml (89%) create mode 100644 .eslintrc.js.yml create mode 100644 .eslintrc.ts.yml delete mode 100644 packages/accounts/test/.eslintrc.yml delete mode 100644 packages/keypairs/test/.eslintrc.yml delete mode 100644 packages/keystores-browser/test/.eslintrc.yml delete mode 100644 packages/keystores-node/test/.eslintrc.yml delete mode 100644 packages/keystores/test/.eslintrc.yml delete mode 100644 packages/providers/test/.eslintrc.yml delete mode 100644 packages/signers/test/.eslintrc.yml delete mode 100644 packages/transactions/test/.eslintrc.yml delete mode 100644 packages/utils/test/.eslintrc.yml delete mode 100644 packages/wallet-account/test/.eslintrc.yml diff --git a/.eslintrc.yml b/.eslintrc.base.yml similarity index 89% rename from .eslintrc.yml rename to .eslintrc.base.yml index e59578dd9..e1b41fd64 100644 --- a/.eslintrc.yml +++ b/.eslintrc.base.yml @@ -5,10 +5,12 @@ extends: - 'eslint:recommended' parserOptions: ecmaVersion: 2018 + sourceType: module rules: indent: - error - 4 + - SwitchCase: 1 linebreak-style: - error - unix diff --git a/.eslintrc.js.yml b/.eslintrc.js.yml new file mode 100644 index 000000000..54abdec6e --- /dev/null +++ b/.eslintrc.js.yml @@ -0,0 +1,7 @@ +extends: './.eslintrc.base.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/.eslintrc.ts.yml b/.eslintrc.ts.yml new file mode 100644 index 000000000..d68c7ff9a --- /dev/null +++ b/.eslintrc.ts.yml @@ -0,0 +1,5 @@ +extends: + - './.eslintrc.base.yml' + - 'plugin:@typescript-eslint/eslint-recommended' + - 'plugin:@typescript-eslint/recommended' +parser: '@typescript-eslint/parser' diff --git a/package.json b/package.json index 10dc0e3b2..fee7979fa 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "preinstall": "npx only-allow pnpm", "build": "turbo run build", "clean": "turbo run clean", - "lint": "turbo run lint", + "lint": "concurrently \"turbo run lint:ts\" \"turbo run lint:js\"", + "lint:fix": "concurrently \"turbo run lint:ts:fix\" \"turbo run lint:js:fix\"", "test": "turbo run test", "release": "changeset publish", "prepare": "husky install" @@ -19,13 +20,14 @@ "@changesets/cli": "^2.24.4", "@commitlint/cli": "^17.0.3", "@commitlint/config-conventional": "^17.0.3", - "typescript": "^4.7.4", "@typescript-eslint/eslint-plugin": "^5.31.0", "@typescript-eslint/parser": "^5.31.0", "commitlint": "^17.0.3", + "concurrently": "^7.6.0", "eslint": "^8.20.0", "husky": "^7.0.4", "rimraf": "^3.0.2", - "turbo": "^1.4.5" + "turbo": "^1.4.5", + "typescript": "^4.7.4" } } diff --git a/packages/accounts/package.json b/packages/accounts/package.json index a43b84e81..fb0687e8f 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/accounts/test/.eslintrc.yml b/packages/accounts/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/accounts/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/accounts/test/config.js b/packages/accounts/test/config.js index 4af00c1c6..1734592c6 100644 --- a/packages/accounts/test/config.js +++ b/packages/accounts/test/config.js @@ -1,44 +1,44 @@ module.exports = function getConfig(env) { switch (env) { - case 'production': - case 'mainnet': - return { - networkId: 'mainnet', - nodeUrl: 'https://rpc.mainnet.near.org', - walletUrl: 'https://wallet.near.org', - helperUrl: 'https://helper.mainnet.near.org', - }; - case 'development': - case 'testnet': - return { - networkId: 'default', - nodeUrl: 'https://rpc.testnet.near.org', - walletUrl: 'https://wallet.testnet.near.org', - helperUrl: 'https://helper.testnet.near.org', - masterAccount: 'test.near', - }; - case 'betanet': - return { - networkId: 'betanet', - nodeUrl: 'https://rpc.betanet.near.org', - walletUrl: 'https://wallet.betanet.near.org', - helperUrl: 'https://helper.betanet.near.org', - }; - case 'local': - return { - networkId: 'local', - nodeUrl: 'http://localhost:3030', - keyPath: `${process.env.HOME}/.near/validator_key.json`, - walletUrl: 'http://localhost:4000/wallet', - }; - case 'test': - case 'ci': - return { - networkId: 'shared-test', - nodeUrl: 'https://rpc.ci-testnet.near.org', - masterAccount: 'test.near', - }; - default: - throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); + case 'production': + case 'mainnet': + return { + networkId: 'mainnet', + nodeUrl: 'https://rpc.mainnet.near.org', + walletUrl: 'https://wallet.near.org', + helperUrl: 'https://helper.mainnet.near.org', + }; + case 'development': + case 'testnet': + return { + networkId: 'default', + nodeUrl: 'https://rpc.testnet.near.org', + walletUrl: 'https://wallet.testnet.near.org', + helperUrl: 'https://helper.testnet.near.org', + masterAccount: 'test.near', + }; + case 'betanet': + return { + networkId: 'betanet', + nodeUrl: 'https://rpc.betanet.near.org', + walletUrl: 'https://wallet.betanet.near.org', + helperUrl: 'https://helper.betanet.near.org', + }; + case 'local': + return { + networkId: 'local', + nodeUrl: 'http://localhost:3030', + keyPath: `${process.env.HOME}/.near/validator_key.json`, + walletUrl: 'http://localhost:4000/wallet', + }; + case 'test': + case 'ci': + return { + networkId: 'shared-test', + nodeUrl: 'https://rpc.ci-testnet.near.org', + masterAccount: 'test.near', + }; + default: + throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); } }; diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 332d9762b..e7849d0bf 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -73,7 +73,7 @@ async function createAccountMultisig({ accountCreator, connection }, options) { accountMultisig.getRecoveryMethods = () => ({ data: [] }); accountMultisig.postSignedJson = async (path) => { switch (path) { - case '/2fa/getAccessKey': return { publicKey }; + case '/2fa/getAccessKey': return { publicKey }; } }; await accountMultisig.deployMultisig(new Uint8Array([...(await fs.readFile(MULTISIG_WASM_PATH))])); diff --git a/packages/keypairs/package.json b/packages/keypairs/package.json index f1e6be372..74d40243d 100644 --- a/packages/keypairs/package.json +++ b/packages/keypairs/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/keypairs/test/.eslintrc.yml b/packages/keypairs/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/keypairs/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index 7fd3d35b2..988270373 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/keystores-browser/test/.eslintrc.yml b/packages/keystores-browser/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/keystores-browser/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 203d66e05..1d7633ef0 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/keystores-node/test/.eslintrc.yml b/packages/keystores-node/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/keystores-node/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/keystores/package.json b/packages/keystores/package.json index e8051018b..4b2c4e87c 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/keystores/test/.eslintrc.yml b/packages/keystores/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/keystores/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 75f4ecfb9..6f8f8a237 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -60,10 +60,9 @@ "compile": "tsc -p ./tsconfig.json", "dev": "pnpm compile -w", "build": "pnpm compile && pnpm browserify", - "test": "jest test", - "lint": "concurrently \"pnpm:lint:*(!fix)\"", + "test": "jest test --passWithNoTests", + "lint": "concurrently \"pnpm:lint:*(!fix) --no-error-on-unmatched-pattern\"", "lint:src": "eslint --ext .ts src", - "lint:test": "eslint --ext .js test", "lint:fix": "concurrently \"pnpm:lint:*:fix\"", "lint:src:fix": "eslint --ext .ts --fix src", "lint:test:fix": "eslint --ext .js --fix test", diff --git a/packages/providers/package.json b/packages/providers/package.json index 24f58829f..b08d0be39 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -6,6 +6,10 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/providers/test/.eslintrc.yml b/packages/providers/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/providers/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/signers/package.json b/packages/signers/package.json index 9841ac931..e1ccd5563 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -6,6 +6,9 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "test": "jest test" }, "keywords": [], diff --git a/packages/signers/test/.eslintrc.yml b/packages/signers/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/signers/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 0dea0e0e0..6045a47dd 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -5,7 +5,12 @@ "main": "lib/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json" + "compile": "tsc -p tsconfig.json", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", + "test": "jest test" }, "keywords": [], "author": "", diff --git a/packages/transactions/test/.eslintrc.yml b/packages/transactions/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/transactions/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/types/package.json b/packages/types/package.json index 217c8f974..007edd2cf 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -5,7 +5,9 @@ "main": "lib/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json" + "compile": "tsc -p tsconfig.json", + "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts", + "lint:fix": "eslint **/*.ts" }, "keywords": [], "author": "", diff --git a/packages/utils/package.json b/packages/utils/package.json index 711f2ca0e..58049ecb7 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -6,6 +6,8 @@ "scripts": { "build": "pnpm compile", "compile": "tsc -p tsconfig.json", + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc && eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix --no-error-on-unmatched-pattern", "test": "jest test" }, "keywords": [], diff --git a/packages/utils/src/errors/errors.ts b/packages/utils/src/errors/errors.ts index c6bd55588..d6c9c5bbc 100644 --- a/packages/utils/src/errors/errors.ts +++ b/packages/utils/src/errors/errors.ts @@ -2,4 +2,4 @@ export function logWarning(...args: any[]): void { if (!process.env['NEAR_NO_LOGS']){ console.warn(...args); } -} \ No newline at end of file +} diff --git a/packages/utils/test/.eslintrc.yml b/packages/utils/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/utils/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/packages/wallet-account/test/.eslintrc.yml b/packages/wallet-account/test/.eslintrc.yml deleted file mode 100644 index a74d2e539..000000000 --- a/packages/wallet-account/test/.eslintrc.yml +++ /dev/null @@ -1,7 +0,0 @@ -extends: '../../../.eslintrc.yml' -env: - jest: true -globals: - jasmine: true - window: false - fail: true diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c4794295..2bfee7e67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,7 @@ importers: '@typescript-eslint/eslint-plugin': ^5.31.0 '@typescript-eslint/parser': ^5.31.0 commitlint: ^17.0.3 + concurrently: ^7.6.0 eslint: ^8.20.0 husky: ^7.0.4 rimraf: ^3.0.2 @@ -24,6 +25,7 @@ importers: '@typescript-eslint/eslint-plugin': 5.46.1_imrg37k3svwu377c6q7gkarwmi '@typescript-eslint/parser': 5.46.1_ha6vam6werchizxrnqvarmz2zu commitlint: 17.3.0 + concurrently: 7.6.0 eslint: 8.29.0 husky: 7.0.4 rimraf: 3.0.2 diff --git a/turbo.json b/turbo.json index 27e8bde21..d0dfc9fe8 100644 --- a/turbo.json +++ b/turbo.json @@ -13,6 +13,22 @@ "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"], "outputs": [] }, + "lint:js": { + "inputs": ["test/**/*.js"], + "outputs": [] + }, + "lint:js:fix": { + "inputs": ["test/**/*.js"], + "outputs": [] + }, + "lint:ts": { + "inputs": ["src/**/*.ts"], + "outputs": [] + }, + "lint:ts:fix": { + "inputs": ["src/**/*.ts"], + "outputs": [] + }, "clean": { "outputs": [], "cache": false From f7fa15e73b3d3c1922c501fa90f4eff635b09ded Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 5 Jan 2023 14:58:50 -0800 Subject: [PATCH 56/84] refactor: fix transactions test --- packages/transactions/package.json | 1 + packages/transactions/test/serialize.test.js | 5 ++++- pnpm-lock.yaml | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 6045a47dd..b9617050c 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -25,6 +25,7 @@ "js-sha256": "^0.9.0" }, "devDependencies": { + "@near-js/keystores": "workspace:*", "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", diff --git a/packages/transactions/test/serialize.test.js b/packages/transactions/test/serialize.test.js index 4df3b3dfd..386362846 100644 --- a/packages/transactions/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -1,4 +1,7 @@ -const { Assignable, InMemoryKeyStore, InMemorySigner, KeyPair, PublicKey } = require('@near-js/utils'); +const { KeyPair, PublicKey } = require('@near-js/keypairs'); +const { InMemoryKeyStore } = require('@near-js/keystores'); +const { InMemorySigner } = require('@near-js/signers'); +const { Assignable } = require('@near-js/types'); const fs = require('fs'); const BN = require('bn.js'); const { baseDecode, baseEncode, deserialize, serialize } = require('borsh'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2bfee7e67..fe05d26b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -280,6 +280,7 @@ importers: packages/transactions: specifiers: '@near-js/keypairs': workspace:* + '@near-js/keystores': workspace:* '@near-js/signers': workspace:* '@near-js/types': workspace:* '@near-js/utils': workspace:* @@ -299,6 +300,7 @@ importers: borsh: 0.7.0 js-sha256: 0.9.0 devDependencies: + '@near-js/keystores': link:../keystores '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli From 1a98ff75201c4ee5f47e92850aeb0842f5a9a5d4 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 5 Jan 2023 15:13:02 -0800 Subject: [PATCH 57/84] fix: unmerged changes --- packages/wallet-account/src/wallet_account.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index 286561a69..34a03d394 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -88,8 +88,12 @@ export class WalletConnection { /** @hidden */ _completeSignInPromise: Promise; - constructor(near: Near, appKeyPrefix: string | null) { - if(typeof window === 'undefined') { + constructor(near: Near, appKeyPrefix: string) { + if (typeof(appKeyPrefix) !== 'string') { + throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); + } + + if (typeof window === 'undefined') { return new Proxy(this, { get(target, property) { if(property === 'isSignedIn') { From 17c376e466a1720d3587900c19dc23196618fb72 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 6 Jan 2023 16:06:52 -0800 Subject: [PATCH 58/84] fix: matching ts versions --- package.json | 2 +- pnpm-lock.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fee7979fa..6cc1e1893 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,6 @@ "husky": "^7.0.4", "rimraf": "^3.0.2", "turbo": "^1.4.5", - "typescript": "^4.7.4" + "typescript": "^4.9.4" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe05d26b7..26009b97b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,7 +16,7 @@ importers: husky: ^7.0.4 rimraf: ^3.0.2 turbo: ^1.4.5 - typescript: ^4.7.4 + typescript: ^4.9.4 devDependencies: '@changesets/changelog-github': 0.4.7 '@changesets/cli': 2.25.2 From 7d6a2064ebbff94b49c0899a20f8612cf78862b1 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 11:04:56 -0800 Subject: [PATCH 59/84] refactor: export action creators under separate key --- packages/accounts/src/account.ts | 26 ++++++++------- packages/accounts/src/account_2fa.ts | 4 ++- packages/accounts/src/account_multisig.ts | 4 ++- packages/accounts/test/account.test.js | 7 ++-- .../accounts/test/account_multisig.test.js | 4 ++- packages/near-api-js/src/transaction.ts | 22 +++++++------ packages/transactions/src/action_creators.ts | 33 +++++++++++++------ packages/transactions/test/serialize.test.js | 16 +++++---- .../transactions/test/transaction.test.js | 4 ++- .../test/wallet_account.test.js | 4 ++- 10 files changed, 80 insertions(+), 44 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 0581d2e02..c82dfa7bf 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -9,18 +9,9 @@ import { } from '@near-js/utils'; import { exponentialBackoff } from '@near-js/providers'; import { - transfer, - createAccount, - signTransaction, - deployContract, - addKey, - functionCall, - fullAccessKey, - functionCallAccessKey, - deleteKey, - stake, - deleteAccount, + actionCreators, Action, + signTransaction, SignedTransaction, stringifyJsonOrBytes } from '@near-js/transactions'; @@ -44,6 +35,19 @@ import { baseDecode, baseEncode } from 'borsh'; import { Connection } from './connection'; +const { + addKey, + createAccount, + deleteAccount, + deleteKey, + deployContract, + fullAccessKey, + functionCall, + functionCallAccessKey, + stake, + transfer, +} = actionCreators; + // Default number of retries with different nonce before giving up on a transaction. const TX_NONCE_RETRY_NUMBER = 12; diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index a137d7b8d..f359e52f0 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -3,7 +3,7 @@ import { PublicKey } from '@near-js/keypairs'; import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; import { fetchJson } from '@near-js/providers'; -import { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } from '@near-js/transactions'; +import { actionCreators } from '@near-js/transactions'; import BN from 'bn.js'; import { SignAndSendTransactionOptions } from './account'; @@ -17,6 +17,8 @@ import { } from './constants'; import { MultisigStateStatus } from './types'; +const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; + type sendCodeFunction = () => Promise; type getCodeFunction = (method: any) => Promise; type verifyCodeFunction = (securityCode: any) => Promise; diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index 9240d24b7..4b18fd348 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -1,6 +1,6 @@ 'use strict'; -import { Action, deployContract, functionCall } from '@near-js/transactions'; +import { Action, actionCreators } from '@near-js/transactions'; import { FinalExecutionOutcome } from '@near-js/types'; import { Account, SignAndSendTransactionOptions } from './account'; @@ -14,6 +14,8 @@ import { } from './constants'; import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types'; +const { deployContract, functionCall } = actionCreators; + enum MultisigCodeStatus { INVALID_CODE, VALID_CODE, diff --git a/packages/accounts/test/account.test.js b/packages/accounts/test/account.test.js index 68fad2512..38f1a2028 100644 --- a/packages/accounts/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -1,5 +1,5 @@ const { getTransactionLastResult } = require('@near-js/utils'); -const { transfer } = require('@near-js/transactions'); +const { actionCreators } = require('@near-js/transactions'); const { TypedError } = require('@near-js/types'); const BN = require('bn.js'); const fs = require('fs'); @@ -52,7 +52,10 @@ test('send money through signAndSendTransaction', async() => { const sender = await testUtils.createAccount(nearjs); const receiver = await testUtils.createAccount(nearjs); const { amount: receiverAmount } = await receiver.state(); - await sender.signAndSendTransaction({receiverId: receiver.accountId, actions: [transfer(new BN(10000))]}); + await sender.signAndSendTransaction({ + receiverId: receiver.accountId, + actions: [actionCreators.transfer(new BN(10000))], + }); const state = await receiver.state(); expect(state.amount).toEqual(new BN(receiverAmount).add(new BN(10000)).toString()); }); diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index 40cc160a8..26787cc26 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -2,7 +2,7 @@ const { parseNearAmount } = require('@near-js/utils'); const { KeyPair } = require('@near-js/keypairs'); const { InMemorySigner } = require('@near-js/signers'); -const { functionCall, transfer } = require('@near-js/transactions'); +const { actionCreators } = require('@near-js/transactions'); const BN = require('bn.js'); const fs = require('fs'); const semver = require('semver'); @@ -10,6 +10,8 @@ const semver = require('semver'); const { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } = require('../lib'); const testUtils = require('./test-utils'); +const { functionCall, transfer } = actionCreators; + let nearjs; let startFromVersion; diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index 424be5ea8..e30019e26 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -1,15 +1,5 @@ export { - addKey, - createAccount, - deleteAccount, - deleteKey, - deployContract, - fullAccessKey, - functionCall, - functionCallAccessKey, - stake, stringifyJsonOrBytes, - transfer, Action, AccessKey, AccessKeyPermission, @@ -30,3 +20,15 @@ export { SignedTransaction, Transaction, } from '@near-js/transactions'; +import { actionCreators } from '@near-js/transactions'; + +export const addKey = actionCreators.addKey; +export const createAccount = actionCreators.createAccount; +export const deleteAccount = actionCreators.deleteAccount; +export const deleteKey = actionCreators.deleteKey; +export const deployContract = actionCreators.deployContract; +export const fullAccessKey = actionCreators.fullAccessKey; +export const functionCall = actionCreators.functionCall; +export const functionCallAccessKey = actionCreators.functionCallAccessKey; +export const stake = actionCreators.stake; +export const transfer = actionCreators.transfer; diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index c8f20ca69..ca28a4033 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -17,7 +17,7 @@ import { Transfer, } from './actions'; -export function fullAccessKey(): AccessKey { +function fullAccessKey(): AccessKey { return new AccessKey({ permission: new AccessKeyPermission({ fullAccess: new FullAccessPermission({}), @@ -25,7 +25,7 @@ export function fullAccessKey(): AccessKey { }); } -export function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey { +function functionCallAccessKey(receiverId: string, methodNames: string[], allowance?: BN): AccessKey { return new AccessKey({ permission: new AccessKeyPermission({ functionCall: new FunctionCallPermission({ receiverId, allowance, methodNames }), @@ -33,11 +33,11 @@ export function functionCallAccessKey(receiverId: string, methodNames: string[], }); } -export function createAccount(): Action { +function createAccount(): Action { return new Action({ createAccount: new CreateAccount({}) }); } -export function deployContract(code: Uint8Array): Action { +function deployContract(code: Uint8Array): Action { return new Action({ deployContract: new DeployContract({ code }) }); } @@ -57,7 +57,7 @@ export function stringifyJsonOrBytes(args: any): Buffer { * @param stringify Convert input arguments into bytes array. * @param jsContract Is contract from JS SDK, skips stringification of arguments. */ -export function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN, stringify = stringifyJsonOrBytes, jsContract = false): Action { +function functionCall(methodName: string, args: Uint8Array | object, gas: BN, deposit: BN, stringify = stringifyJsonOrBytes, jsContract = false): Action { if(jsContract){ return new Action({ functionCall: new FunctionCall({ methodName, args, gas, deposit }) }); } @@ -72,22 +72,35 @@ export function functionCall(methodName: string, args: Uint8Array | object, gas: }); } -export function transfer(deposit: BN): Action { +function transfer(deposit: BN): Action { return new Action({ transfer: new Transfer({ deposit }) }); } -export function stake(stake: BN, publicKey: PublicKey): Action { +function stake(stake: BN, publicKey: PublicKey): Action { return new Action({ stake: new Stake({ stake, publicKey }) }); } -export function addKey(publicKey: PublicKey, accessKey: AccessKey): Action { +function addKey(publicKey: PublicKey, accessKey: AccessKey): Action { return new Action({ addKey: new AddKey({ publicKey, accessKey}) }); } -export function deleteKey(publicKey: PublicKey): Action { +function deleteKey(publicKey: PublicKey): Action { return new Action({ deleteKey: new DeleteKey({ publicKey }) }); } -export function deleteAccount(beneficiaryId: string): Action { +function deleteAccount(beneficiaryId: string): Action { return new Action({ deleteAccount: new DeleteAccount({ beneficiaryId }) }); } + +export const actionCreators = { + addKey, + createAccount, + deleteAccount, + deleteKey, + deployContract, + fullAccessKey, + functionCall, + functionCallAccessKey, + stake, + transfer, +}; diff --git a/packages/transactions/test/serialize.test.js b/packages/transactions/test/serialize.test.js index 386362846..740405d06 100644 --- a/packages/transactions/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -6,22 +6,26 @@ const fs = require('fs'); const BN = require('bn.js'); const { baseDecode, baseEncode, deserialize, serialize } = require('borsh'); +const { + actionCreators, + createTransaction, + SCHEMA, + signTransaction, + SignedTransaction, + Transaction, +} = require('../lib'); + const { addKey, createAccount, - createTransaction, deleteAccount, deleteKey, deployContract, functionCall, functionCallAccessKey, - SCHEMA, - signTransaction, - SignedTransaction, stake, - Transaction, transfer, -} = require('../lib'); +} = actionCreators; class Test extends Assignable { } diff --git a/packages/transactions/test/transaction.test.js b/packages/transactions/test/transaction.test.js index 9a615850b..29bda2319 100644 --- a/packages/transactions/test/transaction.test.js +++ b/packages/transactions/test/transaction.test.js @@ -1,6 +1,8 @@ const BN = require('bn.js'); -const { functionCall } = require('../lib'); +const { actionCreators } = require('../lib'); + +const { functionCall } = actionCreators; test('functionCall with already serialized args', () => { const serializedArgs = Buffer.from('{}'); diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 502a72193..996868f40 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -1,7 +1,7 @@ const { KeyPair, PublicKey } = require('@near-js/keypairs'); const { InMemoryKeyStore } = require('@near-js/keystores'); const { InMemorySigner } = require('@near-js/signers'); -const { createTransaction, functionCall, SCHEMA, Transaction, transfer } = require('@near-js/transactions'); +const { actionCreators, createTransaction, SCHEMA, Transaction } = require('@near-js/transactions'); const BN = require('bn.js'); const { baseDecode, deserialize } = require('borsh'); const localStorage = require('localstorage-memory'); @@ -9,6 +9,8 @@ const url = require('url'); const { WalletConnection } = require('../lib/wallet_account'); +const { functionCall, transfer } = actionCreators; + // If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; From 416ee21588a2d1a749d1ad06d64079d784974d19 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 11:32:34 -0800 Subject: [PATCH 60/84] test: add missing jest config --- packages/keystores-browser/jest.config.js | 5 +++++ packages/keystores-node/jest.config.js | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/keystores-browser/jest.config.js create mode 100644 packages/keystores-node/jest.config.js diff --git a/packages/keystores-browser/jest.config.js b/packages/keystores-browser/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/keystores-browser/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/keystores-node/jest.config.js b/packages/keystores-node/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/keystores-node/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; From 879f2401f4e451c4415d19965ce9f475f68c00fb Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 11:33:17 -0800 Subject: [PATCH 61/84] test: fix references --- .../wallet-account/test/wallet_account.test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 996868f40..1e6ca2cb4 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -66,10 +66,10 @@ it('not signed in by default', () => { }); it('throws if non string appKeyPrefix', () => { - expect(() => new nearApi.WalletConnection(nearFake)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); }); describe('fails gracefully on the server side (without window)', () => { @@ -89,10 +89,10 @@ describe('fails gracefully on the server side (without window)', () => { }); it('throws if non string appKeyPrefix in server context', () => { - expect(() => new nearApi.WalletConnection(nearFake)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); - expect(() => new nearApi.WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); + expect(() => new WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); }); it('returns an empty string as accountId', () => { From ef722dc2516f7f79b1bf87cb64a3591517557578 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 11:44:00 -0800 Subject: [PATCH 62/84] test: retain now-duplicated near-api-js tests during transition --- packages/near-api-js/jest.config.js | 5 + packages/near-api-js/package.json | 2 +- packages/near-api-js/test/.eslintrc.yml | 7 + .../test/account.access_key.test.js | 96 ++++ packages/near-api-js/test/account.test.js | 469 ++++++++++++++++ .../near-api-js/test/account_multisig.test.js | 135 +++++ packages/near-api-js/test/config.js | 44 ++ packages/near-api-js/test/contract.test.js | 101 ++++ packages/near-api-js/test/data/main.wasm | Bin 0 -> 86 bytes packages/near-api-js/test/data/multisig.wasm | Bin 0 -> 356105 bytes .../test/data/signed_transaction1.json | 4 + .../near-api-js/test/data/transaction1.json | 4 + packages/near-api-js/test/key_pair.test.js | 41 ++ .../test/key_stores/browser_keystore.test.js | 13 + .../key_stores/in_memory_keystore.test.js | 13 + .../test/key_stores/keystore_common.js | 57 ++ .../test/key_stores/merge_keystore.test.js | 38 ++ .../unencrypted_file_system_keystore.test.js | 36 ++ packages/near-api-js/test/promise.test.js | 324 +++++++++++ packages/near-api-js/test/providers.test.js | 335 ++++++++++++ packages/near-api-js/test/serialize.test.js | 166 ++++++ packages/near-api-js/test/signer.test.js | 7 + packages/near-api-js/test/test-utils.js | 127 +++++ packages/near-api-js/test/transaction.test.js | 28 + .../near-api-js/test/utils/format.test.js | 61 +++ .../near-api-js/test/utils/rpc-errors.test.js | 125 +++++ packages/near-api-js/test/utils/web.test.js | 27 + packages/near-api-js/test/validator.test.js | 36 ++ .../near-api-js/test/wallet-account.test.js | 511 ++++++++++++++++++ packages/near-api-js/test/wasm/multisig.wasm | Bin 0 -> 356105 bytes 30 files changed, 2811 insertions(+), 1 deletion(-) create mode 100644 packages/near-api-js/jest.config.js create mode 100644 packages/near-api-js/test/.eslintrc.yml create mode 100644 packages/near-api-js/test/account.access_key.test.js create mode 100644 packages/near-api-js/test/account.test.js create mode 100644 packages/near-api-js/test/account_multisig.test.js create mode 100644 packages/near-api-js/test/config.js create mode 100644 packages/near-api-js/test/contract.test.js create mode 100755 packages/near-api-js/test/data/main.wasm create mode 100755 packages/near-api-js/test/data/multisig.wasm create mode 100644 packages/near-api-js/test/data/signed_transaction1.json create mode 100644 packages/near-api-js/test/data/transaction1.json create mode 100644 packages/near-api-js/test/key_pair.test.js create mode 100644 packages/near-api-js/test/key_stores/browser_keystore.test.js create mode 100644 packages/near-api-js/test/key_stores/in_memory_keystore.test.js create mode 100644 packages/near-api-js/test/key_stores/keystore_common.js create mode 100644 packages/near-api-js/test/key_stores/merge_keystore.test.js create mode 100644 packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js create mode 100644 packages/near-api-js/test/promise.test.js create mode 100644 packages/near-api-js/test/providers.test.js create mode 100644 packages/near-api-js/test/serialize.test.js create mode 100644 packages/near-api-js/test/signer.test.js create mode 100644 packages/near-api-js/test/test-utils.js create mode 100644 packages/near-api-js/test/transaction.test.js create mode 100644 packages/near-api-js/test/utils/format.test.js create mode 100644 packages/near-api-js/test/utils/rpc-errors.test.js create mode 100644 packages/near-api-js/test/utils/web.test.js create mode 100644 packages/near-api-js/test/validator.test.js create mode 100644 packages/near-api-js/test/wallet-account.test.js create mode 100755 packages/near-api-js/test/wasm/multisig.wasm diff --git a/packages/near-api-js/jest.config.js b/packages/near-api-js/jest.config.js new file mode 100644 index 000000000..749b7fcb2 --- /dev/null +++ b/packages/near-api-js/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + collectCoverage: true +}; diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 6f8f8a237..7832cbcab 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -60,7 +60,7 @@ "compile": "tsc -p ./tsconfig.json", "dev": "pnpm compile -w", "build": "pnpm compile && pnpm browserify", - "test": "jest test --passWithNoTests", + "test": "jest test", "lint": "concurrently \"pnpm:lint:*(!fix) --no-error-on-unmatched-pattern\"", "lint:src": "eslint --ext .ts src", "lint:fix": "concurrently \"pnpm:lint:*:fix\"", diff --git a/packages/near-api-js/test/.eslintrc.yml b/packages/near-api-js/test/.eslintrc.yml new file mode 100644 index 000000000..0fae1d994 --- /dev/null +++ b/packages/near-api-js/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.js.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true diff --git a/packages/near-api-js/test/account.access_key.test.js b/packages/near-api-js/test/account.access_key.test.js new file mode 100644 index 000000000..9a44f9f73 --- /dev/null +++ b/packages/near-api-js/test/account.access_key.test.js @@ -0,0 +1,96 @@ +const nearApi = require('../src/index'); +const testUtils = require('./test-utils'); + +let nearjs; +let workingAccount; +let contractId; +let contract; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; + +beforeAll(async () => { + nearjs = await testUtils.setUpTestConnection(); +}); + +beforeEach(async () => { + contractId = testUtils.generateUniqueString('test'); + workingAccount = await testUtils.createAccount(nearjs); + contract = await testUtils.deployContract(workingAccount, contractId); +}); + +test('make function call using access key', async() => { + const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', '2000000000000000000000000'); + + // Override in the key store the workingAccount key to the given access key. + await nearjs.connection.signer.keyStore.setKey(testUtils.networkId, workingAccount.accountId, keyPair); + const setCallValue = testUtils.generateUniqueString('setCallPrefix'); + await contract.setValue({ args: { value: setCallValue } }); + expect(await contract.getValue()).toEqual(setCallValue); +}); + +test('remove access key no longer works', async() => { + const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + let publicKey = keyPair.getPublicKey(); + await workingAccount.addKey(publicKey, contractId, '', 400000); + await workingAccount.deleteKey(publicKey); + // Override in the key store the workingAccount key to the given access key. + await nearjs.connection.signer.keyStore.setKey(testUtils.networkId, workingAccount.accountId, keyPair); + try { + await contract.setValue({ args: { value: 'test' } }); + fail('should throw an error'); + } catch (e) { + expect(e.message).toEqual(`Can not sign transactions for account ${workingAccount.accountId} on network ${testUtils.networkId}, no matching key pair exists for this account`); + expect(e.type).toEqual('KeyNotFound'); + } +}); + +test('view account details after adding access keys', async() => { + const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', 1000000000); + + const contract2 = await testUtils.deployContract(workingAccount, testUtils.generateUniqueString('test_contract2')); + const keyPair2 = nearApi.utils.KeyPair.fromRandom('ed25519'); + await workingAccount.addKey(keyPair2.getPublicKey(), contract2.contractId, '', 2000000000); + + const details = await workingAccount.getAccountDetails(); + const expectedResult = { + authorizedApps: [{ + contractId: contractId, + amount: '1000000000', + publicKey: keyPair.getPublicKey().toString(), + }, + { + contractId: contract2.contractId, + amount: '2000000000', + publicKey: keyPair2.getPublicKey().toString(), + }], + transactions: [] + }; + expect(details.authorizedApps).toEqual(jasmine.arrayContaining(expectedResult.authorizedApps)); +}); + +test('loading account after adding a full key', async() => { + const keyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + // wallet calls this with an empty string for contract id and method + await workingAccount.addKey(keyPair.getPublicKey(), '', ''); + + let accessKeys = await workingAccount.getAccessKeys(); + + expect(accessKeys.length).toBe(2); + const addedKey = accessKeys.find(item => item.public_key == keyPair.getPublicKey().toString()); + expect(addedKey).toBeTruthy(); + expect(addedKey.access_key.permission).toEqual('FullAccess'); +}); + +test('load invalid key pair', async() => { + // Override in the key store with invalid key pair + await nearjs.connection.signer.keyStore.setKey(testUtils.networkId, workingAccount.accountId, ''); + try { + await contract.setValue({ args: { value: 'test' } }); + fail('should throw an error'); + } catch (e) { + expect(e.message).toEqual(`no matching key pair found in ${nearjs.connection.signer}`); + expect(e.type).toEqual('PublicKeyNotFound'); + } +}); \ No newline at end of file diff --git a/packages/near-api-js/test/account.test.js b/packages/near-api-js/test/account.test.js new file mode 100644 index 000000000..f69cc5ca3 --- /dev/null +++ b/packages/near-api-js/test/account.test.js @@ -0,0 +1,469 @@ + +const { Account, Contract, providers } = require('../src/index'); +const testUtils = require('./test-utils'); +const { TypedError } = require('../src/utils/errors'); +const fs = require('fs'); +const BN = require('bn.js'); +const { transfer } = require('../src/transaction'); + +let nearjs; +let workingAccount; + +const { HELLO_WASM_PATH, HELLO_WASM_BALANCE } = testUtils; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; + +beforeAll(async () => { + nearjs = await testUtils.setUpTestConnection(); + workingAccount = await testUtils.createAccount(nearjs); +}); + +afterAll(async () => { + await workingAccount.deleteAccount(workingAccount.accountId); +}); + +test('view pre-defined account works and returns correct name', async () => { + let status = await workingAccount.state(); + expect(status.code_hash).toEqual('11111111111111111111111111111111'); +}); + +test('create account and then view account returns the created account', async () => { + const newAccountName = testUtils.generateUniqueString('test'); + const newAccountPublicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE'; + const { amount } = await workingAccount.state(); + const newAmount = new BN(amount).div(new BN(10)); + await workingAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); + const newAccount = new Account(nearjs.connection, newAccountName); + const state = await newAccount.state(); + expect(state.amount).toEqual(newAmount.toString()); +}); + +test('send money', async() => { + const sender = await testUtils.createAccount(nearjs); + const receiver = await testUtils.createAccount(nearjs); + const { amount: receiverAmount } = await receiver.state(); + await sender.sendMoney(receiver.accountId, new BN(10000)); + const state = await receiver.state(); + expect(state.amount).toEqual(new BN(receiverAmount).add(new BN(10000)).toString()); +}); + +test('send money through signAndSendTransaction', async() => { + const sender = await testUtils.createAccount(nearjs); + const receiver = await testUtils.createAccount(nearjs); + const { amount: receiverAmount } = await receiver.state(); + await sender.signAndSendTransaction({receiverId: receiver.accountId, actions: [transfer(new BN(10000))]}); + const state = await receiver.state(); + expect(state.amount).toEqual(new BN(receiverAmount).add(new BN(10000)).toString()); +}); + +test('delete account', async() => { + const sender = await testUtils.createAccount(nearjs); + const receiver = await testUtils.createAccount(nearjs); + await sender.deleteAccount(receiver.accountId); + const reloaded = new Account(sender.connection, sender); + await expect(reloaded.state()).rejects.toThrow(); +}); + +test('multiple parallel transactions', async () => { + const PARALLEL_NUMBER = 5; + await Promise.all([...Array(PARALLEL_NUMBER).keys()].map(async (_, i) => { + const account = new Account(workingAccount.connection, workingAccount.accountId); + // NOTE: Need to have different transactions outside of nonce, or they all succeed by being identical + // TODO: Check if randomization of exponential back off helps to do more transactions without exceeding retries + await account.sendMoney(account.accountId, new BN(i)); + })); +}); + +test('findAccessKey returns the same access key when fetched simultaneously', async() => { + const account = await testUtils.createAccount(nearjs); + + const [key1, key2] = await Promise.all([ + account.findAccessKey(), + account.findAccessKey() + ]); + + expect(key1.accessKey).toBe(key2.accessKey); +}); + +describe('errors', () => { + let oldLog; + let logs; + + beforeEach(async () => { + oldLog = console.log; + logs = []; + console.log = function () { + logs.push(Array.from(arguments).join(' ')); + }; + }); + + afterEach(async () => { + console.log = oldLog; + }); + + test('create existing account', async() => { + await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100)) + .rejects.toThrow(/Can't create a new account .+, because it already exists/); + }); +}); + +describe('with deploy contract', () => { + let oldLog; + let logs; + let contractId = testUtils.generateUniqueString('test_contract'); + let contract; + + beforeAll(async () => { + const newPublicKey = await nearjs.connection.signer.createKey(contractId, testUtils.networkId); + const data = [...fs.readFileSync(HELLO_WASM_PATH)]; + await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); + contract = new Contract(workingAccount, contractId, { + viewMethods: ['hello', 'getValue', 'returnHiWithLogs'], + changeMethods: ['setValue', 'generateLogs', 'triggerAssert', 'testSetRemove', 'crossContract'] + }); + }); + + beforeEach(async () => { + oldLog = console.log; + logs = []; + console.log = function () { + logs.push(Array.from(arguments).join(' ')); + }; + }); + + afterEach(async () => { + console.log = oldLog; + }); + + test('cross-contact assertion and panic', async () => { + await expect(contract.crossContract({ + args: {}, + gas: 300000000000000 + })).rejects.toThrow(/Smart contract panicked: expected to fail./); + expect(logs.length).toEqual(7); + expect(logs[0]).toMatch(new RegExp('^Receipts: \\w+, \\w+, \\w+$')); + // Log [test_contract1591458385248117]: test_contract1591458385248117 + expect(logs[1]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ${contractId}$`)); + expect(logs[2]).toMatch(new RegExp('^Receipt: \\w+$')); + // Log [test_contract1591459677449181]: log before planned panic + expect(logs[3]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: log before planned panic$`)); + expect(logs[4]).toMatch(new RegExp('^Receipt: \\w+$')); + expect(logs[5]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: log before assert$`)); + expect(logs[6]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ABORT: expected to fail, filename: \\"assembly/index.ts" line: \\d+ col: \\d+$`)); + }); + + test('make function calls via account', async() => { + const result = await workingAccount.viewFunction({ + contractId, + methodName: 'hello', // this is the function defined in hello.wasm file that we are calling + args: {name: 'trex'} + }); + expect(result).toEqual('hello trex'); + + const setCallValue = testUtils.generateUniqueString('setCallPrefix'); + const result2 = await workingAccount.functionCall({ + contractId, + methodName: 'setValue', + args: { value: setCallValue } + }); + expect(providers.getTransactionLastResult(result2)).toEqual(setCallValue); + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue' + })).toEqual(setCallValue); + }); + + test('view contract state', async() => { + const setCallValue = testUtils.generateUniqueString('setCallPrefix'); + await workingAccount.functionCall({ + contractId, + methodName: 'setValue', + args: { value: setCallValue } + }); + + const contractAccount = await nearjs.account(contractId); + const state = (await contractAccount.viewState('')).map(({ key, value }) => [key.toString('utf-8'), value.toString('utf-8')]); + expect(state).toEqual([['name', setCallValue]]); + }); + + test('make function calls via account with custom parser', async() => { + const result = await workingAccount.viewFunction({ + contractId, + methodName:'hello', // this is the function defined in hello.wasm file that we are calling + args: {name: 'trex'}, + parse: x => JSON.parse(x.toString()).replace('trex', 'friend') + }); + expect(result).toEqual('hello friend'); + }); + + test('make function calls via contract', async() => { + const result = await contract.hello({ name: 'trex' }); + expect(result).toEqual('hello trex'); + + const setCallValue = testUtils.generateUniqueString('setCallPrefix'); + const result2 = await contract.setValue({ args: { value: setCallValue } }); + expect(result2).toEqual(setCallValue); + expect(await contract.getValue()).toEqual(setCallValue); + }); + + test('view function calls by block Id and finality', async() => { + const setCallValue1 = testUtils.generateUniqueString('setCallPrefix'); + const result1 = await contract.setValue({ args: { value: setCallValue1 } }); + expect(result1).toEqual(setCallValue1); + expect(await contract.getValue()).toEqual(setCallValue1); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { finality: 'optimistic' }, + })).toEqual(setCallValue1); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue' + })).toEqual(setCallValue1); + + const block1 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const blockHash1 = block1.header.hash; + const blockIndex1 = block1.header.height; + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockHash1 }, + })).toEqual(setCallValue1); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockIndex1 }, + })).toEqual(setCallValue1); + + const setCallValue2 = testUtils.generateUniqueString('setCallPrefix'); + const result2 = await contract.setValue({ args: { value: setCallValue2 } }); + expect(result2).toEqual(setCallValue2); + expect(await contract.getValue()).toEqual(setCallValue2); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { finality: 'optimistic' }, + })).toEqual(setCallValue2); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue' + })).toEqual(setCallValue2); + + // Old blockHash should still be value #1 + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockHash1 }, + })).toEqual(setCallValue1); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockIndex1 }, + })).toEqual(setCallValue1); + + const block2 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const blockHash2 = block2.header.hash; + const blockIndex2 = block2.header.height; + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockHash2 }, + })).toEqual(setCallValue2); + + expect(await workingAccount.viewFunction({ + contractId, + methodName: 'getValue', + blockQuery: { blockId: blockIndex2 }, + })).toEqual(setCallValue2); + }); + + test('make function calls via contract with gas', async() => { + const setCallValue = testUtils.generateUniqueString('setCallPrefix'); + const result2 = await contract.setValue({ + args: { value: setCallValue }, + gas: 1000000 * 1000000 + }); + expect(result2).toEqual(setCallValue); + expect(await contract.getValue()).toEqual(setCallValue); + }); + + test('can get logs from method result', async () => { + await contract.generateLogs(); + expect(logs.length).toEqual(3); + expect(logs[0].substr(0, 8)).toEqual('Receipt:'); + expect(logs.slice(1)).toEqual([`\tLog [${contractId}]: log1`, `\tLog [${contractId}]: log2`]); + }); + + test('can get logs from view call', async () => { + let result = await contract.returnHiWithLogs(); + expect(result).toEqual('Hi'); + expect(logs).toEqual([`Log [${contractId}]: loooog1`, `Log [${contractId}]: loooog2`]); + }); + + test('can get assert message from method result', async () => { + await expect(contract.triggerAssert()).rejects.toThrow(/Smart contract panicked: expected to fail.+/); + expect(logs[1]).toEqual(`\tLog [${contractId}]: log before assert`); + expect(logs[2]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ABORT: expected to fail, filename: \\"assembly/index.ts" line: \\d+ col: \\d+$`)); + }); + + test('test set/remove', async () => { + await contract.testSetRemove({ + args: { value: '123' } + }); + }); + + test('can have view methods only', async () => { + const contract = new Contract(workingAccount, contractId, { + viewMethods: ['hello'], + }); + expect(await contract.hello({ name: 'world' })).toEqual('hello world'); + }); + + test('can have change methods only', async () => { + const contract = new Contract(workingAccount, contractId, { + changeMethods: ['hello'], + }); + expect(await contract.hello({ + args: { name: 'world' } + })).toEqual('hello world'); + }); + + test('make viewFunction call with object format', async() => { + const result = await workingAccount.viewFunction({ + contractId, + methodName: 'hello', // this is the function defined in hello.wasm file that we are calling + args: { name: 'trex' }, + }); + expect(result).toEqual('hello trex'); + }); + + test('get total stake balance and validator responses', async() => { + const CUSTOM_ERROR = new TypedError('Querying failed: wasm execution failed with error: FunctionCallError(CompilationError(CodeDoesNotExist { account_id: AccountId("invalid_account_id") })).', 'UntypedError'); + const mockConnection = { + ...nearjs.connection, + provider: { + ...nearjs.connection.provider, + validators: () => ({ + current_validators: [ + { + account_id: 'testing1.pool.f863973.m0', + is_slashed: false, + num_expected_blocks: 7, + num_expected_chunks: 19, + num_produced_blocks: 7, + num_produced_chunks: 18, + public_key: 'ed25519:5QzHuNZ4stznMwf3xbDfYGUbjVt8w48q8hinDRmVx41z', + shards: [ 1 ], + stake: '73527610191458905577047103204' + }, + { + account_id: 'testing2.pool.f863973.m0', + is_slashed: false, + num_expected_blocks: 4, + num_expected_chunks: 22, + num_produced_blocks: 4, + num_produced_chunks: 20, + public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', + shards: [ 2 ], + stake: '74531922534760985104659653178' + }, + { + account_id: 'invalid_account_id', + is_slashed: false, + num_expected_blocks: 4, + num_expected_chunks: 22, + num_produced_blocks: 4, + num_produced_chunks: 20, + public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', + shards: [ 2 ], + stake: '0' + }, + ], + next_validators: [], + current_proposals: [], + }), + }, + }; + + const account = new Account(mockConnection, 'test.near'); + // mock internal functions that are being used on getActiveDelegatedStakeBalance + account.viewFunction = async ({ methodName, ...args}) => { + if (methodName === 'get_account_total_balance') { + // getActiveDelegatedStakeBalance sums stake from active validators and ignores throws + if (args.contractId === 'invalid_account_id') { + throw CUSTOM_ERROR; + } + return Promise.resolve('10000'); + } else { + return await account.viewFunction({ methodName, ...args }); + } + }; + account.connection.provider.block = async () => { + return Promise.resolve({ header: { hash: 'dontcare' } }); + }; + const result = await account.getActiveDelegatedStakeBalance(); + expect(result).toEqual({ + stakedValidators: [{ validatorId: 'testing1.pool.f863973.m0', amount: '10000'}, { validatorId: 'testing2.pool.f863973.m0', amount: '10000'}], + failedValidators: [{ validatorId: 'invalid_account_id', error: CUSTOM_ERROR}], + total: '20000' + }); + }); + test('Fail to get total stake balance upon timeout error', async () => { + const ERROR_MESSAGE = 'Failed to get delegated stake balance'; + const CUSTOM_ERROR = new TypedError('RPC DOWN', 'TimeoutError'); + const mockConnection = { + ...nearjs.connection, + provider: { + ...nearjs.connection.provider, + validators: () => ({ + current_validators: [ + { + account_id: 'timeout_account_id', + is_slashed: false, + num_expected_blocks: 4, + num_expected_chunks: 22, + num_produced_blocks: 4, + num_produced_chunks: 20, + public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', + shards: [ 2 ], + stake: '0' + }, + ], + next_validators: [], + current_proposals: [], + }), + }, + }; + + const account = new Account(mockConnection, 'test.near'); + // mock internal functions that are being used on getActiveDelegatedStakeBalance + account.viewFunction = async ({ methodName, ...args}) => { + if (methodName === 'get_account_total_balance') { + // getActiveDelegatedStakeBalance sums stake from active validators and ignores throws + if (args.contractId === 'timeout_account_id') { + throw CUSTOM_ERROR; + } + return Promise.resolve('10000'); + } else { + return await account.viewFunction({ methodName, ...args }); + } + }; + account.connection.provider.block = async () => { + return Promise.resolve({ header: { hash: 'dontcare' } }); + }; + + try { + await account.getActiveDelegatedStakeBalance(); + } catch(e) { + expect(e).toEqual(new Error(ERROR_MESSAGE)); + } + }); +}); diff --git a/packages/near-api-js/test/account_multisig.test.js b/packages/near-api-js/test/account_multisig.test.js new file mode 100644 index 000000000..ff638c4bc --- /dev/null +++ b/packages/near-api-js/test/account_multisig.test.js @@ -0,0 +1,135 @@ +/* global BigInt */ +const nearApi = require('../src/index'); +const fs = require('fs'); +const BN = require('bn.js'); +const testUtils = require('./test-utils'); +const semver = require('semver'); +const { transfer } = require('../src/transaction'); + +let nearjs; +let startFromVersion; + +const { + KeyPair, + transactions: { functionCall }, + InMemorySigner, + multisig: { Account2FA, MULTISIG_GAS, MULTISIG_DEPOSIT }, + utils: { format: { parseNearAmount } } +} = nearApi; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; + +const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) => ({ publicKey, kind: 'phone' })) => { + // modifiers to functions replaces contract helper (CH) + const { accountId } = account; + const keys = await account.getAccessKeys(); + const account2fa = new Account2FA(nearjs.connection, accountId, { + // skip this (not using CH) + getCode: () => {}, + sendCode: () => {}, + // auto accept "code" + verifyCode: () => ({ }), // TODO: Is there any content needed in result? + onAddRequestResult: async () => { + const { requestId } = account2fa.getRequest(); + // set confirmKey as signer + const originalSigner = nearjs.connection.signer; + nearjs.connection.signer = await InMemorySigner.fromKeyPair(nearjs.connection.networkId, accountId, account2fa.confirmKey); + // 2nd confirmation signing with confirmKey from Account instance + await account.signAndSendTransaction({ + receiverId: accountId, + actions: [ + functionCall('confirm', { request_id: requestId }, MULTISIG_GAS, MULTISIG_DEPOSIT) + ] + }); + nearjs.connection.signer = originalSigner; + } + }); + account2fa.confirmKey = KeyPair.fromRandom('ed25519'); + account2fa.postSignedJson = () => ({ publicKey: account2fa.confirmKey.getPublicKey() }); + account2fa.getRecoveryMethods = () => ({ + data: keys.map(keyMapping) + }); + await account2fa.deployMultisig([...fs.readFileSync('./test/data/multisig.wasm')]); + return account2fa; +}; + +beforeAll(async () => { + nearjs = await testUtils.setUpTestConnection(); + let nodeStatus = await nearjs.connection.provider.status(); + startFromVersion = (version) => semver.gte(nodeStatus.version.version, version); + console.log(startFromVersion); +}); + +describe('deployMultisig key rotations', () => { + + test('full access key if recovery method is "ledger" or "phrase", limited access key if "phone"', async () => { + const account = await testUtils.createAccount(nearjs); + await account.addKey(KeyPair.fromRandom('ed25519').getPublicKey()); + await account.addKey(KeyPair.fromRandom('ed25519').getPublicKey()); + const keys = await account.getAccessKeys(); + const kinds = ['ledger', 'phrase', 'phone']; + const account2fa = await getAccount2FA( + account, + ({ public_key: publicKey }, i) => ({ publicKey, kind: kinds[i] }) + ); + const currentKeys = await account2fa.getAccessKeys(); + expect(currentKeys.find(({ public_key }) => keys[0].public_key === public_key).access_key.permission).toEqual('FullAccess'); + expect(currentKeys.find(({ public_key }) => keys[1].public_key === public_key).access_key.permission).toEqual('FullAccess'); + expect(currentKeys.find(({ public_key }) => keys[2].public_key === public_key).access_key.permission).not.toEqual('FullAccess'); + }); + +}); + +describe('account2fa transactions', () => { + + test('add app key before deployMultisig', async() => { + let account = await testUtils.createAccount(nearjs); + const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey(); + const appAccountId = 'foobar'; + const appMethodNames = ['some_app_stuff','some_more_app_stuff']; + await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, new BN(parseNearAmount('0.25'))); + account = await getAccount2FA(account); + const keys = await account.getAccessKeys(); + expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) + .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); + expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) + .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); + }); + + test('add app key', async() => { + let account = await testUtils.createAccount(nearjs); + account = await getAccount2FA(account); + const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey(); + const appAccountId = 'foobar'; + const appMethodNames = ['some_app_stuff', 'some_more_app_stuff']; + await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, new BN(parseNearAmount('0.25'))); + const keys = await account.getAccessKeys(); + expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) + .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); + expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) + .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); + }); + + test('send money', async() => { + let sender = await testUtils.createAccount(nearjs); + let receiver = await testUtils.createAccount(nearjs); + sender = await getAccount2FA(sender); + receiver = await getAccount2FA(receiver); + const { amount: receiverAmount } = await receiver.state(); + await sender.sendMoney(receiver.accountId, new BN(parseNearAmount('1'))); + const state = await receiver.state(); + expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(new BN(receiverAmount).add(new BN(parseNearAmount('0.9'))).toString())); + }); + + test('send money through signAndSendTransaction', async() => { + let sender = await testUtils.createAccount(nearjs); + let receiver = await testUtils.createAccount(nearjs); + sender = await getAccount2FA(sender); + receiver = await getAccount2FA(receiver); + const { amount: receiverAmount } = await receiver.state(); + await sender.signAndSendTransaction({receiverId: receiver.accountId, actions: [transfer(new BN(parseNearAmount('1')))]}); + const state = await receiver.state(); + expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(new BN(receiverAmount).add(new BN(parseNearAmount('0.9'))).toString())); + }); + +}); diff --git a/packages/near-api-js/test/config.js b/packages/near-api-js/test/config.js new file mode 100644 index 000000000..4af00c1c6 --- /dev/null +++ b/packages/near-api-js/test/config.js @@ -0,0 +1,44 @@ +module.exports = function getConfig(env) { + switch (env) { + case 'production': + case 'mainnet': + return { + networkId: 'mainnet', + nodeUrl: 'https://rpc.mainnet.near.org', + walletUrl: 'https://wallet.near.org', + helperUrl: 'https://helper.mainnet.near.org', + }; + case 'development': + case 'testnet': + return { + networkId: 'default', + nodeUrl: 'https://rpc.testnet.near.org', + walletUrl: 'https://wallet.testnet.near.org', + helperUrl: 'https://helper.testnet.near.org', + masterAccount: 'test.near', + }; + case 'betanet': + return { + networkId: 'betanet', + nodeUrl: 'https://rpc.betanet.near.org', + walletUrl: 'https://wallet.betanet.near.org', + helperUrl: 'https://helper.betanet.near.org', + }; + case 'local': + return { + networkId: 'local', + nodeUrl: 'http://localhost:3030', + keyPath: `${process.env.HOME}/.near/validator_key.json`, + walletUrl: 'http://localhost:4000/wallet', + }; + case 'test': + case 'ci': + return { + networkId: 'shared-test', + nodeUrl: 'https://rpc.ci-testnet.near.org', + masterAccount: 'test.near', + }; + default: + throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); + } +}; diff --git a/packages/near-api-js/test/contract.test.js b/packages/near-api-js/test/contract.test.js new file mode 100644 index 000000000..fbeb57790 --- /dev/null +++ b/packages/near-api-js/test/contract.test.js @@ -0,0 +1,101 @@ +const { Contract } = require('../src/contract'); +const { PositionalArgsError } = require('../src/utils/errors'); + +const account = { + viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery}) { + return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + }, + functionCall() { + return this; + } +}; + +const contract = new Contract(account, 'contractId', { + viewMethods: ['viewMethod'], + changeMethods: ['changeMethod'], +}); + +['viewMethod', 'changeMethod'].forEach(method => { + describe(method, () => { + test('returns what you expect for .name', () => { + expect(contract[method].name).toBe(method); + }); + + test('maintains correct reference to `this` when passed around an application', async () => { + function callFuncInNewContext(fn) { + return fn(); + } + expect(await callFuncInNewContext(contract[method])); + }); + + test('throws PositionalArgsError if first argument is not an object', async() => { + await expect(contract[method](1)).rejects.toBeInstanceOf(PositionalArgsError); + await expect(contract[method]('lol')).rejects.toBeInstanceOf(PositionalArgsError); + await expect(contract[method]([])).rejects.toBeInstanceOf(PositionalArgsError); + await expect(contract[method](new Date())).rejects.toBeInstanceOf(PositionalArgsError); + await expect(contract[method](null)).rejects.toBeInstanceOf(PositionalArgsError); + await expect(contract[method](new Set())).rejects.toBeInstanceOf(PositionalArgsError); + }); + + test('throws PositionalArgsError if given too many arguments', () => { + return expect(contract[method]({}, 1, 0, 'oops')).rejects.toBeInstanceOf(PositionalArgsError); + }); + + test('allows args encoded as Uint8Array (for borsh)', async () => { + expect(await contract[method](new Uint8Array())); + }); + }); +}); + +describe('viewMethod', () => { + test('passes options through to account viewFunction', async () => { + function customParser () {} + const stubbedReturnValue = await account.viewFunction({ parse: customParser }); + expect(stubbedReturnValue.parse).toBe(customParser); + }); + + describe.each([ + 1, + 'lol', + [], + new Date(), + null, + new Set(), + ])('throws PositionalArgsError if 2nd arg is not an object', badArg => { + test(String(badArg), async () => { + try { + await contract.viewMethod({ a: 1 }, badArg); + throw new Error(`Calling \`contract.viewMethod({ a: 1 }, ${badArg})\` worked. It shouldn't have worked.`); + } catch (e) { + if (!(e instanceof PositionalArgsError)) throw e; + } + }); + }); +}); + +describe('changeMethod', () => { + test('throws error message for invalid gas argument', () => { + return expect(contract.changeMethod({ a: 1}, 'whatever')).rejects.toThrow(/Expected number, decimal string or BN for 'gas' argument, but got.+/); + }); + + test('gives error message for invalid amount argument', () => { + return expect(contract.changeMethod({ a: 1}, 1000, 'whatever')).rejects.toThrow(/Expected number, decimal string or BN for 'amount' argument, but got.+/); + }); + + test('makes a functionCall and passes along walletCallbackUrl and walletMeta', async() => { + account.functionCall = jest.fn(() => Promise.resolve(account)); + await contract.changeMethod({ + args: {}, + meta: 'someMeta', + callbackUrl: 'http://neartest.test/somepath?and=query', + }); + + expect(account.functionCall).toHaveBeenCalledWith({ + args: {}, + contractId: 'contractId', + methodName: 'changeMethod', + walletMeta: 'someMeta', + walletCallbackUrl: 'http://neartest.test/somepath?and=query' + }); + }); +}); diff --git a/packages/near-api-js/test/data/main.wasm b/packages/near-api-js/test/data/main.wasm new file mode 100755 index 0000000000000000000000000000000000000000..639540286e293cbf7f61295c9ffadee0d1a5867d GIT binary patch literal 86 zcmZQbEY4+QU|?WjWh`K1WMpM#WDsDJWUgm)Y-l*Zz+KOPO0%mnv*o7d<`-2mF>uAl drzDmn#;4|`Ff($;$7iG_7Q`nd7N;^Z0RaAq6wd$v literal 0 HcmV?d00001 diff --git a/packages/near-api-js/test/data/multisig.wasm b/packages/near-api-js/test/data/multisig.wasm new file mode 100755 index 0000000000000000000000000000000000000000..c904c5b7842091e6eed8cd0d7822c705091a1613 GIT binary patch literal 356105 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�hdDh~%_?sm(HeD%k>0iQKDbJSt9Oe}gB^xHWt7bu|38}m>SzBL z2udr=7|Q+-!{wEqqhF|HIGY;ptQUAP&l~s;ngnA4J|bcBm@Nw|6Bqd#m|aL1vcS#H zoVI`{VxXUslf0-j1%}z&OOs^zmA4#SPH*8}VD?jxw_smvkAH*fwid0eIGslGDx!kL zyt`S`R+D|Pt*z{g>D9j#1F3^)fw8rjye)gd2&1t?iBhK+FPzX)b>`>TTRC6gN}xnB zzi+%Cpb3EGc|-)4eugwi`1E`sFJ!U6AG9Gce!J-IADcJ3MQ}i>EuV!V@~F?Q-xeGg}s}BhXx7D36Vhphh7pfpl zNc`6d8dU>8gWA20!UH)Nb(dOHx)he5mB7D7rf8$Tk&4JF#VfYne<)jP>A)oX2)O+} zy0~gl@0s9Wvz1%LI!kGfr&abQ(=3m#JV3UCS0(fIHg}Udtv3Y~q^@;S`FY@StSAPe zUS`4GHr&J}_qEEDRmL_3jil^#mfbosgaVL&(Gn7*3nzZA`uH1uT%D z8HQ_-;@fRV84f&M;Zf8=(0K|b!KDv@2Y){#Gsn`&oepi!hnAqppqOfru4o8)Waf^0 z5r(FJ5wZ? zJL9ITL4=M6&C>9;E1#H=-(*CC^5GbRSbzesczWd%VxeNw8YulmhMkHj9BI)~gbxtm z>3&Dz(L{;_ec%GN^lIEX{r>+trL3-}>&KOvAbl*ja6@pxag)^+V^Z6_A&a6x;F%43SXP$>t_iFv zi(W6{rNYd-d%mdL-heZFpZ~^iOM2%3=Bd`P5GZJi!1K%&W`{EhAW*ZR%$5FgwWU^c zpN7{^e3z>YDmKpJYYN&h#73H$qxi&jlBkGj z|5hW8u#r3vLts?RQh?6tHo9&ZI{0+$xJ?&x8ypCN5x|+{6*XFDX<#jh;r7GQ8l-+S zG;nPbHPC{6sUn z*LK`m;KCZJ$&9XIOZVom*;3OlvOVBGdO?CyAIh9kGV0=aZQcA^#YwBo!i4_FdtrU^ zu<;=}Ks>W~y4L93J~4Vs9zj}(%8b%93(j+um~2m7yJ~9&wx!RU3pUG_>9&|3((Cz6 zH}L6NSApz?zo+?PqydZ~u1NY{OshF&cL@t6j7%vA$R4W86Jis@hB>id?w05r%C+Hv z%%m@8!U0bDzk#GR1LW>qKHE+zB&Fad#}4wrAaE;=7Z5Xi)I%oLwd#!Oa2NRMo~^12 zt*W|-dZ@Tok7|iDN*8}$F&i3lo79ZMJ*G*6jbSo`l8MeRkLgXRxgU>IJ@5IRqk^-j zu~+9T_!3%OBZ-AZtAu1thq4_&xprvM0{aQUj!7w7KSMJN&Z zPi0ok-945PmfCKu3GIr03iH3XEU()-d_;pzD0 z8f`9|{3s67WCjGd6N$laAN9QeyydW5al2;=>x}u+ZZLS&Ef2YQOts6y78t%_8mI@( zdlN`M9o@bKYOlLjUOPLWOb*;to0#sECnI|D{%cwZ3oXs0(V=0Jx*fX1IzTE`UxEpK zC1O71Wyuh)CoQ||#EO!Yp>X;FN7&R52vRM(E?T{2q80b%-IvEU5_;^=bnD0aWMUmg$) zVEHjX{pHDbj~B1GD*0};@fJKHOqN=;=ll_oR$45;Safbd3SV9*oa|& zaI~?{L+eHw)Z0>@2i`XMH%bog6&K?zcnFC5#UiVWOZLW1*Df6`(h~2oRxr93^{n+@ zm%E1LYwX=RS`@Muibb|YzNN@U3kon{8^*rT1&m2||L8*b&E7I%lf`Jsk0j|!08ibU z={p$RRCA(fFnTn#0De$Jb-GSdfAOWwn=fricveL~V7@et-R9G119MjGTd_eR+Xnd^ z{NQj}<)|5zn4d#KS}J<-n=KSe`^JmjZ?=hN8s^)+(MBp~?prD}&cz~s7&eYN;roRE z27yt*l%tGlrW|Ew(Fl&R=|(O90N*%xkjAa1COuiAO??OMN->KDR0~(VUR33%GkOaWsIjE4>%MIy5=jcQ?2HthR)$a=0=+(#8u5CgXix{#^* z3SGN5?sFVy=X!ew8V9DPI)u9Wdl&Yb%J05DQ4 zHc6r)Tc#ii(Y?^-eljZ(qDSGryZQUv7c~J;d{eAi&k z1_E)0Bfej362otFi*OY9-e~w{4YY>uBI0}J6uu|1>{+Rf(Uu6K#AVBxwJsad^=%T9 z-iZN9TknH~t$|`;_+IG%noIXc zL>LpjTCkfk7#xI<$Izx%9+t*1vgm}#RdX!lV*T+LUMl+9m)O5%&}4mt!$$4r^&~A6 z{jxtz0`p4C;>5Gi!i;5cvYtvaCU!Id>G8ONd;M`?raN+emNcB`Vic%d*{hip%yYQNB&BJv?3VB0o$K`QXJQY%4r%^dlT(WV ziHhe?kru_4{76uc)PXYv8j}MO&$p^mh-%)+MT}r5$sIFQdiSJ{$0=05Au1CIiU6ry z1h^pLE64FzeZI-^aj&0KglS`>V}~ybPImj zbHN;En&%x#-d4qJK=$Do*;A-*)O#Wx30f@RRi1VFoZA4bw>ETjWp`AjW?{yYAB!_s zll@<4rR}(#1Rv*=yQMxkc){l{NO<}-;*Mw z7*5D+_$grzzki82?_(@`+W+o~&1pO=Mo<(Fm_I|qWU|7fy%F1REKz&hY2jh`r^YUl z#vC7%)tuH^cZm4xe_~KMt|qs5o?`w^hL5WjYl)+g_ZXGy?acpljItZ&?k|$>Fbger zU(^$wA7c{o^Aa5ylpELP(mQhZLCj$o{<;Aunr}9S!(kKOVeWp^(mA~ft9-uGTv%9W zEwnpP2z16s2jmg*gb@J|acwvaYr}kG*<-A=VLxJ?tFS?`J)XGFs)EOD5Aj3|?Q^T) zB{LvM0Xzo~Ix`iVBNFWR?1?Ved@Br{9Ts~t5aj39g~3b(a*Lb;4!ScHK;9Nxoeclj z6KavYBxMJK2!cuAYE~kD&KARsP*nIV*lx~uFe83w$ZuRLL*UCxOLgXBk z|I8GbFq=}Fcw95fh`PmWR4PN2e36|~8N}YlIaPMammOMJ)P=Rm_EfgUc4DJFVmYQR z7z%c=(>l(S<*Q_2nKNWDoT%rqmY1Z<>O445w_k(G3`QPZ5$iqwe11UdKunTu^(*dOvM)%9*9o1P8|r>okGqjFC_uNV$+yVWwj(vIy@VH*!KU#9S)dk(VHM`<|9M|oB2oUc{FD0QYa&D08_ zeHZ8el%-~>1m^b7OJt6TUXzn9WjF&u^Zu7xRV0kGf;6JKE_)0S(Wp;o09?nkb!UI= zf@1d9kHlH#ciNS+Kj>ukPt&w9Is4O(`A(iBNuJ~>#24_Pk*%WyTu44E?8|7#OmsLj ziRzZwBHD~oO&Do8VNmvJi=*)Uh<^)m%uF4Uc9NhP8hK^vJVZU)G%ONp0>$`X78LW8 z)x~|pCDwZs_pCYBQC|8`LKca8l*tKk53~rgYmx0dO^SPAeM;ObZ!CdE%3nf z%X~Y#tsN71v+Wq&?3#t~GGBJrnaaw2G9&P4cc(8~oN1t8J955SVK?TP4$PGGe=$ot z(HO5x`AB!3mrQo)m{Y%g%m^^xl4MPd0Z$ABJBeYxHvh3qFM)(ztdQQkAOuI^M5|?- zIpr=T@`{uZF50h1Ga0TH zeS~%*v6jfY9?ibTCJa1)aZlDoWSQ^TiF}#3R_#!6HvEFbljfPY6yCr2AIBcEKI%YK zAIVV)#{R#rY^BNJ(l}^}(ZI-c$aPJg8oXY z_UC5EbCFm5{U^15UuB{s870{sl$K}8_=0r!RsSsX4u40_7Oa#GfA~Gs^SkxjU&4m= zvpsWwh3^#E+KIO}v&PLW0=Sf&;0b&$EV%oBO1gTS@rOphpS&G^rU9`+=h7amn5~9~!e&c<&740LqdpNc9pq*5WK2m0S z>0~Cc1(8m>@zmrLbyps^@3tf3wqiVJ{yJtN;M4B-gaI5k`Pq!_LKq8AyKuefseS9X zvoh`$I=64!TOPaz2Rd~QSH`_t$NiP@T+v@%e9x`gn8Y&Ha`7HL>5i(-^6)*BUEcH_ z)?IsiSQ&RL8(RvfmAzjOuJ-3=EFb$wMF{yF2gYpv7XWtYg>Aa_*L$=LD4;+x34l=# z!NpvpJ-}m@NN)v5Af>IM4R9Q2JfM9Z=XeA{aMlH+qB8|l?^Y@l;Izfsz^yC|DvDBr z1YZh;Agthrt2^W?>FV@C`oR8E4XbG|j7Du76+Uf{l|z|nD3PuZl={5SMpK8c$I;9* znr7Ov{kN7+16s=$pxLbka1d4J(_h_RhHkSxrc+eWb|V^eb8j7`peh;um_^Pc!=LoO zgw)t~PJmD8=z2$6zfZX>DibGER=r#B6V1mzRv!vpwFv9_RdJ z6Z{`c>w(Ob|4lpz%q%y`!)wmCi~#T-H9X{E0UfU7cQAT?TiBz|XvOOT%uj35of;rN zb{>XNz=afQ5^)khrb(c~kK&{2p2+^4DFL4znERIJ4v%vk$PGxt_X-mK*DAf6+bUvA zzCcHgKxurk!XYZZF&UNLn25@UKnxBzNhI(#+{Uag+-^PA=uKT4@TrL{_`6Hzp>MB> zWR7yorY%M0*ukIh`_ys!bk0s22ihlji6+RMh%vlqeei$Q#(s7oL#Gw{O z+RI=4-A~=}xqH9xt6x!e#nIJHMoLEXNPS{oo)%`U&zXCKWwyrfOUC+^1a-#OZ_V}~ zqX}oc5=pFC2)V2&2!X802hP%20wbwAl_ihN2a+o_tx2v+z85{sG|UE#g89kMJj9RO z%9n=CU$>4MD{QxGXjagpPv?;dg4wFCvQY1PmY+{=C8H*jkXOZ(i)Lu9%^YXb+RC^= z8LDI2YukjUk73SZ^1L-pob50Hsd1HTtN^qxLJg`}sk8|2z7c8za6xJxZ>??KeO`J@ z|5cvUMhR?mzB&(-%Kz8e5BB~Bmwdt=W-N$RBeSBM=nNOTK*nzR#D3grOKWC0Fg5ik zCi{Y^P~jQbp4=CoW6^ia**S^{l9rED9uGJG$&3rV`iOFfQVT?i$7Q3Hlzg7xn*mR< zBN7Eo`)FPJikWWEf!gcLP0M}(gby3?^3eeD4flaw^;UGLaZ~!Mt^j2xB&Pu0#5Mso zNbVcYYaS#s`%IYEQJc*7V@DNRk&w5ol5M1dz(5i;Y<~hW=lHumh8&N}MeNepNB8}u zoJG&j+Osy^JssRcWnDYn`uO_}Q5z4fkrA*)uJ z);ME?&Pe>tb5L83ec~QeSq{N}!s-ao+H{BrRinFeUl+78WCfzN8-{;Gk{y3XpJZeY4v3A9tCYclEob`81cFqxlI zMBM)01a7T%W(z~0TNwV0gn}gwzof_Duhu-7y8Ca{ILxn=iD^u5lIf^r8V>*3*93-j z;RLwmv%Awi>ga9avG3B$@H5J{B@9KG@o5d8)4Bo~pVdp5k$Q%w18l;mZ}!iuz~b7? zo?UIKtMo`F{eN50Xf^o?g+&6_g$18!)M=`h18xFUBn51y2Cg5GBJEEdxA5Q9tJxjD z-v}UZYEH|{d0N+C*28Lfd6FU@aWTP*Fl7JnVa|r|!?^@*GmiH3FR*RJKp;yCSRFOF ze~14C9!)JaF_@APVs4A(c00KZ&)#*4YLJ~r*o1<$q_bIsLtDig0D)la%9!dgpN{*> zR~@}uGFhTRB1qFIbK0)kmgPfVT|M=Da?262z3k}Ste;dlvzT*p$a(sew@|L*-=5xa z%Mq3Oms6#3E4939DjAOo%`T6pl3Plq&ZC0m*#*_VS_Ei_APs&#qrfDJm}iNg4Ke3AA!wtOnnBQ#PoAcr z3zYnVDCk1fFoT9o)?Cq>#V0O*A!x(82)ZZ~{rW7nCFVRQ1Z}laGYHz|lcy21J+^WF z6m+qwfP%Wy%(p`yx|7V;GM^Uyr)JS1c3jI#h_?Lg|nz|wMx&f1wqP{RGy@=Db^vHwkrYHhPw2ZhG35BYcON)d6*r03VWC)c+~}^ZzO_-Ty6jD1qre*mJ;jW>)!RPC z#JJ6!dN;pid}&NQJ(2pT&9)=AdUUP4$JR_eSx-gCqZ(M9Q7P5>{nHaEAkLkzQZbKi zRy|cu0rjRIG7l)OpL;c?rzQVhv^h~1jgLb$xb-8`0BA{p2ceM@F$|3ybY3D~$AmEC zyCFkw2i4@g*aw5e>Y$8PqcaPRn{`asOAB8S|yUV{%yj62hqbbTnqx-}~!g>Xk z`oGfLkER6vMmR&%@ys?pNU1&bUg0nT+2|ilQF9n$T(a;fd**I*7ks~a@D6fGLm&RF zjQ)`+E`eAwx0;zp4%I`>Xq6;3rdcHWhhMYY7WoJvBt&eO$DX5FZd0@Ipx+~+WcN+( zq*3%|rGF*;ucp;fbmasDXZhPKL?r~Nz?%*D)yJu-ahJap$Ct?$xqKpt(-+_*SvSYx zm_y8@#C5O`GtU*3sBa1#wAHrOUR?6B1#|6QHrpWArbzjYZWp>_j^yr>-uT$Y<2^pr z+>dy?Wpm{2q+#}Jau0E6CdWv~`_ww9Uw|;H7fF+TpRX-yy!}6`*$sH`rtAh&dgHce z2HBhLW*_Mj`<)^EaxZeG9hAX_!JN<;bg}@R_TXyy?shaUZ+7wGwp$&S|$G)#ZM zg*;QK;E4aD8Y~%vg}GRq&0#639qGE}aVf|mnf3m_6LPJ0R*E_XJ?jKSSRUI`MleE6 z!{Sfk&~>0qJH6SUqE6c#l9L`S*mVv_d0QeeN}uC$hAn0!BgE*;g2vZBX2`rdV9?N4 zVwt)k8sDE)9(P24!_$|6K#Yp~<66|S^#^>AtX`0cz|!gk3-B;5JL`E0w2)n^fhU80 z&_C%2o~}{jrC4x;j`+uRf@*M;)FS^sCms(T69Q>hQred2o9lT=YLR3 zT#tyqGvdLs)deO~V3EK!=2%Pm>m(P<_h3Z+uxiDTkxt{%;5SWY@H{09Qiv{AtB9(r zuyJAePu+*{HA)?@%T&_n{|o3y2??uZw|P;(9QLGyL;zMi{_hxl%Vn`O68C=SjiG^a zglM5re;|<9S!h=I)CX?N)X17yXw3@7RWpOH*6w-AxM$avaTj^~j15UaV%Y&$)BdJ4 z6TK5^x(>QHT*p1N4l#I=;BO?F>ECt8V(pQ7`28>TNKMta_DKD73Sot-a3+1JMuN6E z0rrmCS|(Sq#K5r+7`RYj)Pi*Wg;yF#GfmB0g(@mKBQXHP5}DWeP?vnCSpL=PV)7tc8MFZF6<2;uczaynUS}@~lwlgQ)Km24uqXg2=~(m= zKVhWAvV!~6^s0kbmUWe>HlY0^Y5QS z>Qs<|lu=#KxoNQ1igoY|D*O5!b-;!&S|Ha1J$dmZ`Ufq)KaHyEcMY2O7cqtD67t z9BRgeWtB~e2ZmhYLG|7h>rHv74ZUsZV6@jQczm6Lw(H?c1&^*%khtPAgZj*!Vc4cX zC5HFR1s`9hAeQ}^3qG<=K{U}b7d*92L9FL97rcL+f~farE_h;{g0`l1#^G3}pxLWu zD)_WSo-EkO;SiuTdzIBG#FjKI{xpK{MxG;fr6kQQn(>wNk2I751>9kKntwsIH> zXfgpGesNdM2(ix->(;BItJFyeIg=J^w()hb{6J{Hr853#G4d#ro5fSUgHxt%!aar} zukM(0tC8)=_o!`*0?=jm8f9YT%cvlz2sIh-Q0&A+{YE)pkuuUq)Y}>sKjVdvuvj`= zbUc)Xy9Q)x{T~4<7KnU!3P8Y`uy|CuG(jzmaHckseF(w}>MfMRWKC0=+OIu8EB(L8 znPXkwTSOT|@%8^{7eX?B0hBX2x7sQ3=tqEVGpH6svpG%=($E#@OA`(xlQEplak!9A z!{PfaDWcjZGy8_*#=dWr1oz3zo*_Bj>SC41hiDb=_7!JTJm1He=V^t`XZ!vat9*_b zr>yv8zGCHOx|pt5$rh3!h*1*0DNBC++khh{9Z^1=UCCXG9OV=1n=0aYLC4&L+~tK! zQ(0V-(%O<8OL_;8C0kI8!LV-W7NrrO)1{S6BtE-f@MX9*KOBlP?!&Rgc=(fN|B<4?Z`&%zq6);SK=1MnTet%Y_jFr;~j< zP3FS~7GR0-GI>kM7rbbZi-TwnK2x8(9Eyn64m&-a@s^U+589yVmXc7HZYg=t4`uC^ zl1F^L*IJFcsJpNyJ2&Nwx0D?HDH@2kl>FNY8#PCNF4$oN(SzqKjhyLsaMX!eabj^}FIGO=-2K*&9b*WmL-~q~Sl$%?}H{4$A+Ed@QrvUVw1whdtKu`TT3J z3W;qEl% zi~)X4NZ8Z{7Ij&O7Iec;Sgl0#>dK22N0jMl$YO6iE@mxV0tw=SI^)Np!IsZFOA}LK z@FfgE21{kHA8@^ed<{7HywL7*LLn&WdOjU<4Ai_|t>ZMPqAO9~4>?H?8t00>pi(3Y zxNh4paP-WgS#z|c^VHB7UeCcN1P;(rUbe*Pi4!exP>R-`&!wen21CEVDhJ8DHN}cM z2dOw5$u7=BeI4#N(UjsISO`D2ru-pxFyFO%fwowkwobDLa2-vvHmE*!lsAF?8<8sYlP}Qsk=H4n^P#a8e zI-5pOAE>`&Q*1XHlIf=RSl#HVXR3y$|7zi37I3-uff*@J2$Ph`_h?N9HV~BI}PffGyh^rU7GuzuSic z`<3VV;-e(q85SQ^d02cjbT|xY63%Y%abjl_tnZZ`DbMh|);`SaYnUuB?NqfZ)vUPM zc~NoQXhxBnNm%=~0Ek#TaBj<@?h@`c-DWhp|3i1ZuD=}#N7=C{r>SYXY}=3SvNeOgE6w@;F=6v%`ACAUSD6h_e>wB-nbsk& zGTe!u)66sQVf=pmfupeRR$?b4+Uh~-9=F6{NHoWP>VD4>7ek`?>{IukC5HV2a(<|x zUzo^gp7PZFv?b1k;+ogV`2kDphD15ANxaPxW9xEGllYtg-wQeAcqZ|$CVC{NGKpWF z$SH?0iJ!N`*t(p-B!1cw`=L(hge3m9B}RnEiA&;dSYm`zZZ;A>I#C?TR^ok@*z|Rh zwDtyotIuq09n+R_Ii0Ymez^AhZatrUEX<=H*4V;Xld&-0Oydq3J@MgB@L2~~>FqAP zUBhwo(&_W?O|F}YQ<7ks1w%}yE+WLKV%?Q{P58zUGg-F@0|`x^JRu;znVv@dAMIf3 zqdTsi)G|DsRj%iXO^o|w$ohYb#_AgPjY;3b^-A-6|7UaUn8&F(U%=-xHVZK-9kT@` zknk#<>&0WCr}!yN?AEIFs+b9?F}_3`64Bd}6!?Vjt1t*z=}K@(|C91(1iY4(xDbxN zYrh4xsTSfK3ms`sStcnGlt^d{{tmFsrA1>Dcn)q=MK)TZPCi3OvK}nJf*2v=rEqLo zHv7%0C+z5b7Is8t3q*nz0UL5uhtvU<_J1jae5mpegmGgfV5^mVSeLF~vT6~ZC^Fm8 zbPg9!9u*N=QGZdwNFYW9w@CXsNYz>xiyO<)6||V%YFb`H3RT14orbVxJ$geyXfx3p zO7t-rude!5;|lc%?0>-+tsi$vkr=j93D)Q|0@l~AT?vg?L|xcF=N+skRHw&UcvSy> z$YcFPt76hqI32UP<03N(kas^%lMILPu@6zzD0dg=`m?k7XN%|a8)nu9Hn3Xpk)QRI zan}bEox3xr43Wxj8`PaUi_iHokjxVX zwP;^s%G0hYuE6HJ!1xR}w@w9i)tz^gT3)g`wSGl?lypmh&WYO~h1269@iFv#Y4VIF z_?IW1`jzlpm4FL2*_9l*N05=jxKYX@PGxa7EnS-L@(b>2BRc=tg6X~#ozNC{tPb8; zcF_H~|0sO;IGii(NqZEFve$lLwCc6rTyFtibYRX7Fat4?CbvuWm^vG0Lka0rOiplK z5tP1>iP2Nhe_k-D8|H#6n)Sw3KHGJ|q-t7Z#rIQ*UP63m;%tV>&+T~&ER>;@h*-zM zo(mzIM1Jx$4zDg`Q*4d-^RNpn)?8{m2hK}QMsbGGjh9^RIW10AlFS_Eh*e^rHGqgU zk)s`(F&wv8c(ZCG1}WrokUFgS!g2UAXz}z6K$AE#X)OwRGKwIG!-|)3wz`?7yzK0v;c`1M#q8gn0eGBvkMp#+|ezW`N>ODSh}wHBoM@on$60 zTG@p2s8x}b5*l9kLtiU7=JvVAtSZ|$JEFI4cvZ4P7%21Xh|T#t-2PJzW5uOhnItad zz9s;#b15ktNOXQ&N=K);boa~i0=ztB&}owZd7i0y5cl$`(L#zP!;iIeCLXg+8?yh_ zG}~6h6iATJ@?%lM2Q98jA20V1tK%WpGggHtom|Hza)Fl!owT4+>#^_g$u*uvoZo7+ z+k~~)`4x!|n;j*N;|nB|ZNH01mJKB@Nd~V-I4#L(zoz3{1KU*Q_tDrLbm(O-NqVnH zQu>Oj&~UwpJn)=_|H^FlU=Y>L`T=UtzcAf{kw7%v^C6V{F)78X^>mPb^ zwny4!>}EQ=Q%##SgX&8BiY&4%XrTRHNWrsp6Ffg4M3QUx7gFS*ZR!#J?^Qar@p!); zffh_(;a`+`(o*TKDAisZxv%gAfhBArfK!r1#&9)Z6t$@4K`QL66U?!NKI7_6Y7TTS zbI<8__>|yD*yEnpybP(Q7`pR$_0HWc))@YO%8KJB89t`e;eWGt&@Ff{5=_bF?Ox@M zxsdx|$Ho0wB~B}smb1+X>ZzfKHGwPqlQ0*t7mQC%CB+z3FNeV(RbYW3&x}mv9`1ql+@v%J})wp;s7+7Zd z83x{=DLd}R&bTqJ8I#~#_*gat9a}Ru(_d=@fLWnQHAJd!c!zMzdS8|{r{y(N6#I%H!X<(btz_L9aCfnK&!+&%}Kk|&lo6TCRxWGFoAHncHRDjG;)FnDFO2k7e91MUOx!6ErBc9`LG4N@ z-anF6`}}G+4u}oi5~BZEVp!4$41>2bV;HikV?h=%qgi3$-@u-}i*j3mEAUL;y?}w% zFvY&ko`)wUaqPv7$^=cTphjz9kQkM_sRI3%u4idZ*qGR<9}shFp^~ep5`!T>B{8|2 z{%eyE*~{}s_3k}a4_A+ttM4quPlpW@1w|*M;Q~6&7f)W(fB!dS9UYKG|NTF{f7LFL zll)8Fr=I^0+Gos3zuL$7jstLCu!P2F#~{y+A%t*mG6$L4fdZ|=YoF&b0g*k6*xxfw z_j8FJQGaX|!#A?ueY;NKLnh?=M_Fi!pZ&*t*CENxfNEX{ODODAl@4k$7c$bwxTUVQw|WfN$l5tcm>S-Ga0lh^Ia7VpEIpKsu){(_+0#%|@02Lf* z#YxT)M<)J?4)Y&q9XdiMWLtHvu?QXYH9ga}e* zRlDUvIhXs8@kzLPaaWW zV!OlrSeC3&WI95-?FpANJ&OduL7rD*SSBj~EJ% zEsAhsA5s{H1fW{9RFHU5Jfv7tPg~D+VHD3A1*!^M4->#)xv`g6P?e}A!x^uE4Fk^b z=EZgyAmpU&4pzSpA|e9<{E!m1RRhGx>ns4t>VQ-*fCj9E1$r+C>)#%X*B46bdi%$ zBe&~mc20L%zvqlcP6Zr25}_HWZ+FP}x|M(R+C>d_dD>L&sv?Xy?Pd>9bGa~whj8xwjbr9H0`FA(go5~1S62>1BLXtv-G6mj^NlUA5;U{b>>k!wxGrW+w-0-5^6 zVQxVx4dUF7q<$GmDM_9IcoJV>p^)E8j(8gcs39waA|8Pvpae-&KYys4N1j*-1&b}! z)L{%})Ky9z0reoL66vNo^T*^IafJ^mYSKNwh#+9}m4>(nEF+)1eM_s~8;dg#vSdVa3Jj#-|p=YOWk z|8zY+w_L~kT#{YYepRx2OXYxO1=}*t(KY|!f+_N+JZ@2VSCbkKDiW82D6OQuTQw3_}4VLSU9Qlo| zV-p8Y``j*RbByKcDp?=!hg5jm9N~e?)GeH_tv`1Mhb7Ji8QCx;5{BzxqzND<-a)z= z)u`^*S6V_yaW*LvD**A@!Ukh5RolwOr|Gs$=P|j%JVJ@G8A%Z6j0QB%dFaU(Ct;Hn zKDQ)=x>$7fA_^Z=cbihk5rC3vK?v$@$h0U$pYDqc4hb)0JcN>yp~I7U@JJ}>?3c!p zd-3k^;jf%DA!;pT=#XB&(Tsn zWR5VCzDT8cP2t9Bj|#W#!`WeTNmRIHi=uF6r7a5fye+EP%PZWeW^F0l*;y#u*%=gW zyR*jc3ZfU4!d+3PDcsC=ShoP;6>e%ggTifVK|$e0)#QNC%VG9PJh@D5A{R5L&&5NTOHU!@0fv%_?&&+<193pLn&jbFQJQEN zHq@&|T!5z*3vCmo!E)BJ0p@GFw}|0kci0S8XAZ>#=sI{!ZqY0m^)PqM4e5Q3uzHsp z5zrCWnVHLZ`y^db_`OD`%LMH;7Kh?s{b1b3oTw#px@gH#?kfB{@`+p;t^HUhUN*4# z!E;Kd#wlP5t#{Hi<&v7ga9j<1=mIVM<)T(RC&BN|j$5vxVR#3BQw{1Qcon&fpcXYf@|q)%n>vEGW<&Po-DHnAfYVq82PJ(BuvImU=a5adjb=j*&|z zIx{+>fL^HCh@&;as510)G{t$@VuL~1U~wT&Omu*vV|FVWB2zF%eI1EltSi2&fYerK~msh*uae(MTXf1gW>$3_2oDN!1A4*l-6hNf8Ei9mV$Z^6GAzKPQKEJZ+{7yAAC zo7|+0Rml89)sH;?ACP-sItzXme)n!=DM(Ijk1_NwbD>5#q+igL8!6$z)}>u-+ozgl$$x1y^XbBz-9$h(&nXmcE62!ESqM>(bsO4)VN4I>LK^p=QMPiPJ?!pY&mKIc^jEtiP~1Zs)FAkwc0USb zHGHsQy74x^?xCAd{!}t3e^m`tImtS?PxO0d9?RF%LSNq0!m51e2-!2^R3r#^$fWsj zU}>Oaobn>usQJh#-}s(!UfKvSl(!L>|D%l{%?I{EUV_CSZ)4!5*Lnb5_jO1~mDbZT zjN80`s4y{&kJo(i91`o@+pwPr28hl^L|Ts|wFF6KS`VOjD}hR7CFE#5+Ws=7^$-mK zamXzX#*mC*wT{73i_&_4PHuUdG#Pv3H-s6r9>&X0rVL1jFk)DJ5+La)R@8pH=$Fig zN5YiuWsBheLNDYZHUyaGG;D(baO-YnQGy-_gQey<1&~hvsZQF0lO(qR87Uvxam*Pa z4GpOcD;+kEyUF=fQVXVKewV2mNHz?0W=jqIqS&Quyr zKkGmU)<-RVdY)RNibBLK6U?9-yvxo+=^XgB9~UWzc)zCV0xb zPB5rSK)^&_g*75`*8<<ZA+D2m~EOmnc|m{OqPj0hAhTBv}n62g!>NPT_>*E67`Bocb3 zH1m1QSAe>kVUF1Cnkewyre9^Z^;8aFYN7z50CZclCaMG|?0lCUrbk3kkRl55?-?_r zhox(1*^2^kq&I?e_^)v(AT~--V1h<1SSw}**(9Qcs)h*!zmk9imy{?40@9vP4zDdD z4#L_3J&{0A;I2s>sRS7Hm_Y#K1p<{ufgt*;1cEICDu|U-69`bp&IYphzVs3`Q*gg!W7hL8(Ayx+*(rN*aSoy+2&rde$a0VYMVQLE3*; zP6!g&8A3%q0I!t#p>}cXFWA5i?OWP}`bp3)I&#n)EQSQ1R+#w7&314iEloPvIxvl* ztA-U?q%t3|IJFw+g`APAgq3gCjJ*98Wx+C)2^pwF%pxtq>qXL3bVZYt6;eSM+9t!I zM#@Yy5lY#2P2o|?1k)l$W(?{HzBZuJ;AVU1f>#~nx0C8%9y`td!+pYtK8y7y8bU5g ze4)cI+SVQufizxITcWB4ZFxOcB+aTWq@DM_AyJz&Mlv(onI zx6VmlB+?w%m9HLUoNyGZp zGNe0QFQ6MK^)Vg(ylQY<>62B#lNI7Lgkc!h+SsN(;kdWE$G|7|QMlFg;8n@Iu@6 z2R?=Bu$%mW9zK~u4`J{FVv#K#)BFEN+I=T#V0eytPPs5~fAtHX%`Ljsd&NP&=F{Js zqwrQI-eiHF;Ut@uG6)@}l2~OYyeTib)CzgPnsFO>X3;w0D4h>u4lR}nnwnb3?R7EL zfv1H`fJBrDu2vONlAu9?yIa5ga`&65{*|pj9QRE7Xc&t*R)y z;YNh{)5!s%DG$=&)zyc%VMP))Afn-Z)*@2kC#k(!X&D}u?30qQG$z0hokKA;jlz}vrv@cW% z!e|m9f$3M84c9ZtYn$&#mH0$+cEl%;c&p zhbRqHcFt90=iI`9(OjXOo1xu-d8%ob2TP?@6wwAHxlJ6rk2u0$z?i)9z(JACoQ9%l zZ>$$K_14!LU5n!ZB7Z^%N3ihtlX?{D44;aN1fP2AiWK2$+-Ih_gW}*|GZe$#Z=h)m zfD*-MHF~vuc{-YitE%LnMYrXEE{|z{C8`(@*z9i5Lc32xXJH7B7H7iVCf>2NQ) zYYakzhTeIA@fWH#jWf98w3w%_+)A@%ed08=0xp1s+u4LZF&rs@rNju$k`d;O5p+L< zhdTlW?}kwbHOMkkm4ON7YfR9rke>T$=G0|%-46g*w?RQzC&sDBY=$t7RDE~=J>gWW z+9d3)#C`7f-<`c8VL~ueF>+#!^|IbNl@%!utFh%+R$jximzF1c`wR4X7UwM77$oJT zUaifcJ*QW@@va6qUI4!u=| zfjF|fo9GcK9^%XL$o`#FkugLd!2+AiAl!o7Y!E zRP8{f(xtG#oV%0inUS)#r3Z=09an;`y5GtKYOM654c5aDFqpAj`P@?1yeV#?kbp5E z;|&56#aOR?{*!wOvbD;f3SM#vR`-oK>liQ$_LiRMlP!4&Jx%5ZqjY{`@8ikf4{}v% zq}N%bkdq~&!cp#Uq*T2z>p@Rz7y%2KlW*;Hor&hH|D#}xIz8xpl`(DF1U07}}4+NQQ5 z*{2Q)h^6pbnqk~%*0|ID*B`Rl(Rlqz=Cy! zGh^M8r$y#LqehROO5uGfh0ac=G}lN0=BaG%N54o@ZhL4lm=y1^6 zsNb(f=A|kR=@UsK-$73r;T&-HOquTmmBlM-2S+{gLhu=s#d|DC(;4@0t`0*W6j_jm z_CSF4A~$bc7~k~=)?CDljF)K{W*1c<7TA@dP6n-7dW?->4sgKVi-NG|?ibY%zi+D} z>(Y-kBO5(7rN`o&nv+?wUsiW_)bo^tC`>L-3b|HYe(l!zMwTWi>KIWpH-S}t@Lb*m zY1nd$22p{PHiX$8`iQcFthMlX0Zu;}`m+r_2?u!~Ur~1D6Xe^73cc$xFo|W290C{h zJawoF@W{{RQIhWgx4t1UjJX%l4cBNQ?|-0M`9GfKePB1g^5Kqrk#(!;vOi>9%%+;_+;t}SGU3{3{y*d!1%#~ zR;gw-px*`bI{yNPwVx#ku-Dr=-?b`zMDr$r_qA8uAMr9$A5dp?envCuL$3qYrn581 znMQfk7VI1BX~@$X?P(WJujSirndm`vM`MlzPVXj5PVZB?Wj|*jZA5l=*22aDV?vDtF%uy#^)f?;a3mL~CMdk;| zG91xShceLFu#Hv>f{O#NIx`U-;M-+|vcL)U{bw+$3XHjNrfAId5$7lC-0;lZICH3Q z_d)9U9Gg*c*W0_*ACLmmqtGrlj*c74$#)^Ci^RPX@xV-o#BdM(uDT&1LRG8Cn*GDS zlIcbR%m?St?8{%ARwbua(|tR`B9&$W-rc>slqw?i@KzByYqQMrpm2vyqd6X%QFm7{3Ewi%re`tbWCcfJ@AFUm+ zo2r+JCC-+BmMFr_ZHuoJnE5oA9;$ImRuXWn0nqa5$3ORybA$<*zCKJt50g9jOCSfY z(kDb)TRgl)`1XK(YfOAQ<+my0Znux^iI1ykDEWkaoS*y{uD^E2PpZkS@uL}AmDv=p zWv^1A`BkN?o9E|R`P|Lf;nKl!0%k9bCRBhEsXzP1dw@OV5E^51vT)yGyg-_iL8f?7 zilzn|E;}&9tkMqh3c|@?b~eS2dQgq7b5~}a575PV+h?*s!@U->6ai~j6POaW}0M{>(*O5HA9D4!R2%pl5@qA${1IJ`0*iZF+wNIBDJ zfacTys{_8dfyi{Ay0J;<)R2R*#Ve>`!9K_A^}OJHy>?`0Ud-{e=nR>HWB!+5Z zi+KPbV1H!5nEuF!a)sQ$5PEb|+R^ovR0{9@`!C7Un;VC>Mn~kMz4CS)Pwp@LFDrUE ziz#muD{++0xb;SG%F}S%ej`dT>iLSiI@d$sC>@82=9_HX7WZn+=CWi9*i2uNZZ^Wx z^m|VimsH8XTZ-O&%e!w|-u>>PvwZW)|9NQn<3I7SCmV;CS3mTr6ZdB;I#8SyoZ*db z`2A2oFFa{2jfzIPYyxV$mH=_AWHHmc25>%dQIqn(ZgkQRgXoGM4z_EstYwfy{EikN z%eBCpYNt&*t=V>T@Hv>N+Ic{AvB`6`r7dUIP=|(W+M4W;W-kz5HFR2QGN*Ire7d@XB-WpuH5C`6NT?hVJoE3$jK)GZ>S!wWV3 zU}Se?r?c@QQg)GWu??CI%xWk$m_oO~SMH12SbMaMB#}Ub7u8;M+*r&bYj7|~^c+9} zi^qG##=UrKdV3Edq$Db-B#e%v$GBf?V8US*V04NNa%Kuuw8x9rRU8zkPe?OvhHDfX ze0O$bM;qMmv4P4r*m1=`E@6xWO@mIFS{LS}llm&DVxC6^0?P*pufW_9ky5hh6W|v7 z^ZxM-PNBq~Y;A2v)s;T}Fgn+XfkUA1S)r zJ6tqZ#>R006Jj-Vo_R^md7+^hrmCSZF$56n{7{|ep!Y_>SlXC9kdAu-hILtXqF;!7KI!x2W%9J)-Xu01$u=L6wu&Ftx%q924)u%vN*p~GQnt5 z_?(ukET%Y$z5bXclgFf(9xQ4FE(odFOo3JQ$GBBinFE zo1x{+Q1a02vZ&<1uOmgw2BaaKydWNJ<;hFR(Kd#Dy9ar6v3^fBM?3WUSZh=u>bH+Z zMx9~{{gd>U-^?HtABZKO;Irijs8x=D;7u-uaEVJ{ZhV%-g;X}2l?j~RaxP*4&mJpd zCKEWP(G$Jvj25A9`np(b5HAdiLjOgf1@;2#Pxg`Fd*OZDpgtn&%6?wpC(*jxJI6jw zioR{?CV~wqEJjsN!6V{sL$O2u8Ck$&ThgMn~~fpCcjK{Na%(;#d) zPY^B<2)iN(mk5MQ1i~en*dfi@6#07TW>8AB5rh4QmcXEWrveQ%h&3hQCzG6nVV?44v~_Bb32aG%ag8z zfmd5#euU$8dC}%xk;-egT?%l+J=zO4Ebd1wo85jtuB38(o(kRZdW!q#FU!@m%{{8$ z=nrz#=PU})mt#Hifalt-+t z%iVMJy5OoOO7xXf{*;(!h8Sw(5li)EYOYdZp2e9wRZ8s1v!{iFEt3HYvv9k$ z^UXlIPQI$wp^xk2J3m~^mYQj=)}x4>@eFpVl$hs|nLJfW%rn#aS2H#-WpYB>F`zj0 zEN%y%FcS~H$?6|W%7WlN@hRRDEyCNvd7EiZi=YeixaMJyKtX=3f`nPYh_Pta90 z@ls`zw%FUo0s;DoW>t$>Rvfx2CpRVKCq)0>E$&oaiyFs6*QO~2@W@kzQmCrrcz>xs zVGqZ=T#_$UJG+4nuRBeUPnEO1>DmE{f~4c>^mVHWwW=0ceg!uv+E*oMfe(;cV0bN& ze)bb$xQIXibWhY%a8xAjnR*J1ab?Iu^-@~Ld&u$xlKSj6ykYs_3rcSohp;qv+CoQ# z@pFCh{x7^*STJzrMQXdt03e^(P`m)d55lk=)Ld_2a99opo)wnX^JS5TIwWVlA z=g>((DFbZUIW*^;Lw2`rH`sfcDOq<{?$WmtK6&>--@AXTRieY}R zEf=>H1Js!#ttN2PkF93a`hW*8 z_5oqBI_}6DNeoZX0(KZt{ujZn7{Ys+BgTvIRgBdF4+~Ktw$+fj=O^OaXx1pkd}7dm zEsQ|&QaBci0Y6|w6Fh#gIZfx?VsS4E5?%aa1jT#5SRhNeSH``Kbs=z3gfW{lkEL4wm{ut!!9`kPFin&I_*;s1nKnjd`MYc*bsarLV6JC=jM({ZK z)Y5@e7O3-at-u8o5O+rh60c!6FXYh^-e!8kiQ^D04oIVc(FXlKmW_~Gc$gYEjrfJU zw}JVKFfY9avDhvYUSvK)2zhgi8qx4*iuH70H#IYYVXa4GNw8b7P5;3kMgdD0mC^m` z%SIM7%F?ca3Ut$PLj8X9cof6-!xSPZ`;q81hcyg68` z7g;QTFP|ZNxqvjJ7uO6p(Z>@IZl;Sxx*Ln_dvP&tR0Px}yurCyMP!a; zmISX`Pb9cdY}J2YV_y8Qi6M`iR_8V7A@bUJVZ;blzUc{!Y+22%=nif`An+&Q^$* zBHWe}wnq^jmlFbv7Zn?&0)r7e_~v@?(9~Qne!?=}i&PyZuAsIIOi@{G9xmd166Vw} zpHvfloK4K{vRz@9LTdE8n7EgYCmlfUG5&)V%sW@P+>W9?-BmUQa2#2=|(AAw-px zUW5lHQll5)k%`pkMR#_nbla8%Oc!Fn@sKkaA`#eSR8(b*%ZBHrL5F1MS)7W0H(GjXggb%YPQ9*ec#b zwNcE|bdR?7?KI1=|FsP*#aLuFFCA_Yx)!#4JxXA7!{_jl^h|%f4SD7m3bu&H7z)Kx zlu;A}SS)iQj{R@Pn+B{qnteG=Uy-tWT6A<9r8JwjAiibAXvP2faaYp*?W~?E-t9K* z0}}kl)zwo;`2G(6eK_@fbu|qcI3igUbY?Qd77qIijhPI-4a#QWQ-ArD@4njei{4{Fi0&miSZgN{3KGy1KU z5bn|Bt?uOAXqKNI{vSxUKS<2favW)sx|0$E`xM9Gak+2{-W0a=-Zr)Xk-_2dybR|B zA?qm9&EW8gtPjIGd7FnR$A`rg^w9Z`ioM@FOzH}SG&l>ltQhh;idh+LTuz-VqtrcM zZNDT<_U$KF_`Y%bHX6Hm1yvi@K?%a9!{bh^;;Ln90ay+L>TEL?0OqE{vPRVCg~ZVC z!?pnTYq8b1RLUGU#5U+J-+b%xE02bbUvzl91u*zg!SUAVUiX!{ZG~!m(^a4Qv%rkE zuPmo4jN9<_VaSHD@^LsCZI@)1he1h)$>9WU1 zQ?`3THMq=ax5tz1koO6$oD?}*HsWi0d?XeZwY=xL*Nt|_tZq0aV0YqutxyJ7^yW+9 z=4!b%-&gPZQX^ylMtij!TB_Wrp}x3Sx&?364)!%@o+(I}D>knb7p@c+tQ=Bj`^uri z0#dO*3Iw}5-ZmP^^3r|=wh8QGJ#2(NGQNcQ%F76bp;wHG?pybdd-oO41H*^O_$IJL zQ#w=3#{e#SFB_q9MMp-g=D!vr*gK5AqN*A79&{mus|9ns`=Z61z92?`Z``~=ra=qn zHynbJ(I+sS`F5z7zn(2LOK?@|&=}M2O>F#GQs4=l#mrE9q8UE25MhwEuScg-JrGQ~ zGTLcDTNG{GFjz#DZeP#OwgNiZuAs0jE93-a&6PtK7FZ=K#o`L6g%StFMTcOrt%r(j zhl=fFT4A}p!-tECP5N-*pFMlJ%n@W1iKuhAkaI-(whe?@ll-isQVD(D`%^|4>nqKqcE$T93w*_ zc?JHE-wogty9j>RzCz!4J5=-!uh2DccW88x_tikTSR0IIVbEP9_ywYet|tUD_JYpF zi(5tn-Td`noFZ?w;h2{vrv+&GFjxvfmy8PbJj4!Y{Jag4AIWxgCqIUGa~HYe`i-j? zrV%}2DRE|GMBP*Mlu{#l?l)^COO5FEQ}xuS5gngMjT+It6RA=Ed2k{%u8=%3k^0NF z(|Egk$~GAXtqjF)=RSAKyHHCsA2%GgR``w74ZjnH6~kWyI_-pM#C*DVvB964tXb@odci00)Xqb1W`BbJx}05)$l(l;<&~8; z@r2B_leM(sSG*!ku;seu0`og^@S>`6AE(9c645%|=<5-8d{X5DDo4nw>HhK(_KPH& z1lJAR4Mr0QO<xhx52~pwhwEh_J4Ezp$`X zbKAFUuF?#K{sa%Oq#?~gUV3x`)0^-Hd%dYG%Lu*6Tj;0LLbgFru{YqiFsp~)8 zFmL0OZP7|l25i}&e|S8{d|z}m=`(alVTL#6Zyg@%1F|@zFo(6g{uAr0=|6}+w+Ep- zss7kn#&}?A&>Tv_d{h$VqmnQ`p(OAoAN}5Ox|R4`sSiO(;JxjYg!!71FmFnN<@HKJ z$Yx5yyeSDx6H*cuqLP4PQc8lAiAsW%3Q7VHn37-_N+m%#`pYTG+sJ|z1D+R(&i?UY z-F_t&#cQYIRL5Dg2=%{tWxVkgCLBJ6@kCH#lzTVrk|v%_u#TJ&%u;V-fzEV^25<23 zTlX_<+0@?&K8{V&G%_4K{E$;qH)en)39@UFK%<_RB%b-WBwZAxaTZIdSVebPp| z#qfp!zR_ZM$es`k z8(iVM3yx}|m=aw{-Y`ZXH~McJBi!nez4F1#;3I~0oHag7k69NV_~zuBIWn=BD^28%dP=E@ zoT!thJE=Ff(|xv{3Myu1$mcWjeE<<@Wa|K zN$Qnc1VU;p>C${R#9Xw@xYk|NyH=p^_Vow-F6bbTY-=?e`u4ZKMV!#Awx^m?v!PR3t|#T0-(tMAfw&R3oW9jmx+kURy~Q3) z=lfnVpia0uew+L?{jTti)*9vzp2E9fjdNXecnYYH#t5UVfeH=-o^y(>VD;uc3uVCaZzbfH08`I5K8}b0=1^6!@Bz`!aNk4TB%%AQB@93N_g|PC|P~a5DT4}|C zuqp1I!V@ZSnmIcshB=vBLndH2g-rSb-Bf4JpjI_0KgwXTp4b86)mcn-5R!I3>t&l3 zHw;={?2;C(QON`qAmY!|a}sIR?0lXknN2-hRzkNpOmrlh2@AjAqHnf3Hr$bSfnps= zu8ogYk9b-$fY1<+OQc4-xpk@)>+wV@jnK+jx1e*dUy|C9G7fKe1%+i>^v z%w&>DU_kb@6WJvz*$ALa*mq=K4U=R7AuBTzHd(@^s3@qYD5&5DDk$z;MMXtGMc`gh zQBe?a_X;X1%Jx4`b@xnX5)|>i?|;AV8|ZYOuIgG(opb8csZ(*1in3A)WN9_0x99^P z8wzLDU6p`&a1o3=fZ8gn8J9rPmdeWQRpnsVk5Qyt7(e>E@4JFT7-7(g*V*t!<#65)Cc##~(-&RhmhgWz!cT z&;kxf6i^^T@Nlk{WyKg?YS2{zkr36EU3)ovk&%9C5jP&wQ4EU@mvuldG?KB0ACBnA zccby<;7;_0KyBa#`%!Z@j1Hq;0mI?2=BoxH#K;I}9ST)t=f{>%;VQAW+oBblrPKat z=0=7G2R0?Q+iD&hHo0X({E8LUj$&+B(SqzOC@`kF&%s55_0L>3xUJ$G5;D|E^rBUG zZQxA0n$reayH)ku@CSL-LUlsUmAtiUGRbZ>F!vN#85 zqT31|9E`*R;2XJrraM3Pulx(-hG7h}9d2s)q4aQs=L$PHe#q{Pjy*@ zm3Mujk6H#8^$flwpMoT0svCL)1TM+_lcp$a zd%AiODk%B}f2^7;;k;E26~TmaXczeDJFTAW4*pPjs8aZV5F%o7I>-Gy<9vheUBrjqQvO0S^5h?L4A{ zoSd@sI*|j?C>SwfNbVP?inX0;I3|$}G9!4i=fL6^5XdWX6q2Zved4!!zWDzBM}9ji z{U6H*xuC|;;7S%01QL@Wdy^~S1w5DP!Z=<4lUPYrJQ26RuZ*r`8=zJ6 z1`tdRn~Fr0m@0yh09O=P|C810k1s@I-K#$4VHVREQJjqpw2c!ufjbq{9pyl`DMYhM?(YQcuo4icr#WJb!OT)|M~QKqeLZ#Oq+KW#9kVIAcupuNrBYCI zN3l6whe*X(6#k=31C@VF{S~hJsmH*)5QWD`Ko~yb!F`du(S)BwX)5S7T0l<* z=fFZ$tCg9rtdvC9FfB06X{`M&69a3s;vIDLydDd?3aj&3tiSS>r87sJZq z@IUNV5vP6&BeMky7dwiHx4;z?7CTL}*_dkc$2nkRg62MV=G2U-)5Q5Ze2ZN*ezbu0 z*5|H73Ntl#U_cSrm1rjY$cgR%nc=>-S$o|s3bm({z|!NQyn`s{evvt6GH7dI24GYU zQAE+S>S33R4DEfWdQ1c_&`-M726fl8Z+rzTp(9=^ZgAozId7mOFC;9XSEr1{merck zg0jgd5PDi*6=;YdyP;r$>*kkfgUY{Tj40e6B!H?`Nd+PN?d0?lzgh8NN}zoP=+WX7GgM8K;gwf7^D`&76`Bg zws2Y=jvturv2!tKfI#LZ7Zlh?%$(Ljy)xC)l8vMoWc zd&$n_wvcizVd3+9TM(T&$Tt6@`{qRotEyZ!L;1b#aLVc5m!J_p$h^L)yo6B0SS6F zi^PV4P{S&7GnY{-0AfOkU0hQU6^k&iT>}ErD9t8Tys@#K+yp&?7hIuna|TRrtgvHa zs&s?P#Y?zihKQl(3mE`h3RR(W6p3ORyIY`eQ>F#I4x{?h5d}9$Pt#$#A@~+75ru~` ziXjSX#Z@I(LWtpWKA*$FXDygql}={Y$mn|gEeLd^NR*w{OcQMyvEk5`S|(bV*+7(J zAjeeoYOFa~#12t(7G1{c>MS8?8|!YW;(R{~bJ#OsJfFzBQ}jGtpM)^X%(5SWV9)^l+4>A;q-Y3~h=`EWscZb`(f8(unG~GAM-t@o+}3kL(8y__n36)Ti!AA|3)8c29E0(XU8%nBqtCsl6Dww zU?-V)!kOFyL`&FfDHd76!jl+Nv0VB$C55qu?`m1CSUs>h-S2NFvk@`TuF~+PsH6?X zsOxe35sZ-zFjzY}MT;q37%@3qpm2d(VARsmMMiCD$b<5OrmRIh+)@{L4eywAtm1t? z)5Dp@tRwa*ZgWIK0thbyI$M9I<>qLr6*|u1wqP*A(Ylb-EN*##4^lvkoBpER0Sx76 z-LeBd=KDf$DMmxRd`5!&?*38Kv$cB_*C>OQ7C0_UFW9kA!StHIsgrgN#Wqk@%^ZUg zd+3wz!ava#``8wso1(gU3a08}#0j*)-+e#}Vh(!JI^A6#8#h7xlp<`jn1@kXS&6#F z*_>H)Ap%To3uhF_hKc~loq@=x^{?9tcfxmHzFR1-%l31OXWxPcvx zmUw{o_q4_mNP#dS1)XQHmBiQZ(wJ`9NC-(_js(A24!44Sh9J+i7=cPpGaP<2i$v@# z4nadelmzmL>}tTXV0Rk0NFRC3fMp>PZZ^um0*E}JN#oi9bBc>rfnAu-5S2y#C?ATd zXD+%^Pnc-VMCt90u<|+P7=92PI3l?4qm-69%jxd?7>#CU)F0RU_r99V?5`?n;My zgBV1{ZWP1JV+$9;BtfY{}zTJb_qKK!+;Hi#!(E0Vekvp z;1|(BexZ_I5EZyiqQ7=@k!PZtz%9x(W-$d2c*P{#z$&iB4V+?jRTd$JYZ#k|{%>Lv zs)NJdu(5{X2SH>p~8J=1hc*Ba$ndJ`gaku@-|CN2mV45g8UTnJRoD~ky=*V;m|=wFy}=CdFn6s-d@zHzv56}krdiT zOr*>S!Q(QL#eu=w^rL{am=-aGs*>eJ9U4r<3#y>4pXS)@k13Nj zkHqkXM0KG$dkds?_fncJAQNNVT;ym{-3QNN#`H?8^*Ptn+%=eNNwG}n|Lq?7pE*nU zPdX_O8Z0KX&~;-R5YSDCj@1x^=6XW&qjfy`UWGm*_)3*TAUAPVS=3?dAZAF;r|dYC zN3aoivVeIg2Hkdvb8cu?v6G%A?DWE9!)dOhhtxG=z9P`X?v59>C6?|K|G6A583&TU zKe8tCDKu4ym6h&T?T|6K#BiI2;oh5i2&xiLpU{a*D&}5T7JD^*wqTwnsVog#0$P!FM2P%qF1KQdOI zgu0P?yrjYy2`ZBClbJNd1$88BF?PX(LX`3`H zmS`Hh(bP~?s}w3~l%ZHMib|_ob5WyMKsLUn&f#eo_!>JRLq)Z8Hx*WIZx)E9I`1J; zND#>)hTb#8ew#Lq(}m}F&?b~i63kgD3yT~NC4LB)Fc}!oft*zRl{a#O`k&r zpLW_58%WQyN?S&;r3|EV^UR#Nn;Fk{3YK?HyXM&CkqWFtgJ zGG9i5e)AQvRaZtaXdB*rT9LauRx3qQGcEU5q=;CI5a?h|N>mVAHt=P4j1@$|07l~o z+91xV+dQ&%hzhf=kopzs5bR3k)?pj*hMwf1euenw=B!_+Un4meKvpz`M?gR$ zOl~YXgI-|sGBn9lRv9hgmQaVP>!*=!xQr1dipDx?q*(048a0i0uuKBS_Sl&dhxPN~ zA{;5iX_y`HgNcfxJUpOm>pGle@U4K@Sy#)M+?E)tQQ>Bst=ctG>|0 zDxk-KQ7SCrF@}YWurm`0{=sM3k{aZFMpJraNoY>73?SA<#E0%h=Uv=7PoD9TrMswx zz~hZvD0bUS*3TQn8^aFd7N;dTWM~h8LDA$(H;{}Ym}^#z-0;X`P?;b#LN*ANNshE# z8mKAOBnYXn7R4Qi4~op=@*+sCp|BR8M0A99SaZZDVC>YxLIUFR+?5E52NCg-ID;G8 zp0P?17ey7blP6oENO+*O*QuXu84bMT`DoFY)oH04sbRaPcCsZ>6K9#>q;C`hsfm*< zd3rfM6A5akp)o0*h89c;XK82!!;W~ZVB%y;?9$Uy1Z5G2pj%C+qd9%hUYbs}6vgH2 zn6-dDOK%0eEu!hb&*^SKquOZ9ue8H0-6y12?eGI~Vv%sRShb(RHws9_kBCjW z@ICLsIYG4Rln`&UK4N)<0SX_4%|f($#pym%@keaciFQBpfq$y|nU5}`ok_I&$PI7` zeMAqscLRWX8)+e*9035&Rg85O>lf49_Z*L+bwsrLz9%cNX-Ert>t;MhVKNMzHi9<4 zOB`(PUicpG_gUNz9N@z<7T0N(63iL|5Ai`m79k$;bY674SWJUW z1Svjlq@j2SQhUMr#Nollp?F3dhbfL2AmTeFTtu)6JEI_CWjk=`o`ztdrFa@2VNy<0 zL>yFphHW9y*LPH`pz%NlVGLIlW1L({vtdy~!yr_nHa5OuX$VUhb{p3_>}br|VjJx8 zNsB}yCFyPC{+UA+gQ;q;Y6PERFp64Bt<7U?aHv_cY13(^9L1yMqQud1nmY>{@)bu8 z)y9@lW6N!Aky-g7B*zY|nu&{V(SyOaTDQ5+Fi#+_1QEX$9>Kk^K#Jsntenqt$ncp? z8r_N;$_RnYI5vuYDzGZWuS2PdB5Wg4IEfYDIV9R*>AkA3DfZStrY^z>v5>7{*e+Hx zt0M)-tO+SBqH9c@j!8{;rvse~swt4cAeoxcP;k~{rDU$sE&w_Tvo+Hbw9M4H*Dbkg ztCIVHyKrz~oQ#OD2jUG~bKpA+4)TAu6DL*UPn--hCfZcO;)u8KI7Toi>K_GzM4mS| zJWbeq%DsWbazEIUP{L{f0n7b_b{H5SO_oAI4h9QbAIrb&Z40_sOkrRlhL1pdTWYHt zRf^3;(rGyG6p@ij)$Hp}M~3oL=79z;qM;K7Q3Z)h7)97uZsT#~Xkz#Nf3vMXwYeJt zi-VXz;4Nw@PH|Ns-cUxAr=dr1+_-zqVMGsZ>X1<(_r@x(P+0d?^P8m#>>1tSgijPz zvwJ}o$T&9)60W=QEMzJNmb+25D8Lx3v8as^IS$V;_?}Z-baSL=J{8;j zaC8?(K`a($RR?*5pz=KI2bIUl7KK$zx-btGi6Va)xuY>4!y+ww%i7}eDAh?cM=>He zM60FBF%{!pL4s{qmoa_)jUh3LgPDaxXkf4q7-Nw>&#*&{#N1xcESj1~aTa%@fV)uz zmdoHz2^&l>VA0wr2>C_~bo^+U0Hew{h!kI=4Nx+aA2tO11dG>PiiU3`QFicor*ZhE zd#xy_v0EJRra6-^siIJd2IQQEa~*=zTk{S>eaU^;Y*APX7@;DfZq$9s1`9HI9;zcS zl+sUIw4SaTFov?AONur|`B%$PA~&~c++3={&21u*^#ZxMAc)}Rq=s4^s$}Nq%+KKI zd=NZcs>0L3UQIk*GV^rFZQ|*YJH*qasyrR6I>^%{x5m>4#Ly%VxnBTJ2U(W>l&52& zfLU5~o=$_t^MsJFD<}oJi24RZ1Hu6Dlvrx0W071eMxLR$(A&>ijcwAaL^#{j{7IPD zrkyNPB2TOu1eR%cgI#iqCJ+-o4mle$*d+{6tDM9dE1}24wGC`IRN4 zJgllTKn3unu0z8UJYRwiq3toAVljeDnqG99risVhtce#Wt7+mXk0TQ1mh=|^1>1D@ zVO@a^2eFwg^Jqm{jOXLLtq(9^Vp73TSMO zPyM{)t~(va+@n}n>#kamU^zqSvt;@-3fYr`h^7@(C!Ai8!I);hvhz%o5-ALZH2e(x z1+gVRL?>g0&jlH6Dy3R$S@I&kFA-6772dg*Pa%jdDIz!EsKw7A5P2St?C0z<^aJO#7m^ytLs`iqy|VFnvLwOh;5BNk#Ry?vh#r7$6P}I4 zG>9EGACf~1@L@g<@+3D$p7zFI<`U;1nX1HDQ<5rW!w3|d zlG|^@{7Hf(GFJ%s4&!YB)*|@`-49<11t+b}jlQ)D86XnIBItJLlsKuf!qGmuiAR$G z5x7K**}tFqChmdY8_etZuNuNS1j2&70rgY|?bHRTas)6#tThX(HcALg+g>Cr;Kf-H zIubx(jU5)SqeA=PGZhw8z+iC%d6W?NnT)CYkYS8}z)Za!wQH`2C0LILy&lRR*Mqon zzIsG(P|?Ra>Jf1f_2AG0cSd6q8*vS2S`KL|ntxkVwV;7(RuNsNGamoJlLWTfw?R3x!pJ zj$+2{UWq2^QCcYc+6dQM!m|Wc{&oDTl|lRyBxjW?AFmERAg0MMZU>b>ZMk7raW4?-(F&lj zEwK=m!w+7^Xj4Obc)=?|>*td5hX|`pw=Ol1Q_L~&ja%juQ-r~4Xv+ZLQ@tI-gFVwnr?+? zI&vJn>7WLJP#{_i?o&iXlh_r+Wp5$25K!H>tk%J zz>-N3h6URS!vP!bBt;tqSdbRx{bKV~^}SZq4We~0g21};PoS{YW}>uy1jbvzIUtH^ z_)o14Vr!paEK>Iv4k@~A#Lz5>3wzj$x`gzRv{z(?GvwXNv^NmNHEB1NVHS4;bet%QsRV+wTeJ<% z7WbAvF9Bucpsxr?xU~WUa?6;cFS`!K5O3he$n@sn^AU8;PZSVBc0Y=K;RKx!jMhI_F=NsG^gBNd#7az%rMV}m)9B`paxLH%TDxh9RxZy%uK`az1A5F<4CWyoo zA&MaYx-LeB?z=#+Uq)GNAbNY@wP=wD1acu+idvI0AWW2a!=+5=EV zFG~&Kkn12(f}M#qP%1h3V0_|SUE>@ReM6LSfA=Zy)6H`h!mKg4R)#nN1DRfE#|)95 zV?fLY5Au80r)|gxaB6>c!e*{Bc(L@t>TYW)J%El@& zoI|P956Mox2Wl&B9Oo&vn!>v$!WgL%U5wkjx$EQLK7z}-N2OBiK$QdTm$<4pHBpkf zNSMBW-$Q0}AQ&sYB4T(HD?Q{`k?grw-cKBbDI@d`s-2V)txJ?~e z6yx>L^AR5)&>s^Ycw_nB7A7~GudshgDbG{b|1m<@UY6(Siq(x=lYo!@ILjQ>vIG=m zzTe$)dXOJnDq9efp6&E#${bZ$f-mNe490m?QRpMaVi?7Q9g(07nGrx8d1zCT0BJo0 z=(d<&iZMpZSon&fx`J-AR7B(0f=jo1$&v69Gh*R556(eZkVrlFK|Q!V5S1|P*nw}u9Yr9>XGosylkftkQk;Nf1ufa>DW{e&>=(G}dzhiYto zt);IQ8PRk$Y>;4K0lbcOOju8(9TU1HDDy+ca^EVxWNDxa+%06ScUS_*S{Rg8tuNg7 z=;@1C8wj|JSeUzL5?K{D5hi&|6TLzu(+9T3l?{Om1JZC4AqDsPYl4B{CL;7+2nMK3 z)iyLFuE#@*?Wn=?Pg(~ESLJ9MhYxG8V$FFAt5EY&yU$oGuM#J?)c(i881occv2axV z>+rYLmfBlF58!^rt~HFirI1zl(biyL&%e~J2AA4H8CPFw4~6~dQv1_(W2t@1XKX8U z)P*dytJRj;pRre4YFDc+wO6%{pvBy}vphqReTBsjy~~b&eZDS(5=L+IDshZ13-!rg z4rc3{KdRPcb=hUBEqa&L4R+ayU}4YSWtRlIER;p{E(?YIX_wsvI|zkxjC<=hf1%4R zsn%tyE!Dd0lB!+yi$z#EyCwG)a^~;5qPy4!2lc+N2HZ=3()$b6AX?yKdQa3C2raBg zp~k?De-PH-55llw!BW+jCNS%~VNhY6H|&opah@>JAB(v`h$oOVg?32LV=<@Z3XFTD z(QIPEjTg(ri}rBk0VUFC2+)GzDzIy&I3xC(VFp9L?{U_ph>v&T;HPUA$I77L)}>JYjP2sH%Te3(uxjcr3M*CsJEy ztye9x))!$)3L9$$_8(mCfY40cT?9<1Es(JJi7kd^bE5c~*NbfFN^C5|)_l!xA*!(u zWfD}MLBeQ6sRTd8YCTfGG){ahGcoJc+47YvLMZpkVKft-yJmGx>0e}n8`}igaJsHq znRo;XXjl%iO|ayGMSazRlfSfBv(aiS10q==A0oOUa+hGft}4L-XqA3NVy#(EJyD~2 z3KNx)C2c2(UP57jmqsQ++GZvYLNw7EM!j^Z4y3^i>R3y6)F1-N zaI6~P;x>EUaK$K4Dbv|R6r3q6j?)hLQmtq)z<4QNvp6maM|~g!vo`05O>;QULl-WZ z%bAO+EQ+f&V$)n?2m`^nnB-WPn9RhoVdn=4g>Ve<7pGpp3kG)eV|4?okA`|e^e4So zva_&f7~KR}CYTD-z`ULXp`cX-)lp+Z5$PNh(#XO#!q$pfcBUw^M<4)8u6slq+O&2- zkk(Yl&`6Zd8@2D8>4GF`reaVMppi5+9nI}^jhwasa`d21;I4_a z0R6&y)L9{P1sGH6Qfy<9d9%aKglfTwGAJefD!PRZ`M2>X&x^AnD&pDa&57g3p?Jgr zFM6wU8*E$E!+_vIxP7v}l{`H3Z-JYShV!RMExBa%z%iTq=5 zu?-R{{JzBEvOG_5VxGr0xh&D=om}V-_-2XkCl>~$ROBY)m6gONd-L+rQ#yCf&+Y8Z z%S`E%i1O!qbEf*sO5>9gk`mG~#25Kqk&WaD1iU5X0X0yj`YUn+K2Kgioq^QUvgux5 zL2=m(;GqMW-3<4N&|{-i=6TyA{u+x5e^ExYXQ98m*fUElEGaMcmY}CSfx@y<)#nXV_)5{|o>C19 z(D~k(<=(u2H@|q+MRvTW7~PtPt}HB_to6x9C`WIU<246d{NsL+3FnphyoqJy{zQ)t z9g3t=6_?Z(>0N~jH0f`pi}EIxl;w*8FGIR4@9hyt6L$knMEMDO{J=rcfCZkyVsCzy zI;mZ;w;-T)>!$h&Cr=5qpA@C4e1nil9iz0Ddr7j=JQX9PfEy0 zNY=-2unQ!M%_R*-zWtatE(7k5XVOfg?;pe1VBIbfr*vd3F3i*E$5Q0m3+e4pz~pc@ z;3I%J1`YT)VA5j){tYncxdHzUnB_F!Gk{6o4H&0f2%2EPO~TS^4mch02bTp@6jIHd z74Z5oU_JSTrNEEEd<{#o)Q+k#VpX5rCDq0^3vh>sD3uiY{TPI5L7}%8NGGk)!{el5`Mp$n%UeDoTq=%Vw0KG`>PlX@K~y2Bv_Ks2*IU zWkvv4fFwm|-`2sMZH1`8vKwJt5f+WGi^nR#9|UViPEIrMN(?Cu?lDMzG}7OM_GLZT z#vD()GH@B;TlH`Q{uX$~_?dcK#_x;^@{{;+oL~t~>Z*aFeY@fLa$Lq3y#mkOaq-9a zZd};}@FUy_4vK%sThvQV5@q& zk^gHne1${fnLCjYWe@{?I}A2>oe}wkAx{T>3$@FBW5 zsm_?%7boNKU8CZo)Ts9e2ndZqk@v%MAkb74`9eC8n!*4=b4I4LP9IVmNnQ&MVDT2gvaMp9-{=j5d1 z%WTa%KbncYYDY;Whr%s(xJEe6> z@08Igvs35Pq}1fpl+;eCsi|qH>8Tm1nW>%AlG2jXQqnr5rKY8&rKe@2Wu|pbPfAZt zPf72To|=v+RC-2wW_st0q>SW@l#EUpsTpY*=@}UrnHilklQNStQ!+bcre>yPre|hk zW@dKoj3RbM_MMS*XMENfuP(m*3Ik;xQ2f+{bV2YjZDYGFuH}-3pnX2T#rdZJe}w0; zCin|HH#WiFg~7+e;NJmrz9mZrB5HCl#F}!S7n2%oT9TzEs3WFO=XQ|iYi}&Xj#iw9qsxi^LGL#~Z;=VtzqO=TiS4^^VN<8HnU)_&76rv2X zadpSViibW?3;mp%6=DFF`Y}C56RCc*k~aZUx_lL_HodT{!ms*EJU&pmvQllr>z(P% zs}K_uOzBGsODV#cXLeWg@RXK9u*C2wge-}1jCRo|WHx2;YQb8Aig~m-LOwdpNFh8* zztVi~OcFy*6mrWdO7l_wi=FfTNxM!*1DE;Gpm|fgc}1LH6^h)ulKf6mJ-MK09aVps zItj3PxtcUd>R87m<)XbQGm#(ZRW{eit4)ubOkn+6ZWJN`gcr9rp`6Tpu!>Bdk3_z}Jm*DwP35gv|<{FXn$BXNx)fFEJX=3@xp zN0{`6?aUwH@wg@sz>n}mTvrjmk1*>#2^W8aJ-7^61HwYUGkG%b1NxJaK~(Sf3n!bp zXi8ZHCQ6_)g{6f7ZDa)LM}^-r*~{tT3=h;RK5w445F$WMVSb4>Fr_R%r_@v8^=mV& z!hG~dA)b9YERZot?b=N(RJ*F3TGVq18ISm9%nkDJ3>`#8UO??#Q7Y&~59BtK;}TZPCY9%%+>pTI zQW%h=11KBo{SVxUUx#t0jQTz9cHB?k&T(}Hcg_VP>$`*u+6;Hf#x=Go25~?S_mr?s zdV?8NtFaNXvW$BEwb)vFCmZdJGVMa%obOT6y0EmWw(P}x6JCnG4P0UlS!4S^8MG4N zY`^k~T+HipK=aGP(d$oIOY8p&E2|b)D`Se$QvMSt1Lw#WmPQcM{$lj-mXV~%4P9bh zR%6>h*|izrY#Y;PhFaR6@5Pj(FfbV^KW+T|NduWj#9#kHD|TwIu~43-ja))U0D_eR z{vH}!7nXVrqw=CZtk$ScqHNShK*x?jp5xQVi$ABB!~BA?sewfNoo~ZRIkK@!@OXVy z0zA^B$uv4sNDEo&lZe|yuMY^WUWzcnQS)0Z*ASXZKOn6(NXuQ!*$6c6A&w$#Kp5vi zQ*e>a`SOY;WvQTh&^wc6$3j0xy4R za-klA`1$yb=@|J`;FA(S)2%JwQ-=4A!e51QPN&Bh9veS-!A#!BQO7H&CY; z5bh?c82P!Jl%wx;Tnd)klvcc+R===Sy(%KS` zMSe{aU=n&bF#(2P^$<1JF)&BRK*&Urw6B@AhGNu}dMDNuH_UNDdH91e7uT-JJwDD= z^qxpNaTVbz##Mr=6jvFpa$M7J`EWtxk|5eh9Bb2Y&A>Gi7p9wHZpSe< z2iII&)Pu~&Rf%f>u7$W3;aZGq39f5!U5jfeuIq4JkLw0p%W&O@>n2>wadDh+Y~6zE zR$MD_-G=LST!VyEs^*t@{iSUMaqwbQRA*y0XSa;XBGa0 zU|UgEW&4EM);v|x(kbwjm8kuP4;h>giWQn5a^j&aIBH85Dl|wyZxLT7s{L3T^K%$6 zNB+ZFy2PAaM-lxdP)=HfaE?hUt~+tvg=;mgHMs7^MLKgYE|fgaS16X?+O;PpqgjC4 zz`Xn}D3#Y(F4gG{&>FN?^*|{>TCNV{sCGfvD1^t$9%aJf?qQbCRkArKVlB3Guk zejX4RYRlx{Tyd&P==+bM4&+mF0Q2|%_xkP&C`WIU<3GK=TQ9ci`mU&JXnnuhdV6vT zj1$r`GCRAod-UwpyHDT#0|pKnJoL(8!$*u9HG0h0ah}|~d~dYJ#~w!1V;X9 zKCvm{uf@e5no3%S>plYb5xyVS0|f9R{2;D}2;fJ!6E0nmIq5>y?bq`|{P=Aet_`^A z;UZ1E4A;ZB7{==nTqbGwe`Q_&!&X(-^$oZ`%F-B@foH?<%<>xH-{P5g`FHw}=u=Jv z^_2=;S$S)Xo^D2Cv_aaB;@XIdKeo>%T#w;;99K4sT8dy5g;gb?PAc$#WokOqK*Gfj z-x?H~)wO!IHkwY4vH|5A1Ew6%9M>>mIz@pIygRs}4uMB3jpcg+9_Y04JmhRMyy{f0 zoI$jNoK+ZLWw0g|NHEeQuWV?_gD_=tBb@kUGr?>lb9}<&1xEZQ`D}t;!E>w$K8xqN zCYbc5g$d?*p}Ee4vd8KyVh|LkBi5(V{#oZVhqPdtq0_PD(HDQRiRo&Dat@|3jnqU4dfEez&Emv>!+;QDWq)8D+Ts+``c<9~;oo`-Un<@Bv+JIV#TUc~hhu9tEB9UiNh{2Ef4 zB<)9@{g5WaG$CjHowQBjpOh4@)y1&UG~gop3;2i^D2ChX8g2*^y@-HA+1-aR@4Ts|7m=iif^~!;!no44%aIL@FPrFwR(a5 z-r|CULVpf~Ctr?O2=@49wJY_`$U)NWNB9b6)RW>~xj4Zs;kU1Dp13c5{|<5YzWp}u zYR+0qHtt_d@0-EJzm-Km;Rsb zw8THt^Xa=knAKzEH$88*wCmX62DMk3lJeY%O(S|)7N0#j?St!jEvVZv@yoW`dd0r~ z#qWocPxSg`+1k!yFK^$w)n>=iV>jmXZdhu2=9w+G_FnTw@3_w%+SOZ`bKslR`_A@W zIB2KOlijJ$XX>oIztx@6r)lBn9$OmT)8~nG4LZED=U;uA_g{13+JzB)ADr;o5AE~1 z_5HGOlZ@Xd`ucj-9^O9l=ZE^v+STZn_nti1ci`N;V|N^@)9;=MJ&r$D)Vtq~P9wg1 z&_1W%gtbpU)pqd{{rtPutxf*?^M1>--gL~ns%ihb^5?x?tLc^fx1ITT`oBC&`X|l3 ztzMt!pYMNp>Bq&zPap5U=Z$(xul=;u0Qc#}Q@?IJZotFch8{^Cx?;d3gN}~7Jb(Lu z(Ub1}VbI*)29#{EmaaRPII!nWuO2=6b>6`Gn}2G4)zMW0o31H;u%h|ifj4^!t}XN_ zgMR+)im#O+nS*>Mmw&cBw{%dSQ=MzQaOS>2jc4!rXZsi5A9U}ktloi*F@y8(J^JXb z8?py?+xhW(&nL_n9Nqc0Wre?Q9K62A$G-C{nq-QY#W-}(|Yo*`aci7dZg`Hd3=W}OGds{=hK;!uH4ljbyVX!R$e(f?UhFl zZGGd);#bDJBJiJBu&WVxF*B#M% zVCQFUn9^rN!oAnt_gMDa5p!Q#*?LpICr3;;@mp!5gI|pBPup^Ri(8wG{Hpg;4SxFQ z%8~aqnso5kuxm!X*<$89kAC^W$SW?nEdQa6qYr;|_p#b%?i{`B ziC2GafBC-A$0m02KRw7grYKEX5$owZ=JjmpMBzWm#=OwJGV{ng_mAn2x^~BhyAF&= zbN8KD=Txn+ue6K*_L~Vk#{S?t`C#kRnPWFCpI$rB{n*&ujUOp|@v9?auUK1e`*-&> z95-ZFzQ-apUjrlDKls z^`piwi|X=^9`Vb@-|@xv;r-XVGQOMo>{k<%U&asG@L62y;>#u^_1kmX+7!=(IUl)S z=sW+m3FC*={ieyGHz&;U5BOL4ThheQLqD+(I-5GN1jVsdhK*s>;12`@tipIR>F1u z37(mAuXr(S*Ug^yCM@sRBWs5zI`yev-`RD_bFH**_Tq`jxerT=@BI8}Z|;8S^K&iY zR_88OkL3*id0*~>UwxCYA;p%r_rA}24V#~p_sWvTi*8+Bo|iItb?=!^J&<>{d-j-7 zhd;=>cS`$T(>l8Hcf9&jphrf}{FzTB4Xf8{R{p5NTMNeC{&;?Rbo$|6U;He;$MPNr zDmFFp*7J`T{LX>F-tTsva4qh$$otiA$@RUBw|G4>=Z$-*<@erh&gCcHdnc|Sde4*3 z?p`{&VEKZaqvxjHSn%8C7rsA~`)Wbv-BZ80KllS+(^zoZccqU)h z{JU{aPP={bx2@Ly(4yAv$(PJHmT>rBR*-l8ded!Eg`r{%gS zUfgf0-#)gl`uY5+jt4U0Q;$70HR{GCnew+^O&xgrp=(}g z-LfcYRJ#+aW(+TSp(rJJ{>Y_8kJ#s*`DE%#MIG|Xu6oPzQ&CzsXZpO?+7>U2zUj4z zPfslV=g!Q#o?mfG@s8hibpJYaXK}`slPfN@oGHH4zdCDFY)Z+uL$cp^^x=Y%FFw2F zg$5(ml$7u3l6KS6|161q_4-$zh_#n4_r-T=@kp1_J13t$G_3!$()kNc-SOp`2TKom zexDm_{iw9#+^yfQJmM;wzNV-{R?}W(D;L?HapcY}>sGhbv|e*Jm+fv)^7id(j+V`z zu&Z+GYmLkIcAmR$WRs!gJ(jNN`K`LRe8eNK)o;`OALU=KOkG{(KUQAnW9gOF8!nwT zs-8PJ@s=^uipJi2=J{uDn)ccIvp-2qd2QOk8yh!x_3W?HhW~KyYTwy--~L~Etc!R# z*LU5CmDjyddWY}Pn^OfI>_>3yiOVw+>e;In(5uK3~2dPmME-&DMJcHpLY z+3NIVm#-buV)%&ZiGz22|M}$WroZ5sJ@wElFHf&OciDZHeel!tacdH9n7+5&jPgaN z;zuYsGuouQxTaq2tur>y`|hi-TR%L@!PAe*j@ilnYrbs5$&@co;>rx zq(`1wdh6XYCtuaSJZ;XuX3D=z*qz-pV%EY=f%%sl={hTU?S{mGF}_*dcHPqD-W3nc z8Z>)G)Xvm{v&uJKUvPSDo!QGq$5(cV>^-||Q|6@WZ@7B4xA@XI=^0PVp5LcqQ|pq? zXJ0k;*VtZPG`aetJF?Qg-8=N^e|5Xg@`bYG>fSeAw&+mL=dT`^dFlyg(ebNu?6dFL zv!vCWU#@M`JA3`uIlqtCTXdac#hlCzukCm=YWtj>C;VGR#r!tsGL}UHg;`!^~U*Y zcYV9$r`?~{BYZVrt_2t{_kc1yv=F2%PF3i?Ty$=M&wxUAZC4sT zyold~i}>b$`;-4PJ>ZHhb!0=kX}Y_+(C(bG8}K^7Tvsq)?)NamF9W7d%?RHEILQQa z53xD^cECWzVEkQxId3+;e*-Y_#DJ+E?qY&l0ygKD1ekMoBR;%=h&|l~+!Zj>lmFzW z&Cn6?B7P|HfBnyBj|ot4Q2%d+yP4os2{CS0iCRPi>du-MQ!v-f2n@OaA+a*ykbpCV+G zv=!fX#P?s}P8zQ7iV7wNZI;#wy2TO9&Dkcw@}k~lVy%uwcQD8*E`|;^TeI`b$-y4* zUNfg)wT^ap0Nkuk4?`jmri#e(HC%@7b~~Q?Xwx*3JD;uum zjaR|nU(>JsvHt#V;8eA}s6oR9l(bn7ZQt4*cqTsl=~lG#R9%f3`j)mplnsouUcfW; zmu6Ts!PJ#=O~VK$+}Z>aryH4I8MsP4v=N_hGZSn9%=H-~oN%lOb^%TZgNbWK{JIF| zx{U$X0c;Mh7Y1|RsX4q6U~~F}&Ebu8_(I2MmI^!mp5gm6X%vZ(i9JvCdbtGOHOiZa z^c$GU-(Am7$75~lrhisRF2v@J5Yr3%-hhe%57pDj$DQRZ!5yDUz4Rd#@Xo|WD9vD0 zEPs2{^!QvXbO%e4AT7l=v@J05(x?K)HQvYIeR$eK&b!ys5WowxU8a6c1^%v6glecC zq^J#hYj{swCGMKdhzygbe!}KyNXR$?z zD`9bEGPn|^R$2tj;Hs*PB%dl zEmE~D{u^raSIU6epS$8O*8kjpd;L#iJa5G{2UpjAxccJi4b*(HKzS2P zr5)ZyBQAF#cEvOM8BHMd#WSWOu(iP63z%e&0L(r&($vZV{)G1|gT8xOC|%~@9n;kF z5e|e_;2rtIB$SPJU0jwn;ysqhrJ#%muPs~gj(U%sxRam4RhH(?Kq#8jNeA$rJ!^zh zF5nvNB)mI`F!Ev}EVrx-N;3Ep!n=Bds)kX62Bn*ng!e2%M-@5|JYyOg{N6}MQ$&T{ zPe44oDc*k=BQ*C<(0XAV9Gz9}fxb`NkvtA8==#&)`lRz8D*WMng0k1JfLYrd^;wm@k<&#fyFDC^vR;3(-F- zYmj@)MK&gP^a^ifpas_<{~jp&I$UhG>g@tA1u$w($kPoIuzTQg(SnlnIpVXwd3}NF zOI%;!;(Pu$roP5y(C7>B0EX>Z*mofGos#qozBO>_TReY<>)*JDQ-lqis;=KIfKItg z>?bONbttu`W!UzqS$_!W-d9nMoewkMRL!f-_QEi<5Sy#GbKjT+(}L7^)^>VupAPnm zAaXFN8QR`yn#pqaa{wE&bQ5f`Bg&uaD?@-)1h;xuR-G_`WuF- zw5-LnzErrP(;ukJ$5wtH3Tt{p#}R=lZ~?y05AAHlT)Y%ECVHWmuu!%Sz_bMI#{Oa7 z@cJIt5I?8tRr~W8-qV^78@#y*yh>jjN0>TP<9TX3qlMM>#=GcY8eMIOJM++|CZIKi z1=x;GdpOb=bxK)Tkt#OOc(gaja5BQFzchJN6pdKe!^wOs}q3P9)_IpGoF75d-p3~fkZu^peQR5@eRB`smC?o-vEc~ z3VW3m3?Ul!;|?_q=SCCf#sP~)5aotc1oRnMPavHBD7FNRCTEbF~SM1&9X3LF|5+#TIhpb!%4 z&2^d((qlU#$7n%CabJ&r3cL#lr_|^Y5w2N$o*6-- zx>h4x!so(Jv~N>f6r_L0-Dvkyc&0w$3@-Egvw$y>Op5In1to!C88)K~y^-D&;1>1O zeN<+A-=QW-X4Z*h8GFpenf4D4QB1DB&Q`M z_KRX8nb7EoSXrZ7LdJ06PTfcw+*!}IxU+0+(7}{7dc)FrB*a(28Ym#6{N{c>hd5%4 z;%i81Z^x1c*x0;^kRzMhH#pgUpOmANP?n`_MjYWn}N7$mMJb&ZUBBFEy@k; zM{9CJ8t$yCk;h;?556NW&d2o?1PfPNNm_f2EOpPWEmhvWN|HYP*(SYtR~O2UT-Vu! zYaawl6$x~YQKZVnpnUPkvh?b=PHAP@3|K6d{dmo#h=skE$2Kk*?=Fe|o&!h!$t|b8 zod564(tqEv{=*Z(hOChh6uirq-y!*4^|%hcj<{e#}4NFI^1U9Yp~}EiUTQ7 z9MKOemZ&GsO3cS@MQo4u`KwDc`3czh9iU$v>^U8y>F~I(mH1p^?7~v`91_uk@qK>~ zzr0wBZln!#J%8FLNrLXiCkHY?yZi)x-kuhu+;`rteW;9q3rz=>qDTjhkg6tRl&9jn z=|Hoe06|le^B`pA4DZ(`r)R%D{YDJOH^%qV_3sV%MZg>%224K6acjUtbhfPlQ#Wje zsS7s4N*HVngWZ71?~U}v0XFBC4>%p+{2l7mq?@w0$rrDj+D&#Ky}9GAoej^HKYH-g zegO|Txb>yePs}*?%c+9`j()GmeeC8@FV#7HRKQ(N&tI{r<-Qv`p8ih2i|!hk+-|~^ z@AN)>Lcq_w@zAE_8>ijvIel8dZ{6_yr8DBzd~@|_E6I{nGJIzDoBQp4?Do^q0v^5R z*@?62c3t?y>3Rar7Lpj<@$-v$pA(Q~`g!u|vaEQK`q$&U6*Lcq7b(fWlw_ZHo{_RItUFMcq0ZmZ&rA3c92U%Fw&mnr+xqNc0S`}l zC3Vudt#4d$cA0=jwt4>MWlIL%KJM&F0lzq}eR-=Hza0vkT_fP-TRz`%?~8{XT7LEc z0k51JSh_do;gb)a-6-I_t2ZpawY=v)wx4}kz(w_IzkKqz*My@9=k^Quom>2cceZNvNWXIj1zhV*Y0;0* zTze+(+))94+oaAd`Hs)G&O7&=fOjt)S{Au`>#{q~oe*$GZ{s6l8b0^Wr_P-gF#8;o zc+H-3AtLK~aQ|0do#SkVzub+le>)TISV_IH2R?G;$(fz6SkOXn8t~=%5pP_*=Z?VA3^`4Y@%ZCe%4djo?uOEFN^Iz|lh*fs11H|3cuua<`_b$~vv9>+6eSh+O zdH=po_pG{M)2?Ni-cL5aD7cs;edK+#$xV)(iw@{ssqSh%E&I3T6Sx1Udzl*ZWz&Z` z77n^4MkWV>ynJJMll<@H58KMc0`5QK*j;ztUvgizOlpMv`$cn~zItZlj}zoM0-n+6 z<5}yDj(B>8yjZ}mzP@_=E!T{{<`#LGfV)k+eb?*5({^o?R|@#4wRikJ;pq;y?UdID z__Lg~cfNRU?@vCJ9}sZsWt~4teRcJNr{#?To^bD~`+axq_@#m6X#wwf>FW{0Uq1L; zvSq7)kGSK0Z1~gZr2{S71^nR53Gb}lqwX%S>=y8$;jvf$G_Jv_1(y8+&Pr-|$H8aT z9$9TUDB#6Q~|rU zEL(GT!%q7@ROrbR`(2xU_Vg_uHNNL(rKf;Dm~rU&(wA@lwzhSkfWsx8YJ79Duh3s1 z#2;x}j!W2f%t5;KaT)LiJd<7<@WWy7BVq8PVK95r{QV{!u95eWQ(vZPToKO1+9j)9 zyQ=BQV2i>09}7z_3N@{Rcl?=jEBS?7MaP=BnDryfD1Qv%lYbd7d#0TUHhg(hFJCMf zCO9=+@XsQv4yN%A(qJ8{f1^turm7hE?$^ID;CI8|29U8>FC)A$U~{<#>hSsc*zouM zfX5}~$G0La>Oc+nc07}h8Ze|yk)HwI6BfQUEWE{e>x^a^pEMC7RBqd(w!!r2`ZI{09o0_h6DO(hx|!mZT&zlm6frnWjvGfP%g(_;(Oi$GgFRziaGL7t|U&KCRn#cWG^98?hy&|gsIZN_|uzbqBL z#`Nc;d@X$k`j)1YOJmWJiFVc>QX2hV<09{K8THmYT#R$X$YDh^l|8McYtF@WY2DQ)zwQD{s#OR!ZEE4!mpxIXD!m$4X&*re->3tjuYeTCH|i6GhoNAWehnXdeXQqLIYce)Ct+Q?&ZD z!JT6+yzUyF{E1QL|9CJ8_BVf)Kl~|SKP!J#a_o!tAuloZ`5LgHA2eV?KR7-iAz`9e zG}bW}6s`(2B+gEPpR7VnFTWOdrf=wUf@6~T~zW2tPA4cTY-#B#i^W!mfdiBfqRxW;W>q{@c`~LAC z7A?8qfrnmt`SqQ9_I~(L-&HTZy<^YbeuIaM9(z^JbvG@4;@Rh3er3nb_v3Uf=)j!Q(&t z?DH=VRNS7J(V%$rZca*!Rx^AAEWC9Ci-JEj(mh*wfL}YOk|kW39?Z)7m*J4=arwvNh3~ zYK>6j2zx}GsG+s%MvRP5tj(e#6^9a`STH=Itv1DJmuuCxWhr$c>^4`#=m<+h{g|QF z9!flpyscxe9i3%u-a1Duu}*DWxy!b2v(m)A@QgAxqQ0|XWWDHm(NpbF_9phR5trF| zM|HGDTV*BL+0oj>?o=u_A}IEXf!25>;OMH0apm0KK@Z*|C2 zqU@QD-i~N{z}Z3>XB`_^xu{XIs0NV(t(Di?AAKOYp*4A(b-{;iBBE`!$_MK#C~?hf z*TIg^8?BWuDNU8yF_K-DQ9TRL)Dnf$&TCm+ave)u+a-1C$@MJ_EsbNE*_u0A$y1dg z%T~)jE&HS2jeO7YzU4#tuIl66^%Lflxe$PGk+9M*3VuM;?3p#k9k9uf6W3dxGdk>>E5J-#g*i=bAQ)a6~!lHB9fEwc+6d zA4F!XxOqcFRF}&O3YXtfmh=6wak+P`Zrg6;-D}s~|G@eUo1Wjg-R_L8-#qJzUROS} ze)rpJBN{h}Ykm0@ukWy`m$q)xu2W`K-~I!K4j(y+I5sKITTtYmId}f`4?Obt=56~P zdwkaJ(z08xio4pTpi2rAIWeJfVRI$9b~9_M$QHKCY(1^DI#fPlZ)I&|ZRbdJ%9Se? zWJETIa#XJBtmHW&lN#7!m8Leid!}`OEzuek5gE~4ZEKB=OjojOO(Lw(5kvcBbc*Q| zk>H40&}P`+c8(6Cn$>F{r@ln@h1(-jLg( zuOq7Rp{wG0Iiu_`mvoMZO7CcORc`B=KRmi`WK{27P5U~AH}AY4qIXntrBA;MrIsVg zj#vxQ8&^Ij*G`F9w7Q_eS-JiCt&0YeFh`jID1uc`6I#$PmW!G7YY%xk?gzMHxmA^#!9iSKd`GdURyt z%4-+&QI=%auD@jHWtH#7D^0AH1?5>bd5Ki{e*1yeD63^rot^``RleHQE?Y<1nxj~*3O`B-N2n2y#+ptQa6u0;nNF-nXQh#nh(A!E%%`P(_-1}_*9U0+db5s{4} zA|jQTi1w9lwT@bBuZaxnW^j`Ae8xLIiB{!W|;QI zBYd;C^fa6@qnQ5;he93}>KYJSk%B!^VSY|Uc}}2AtU}SxLzT2HL0X$J51n|)T1(xA zYV3~#VIvII(s<#jO^(lN5%GO74vDIM{9Zd zB{o{uuS?_DQAy3#jryrY?6{=VPbVIq8hh1{GS5|aZ;j2F{Lk1)(ueCNNryl6Bz@}1 z`|ilP{C!`2nt$r2Sg$NS<~=T-^R|`kvQxek5>y}AioaIbVv)94Emk?wBFm>D_R2On zIUWHxeO;D2%1(QfQ%3x#`Yze)kfUU4ECO3J9q-I?;#;{TNf}JZ0RkHlWr>k% z%UQD3>X7SMnjrPgAZAtzNV?p@qR39dHW~TVvorvy%VIW&8-a6XTgcsT{ACKrUuVSz z8pwCeMGOo_OFc_7OJ|FgZgaV>4CWz^lPwX>$XwYH87a&5{+6aFhb(8*LM1FVr`#%1 zF0i7)ki{BXtcuGTgGW2cca@9@rP5q!ZfSwDNaYBJY;h|1DBhZi^U~ww>5A17DchBg zEEeQ1M=({3!yaXk+sckQS6Gu|M8^Mik_!RWE-|BDNc+u{If*c9F{0Hu`St#HbrDtOs81MGEJF_(oIrfB`&wJLSm68+9xVN ze7hv)S{bvMb*QCQ++ew(QettCQOJ!gHu(-qle#f-8%JYjf|7*BwOHzaS!CNOghtB_ z^q*7ig#0ZQ8=AYRTua>ZZIQ|>l){SKZiOhP$kDDizz?Is29_ks@s=KPeU#yde49;D zP-JU6=rkX+Km@eh>D(-**UnUuk)s7APf=RgWXI(=sy8(f6iCidkR4ilt^EI$b{B9` z)eWP^XLc8M>F(|hK|n#JyVF*>TLA%45JUw;$`+)%ySux)Lq!_wyuYK*;O~9zd!P5- z&;9Fhe_zjR%*>hpPMkR#9hZh{FFKqjHagyxxZt8n7jDA!&xxXL$HeEqnWCD;1*h&9 zT-K~<6U{~vk0%DALct~y9>w>F31?53KYkS3OM>`O(M7pBL&1+4Zj?4EHn=$Z#peri zeTKs|f^QLK_qqfjet9Q|35Cn2;F=D;R!k^5LGq~BtTE%FLuF!0#D$ZF)5Z%Y<13~3 z>#M}$PHs#nwo4oj1jAXI54_8?6Pm~!-HE9##vhnDdtBOV6XTZW-k5-$_)=Av-aJ+D zInC3Aer=vE$Di5LhhiqBADB2thKcDfWvI}#MaIz4Ng1afzmzd_eNeXI_gdr}c5iXc ziW%c)EEe39!tv6DvxT>CV>uYk9UGN3Uh^=&8-lYLoT=apqz*R@509H3&YPe>C~vUE zMHPyQ4mFF4A4(8QRoGY|6cs-zFH0P!4o0@9Qo;G>=EfbWsQ58SxFyBD`qviLhVRH` z$30=bdN9~P`9Cr7;>CnhWalq}JsK(&E*5?}eoS;!;=jIIsA$TpL)vn67mrF%kgYsa zD4Zm=L5O=VA3AI^O>5)}{TyjU(< zwwYXi&w0!<>6t3IZsIZTB@1Ww%oog6Y<|Jk8BX|rHTMNKJ}#i>gv@>A_}s*NX8!+X z{^O!&1`OuIU=IA}dj#_!*C1O;T(}`~VdA)!%!P^mA7;Y;eI85@E)w16KhA;0gBg%* z?Ef_XeWKZKd)Z*-+ma&`{XX+rD1Ha#jWB-#yFnC>$oSd+``IgBxbnlftC(i4f^1H= zgA0urE0jEW@HFE8I4@QGALb-3_Hf#8Qq4`C`*;5Yo zSK#Trha-3n_SJ`CKRvpa+>Gum2lrXodFJ7GhI;Lx{{A~XedXuqe)2~8qP&T|ByXlK z%UkFx@>cq)yp6slZ>PVKchFzUJLzxaUG#N%H~p==hrS{2rEkjn=v(rB`nG(4z9S!` z@5+bhd-7rWzI=p!ARnc_laJBg%g5;-}UayOc>~sz}C!I^qP3MvG()r~4bOE^_T}UoW7mR$ZbCPeo6%3p!QXy(Ru1-=dR~5keo^L;Qs`y5Io(2TNw<<) z(>&OGINH+f87j!qRACG&=U*cZ!L_CQlGA7eg zfkub{{1 z^egcytzS#8lef~_E1#z?$QS8L@@4vpe3iZ?e?@;Se?woFzol=;H|bmQZTgOUkA5KE|2Kb! zzt{R7=nv%|=?(Hv^w07y^sn-7^zZT?^q(^K@_F}iWa|%wc=j#CBkzYJh7OL66B)tZ z<&Kr(=mc^?I+2{1P9i6zlgY{H6mm*Bm7JPRBM18$1^f6%MtVBf-zYLN(wXGUbQU=) z9UR*yGP2VXba~{&xwJkvokz|~=aci(1>}NsA-OPJL@r7flZ(@j$R+5Ka#^}f;mA+R zF^X_-tf9zwlKxCT|5LcFPG1pM(&;PH19kc;_&u$!N{^7M(beS|bWJ&UpGI)3{KyFQ z^{Oie``_1>yK}#&$G`3y1)tNCensw0_mSVEAIL-Lx$?U-JFPw(@6q$+Ve|rd1ie=t zN$-5cLvdXqeb-Xc$>x60G#ZSr(_yF7#5A z^d0#-+SmV&_$RIZnZ7UoLjNlNPWPAppkJ5&qzA}6dlPEF5*a)j9cm~?(T(J2I@nh# zGJ@Sin#l3!rgD6`nH)<$EyvN%$UHM1dR9(IKPM-mpO+KUFUU#gVBf9CNJ_sXC!=4M zlheiJ6!ar=(=)Ix}IE&t}i#ITgWZxR&r~)jog-QC%30N$Q|iUa%Z}W+?DPocc**E zJ?U5ESLt4IZ@Q2C8r@g!NB5Usrw7Py&~M5E=|S=^dbm7-eqSC*e;|*dN6TaAvGO?j zL-`~6WBC*MQ~5LcbNLH;ygY&aQl3apk|)zs|k}uO&o>3nj2x`14eE+iMGi^xUkV)7$&3%LZ{QZ7lik{_j8 z%cbZxa%sA)T!wBZKSsBgAE!IWW$BJ`Il7bl1l?JFlI|iuMR%3U)7|6>ba%NT-9xTK z_mnHsugF#CSLLd7FS#1sTdq#`k!#Se$u;S|axJ=_T$}DM*P&mR>(T?{dh{D|efmwg z0XK~&FGIJ3MCXOY+Atnvn&P2PyJ z%bRcxc{9!_Z^60btvI*54d;<};C%8vTtuFjirf2~f3~+txQ9F$_mrpLSLCVqRe2ij zB~Qn_*|?uP2ltof;@9PQcz`?~zacNcZ^{etKzR`!BrnEq$xHBH zc_|(uFT-!k%kfZo1%5|fiQko1;rHa#c$mBf50}^C5%N0xzPuiflsDiHYAHZMA z2k}Jt5S}C-#*^hEc#3=!PnD11Y4UMAT|R+l$S3hk`4pZdpT@K0GkA`C7SEN>;d%0T zJYT+m7swa!LirM2Bwxmh8-35*hdCpXK}XFY*KWSNS{oH~D+|cXe01HpP`?XpQE3bU!Y%;JBEAp#!FS$3}M}Cd&EBB-O%dgV|ze~R-52J_6Bk1?#k@N@hD0;L!h8`=Aqd$}<&=(?;6(1+!Nm@Udo+3}Br^(al z8S+eemOPuDBhRJh$@A$2@AZ3=I-mR)U0i;g zenc)ymypZRCFLjRN98B!Qu0%DX}LUIMy^3ukZaNv(W)_XX%FW z^K@hRMY^f{GX1pNo^CF8pj*ft>6UUQx|Q6SZY_7A+sIw%wsJSRo%}96P=1ddBoCwC zl84iSB;gOdWt-ko+{6yr^)l_>GA@4hP;wqD6gUy$*bw^@*28_yp~=f@1%Rm zyXaTs-Sn&S9=eyjm);_uqz}rc=tJ^py0?6W?jwIgUzD%Ym*j8he)0{vzkHLvCjUgg zCjU(Lm4BgMmw%-P$iLAK;6~;ai_>H13nhQ*W?no9!q32r@PB7=pJ%Qx~JTV zenoCgzbdz(d&zC--f}y-kKCSqP3}PVl{?b?cctHuyU}mTgXtmi z^4x4MERnH-J|wTC56i3QBl2qcsJw5rO(UT=nL|8`l7spz9jFYFUz~=EAnpos=SB3Chw)clK0VH%lqkXPE0=`C!wE| zlhRMg$>{QOa=LT-IzhMa+}DQBc>$(iWd za%Q@YoQ19{XQk`O+35OmcDjL_gKjA2q#Mb(=*Dtxx`~{JZYt-co5}g;r{(0UM@_(AQz!ul#9|Y$<65&a!b0C_SbHO2V{xNR@|2gto7}1dzQ%PKzEco z(VgWkbVGf9SKLGHhIudc!_gf#l6&G;SR$hr-CKTxF0a$Si7Uth@gSZ4Eqbs#gnnBd zO1~q&OTQ-%qle2Q==bH3^at`NdbB)-9xIQdKa@YBKbAkCKb1eDKbOCt$IBDwFXf5! zBzZDDMV?Aelc&=&&p%3hH@jivD}1iDmSB_mY<=Ym7k-ZmtUY?lwYD>mYdTpo?l^!jNEh{IWL`0&QBMR3(|$;!gLY2C|yi0 zL6?*trAx`B=`!+T^y6|_x}5w3{iOU9U0$w0SClK!mE|gQRk<2nU9Lgblxxwo&b${m#{HE3qqzB1w(Szk7^xN`K`W<;VJwkq; zZln7rqwr|0A489oKcYXDKcPRBKchdFzo5s<6X-AHiS#6SGCf6}N>7ug(=+6m^elNc z-B#yk4t_zNi(AO^aA$cw?kF$B6XZqs9eFVxFE7E3wg1Layo@C>meVWbmGmlkHN8e& zORtmH(;MW?^cHz5Jx<<6zonmVJ07X+YX{z`)9<2p%iq&K$T|LG{$PoWoOC5Q7hPG- zO;?fg&{gHUbTv62U0u#k*N_X)HRXbIEx8a~TP{r3k&DoE<)U;wxfoqvE>1U)AE6t{ zCFn+SNxHH8DBVOZMK_g8)6L{E^waWV^fU6~^s{nV`Z+li6AtBPiHzXTPz7X;r4}kE z2Y-&EkQ_r7mN^t!sEEt~(n3XL4(b;wCUek3-p}^W_fJ4SA}6Fv$Q(40_p|+TdX7rV z``P|+61tR}lrAkNqsz$2>Br<0^y6|$x~!avE+?m^pODkgPs(ZOr{r{Wd6}c3@qV^{ ze*O$}ML8o~NzO!9mNU~;ra_vwA|NP55g0ewIo zMIV$$(}(0S^kI1{eMBBdAC*6(kI5g=$K{Xd6Y?kYN%>Ryl>8ZeTK=3qBY#1kmB-WP z96D&^w;uC`Wtx`eO;bSe=EVoad`!OLS9LqlvmNG { + const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('26x56YPzPDro5t2smQfGcYAPy3j7R2jB2NUb7xKbAGK23B6x4WNQPh3twb6oDksFov5X8ts5CtntUNbpQpAKFdbR'); + expect(keyPair.publicKey.toString()).toEqual('ed25519:AYWv9RAN1hpSQA4p1DLhCNnpnNXwxhfH9qeHN8B4nJ59'); + const message = new Uint8Array(sha256.array('message')); + const signature = keyPair.sign(message); + expect(nearApi.utils.serialize.base_encode(signature.signature)).toEqual('26gFr4xth7W9K7HPWAxq3BLsua8oTy378mC1MYFiEXHBBpeBjP8WmJEJo8XTBowetvqbRshcQEtBUdwQcAqDyP8T'); +}); + +test('test sign and verify with random', async () => { + const keyPair = nearApi.utils.key_pair.KeyPairEd25519.fromRandom(); + const message = new Uint8Array(sha256.array('message')); + const signature = keyPair.sign(message); + expect(keyPair.verify(message, signature.signature)).toBeTruthy(); +}); + +test('test sign and verify with public key', async () => { + const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); + const message = new Uint8Array(sha256.array('message')); + const signature = keyPair.sign(message); + const publicKey = nearApi.utils.key_pair.PublicKey.from('ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW'); + expect(publicKey.verify(message, signature.signature)).toBeTruthy(); +}); + +test('test from secret', async () => { + const keyPair = new nearApi.utils.key_pair.KeyPairEd25519('5JueXZhEEVqGVT5powZ5twyPP8wrap2K7RdAYGGdjBwiBdd7Hh6aQxMP1u3Ma9Yanq1nEv32EW7u8kUJsZ6f315C'); + expect(keyPair.publicKey.toString()).toEqual('ed25519:EWrekY1deMND7N3Q7Dixxj12wD7AVjFRt2H9q21QHUSW'); +}); + +test('convert to string', async () => { + const keyPair = nearApi.utils.key_pair.KeyPairEd25519.fromRandom(); + const newKeyPair = nearApi.utils.key_pair.KeyPair.fromString(keyPair.toString()); + expect(newKeyPair.secretKey).toEqual(keyPair.secretKey); + + const keyString = 'ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'; + const keyPair2 = nearApi.utils.key_pair.KeyPair.fromString(keyString); + expect(keyPair2.toString()).toEqual(keyString); +}); diff --git a/packages/near-api-js/test/key_stores/browser_keystore.test.js b/packages/near-api-js/test/key_stores/browser_keystore.test.js new file mode 100644 index 000000000..d7f7b19d2 --- /dev/null +++ b/packages/near-api-js/test/key_stores/browser_keystore.test.js @@ -0,0 +1,13 @@ +const nearApi = require('../../src/index'); + +const BrowserLocalStorageKeyStore = nearApi.keyStores.BrowserLocalStorageKeyStore; + +describe('Browser keystore', () => { + let ctx = {}; + + beforeAll(async () => { + ctx.keyStore = new BrowserLocalStorageKeyStore(require('localstorage-memory')); + }); + + require('./keystore_common').shouldStoreAndRetriveKeys(ctx); +}); diff --git a/packages/near-api-js/test/key_stores/in_memory_keystore.test.js b/packages/near-api-js/test/key_stores/in_memory_keystore.test.js new file mode 100644 index 000000000..99affb3ff --- /dev/null +++ b/packages/near-api-js/test/key_stores/in_memory_keystore.test.js @@ -0,0 +1,13 @@ +const nearApi = require('../../src/index'); + +const InMemoryKeyStore = nearApi.keyStores.InMemoryKeyStore; + +describe('In-memory keystore', () => { + let ctx = {}; + + beforeAll(async () => { + ctx.keyStore = new InMemoryKeyStore(); + }); + + require('./keystore_common').shouldStoreAndRetriveKeys(ctx); +}); diff --git a/packages/near-api-js/test/key_stores/keystore_common.js b/packages/near-api-js/test/key_stores/keystore_common.js new file mode 100644 index 000000000..7008b950a --- /dev/null +++ b/packages/near-api-js/test/key_stores/keystore_common.js @@ -0,0 +1,57 @@ + +const nearApi = require('../../src/index'); + +const KeyPair = nearApi.utils.KeyPairEd25519; + +const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; +const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; +const KEYPAIR_SINGLE_KEY = new KeyPair('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); + +module.exports.shouldStoreAndRetriveKeys = ctx => { + beforeEach(async () => { + await ctx.keyStore.clear(); + await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); + }); + + test('Get all keys with empty network returns empty list', async () => { + const emptyList = await ctx.keyStore.getAccounts('emptynetwork'); + expect(emptyList).toEqual([]); + }); + + test('Get all keys with single key in keystore', async () => { + const accountIds = await ctx.keyStore.getAccounts(NETWORK_ID_SINGLE_KEY); + expect(accountIds).toEqual([ACCOUNT_ID_SINGLE_KEY]); + }); + + test('Get not-existing account', async () => { + expect(await ctx.keyStore.getKey('somenetwork', 'someaccount')).toBeNull(); + }); + + test('Get account id from a network with single key', async () => { + const key = await ctx.keyStore.getKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY); + expect(key).toEqual(KEYPAIR_SINGLE_KEY); + }); + + test('Get networks', async () => { + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY]); + }); + + test('Add two keys to network and retrieve them', async () => { + const networkId = 'twoKeyNetwork'; + const accountId1 = 'acc1'; + const accountId2 = 'acc2'; + const key1Expected = KeyPair.fromRandom(); + const key2Expected = KeyPair.fromRandom(); + await ctx.keyStore.setKey(networkId, accountId1, key1Expected); + await ctx.keyStore.setKey(networkId, accountId2, key2Expected); + const key1 = await ctx.keyStore.getKey(networkId, accountId1); + const key2 = await ctx.keyStore.getKey(networkId, accountId2); + expect(key1).toEqual(key1Expected); + expect(key2).toEqual(key2Expected); + const accountIds = await ctx.keyStore.getAccounts(networkId); + expect(accountIds).toEqual([accountId1, accountId2]); + const networks = await ctx.keyStore.getNetworks(); + expect(networks).toEqual([NETWORK_ID_SINGLE_KEY, networkId]); + }); +}; diff --git a/packages/near-api-js/test/key_stores/merge_keystore.test.js b/packages/near-api-js/test/key_stores/merge_keystore.test.js new file mode 100644 index 000000000..86f8fee64 --- /dev/null +++ b/packages/near-api-js/test/key_stores/merge_keystore.test.js @@ -0,0 +1,38 @@ +const nearApi = require('../../src/index'); + +const KeyPair = nearApi.utils.KeyPairEd25519; + +const MergeKeyStore = nearApi.keyStores.MergeKeyStore; +const InMemoryKeyStore = nearApi.keyStores.InMemoryKeyStore; + +describe('Merge keystore', () => { + let ctx = {}; + + beforeAll(async () => { + ctx.stores = [new InMemoryKeyStore(), new InMemoryKeyStore()]; + ctx.keyStore = new MergeKeyStore(ctx.stores); + }); + + it('looks up key from fallback key store if needed', async () => { + const key1 = KeyPair.fromRandom(); + await ctx.stores[1].setKey('network', 'account', key1); + expect(await ctx.keyStore.getKey('network', 'account')).toEqual(key1); + }); + + it('looks up key in proper order', async () => { + const key1 = KeyPair.fromRandom(); + const key2 = KeyPair.fromRandom(); + await ctx.stores[0].setKey('network', 'account', key1); + await ctx.stores[1].setKey('network', 'account', key2); + expect(await ctx.keyStore.getKey('network', 'account')).toEqual(key1); + }); + + it('sets keys only in first key store', async () => { + const key1 = KeyPair.fromRandom(); + await ctx.keyStore.setKey('network', 'account', key1); + expect(await ctx.stores[0].getAccounts('network')).toHaveLength(1); + expect(await ctx.stores[1].getAccounts('network')).toHaveLength(0); + }); + + require('./keystore_common').shouldStoreAndRetriveKeys(ctx); +}); diff --git a/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js b/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js new file mode 100644 index 000000000..6ab9bfd89 --- /dev/null +++ b/packages/near-api-js/test/key_stores/unencrypted_file_system_keystore.test.js @@ -0,0 +1,36 @@ + +const rimraf = require('util').promisify(require('rimraf')); + +const nearApi = require('../../src/index'); +const UnencryptedFileSystemKeyStore = nearApi.keyStores.UnencryptedFileSystemKeyStore; +const KeyPair = nearApi.utils.KeyPairEd25519; +const { ensureDir } = require('../test-utils'); +const fs = require('fs'); +const path = require('path'); + +const KEYSTORE_PATH = '../test-keys'; + +describe('Unencrypted file system keystore', () => { + let ctx = {}; + + beforeAll(async () => { + await rimraf(KEYSTORE_PATH); + await ensureDir(KEYSTORE_PATH); + ctx.keyStore = new UnencryptedFileSystemKeyStore(KEYSTORE_PATH); + }); + + require('./keystore_common').shouldStoreAndRetriveKeys(ctx); + + it('test path resolve', async() => { + expect(ctx.keyStore.keyDir).toEqual(path.join(process.cwd(), KEYSTORE_PATH)); + }); + + it('test public key exists', async () => { + const key1 = KeyPair.fromRandom(); + await ctx.keyStore.setKey('network', 'account', key1); + const keyFilePath = ctx.keyStore.getKeyFilePath('network', 'account'); + const content = fs.readFileSync(keyFilePath); + const accountInfo = JSON.parse(content.toString()); + expect(accountInfo.public_key).toEqual(key1.getPublicKey().toString()); + }); +}); diff --git a/packages/near-api-js/test/promise.test.js b/packages/near-api-js/test/promise.test.js new file mode 100644 index 000000000..02e57cbd8 --- /dev/null +++ b/packages/near-api-js/test/promise.test.js @@ -0,0 +1,324 @@ +const BN = require('bn.js'); +const testUtils = require('./test-utils'); + +let nearjs; +let workingAccount; + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 120000; + +const CONTRACT_CALL_GAS = new BN(300000000000000); + +beforeAll(async () => { + nearjs = await testUtils.setUpTestConnection(); + workingAccount = await testUtils.createAccount(nearjs); +}); + +describe('with promises', () => { + let contract, contract1, contract2; + let oldLog; + let logs; + let contractName = testUtils.generateUniqueString('cnt'); + let contractName1 = testUtils.generateUniqueString('cnt'); + let contractName2 = testUtils.generateUniqueString('cnt'); + + beforeAll(async () => { + contract = await testUtils.deployContract(workingAccount, contractName); + contract1 = await testUtils.deployContract(workingAccount, contractName1); + contract2 = await testUtils.deployContract(workingAccount, contractName2); + }); + + beforeEach(async () => { + oldLog = console.log; + logs = []; + console.log = function() { + logs.push(Array.from(arguments).join(' ')); + }; + }); + + afterEach(async () => { + console.log = oldLog; + }); + + // -> means async call + // => means callback + + test('single promise, no callback (A->B)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callbackWithName', + args: null, + gas: '3000000000000', + balance: '0', + callback: null, + callbackArgs: null, + callbackBalance: '0', + callbackGas: '0', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult = await contract1.getLastResult(); + expect(lastResult).toEqual({ + rs: [], + n: contractName1, + }); + expect(realResult).toEqual(lastResult); + }); + + test('single promise with callback (A->B=>A)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callbackWithName', + args: null, + gas: '3000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '2000000000000', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult1 = await contract1.getLastResult(); + expect(lastResult1).toEqual({ + rs: [], + n: contractName1, + }); + const lastResult = await contract.getLastResult(); + expect(lastResult).toEqual({ + rs: [{ + ok: true, + r: lastResult1, + }], + n: contractName, + }); + expect(realResult).toEqual(lastResult); + }); + + test('two promises, no callbacks (A->B->C)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callPromise', + args: { + receiver: contractName2, + methodName: 'callbackWithName', + args: null, + gas: '40000000000000', + balance: '0', + callback: null, + callbackArgs: null, + callbackBalance: '0', + callbackGas: '20000000000000', + }, + gas: '60000000000000', + balance: '0', + callback: null, + callbackArgs: null, + callbackBalance: '0', + callbackGas: '60000000000000', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult2 = await contract2.getLastResult(); + expect(lastResult2).toEqual({ + rs: [], + n: contractName2, + }); + expect(realResult).toEqual(lastResult2); + }); + + test('two promises, with two callbacks (A->B->C=>B=>A)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callPromise', + args: { + receiver: contractName2, + methodName: 'callbackWithName', + args: null, + gas: '40000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '20000000000000', + }, + gas: '100000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '30000000000000', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult2 = await contract2.getLastResult(); + expect(lastResult2).toEqual({ + rs: [], + n: contractName2, + }); + const lastResult1 = await contract1.getLastResult(); + expect(lastResult1).toEqual({ + rs: [{ + ok: true, + r: lastResult2, + }], + n: contractName1, + }); + const lastResult = await contract.getLastResult(); + expect(lastResult).toEqual({ + rs: [{ + ok: true, + r: lastResult1, + }], + n: contractName, + }); + expect(realResult).toEqual(lastResult); + }); + + test('cross contract call with callbacks (A->B->A=>B=>A)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callPromise', + args: { + receiver: contractName, + methodName: 'callbackWithName', + args: null, + gas: '40000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '40000000000000', + }, + gas: '100000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '30000000000000', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult1 = await contract1.getLastResult(); + expect(lastResult1).toEqual({ + rs: [{ + ok: true, + r: { + rs: [], + n: contractName, + }, + }], + n: contractName1, + }); + const lastResult = await contract.getLastResult(); + expect(lastResult).toEqual({ + rs: [{ + ok: true, + r: lastResult1, + }], + n: contractName, + }); + expect(realResult).toEqual(lastResult); + }); + + test('2 promises with 1 skipped callbacks (A->B->C=>A)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callPromise', + args: { + receiver: contractName2, + methodName: 'callbackWithName', + args: null, + gas: '20000000000000', + balance: '0', + callback: null, + callbackArgs: null, + callbackBalance: '0', + callbackGas: '20000000000000', + }, + gas: '50000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '30000000000000' + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult2 = await contract2.getLastResult(); + expect(lastResult2).toEqual({ + rs: [], + n: contractName2, + }); + const lastResult = await contract.getLastResult(); + expect(lastResult).toEqual({ + rs: [{ + ok: true, + r: lastResult2, + }], + n: contractName, + }); + expect(realResult).toEqual(lastResult); + }); + + test('two promises, with one callbacks to B only (A->B->C=>B)', async () => { + const realResult = await contract.callPromise({ + args: { + args: { + receiver: contractName1, + methodName: 'callPromise', + args: { + receiver: contractName2, + methodName: 'callbackWithName', + args: null, + gas: '40000000000000', + balance: '0', + callback: 'callbackWithName', + callbackArgs: null, + callbackBalance: '0', + callbackGas: '40000000000000', + }, + gas: '100000000000000', + balance: '0', + callback: null, + callbackArgs: null, + callbackBalance: '0', + callbackGas: '0', + } + }, + gas: CONTRACT_CALL_GAS + }); + const lastResult2 = await contract2.getLastResult(); + expect(lastResult2).toEqual({ + rs: [], + n: contractName2, + }); + const lastResult1 = await contract1.getLastResult(); + expect(lastResult1).toEqual({ + rs: [{ + ok: true, + r: lastResult2, + }], + n: contractName1, + }); + expect(realResult).toEqual(lastResult1); + }); + +}); diff --git a/packages/near-api-js/test/providers.test.js b/packages/near-api-js/test/providers.test.js new file mode 100644 index 000000000..2c489807b --- /dev/null +++ b/packages/near-api-js/test/providers.test.js @@ -0,0 +1,335 @@ +const nearApi = require('../src/index'); +const testUtils = require('./test-utils'); +const BN = require('bn.js'); +const base58 = require('bs58'); + +jest.setTimeout(30000); + +const withProvider = (fn) => { + const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test')); + const provider = new nearApi.providers.JsonRpcProvider(config.nodeUrl); + return () => fn(provider); +}; + +test('json rpc fetch node status', withProvider(async (provider) => { + let response = await provider.status(); + expect(response.chain_id).toBeTruthy(); +})); + +test('json rpc fetch block info', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.block({ blockId: height }); + expect(response.header.height).toEqual(height); + + let sameBlock = await provider.block({ blockId: response.header.hash }); + expect(sameBlock.header.height).toEqual(height); + + let optimisticBlock = await provider.block({ finality: 'optimistic' }); + expect(optimisticBlock.header.height - height).toBeLessThan(5); + + let nearFinalBlock = await provider.block({ finality: 'near-final' }); + expect(nearFinalBlock.header.height - height).toBeLessThan(5); + + let finalBlock = await provider.block({ finality: 'final' }); + expect(finalBlock.header.height - height).toBeLessThan(5); +})); + +test('json rpc fetch block changes', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.blockChanges({ blockId: height }); + + expect(response).toMatchObject({ + block_hash: expect.any(String), + changes: expect.any(Array) + }); +})); + +test('json rpc fetch chunk info', withProvider(async (provider) => { + let stat = await provider.status(); + let height = stat.sync_info.latest_block_height - 1; + let response = await provider.chunk([height, 0]); + expect(response.header.shard_id).toEqual(0); + let sameChunk = await provider.chunk(response.header.chunk_hash); + expect(sameChunk.header.chunk_hash).toEqual(response.header.chunk_hash); + expect(sameChunk.header.shard_id).toEqual(0); +})); + +test('json rpc fetch validators info', withProvider(async (provider) => { + let validators = await provider.validators(null); + expect(validators.current_validators.length).toBeGreaterThanOrEqual(1); +})); + +test('txStatus with string hash and buffer hash', withProvider(async(provider) => { + const near = await testUtils.setUpTestConnection(); + const sender = await testUtils.createAccount(near); + const receiver = await testUtils.createAccount(near); + const outcome = await sender.sendMoney(receiver.accountId, new BN('1')); + + const responseWithString = await provider.txStatus(outcome.transaction.hash, sender.accountId); + const responseWithUint8Array = await provider.txStatus(base58.decode(outcome.transaction.hash), sender.accountId); + expect(responseWithString).toMatchObject(outcome); + expect(responseWithUint8Array).toMatchObject(outcome); +})); + +test('txStatusReciept with string hash and buffer hash', withProvider(async(provider) => { + const near = await testUtils.setUpTestConnection(); + const sender = await testUtils.createAccount(near); + const receiver = await testUtils.createAccount(near); + const outcome = await sender.sendMoney(receiver.accountId, new BN('1')); + const reciepts = await provider.sendJsonRpc('EXPERIMENTAL_tx_status', [outcome.transaction.hash, sender.accountId]); + + const responseWithString = await provider.txStatusReceipts(outcome.transaction.hash, sender.accountId); + const responseWithUint8Array = await provider.txStatusReceipts(base58.decode(outcome.transaction.hash), sender.accountId); + expect(responseWithString).toMatchObject(reciepts); + expect(responseWithUint8Array).toMatchObject(reciepts); +})); + +test('json rpc query with block_id', withProvider(async(provider) => { + const stat = await provider.status(); + let block_id = stat.sync_info.latest_block_height - 1; + + const response = await provider.query({ + block_id, + request_type: 'view_account', + account_id: 'test.near' + }); + + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + amount: expect.any(String), + locked: expect.any(String), + code_hash: '11111111111111111111111111111111', + storage_usage: 182, + storage_paid_at: 0, + }); +})); + +test('json rpc query account', withProvider(async (provider) => { + const near = await testUtils.setUpTestConnection(); + const account = await testUtils.createAccount(near); + let response = await provider.query(`account/${account.accountId}`, ''); + expect(response.code_hash).toEqual('11111111111111111111111111111111'); +})); + +test('json rpc query view_state', withProvider(async (provider) => { + const near = await testUtils.setUpTestConnection(); + const account = await testUtils.createAccount(near); + const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); + + await contract.setValue({ args: { value: 'hello' } }); + + return testUtils.waitFor(async() => { + const response = await provider.query({ + request_type: 'view_state', + finality: 'final', + account_id: contract.contractId, + prefix_base64: '' + }); + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + values: [ + { key: 'bmFtZQ==', value: 'aGVsbG8=', proof: [] } + ], + proof: [] + }); + }); +})); + +test('json rpc query view_account', withProvider(async (provider) => { + const response = await provider.query({ + request_type: 'view_account', + finality: 'final', + account_id: 'test.near' + }); + + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + amount: expect.any(String), + locked: expect.any(String), + code_hash: '11111111111111111111111111111111', + storage_usage: 182, + storage_paid_at: 0, + }); +})); + +test('json rpc query view_code', withProvider(async (provider) => { + const near = await testUtils.setUpTestConnection(); + const account = await testUtils.createAccount(near); + const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); + + return testUtils.waitFor(async() => { + const response = await provider.query({ + request_type: 'view_code', + finality: 'final', + account_id: contract.contractId + }); + + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + code_base64: expect.any(String), + hash: expect.any(String) + }); + }); +})); + +test('json rpc query call_function', withProvider(async (provider) => { + const near = await testUtils.setUpTestConnection(); + const account = await testUtils.createAccount(near); + const contract = await testUtils.deployContract(account, testUtils.generateUniqueString('test')); + + await contract.setValue({ args: { value: 'hello' } }); + + return testUtils.waitFor(async() => { + const response = await provider.query({ + request_type: 'call_function', + finality: 'final', + account_id: contract.contractId, + method_name: 'getValue', + args_base64: '' + }); + expect(response).toEqual({ + block_height: expect.any(Number), + block_hash: expect.any(String), + logs: [], + result: [ + 34, + 104, + 101, + 108, + 108, + 111, + 34 + ] + }); + }); +})); + +test('final tx result', async() => { + const result = { + status: { SuccessValue: 'e30=' }, + transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, + receipts: [ + { id: '11112', outcome: { status: { SuccessValue: 'e30=' }, logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, + { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } + ] + }; + expect(nearApi.providers.getTransactionLastResult(result)).toEqual({}); +}); + +test('final tx result with null', async() => { + const result = { + status: 'Failure', + transaction: { id: '11111', outcome: { status: { SuccessReceiptId: '11112' }, logs: [], receipt_ids: ['11112'], gas_burnt: 1 } }, + receipts: [ + { id: '11112', outcome: { status: 'Failure', logs: [], receipt_ids: ['11112'], gas_burnt: 9001 } }, + { id: '11113', outcome: { status: { SuccessValue: '' }, logs: [], receipt_ids: [], gas_burnt: 0 } } + ] + }; + expect(nearApi.providers.getTransactionLastResult(result)).toEqual(null); +}); + +test('json rpc light client proof', async() => { + const near = await testUtils.setUpTestConnection(); + const workingAccount = await testUtils.createAccount(near); + const executionOutcome = await workingAccount.sendMoney(workingAccount.accountId, new BN(10000)); + const provider = near.connection.provider; + + async function waitForStatusMatching(isMatching) { + const MAX_ATTEMPTS = 10; + for (let i = 0; i < MAX_ATTEMPTS; i++) { + await testUtils.sleep(500); + const nodeStatus = await provider.status(); + if (isMatching(nodeStatus)) { + return nodeStatus; + } + } + throw new Error(`Exceeded ${MAX_ATTEMPTS} attempts waiting for matching node status.`); + } + + const comittedStatus = await waitForStatusMatching(status => + status.sync_info.latest_block_hash !== executionOutcome.transaction_outcome.block_hash); + const BLOCKS_UNTIL_FINAL = 2; + const finalizedStatus = await waitForStatusMatching(status => + status.sync_info.latest_block_height > comittedStatus.sync_info.latest_block_height + BLOCKS_UNTIL_FINAL); + + const block = await provider.block({ blockId: finalizedStatus.sync_info.latest_block_hash }); + const lightClientHead = block.header.last_final_block; + let lightClientRequest = { + type: 'transaction', + light_client_head: lightClientHead, + transaction_hash: executionOutcome.transaction.hash, + sender_id: workingAccount.accountId, + }; + const lightClientProof = await provider.lightClientProof(lightClientRequest); + expect('prev_block_hash' in lightClientProof.block_header_lite).toBe(true); + expect('inner_rest_hash' in lightClientProof.block_header_lite).toBe(true); + expect('inner_lite' in lightClientProof.block_header_lite).toBe(true); + expect(lightClientProof.outcome_proof.id).toEqual(executionOutcome.transaction_outcome.id); + expect('block_hash' in lightClientProof.outcome_proof).toBe(true); + expect(lightClientProof.outcome_root_proof).toEqual([]); + expect(lightClientProof.block_proof.length).toBeGreaterThan(0); + + // pass nonexistent hash for light client head will fail + lightClientRequest = { + type: 'transaction', + light_client_head: '11111111111111111111111111111111', + transaction_hash: executionOutcome.transaction.hash, + sender_id: workingAccount.accountId, + }; + await expect(provider.lightClientProof(lightClientRequest)).rejects.toThrow('DB Not Found Error'); + + // Use old block hash as light client head should fail + lightClientRequest = { + type: 'transaction', + light_client_head: executionOutcome.transaction_outcome.block_hash, + transaction_hash: executionOutcome.transaction.hash, + sender_id: workingAccount.accountId, + }; + + await expect(provider.lightClientProof(lightClientRequest)).rejects.toThrow(/.+ block .+ is ahead of head block .+/); +}); + +test('json rpc fetch protocol config', withProvider(async (provider) => { + const status = await provider.status(); + const blockHeight = status.sync_info.latest_block_height; + const blockHash = status.sync_info.latest_block_hash; + for (const blockReference of [{ sync_checkpoint: 'genesis' }, { blockId: blockHeight }, { blockId: blockHash }, { finality: 'final' }, { finality: 'optimistic' }]) { + const response = await provider.experimental_protocolConfig(blockReference); + expect('chain_id' in response).toBe(true); + expect('genesis_height' in response).toBe(true); + expect('runtime_config' in response).toBe(true); + expect('storage_amount_per_byte' in response.runtime_config).toBe(true); + } +})); + +test('json rpc gas price', withProvider(async (provider) => { + let status = await provider.status(); + let positiveIntegerRegex = /^[+]?\d+([.]\d+)?$/; + + let response1 = await provider.gasPrice(status.sync_info.latest_block_height); + expect(response1.gas_price).toMatch(positiveIntegerRegex); + + let response2 = await provider.gasPrice(status.sync_info.latest_block_hash); + expect(response2.gas_price).toMatch(positiveIntegerRegex); + + let response3 = await provider.gasPrice(); + expect(response3.gas_price).toMatch(positiveIntegerRegex); +})); + +test('JsonRpc connection object exist without connectionInfo provided', async () => { + const provider = new nearApi.providers.JsonRpcProvider(); + expect(provider.connection).toStrictEqual({ url: '' }); +}); + +test('near json rpc fetch node status', async () => { + const config = require('./config')(process.env.NODE_ENV || 'test'); + const near = await nearApi.connect(config); + let response = await near.connection.provider.status(); + expect(response.chain_id).toBeTruthy(); +}); \ No newline at end of file diff --git a/packages/near-api-js/test/serialize.test.js b/packages/near-api-js/test/serialize.test.js new file mode 100644 index 000000000..c726c54b2 --- /dev/null +++ b/packages/near-api-js/test/serialize.test.js @@ -0,0 +1,166 @@ + +const fs = require('fs'); +const BN = require('bn.js'); +const nearApi = require('../src/index'); + +class Test extends nearApi.utils.enums.Assignable { +} + +test('serialize object', async () => { + const value = new Test({ x: 255, y: 20, z: '123', q: [1, 2, 3]}); + const schema = new Map([[Test, {kind: 'struct', fields: [['x', 'u8'], ['y', 'u64'], ['z', 'string'], ['q', [3]]] }]]); + let buf = nearApi.utils.serialize.serialize(schema, value); + let new_value = nearApi.utils.serialize.deserialize(schema, Test, buf); + expect(new_value.x).toEqual(255); + expect(new_value.y.toString()).toEqual('20'); + expect(new_value.z).toEqual('123'); + expect(new_value.q).toEqual(new Uint8Array([1, 2, 3])); +}); + +test('serialize and sign multi-action tx', async() => { + const keyStore = new nearApi.keyStores.InMemoryKeyStore(); + const keyPair = nearApi.utils.KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); + await keyStore.setKey('test', 'test.near', keyPair); + const publicKey = keyPair.publicKey; + const actions = [ + nearApi.transactions.createAccount(), + nearApi.transactions.deployContract(new Uint8Array([1, 2, 3])), + nearApi.transactions.functionCall('qqq', new Uint8Array([1, 2, 3]), 1000, 1000000), + nearApi.transactions.transfer(123), + nearApi.transactions.stake(1000000, publicKey), + nearApi.transactions.addKey(publicKey, nearApi.transactions.functionCallAccessKey('zzz', ['www'], null)), + nearApi.transactions.deleteKey(publicKey), + nearApi.transactions.deleteAccount('123') + ]; + const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + let [hash, { transaction }] = await nearApi.transactions.signTransaction('123', 1, actions, blockHash, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); + expect(nearApi.utils.serialize.base_encode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); + const serialized = nearApi.utils.serialize.serialize(nearApi.transactions.SCHEMA, transaction); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e656172000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80100000000000000030000003132330fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef608000000000103000000010203020300000071717103000000010203e80300000000000040420f00000000000000000000000000037b0000000000000000000000000000000440420f00000000000000000000000000000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef805000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef800000000000000000000030000007a7a7a010000000300000077777706000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80703000000313233'); +}); + +function createTransferTx() { + const actions = [ + nearApi.transactions.transfer(1), + ]; + const blockHash = nearApi.utils.serialize.base_decode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); + return nearApi.transactions.createTransaction( + 'test.near', + nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 'whatever.near', + 1, + actions, + blockHash); +} + +test('serialize transfer tx', async() => { + const transaction = createTransferTx(); + + const serialized = transaction.encode(); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef6010000000301000000000000000000000000000000'); + + const deserialized = nearApi.transactions.Transaction.decode(serialized); + expect(deserialized.encode()).toEqual(serialized); +}); + +async function createKeyStore() { + const keyStore = new nearApi.keyStores.InMemoryKeyStore(); + const keyPair = nearApi.utils.KeyPair.fromString('ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv'); + await keyStore.setKey('test', 'test.near', keyPair); + return keyStore; +} + +async function verifySignedTransferTx(signedTx) { + expect(Buffer.from(signedTx.signature.data).toString('base64')).toEqual('lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ=='); + const serialized = signedTx.encode(); + expect(serialized.toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01'); + + const deserialized = nearApi.transactions.SignedTransaction.decode(serialized); + expect(deserialized.encode()).toEqual(serialized); +} + +test('serialize and sign transfer tx', async() => { + const transaction = createTransferTx(); + const keyStore = await createKeyStore(); + + let [, signedTx] = await nearApi.transactions.signTransaction(transaction.receiverId, transaction.nonce, transaction.actions, transaction.blockHash, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); + + verifySignedTransferTx(signedTx); +}); + +test('serialize and sign transfer tx object', async() => { + const transaction = createTransferTx(); + const keyStore = await createKeyStore(); + + let [, signedTx] = await nearApi.transactions.signTransaction(transaction, new nearApi.InMemorySigner(keyStore), 'test.near', 'test'); + + verifySignedTransferTx(signedTx); +}); + +describe('roundtrip test', () => { + const dataDir = './test/data'; + const testFiles = fs.readdirSync(dataDir); + for (const testFile of testFiles) { + if (/.+\.json$/.test(testFile)) { + const testDefinition = JSON.parse(fs.readFileSync(dataDir + '/' + testFile)); + test(testFile, () => { + const data = Buffer.from(testDefinition.data, 'hex'); + const type = Array.from(nearApi.transactions.SCHEMA.keys()).find(key => key.name === testDefinition.type); + const deserialized = nearApi.utils.serialize.deserialize(nearApi.transactions.SCHEMA, type, data); + const serialized = nearApi.utils.serialize.serialize(nearApi.transactions.SCHEMA, deserialized); + expect(serialized).toEqual(data); + }); + } + } +}); + +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 diff --git a/packages/near-api-js/test/signer.test.js b/packages/near-api-js/test/signer.test.js new file mode 100644 index 000000000..9de563bdb --- /dev/null +++ b/packages/near-api-js/test/signer.test.js @@ -0,0 +1,7 @@ + +const nearApi = require('../src/index'); + +test('test no key', async() => { + const signer = new nearApi.InMemorySigner(new nearApi.keyStores.InMemoryKeyStore()); + await expect(signer.signMessage('message', 'user', 'network')).rejects.toThrow(/Key for user not found in network/); +}); diff --git a/packages/near-api-js/test/test-utils.js b/packages/near-api-js/test/test-utils.js new file mode 100644 index 000000000..5ff2ebbe9 --- /dev/null +++ b/packages/near-api-js/test/test-utils.js @@ -0,0 +1,127 @@ +const fs = require('fs').promises; +const BN = require('bn.js'); + +const nearApi = require('../src/index'); + +const networkId = 'unittest'; + +const HELLO_WASM_PATH = process.env.HELLO_WASM_PATH || 'node_modules/near-hello/dist/main.wasm'; +const HELLO_WASM_BALANCE = new BN('10000000000000000000000000'); +const HELLO_WASM_METHODS = { + viewMethods: ['getValue', 'getLastResult'], + changeMethods: ['setValue', 'callPromise'] +}; +const MULTISIG_WASM_PATH = process.env.MULTISIG_WASM_PATH || './test/wasm/multisig.wasm'; +// Length of a random account. Set to 40 because in the protocol minimal allowed top-level account length should be at +// least 32. +const RANDOM_ACCOUNT_LENGTH = 40; + +async function setUpTestConnection() { + const keyStore = new nearApi.keyStores.InMemoryKeyStore(); + const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test'), { + networkId: networkId, + keyStore + }); + + if (config.masterAccount) { + // full accessKey on ci-testnet, dedicated rpc for tests. + await keyStore.setKey(networkId, config.masterAccount, nearApi.utils.KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw')); + } + return nearApi.connect(config); +} + +// Generate some unique string of length at least RANDOM_ACCOUNT_LENGTH with a given prefix using the alice nonce. +function generateUniqueString(prefix) { + let result = `${prefix}-${Date.now()}-${Math.round(Math.random() * 1000000)}`; + let add_symbols = Math.max(RANDOM_ACCOUNT_LENGTH - result.length, 1); + for (let i = add_symbols; i > 0; --i) result += '0'; + return result; +} + +async function createAccount(near) { + const newAccountName = generateUniqueString('test'); + const newPublicKey = await near.connection.signer.createKey(newAccountName, networkId); + await near.createAccount(newAccountName, newPublicKey); + const account = new nearApi.Account(near.connection, newAccountName); + return account; +} + +async function createAccountMultisig(near, options) { + const newAccountName = generateUniqueString('test'); + const newPublicKey = await near.connection.signer.createKey(newAccountName, networkId); + await near.createAccount(newAccountName, newPublicKey); + // add a confirm key for multisig (contract helper sim) + + try { + const confirmKeyPair = nearApi.utils.KeyPair.fromRandom('ed25519'); + const { publicKey } = confirmKeyPair; + // const account = new nearApi.Account(near.connection, newAccountName); + // await account.addKey(publicKey, account.accountId, nearApi.multisig.MULTISIG_CONFIRM_METHODS, '0') + // create multisig account instance and deploy contract + const accountMultisig = new nearApi.multisig.AccountMultisig(near.connection, newAccountName, options); + accountMultisig.useConfirmKey = async () => { + await near.connection.signer.setKey(networkId, options.masterAccount, confirmKeyPair); + }; + accountMultisig.getRecoveryMethods = () => ({ data: [] }); + accountMultisig.postSignedJson = async (path) => { + switch (path) { + case '/2fa/getAccessKey': return { publicKey }; + } + }; + await accountMultisig.deployMultisig(new Uint8Array([...(await fs.readFile(MULTISIG_WASM_PATH))])); + return accountMultisig; + } catch (e) { + console.log(e); + } +} + +async function deployContract(workingAccount, contractId) { + const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const data = [...(await fs.readFile(HELLO_WASM_PATH))]; + await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); + return new nearApi.Contract(workingAccount, contractId, HELLO_WASM_METHODS); +} + +function sleep(time) { + return new Promise(function (resolve) { + setTimeout(resolve, time); + }); +} + +function waitFor(fn) { + const _waitFor = async (count = 10) => { + try { + return await fn(); + } catch (e) { + if (count > 0) { + await sleep(500); + return _waitFor(count - 1); + } + else throw e; + } + }; + + return _waitFor(); +} + +async function ensureDir(dirpath) { + try { + await fs.mkdir(dirpath, { recursive: true }); + } catch (err) { + if (err.code !== 'EEXIST') throw err; + } +} + +module.exports = { + setUpTestConnection, + networkId, + generateUniqueString, + createAccount, + createAccountMultisig, + deployContract, + sleep, + waitFor, + ensureDir, + HELLO_WASM_PATH, + HELLO_WASM_BALANCE, +}; diff --git a/packages/near-api-js/test/transaction.test.js b/packages/near-api-js/test/transaction.test.js new file mode 100644 index 000000000..1f0c05eeb --- /dev/null +++ b/packages/near-api-js/test/transaction.test.js @@ -0,0 +1,28 @@ +const { functionCall } = require('../src/transaction'); +const BN = require('bn.js'); + +test('functionCall with already serialized args', () => { + const serializedArgs = Buffer.from('{}'); + const action = functionCall('methodName', serializedArgs, new BN(1), new BN(2)); + expect(action).toMatchObject({ + functionCall: { + methodName: 'methodName', + args: serializedArgs, + gas: new BN(1), + deposit: new BN(2) + } + }); +}); + +test('functionCall with non-serialized args', () => { + const serializedArgs = Buffer.from('{}'); + const action = functionCall('methodName', {}, new BN(1), new BN(2)); + expect(action).toMatchObject({ + functionCall: { + methodName: 'methodName', + args: serializedArgs, + gas: new BN(1), + deposit: new BN(2) + } + }); +}); \ No newline at end of file diff --git a/packages/near-api-js/test/utils/format.test.js b/packages/near-api-js/test/utils/format.test.js new file mode 100644 index 000000000..009df1f8d --- /dev/null +++ b/packages/near-api-js/test/utils/format.test.js @@ -0,0 +1,61 @@ +// Unit tests for simple util code + +const nearApi = require('../../src/index'); + +jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; + +test.each` + balance | fracDigits | expected + ${'8999999999837087887'} | ${undefined} | ${'0.000008999999999837087887'} + ${'8099099999837087887'} | ${undefined} | ${'0.000008099099999837087887'} + ${'999998999999999837087887000'} | ${undefined} | ${'999.998999999999837087887'} + ${'1'+'0'.repeat(13)} | ${undefined} | ${'0.00000000001'} + ${'9999989999999998370878870000000'} | ${undefined} | ${'9,999,989.99999999837087887'} + ${'000000000000000000000000'} | ${undefined} | ${'0'} + ${'1000000000000000000000000'} | ${undefined} | ${'1'} + ${'999999999999999999000000'} | ${undefined} | ${'0.999999999999999999'} + ${'999999999999999999000000'} | ${10} | ${'1'} + ${'1003000000000000000000000'} | ${3} | ${'1.003'} + ${'3000000000000000000000'} | ${3} | ${'0.003'} + ${'3000000000000000000000'} | ${4} | ${'0.003'} + ${'3500000000000000000000'} | ${3} | ${'0.004'} + ${'03500000000000000000000'} | ${3} | ${'0.004'} + ${'10000000999999997410000000'} | ${undefined} | ${'10.00000099999999741'} + ${'10100000999999997410000000'} | ${undefined} | ${'10.10000099999999741'} + ${'10040000999999997410000000'} | ${2} | ${'10.04'} + ${'10999000999999997410000000'} | ${2} | ${'11'} + ${'1000000100000000000000000000000'} | ${undefined} | ${'1,000,000.1'} + ${'1000100000000000000000000000000'} | ${undefined} | ${'1,000,100'} + ${'910000000000000000000000'} | ${0} | ${'1'} +`('formatNearAmount($balance, $fracDigits) returns $expected', ({ balance, fracDigits, expected }) => { + expect(nearApi.utils.format.formatNearAmount(balance, fracDigits)).toEqual(expected); +}); + +test.each` + amt | expected + ${null} | ${null} + ${'5.3'} | ${'5300000000000000000000000'} + ${'5'} | ${'5000000000000000000000000'} + ${'1'} | ${'1000000000000000000000000'} + ${'10'} | ${'10000000000000000000000000'} + ${'0.000008999999999837087887'} | ${'8999999999837087887'} + ${'0.000008099099999837087887'} | ${'8099099999837087887'} + ${'999.998999999999837087887000'} | ${'999998999999999837087887000'} + ${'0.000000000000001'} | ${'1000000000'} + ${'0'} | ${'0'} + ${'0.000'} | ${'0'} + ${'0.000001'} | ${'1000000000000000000'} + ${'.000001'} | ${'1000000000000000000'} + ${'000000.000001'} | ${'1000000000000000000'} + ${'1,000,000.1'} | ${'1000000100000000000000000000000'} +`('parseNearAmount($amt) returns $expected', ({ amt, expected }) => { + expect(nearApi.utils.format.parseNearAmount(amt)).toStrictEqual(expected); +}); + +test('parseNearAmount fails when parsing values with ≥25 decimal places', () => { + expect(() => { + nearApi.utils.format.parseNearAmount('0.0000080990999998370878871'); + }).toThrowError( + 'Cannot parse \'0.0000080990999998370878871\' as NEAR amount' + ); +}); diff --git a/packages/near-api-js/test/utils/rpc-errors.test.js b/packages/near-api-js/test/utils/rpc-errors.test.js new file mode 100644 index 000000000..a176edfe3 --- /dev/null +++ b/packages/near-api-js/test/utils/rpc-errors.test.js @@ -0,0 +1,125 @@ +const nearApi = require('../../src/index'); +const { ServerError } = require('../../src/utils/rpc_errors'); +const { + parseRpcError, + formatError, + getErrorTypeFromErrorMessage +} = nearApi.utils.rpc_errors; +describe('rpc-errors', () => { + test('test AccountAlreadyExists error', async () => { + let rpc_error = { + TxExecutionError: { + ActionError: { + index: 1, + kind: {AccountAlreadyExists: {account_id: 'bob.near'}} + } + } + }; + let error = parseRpcError(rpc_error); + expect(error.type === 'AccountAlreadyExists').toBe(true); + expect(error.index).toBe(1); + expect(error.account_id).toBe('bob.near'); + expect(formatError(error.type, error)).toBe('Can\'t create a new account bob.near, because it already exists'); + }); + + test('test ReceiverMismatch error', async () => { + let rpc_error = { + TxExecutionError: { + InvalidTxError: { + InvalidAccessKeyError: { + ReceiverMismatch: { + ak_receiver: 'test.near', + tx_receiver: 'bob.near' + } + } + } + } + }; + let error = parseRpcError(rpc_error); + expect(error.type === 'ReceiverMismatch').toBe(true); + expect(error.ak_receiver).toBe('test.near'); + expect(error.tx_receiver).toBe('bob.near'); + expect(formatError(error.type, error)).toBe( + 'Wrong AccessKey used for transaction: transaction is sent to receiver_id=bob.near, but is signed with function call access key that restricted to only use with receiver_id=test.near. Either change receiver_id in your transaction or switch to use a FullAccessKey.' + ); + }); + + test('test InvalidIteratorIndex error', async () => { + let rpc_error = { + TxExecutionError: { + ActionError: { + FunctionCallError: { + HostError: { + InvalidIteratorIndex: {iterator_index: 42} + } + } + } + } + }; + let error = parseRpcError(rpc_error); + expect(error.type === 'InvalidIteratorIndex').toBe(true); + expect(error.iterator_index).toBe(42); + expect(formatError(error.type, error)).toBe('Iterator index 42 does not exist'); + }); + + test('test ActionError::FunctionCallError::GasLimitExceeded error', async () => { + let rpc_error = { + ActionError: { + 'index': 0, + 'kind': { + FunctionCallError: { + 'HostError': 'GasLimitExceeded' + } + } + } + }; + let error = parseRpcError(rpc_error); + expect(error.type === 'GasLimitExceeded').toBe(true); + + expect(formatError(error.type, error)).toBe('Exceeded the maximum amount of gas allowed to burn per contract'); + }); + + test('test parse error object', async () => { + const errorStr = '{"status":{"Failure":{"ActionError":{"index":0,"kind":{"FunctionCallError":{"EvmError":"ArgumentParseError"}}}}},"transaction":{"signer_id":"test.near","public_key":"ed25519:D5HVgBE8KgXkSirDE4UQ8qwieaLAR4wDDEgrPRtbbNep","nonce":110,"receiver_id":"evm","actions":[{"FunctionCall":{"method_name":"transfer","args":"888ZO7SvECKvfSCJ832LrnFXuF/QKrSGztwAAA==","gas":300000000000000,"deposit":"0"}}],"signature":"ed25519:7JtWQ2Ux63ixaKy7bTDJuRTWnv6XtgE84ejFMMjYGKdv2mLqPiCfkMqbAPt5xwLWwFdKjJniTcxWZe7FdiRWpWv","hash":"E1QorKKEh1WLJwRQSQ1pdzQN3f8yeFsQQ8CbJjnz1ZQe"},"transaction_outcome":{"proof":[],"block_hash":"HXXBPjGp65KaFtam7Xr67B8pZVGujZMZvTmVW6Fy9tXf","id":"E1QorKKEh1WLJwRQSQ1pdzQN3f8yeFsQQ8CbJjnz1ZQe","outcome":{"logs":[],"receipt_ids":["ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL"],"gas_burnt":2428001493624,"tokens_burnt":"2428001493624000000000","executor_id":"test.near","status":{"SuccessReceiptId":"ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL"}}},"receipts_outcome":[{"proof":[],"block_hash":"H6fQCVpxBDv9y2QtmTVHoxHibJvamVsHau7fDi7AmFa2","id":"ZsKetkrZQGVTtmXr2jALgNjzcRqpoQQsk9HdLmFafeL","outcome":{"logs":[],"receipt_ids":["DgRyf1Wv3ZYLFvM8b67k2yZjdmnyUUJtRkTxAwoFi3qD"],"gas_burnt":2428001493624,"tokens_burnt":"2428001493624000000000","executor_id":"evm","status":{"Failure":{"ActionError":{"index":0,"kind":{"FunctionCallError":{"EvmError":"ArgumentParseError"}}}}}}},{"proof":[],"block_hash":"9qNVA235L9XdZ8rZLBAPRNBbiGPyNnMUfpbi9WxbRdbB","id":"DgRyf1Wv3ZYLFvM8b67k2yZjdmnyUUJtRkTxAwoFi3qD","outcome":{"logs":[],"receipt_ids":[],"gas_burnt":0,"tokens_burnt":"0","executor_id":"test.near","status":{"SuccessValue":""}}}]}'; + const error = parseRpcError(JSON.parse(errorStr).status.Failure); + expect(error).toEqual(new ServerError('{"index":0,"kind":{"EvmError":"ArgumentParseError"}}')); + }); + + test('test getErrorTypeFromErrorMessage', () => { + const err1 = 'account random.near does not exist while viewing'; + const err2 = 'Account random2.testnet doesn\'t exist'; + const err3 = 'access key ed25519:DvXowCpBHKdbD2qutgfhG6jvBMaXyUh7DxrDSjkLxMHp does not exist while viewing'; + const err4 = 'wasm execution failed with error: FunctionCallError(CompilationError(CodeDoesNotExist { account_id: "random.testnet" }))'; + const err5 = '[-32000] Server error: Invalid transaction: Transaction nonce 1 must be larger than nonce of the used access key 1'; + expect(getErrorTypeFromErrorMessage(err1)).toEqual('AccountDoesNotExist'); + expect(getErrorTypeFromErrorMessage(err2)).toEqual('AccountDoesNotExist'); + expect(getErrorTypeFromErrorMessage(err3)).toEqual('AccessKeyDoesNotExist'); + expect(getErrorTypeFromErrorMessage(err4)).toEqual('CodeDoesNotExist'); + expect(getErrorTypeFromErrorMessage(err5)).toEqual('InvalidNonce'); + }); + + test('test NotEnoughBalance message uses human readable values', () => { + const error = parseRpcError({ + NotEnoughBalance: { + balance: '1000000000000000000000000', + cost: '10000000000000000000000000', + signer_id: 'test.near' + } + }); + + expect(error.message).toEqual('Sender test.near does not have enough balance 1 for operation costing 10'); + }); + + test('test TriesToStake message uses human readable values', () => { + const error = parseRpcError({ + TriesToStake: { + account_id: 'test.near', + balance: '9000000000000000000000000', + locked: '1000000000000000000000000', + stake: '10000000000000000000000000', + } + }); + + expect(error.message).toEqual('Account test.near tried to stake 10, but has staked 1 and only has 9'); + }); +}); diff --git a/packages/near-api-js/test/utils/web.test.js b/packages/near-api-js/test/utils/web.test.js new file mode 100644 index 000000000..880e9bd66 --- /dev/null +++ b/packages/near-api-js/test/utils/web.test.js @@ -0,0 +1,27 @@ +const nearApi = require('../../src/index'); +const { web } = nearApi.utils; + +describe('web', () => { + test('string parameter in fetchJson', async () => { + const RPC_URL = 'https://rpc.testnet.near.org'; + const statusRequest = { + 'jsonrpc': '2.0', + 'id': 'dontcare', + 'method': 'status', + 'params': [] + }; + const result = await web.fetchJson(RPC_URL, JSON.stringify(statusRequest)); + expect(result.result.chain_id).toBe('testnet'); + }); + test('object parameter in fetchJson', async () => { + const connection = { url: 'https://rpc.testnet.near.org' }; + const statusRequest = { + 'jsonrpc': '2.0', + 'id': 'dontcare', + 'method': 'status', + 'params': [] + }; + const result = await web.fetchJson(connection, JSON.stringify(statusRequest)); + expect(result.result.chain_id).toBe('testnet'); + }); +}); diff --git a/packages/near-api-js/test/validator.test.js b/packages/near-api-js/test/validator.test.js new file mode 100644 index 000000000..20e280fa3 --- /dev/null +++ b/packages/near-api-js/test/validator.test.js @@ -0,0 +1,36 @@ + +const nearApi = require('../src/index'); +const BN = require('bn.js'); + +test('find seat price', async () => { + expect(nearApi.validators.findSeatPrice( + [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 2, [1, 6250], 49 + )).toEqual(new BN('101')); + expect(nearApi.validators.findSeatPrice( + [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 3, [1, 6250] + )).toEqual(new BN('101')); + expect(nearApi.validators.findSeatPrice( + [{stake: '1000000'}, {stake: '1000000'}, {stake: '100'}], 4, [1, 6250], 49 + )).toEqual(new BN('320')); + expect(nearApi.validators.findSeatPrice( + [{stake: '1000'}, {stake: '1000'}, {stake: '200'}], 100, [1, 25] + )).toEqual(new BN('88')); +}); + +test('diff validators', async () => { + expect(nearApi.validators.diffEpochValidators( + [{account_id: 'x', stake: '10'}], + [{ account_id: 'x', stake: '10' }] + )).toEqual({newValidators: [], removedValidators: [], changedValidators: []}); + expect(nearApi.validators.diffEpochValidators( + [{ account_id: 'x', stake: '10' }, { account_id: 'y', stake: '10' }], + [{ account_id: 'x', stake: '11' }, { account_id: 'z', stake: '11' }] + )).toEqual({ + newValidators: [{ account_id: 'z', stake: '11' }], + removedValidators: [{ account_id: 'y', stake: '10' }], + changedValidators: [{ + current: { account_id: 'x', stake: '10' }, + next: { account_id: 'x', stake: '11' } + }] + }); +}); \ No newline at end of file diff --git a/packages/near-api-js/test/wallet-account.test.js b/packages/near-api-js/test/wallet-account.test.js new file mode 100644 index 000000000..64c32ae05 --- /dev/null +++ b/packages/near-api-js/test/wallet-account.test.js @@ -0,0 +1,511 @@ +const url = require('url'); +const localStorage = require('localstorage-memory'); +const BN = require('bn.js'); + +// If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 +const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; + +let lastRedirectUrl; +let lastTransaction; +global.window = { + localStorage +}; +global.document = { + title: 'documentTitle' +}; +const nearApi = require('../src/index'); + +let history; +let nearFake; +let walletConnection; +let keyStore = new nearApi.keyStores.InMemoryKeyStore(); +beforeEach(() => { + keyStore.clear(); + nearFake = { + config: { + networkId: 'networkId', + contractName: 'contractId', + walletUrl: 'http://example.com/wallet', + }, + connection: { + networkId: 'networkId', + signer: new nearApi.InMemorySigner(keyStore) + }, + account() { + return { + state() {} + }; + } + }; + lastRedirectUrl = null; + history = []; + Object.assign(global.window, { + location: { + href: 'http://example.com/location', + assign(url) { + lastRedirectUrl = url; + } + }, + history: { + replaceState: (state, title, url) => history.push([state, title, url]) + } + }); + walletConnection = new nearApi.WalletConnection(nearFake, ''); +}); + +it('not signed in by default', () => { + expect(walletConnection.isSignedIn()).not.toBeTruthy(); +}); + +it('throws if non string appKeyPrefix', () => { + expect(() => new nearApi.WalletConnection(nearFake)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); +}); + +describe('fails gracefully on the server side (without window)', () => { + const windowValueBefore = global.window; + + beforeEach(() => { + global.window = undefined; + keyStore.clear(); + }); + + afterEach(() => { + global.window = windowValueBefore; + }); + + it('does not throw on instantiation', () => { + expect(() => new nearApi.WalletConnection(nearFake, '')).not.toThrowError(); + }); + + it('throws if non string appKeyPrefix in server context', () => { + expect(() => new nearApi.WalletConnection(nearFake)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); + expect(() => new nearApi.WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); + }); + + it('returns an empty string as accountId', () => { + const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + expect(serverWalletConnection.getAccountId()).toEqual(''); + }); + + it('returns false as isSignedIn', () => { + const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + expect(serverWalletConnection.isSignedIn()).toEqual(false); + }); + + it('throws explicit error when calling other methods on the instance', () => { + const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + expect(() => serverWalletConnection.requestSignIn('signInContract', 'signInTitle', 'http://example.com/success', 'http://example.com/fail')).toThrow(/please ensure you are using WalletConnection on the browser/); + }); + + it('can access other props on the instance', () => { + const serverWalletConnection = new nearApi.WalletConnection(nearFake, ''); + expect(serverWalletConnection['randomValue']).toEqual(undefined); + }); +}); + +describe('can request sign in', () => { + beforeEach(() => keyStore.clear()); + + it('V2', () => { + return walletConnection.requestSignIn({ + contractId: 'signInContract', + successUrl: 'http://example.com/success', + failureUrl: 'http://example.com/fail' + }); + }); + + afterEach(async () => { + let accounts = await keyStore.getAccounts('networkId'); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toMatch(/^pending_key.+/); + expect(url.parse(lastRedirectUrl, true)).toMatchObject({ + protocol: 'http:', + host: 'example.com', + query: { + contract_id: 'signInContract', + success_url: 'http://example.com/success', + failure_url: 'http://example.com/fail', + public_key: (await keyStore.getKey('networkId', accounts[0])).publicKey.toString() + } + }); + }); +}); + +it('can request sign in with methodNames', async () => { + await walletConnection.requestSignIn({ + contractId: 'signInContract', + methodNames: ['hello', 'goodbye'], + successUrl: 'http://example.com/success', + failureUrl: 'http://example.com/fail' + }); + + let accounts = await keyStore.getAccounts('networkId'); + expect(accounts).toHaveLength(1); + expect(accounts[0]).toMatch(/^pending_key.+/); + expect(url.parse(lastRedirectUrl, true)).toMatchObject({ + protocol: 'http:', + host: 'example.com', + query: { + contract_id: 'signInContract', + methodNames: ['hello', 'goodbye'], + success_url: 'http://example.com/success', + failure_url: 'http://example.com/fail', + public_key: (await keyStore.getKey('networkId', accounts[0])).publicKey.toString() + } + }); +}); + +it('can complete sign in', async () => { + const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + global.window.location.href = `http://example.com/location?account_id=near.account&public_key=${keyPair.publicKey}`; + await keyStore.setKey('networkId', 'pending_key' + keyPair.publicKey, keyPair); + + await walletConnection._completeSignInWithAccessKey(); + + expect(await keyStore.getKey('networkId', 'near.account')).toEqual(keyPair); + expect(localStorage.getItem('contractId_wallet_auth_key')); + expect(history.slice(1)).toEqual([ + [{}, 'documentTitle', 'http://example.com/location'] + ]); +}); + +it('Promise until complete sign in', async () => { + const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + global.window.location.href = `http://example.com/location?account_id=near2.account&public_key=${keyPair.publicKey}`; + await keyStore.setKey('networkId', 'pending_key' + keyPair.publicKey, keyPair); + + const newWalletConn = new nearApi.WalletConnection(nearFake, 'promise_on_complete_signin'); + + expect(newWalletConn.isSignedIn()).toEqual(false); + expect(await newWalletConn.isSignedInAsync()).toEqual(true); + expect(await keyStore.getKey('networkId', 'near2.account')).toEqual(keyPair); + expect(localStorage.getItem('promise_on_complete_signin_wallet_auth_key')); + expect(history).toEqual([ + [{}, 'documentTitle', 'http://example.com/location'] + ]); +}); + +const BLOCK_HASH = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; +const blockHash = nearApi.utils.serialize.base_decode(BLOCK_HASH); +function createTransferTx() { + const actions = [ + nearApi.transactions.transfer(1), + ]; + return nearApi.transactions.createTransaction( + 'test.near', + nearApi.utils.PublicKey.fromString('Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC'), + 'whatever.near', + 1, + actions, + blockHash); +} + +describe('can request transaction signing', () => { + it('V1', async () => { + await walletConnection.requestSignTransactions({ + transactions: [createTransferTx()], + callbackUrl: 'http://example.com/callback' + }); + + expect(url.parse(lastRedirectUrl, true)).toMatchObject({ + protocol: 'http:', + host: 'example.com', + query: { + callbackUrl: 'http://example.com/callback', + transactions: 'CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAA==' + } + }); + }); + + it('V2', async () => { + await walletConnection.requestSignTransactions({ + transactions: [createTransferTx()], + meta: 'something', + callbackUrl: 'http://example.com/after' + }); + + expect(url.parse(lastRedirectUrl, true)).toMatchObject({ + protocol: 'http:', + host: 'example.com', + query: { + meta: 'something', + callbackUrl: 'http://example.com/after', + transactions: 'CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAA==' + } + }); + }); +}); + +function parseTransactionsFromUrl(urlToParse, callbackUrl = 'http://example.com/location') { + const parsedUrl = url.parse(urlToParse, true); + expect(parsedUrl).toMatchObject({ + protocol: 'http:', + host: 'example.com', + query: { + callbackUrl + } + }); + const transactions = parsedUrl.query.transactions.split(',') + .map(txBase64 => nearApi.utils.serialize.deserialize( + nearApi.transactions.SCHEMA, + nearApi.transactions.Transaction, + Buffer.from(txBase64, 'base64'))); + return transactions; +} + +function setupWalletConnectionForSigning({ allKeys, accountAccessKeys }) { + walletConnection._authData = { + allKeys: allKeys, + accountId: 'signer.near' + }; + nearFake.connection.provider = { + query(params) { + if (params.request_type === 'view_account' && params.account_id === 'signer.near') { + return { }; + } + if (params.request_type === 'view_access_key_list' && params.account_id === 'signer.near') { + return { keys: accountAccessKeys }; + } + if (params.request_type === 'view_access_key' && params.account_id === 'signer.near') { + for (let accessKey of accountAccessKeys) { + if (accessKey.public_key === params.public_key) { + return accessKey; + } + } + } + fail(`Unexpected query: ${JSON.stringify(params)}`); + }, + sendTransaction(signedTransaction) { + lastTransaction = signedTransaction; + return { + transaction_outcome: { outcome: { logs: [] } }, + receipts_outcome: [] + }; + }, + block() { + return { + header: { + hash: BLOCK_HASH + } + }; + } + }; +} + +describe('requests transaction signing automatically when there is no local key', () => { + const keyPair = nearApi.KeyPair.fromRandom('ed25519'); + let transactions; + beforeEach(() => { + setupWalletConnectionForSigning({ + allKeys: [ 'no_such_access_key', keyPair.publicKey.toString() ], + accountAccessKeys: [{ + access_key: { + nonce: 1, + permission: 'FullAccess' + }, + public_key: keyPair.publicKey.toString() + }] + }); + }); + + it('V2', async() => { + try { + await walletConnection.account().signAndSendTransaction({ + receiverId: 'receiver.near', + actions: [nearApi.transactions.transfer(1)], + walletCallbackUrl: 'http://callback.com/callback' + }); + fail('expected to throw'); + } catch (e) { + expect(e.message).toEqual('Failed to redirect to sign transaction'); + } + transactions = parseTransactionsFromUrl(lastRedirectUrl, 'http://callback.com/callback'); + }); + + afterEach(() => { + expect(transactions).toHaveLength(1); + expect(transactions[0]).toMatchObject({ + signerId: 'signer.near', + // nonce: new BN(2) + receiverId: 'receiver.near', + actions: [{ + transfer: { + // deposit: new BN(1) + } + }] + }); + expect(transactions[0].nonce.toString()).toEqual('2'); + expect(transactions[0].actions[0].transfer.deposit.toString()).toEqual('1'); + expect(Buffer.from(transactions[0].publicKey.data)).toEqual(Buffer.from(keyPair.publicKey.data)); + }); +}); + +describe('requests transaction signing automatically when function call has attached deposit', () => { + beforeEach(async() => { + const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + const walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + setupWalletConnectionForSigning({ + allKeys: [ walletKeyPair.publicKey.toString() ], + accountAccessKeys: [{ + access_key: { + nonce: 1, + permission: { + FunctionCall: { + allowance: '1000000000', + receiver_id: 'receiver.near', + method_names: [] + } + } + }, + public_key: localKeyPair.publicKey.toString() + }, { + access_key: { + nonce: 1, + permission: 'FullAccess' + }, + public_key: walletKeyPair.publicKey.toString() + }] + }); + await keyStore.setKey('networkId', 'signer.near', localKeyPair); + }); + + it('V2', async() => { + try { + await walletConnection.account().signAndSendTransaction({ + receiverId: 'receiver.near', + actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))], + walletCallbackUrl: 'http://example.com/after', + walletMeta: 'someStuff' + }); + fail('expected to throw'); + } catch (e) { + expect(e.message).toEqual('Failed to redirect to sign transaction'); + } + + const transactions = parseTransactionsFromUrl(lastRedirectUrl, 'http://example.com/after'); + expect(transactions).toHaveLength(1); + }); +}); + +describe('requests transaction signing with 2fa access key', () => { + beforeEach(async () => { + let localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + let walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + setupWalletConnectionForSigning({ + allKeys: [ walletKeyPair.publicKey.toString() ], + accountAccessKeys: [{ + access_key: { + nonce: 1, + permission: { + FunctionCall: { + allowance: '1000000000', + receiver_id: 'signer.near', + method_names: [MULTISIG_HAS_METHOD] + } + } + }, + public_key: localKeyPair.publicKey.toString() + }] + }); + await keyStore.setKey('networkId', 'signer.near', localKeyPair); + }); + + it('V2', async () => { + try { + const res = await walletConnection.account().signAndSendTransaction({ + receiverId: 'receiver.near', + actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] + }); + + // multisig access key is accepted res is object representing transaction, populated upon wallet redirect to app + expect(res).toHaveProperty('transaction_outcome'); + expect(res).toHaveProperty('receipts_outcome'); + } catch (e) { + fail('expected transaction outcome'); + } + }); +}); + +describe('fails requests transaction signing without 2fa access key', () => { + beforeEach(async () => { + const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + const walletKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + setupWalletConnectionForSigning({ + allKeys: [ walletKeyPair.publicKey.toString() ], + accountAccessKeys: [{ + access_key: { + nonce: 1, + permission: { + FunctionCall: { + allowance: '1000000000', + receiver_id: 'signer.near', + method_names: ['not_a_valid_2fa_method'] + } + } + }, + public_key: localKeyPair.publicKey.toString() + }] + }); + await keyStore.setKey('networkId', 'signer.near', localKeyPair); + }); + + it('V2', () => { + return expect( + walletConnection.account().signAndSendTransaction({ + receiverId: 'receiver.near', + actions: [nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('1'))] + }) + ).rejects.toThrow('Cannot find matching key for transaction sent to receiver.near'); + }); +}); + +describe('can sign transaction locally when function call has no attached deposit', () => { + beforeEach(async () => { + const localKeyPair = nearApi.KeyPair.fromRandom('ed25519'); + setupWalletConnectionForSigning({ + allKeys: [ /* no keys in wallet needed */ ], + accountAccessKeys: [{ + access_key: { + nonce: 1, + permission: { + FunctionCall: { + allowance: '1000000000', + receiver_id: 'receiver.near', + method_names: [] + } + } + }, + public_key: localKeyPair.publicKey.toString() + }] + }); + await keyStore.setKey('networkId', 'signer.near', localKeyPair); + }); + + it.each([ + nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1'), new BN('0')), + nearApi.transactions.functionCall('someMethod', new Uint8Array(), new BN('1')), + nearApi.transactions.functionCall('someMethod', new Uint8Array()) + ])('V2', async (functionCall) => { + await walletConnection.account().signAndSendTransaction({ + receiverId: 'receiver.near', + actions: [ functionCall ] + }); + // NOTE: Transaction gets signed without wallet in this test + expect(lastTransaction).toMatchObject({ + transaction: { + receiverId: 'receiver.near', + signerId: 'signer.near', + actions: [{ + functionCall: { + methodName: 'someMethod', + } + }] + } + }); + }); +}); diff --git a/packages/near-api-js/test/wasm/multisig.wasm b/packages/near-api-js/test/wasm/multisig.wasm new file mode 100755 index 0000000000000000000000000000000000000000..c904c5b7842091e6eed8cd0d7822c705091a1613 GIT binary patch literal 356105 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�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 Date: Mon, 9 Jan 2023 12:23:57 -0800 Subject: [PATCH 63/84] test: missing test eslint --- packages/accounts/test/.eslintrc.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 packages/accounts/test/.eslintrc.yml diff --git a/packages/accounts/test/.eslintrc.yml b/packages/accounts/test/.eslintrc.yml new file mode 100644 index 000000000..0fae1d994 --- /dev/null +++ b/packages/accounts/test/.eslintrc.yml @@ -0,0 +1,7 @@ +extends: '../../../.eslintrc.js.yml' +env: + jest: true +globals: + jasmine: true + window: false + fail: true From f9ea92b3bd8c729d9e97228607d4ed8e0ca03d44 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 13:00:20 -0800 Subject: [PATCH 64/84] test: temporarily test in dependency order to resolve issues with duplicate keys --- turbo.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/turbo.json b/turbo.json index d0dfc9fe8..8d9d5512e 100644 --- a/turbo.json +++ b/turbo.json @@ -3,14 +3,16 @@ "pipeline": { "build": { "dependsOn": ["^build"], - "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"], + "inputs": ["src/**/*.ts", "test/**/*.js"], "outputs": ["dist/"] }, "test": { - "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"] + /* TODO remove once near-api-js tests are removed or packages/accounts/tests gets its own set of keys */ + "dependsOn": ["^test"], + "inputs": ["src/**/*.ts", "test/**/*.js"] }, "lint": { - "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"], + "inputs": ["src/**/*.ts", "test/**/*.js"], "outputs": [] }, "lint:js": { From b0ef6c084bf33a8c8b84e71b6561d8a67123b3f7 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 9 Jan 2023 17:43:36 -0800 Subject: [PATCH 65/84] docs: readmes --- README.md | 14 ++++++++++++++ packages/accounts/README.md | 19 +++++++++++++++++++ packages/keypairs/README.md | 16 ++++++++++++++++ packages/keystores-browser/README.md | 12 ++++++++++++ packages/keystores-node/README.md | 12 ++++++++++++ packages/keystores/README.md | 14 ++++++++++++++ packages/providers/README.md | 15 +++++++++++++++ packages/signers/README.md | 13 +++++++++++++ packages/transactions/README.md | 16 ++++++++++++++++ packages/types/README.md | 8 ++++++++ packages/utils/README.md | 15 +++++++++++++++ packages/wallet-account/README.md | 13 +++++++++++++ 12 files changed, 167 insertions(+) create mode 100644 packages/accounts/README.md create mode 100644 packages/keypairs/README.md create mode 100644 packages/keystores-browser/README.md create mode 100644 packages/keystores-node/README.md create mode 100644 packages/keystores/README.md create mode 100644 packages/providers/README.md create mode 100644 packages/signers/README.md create mode 100644 packages/transactions/README.md create mode 100644 packages/types/README.md create mode 100644 packages/utils/README.md create mode 100644 packages/wallet-account/README.md diff --git a/README.md b/README.md index a49eec7f6..4ab50a2ea 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,20 @@ Follow next steps: 2. Fetch new schema: `node fetch_error_schema.js` 3. `pnpm build` to update `lib/**.js` files + +## Packages + +- [accounts](https://github.com/near/near-api-js/tree/master/packages/accounts) account creation & management +- [keypairs](https://github.com/near/near-api-js/tree/master/packages/keypairs) cryptographic key pairs & signing +- [keystores](https://github.com/near/near-api-js/tree/master/packages/keystores) general-purpose key persistence & management +- [keystores-browser](https://github.com/near/near-api-js/tree/master/packages/keystores-browser) browser keystores +- [keystores-node](https://github.com/near/near-api-js/tree/master/packages/keystores-node) NodeJS keystores +- [providers](https://github.com/near/near-api-js/tree/master/packages/providers) RPC interaction +- [transactions](https://github.com/near/near-api-js/tree/master/packages/transactions) transaction composition & signing +- [types](https://github.com/near/near-api-js/tree/master/packages/types) common types +- [utils](https://github.com/near/near-api-js/tree/master/packages/types) common methods +- [wallet-account](https://github.com/near/near-api-js/tree/master/packages/wallet-account) accounts in browser-based wallets + ## License This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). diff --git a/packages/accounts/README.md b/packages/accounts/README.md new file mode 100644 index 000000000..45153e405 --- /dev/null +++ b/packages/accounts/README.md @@ -0,0 +1,19 @@ +# @near-js/accounts + +A collection of classes, functions, and types for interacting with accounts and contracts. + +## Modules + +- [Account](src/account.ts) a class with methods to transfer NEAR, manage account keys, sign transactions, etc. +- [AccountMultisig](src/account_multisig.ts) a [multisig](https://github.com/near/core-contracts/tree/master/multisig) deployed `Account` requiring multiple keys to sign transactions +- [Account2FA](src/account_2fa.ts) extension of `AccountMultisig` used in conjunction with 2FA provided by [near-contract-helper](https://github.com/near/near-contract-helper) +- [AccountCreator](src/account_creator.ts) classes for creating NEAR accounts +- [Contract](src/contract.ts) represents a deployed smart contract with view and/or change methods +- [Connection](src/connection.ts) a record containing the information required to connect to NEAR RPC +- [Constants](src/constants.ts) account-specific constants +- [Types](src/types.ts) account-specific types + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/keypairs/README.md b/packages/keypairs/README.md new file mode 100644 index 000000000..3c8bfccb9 --- /dev/null +++ b/packages/keypairs/README.md @@ -0,0 +1,16 @@ +# @near-js/keypairs + +A collection of classes and types for representing cryptographic key pairs. + +## Modules + +- [PublicKey](src/public_key.ts) representation of a public key capable of verifying signatures +- [KeyPairBase](src/key_pair_base.ts) abstract class representing a key pair +- [KeyPair](src/key_pair.ts) abstract extension of `KeyPairBase` with static methods for parsing and generating key pairs +- [KeyPairEd25519](src/key_pair_ed25519.ts) implementation of `KeyPair` using [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) +- [Constants](src/constants.ts) keypair-specific constants + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/keystores-browser/README.md b/packages/keystores-browser/README.md new file mode 100644 index 000000000..8d47cf76a --- /dev/null +++ b/packages/keystores-browser/README.md @@ -0,0 +1,12 @@ +# @near-js/keystores-browser + +A collection of classes for managing keys in a web browser execution context. + +## Modules + +- [BrowserLocalStorageKeyStore](src/browser_local_storage_key_store.ts) implementation of [KeyStore](../keystores/src/keystore.ts) storing unencrypted keys in browser LocalStorage + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/keystores-node/README.md b/packages/keystores-node/README.md new file mode 100644 index 000000000..f0a935d29 --- /dev/null +++ b/packages/keystores-node/README.md @@ -0,0 +1,12 @@ +# @near-js/keystores-node + +A collection of classes and functions for managing keys in NodeJS execution context. + +## Modules + +- [UnencryptedFileSystemKeyStore](src/unencrypted_file_system_keystore.ts) implementation of [KeyStore](../keystores/src/keystore.ts) storing unencrypted keys on the local filesystem + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/keystores/README.md b/packages/keystores/README.md new file mode 100644 index 000000000..e14707454 --- /dev/null +++ b/packages/keystores/README.md @@ -0,0 +1,14 @@ +# @near-js/keystores + +A collection of classes for managing NEAR-compatible cryptographic keys. + +## Modules + +- [KeyStore](src/keystore.ts) abstract class for managing account keys +- [InMemoryKeyStore](src/in_memory_key_store.ts) implementation of `KeyStore` using an in-memory data structure local to the instance +- [MergeKeyStore](src/merge_key_store.ts) implementation of `KeyStore` aggregating multiple `KeyStore` implementations + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/providers/README.md b/packages/providers/README.md new file mode 100644 index 000000000..9373b95c2 --- /dev/null +++ b/packages/providers/README.md @@ -0,0 +1,15 @@ +# @near-js/providers + +A collection of classes, functions, and types for communicating with the NEAR blockchain directly. For use with both client- and server-side JavaScript execution contexts. + +## Modules + +- [Provider](src/provider.ts) abstract class for interacting with NEAR RPC +- [JsonRpcProvider](src/json-rpc-provider.ts) implementation of `Provider` for [JSON-RPC](https://www.jsonrpc.org/) +- [fetch](src/fetch.ts) NodeJS `fetch` implementation +- [fetchJson](src/fetch_json.ts) low-level function for fetching and parsing RPC data + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/signers/README.md b/packages/signers/README.md new file mode 100644 index 000000000..c70a03991 --- /dev/null +++ b/packages/signers/README.md @@ -0,0 +1,13 @@ +# @near-js/signers + +A collection of classes and types to facilitate cryptographic signing. + +## Modules + +- [Signer](src/signer.ts) abstract class for cryptographic signing +- [InMemorySigner](src/in_memory_signer.ts) implementation of `Signer` using [InMemoryKeyStore](../keystores/src/in_memory_key_store.ts) to provide keys + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/transactions/README.md b/packages/transactions/README.md new file mode 100644 index 000000000..ba88ef08f --- /dev/null +++ b/packages/transactions/README.md @@ -0,0 +1,16 @@ +# @near-js/transactions + +A collection of classes, functions, and types for composing, serializing, and signing NEAR transactions. + +## Modules + +- [actionCreators](src/action_creators.ts) functions for composing actions +- [Actions](src/actions.ts) classes for actions +- [Schema](src/schema.ts) classes and types concerned with (de-)serialization of transactions +- [createTransaction](src/create_transaction.ts) function for composing a transaction +- [signTransaction](src/sign.ts) function for signing a transaction + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/types/README.md b/packages/types/README.md new file mode 100644 index 000000000..33ccb5eb7 --- /dev/null +++ b/packages/types/README.md @@ -0,0 +1,8 @@ +# @near-js/types + +A collection of commonly-used classes and types. + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 000000000..6da1b4d45 --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1,15 @@ +# @near-js/utils + +A collection of commonly-used functions and constants. + +## Modules + +- [Format](src/format.ts) NEAR denomination formatting functions +- [Logging](src/logging.ts) functions for printing formatted RPC output +- [Provider](src/provider.ts) functions for parsing RPC output +- [Validators](src/validators.ts) functions for querying blockchain validators + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/wallet-account/README.md b/packages/wallet-account/README.md new file mode 100644 index 000000000..665024342 --- /dev/null +++ b/packages/wallet-account/README.md @@ -0,0 +1,13 @@ +# @near-js/wallet-account + +A collection of classes and types for working with accounts within browser-based wallets. + +## Modules + +- [Near](src/near.ts) a general purpose class for configuring and working with the NEAR blockchain +- [WalletAccount](src/wallet_account.ts) an [Account](../accounts/src/account.ts) implementation for use in a browser-based wallet + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. From 96987641caee587177f4b6b3345d9371598a32b7 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 11 Jan 2023 15:30:48 -0800 Subject: [PATCH 66/84] docs: changeset --- .changeset/slimy-baboons-itch.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .changeset/slimy-baboons-itch.md diff --git a/.changeset/slimy-baboons-itch.md b/.changeset/slimy-baboons-itch.md new file mode 100644 index 000000000..23b8f2258 --- /dev/null +++ b/.changeset/slimy-baboons-itch.md @@ -0,0 +1,16 @@ +--- +"near-api-js": minor +"@near-js/accounts": patch +"@near-js/keypairs": patch +"@near-js/keystores": patch +"@near-js/keystores-browser": patch +"@near-js/keystores-node": patch +"@near-js/providers": patch +"@near-js/signers": patch +"@near-js/transactions": patch +"@near-js/types": patch +"@near-js/utils": patch +"@near-js/wallet-account": patch +--- + +Major functionality in near-api-js has now been broken up into packages under @near-js From fcbf76b4f932784b0ff6f695a1d1afef59b43f93 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 13 Jan 2023 11:06:18 -0800 Subject: [PATCH 67/84] refactor: rename keypairs to crypto --- packages/accounts/package.json | 2 +- packages/accounts/src/account.ts | 2 +- packages/accounts/src/account_2fa.ts | 2 +- packages/accounts/src/account_creator.ts | 2 +- .../accounts/test/account.access_key.test.js | 2 +- .../accounts/test/account_multisig.test.js | 2 +- packages/accounts/test/test-utils.js | 2 +- packages/{keypairs => crypto}/README.md | 6 ++-- packages/{keypairs => crypto}/jest.config.js | 0 packages/{keypairs => crypto}/package.json | 2 +- .../{keypairs => crypto}/src/constants.ts | 0 packages/{keypairs => crypto}/src/index.ts | 0 packages/{keypairs => crypto}/src/key_pair.ts | 0 .../{keypairs => crypto}/src/key_pair_base.ts | 0 .../src/key_pair_ed25519.ts | 0 .../{keypairs => crypto}/src/public_key.ts | 0 .../test/key_pair.test.js | 0 packages/{keypairs => crypto}/tsconfig.json | 0 packages/keystores-browser/package.json | 2 +- .../src/browser_local_storage_key_store.ts | 2 +- .../keystores-browser/test/keystore_common.js | 2 +- packages/keystores-node/package.json | 2 +- .../src/unencrypted_file_system_keystore.ts | 2 +- .../keystores-node/test/keystore_common.js | 2 +- .../unencrypted_file_system_keystore.test.js | 2 +- packages/keystores/package.json | 2 +- packages/keystores/src/in_memory_key_store.ts | 2 +- packages/keystores/src/keystore.ts | 2 +- packages/keystores/src/merge_key_store.ts | 2 +- packages/keystores/test/keystore_common.js | 2 +- .../keystores/test/merge_keystore.test.js | 2 +- packages/near-api-js/package.json | 2 +- packages/near-api-js/src/utils/key_pair.ts | 2 +- packages/signers/package.json | 2 +- packages/signers/src/in_memory_signer.ts | 2 +- packages/signers/src/signer.ts | 2 +- packages/transactions/package.json | 2 +- packages/transactions/src/action_creators.ts | 2 +- packages/transactions/src/actions.ts | 2 +- .../transactions/src/create_transaction.ts | 2 +- packages/transactions/src/schema.ts | 2 +- packages/transactions/test/serialize.test.js | 2 +- packages/wallet-account/package.json | 2 +- packages/wallet-account/src/near.ts | 2 +- packages/wallet-account/src/wallet_account.ts | 2 +- .../test/wallet_account.test.js | 2 +- pnpm-lock.yaml | 34 +++++++++---------- 47 files changed, 56 insertions(+), 56 deletions(-) rename packages/{keypairs => crypto}/README.md (82%) rename packages/{keypairs => crypto}/jest.config.js (100%) rename packages/{keypairs => crypto}/package.json (96%) rename packages/{keypairs => crypto}/src/constants.ts (100%) rename packages/{keypairs => crypto}/src/index.ts (100%) rename packages/{keypairs => crypto}/src/key_pair.ts (100%) rename packages/{keypairs => crypto}/src/key_pair_base.ts (100%) rename packages/{keypairs => crypto}/src/key_pair_ed25519.ts (100%) rename packages/{keypairs => crypto}/src/public_key.ts (100%) rename packages/{keypairs => crypto}/test/key_pair.test.js (100%) rename packages/{keypairs => crypto}/tsconfig.json (100%) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index fb0687e8f..78a42d831 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/providers": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index c82dfa7bf..075f29d6d 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -15,7 +15,7 @@ import { SignedTransaction, stringifyJsonOrBytes } from '@near-js/transactions'; -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import { PositionalArgsError, FinalExecutionOutcome, diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index f359e52f0..e73e811e2 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -1,6 +1,6 @@ 'use strict'; -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; import { fetchJson } from '@near-js/providers'; import { actionCreators } from '@near-js/transactions'; diff --git a/packages/accounts/src/account_creator.ts b/packages/accounts/src/account_creator.ts index 899bd9c18..29d306f80 100644 --- a/packages/accounts/src/account_creator.ts +++ b/packages/accounts/src/account_creator.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import { fetchJson } from '@near-js/providers'; import BN from 'bn.js'; diff --git a/packages/accounts/test/account.access_key.test.js b/packages/accounts/test/account.access_key.test.js index db7197b96..416ee2559 100644 --- a/packages/accounts/test/account.access_key.test.js +++ b/packages/accounts/test/account.access_key.test.js @@ -1,4 +1,4 @@ -const { KeyPair } = require('@near-js/keypairs'); +const { KeyPair } = require('@near-js/crypto'); const testUtils = require('./test-utils'); diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index 26787cc26..30cfa9dcf 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -1,6 +1,6 @@ /* global BigInt */ const { parseNearAmount } = require('@near-js/utils'); -const { KeyPair } = require('@near-js/keypairs'); +const { KeyPair } = require('@near-js/crypto'); const { InMemorySigner } = require('@near-js/signers'); const { actionCreators } = require('@near-js/transactions'); const BN = require('bn.js'); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index e7849d0bf..7875a4b17 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -1,4 +1,4 @@ -const { KeyPair } = require('@near-js/keypairs'); +const { KeyPair } = require('@near-js/crypto'); const { InMemoryKeyStore } = require('@near-js/keystores'); const BN = require('bn.js'); const fs = require('fs').promises; diff --git a/packages/keypairs/README.md b/packages/crypto/README.md similarity index 82% rename from packages/keypairs/README.md rename to packages/crypto/README.md index 3c8bfccb9..2b21f6c89 100644 --- a/packages/keypairs/README.md +++ b/packages/crypto/README.md @@ -1,13 +1,13 @@ -# @near-js/keypairs +# @near-js/crypto -A collection of classes and types for representing cryptographic key pairs. +A collection of classes and types for working with cryptographic key pairs. ## Modules - [PublicKey](src/public_key.ts) representation of a public key capable of verifying signatures - [KeyPairBase](src/key_pair_base.ts) abstract class representing a key pair - [KeyPair](src/key_pair.ts) abstract extension of `KeyPairBase` with static methods for parsing and generating key pairs -- [KeyPairEd25519](src/key_pair_ed25519.ts) implementation of `KeyPair` using [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) +- [KeyPairEd25519](src/key_pair_ed25519.ts) implementation of `KeyPairBase` using [Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) - [Constants](src/constants.ts) keypair-specific constants # License diff --git a/packages/keypairs/jest.config.js b/packages/crypto/jest.config.js similarity index 100% rename from packages/keypairs/jest.config.js rename to packages/crypto/jest.config.js diff --git a/packages/keypairs/package.json b/packages/crypto/package.json similarity index 96% rename from packages/keypairs/package.json rename to packages/crypto/package.json index 74d40243d..b69db6503 100644 --- a/packages/keypairs/package.json +++ b/packages/crypto/package.json @@ -1,5 +1,5 @@ { - "name": "@near-js/keypairs", + "name": "@near-js/crypto", "version": "0.0.1", "description": "Abstractions around NEAR-compatible elliptical curves and cryptographic keys", "main": "lib/index.js", diff --git a/packages/keypairs/src/constants.ts b/packages/crypto/src/constants.ts similarity index 100% rename from packages/keypairs/src/constants.ts rename to packages/crypto/src/constants.ts diff --git a/packages/keypairs/src/index.ts b/packages/crypto/src/index.ts similarity index 100% rename from packages/keypairs/src/index.ts rename to packages/crypto/src/index.ts diff --git a/packages/keypairs/src/key_pair.ts b/packages/crypto/src/key_pair.ts similarity index 100% rename from packages/keypairs/src/key_pair.ts rename to packages/crypto/src/key_pair.ts diff --git a/packages/keypairs/src/key_pair_base.ts b/packages/crypto/src/key_pair_base.ts similarity index 100% rename from packages/keypairs/src/key_pair_base.ts rename to packages/crypto/src/key_pair_base.ts diff --git a/packages/keypairs/src/key_pair_ed25519.ts b/packages/crypto/src/key_pair_ed25519.ts similarity index 100% rename from packages/keypairs/src/key_pair_ed25519.ts rename to packages/crypto/src/key_pair_ed25519.ts diff --git a/packages/keypairs/src/public_key.ts b/packages/crypto/src/public_key.ts similarity index 100% rename from packages/keypairs/src/public_key.ts rename to packages/crypto/src/public_key.ts diff --git a/packages/keypairs/test/key_pair.test.js b/packages/crypto/test/key_pair.test.js similarity index 100% rename from packages/keypairs/test/key_pair.test.js rename to packages/crypto/test/key_pair.test.js diff --git a/packages/keypairs/tsconfig.json b/packages/crypto/tsconfig.json similarity index 100% rename from packages/keypairs/tsconfig.json rename to packages/crypto/tsconfig.json diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index 988270373..3d7dae8bd 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*" }, "devDependencies": { diff --git a/packages/keystores-browser/src/browser_local_storage_key_store.ts b/packages/keystores-browser/src/browser_local_storage_key_store.ts index ba4cd6372..0fc42b522 100644 --- a/packages/keystores-browser/src/browser_local_storage_key_store.ts +++ b/packages/keystores-browser/src/browser_local_storage_key_store.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '@near-js/keypairs'; +import { KeyPair } from '@near-js/crypto'; import { KeyStore } from '@near-js/keystores'; const LOCAL_STORAGE_KEY_PREFIX = 'near-api-js:keystore:'; diff --git a/packages/keystores-browser/test/keystore_common.js b/packages/keystores-browser/test/keystore_common.js index 079315bf6..40fd26361 100644 --- a/packages/keystores-browser/test/keystore_common.js +++ b/packages/keystores-browser/test/keystore_common.js @@ -1,4 +1,4 @@ -const { KeyPairEd25519 } = require('@near-js/keypairs'); +const { KeyPairEd25519 } = require('@near-js/crypto'); const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 1d7633ef0..8f8426910 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*" }, "devDependencies": { diff --git a/packages/keystores-node/src/unencrypted_file_system_keystore.ts b/packages/keystores-node/src/unencrypted_file_system_keystore.ts index 589547d69..1071ee8eb 100644 --- a/packages/keystores-node/src/unencrypted_file_system_keystore.ts +++ b/packages/keystores-node/src/unencrypted_file_system_keystore.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '@near-js/keypairs'; +import { KeyPair } from '@near-js/crypto'; import { KeyStore } from '@near-js/keystores'; import fs from 'fs'; import path from 'path'; diff --git a/packages/keystores-node/test/keystore_common.js b/packages/keystores-node/test/keystore_common.js index 079315bf6..40fd26361 100644 --- a/packages/keystores-node/test/keystore_common.js +++ b/packages/keystores-node/test/keystore_common.js @@ -1,4 +1,4 @@ -const { KeyPairEd25519 } = require('@near-js/keypairs'); +const { KeyPairEd25519 } = require('@near-js/crypto'); const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; diff --git a/packages/keystores-node/test/unencrypted_file_system_keystore.test.js b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js index 8c0b75b56..d4845b839 100644 --- a/packages/keystores-node/test/unencrypted_file_system_keystore.test.js +++ b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js @@ -1,4 +1,4 @@ -const { KeyPairEd25519 } = require('@near-js/keypairs'); +const { KeyPairEd25519 } = require('@near-js/crypto'); const fs = require('fs').promises; const path = require('path'); const rimraf = require('util').promisify(require('rimraf')); diff --git a/packages/keystores/package.json b/packages/keystores/package.json index 4b2c4e87c..b66ad3eb7 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/types": "workspace:*" }, "devDependencies": { diff --git a/packages/keystores/src/in_memory_key_store.ts b/packages/keystores/src/in_memory_key_store.ts index bbd4d16eb..c4114e477 100644 --- a/packages/keystores/src/in_memory_key_store.ts +++ b/packages/keystores/src/in_memory_key_store.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '@near-js/keypairs'; +import { KeyPair } from '@near-js/crypto'; import { KeyStore } from './keystore'; /** diff --git a/packages/keystores/src/keystore.ts b/packages/keystores/src/keystore.ts index 22fdb6de5..3053e08a8 100644 --- a/packages/keystores/src/keystore.ts +++ b/packages/keystores/src/keystore.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '@near-js/keypairs'; +import { KeyPair } from '@near-js/crypto'; /** * KeyStores are passed to {@link near!Near} via {@link near!NearConfig} diff --git a/packages/keystores/src/merge_key_store.ts b/packages/keystores/src/merge_key_store.ts index 8d6270132..ca3e5705d 100644 --- a/packages/keystores/src/merge_key_store.ts +++ b/packages/keystores/src/merge_key_store.ts @@ -1,4 +1,4 @@ -import { KeyPair } from '@near-js/keypairs'; +import { KeyPair } from '@near-js/crypto'; import { KeyStore } from './keystore'; /** diff --git a/packages/keystores/test/keystore_common.js b/packages/keystores/test/keystore_common.js index 079315bf6..40fd26361 100644 --- a/packages/keystores/test/keystore_common.js +++ b/packages/keystores/test/keystore_common.js @@ -1,4 +1,4 @@ -const { KeyPairEd25519 } = require('@near-js/keypairs'); +const { KeyPairEd25519 } = require('@near-js/crypto'); const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; diff --git a/packages/keystores/test/merge_keystore.test.js b/packages/keystores/test/merge_keystore.test.js index 5bf31d6e1..76ee7ac2e 100644 --- a/packages/keystores/test/merge_keystore.test.js +++ b/packages/keystores/test/merge_keystore.test.js @@ -1,4 +1,4 @@ -const { KeyPairEd25519 } = require('@near-js/keypairs'); +const { KeyPairEd25519 } = require('@near-js/crypto'); const { InMemoryKeyStore, MergeKeyStore } = require('../lib'); diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 7832cbcab..036afc4d3 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -12,7 +12,7 @@ "types": "lib/index.d.ts", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", "@near-js/keystores-browser": "workspace:*", "@near-js/keystores-node": "workspace:*", diff --git a/packages/near-api-js/src/utils/key_pair.ts b/packages/near-api-js/src/utils/key_pair.ts index 82d3ad7be..c50a73981 100644 --- a/packages/near-api-js/src/utils/key_pair.ts +++ b/packages/near-api-js/src/utils/key_pair.ts @@ -4,6 +4,6 @@ export { KeyType, PublicKey, Signature, -} from '@near-js/keypairs'; +} from '@near-js/crypto'; export type Arrayish = string | ArrayLike; diff --git a/packages/signers/package.json b/packages/signers/package.json index e1ccd5563..866fe950e 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -15,7 +15,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", "js-sha256": "^0.9.0" }, diff --git a/packages/signers/src/in_memory_signer.ts b/packages/signers/src/in_memory_signer.ts index da9c0cc0e..078acfe65 100644 --- a/packages/signers/src/in_memory_signer.ts +++ b/packages/signers/src/in_memory_signer.ts @@ -1,4 +1,4 @@ -import { KeyPair, PublicKey, Signature } from '@near-js/keypairs'; +import { KeyPair, PublicKey, Signature } from '@near-js/crypto'; import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; import sha256 from 'js-sha256'; diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index 753e86216..d5d5ae670 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,4 +1,4 @@ -import { Signature, PublicKey } from '@near-js/keypairs'; +import { Signature, PublicKey } from '@near-js/crypto'; /** * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. diff --git a/packages/transactions/package.json b/packages/transactions/package.json index b9617050c..d91827dd9 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -16,7 +16,7 @@ "author": "", "license": "ISC", "dependencies": { - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index ca28a4033..f3e9bd6b9 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import BN from 'bn.js'; import { diff --git a/packages/transactions/src/actions.ts b/packages/transactions/src/actions.ts index 3a55a9672..0cbfde40c 100644 --- a/packages/transactions/src/actions.ts +++ b/packages/transactions/src/actions.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import { Assignable } from '@near-js/types'; import BN from 'bn.js'; diff --git a/packages/transactions/src/create_transaction.ts b/packages/transactions/src/create_transaction.ts index 6914a2cfb..9f90de00f 100644 --- a/packages/transactions/src/create_transaction.ts +++ b/packages/transactions/src/create_transaction.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import BN from 'bn.js'; import { Action } from './actions'; diff --git a/packages/transactions/src/schema.ts b/packages/transactions/src/schema.ts index b32a4f8c1..6cb6540b5 100644 --- a/packages/transactions/src/schema.ts +++ b/packages/transactions/src/schema.ts @@ -1,4 +1,4 @@ -import { KeyType, PublicKey } from '@near-js/keypairs'; +import { KeyType, PublicKey } from '@near-js/crypto'; import { Assignable } from '@near-js/types'; import BN from 'bn.js'; import { deserialize, serialize } from 'borsh'; diff --git a/packages/transactions/test/serialize.test.js b/packages/transactions/test/serialize.test.js index 740405d06..67d32c7f5 100644 --- a/packages/transactions/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -1,4 +1,4 @@ -const { KeyPair, PublicKey } = require('@near-js/keypairs'); +const { KeyPair, PublicKey } = require('@near-js/crypto'); const { InMemoryKeyStore } = require('@near-js/keystores'); const { InMemorySigner } = require('@near-js/signers'); const { Assignable } = require('@near-js/types'); diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json index e6d2096c0..871726e61 100644 --- a/packages/wallet-account/package.json +++ b/packages/wallet-account/package.json @@ -13,7 +13,7 @@ "license": "ISC", "dependencies": { "@near-js/accounts": "workspace:*", - "@near-js/keypairs": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts index f2f874623..31d310e91 100644 --- a/packages/wallet-account/src/near.ts +++ b/packages/wallet-account/src/near.ts @@ -14,7 +14,7 @@ import { LocalAccountCreator, UrlAccountCreator, } from '@near-js/accounts'; -import { PublicKey } from '@near-js/keypairs'; +import { PublicKey } from '@near-js/crypto'; import { KeyStore } from '@near-js/keystores'; import { Signer } from '@near-js/signers'; import BN from 'bn.js'; diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index 34a03d394..fb495df7f 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -11,7 +11,7 @@ import { Connection, SignAndSendTransactionOptions, } from '@near-js/accounts'; -import { KeyPair, PublicKey } from '@near-js/keypairs'; +import { KeyPair, PublicKey } from '@near-js/crypto'; import { KeyStore } from '@near-js/keystores'; import { InMemorySigner } from '@near-js/signers'; import { FinalExecutionOutcome } from '@near-js/types'; diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 1e6ca2cb4..8500b5265 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -1,4 +1,4 @@ -const { KeyPair, PublicKey } = require('@near-js/keypairs'); +const { KeyPair, PublicKey } = require('@near-js/crypto'); const { InMemoryKeyStore } = require('@near-js/keystores'); const { InMemorySigner } = require('@near-js/signers'); const { actionCreators, createTransaction, SCHEMA, Transaction } = require('@near-js/transactions'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26009b97b..48ef520bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ importers: packages/accounts: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@near-js/providers': workspace:* '@near-js/signers': workspace:* @@ -51,7 +51,7 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/providers': link:../providers '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions @@ -79,7 +79,7 @@ importers: homedir: 0.6.0 near-api-js: link:../near-api-js - packages/keypairs: + packages/crypto: specifiers: '@near-js/types': workspace:* '@types/node': ^18.11.18 @@ -102,14 +102,14 @@ importers: packages/keystores: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/types': workspace:* '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/types': link:../types devDependencies: '@types/node': 18.11.18 @@ -119,13 +119,13 @@ importers: packages/keystores-browser: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* jest: ^26.0.1 ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores devDependencies: jest: 26.6.3 @@ -134,14 +134,14 @@ importers: packages/keystores-node: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores devDependencies: '@types/node': 18.11.18 @@ -152,7 +152,7 @@ importers: packages/near-api-js: specifiers: '@near-js/accounts': workspace:* - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@near-js/keystores-browser': workspace:* '@near-js/keystores-node': workspace:* @@ -191,7 +191,7 @@ importers: uglifyify: ^5.0.1 dependencies: '@near-js/accounts': link:../accounts - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores '@near-js/keystores-browser': link:../keystores-browser '@near-js/keystores-node': link:../keystores-node @@ -260,7 +260,7 @@ importers: packages/signers: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@types/node': ^18.11.18 jest: ^26.0.1 @@ -268,7 +268,7 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores js-sha256: 0.9.0 devDependencies: @@ -279,7 +279,7 @@ importers: packages/transactions: specifiers: - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@near-js/signers': workspace:* '@near-js/types': workspace:* @@ -292,7 +292,7 @@ importers: ts-jest: ^26.5.6 typescript: ^4.9.4 dependencies: - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/signers': link:../signers '@near-js/types': link:../types '@near-js/utils': link:../utils @@ -345,7 +345,7 @@ importers: packages/wallet-account: specifiers: '@near-js/accounts': workspace:* - '@near-js/keypairs': workspace:* + '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* '@near-js/signers': workspace:* '@near-js/transactions': workspace:* @@ -360,7 +360,7 @@ importers: typescript: ^4.9.4 dependencies: '@near-js/accounts': link:../accounts - '@near-js/keypairs': link:../keypairs + '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores '@near-js/signers': link:../signers '@near-js/transactions': link:../transactions From 5c21ed4e88fd40b96984a172688ce5dcc3367a74 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 13 Jan 2023 15:12:26 -0800 Subject: [PATCH 68/84] chore: update changeset to promote near-api-js change and rename package --- .changeset/slimy-baboons-itch.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/slimy-baboons-itch.md b/.changeset/slimy-baboons-itch.md index 23b8f2258..48fd397ee 100644 --- a/.changeset/slimy-baboons-itch.md +++ b/.changeset/slimy-baboons-itch.md @@ -1,7 +1,7 @@ --- -"near-api-js": minor +"near-api-js": major "@near-js/accounts": patch -"@near-js/keypairs": patch +"@near-js/crypto": patch "@near-js/keystores": patch "@near-js/keystores-browser": patch "@near-js/keystores-node": patch From e3705e6c04b52d1e3a974acd284e36734f3bd7e4 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 19 Jan 2023 13:57:36 -0800 Subject: [PATCH 69/84] docs: describe breaking change --- .changeset/slimy-baboons-itch.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.changeset/slimy-baboons-itch.md b/.changeset/slimy-baboons-itch.md index 48fd397ee..af284ca8e 100644 --- a/.changeset/slimy-baboons-itch.md +++ b/.changeset/slimy-baboons-itch.md @@ -14,3 +14,6 @@ --- Major functionality in near-api-js has now been broken up into packages under @near-js + +Breaking changes: + - `KeyPairEd25519` no longer supports the `fromString` static method. This method is now only available on the `KeyPair` class. From b2553d3eaea1e6548d10768f2b1c10b12a8ba63f Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 25 Jan 2023 12:12:01 -0800 Subject: [PATCH 70/84] chore: merge abi validation for contract --- packages/accounts/package.json | 5 +- packages/accounts/src/contract.ts | 120 ++++++++++--- packages/accounts/src/errors.ts | 25 +++ packages/accounts/src/index.ts | 6 + packages/accounts/test/contract_abi.test.js | 186 ++++++++++++++++++++ packages/accounts/tsconfig.json | 1 + packages/wallet-account/tsconfig.json | 1 + pnpm-lock.yaml | 6 + 8 files changed, 329 insertions(+), 21 deletions(-) create mode 100644 packages/accounts/src/errors.ts create mode 100644 packages/accounts/test/contract_abi.test.js diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 78a42d831..187bbb7cd 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -22,9 +22,12 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "ajv": "^8.11.2", + "ajv-formats": "^2.1.1", "bn.js": "5.2.1", "borsh": "^0.7.0", - "depd": "^2.0.0" + "depd": "^2.0.0", + "near-abi": "0.1.1" }, "devDependencies": { "@near-js/keystores": "workspace:*", diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts index 4ba7cb1e6..c40942cfb 100644 --- a/packages/accounts/src/contract.ts +++ b/packages/accounts/src/contract.ts @@ -1,9 +1,14 @@ import { getTransactionLastResult } from '@near-js/utils'; import { ArgumentTypeError, PositionalArgsError } from '@near-js/types'; +import Ajv from 'ajv'; +import addFormats from 'ajv-formats'; import BN from 'bn.js'; import depd from 'depd'; +import { AbiFunction, AbiFunctionKind, AbiRoot, AbiSerializationType } from 'near-abi'; import { Account } from './account'; +import { UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './errors'; + // Makes `function.name` return given name function nameFunction(name: string, body: (args?: any[]) => any) { @@ -14,6 +19,52 @@ function nameFunction(name: string, body: (args?: any[]) => any) { }[name]; } +function validateArguments(args: object, abiFunction: AbiFunction, ajv: Ajv, abiRoot: AbiRoot) { + if (!isObject(args)) return; + + if (abiFunction.params && abiFunction.params.serialization_type !== AbiSerializationType.Json) { + throw new UnsupportedSerializationError(abiFunction.name, abiFunction.params.serialization_type); + } + + if (abiFunction.result && abiFunction.result.serialization_type !== AbiSerializationType.Json) { + throw new UnsupportedSerializationError(abiFunction.name, abiFunction.result.serialization_type); + } + + const params = abiFunction.params?.args || []; + for (const p of params) { + const arg = args[p.name]; + const typeSchema = p.type_schema; + typeSchema.definitions = abiRoot.body.root_schema.definitions; + const validate = ajv.compile(typeSchema); + if (!validate(arg)) { + throw new ArgumentSchemaError(p.name, validate.errors); + } + } + // Check there are no extra unknown arguments passed + for (const argName of Object.keys(args)) { + const param = params.find((p) => p.name === argName); + if (!param) { + throw new UnknownArgumentError(argName, params.map((p) => p.name)); + } + } +} + +function createAjv() { + // Strict mode is disabled for now as it complains about unknown formats. We need to + // figure out if we want to support a fixed set of formats. `uint32` and `uint64` + // are added explicitly just to reduce the amount of warnings as these are very popular + // types. + const ajv = new Ajv({ + strictSchema: false, + formats: { + uint32: true, + uint64: true + } + }); + addFormats(ajv); + return ajv; +} + const isUint8Array = (x: any) => x && x.byteLength !== undefined && x.byteLength === x.length; @@ -32,27 +83,32 @@ interface ChangeMethodOptions { export interface ContractMethods { /** * Methods that change state. These methods cost gas and require a signed transaction. - * + * * @see {@link account!Account.functionCall} */ changeMethods: string[]; /** * View methods do not require a signed transaction. - * + * * @see {@link account!Account#viewFunction} */ viewMethods: string[]; + + /** + * ABI defining this contract's interface. + */ + abi: AbiRoot; } /** * Defines a smart contract on NEAR including the change (mutable) and view (non-mutable) methods - * + * * @see [https://docs.near.org/tools/near-api-js/quick-reference#contract](https://docs.near.org/tools/near-api-js/quick-reference#contract) * @example * ```js * import { Contract } from 'near-api-js'; - * + * * async function contractExample() { * const methodOptions = { * viewMethods: ['getMessageByAccountId'], @@ -63,12 +119,12 @@ export interface ContractMethods { * 'contract-id.testnet', * methodOptions * ); - * + * * // use a contract view method * const messages = await contract.getMessages({ * accountId: 'example-account.testnet' * }); - * + * * // use a contract change method * await contract.addMessage({ * meta: 'some info', @@ -91,45 +147,69 @@ export class Contract { constructor(account: Account, contractId: string, options: ContractMethods) { this.account = account; this.contractId = contractId; - const { viewMethods = [], changeMethods = [] } = options; - viewMethods.forEach((methodName) => { - Object.defineProperty(this, methodName, { + const { viewMethods = [], changeMethods = [], abi: abiRoot } = options; + + let viewMethodsWithAbi = viewMethods.map((name) => ({ name, abi: null as AbiFunction })); + let changeMethodsWithAbi = changeMethods.map((name) => ({ name, abi: null as AbiFunction })); + if (abiRoot) { + if (viewMethodsWithAbi.length > 0 || changeMethodsWithAbi.length > 0) { + throw new ConflictingOptions(); + } + viewMethodsWithAbi = abiRoot.body.functions + .filter((m) => m.kind === AbiFunctionKind.View) + .map((m) => ({ name: m.name, abi: m })); + changeMethodsWithAbi = abiRoot.body.functions + .filter((methodAbi) => methodAbi.kind === AbiFunctionKind.Call) + .map((methodAbi) => ({ name: methodAbi.name, abi: methodAbi })); + } + + const ajv = createAjv(); + viewMethodsWithAbi.forEach(({ name, abi }) => { + Object.defineProperty(this, name, { writable: false, enumerable: true, - value: nameFunction(methodName, async (args: object = {}, options = {}, ...ignored) => { + value: nameFunction(name, async (args: object = {}, options = {}, ...ignored) => { if (ignored.length || !(isObject(args) || isUint8Array(args)) || !isObject(options)) { throw new PositionalArgsError(); } + + if (abi) { + validateArguments(args, abi, ajv, abiRoot); + } + return this.account.viewFunction({ contractId: this.contractId, - methodName, + methodName: name, args, ...options, }); }) }); }); - changeMethods.forEach((methodName) => { - Object.defineProperty(this, methodName, { + changeMethodsWithAbi.forEach(({ name, abi }) => { + Object.defineProperty(this, name, { writable: false, enumerable: true, - value: nameFunction(methodName, async (...args: any[]) => { + value: nameFunction(name, async (...args: any[]) => { if (args.length && (args.length > 3 || !(isObject(args[0]) || isUint8Array(args[0])))) { throw new PositionalArgsError(); } - if(args.length > 1 || !(args[0] && args[0].args)) { + if (args.length > 1 || !(args[0] && args[0].args)) { const deprecate = depd('contract.methodName(args, gas, amount)'); deprecate('use `contract.methodName({ args, gas?, amount?, callbackUrl?, meta? })` instead'); - return this._changeMethod({ - methodName, + args[0] = { args: args[0], gas: args[1], amount: args[2] - }); + }; } - return this._changeMethod({ methodName, ...args[0] }); + if (abi) { + validateArguments(args[0].args, abi, ajv, abiRoot); + } + + return this._changeMethod({ methodName: name, ...args[0] }); }) }); }); @@ -165,4 +245,4 @@ function validateBNLike(argMap: { [name: string]: any }) { throw new ArgumentTypeError(argName, bnLike, argValue); } } -} +} \ No newline at end of file diff --git a/packages/accounts/src/errors.ts b/packages/accounts/src/errors.ts new file mode 100644 index 000000000..b37051625 --- /dev/null +++ b/packages/accounts/src/errors.ts @@ -0,0 +1,25 @@ +import { ErrorObject } from 'ajv'; + +export class UnsupportedSerializationError extends Error { + constructor(methodName: string, serializationType: string) { + super(`Contract method '${methodName}' is using an unsupported serialization type ${serializationType}`); + } +} + +export class UnknownArgumentError extends Error { + constructor(actualArgName: string, expectedArgNames: string[]) { + super(`Unrecognized argument '${actualArgName}', expected '${JSON.stringify(expectedArgNames)}'`); + } +} + +export class ArgumentSchemaError extends Error { + constructor(argName: string, errors: ErrorObject[]) { + super(`Argument '${argName}' does not conform to the specified ABI schema: '${JSON.stringify(errors)}'`); + } +} + +export class ConflictingOptions extends Error { + constructor() { + super('Conflicting contract method options have been passed. You can either specify ABI or a list of view/call methods.'); + } +} diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index cb1778b0b..fe936335d 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -27,6 +27,12 @@ export { Contract, ContractMethods, } from './contract'; +export { + ArgumentSchemaError, + ConflictingOptions, + UnknownArgumentError, + UnsupportedSerializationError, +} from './errors'; export { MultisigDeleteRequestRejectionError, MultisigStateStatus, diff --git a/packages/accounts/test/contract_abi.test.js b/packages/accounts/test/contract_abi.test.js new file mode 100644 index 000000000..72a73187d --- /dev/null +++ b/packages/accounts/test/contract_abi.test.js @@ -0,0 +1,186 @@ +const { Contract } = require('../src/contract'); +const { ArgumentSchemaError, UnknownArgumentError, UnsupportedSerializationError } = require('../src/errors'); + +let rawAbi = `{ + "schema_version": "0.3.0", + "body": { + "functions": [ + { + "name": "add", + "doc": " Adds two pairs point-wise.", + "kind": "view", + "params": { + "serialization_type": "json", + "args": [ + { + "name": "a", + "type_schema": { + "$ref": "#/definitions/Pair" + } + }, + { + "name": "b", + "type_schema": { + "$ref": "#/definitions/Pair" + } + } + ] + }, + "result": { + "serialization_type": "json", + "type_schema": { + "$ref": "#/definitions/Pair" + } + } + }, + { + "name": "add_call", + "doc": " Adds two pairs point-wise.", + "kind": "call", + "params": { + "serialization_type": "json", + "args": [ + { + "name": "a", + "type_schema": { + "$ref": "#/definitions/Pair" + } + }, + { + "name": "b", + "type_schema": { + "$ref": "#/definitions/Pair" + } + } + ] + }, + "result": { + "serialization_type": "json", + "type_schema": { + "$ref": "#/definitions/Pair" + } + } + }, + { + "name": "empty_call", + "kind": "call" + } + ], + "root_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string", + "definitions": { + "Pair": { + "type": "array", + "items": [ + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + ], + "maxItems": 2, + "minItems": 2 + } + } + } + } +}`; + +const account = { + viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery }) { + return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + }, + functionCall() { + return this; + } +}; + +const abi = JSON.parse(rawAbi); + +const contract = new Contract(account, 'contractId', { + abi +}); + +describe('add', () => { + test('can be called successfully', async () => { + await contract.add({ a: [1, 2], b: [3, 4] }); + }); + + test('throws ArgumentSchemaError if required argument was not supplied', async () => { + await expect(contract.add({})).rejects.toBeInstanceOf(ArgumentSchemaError); + }); + + test('throws ArgumentSchemaError if argument has unexpected type', async () => { + await expect(contract.add({ a: 1, b: [3, 4] })).rejects.toBeInstanceOf(ArgumentSchemaError); + }); + + test('throws UnknownArgumentError if unknown argument was supplied', async () => { + await expect(contract.add({ a: [1, 2], b: [3, 4], c: 5 })).rejects.toBeInstanceOf(UnknownArgumentError); + }); +}); + + +describe('add_call', () => { + test('can be called successfully', async () => { + await contract.add_call({ args: { a: [1, 2], b: [3, 4] } }); + }); + + test('throws ArgumentSchemaError if required argument was not supplied', async () => { + await expect(contract.add_call({ args: {} })).rejects.toBeInstanceOf(ArgumentSchemaError); + }); + + test('throws ArgumentSchemaError if argument has unexpected type', async () => { + await expect(contract.add_call({ args: { a: 1, b: [3, 4] } })).rejects.toBeInstanceOf(ArgumentSchemaError); + }); + + test('throws UnknownArgumentError if unknown argument was supplied', async () => { + await expect(contract.add_call({ args: { a: [1, 2], b: [3, 4], c: 5 } })).rejects.toBeInstanceOf(UnknownArgumentError); + }); +}); + +describe('empty_call', () => { + test('can be called successfully', async () => { + await contract.empty_call({}); + }); +}); + +describe('Contract constructor', () => { + test('throws UnsupportedSerializationError when ABI has borsh serialization', async () => { + let rawAbi = `{ + "schema_version": "0.3.0", + "body": { + "functions": [ + { + "name": "add", + "kind": "view", + "params": { + "serialization_type": "borsh", + "args": [ + { + "name": "b", + "type_schema": { + "type": "integer" + } + } + ] + } + } + ], + "root_schema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "String", + "type": "string" + } + } + }`; + const contract = new Contract(account, 'contractId', { abi: JSON.parse(rawAbi) }); + await expect(contract.add({ a: 1 })).rejects.toBeInstanceOf(UnsupportedSerializationError); + }); +}); diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json index ae42955e4..fdc99be31 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.node.json", "compilerOptions": { + "preserveSymlinks": false, "outDir": "./lib", }, "files": [ diff --git a/packages/wallet-account/tsconfig.json b/packages/wallet-account/tsconfig.json index 24c600084..cd1fd28f0 100644 --- a/packages/wallet-account/tsconfig.json +++ b/packages/wallet-account/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.browser.json", "compilerOptions": { + "preserveSymlinks": false, "outDir": "./lib", }, "files": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 48ef520bf..e6c65a031 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,11 +42,14 @@ importers: '@near-js/types': workspace:* '@near-js/utils': workspace:* '@types/node': ^18.11.18 + ajv: ^8.11.2 + ajv-formats: ^2.1.1 bn.js: 5.2.1 borsh: ^0.7.0 bs58: ^4.0.0 depd: ^2.0.0 jest: ^26.0.1 + near-abi: 0.1.1 near-hello: ^0.5.1 ts-jest: ^26.5.6 typescript: ^4.9.4 @@ -57,9 +60,12 @@ importers: '@near-js/transactions': link:../transactions '@near-js/types': link:../types '@near-js/utils': link:../utils + ajv: 8.11.2 + ajv-formats: 2.1.1_ajv@8.11.2 bn.js: 5.2.1 borsh: 0.7.0 depd: 2.0.0 + near-abi: 0.1.1 devDependencies: '@near-js/keystores': link:../keystores '@types/node': 18.11.18 From 5413d8aa528b0e8220162d031cbaee746c3274dc Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 25 Jan 2023 15:12:10 -0800 Subject: [PATCH 71/84] fix: export errors from utils/errors --- packages/near-api-js/src/utils/errors.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/near-api-js/src/utils/errors.ts b/packages/near-api-js/src/utils/errors.ts index dd8a24793..522334d15 100644 --- a/packages/near-api-js/src/utils/errors.ts +++ b/packages/near-api-js/src/utils/errors.ts @@ -1,7 +1,13 @@ -export { logWarning } from '@near-js/utils'; +export { + ArgumentSchemaError, + ConflictingOptions, + UnknownArgumentError, + UnsupportedSerializationError, +} from '@near-js/accounts'; export { ArgumentTypeError, ErrorContext, PositionalArgsError, TypedError, } from '@near-js/types'; +export { logWarning } from '@near-js/utils'; From 9386eada0f904dec4a8310c0679e9206dbc8ab22 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 25 Jan 2023 16:49:00 -0800 Subject: [PATCH 72/84] fix: restore promisify shim on NAJ-dependent versions --- .../src/unencrypted_file_system_keystore.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/keystores-node/src/unencrypted_file_system_keystore.ts b/packages/keystores-node/src/unencrypted_file_system_keystore.ts index 1071ee8eb..c47febb23 100644 --- a/packages/keystores-node/src/unencrypted_file_system_keystore.ts +++ b/packages/keystores-node/src/unencrypted_file_system_keystore.ts @@ -2,7 +2,17 @@ import { KeyPair } from '@near-js/crypto'; import { KeyStore } from '@near-js/keystores'; import fs from 'fs'; import path from 'path'; -import { promisify } from 'util'; +import { promisify as _promisify } from 'util'; + +/* remove for versions not referenced by near-api-js */ +const promisify = (fn: any) => { + if (!fn) { + return () => { + throw new Error('Trying to use unimplemented function. `fs` module not available in web build?'); + }; + } + return _promisify(fn); +}; const exists = promisify(fs.exists); const readFile = promisify(fs.readFile); From 4abbe3f6bf085f5572170edabb73c3bf4e763925 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 25 Jan 2023 21:43:45 -0800 Subject: [PATCH 73/84] ci: properly cache build output --- turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 8d9d5512e..18325e563 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,7 @@ "build": { "dependsOn": ["^build"], "inputs": ["src/**/*.ts", "test/**/*.js"], - "outputs": ["dist/"] + "outputs": ["dist/**", "lib/**"] }, "test": { /* TODO remove once near-api-js tests are removed or packages/accounts/tests gets its own set of keys */ From 9149734e4bc77247ac9a4bc7b6e55f15f6fe512c Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 25 Jan 2023 21:44:23 -0800 Subject: [PATCH 74/84] ci: command for sanitizing dev environment --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 6cc1e1893..716e74a2b 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "clean": "turbo run clean", "lint": "concurrently \"turbo run lint:ts\" \"turbo run lint:js\"", "lint:fix": "concurrently \"turbo run lint:ts:fix\" \"turbo run lint:js:fix\"", + "autoclave": "concurrently \"rimraf packages/**/dist\" \"rimraf packages/**/lib\" \"rimraf packages/**/node_modules\" \"rimraf packages/**/coverage\" \"rimraf packages/**/.turbo\" && rm -rf node_modules", "test": "turbo run test", "release": "changeset publish", "prepare": "husky install" From 62f6b03439c8192bb8579035c7bef016fc69cdf6 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 30 Jan 2023 16:25:21 -0800 Subject: [PATCH 75/84] build: transpile cjs and esm for @near-js/accounts --- packages/accounts/jest.config.js | 2 +- packages/accounts/package.json | 14 +++++++++++--- packages/accounts/src/account.ts | 2 +- packages/accounts/src/account_2fa.ts | 10 +++++----- packages/accounts/src/account_creator.ts | 4 ++-- packages/accounts/src/account_multisig.ts | 8 ++++---- packages/accounts/src/contract.ts | 8 +++++--- packages/accounts/src/index.ts | 18 +++++++++--------- packages/accounts/test/account.test.js | 2 +- .../accounts/test/account_multisig.test.js | 2 +- packages/accounts/test/contract.test.js | 2 +- packages/accounts/test/contract_abi.test.js | 3 +-- packages/accounts/test/test-utils.js | 2 +- packages/accounts/tsconfig.esm.json | 12 ++++++++++++ packages/accounts/tsconfig.json | 2 +- tsconfig.esm.json | 10 ++++++++++ 16 files changed, 66 insertions(+), 35 deletions(-) create mode 100644 packages/accounts/tsconfig.esm.json create mode 100644 tsconfig.esm.json diff --git a/packages/accounts/jest.config.js b/packages/accounts/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/accounts/jest.config.js +++ b/packages/accounts/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 187bbb7cd..d2c0609b7 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -2,10 +2,10 @@ "name": "@near-js/accounts", "version": "0.0.1", "description": "Classes encapsulating account-specific functionality", - "main": "lib/index.js", + "type": "module", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "tsc -p tsconfig.json && tsc -p tsconfig.esm.json", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -37,5 +37,13 @@ "near-hello": "^0.5.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "dist", + "lib" + ] } diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 075f29d6d..3220c5711 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -33,7 +33,7 @@ import { } from '@near-js/types'; import { baseDecode, baseEncode } from 'borsh'; -import { Connection } from './connection'; +import { Connection } from './connection.js' const { addKey, diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index e73e811e2..debb27e36 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -6,16 +6,16 @@ import { fetchJson } from '@near-js/providers'; import { actionCreators } from '@near-js/transactions'; import BN from 'bn.js'; -import { SignAndSendTransactionOptions } from './account'; -import { AccountMultisig } from './account_multisig'; -import { Connection } from './connection'; +import { SignAndSendTransactionOptions } from './account.js' +import { AccountMultisig } from './account_multisig.js' +import { Connection } from './connection.js' import { MULTISIG_CHANGE_METHODS, MULTISIG_CONFIRM_METHODS, MULTISIG_DEPOSIT, MULTISIG_GAS, -} from './constants'; -import { MultisigStateStatus } from './types'; +} from './constants.js'; +import { MultisigStateStatus } from './types.js' const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; diff --git a/packages/accounts/src/account_creator.ts b/packages/accounts/src/account_creator.ts index 29d306f80..f821aed8a 100644 --- a/packages/accounts/src/account_creator.ts +++ b/packages/accounts/src/account_creator.ts @@ -2,8 +2,8 @@ import { PublicKey } from '@near-js/crypto'; import { fetchJson } from '@near-js/providers'; import BN from 'bn.js'; -import { Connection } from './connection'; -import { Account } from './account'; +import { Connection } from './connection.js' +import { Account } from './account.js' /** * Account creator provides an interface for implementations to actually create accounts diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index 4b18fd348..018706b61 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -3,16 +3,16 @@ import { Action, actionCreators } from '@near-js/transactions'; import { FinalExecutionOutcome } from '@near-js/types'; -import { Account, SignAndSendTransactionOptions } from './account'; -import { Connection } from './connection'; +import { Account, SignAndSendTransactionOptions } from './account.js' +import { Connection } from './connection.js' import { MULTISIG_ALLOWANCE, MULTISIG_CHANGE_METHODS, MULTISIG_DEPOSIT, MULTISIG_GAS, MULTISIG_STORAGE_KEY, -} from './constants'; -import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types'; +} from './constants.js'; +import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types.js' const { deployContract, functionCall } = actionCreators; diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts index c40942cfb..cf484c8b0 100644 --- a/packages/accounts/src/contract.ts +++ b/packages/accounts/src/contract.ts @@ -6,9 +6,8 @@ import BN from 'bn.js'; import depd from 'depd'; import { AbiFunction, AbiFunctionKind, AbiRoot, AbiSerializationType } from 'near-abi'; -import { Account } from './account'; -import { UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './errors'; - +import { Account } from './account.js' +import { UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './errors.js' // Makes `function.name` return given name function nameFunction(name: string, body: (args?: any[]) => any) { @@ -19,6 +18,7 @@ function nameFunction(name: string, body: (args?: any[]) => any) { }[name]; } +// @ts-ignore (TS2709: Cannot use namespace 'Ajv' as a type) function validateArguments(args: object, abiFunction: AbiFunction, ajv: Ajv, abiRoot: AbiRoot) { if (!isObject(args)) return; @@ -54,6 +54,7 @@ function createAjv() { // figure out if we want to support a fixed set of formats. `uint32` and `uint64` // are added explicitly just to reduce the amount of warnings as these are very popular // types. + // @ts-ignore (TS2351: This expression is not constructable) const ajv = new Ajv({ strictSchema: false, formats: { @@ -61,6 +62,7 @@ function createAjv() { uint64: true } }); + // @ts-ignore (TS2349: This expression is not callable) addFormats(ajv); return ajv; } diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index fe936335d..2e048dc93 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -6,15 +6,15 @@ export { FunctionCallOptions, ChangeFunctionCallOptions, ViewFunctionCallOptions, -} from './account'; -export { Account2FA } from './account_2fa'; +} from './account.js'; +export { Account2FA } from './account_2fa.js'; export { AccountCreator, LocalAccountCreator, UrlAccountCreator, -} from './account_creator'; -export { AccountMultisig } from './account_multisig'; -export { Connection } from './connection'; +} from './account_creator.js'; +export { AccountMultisig } from './account_multisig.js'; +export { Connection } from './connection.js'; export { MULTISIG_STORAGE_KEY, MULTISIG_ALLOWANCE, @@ -22,18 +22,18 @@ export { MULTISIG_DEPOSIT, MULTISIG_CHANGE_METHODS, MULTISIG_CONFIRM_METHODS, -} from './constants'; +} from './constants.js'; export { Contract, ContractMethods, -} from './contract'; +} from './contract.js'; export { ArgumentSchemaError, ConflictingOptions, UnknownArgumentError, UnsupportedSerializationError, -} from './errors'; +} from './errors.js'; export { MultisigDeleteRequestRejectionError, MultisigStateStatus, -} from './types'; +} from './types.js'; diff --git a/packages/accounts/test/account.test.js b/packages/accounts/test/account.test.js index 38f1a2028..0c2ffc78f 100644 --- a/packages/accounts/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -4,7 +4,7 @@ const { TypedError } = require('@near-js/types'); const BN = require('bn.js'); const fs = require('fs'); -const { Account, Contract } = require('../lib'); +const { Account, Contract } = require('../lib/cjs'); const testUtils = require('./test-utils'); let nearjs; diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index 30cfa9dcf..3c7d7f0ba 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -7,7 +7,7 @@ const BN = require('bn.js'); const fs = require('fs'); const semver = require('semver'); -const { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } = require('../lib'); +const { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } = require('../lib/cjs'); const testUtils = require('./test-utils'); const { functionCall, transfer } = actionCreators; diff --git a/packages/accounts/test/contract.test.js b/packages/accounts/test/contract.test.js index d56fff804..4a2b2376f 100644 --- a/packages/accounts/test/contract.test.js +++ b/packages/accounts/test/contract.test.js @@ -1,6 +1,6 @@ const { PositionalArgsError } = require('@near-js/types'); -const { Contract } = require('../lib'); +const { Contract } = require('../lib/cjs'); const account = { viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery}) { diff --git a/packages/accounts/test/contract_abi.test.js b/packages/accounts/test/contract_abi.test.js index 72a73187d..2705ee028 100644 --- a/packages/accounts/test/contract_abi.test.js +++ b/packages/accounts/test/contract_abi.test.js @@ -1,5 +1,4 @@ -const { Contract } = require('../src/contract'); -const { ArgumentSchemaError, UnknownArgumentError, UnsupportedSerializationError } = require('../src/errors'); +const { ArgumentSchemaError, Contract, UnknownArgumentError, UnsupportedSerializationError } = require('../lib/cjs'); let rawAbi = `{ "schema_version": "0.3.0", diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 7875a4b17..dddc74d0f 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -3,7 +3,7 @@ const { InMemoryKeyStore } = require('@near-js/keystores'); const BN = require('bn.js'); const fs = require('fs').promises; -const { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } = require('../lib'); +const { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } = require('../lib/cjs'); const networkId = 'unittest'; diff --git a/packages/accounts/tsconfig.esm.json b/packages/accounts/tsconfig.esm.json new file mode 100644 index 000000000..8e739b00f --- /dev/null +++ b/packages/accounts/tsconfig.esm.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.esm.json", + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16", + "preserveSymlinks": false, + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json index fdc99be31..b76e40fc6 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.json @@ -2,7 +2,7 @@ "extends": "../../tsconfig.node.json", "compilerOptions": { "preserveSymlinks": false, - "outDir": "./lib", + "outDir": "./lib/cjs", }, "files": [ "src/index.ts" diff --git a/tsconfig.esm.json b/tsconfig.esm.json new file mode 100644 index 000000000..fb35a180e --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "lib": [ + "es2015", + "esnext" + ], + "module": "nodenext" + } +} From 321f6f642fd1f94772e343ff9806c665256f5b70 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 30 Jan 2023 16:35:24 -0800 Subject: [PATCH 76/84] build: transpile cjs and esm for @near-js/transactions --- packages/transactions/package.json | 13 ++++++++++--- packages/transactions/src/action_creators.ts | 2 +- packages/transactions/src/create_transaction.ts | 4 ++-- packages/transactions/src/index.ts | 10 +++++----- packages/transactions/src/schema.ts | 2 +- packages/transactions/src/sign.ts | 6 +++--- packages/transactions/tsconfig.json | 2 +- 7 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/transactions/package.json b/packages/transactions/package.json index d91827dd9..a26f9f3c6 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -2,10 +2,10 @@ "name": "@near-js/transactions", "version": "0.0.1", "description": "Functions and data types for transactions on NEAR", - "main": "lib/index.js", + "type": "module", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "tsc -p tsconfig.json && tsc -p tsconfig.esm.json", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -30,5 +30,12 @@ "jest": "^26.0.1", "ts-jest": "^26.5.6", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index f3e9bd6b9..9e96d1913 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -15,7 +15,7 @@ import { FunctionCallPermission, Stake, Transfer, -} from './actions'; +} from './actions.js'; function fullAccessKey(): AccessKey { return new AccessKey({ diff --git a/packages/transactions/src/create_transaction.ts b/packages/transactions/src/create_transaction.ts index 9f90de00f..e655273a3 100644 --- a/packages/transactions/src/create_transaction.ts +++ b/packages/transactions/src/create_transaction.ts @@ -1,8 +1,8 @@ import { PublicKey } from '@near-js/crypto'; import BN from 'bn.js'; -import { Action } from './actions'; -import { Transaction } from './schema'; +import { Action } from './actions.js'; +import { Transaction } from './schema.js'; 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 }); diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts index 08e1eecc2..91b65b569 100644 --- a/packages/transactions/src/index.ts +++ b/packages/transactions/src/index.ts @@ -1,5 +1,5 @@ -export * from './action_creators'; -export * from './actions'; -export * from './create_transaction'; -export * from './schema'; -export * from './sign'; +export * from './action_creators.js'; +export * from './actions.js'; +export * from './create_transaction.js'; +export * from './schema.js'; +export * from './sign.js'; diff --git a/packages/transactions/src/schema.ts b/packages/transactions/src/schema.ts index 6cb6540b5..3e2dd9e19 100644 --- a/packages/transactions/src/schema.ts +++ b/packages/transactions/src/schema.ts @@ -17,7 +17,7 @@ import { FunctionCallPermission, Stake, Transfer, -} from './actions'; +} from './actions.js'; export class Signature extends Assignable { keyType: KeyType; diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts index 830751bf0..cb5013ae1 100644 --- a/packages/transactions/src/sign.ts +++ b/packages/transactions/src/sign.ts @@ -3,9 +3,9 @@ import sha256 from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; -import { Action } from './actions'; -import { createTransaction } from './create_transaction'; -import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema'; +import { Action } from './actions.js'; +import { createTransaction } from './create_transaction.js'; +import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema.js'; /** * Signs a given transaction from an account with given keys, applied to the given network diff --git a/packages/transactions/tsconfig.json b/packages/transactions/tsconfig.json index ae42955e4..c3e87e958 100644 --- a/packages/transactions/tsconfig.json +++ b/packages/transactions/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.node.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/cjs", }, "files": [ "src/index.ts" From d4ad5c462f8e782a589c1c337e51738e4f6d41a0 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 31 Jan 2023 11:11:56 -0800 Subject: [PATCH 77/84] build: modulify remaining packages --- packages/accounts/package.json | 6 +++-- packages/accounts/src/account.ts | 2 +- packages/accounts/src/account_2fa.ts | 8 +++---- packages/accounts/src/account_creator.ts | 4 ++-- packages/accounts/src/account_multisig.ts | 6 ++--- packages/accounts/src/contract.ts | 7 ++---- .../{tsconfig.json => tsconfig.cjs.json} | 2 +- packages/accounts/tsconfig.esm.json | 4 +--- packages/crypto/jest.config.js | 2 +- packages/crypto/package.json | 16 ++++++++++--- packages/crypto/src/index.ts | 10 ++++---- packages/crypto/src/key_pair.ts | 4 ++-- packages/crypto/src/key_pair_base.ts | 2 +- packages/crypto/src/key_pair_ed25519.ts | 6 ++--- packages/crypto/src/public_key.ts | 2 +- .../tsconfig.cjs.json} | 2 +- .../tsconfig.esm.json} | 4 ++-- packages/keystores-browser/package.json | 16 ++++++++++--- packages/keystores-browser/tsconfig.cjs.json | 9 +++++++ .../{tsconfig.json => tsconfig.esm.json} | 4 ++-- packages/keystores-node/package.json | 16 ++++++++++--- .../{tsconfig.json => tsconfig.cjs.json} | 4 ++-- .../tsconfig.esm.json} | 4 ++-- packages/keystores/package.json | 16 ++++++++++--- packages/keystores/src/in_memory_key_store.ts | 3 ++- packages/keystores/src/index.ts | 6 ++--- packages/keystores/src/merge_key_store.ts | 3 ++- packages/keystores/tsconfig.cjs.json | 9 +++++++ .../tsconfig.esm.json} | 4 ++-- packages/near-api-js/src/connect.ts | 2 +- packages/providers/package.json | 16 ++++++++++--- packages/providers/src/fetch_json.ts | 7 +++--- packages/providers/src/index.ts | 8 +++---- packages/providers/src/json-rpc-provider.ts | 6 ++--- packages/providers/tsconfig.cjs.json | 14 +++++++++++ packages/providers/tsconfig.esm.json | 13 ++++++++++ packages/signers/package.json | 20 ++++++++++++---- packages/signers/src/in_memory_signer.ts | 2 +- packages/signers/src/index.ts | 4 ++-- packages/signers/tsconfig.cjs.json | 9 +++++++ packages/signers/tsconfig.esm.json | 9 +++++++ packages/signers/tsconfig.json | 9 ------- packages/transactions/package.json | 5 +++- packages/transactions/src/sign.ts | 4 ++-- packages/transactions/tsconfig.cjs.json | 9 +++++++ packages/transactions/tsconfig.esm.json | 9 +++++++ .../tsconfig/base.json | 2 +- packages/tsconfig/browser.esm.json | 11 +++++++++ .../tsconfig/browser.json | 4 ++-- packages/tsconfig/esm.json | 9 +++++++ .../tsconfig/node.json | 4 ++-- packages/tsconfig/package.json | 5 ++++ packages/types/package.json | 16 ++++++++++--- packages/types/src/index.ts | 6 ++--- packages/types/src/provider/index.ts | 11 ++++----- packages/types/src/provider/light_client.ts | 4 ++-- packages/types/src/provider/request.ts | 2 +- packages/types/src/provider/response.ts | 2 +- packages/types/tsconfig.cjs.json | 9 +++++++ packages/types/tsconfig.esm.json | 9 +++++++ packages/types/tsconfig.json | 9 ------- packages/utils/package.json | 16 ++++++++++--- packages/utils/src/errors/index.ts | 4 ++-- packages/utils/src/errors/rpc_errors.ts | 2 +- packages/utils/src/index.ts | 12 +++++----- packages/utils/src/logging.ts | 2 +- packages/utils/tsconfig.cjs.json | 9 +++++++ packages/utils/tsconfig.esm.json | 9 +++++++ packages/utils/tsconfig.json | 9 ------- packages/wallet-account/package.json | 18 ++++++++++---- packages/wallet-account/src/index.ts | 4 ++-- packages/wallet-account/src/wallet_account.ts | 2 +- packages/wallet-account/tsconfig.cjs.json | 10 ++++++++ packages/wallet-account/tsconfig.esm.json | 10 ++++++++ packages/wallet-account/tsconfig.json | 10 -------- pnpm-lock.yaml | 24 +++++++++++++++++++ tsconfig.esm.json | 10 -------- 77 files changed, 405 insertions(+), 176 deletions(-) rename packages/accounts/{tsconfig.json => tsconfig.cjs.json} (77%) rename packages/{transactions/tsconfig.json => crypto/tsconfig.cjs.json} (72%) rename packages/{keystores/tsconfig.json => crypto/tsconfig.esm.json} (53%) create mode 100644 packages/keystores-browser/tsconfig.cjs.json rename packages/keystores-browser/{tsconfig.json => tsconfig.esm.json} (51%) rename packages/keystores-node/{tsconfig.json => tsconfig.cjs.json} (53%) rename packages/{crypto/tsconfig.json => keystores-node/tsconfig.esm.json} (53%) create mode 100644 packages/keystores/tsconfig.cjs.json rename packages/{providers/tsconfig.json => keystores/tsconfig.esm.json} (53%) create mode 100644 packages/providers/tsconfig.cjs.json create mode 100644 packages/providers/tsconfig.esm.json create mode 100644 packages/signers/tsconfig.cjs.json create mode 100644 packages/signers/tsconfig.esm.json delete mode 100644 packages/signers/tsconfig.json create mode 100644 packages/transactions/tsconfig.cjs.json create mode 100644 packages/transactions/tsconfig.esm.json rename tsconfig.base.json => packages/tsconfig/base.json (94%) create mode 100644 packages/tsconfig/browser.esm.json rename tsconfig.browser.json => packages/tsconfig/browser.json (69%) create mode 100644 packages/tsconfig/esm.json rename tsconfig.node.json => packages/tsconfig/node.json (65%) create mode 100644 packages/tsconfig/package.json create mode 100644 packages/types/tsconfig.cjs.json create mode 100644 packages/types/tsconfig.esm.json delete mode 100644 packages/types/tsconfig.json create mode 100644 packages/utils/tsconfig.cjs.json create mode 100644 packages/utils/tsconfig.esm.json delete mode 100644 packages/utils/tsconfig.json create mode 100644 packages/wallet-account/tsconfig.cjs.json create mode 100644 packages/wallet-account/tsconfig.esm.json delete mode 100644 packages/wallet-account/tsconfig.json delete mode 100644 tsconfig.esm.json diff --git a/packages/accounts/package.json b/packages/accounts/package.json index d2c0609b7..5f72fb366 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -2,10 +2,12 @@ "name": "@near-js/accounts", "version": "0.0.1", "description": "Classes encapsulating account-specific functionality", + "main": "lib/esm/index.js", "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -36,6 +38,7 @@ "jest": "^26.0.1", "near-hello": "^0.5.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" }, "exports": { @@ -43,7 +46,6 @@ "require": "./lib/cjs/index.js" }, "files": [ - "dist", "lib" ] } diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 3220c5711..7965f4b0c 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -33,7 +33,7 @@ import { } from '@near-js/types'; import { baseDecode, baseEncode } from 'borsh'; -import { Connection } from './connection.js' +import { Connection } from './connection.js'; const { addKey, diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index debb27e36..8b1749dfd 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -6,16 +6,16 @@ import { fetchJson } from '@near-js/providers'; import { actionCreators } from '@near-js/transactions'; import BN from 'bn.js'; -import { SignAndSendTransactionOptions } from './account.js' -import { AccountMultisig } from './account_multisig.js' -import { Connection } from './connection.js' +import { SignAndSendTransactionOptions } from './account.js'; +import { AccountMultisig } from './account_multisig.js'; +import { Connection } from './connection.js'; import { MULTISIG_CHANGE_METHODS, MULTISIG_CONFIRM_METHODS, MULTISIG_DEPOSIT, MULTISIG_GAS, } from './constants.js'; -import { MultisigStateStatus } from './types.js' +import { MultisigStateStatus } from './types.js'; const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; diff --git a/packages/accounts/src/account_creator.ts b/packages/accounts/src/account_creator.ts index f821aed8a..0ba2135ea 100644 --- a/packages/accounts/src/account_creator.ts +++ b/packages/accounts/src/account_creator.ts @@ -2,8 +2,8 @@ import { PublicKey } from '@near-js/crypto'; import { fetchJson } from '@near-js/providers'; import BN from 'bn.js'; -import { Connection } from './connection.js' -import { Account } from './account.js' +import { Connection } from './connection.js'; +import { Account } from './account.js'; /** * Account creator provides an interface for implementations to actually create accounts diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index 018706b61..b9c937175 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -3,8 +3,8 @@ import { Action, actionCreators } from '@near-js/transactions'; import { FinalExecutionOutcome } from '@near-js/types'; -import { Account, SignAndSendTransactionOptions } from './account.js' -import { Connection } from './connection.js' +import { Account, SignAndSendTransactionOptions } from './account.js'; +import { Connection } from './connection.js'; import { MULTISIG_ALLOWANCE, MULTISIG_CHANGE_METHODS, @@ -12,7 +12,7 @@ import { MULTISIG_GAS, MULTISIG_STORAGE_KEY, } from './constants.js'; -import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types.js' +import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types.js'; const { deployContract, functionCall } = actionCreators; diff --git a/packages/accounts/src/contract.ts b/packages/accounts/src/contract.ts index cf484c8b0..d7db3236b 100644 --- a/packages/accounts/src/contract.ts +++ b/packages/accounts/src/contract.ts @@ -6,8 +6,8 @@ import BN from 'bn.js'; import depd from 'depd'; import { AbiFunction, AbiFunctionKind, AbiRoot, AbiSerializationType } from 'near-abi'; -import { Account } from './account.js' -import { UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './errors.js' +import { Account } from './account.js'; +import { UnsupportedSerializationError, UnknownArgumentError, ArgumentSchemaError, ConflictingOptions } from './errors.js'; // Makes `function.name` return given name function nameFunction(name: string, body: (args?: any[]) => any) { @@ -18,7 +18,6 @@ function nameFunction(name: string, body: (args?: any[]) => any) { }[name]; } -// @ts-ignore (TS2709: Cannot use namespace 'Ajv' as a type) function validateArguments(args: object, abiFunction: AbiFunction, ajv: Ajv, abiRoot: AbiRoot) { if (!isObject(args)) return; @@ -54,7 +53,6 @@ function createAjv() { // figure out if we want to support a fixed set of formats. `uint32` and `uint64` // are added explicitly just to reduce the amount of warnings as these are very popular // types. - // @ts-ignore (TS2351: This expression is not constructable) const ajv = new Ajv({ strictSchema: false, formats: { @@ -62,7 +60,6 @@ function createAjv() { uint64: true } }); - // @ts-ignore (TS2349: This expression is not callable) addFormats(ajv); return ajv; } diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.cjs.json similarity index 77% rename from packages/accounts/tsconfig.json rename to packages/accounts/tsconfig.cjs.json index b76e40fc6..78ee67801 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.cjs.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/node.json", "compilerOptions": { "preserveSymlinks": false, "outDir": "./lib/cjs", diff --git a/packages/accounts/tsconfig.esm.json b/packages/accounts/tsconfig.esm.json index 8e739b00f..90660683c 100644 --- a/packages/accounts/tsconfig.esm.json +++ b/packages/accounts/tsconfig.esm.json @@ -1,8 +1,6 @@ { - "extends": "../../tsconfig.esm.json", + "extends": "tsconfig/esm.json", "compilerOptions": { - "module": "node16", - "moduleResolution": "node16", "preserveSymlinks": false, "outDir": "./lib/esm", }, diff --git a/packages/crypto/jest.config.js b/packages/crypto/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/crypto/jest.config.js +++ b/packages/crypto/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/crypto/package.json b/packages/crypto/package.json index b69db6503..fe60d41a0 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -2,10 +2,12 @@ "name": "@near-js/crypto", "version": "0.0.1", "description": "Abstractions around NEAR-compatible elliptical curves and cryptographic keys", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -25,6 +27,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index f7f05c045..bd1487ed7 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -1,5 +1,5 @@ -export { KeyType } from './constants'; -export { KeyPair } from './key_pair'; -export { Signature } from './key_pair_base'; -export { KeyPairEd25519 } from './key_pair_ed25519'; -export { PublicKey } from './public_key'; +export { KeyType } from './constants.js'; +export { KeyPair } from './key_pair.js'; +export { Signature } from './key_pair_base.js'; +export { KeyPairEd25519 } from './key_pair_ed25519.js'; +export { PublicKey } from './public_key.js'; diff --git a/packages/crypto/src/key_pair.ts b/packages/crypto/src/key_pair.ts index d02790a15..b41650414 100644 --- a/packages/crypto/src/key_pair.ts +++ b/packages/crypto/src/key_pair.ts @@ -1,5 +1,5 @@ -import { KeyPairBase } from './key_pair_base'; -import { KeyPairEd25519 } from './key_pair_ed25519'; +import { KeyPairBase } from './key_pair_base.js'; +import { KeyPairEd25519 } from './key_pair_ed25519.js'; export abstract class KeyPair extends KeyPairBase { /** diff --git a/packages/crypto/src/key_pair_base.ts b/packages/crypto/src/key_pair_base.ts index 47da6c450..619eb596b 100644 --- a/packages/crypto/src/key_pair_base.ts +++ b/packages/crypto/src/key_pair_base.ts @@ -1,4 +1,4 @@ -import { PublicKey } from './public_key'; +import { PublicKey } from './public_key.js'; export interface Signature { signature: Uint8Array; diff --git a/packages/crypto/src/key_pair_ed25519.ts b/packages/crypto/src/key_pair_ed25519.ts index 1414aca51..a91e29bf1 100644 --- a/packages/crypto/src/key_pair_ed25519.ts +++ b/packages/crypto/src/key_pair_ed25519.ts @@ -1,9 +1,9 @@ import { baseEncode, baseDecode } from 'borsh'; import nacl from 'tweetnacl'; -import { KeyType } from './constants'; -import { KeyPairBase, Signature} from './key_pair_base'; -import { PublicKey } from './public_key'; +import { KeyType } from './constants.js'; +import { KeyPairBase, Signature} from './key_pair_base.js'; +import { PublicKey } from './public_key.js'; /** * This class provides key pair functionality for Ed25519 curve: diff --git a/packages/crypto/src/public_key.ts b/packages/crypto/src/public_key.ts index b656394e9..86fd0f308 100644 --- a/packages/crypto/src/public_key.ts +++ b/packages/crypto/src/public_key.ts @@ -2,7 +2,7 @@ import { Assignable } from '@near-js/types'; import { baseEncode, baseDecode } from 'borsh'; import nacl from 'tweetnacl'; -import { KeyType } from './constants'; +import { KeyType } from './constants.js'; function key_type_to_str(keyType: KeyType): string { switch (keyType) { diff --git a/packages/transactions/tsconfig.json b/packages/crypto/tsconfig.cjs.json similarity index 72% rename from packages/transactions/tsconfig.json rename to packages/crypto/tsconfig.cjs.json index c3e87e958..b055ecfb9 100644 --- a/packages/transactions/tsconfig.json +++ b/packages/crypto/tsconfig.cjs.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/node.json", "compilerOptions": { "outDir": "./lib/cjs", }, diff --git a/packages/keystores/tsconfig.json b/packages/crypto/tsconfig.esm.json similarity index 53% rename from packages/keystores/tsconfig.json rename to packages/crypto/tsconfig.esm.json index ae42955e4..5f3d7bd79 100644 --- a/packages/keystores/tsconfig.json +++ b/packages/crypto/tsconfig.esm.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/esm.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/esm", }, "files": [ "src/index.ts" diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index 3d7dae8bd..edc3a7f57 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -2,10 +2,12 @@ "name": "@near-js/keystores-browser", "version": "0.0.1", "description": "KeyStore implementation for working with keys in browser LocalStorage", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -22,6 +24,14 @@ "devDependencies": { "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/keystores-browser/tsconfig.cjs.json b/packages/keystores-browser/tsconfig.cjs.json new file mode 100644 index 000000000..d3048afee --- /dev/null +++ b/packages/keystores-browser/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/browser.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/keystores-browser/tsconfig.json b/packages/keystores-browser/tsconfig.esm.json similarity index 51% rename from packages/keystores-browser/tsconfig.json rename to packages/keystores-browser/tsconfig.esm.json index 24c600084..5a929300c 100644 --- a/packages/keystores-browser/tsconfig.json +++ b/packages/keystores-browser/tsconfig.esm.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.browser.json", + "extends": "tsconfig/browser.esm.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/esm", }, "files": [ "src/index.ts" diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 8f8426910..3754666ce 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -2,10 +2,12 @@ "name": "@near-js/keystores-node", "version": "0.0.1", "description": "KeyStore implementation for working with keys in the local filesystem", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -23,6 +25,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/keystores-node/tsconfig.json b/packages/keystores-node/tsconfig.cjs.json similarity index 53% rename from packages/keystores-node/tsconfig.json rename to packages/keystores-node/tsconfig.cjs.json index ae42955e4..b055ecfb9 100644 --- a/packages/keystores-node/tsconfig.json +++ b/packages/keystores-node/tsconfig.cjs.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/node.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/cjs", }, "files": [ "src/index.ts" diff --git a/packages/crypto/tsconfig.json b/packages/keystores-node/tsconfig.esm.json similarity index 53% rename from packages/crypto/tsconfig.json rename to packages/keystores-node/tsconfig.esm.json index ae42955e4..5f3d7bd79 100644 --- a/packages/crypto/tsconfig.json +++ b/packages/keystores-node/tsconfig.esm.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/esm.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/esm", }, "files": [ "src/index.ts" diff --git a/packages/keystores/package.json b/packages/keystores/package.json index b66ad3eb7..0c12610cf 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -2,10 +2,12 @@ "name": "@near-js/keystores", "version": "0.0.1", "description": "Key storage and management implementations", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -23,6 +25,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/keystores/src/in_memory_key_store.ts b/packages/keystores/src/in_memory_key_store.ts index c4114e477..3e831f74a 100644 --- a/packages/keystores/src/in_memory_key_store.ts +++ b/packages/keystores/src/in_memory_key_store.ts @@ -1,5 +1,6 @@ import { KeyPair } from '@near-js/crypto'; -import { KeyStore } from './keystore'; + +import { KeyStore } from './keystore.js'; /** * Simple in-memory keystore for mainly for testing purposes. diff --git a/packages/keystores/src/index.ts b/packages/keystores/src/index.ts index f095dee7f..49569e632 100644 --- a/packages/keystores/src/index.ts +++ b/packages/keystores/src/index.ts @@ -1,3 +1,3 @@ -export { InMemoryKeyStore } from './in_memory_key_store'; -export { KeyStore } from './keystore'; -export { MergeKeyStore } from './merge_key_store'; +export { InMemoryKeyStore } from './in_memory_key_store.js'; +export { KeyStore } from './keystore.js'; +export { MergeKeyStore } from './merge_key_store.js'; diff --git a/packages/keystores/src/merge_key_store.ts b/packages/keystores/src/merge_key_store.ts index ca3e5705d..24467d4e1 100644 --- a/packages/keystores/src/merge_key_store.ts +++ b/packages/keystores/src/merge_key_store.ts @@ -1,5 +1,6 @@ import { KeyPair } from '@near-js/crypto'; -import { KeyStore } from './keystore'; + +import { KeyStore } from './keystore.js'; /** * Keystore which can be used to merge multiple key stores into one virtual key store. diff --git a/packages/keystores/tsconfig.cjs.json b/packages/keystores/tsconfig.cjs.json new file mode 100644 index 000000000..b055ecfb9 --- /dev/null +++ b/packages/keystores/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/providers/tsconfig.json b/packages/keystores/tsconfig.esm.json similarity index 53% rename from packages/providers/tsconfig.json rename to packages/keystores/tsconfig.esm.json index ae42955e4..5f3d7bd79 100644 --- a/packages/providers/tsconfig.json +++ b/packages/keystores/tsconfig.esm.json @@ -1,7 +1,7 @@ { - "extends": "../../tsconfig.node.json", + "extends": "tsconfig/esm.json", "compilerOptions": { - "outDir": "./lib", + "outDir": "./lib/esm", }, "files": [ "src/index.ts" diff --git a/packages/near-api-js/src/connect.ts b/packages/near-api-js/src/connect.ts index 8b95c54d8..aaeeed20c 100644 --- a/packages/near-api-js/src/connect.ts +++ b/packages/near-api-js/src/connect.ts @@ -56,7 +56,7 @@ export async function connect(config: ConnectConfig): Promise { keyPathStore, config.keyStore ], { writeKeyStoreIndex: 1 }); - if (!process.env['NEAR_NO_LOGS']) { + if (typeof process !== 'undefined' && !process.env['NEAR_NO_LOGS']) { console.log(`Loaded master account ${accountKeyFile[0]} key from ${config.keyPath} with public key = ${keyPair.getPublicKey()}`); } } diff --git a/packages/providers/package.json b/packages/providers/package.json index b08d0be39..878d9c7a9 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -2,10 +2,12 @@ "name": "@near-js/providers", "version": "0.0.1", "description": "Library of implementations for interfacing with the NEAR blockchain", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -27,9 +29,17 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" }, "optionalDependencies": { "node-fetch": "^2.6.1" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index e80c50c19..89d95d834 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -1,8 +1,9 @@ import { TypedError } from '@near-js/types'; +import { logWarning } from '@near-js/utils'; import createError from 'http-errors'; -import { exponentialBackoff } from './exponential-backoff'; -import nodeFetch from './fetch'; +import { exponentialBackoff } from './exponential-backoff.js'; +import nodeFetch from './fetch.js'; const START_WAIT_TIME_MS = 1000; const BACKOFF_MULTIPLIER = 1.5; @@ -17,8 +18,6 @@ export interface ConnectionInfo { headers?: { [key: string]: string | number }; } -const logWarning = (...args) => !process.env['NEAR_NO_LOGS'] && console.warn(...args); - export async function fetchJson(connectionInfoOrUrl: string | ConnectionInfo, json?: string): Promise { let connectionInfo: ConnectionInfo = { url: null }; if (typeof (connectionInfoOrUrl) === 'string') { diff --git a/packages/providers/src/index.ts b/packages/providers/src/index.ts index 8c6eda449..630c47667 100644 --- a/packages/providers/src/index.ts +++ b/packages/providers/src/index.ts @@ -1,4 +1,4 @@ -export { exponentialBackoff } from './exponential-backoff'; -export { JsonRpcProvider } from './json-rpc-provider'; -export { Provider } from './provider'; -export { fetchJson } from './fetch_json'; +export { exponentialBackoff } from './exponential-backoff.js'; +export { JsonRpcProvider } from './json-rpc-provider.js'; +export { Provider } from './provider.js'; +export { fetchJson } from './fetch_json.js'; diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 1f5b20e92..e8f10eebe 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -33,9 +33,9 @@ import { } from '@near-js/transactions'; import { baseEncode } from 'borsh'; -import { exponentialBackoff } from './exponential-backoff'; -import { Provider } from './provider'; -import { ConnectionInfo, fetchJson } from './fetch_json'; +import { exponentialBackoff } from './exponential-backoff.js'; +import { Provider } from './provider.js'; +import { ConnectionInfo, fetchJson } from './fetch_json.js'; /** @hidden */ // Default number of retries before giving up on a request. diff --git a/packages/providers/tsconfig.cjs.json b/packages/providers/tsconfig.cjs.json new file mode 100644 index 000000000..d8d808021 --- /dev/null +++ b/packages/providers/tsconfig.cjs.json @@ -0,0 +1,14 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "lib": [ + "es2015", + "esnext", + "dom" + ], + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/providers/tsconfig.esm.json b/packages/providers/tsconfig.esm.json new file mode 100644 index 000000000..98533488e --- /dev/null +++ b/packages/providers/tsconfig.esm.json @@ -0,0 +1,13 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "lib": [ + "esnext", + "dom" + ], + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/signers/package.json b/packages/signers/package.json index 866fe950e..32965fb88 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -2,14 +2,16 @@ "name": "@near-js/signers", "version": "0.0.1", "description": "Core dependencies for the NEAR API JavaScript client", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", - "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", @@ -23,6 +25,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/signers/src/in_memory_signer.ts b/packages/signers/src/in_memory_signer.ts index 078acfe65..b8d92e0b4 100644 --- a/packages/signers/src/in_memory_signer.ts +++ b/packages/signers/src/in_memory_signer.ts @@ -2,7 +2,7 @@ import { KeyPair, PublicKey, Signature } from '@near-js/crypto'; import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; import sha256 from 'js-sha256'; -import { Signer } from './signer'; +import { Signer } from './signer.js'; /** * Signs using in memory key store. diff --git a/packages/signers/src/index.ts b/packages/signers/src/index.ts index 9a5ebfb0b..01dc2970d 100644 --- a/packages/signers/src/index.ts +++ b/packages/signers/src/index.ts @@ -1,2 +1,2 @@ -export { InMemorySigner } from './in_memory_signer'; -export { Signer } from './signer'; +export { InMemorySigner } from './in_memory_signer.js'; +export { Signer } from './signer.js'; diff --git a/packages/signers/tsconfig.cjs.json b/packages/signers/tsconfig.cjs.json new file mode 100644 index 000000000..b055ecfb9 --- /dev/null +++ b/packages/signers/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/signers/tsconfig.esm.json b/packages/signers/tsconfig.esm.json new file mode 100644 index 000000000..5f3d7bd79 --- /dev/null +++ b/packages/signers/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/signers/tsconfig.json b/packages/signers/tsconfig.json deleted file mode 100644 index ae42955e4..000000000 --- a/packages/signers/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.node.json", - "compilerOptions": { - "outDir": "./lib", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/packages/transactions/package.json b/packages/transactions/package.json index a26f9f3c6..a54b28768 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -2,10 +2,12 @@ "name": "@near-js/transactions", "version": "0.0.1", "description": "Functions and data types for transactions on NEAR", + "main": "lib/esm/index.js", "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json && tsc -p tsconfig.esm.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", @@ -29,6 +31,7 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" }, "exports": { diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts index cb5013ae1..d7676e95c 100644 --- a/packages/transactions/src/sign.ts +++ b/packages/transactions/src/sign.ts @@ -1,5 +1,5 @@ import { Signer } from '@near-js/signers'; -import sha256 from 'js-sha256'; +import { sha256 } from 'js-sha256'; import BN from 'bn.js'; import { serialize } from 'borsh'; @@ -16,7 +16,7 @@ import { SCHEMA, Signature, SignedTransaction, Transaction } from './schema.js'; */ async function signTransactionObject(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]> { const message = serialize(SCHEMA, transaction); - const hash = new Uint8Array(sha256.sha256.array(message)); + const hash = new Uint8Array(sha256.array(message)); const signature = await signer.signMessage(message, accountId, networkId); const signedTx = new SignedTransaction({ transaction, diff --git a/packages/transactions/tsconfig.cjs.json b/packages/transactions/tsconfig.cjs.json new file mode 100644 index 000000000..b055ecfb9 --- /dev/null +++ b/packages/transactions/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/transactions/tsconfig.esm.json b/packages/transactions/tsconfig.esm.json new file mode 100644 index 000000000..5f3d7bd79 --- /dev/null +++ b/packages/transactions/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/tsconfig.base.json b/packages/tsconfig/base.json similarity index 94% rename from tsconfig.base.json rename to packages/tsconfig/base.json index 3ae27a0e3..f357c9119 100644 --- a/tsconfig.base.json +++ b/packages/tsconfig/base.json @@ -15,6 +15,6 @@ "noImplicitReturns": true, "noUnusedLocals": true, "experimentalDecorators": true, - "resolveJsonModule": true, + "resolveJsonModule": true } } diff --git a/packages/tsconfig/browser.esm.json b/packages/tsconfig/browser.esm.json new file mode 100644 index 000000000..57c3b28db --- /dev/null +++ b/packages/tsconfig/browser.esm.json @@ -0,0 +1,11 @@ +{ + "extends": "./browser.json", + "compilerOptions": { + "lib": [ + "es2015", + "esnext", + "dom" + ], + "module": "es2022" + } +} diff --git a/tsconfig.browser.json b/packages/tsconfig/browser.json similarity index 69% rename from tsconfig.browser.json rename to packages/tsconfig/browser.json index 41a429f16..0fd297656 100644 --- a/tsconfig.browser.json +++ b/packages/tsconfig/browser.json @@ -1,10 +1,10 @@ { - "extends": "./tsconfig.base.json", + "extends": "./base.json", "compilerOptions": { "lib": [ "es2015", "esnext", "dom" - ], + ] } } diff --git a/packages/tsconfig/esm.json b/packages/tsconfig/esm.json new file mode 100644 index 000000000..14a8c0bc4 --- /dev/null +++ b/packages/tsconfig/esm.json @@ -0,0 +1,9 @@ +{ + "extends": "./base.json", + "compilerOptions": { + "lib": [ + "esnext" + ], + "module": "es2022" + } +} diff --git a/tsconfig.node.json b/packages/tsconfig/node.json similarity index 65% rename from tsconfig.node.json rename to packages/tsconfig/node.json index b4f764428..6da7ec9a1 100644 --- a/tsconfig.node.json +++ b/packages/tsconfig/node.json @@ -1,9 +1,9 @@ { - "extends": "./tsconfig.base.json", + "extends": "./base.json", "compilerOptions": { "lib": [ "es2015", "esnext" - ], + ] } } diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json new file mode 100644 index 000000000..0d32af300 --- /dev/null +++ b/packages/tsconfig/package.json @@ -0,0 +1,5 @@ +{ + "name": "tsconfig", + "version": "1.0.0", + "private": true +} diff --git a/packages/types/package.json b/packages/types/package.json index 007edd2cf..2f77f9a30 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -2,10 +2,12 @@ "name": "@near-js/types", "version": "0.0.1", "description": "TypeScript types for working with the Near JS API", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts", "lint:fix": "eslint **/*.ts" }, @@ -19,6 +21,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 392a6af8d..c3bcd521f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,3 +1,3 @@ -export * from './assignable'; -export * from './errors'; -export * from './provider'; +export * from './assignable.js'; +export * from './errors.js'; +export * from './provider/index.js'; diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index 370447265..65c322b38 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -1,10 +1,9 @@ - export { IdType, LightClientBlockLiteView, LightClientProof, LightClientProofRequest, -} from './light_client'; +} from './light_client.js'; export { AccessKeyWithPublicKey, BlockHash, @@ -34,7 +33,7 @@ export { SyncInfo, TotalWeight, Transaction as ProviderTransaction, -} from './protocol'; +} from './protocol.js'; export { CallFunctionRequest, RpcQueryRequest, @@ -43,7 +42,7 @@ export { ViewAccountRequest, ViewCodeRequest, ViewStateRequest, -} from './request'; +} from './request.js'; export { AccessKeyInfoView, AccessKeyList, @@ -64,10 +63,10 @@ export { FunctionCallPermissionView, QueryResponseKind, ViewStateResult, -} from './response'; +} from './response.js'; export { CurrentEpochValidatorInfo, EpochValidatorInfo, NextEpochValidatorInfo, ValidatorStakeView, -} from './validator'; +} from './validator.js'; diff --git a/packages/types/src/provider/light_client.ts b/packages/types/src/provider/light_client.ts index d69235c19..257ffb950 100644 --- a/packages/types/src/provider/light_client.ts +++ b/packages/types/src/provider/light_client.ts @@ -3,8 +3,8 @@ * @module */ -import { BlockHeaderInnerLiteView, MerklePath } from './protocol'; -import { ExecutionOutcomeWithIdView } from './response'; +import { BlockHeaderInnerLiteView, MerklePath } from './protocol.js'; +import { ExecutionOutcomeWithIdView } from './response.js'; export interface LightClientBlockLiteView { prev_block_hash: string; diff --git a/packages/types/src/provider/request.ts b/packages/types/src/provider/request.ts index 387fabfb8..541140314 100644 --- a/packages/types/src/provider/request.ts +++ b/packages/types/src/provider/request.ts @@ -3,7 +3,7 @@ * @module */ -import { BlockReference } from './protocol'; +import { BlockReference } from './protocol.js'; export interface ViewAccountRequest { request_type: 'view_account'; diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index dffb59531..f63a5c418 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -5,7 +5,7 @@ import BN from 'bn.js'; -import { BlockHash, BlockHeight, MerklePath } from './protocol'; +import { BlockHash, BlockHeight, MerklePath } from './protocol.js'; export enum ExecutionStatusBasic { Unknown = 'Unknown', diff --git a/packages/types/tsconfig.cjs.json b/packages/types/tsconfig.cjs.json new file mode 100644 index 000000000..b055ecfb9 --- /dev/null +++ b/packages/types/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/types/tsconfig.esm.json b/packages/types/tsconfig.esm.json new file mode 100644 index 000000000..5f3d7bd79 --- /dev/null +++ b/packages/types/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json deleted file mode 100644 index ae42955e4..000000000 --- a/packages/types/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.node.json", - "compilerOptions": { - "outDir": "./lib", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/packages/utils/package.json b/packages/utils/package.json index 58049ecb7..f2d85c4d7 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,10 +2,12 @@ "name": "@near-js/utils", "version": "0.0.1", "description": "Common methods and constants for the NEAR API JavaScript client", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc && eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix --no-error-on-unmatched-pattern", "test": "jest test" @@ -23,6 +25,14 @@ "@types/node": "^18.11.18", "jest": "^26.0.1", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/utils/src/errors/index.ts b/packages/utils/src/errors/index.ts index 427780331..2e0253cdf 100644 --- a/packages/utils/src/errors/index.ts +++ b/packages/utils/src/errors/index.ts @@ -1,8 +1,8 @@ -export { logWarning } from './errors'; +export { logWarning } from './errors.js'; export { ServerError, formatError, getErrorTypeFromErrorMessage, parseResultError, parseRpcError, -} from './rpc_errors'; +} from './rpc_errors.js'; diff --git a/packages/utils/src/errors/rpc_errors.ts b/packages/utils/src/errors/rpc_errors.ts index 54605bc16..a86498bf7 100644 --- a/packages/utils/src/errors/rpc_errors.ts +++ b/packages/utils/src/errors/rpc_errors.ts @@ -1,7 +1,7 @@ import { TypedError } from '@near-js/types'; import Mustache from 'mustache'; -import { formatNearAmount } from '../format'; +import { formatNearAmount } from '../format.js'; import messages from './error_messages.json'; import schema from './rpc_error_schema.json'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index acf0293d2..81ba2660e 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,6 +1,6 @@ -export * from './constants'; -export * from './errors'; -export * from './format'; -export * from './logging'; -export * from './provider'; -export * from './validators'; +export * from './constants.js'; +export * from './errors/index.js'; +export * from './format.js'; +export * from './logging.js'; +export * from './provider.js'; +export * from './validators.js'; diff --git a/packages/utils/src/logging.ts b/packages/utils/src/logging.ts index 5024a7559..d7cefd2c5 100644 --- a/packages/utils/src/logging.ts +++ b/packages/utils/src/logging.ts @@ -1,6 +1,6 @@ import { FinalExecutionOutcome } from '@near-js/types'; -import { parseRpcError } from './errors'; +import { parseRpcError } from './errors/index.js'; const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; diff --git a/packages/utils/tsconfig.cjs.json b/packages/utils/tsconfig.cjs.json new file mode 100644 index 000000000..b055ecfb9 --- /dev/null +++ b/packages/utils/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/node.json", + "compilerOptions": { + "outDir": "./lib/cjs", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/utils/tsconfig.esm.json b/packages/utils/tsconfig.esm.json new file mode 100644 index 000000000..5f3d7bd79 --- /dev/null +++ b/packages/utils/tsconfig.esm.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json deleted file mode 100644 index ae42955e4..000000000 --- a/packages/utils/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.node.json", - "compilerOptions": { - "outDir": "./lib", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json index 871726e61..c33c341f0 100644 --- a/packages/wallet-account/package.json +++ b/packages/wallet-account/package.json @@ -2,11 +2,13 @@ "name": "@near-js/wallet-account", "version": "0.0.1", "description": "Dependencies for the NEAR API JavaScript client in the browser", - "main": "lib/index.js", + "main": "lib/esm/index.js", + "type": "module", + "browser": "./lib/cjs/index.js", "scripts": { "build": "pnpm compile", - "compile": "tsc -p tsconfig.json", - "test": "jest test" + "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", @@ -27,6 +29,14 @@ "jest": "^26.0.1", "localstorage-memory": "^1.0.3", "ts-jest": "^26.5.6", + "tsconfig": "workspace:*", "typescript": "^4.9.4" - } + }, + "exports": { + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js" + }, + "files": [ + "lib" + ] } diff --git a/packages/wallet-account/src/index.ts b/packages/wallet-account/src/index.ts index ea0f840b6..950edd4f8 100644 --- a/packages/wallet-account/src/index.ts +++ b/packages/wallet-account/src/index.ts @@ -1,2 +1,2 @@ -export { Near, NearConfig } from './near'; -export { ConnectedWalletAccount, WalletConnection } from './wallet_account'; \ No newline at end of file +export { Near, NearConfig } from './near.js'; +export { ConnectedWalletAccount, WalletConnection } from './wallet_account.js'; \ No newline at end of file diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index fb495df7f..31e092ba1 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -19,7 +19,7 @@ import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transac import BN from 'bn.js'; import { baseDecode, serialize } from 'borsh'; -import { Near } from './near'; +import { Near } from './near.js'; const LOGIN_WALLET_URL_SUFFIX = '/login/'; const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; diff --git a/packages/wallet-account/tsconfig.cjs.json b/packages/wallet-account/tsconfig.cjs.json new file mode 100644 index 000000000..d0e594ad2 --- /dev/null +++ b/packages/wallet-account/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "tsconfig/browser.json", + "compilerOptions": { + "outDir": "./lib/cjs", + "preserveSymlinks": false + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/wallet-account/tsconfig.esm.json b/packages/wallet-account/tsconfig.esm.json new file mode 100644 index 000000000..6142e8356 --- /dev/null +++ b/packages/wallet-account/tsconfig.esm.json @@ -0,0 +1,10 @@ +{ + "extends": "tsconfig/browser.esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + "preserveSymlinks": false + }, + "files": [ + "src/index.ts" + ] +} diff --git a/packages/wallet-account/tsconfig.json b/packages/wallet-account/tsconfig.json deleted file mode 100644 index cd1fd28f0..000000000 --- a/packages/wallet-account/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.browser.json", - "compilerOptions": { - "preserveSymlinks": false, - "outDir": "./lib", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6c65a031..22ecf26f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,6 +52,7 @@ importers: near-abi: 0.1.1 near-hello: ^0.5.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -73,6 +74,7 @@ importers: jest: 26.6.3 near-hello: 0.5.1 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/cookbook: @@ -93,6 +95,7 @@ importers: borsh: ^0.7.0 jest: ^26.0.1 ts-jest: ^26.5.6 + tsconfig: workspace:* tweetnacl: ^1.0.1 typescript: ^4.9.4 dependencies: @@ -113,6 +116,7 @@ importers: '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -121,6 +125,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/keystores-browser: @@ -129,6 +134,7 @@ importers: '@near-js/keystores': workspace:* jest: ^26.0.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -136,6 +142,7 @@ importers: devDependencies: jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/keystores-node: @@ -145,6 +152,7 @@ importers: '@types/node': ^18.11.18 jest: ^26.0.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -153,6 +161,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/near-api-js: @@ -248,6 +257,7 @@ importers: jest: ^26.0.1 node-fetch: ^2.6.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/transactions': link:../transactions @@ -262,6 +272,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/signers: @@ -272,6 +283,7 @@ importers: jest: ^26.0.1 js-sha256: ^0.9.0 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -281,6 +293,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/transactions: @@ -296,6 +309,7 @@ importers: jest: ^26.0.1 js-sha256: ^0.9.0 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/crypto': link:../crypto @@ -310,14 +324,19 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 + packages/tsconfig: + specifiers: {} + packages/types: specifiers: '@types/node': ^18.11.18 bn.js: 5.2.1 jest: ^26.0.1 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: bn.js: 5.2.1 @@ -325,6 +344,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/utils: @@ -336,6 +356,7 @@ importers: jest: ^26.0.1 mustache: ^4.0.0 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/types': link:../types @@ -346,6 +367,7 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages/wallet-account: @@ -363,6 +385,7 @@ importers: jest: ^26.0.1 localstorage-memory: ^1.0.3 ts-jest: ^26.5.6 + tsconfig: workspace:* typescript: ^4.9.4 dependencies: '@near-js/accounts': link:../accounts @@ -379,6 +402,7 @@ importers: jest: 26.6.3 localstorage-memory: 1.0.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 packages: diff --git a/tsconfig.esm.json b/tsconfig.esm.json deleted file mode 100644 index fb35a180e..000000000 --- a/tsconfig.esm.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.base.json", - "compilerOptions": { - "lib": [ - "es2015", - "esnext" - ], - "module": "nodenext" - } -} From aa4136218348113a33094377ed3cc1ac087cc8aa Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 31 Jan 2023 14:06:43 -0800 Subject: [PATCH 78/84] build: guard global references --- packages/accounts/src/account.ts | 2 +- packages/providers/src/fetch_json.ts | 20 ++++++++++++++++++-- packages/providers/src/json-rpc-provider.ts | 2 +- packages/utils/src/errors/errors.ts | 2 +- packages/utils/src/logging.ts | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 7965f4b0c..8ceab3f47 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -355,7 +355,7 @@ export class Account { * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted */ async deleteAccount(beneficiaryId: string) { - if (!process.env['NEAR_NO_LOGS']) { + if (typeof process !== 'undefined' && !process.env['NEAR_NO_LOGS']) { console.log('Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting.'); } return this.signAndSendTransaction({ diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index 89d95d834..234fdbf1d 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -3,7 +3,22 @@ import { logWarning } from '@near-js/utils'; import createError from 'http-errors'; import { exponentialBackoff } from './exponential-backoff.js'; -import nodeFetch from './fetch.js'; + +async function resolveFetch() { + if (typeof fetch !== 'undefined') { + return fetch; + } + + if (typeof global !== 'undefined' && global.fetch) { + return global.fetch; + } + + try { + return (await import('./fetch.js')).default; + } catch { + return () => undefined; + } +} const START_WAIT_TIME_MS = 1000; const BACKOFF_MULTIPLIER = 1.5; @@ -28,7 +43,8 @@ export async function fetchJson(connectionInfoOrUrl: string | ConnectionInfo, js const response = await exponentialBackoff(START_WAIT_TIME_MS, RETRY_NUMBER, BACKOFF_MULTIPLIER, async () => { try { - const response = await (global.fetch || nodeFetch)(connectionInfo.url, { + const fnFetch = await resolveFetch(); + const response = await fnFetch(connectionInfo.url, { method: json ? 'POST' : 'GET', body: json ? json : undefined, headers: { ...connectionInfo.headers, 'Content-Type': 'application/json' } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index e8f10eebe..6458bded6 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -358,7 +358,7 @@ export class JsonRpcProvider extends Provider { return response; } catch (error) { if (error.type === 'TimeoutError') { - if (!process.env['NEAR_NO_LOGS']) { + if (typeof process !== 'undefined' && !process.env['NEAR_NO_LOGS']) { console.warn(`Retrying request to ${method} as it has timed out`, params); } return null; diff --git a/packages/utils/src/errors/errors.ts b/packages/utils/src/errors/errors.ts index d6c9c5bbc..27fdd8c19 100644 --- a/packages/utils/src/errors/errors.ts +++ b/packages/utils/src/errors/errors.ts @@ -1,5 +1,5 @@ export function logWarning(...args: any[]): void { - if (!process.env['NEAR_NO_LOGS']){ + if (typeof process !== 'undefined' && !process.env['NEAR_NO_LOGS']) { console.warn(...args); } } diff --git a/packages/utils/src/logging.ts b/packages/utils/src/logging.ts index d7cefd2c5..be0156646 100644 --- a/packages/utils/src/logging.ts +++ b/packages/utils/src/logging.ts @@ -2,7 +2,7 @@ import { FinalExecutionOutcome } from '@near-js/types'; import { parseRpcError } from './errors/index.js'; -const SUPPRESS_LOGGING = !!process.env.NEAR_NO_LOGS; +const SUPPRESS_LOGGING = typeof process !== 'undefined' && !!process.env.NEAR_NO_LOGS; /** * Parse and print details from a query execution response From 25b84ab2cdfbc406984b92e7a09abd340f13fcc2 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Wed, 1 Feb 2023 16:30:04 -0800 Subject: [PATCH 79/84] feat: example usage packages --- package.json | 2 +- packages/example-esm/index.js | 11 + packages/example-esm/package.json | 17 + packages/example-vite/.gitignore | 24 + packages/example-vite/README.md | 18 + packages/example-vite/index.html | 13 + packages/example-vite/package.json | 22 + packages/example-vite/public/vite.svg | 1 + packages/example-vite/src/App.vue | 29 + packages/example-vite/src/assets/vue.svg | 1 + .../src/components/HelloWorld.vue | 53 ++ packages/example-vite/src/main.ts | 5 + packages/example-vite/src/style.css | 81 +++ packages/example-vite/src/vite-env.d.ts | 1 + packages/example-vite/tsconfig.json | 18 + packages/example-vite/tsconfig.node.json | 9 + packages/example-vite/vite.config.ts | 10 + pnpm-lock.yaml | 567 ++++++++++++++++-- 18 files changed, 847 insertions(+), 35 deletions(-) create mode 100644 packages/example-esm/index.js create mode 100644 packages/example-esm/package.json create mode 100644 packages/example-vite/.gitignore create mode 100644 packages/example-vite/README.md create mode 100644 packages/example-vite/index.html create mode 100644 packages/example-vite/package.json create mode 100644 packages/example-vite/public/vite.svg create mode 100644 packages/example-vite/src/App.vue create mode 100644 packages/example-vite/src/assets/vue.svg create mode 100644 packages/example-vite/src/components/HelloWorld.vue create mode 100644 packages/example-vite/src/main.ts create mode 100644 packages/example-vite/src/style.css create mode 100644 packages/example-vite/src/vite-env.d.ts create mode 100644 packages/example-vite/tsconfig.json create mode 100644 packages/example-vite/tsconfig.node.json create mode 100644 packages/example-vite/vite.config.ts diff --git a/package.json b/package.json index 716e74a2b..6b168a484 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "@typescript-eslint/parser": "^5.31.0", "commitlint": "^17.0.3", "concurrently": "^7.6.0", - "eslint": "^8.20.0", + "eslint": "^8.32.0", "husky": "^7.0.4", "rimraf": "^3.0.2", "turbo": "^1.4.5", diff --git a/packages/example-esm/index.js b/packages/example-esm/index.js new file mode 100644 index 000000000..3432a5054 --- /dev/null +++ b/packages/example-esm/index.js @@ -0,0 +1,11 @@ +import { Account, Connection } from '@near-js/accounts'; + +const account = new Account(Connection.fromConfig({ + networkId: 'mainnet', + provider: { type: 'JsonRpcProvider', args: { url: 'https://testnet.rpc.near.org' } }, + signer: { type: 'InMemorySigner', keyStore: {} }, +}), 'gornt.testnet'); + +(async function () { + console.log(await account.getAccessKeys()); +}()); diff --git a/packages/example-esm/package.json b/packages/example-esm/package.json new file mode 100644 index 000000000..d63ca32ad --- /dev/null +++ b/packages/example-esm/package.json @@ -0,0 +1,17 @@ +{ + "name": "example-esm", + "private": true, + "version": "0.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "@near-js/accounts": "workspace:*" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/example-vite/.gitignore b/packages/example-vite/.gitignore new file mode 100644 index 000000000..a547bf36d --- /dev/null +++ b/packages/example-vite/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/example-vite/README.md b/packages/example-vite/README.md new file mode 100644 index 000000000..ef72fd524 --- /dev/null +++ b/packages/example-vite/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/packages/example-vite/package.json b/packages/example-vite/package.json new file mode 100644 index 000000000..83fce60f8 --- /dev/null +++ b/packages/example-vite/package.json @@ -0,0 +1,22 @@ +{ + "name": "example-vite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@near-js/accounts": "workspace:*", + "dayjs": "^1.11.7", + "typescript": "^4.9.4", + "vue": "^3.2.45" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.0.0", + "vite": "^4.0.0", + "vue-tsc": "^1.0.11" + } +} \ No newline at end of file diff --git a/packages/example-vite/public/vite.svg b/packages/example-vite/public/vite.svg new file mode 100644 index 000000000..e7b8dfb1b --- /dev/null +++ b/packages/example-vite/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/example-vite/src/App.vue b/packages/example-vite/src/App.vue new file mode 100644 index 000000000..fb679f1d5 --- /dev/null +++ b/packages/example-vite/src/App.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/packages/example-vite/src/assets/vue.svg b/packages/example-vite/src/assets/vue.svg new file mode 100644 index 000000000..770e9d333 --- /dev/null +++ b/packages/example-vite/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/example-vite/src/components/HelloWorld.vue b/packages/example-vite/src/components/HelloWorld.vue new file mode 100644 index 000000000..f191dba83 --- /dev/null +++ b/packages/example-vite/src/components/HelloWorld.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/packages/example-vite/src/main.ts b/packages/example-vite/src/main.ts new file mode 100644 index 000000000..2425c0f74 --- /dev/null +++ b/packages/example-vite/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/packages/example-vite/src/style.css b/packages/example-vite/src/style.css new file mode 100644 index 000000000..0192f9aac --- /dev/null +++ b/packages/example-vite/src/style.css @@ -0,0 +1,81 @@ +:root { + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/packages/example-vite/src/vite-env.d.ts b/packages/example-vite/src/vite-env.d.ts new file mode 100644 index 000000000..11f02fe2a --- /dev/null +++ b/packages/example-vite/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/example-vite/tsconfig.json b/packages/example-vite/tsconfig.json new file mode 100644 index 000000000..b557c4047 --- /dev/null +++ b/packages/example-vite/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/example-vite/tsconfig.node.json b/packages/example-vite/tsconfig.node.json new file mode 100644 index 000000000..9d31e2aed --- /dev/null +++ b/packages/example-vite/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/example-vite/vite.config.ts b/packages/example-vite/vite.config.ts new file mode 100644 index 000000000..411e108c0 --- /dev/null +++ b/packages/example-vite/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + define: { + global: {}, + } +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22ecf26f8..67a2e2399 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ importers: '@typescript-eslint/parser': ^5.31.0 commitlint: ^17.0.3 concurrently: ^7.6.0 - eslint: ^8.20.0 + eslint: ^8.32.0 husky: ^7.0.4 rimraf: ^3.0.2 turbo: ^1.4.5 @@ -22,11 +22,11 @@ importers: '@changesets/cli': 2.25.2 '@commitlint/cli': 17.3.0 '@commitlint/config-conventional': 17.3.0 - '@typescript-eslint/eslint-plugin': 5.46.1_imrg37k3svwu377c6q7gkarwmi - '@typescript-eslint/parser': 5.46.1_ha6vam6werchizxrnqvarmz2zu + '@typescript-eslint/eslint-plugin': 5.46.1_r6k47pdd6iv64eqbfzxfkmzwzu + '@typescript-eslint/parser': 5.46.1_7uibuqfxkfaozanbtbziikiqje commitlint: 17.3.0 concurrently: 7.6.0 - eslint: 8.29.0 + eslint: 8.32.0 husky: 7.0.4 rimraf: 3.0.2 turbo: 1.6.3 @@ -107,8 +107,34 @@ importers: '@types/node': 18.11.18 jest: 26.6.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli + tsconfig: link:../tsconfig typescript: 4.9.4 + packages/example-esm: + specifiers: + '@near-js/accounts': workspace:* + dependencies: + '@near-js/accounts': link:../accounts + + packages/example-vite: + specifiers: + '@near-js/accounts': workspace:* + '@vitejs/plugin-vue': ^4.0.0 + dayjs: ^1.11.7 + typescript: ^4.9.4 + vite: ^4.0.0 + vue: ^3.2.45 + vue-tsc: ^1.0.11 + dependencies: + '@near-js/accounts': link:../accounts + dayjs: 1.11.7 + typescript: 4.9.4 + vue: 3.2.45 + devDependencies: + '@vitejs/plugin-vue': 4.0.0_vite@4.0.4+vue@3.2.45 + vite: 4.0.4 + vue-tsc: 1.0.24_typescript@4.9.4 + packages/keystores: specifiers: '@near-js/crypto': workspace:* @@ -537,12 +563,10 @@ packages: /@babel/helper-string-parser/7.19.4: resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-identifier/7.19.1: resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option/7.18.6: resolution: {integrity: sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==} @@ -575,7 +599,6 @@ packages: hasBin: true dependencies: '@babel/types': 7.20.5 - dev: true /@babel/plugin-syntax-async-generators/7.8.4_@babel+core@7.20.5: resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} @@ -727,7 +750,6 @@ packages: '@babel/helper-string-parser': 7.19.4 '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - dev: true /@bcoe/v8-coverage/0.2.3: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1119,8 +1141,206 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@eslint/eslintrc/1.3.3: - resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} + /@esbuild/android-arm/0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint/eslintrc/1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -1777,7 +1997,7 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/eslint-plugin/5.46.1_imrg37k3svwu377c6q7gkarwmi: + /@typescript-eslint/eslint-plugin/5.46.1_r6k47pdd6iv64eqbfzxfkmzwzu: resolution: {integrity: sha512-YpzNv3aayRBwjs4J3oz65eVLXc9xx0PDbIRisHj+dYhvBn02MjYOD96P8YGiWEIFBrojaUjxvkaUpakD82phsA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1788,12 +2008,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.46.1_ha6vam6werchizxrnqvarmz2zu + '@typescript-eslint/parser': 5.46.1_7uibuqfxkfaozanbtbziikiqje '@typescript-eslint/scope-manager': 5.46.1 - '@typescript-eslint/type-utils': 5.46.1_ha6vam6werchizxrnqvarmz2zu - '@typescript-eslint/utils': 5.46.1_ha6vam6werchizxrnqvarmz2zu + '@typescript-eslint/type-utils': 5.46.1_7uibuqfxkfaozanbtbziikiqje + '@typescript-eslint/utils': 5.46.1_7uibuqfxkfaozanbtbziikiqje debug: 4.3.4 - eslint: 8.29.0 + eslint: 8.32.0 ignore: 5.2.1 natural-compare-lite: 1.4.0 regexpp: 3.2.0 @@ -1804,7 +2024,7 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.46.1_ha6vam6werchizxrnqvarmz2zu: + /@typescript-eslint/parser/5.46.1_7uibuqfxkfaozanbtbziikiqje: resolution: {integrity: sha512-RelQ5cGypPh4ySAtfIMBzBGyrNerQcmfA1oJvPj5f+H4jI59rl9xxpn4bonC0tQvUKOEN7eGBFWxFLK3Xepneg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1818,7 +2038,7 @@ packages: '@typescript-eslint/types': 5.46.1 '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.9.4 debug: 4.3.4 - eslint: 8.29.0 + eslint: 8.32.0 typescript: 4.9.4 transitivePeerDependencies: - supports-color @@ -1832,7 +2052,7 @@ packages: '@typescript-eslint/visitor-keys': 5.46.1 dev: true - /@typescript-eslint/type-utils/5.46.1_ha6vam6werchizxrnqvarmz2zu: + /@typescript-eslint/type-utils/5.46.1_7uibuqfxkfaozanbtbziikiqje: resolution: {integrity: sha512-V/zMyfI+jDmL1ADxfDxjZ0EMbtiVqj8LUGPAGyBkXXStWmCUErMpW873zEHsyguWCuq2iN4BrlWUkmuVj84yng==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1843,9 +2063,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.9.4 - '@typescript-eslint/utils': 5.46.1_ha6vam6werchizxrnqvarmz2zu + '@typescript-eslint/utils': 5.46.1_7uibuqfxkfaozanbtbziikiqje debug: 4.3.4 - eslint: 8.29.0 + eslint: 8.32.0 tsutils: 3.21.0_typescript@4.9.4 typescript: 4.9.4 transitivePeerDependencies: @@ -1878,7 +2098,7 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.46.1_ha6vam6werchizxrnqvarmz2zu: + /@typescript-eslint/utils/5.46.1_7uibuqfxkfaozanbtbziikiqje: resolution: {integrity: sha512-RBdBAGv3oEpFojaCYT4Ghn4775pdjvwfDOfQ2P6qzNVgQOVrnSPe5/Pb88kv7xzYQjoio0eKHKB9GJ16ieSxvA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1889,9 +2109,9 @@ packages: '@typescript-eslint/scope-manager': 5.46.1 '@typescript-eslint/types': 5.46.1 '@typescript-eslint/typescript-estree': 5.46.1_typescript@4.9.4 - eslint: 8.29.0 + eslint: 8.32.0 eslint-scope: 5.1.1 - eslint-utils: 3.0.0_eslint@8.29.0 + eslint-utils: 3.0.0_eslint@8.32.0 semver: 7.3.8 transitivePeerDependencies: - supports-color @@ -1906,6 +2126,129 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /@vitejs/plugin-vue/4.0.0_vite@4.0.4+vue@3.2.45: + resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 + vue: ^3.2.25 + dependencies: + vite: 4.0.4 + vue: 3.2.45 + dev: true + + /@volar/language-core/1.0.24: + resolution: {integrity: sha512-vTN+alJiWwK0Pax6POqrmevbtFW2dXhjwWiW/MW4f48eDYPLdyURWcr8TixO7EN/nHsUBj2udT7igFKPtjyAKg==} + dependencies: + '@volar/source-map': 1.0.24 + muggle-string: 0.1.0 + dev: true + + /@volar/source-map/1.0.24: + resolution: {integrity: sha512-Qsv/tkplx18pgBr8lKAbM1vcDqgkGKQzbChg6NW+v0CZc3G7FLmK+WrqEPzKlN7Cwdc6XVL559Nod8WKAfKr4A==} + dependencies: + muggle-string: 0.1.0 + dev: true + + /@volar/typescript/1.0.24: + resolution: {integrity: sha512-f8hCSk+PfKR1/RQHxZ79V1NpDImHoivqoizK+mstphm25tn/YJ/JnKNjZHB+o21fuW0yKlI26NV3jkVb2Cc/7A==} + dependencies: + '@volar/language-core': 1.0.24 + dev: true + + /@volar/vue-language-core/1.0.24: + resolution: {integrity: sha512-2NTJzSgrwKu6uYwPqLiTMuAzi7fAY3yFy5PJ255bGJc82If0Xr+cW8pC80vpjG0D/aVLmlwAdO4+Ya2BI8GdDg==} + dependencies: + '@volar/language-core': 1.0.24 + '@volar/source-map': 1.0.24 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-sfc': 3.2.45 + '@vue/reactivity': 3.2.45 + '@vue/shared': 3.2.45 + minimatch: 5.1.6 + vue-template-compiler: 2.7.14 + dev: true + + /@volar/vue-typescript/1.0.24: + resolution: {integrity: sha512-9a25oHDvGaNC0okRS47uqJI6FxY4hUQZUsxeOUFHcqVxZEv8s17LPuP/pMMXyz7jPygrZubB/qXqHY5jEu/akA==} + dependencies: + '@volar/typescript': 1.0.24 + '@volar/vue-language-core': 1.0.24 + dev: true + + /@vue/compiler-core/3.2.45: + resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} + dependencies: + '@babel/parser': 7.20.5 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + source-map: 0.6.1 + + /@vue/compiler-dom/3.2.45: + resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} + dependencies: + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 + + /@vue/compiler-sfc/3.2.45: + resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} + dependencies: + '@babel/parser': 7.20.5 + '@vue/compiler-core': 3.2.45 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-ssr': 3.2.45 + '@vue/reactivity-transform': 3.2.45 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + magic-string: 0.25.9 + postcss: 8.4.21 + source-map: 0.6.1 + + /@vue/compiler-ssr/3.2.45: + resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} + dependencies: + '@vue/compiler-dom': 3.2.45 + '@vue/shared': 3.2.45 + + /@vue/reactivity-transform/3.2.45: + resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} + dependencies: + '@babel/parser': 7.20.5 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 + estree-walker: 2.0.2 + magic-string: 0.25.9 + + /@vue/reactivity/3.2.45: + resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} + dependencies: + '@vue/shared': 3.2.45 + + /@vue/runtime-core/3.2.45: + resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} + dependencies: + '@vue/reactivity': 3.2.45 + '@vue/shared': 3.2.45 + + /@vue/runtime-dom/3.2.45: + resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} + dependencies: + '@vue/runtime-core': 3.2.45 + '@vue/shared': 3.2.45 + csstype: 2.6.21 + + /@vue/server-renderer/3.2.45_vue@3.2.45: + resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} + peerDependencies: + vue: 3.2.45 + dependencies: + '@vue/compiler-ssr': 3.2.45 + '@vue/shared': 3.2.45 + vue: 3.2.45 + + /@vue/shared/3.2.45: + resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} + /JSONStream/1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -2282,6 +2625,12 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces/2.3.2: resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==} engines: {node: '>=0.10.0'} @@ -2991,6 +3340,9 @@ packages: cssom: 0.3.8 dev: true + /csstype/2.6.21: + resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} + /csv-generate/3.4.3: resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} dev: true @@ -3104,6 +3456,14 @@ packages: engines: {node: '>=0.11'} dev: true + /dayjs/1.11.7: + resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + dev: false + + /de-indent/1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + dev: true + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -3436,6 +3796,36 @@ packages: is-symbol: 1.0.4 dev: true + /esbuild/0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -3485,13 +3875,13 @@ packages: estraverse: 5.3.0 dev: true - /eslint-utils/3.0.0_eslint@8.29.0: + /eslint-utils/3.0.0_eslint@8.32.0: resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.29.0 + eslint: 8.32.0 eslint-visitor-keys: 2.1.0 dev: true @@ -3505,12 +3895,12 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.29.0: - resolution: {integrity: sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==} + /eslint/8.32.0: + resolution: {integrity: sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 1.3.3 + '@eslint/eslintrc': 1.4.1 '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -3521,7 +3911,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.1.1 - eslint-utils: 3.0.0_eslint@8.29.0 + eslint-utils: 3.0.0_eslint@8.32.0 eslint-visitor-keys: 3.3.0 espree: 9.4.1 esquery: 1.4.0 @@ -3592,6 +3982,9 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + /esutils/2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -4217,6 +4610,11 @@ packages: minimalistic-assert: 1.0.1 dev: true + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + /hmac-drbg/1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: @@ -5619,6 +6017,11 @@ packages: yallist: 4.0.0 dev: true + /magic-string/0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + /make-dir/3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -5800,6 +6203,13 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch/5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist-options/4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -5870,11 +6280,20 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true + /muggle-string/0.1.0: + resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==} + dev: true + /mustache/4.2.0: resolution: {integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==} hasBin: true dev: false + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + /nanomatch/1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -6301,7 +6720,6 @@ packages: /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -6334,6 +6752,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + /preferred-pm/3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} engines: {node: '>=10'} @@ -6697,6 +7123,14 @@ packages: inherits: 2.0.4 dev: true + /rollup/3.10.0: + resolution: {integrity: sha512-JmRYz44NjC1MjVF2VKxc0M1a97vn+cDxeqWmnwyAF4FvpjK8YFdHpaqvQB+3IxCvX05vJxKZkoMDU8TShhmJVA==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + /rsvp/4.8.5: resolution: {integrity: sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==} engines: {node: 6.* || >= 7.*} @@ -6933,6 +7367,10 @@ packages: - supports-color dev: true + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + /source-map-resolve/0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -6964,13 +7402,16 @@ packages: /source-map/0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - dev: true /source-map/0.7.4: resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} engines: {node: '>= 8'} dev: true + /sourcemap-codec/1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + /spawn-command/0.0.2-1: resolution: {integrity: sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==} dev: true @@ -7316,7 +7757,6 @@ packages: /to-fast-properties/2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - dev: true /to-object-path/0.3.0: resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==} @@ -7402,7 +7842,7 @@ packages: lodash: 4.17.21 make-error: 1.3.6 mkdirp: 1.0.4 - semver: 7.3.8 + semver: 7.3.7 yargs-parser: 20.2.9 dev: true @@ -7623,7 +8063,6 @@ packages: resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true - dev: true /u3/0.1.1: resolution: {integrity: sha512-+J5D5ir763y+Am/QY6hXNRlwljIeRMZMGs0cT6qqZVVzzT3X3nFPXVyPOFRMOR4kupB0T8JnCdpWdp6Q/iXn3w==} @@ -7783,10 +8222,70 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /vite/4.0.4: + resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.16.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.10.0 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /vm-browserify/1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} dev: true + /vue-template-compiler/2.7.14: + resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + dev: true + + /vue-tsc/1.0.24_typescript@4.9.4: + resolution: {integrity: sha512-mmU1s5SAqE1nByQAiQnao9oU4vX+mSdsgI8H57SfKH6UVzq/jP9+Dbi2GaV+0b4Cn361d2ln8m6xeU60ApiEXg==} + hasBin: true + peerDependencies: + typescript: '*' + dependencies: + '@volar/vue-language-core': 1.0.24 + '@volar/vue-typescript': 1.0.24 + typescript: 4.9.4 + dev: true + + /vue/3.2.45: + resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==} + dependencies: + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-sfc': 3.2.45 + '@vue/runtime-dom': 3.2.45 + '@vue/server-renderer': 3.2.45_vue@3.2.45 + '@vue/shared': 3.2.45 + /w3c-hr-time/1.0.2: resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} deprecated: Use your platform's native performance.now() and performance.timeOrigin. From d09e4e9da4352bcce179d4cd01a1f7e5ab7b1b8e Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Fri, 3 Feb 2023 17:09:09 -0800 Subject: [PATCH 80/84] feat: update tests --- packages/accounts/package.json | 2 +- .../accounts/test/account.access_key.test.js | 4 ++-- packages/accounts/test/account.test.js | 16 +++++++-------- .../accounts/test/account_multisig.test.js | 18 ++++++++--------- packages/accounts/test/config.js | 4 ++-- packages/accounts/test/contract.test.js | 5 +++-- packages/accounts/test/contract_abi.test.js | 2 +- packages/accounts/test/promise.test.js | 4 ++-- packages/accounts/test/providers.test.js | 8 ++++---- packages/accounts/test/test-utils.js | 10 +++++----- packages/crypto/package.json | 2 +- packages/crypto/test/key_pair.test.js | 6 +++--- packages/keystores-browser/jest.config.js | 2 +- packages/keystores-browser/package.json | 5 +++-- .../test/browser_keystore.test.js | 9 ++++++--- .../keystores-browser/test/keystore_common.js | 4 ++-- packages/keystores-node/jest.config.js | 2 +- packages/keystores-node/package.json | 2 +- .../keystores-node/test/keystore_common.js | 4 ++-- .../unencrypted_file_system_keystore.test.js | 15 ++++++++------ packages/keystores/jest.config.js | 2 +- packages/keystores/package.json | 2 +- .../keystores/test/in_memory_keystore.test.js | 5 +++-- packages/keystores/test/keystore_common.js | 4 ++-- .../keystores/test/merge_keystore.test.js | 7 ++++--- .../test/key_stores/keystore_common.js | 2 +- packages/providers/jest.config.js | 2 +- packages/providers/package.json | 2 +- packages/providers/test/fetch_json.test.js | 2 +- packages/providers/test/providers.test.js | 5 +++-- packages/signers/jest.config.js | 2 +- packages/signers/test/signer.test.js | 4 ++-- packages/transactions/jest.config.js | 2 +- packages/transactions/package.json | 2 +- packages/transactions/test/serialize.test.js | 20 +++++++++---------- .../transactions/test/transaction.test.js | 4 ++-- packages/utils/jest.config.js | 2 +- packages/utils/package.json | 2 +- packages/utils/test/format.test.js | 2 +- packages/utils/test/rpc-errors.test.js | 2 +- packages/utils/test/validator.test.js | 4 ++-- packages/wallet-account/jest.config.js | 2 +- .../test/wallet_account.test.js | 20 +++++++++---------- pnpm-lock.yaml | 2 ++ 44 files changed, 120 insertions(+), 107 deletions(-) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 5f72fb366..5866e9c74 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/accounts/test/account.access_key.test.js b/packages/accounts/test/account.access_key.test.js index 416ee2559..3275173af 100644 --- a/packages/accounts/test/account.access_key.test.js +++ b/packages/accounts/test/account.access_key.test.js @@ -1,6 +1,6 @@ -const { KeyPair } = require('@near-js/crypto'); +import { KeyPair } from '@near-js/crypto'; -const testUtils = require('./test-utils'); +import testUtils from './test-utils.js'; let nearjs; let workingAccount; diff --git a/packages/accounts/test/account.test.js b/packages/accounts/test/account.test.js index 0c2ffc78f..9007f5d7f 100644 --- a/packages/accounts/test/account.test.js +++ b/packages/accounts/test/account.test.js @@ -1,11 +1,11 @@ -const { getTransactionLastResult } = require('@near-js/utils'); -const { actionCreators } = require('@near-js/transactions'); -const { TypedError } = require('@near-js/types'); -const BN = require('bn.js'); -const fs = require('fs'); - -const { Account, Contract } = require('../lib/cjs'); -const testUtils = require('./test-utils'); +import { getTransactionLastResult } from '@near-js/utils'; +import { actionCreators } from '@near-js/transactions'; +import { TypedError } from '@near-js/types'; +import BN from 'bn.js'; +import fs from 'fs'; + +import { Account, Contract } from '../lib/esm'; +import testUtils from './test-utils.js'; let nearjs; let workingAccount; diff --git a/packages/accounts/test/account_multisig.test.js b/packages/accounts/test/account_multisig.test.js index 3c7d7f0ba..6eaeda697 100644 --- a/packages/accounts/test/account_multisig.test.js +++ b/packages/accounts/test/account_multisig.test.js @@ -1,14 +1,14 @@ /* global BigInt */ -const { parseNearAmount } = require('@near-js/utils'); -const { KeyPair } = require('@near-js/crypto'); -const { InMemorySigner } = require('@near-js/signers'); -const { actionCreators } = require('@near-js/transactions'); -const BN = require('bn.js'); -const fs = require('fs'); -const semver = require('semver'); +import { parseNearAmount } from '@near-js/utils'; +import { KeyPair } from '@near-js/crypto'; +import { InMemorySigner } from '@near-js/signers'; +import { actionCreators } from '@near-js/transactions'; +import BN from 'bn.js'; +import fs from 'fs'; +import semver from 'semver'; -const { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } = require('../lib/cjs'); -const testUtils = require('./test-utils'); +import { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS } from '../lib/esm'; +import testUtils from './test-utils.js'; const { functionCall, transfer } = actionCreators; diff --git a/packages/accounts/test/config.js b/packages/accounts/test/config.js index 1734592c6..434f94c15 100644 --- a/packages/accounts/test/config.js +++ b/packages/accounts/test/config.js @@ -1,4 +1,4 @@ -module.exports = function getConfig(env) { +export default function getConfig(env) { switch (env) { case 'production': case 'mainnet': @@ -41,4 +41,4 @@ module.exports = function getConfig(env) { default: throw Error(`Unconfigured environment '${env}'. Can be configured in src/config.js.`); } -}; +} diff --git a/packages/accounts/test/contract.test.js b/packages/accounts/test/contract.test.js index 4a2b2376f..eb49f94f2 100644 --- a/packages/accounts/test/contract.test.js +++ b/packages/accounts/test/contract.test.js @@ -1,6 +1,7 @@ -const { PositionalArgsError } = require('@near-js/types'); +import { jest } from '@jest/globals'; +import { PositionalArgsError } from '@near-js/types'; -const { Contract } = require('../lib/cjs'); +import { Contract } from '../lib/esm'; const account = { viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery}) { diff --git a/packages/accounts/test/contract_abi.test.js b/packages/accounts/test/contract_abi.test.js index 2705ee028..e56378d50 100644 --- a/packages/accounts/test/contract_abi.test.js +++ b/packages/accounts/test/contract_abi.test.js @@ -1,4 +1,4 @@ -const { ArgumentSchemaError, Contract, UnknownArgumentError, UnsupportedSerializationError } = require('../lib/cjs'); +import { ArgumentSchemaError, Contract, UnknownArgumentError, UnsupportedSerializationError } from '../lib/esm'; let rawAbi = `{ "schema_version": "0.3.0", diff --git a/packages/accounts/test/promise.test.js b/packages/accounts/test/promise.test.js index f0cc67cc3..1b69e1c69 100644 --- a/packages/accounts/test/promise.test.js +++ b/packages/accounts/test/promise.test.js @@ -1,6 +1,6 @@ -const BN = require('bn.js'); +import BN from 'bn.js'; -const testUtils = require('./test-utils'); +import testUtils from './test-utils.js'; let nearjs; let workingAccount; diff --git a/packages/accounts/test/providers.test.js b/packages/accounts/test/providers.test.js index 335d04dc2..6cea5f654 100644 --- a/packages/accounts/test/providers.test.js +++ b/packages/accounts/test/providers.test.js @@ -1,8 +1,8 @@ -const { JsonRpcProvider } = require('@near-js/providers'); -const BN = require('bn.js'); -const base58 = require('bs58'); +import { JsonRpcProvider } from '@near-js/providers'; +import BN from 'bn.js'; +import base58 from 'bs58'; -const testUtils = require('./test-utils'); +import testUtils from './test-utils.js'; jest.setTimeout(20000); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index dddc74d0f..68cc0a643 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -1,9 +1,9 @@ -const { KeyPair } = require('@near-js/crypto'); -const { InMemoryKeyStore } = require('@near-js/keystores'); -const BN = require('bn.js'); -const fs = require('fs').promises; +import { KeyPair } from '@near-js/crypto'; +import { InMemoryKeyStore } from '@near-js/keystores'; +import BN from 'bn.js'; +import { promises as fs } from 'fs'; -const { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } = require('../lib/cjs'); +import { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } from '../lib/esm'; const networkId = 'unittest'; diff --git a/packages/crypto/package.json b/packages/crypto/package.json index fe60d41a0..fc1c2fde2 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/crypto/test/key_pair.test.js b/packages/crypto/test/key_pair.test.js index b9796559b..8aa7b611b 100644 --- a/packages/crypto/test/key_pair.test.js +++ b/packages/crypto/test/key_pair.test.js @@ -1,7 +1,7 @@ -const { baseEncode } = require('borsh'); -const { sha256 } = require('js-sha256'); +import { baseEncode } from 'borsh'; +import { sha256 } from 'js-sha256'; -const { KeyPair, KeyPairEd25519, PublicKey } = require('../lib'); +import { KeyPair, KeyPairEd25519, PublicKey } from '../lib/esm'; test('test sign and verify', async () => { const keyPair = new KeyPairEd25519('26x56YPzPDro5t2smQfGcYAPy3j7R2jB2NUb7xKbAGK23B6x4WNQPh3twb6oDksFov5X8ts5CtntUNbpQpAKFdbR'); diff --git a/packages/keystores-browser/jest.config.js b/packages/keystores-browser/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/keystores-browser/jest.config.js +++ b/packages/keystores-browser/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index edc3a7f57..3362a8b13 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", @@ -23,6 +23,7 @@ }, "devDependencies": { "jest": "^26.0.1", + "localstorage-memory": "^1.0.3", "ts-jest": "^26.5.6", "tsconfig": "workspace:*", "typescript": "^4.9.4" @@ -32,6 +33,6 @@ "require": "./lib/cjs/index.js" }, "files": [ - "lib" + "lib" ] } diff --git a/packages/keystores-browser/test/browser_keystore.test.js b/packages/keystores-browser/test/browser_keystore.test.js index cb71965ca..9d6609a9e 100644 --- a/packages/keystores-browser/test/browser_keystore.test.js +++ b/packages/keystores-browser/test/browser_keystore.test.js @@ -1,11 +1,14 @@ -const { BrowserLocalStorageKeyStore } = require('../lib'); +import localStorageMemory from 'localstorage-memory'; + +import { BrowserLocalStorageKeyStore } from '../lib/esm'; +import { shouldStoreAndRetrieveKeys } from './keystore_common.js'; describe('Browser keystore', () => { let ctx = {}; beforeAll(async () => { - ctx.keyStore = new BrowserLocalStorageKeyStore(require('localstorage-memory')); + ctx.keyStore = new BrowserLocalStorageKeyStore(localStorageMemory); }); - require('./keystore_common').shouldStoreAndRetriveKeys(ctx); + shouldStoreAndRetrieveKeys(ctx); }); diff --git a/packages/keystores-browser/test/keystore_common.js b/packages/keystores-browser/test/keystore_common.js index 40fd26361..ded613074 100644 --- a/packages/keystores-browser/test/keystore_common.js +++ b/packages/keystores-browser/test/keystore_common.js @@ -1,10 +1,10 @@ -const { KeyPairEd25519 } = require('@near-js/crypto'); +import { KeyPairEd25519 } from '@near-js/crypto'; const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); -module.exports.shouldStoreAndRetriveKeys = ctx => { +export const shouldStoreAndRetrieveKeys = ctx => { beforeEach(async () => { await ctx.keyStore.clear(); await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); diff --git a/packages/keystores-node/jest.config.js b/packages/keystores-node/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/keystores-node/jest.config.js +++ b/packages/keystores-node/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 3754666ce..f030f063e 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/keystores-node/test/keystore_common.js b/packages/keystores-node/test/keystore_common.js index 40fd26361..ded613074 100644 --- a/packages/keystores-node/test/keystore_common.js +++ b/packages/keystores-node/test/keystore_common.js @@ -1,10 +1,10 @@ -const { KeyPairEd25519 } = require('@near-js/crypto'); +import { KeyPairEd25519 } from '@near-js/crypto'; const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); -module.exports.shouldStoreAndRetriveKeys = ctx => { +export const shouldStoreAndRetrieveKeys = ctx => { beforeEach(async () => { await ctx.keyStore.clear(); await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); diff --git a/packages/keystores-node/test/unencrypted_file_system_keystore.test.js b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js index d4845b839..166a265d3 100644 --- a/packages/keystores-node/test/unencrypted_file_system_keystore.test.js +++ b/packages/keystores-node/test/unencrypted_file_system_keystore.test.js @@ -1,10 +1,13 @@ -const { KeyPairEd25519 } = require('@near-js/crypto'); -const fs = require('fs').promises; -const path = require('path'); -const rimraf = require('util').promisify(require('rimraf')); +import { KeyPairEd25519 } from '@near-js/crypto'; +import { promises as fs } from 'fs'; +import path from 'path'; +import rimrafPkg from 'rimraf'; +import { promisify } from 'util'; -const { UnencryptedFileSystemKeyStore } = require('../lib'); +import { UnencryptedFileSystemKeyStore } from '../lib/esm'; +import { shouldStoreAndRetrieveKeys } from './keystore_common.js'; +const rimraf = promisify(rimrafPkg); const KEYSTORE_PATH = '../../test-keys'; describe('Unencrypted file system keystore', () => { @@ -20,7 +23,7 @@ describe('Unencrypted file system keystore', () => { ctx.keyStore = new UnencryptedFileSystemKeyStore(KEYSTORE_PATH); }); - require('./keystore_common').shouldStoreAndRetriveKeys(ctx); + shouldStoreAndRetrieveKeys(ctx); it('test path resolve', async() => { expect(ctx.keyStore.keyDir).toEqual(path.join(process.cwd(), KEYSTORE_PATH)); diff --git a/packages/keystores/jest.config.js b/packages/keystores/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/keystores/jest.config.js +++ b/packages/keystores/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/keystores/package.json b/packages/keystores/package.json index 0c12610cf..818e468cc 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/keystores/test/in_memory_keystore.test.js b/packages/keystores/test/in_memory_keystore.test.js index 52a0eda48..d1aea1e61 100644 --- a/packages/keystores/test/in_memory_keystore.test.js +++ b/packages/keystores/test/in_memory_keystore.test.js @@ -1,4 +1,5 @@ -const { InMemoryKeyStore } = require('../lib'); +import { InMemoryKeyStore } from '../lib/esm'; +import { shouldStoreAndRetrieveKeys } from './keystore_common.js'; describe('In-memory keystore', () => { let ctx = {}; @@ -7,5 +8,5 @@ describe('In-memory keystore', () => { ctx.keyStore = new InMemoryKeyStore(); }); - require('./keystore_common').shouldStoreAndRetriveKeys(ctx); + shouldStoreAndRetrieveKeys(ctx); }); diff --git a/packages/keystores/test/keystore_common.js b/packages/keystores/test/keystore_common.js index 40fd26361..ded613074 100644 --- a/packages/keystores/test/keystore_common.js +++ b/packages/keystores/test/keystore_common.js @@ -1,10 +1,10 @@ -const { KeyPairEd25519 } = require('@near-js/crypto'); +import { KeyPairEd25519 } from '@near-js/crypto'; const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; const KEYPAIR_SINGLE_KEY = new KeyPairEd25519('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); -module.exports.shouldStoreAndRetriveKeys = ctx => { +export const shouldStoreAndRetrieveKeys = ctx => { beforeEach(async () => { await ctx.keyStore.clear(); await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); diff --git a/packages/keystores/test/merge_keystore.test.js b/packages/keystores/test/merge_keystore.test.js index 76ee7ac2e..9c79f7a4b 100644 --- a/packages/keystores/test/merge_keystore.test.js +++ b/packages/keystores/test/merge_keystore.test.js @@ -1,6 +1,7 @@ -const { KeyPairEd25519 } = require('@near-js/crypto'); +import { KeyPairEd25519 } from '@near-js/crypto'; -const { InMemoryKeyStore, MergeKeyStore } = require('../lib'); +import { InMemoryKeyStore, MergeKeyStore } from '../lib/esm'; +import { shouldStoreAndRetrieveKeys } from './keystore_common.js'; describe('Merge keystore', () => { let ctx = {}; @@ -31,5 +32,5 @@ describe('Merge keystore', () => { expect(await ctx.stores[1].getAccounts('network')).toHaveLength(0); }); - require('./keystore_common').shouldStoreAndRetriveKeys(ctx); + shouldStoreAndRetrieveKeys(ctx); }); diff --git a/packages/near-api-js/test/key_stores/keystore_common.js b/packages/near-api-js/test/key_stores/keystore_common.js index 7008b950a..4fbd14a93 100644 --- a/packages/near-api-js/test/key_stores/keystore_common.js +++ b/packages/near-api-js/test/key_stores/keystore_common.js @@ -7,7 +7,7 @@ const NETWORK_ID_SINGLE_KEY = 'singlekeynetworkid'; const ACCOUNT_ID_SINGLE_KEY = 'singlekey_accountid'; const KEYPAIR_SINGLE_KEY = new KeyPair('2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); -module.exports.shouldStoreAndRetriveKeys = ctx => { +module.exports.shouldStoreAndRetrieveKeys = ctx => { beforeEach(async () => { await ctx.keyStore.clear(); await ctx.keyStore.setKey(NETWORK_ID_SINGLE_KEY, ACCOUNT_ID_SINGLE_KEY, KEYPAIR_SINGLE_KEY); diff --git a/packages/providers/jest.config.js b/packages/providers/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/providers/jest.config.js +++ b/packages/providers/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/providers/package.json b/packages/providers/package.json index 878d9c7a9..34d50e287 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/providers/test/fetch_json.test.js b/packages/providers/test/fetch_json.test.js index 6711d53e2..48d84720d 100644 --- a/packages/providers/test/fetch_json.test.js +++ b/packages/providers/test/fetch_json.test.js @@ -1,4 +1,4 @@ -const { fetchJson } = require('../lib'); +import { fetchJson } from '../lib/esm'; describe('fetchJson', () => { test('string parameter in fetchJson', async () => { diff --git a/packages/providers/test/providers.test.js b/packages/providers/test/providers.test.js index ea824f697..e63f61390 100644 --- a/packages/providers/test/providers.test.js +++ b/packages/providers/test/providers.test.js @@ -1,6 +1,7 @@ -const { getTransactionLastResult } = require('@near-js/utils'); +import { jest } from '@jest/globals'; +import { getTransactionLastResult } from '@near-js/utils'; -const { JsonRpcProvider } = require('../lib'); +import { JsonRpcProvider } from '../lib/esm'; jest.setTimeout(20000); diff --git a/packages/signers/jest.config.js b/packages/signers/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/signers/jest.config.js +++ b/packages/signers/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/signers/test/signer.test.js b/packages/signers/test/signer.test.js index f037b8889..e84abfe54 100644 --- a/packages/signers/test/signer.test.js +++ b/packages/signers/test/signer.test.js @@ -1,6 +1,6 @@ -const { InMemoryKeyStore } = require('@near-js/keystores'); +import { InMemoryKeyStore } from '@near-js/keystores'; -const { InMemorySigner } = require('../lib'); +import { InMemorySigner } from '../lib/esm'; test('test no key', async() => { const signer = new InMemorySigner(new InMemoryKeyStore()); diff --git a/packages/transactions/jest.config.js b/packages/transactions/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/transactions/jest.config.js +++ b/packages/transactions/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/transactions/package.json b/packages/transactions/package.json index a54b28768..5e858c67f 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -12,7 +12,7 @@ "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/transactions/test/serialize.test.js b/packages/transactions/test/serialize.test.js index 67d32c7f5..7c2ed1dea 100644 --- a/packages/transactions/test/serialize.test.js +++ b/packages/transactions/test/serialize.test.js @@ -1,19 +1,19 @@ -const { KeyPair, PublicKey } = require('@near-js/crypto'); -const { InMemoryKeyStore } = require('@near-js/keystores'); -const { InMemorySigner } = require('@near-js/signers'); -const { Assignable } = require('@near-js/types'); -const fs = require('fs'); -const BN = require('bn.js'); -const { baseDecode, baseEncode, deserialize, serialize } = require('borsh'); - -const { +import { KeyPair, PublicKey } from '@near-js/crypto'; +import { InMemoryKeyStore } from '@near-js/keystores'; +import { InMemorySigner } from '@near-js/signers'; +import { Assignable } from '@near-js/types'; +import fs from 'fs'; +import BN from 'bn.js'; +import { baseDecode, baseEncode, deserialize, serialize } from 'borsh'; + +import { actionCreators, createTransaction, SCHEMA, signTransaction, SignedTransaction, Transaction, -} = require('../lib'); +} from '../lib/esm'; const { addKey, diff --git a/packages/transactions/test/transaction.test.js b/packages/transactions/test/transaction.test.js index 29bda2319..23896ebc5 100644 --- a/packages/transactions/test/transaction.test.js +++ b/packages/transactions/test/transaction.test.js @@ -1,6 +1,6 @@ -const BN = require('bn.js'); +import BN from 'bn.js'; -const { actionCreators } = require('../lib'); +import { actionCreators } from '../lib/esm'; const { functionCall } = actionCreators; diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/utils/jest.config.js +++ b/packages/utils/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/utils/package.json b/packages/utils/package.json index f2d85c4d7..cb180a574 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -10,7 +10,7 @@ "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc && eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix --no-error-on-unmatched-pattern", - "test": "jest test" + "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], "author": "", diff --git a/packages/utils/test/format.test.js b/packages/utils/test/format.test.js index fcd642d5b..ee5a5231b 100644 --- a/packages/utils/test/format.test.js +++ b/packages/utils/test/format.test.js @@ -1,4 +1,4 @@ -const { formatNearAmount, parseNearAmount } = require('../lib'); +import { formatNearAmount, parseNearAmount } from '../lib/esm'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 50000; diff --git a/packages/utils/test/rpc-errors.test.js b/packages/utils/test/rpc-errors.test.js index 203932452..579e7c77c 100644 --- a/packages/utils/test/rpc-errors.test.js +++ b/packages/utils/test/rpc-errors.test.js @@ -1,4 +1,4 @@ -const { formatError, getErrorTypeFromErrorMessage, parseRpcError, ServerError } = require('../lib'); +import { formatError, getErrorTypeFromErrorMessage, parseRpcError, ServerError } from '../lib/esm'; describe('rpc-errors', () => { test('test AccountAlreadyExists error', async () => { diff --git a/packages/utils/test/validator.test.js b/packages/utils/test/validator.test.js index 6bb95cec4..a955735bb 100644 --- a/packages/utils/test/validator.test.js +++ b/packages/utils/test/validator.test.js @@ -1,6 +1,6 @@ -const BN = require('bn.js'); +import BN from 'bn.js'; -const { diffEpochValidators, findSeatPrice } = require('../lib'); +import { diffEpochValidators, findSeatPrice } from '../lib/esm'; test('find seat price', async () => { expect(findSeatPrice( diff --git a/packages/wallet-account/jest.config.js b/packages/wallet-account/jest.config.js index 749b7fcb2..4b2c7ee3d 100644 --- a/packages/wallet-account/jest.config.js +++ b/packages/wallet-account/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { preset: 'ts-jest', testEnvironment: 'node', collectCoverage: true diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 8500b5265..46c678559 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -1,13 +1,13 @@ -const { KeyPair, PublicKey } = require('@near-js/crypto'); -const { InMemoryKeyStore } = require('@near-js/keystores'); -const { InMemorySigner } = require('@near-js/signers'); -const { actionCreators, createTransaction, SCHEMA, Transaction } = require('@near-js/transactions'); -const BN = require('bn.js'); -const { baseDecode, deserialize } = require('borsh'); -const localStorage = require('localstorage-memory'); -const url = require('url'); - -const { WalletConnection } = require('../lib/wallet_account'); +import { KeyPair, PublicKey } from '@near-js/crypto'; +import { InMemoryKeyStore } from '@near-js/keystores'; +import { InMemorySigner } from '@near-js/signers'; +import { actionCreators, createTransaction, SCHEMA, Transaction } from '@near-js/transactions'; +import BN from 'bn.js'; +import { baseDecode, deserialize } from 'borsh'; +import localStorage from 'localstorage-memory'; +import url from 'url'; + +import { WalletConnection } from '../lib/wallet_account'; const { functionCall, transfer } = actionCreators; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67a2e2399..33a4bb08e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -159,6 +159,7 @@ importers: '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* jest: ^26.0.1 + localstorage-memory: ^1.0.3 ts-jest: ^26.5.6 tsconfig: workspace:* typescript: ^4.9.4 @@ -167,6 +168,7 @@ importers: '@near-js/keystores': link:../keystores devDependencies: jest: 26.6.3 + localstorage-memory: 1.0.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli tsconfig: link:../tsconfig typescript: 4.9.4 From f1b5051d3557778cc2e0a5c9de3fca0c36b53f1a Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Mon, 6 Feb 2023 14:58:32 -0800 Subject: [PATCH 81/84] feat: add missing dev dependency --- packages/keystores-browser/package.json | 1 + pnpm-lock.yaml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/packages/keystores-browser/package.json b/packages/keystores-browser/package.json index 3362a8b13..40df10721 100644 --- a/packages/keystores-browser/package.json +++ b/packages/keystores-browser/package.json @@ -22,6 +22,7 @@ "@near-js/keystores": "workspace:*" }, "devDependencies": { + "@types/node": "^18.11.18", "jest": "^26.0.1", "localstorage-memory": "^1.0.3", "ts-jest": "^26.5.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 33a4bb08e..882c3766d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -158,6 +158,7 @@ importers: specifiers: '@near-js/crypto': workspace:* '@near-js/keystores': workspace:* + '@types/node': ^18.11.18 jest: ^26.0.1 localstorage-memory: ^1.0.3 ts-jest: ^26.5.6 @@ -167,6 +168,7 @@ importers: '@near-js/crypto': link:../crypto '@near-js/keystores': link:../keystores devDependencies: + '@types/node': 18.11.18 jest: 26.6.3 localstorage-memory: 1.0.3 ts-jest: 26.5.6_vxa7amr3o4p5wmsiameezakoli From a4b34884ea007a296e15b81246ad1550bc56c533 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Thu, 2 Mar 2023 11:35:29 -0800 Subject: [PATCH 82/84] fix: example rpc URLs --- packages/example-esm/index.js | 2 +- packages/example-vite/src/components/HelloWorld.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/example-esm/index.js b/packages/example-esm/index.js index 3432a5054..ee68c0062 100644 --- a/packages/example-esm/index.js +++ b/packages/example-esm/index.js @@ -2,7 +2,7 @@ import { Account, Connection } from '@near-js/accounts'; const account = new Account(Connection.fromConfig({ networkId: 'mainnet', - provider: { type: 'JsonRpcProvider', args: { url: 'https://testnet.rpc.near.org' } }, + provider: { type: 'JsonRpcProvider', args: { url: 'https://rpc.testnet.near.org' } }, signer: { type: 'InMemorySigner', keyStore: {} }, }), 'gornt.testnet'); diff --git a/packages/example-vite/src/components/HelloWorld.vue b/packages/example-vite/src/components/HelloWorld.vue index f191dba83..dccc69c4a 100644 --- a/packages/example-vite/src/components/HelloWorld.vue +++ b/packages/example-vite/src/components/HelloWorld.vue @@ -5,7 +5,7 @@ import { Account, Connection } from '@near-js/accounts' const account = new Account(Connection.fromConfig({ networkId: 'testnet', - provider: { type: 'JsonRpcProvider', args: { url: 'https://testnet.rpc.near.org' } }, + provider: { type: 'JsonRpcProvider', args: { url: 'https://rpc.testnet.near.org' } }, signer: { type: 'InMemorySigner', keyStore: {} }, }), 'gornt.testnet'); From 58064e9575cd00e9bdbe02241207bff8428e4224 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 7 Mar 2023 14:02:19 -0800 Subject: [PATCH 83/84] fix: import workaround JSON --- packages/types/package.json | 4 +- packages/utils/package.json | 4 +- packages/utils/src/errors/error_messages.json | 66 -- packages/utils/src/errors/error_messages.ts | 66 ++ .../utils/src/errors/rpc_error_schema.json | 869 ------------------ packages/utils/src/errors/rpc_error_schema.ts | 869 ++++++++++++++++++ packages/utils/src/errors/rpc_errors.ts | 5 +- 7 files changed, 943 insertions(+), 940 deletions(-) delete mode 100644 packages/utils/src/errors/error_messages.json create mode 100644 packages/utils/src/errors/error_messages.ts delete mode 100644 packages/utils/src/errors/rpc_error_schema.json create mode 100644 packages/utils/src/errors/rpc_error_schema.ts diff --git a/packages/types/package.json b/packages/types/package.json index 2f77f9a30..8fe722d7b 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -8,8 +8,8 @@ "scripts": { "build": "pnpm compile", "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", - "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts", - "lint:fix": "eslint **/*.ts" + "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix" }, "keywords": [], "author": "", diff --git a/packages/utils/package.json b/packages/utils/package.json index cb180a574..b0e515a68 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -8,8 +8,10 @@ "scripts": { "build": "pnpm compile", "compile": "concurrently \"tsc -p tsconfig.cjs.json\" \"tsc -p tsconfig.esm.json\"", + "lint:js": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc", + "lint:js:fix": "eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix", "lint:ts": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc", - "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc && eslint -c ../../.eslintrc.js.yml test/**/*.js --no-eslintrc --fix --no-error-on-unmatched-pattern", + "lint:ts:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts --no-eslintrc --fix", "test": "NODE_OPTIONS=--experimental-vm-modules jest test" }, "keywords": [], diff --git a/packages/utils/src/errors/error_messages.json b/packages/utils/src/errors/error_messages.json deleted file mode 100644 index 085313959..000000000 --- a/packages/utils/src/errors/error_messages.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "GasLimitExceeded": "Exceeded the maximum amount of gas allowed to burn per contract", - "MethodEmptyName": "Method name is empty", - "WasmerCompileError": "Wasmer compilation error: {{msg}}", - "GuestPanic": "Smart contract panicked: {{panic_msg}}", - "Memory": "Error creating Wasm memory", - "GasExceeded": "Exceeded the prepaid gas", - "MethodUTF8Error": "Method name is not valid UTF8 string", - "BadUTF16": "String encoding is bad UTF-16 sequence", - "WasmTrap": "WebAssembly trap: {{msg}}", - "GasInstrumentation": "Gas instrumentation failed or contract has denied instructions.", - "InvalidPromiseIndex": "{{promise_idx}} does not correspond to existing promises", - "InvalidPromiseResultIndex": "Accessed invalid promise result index: {{result_idx}}", - "Deserialization": "Error happened while deserializing the module", - "MethodNotFound": "Contract method is not found", - "InvalidRegisterId": "Accessed invalid register id: {{register_id}}", - "InvalidReceiptIndex": "VM Logic returned an invalid receipt index: {{receipt_index}}", - "EmptyMethodName": "Method name is empty in contract call", - "CannotReturnJointPromise": "Returning joint promise is currently prohibited", - "StackHeightInstrumentation": "Stack instrumentation failed", - "CodeDoesNotExist": "Cannot find contract code for account {{account_id}}", - "MethodInvalidSignature": "Invalid method signature", - "IntegerOverflow": "Integer overflow happened during contract execution", - "MemoryAccessViolation": "MemoryAccessViolation", - "InvalidIteratorIndex": "Iterator index {{iterator_index}} does not exist", - "IteratorWasInvalidated": "Iterator {{iterator_index}} was invalidated after its creation by performing a mutable operation on trie", - "InvalidAccountId": "VM Logic returned an invalid account id", - "Serialization": "Error happened while serializing the module", - "CannotAppendActionToJointPromise": "Actions can only be appended to non-joint promise.", - "InternalMemoryDeclared": "Internal memory declaration has been found in the module", - "Instantiate": "Error happened during instantiation", - "ProhibitedInView": "{{method_name}} is not allowed in view calls", - "InvalidMethodName": "VM Logic returned an invalid method name", - "BadUTF8": "String encoding is bad UTF-8 sequence", - "BalanceExceeded": "Exceeded the account balance", - "LinkError": "Wasm contract link error: {{msg}}", - "InvalidPublicKey": "VM Logic provided an invalid public key", - "ActorNoPermission": "Actor {{actor_id}} doesn't have permission to account {{account_id}} to complete the action", - "LackBalanceForState": "The account {{account_id}} wouldn't have enough balance to cover storage, required to have {{amount}} yoctoNEAR more", - "ReceiverMismatch": "Wrong AccessKey used for transaction: transaction is sent to receiver_id={{tx_receiver}}, but is signed with function call access key that restricted to only use with receiver_id={{ak_receiver}}. Either change receiver_id in your transaction or switch to use a FullAccessKey.", - "CostOverflow": "Transaction gas or balance cost is too high", - "InvalidSignature": "Transaction is not signed with the given public key", - "AccessKeyNotFound": "Signer \"{{account_id}}\" doesn't have access key with the given public_key {{public_key}}", - "NotEnoughBalance": "Sender {{signer_id}} does not have enough balance {{#formatNear}}{{balance}}{{/formatNear}} for operation costing {{#formatNear}}{{cost}}{{/formatNear}}", - "NotEnoughAllowance": "Access Key {account_id}:{public_key} does not have enough balance {{#formatNear}}{{allowance}}{{/formatNear}} for transaction costing {{#formatNear}}{{cost}}{{/formatNear}}", - "Expired": "Transaction has expired", - "DeleteAccountStaking": "Account {{account_id}} is staking and can not be deleted", - "SignerDoesNotExist": "Signer {{signer_id}} does not exist", - "TriesToStake": "Account {{account_id}} tried to stake {{#formatNear}}{{stake}}{{/formatNear}}, but has staked {{#formatNear}}{{locked}}{{/formatNear}} and only has {{#formatNear}}{{balance}}{{/formatNear}}", - "AddKeyAlreadyExists": "The public key {{public_key}} is already used for an existing access key", - "InvalidSigner": "Invalid signer account ID {{signer_id}} according to requirements", - "CreateAccountNotAllowed": "The new account_id {{account_id}} can't be created by {{predecessor_id}}", - "RequiresFullAccess": "The transaction contains more then one action, but it was signed with an access key which allows transaction to apply only one specific action. To apply more then one actions TX must be signed with a full access key", - "TriesToUnstake": "Account {{account_id}} is not yet staked, but tried to unstake", - "InvalidNonce": "Transaction nonce {{tx_nonce}} must be larger than nonce of the used access key {{ak_nonce}}", - "AccountAlreadyExists": "Can't create a new account {{account_id}}, because it already exists", - "InvalidChain": "Transaction parent block hash doesn't belong to the current chain", - "AccountDoesNotExist": "Can't complete the action because account {{account_id}} doesn't exist", - "MethodNameMismatch": "Transaction method name {{method_name}} isn't allowed by the access key", - "DeleteAccountHasRent": "Account {{account_id}} can't be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover the rent", - "DeleteAccountHasEnoughBalance": "Account {{account_id}} can't be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover it's storage", - "InvalidReceiver": "Invalid receiver account ID {{receiver_id}} according to requirements", - "DeleteKeyDoesNotExist": "Account {{account_id}} tries to remove an access key that doesn't exist", - "Timeout": "Timeout exceeded", - "Closed": "Connection closed" -} diff --git a/packages/utils/src/errors/error_messages.ts b/packages/utils/src/errors/error_messages.ts new file mode 100644 index 000000000..b341eb4ec --- /dev/null +++ b/packages/utils/src/errors/error_messages.ts @@ -0,0 +1,66 @@ +export default { + 'GasLimitExceeded': 'Exceeded the maximum amount of gas allowed to burn per contract', + 'MethodEmptyName': 'Method name is empty', + 'WasmerCompileError': 'Wasmer compilation error: {{msg}}', + 'GuestPanic': 'Smart contract panicked: {{panic_msg}}', + 'Memory': 'Error creating Wasm memory', + 'GasExceeded': 'Exceeded the prepaid gas', + 'MethodUTF8Error': 'Method name is not valid UTF8 string', + 'BadUTF16': 'String encoding is bad UTF-16 sequence', + 'WasmTrap': 'WebAssembly trap: {{msg}}', + 'GasInstrumentation': 'Gas instrumentation failed or contract has denied instructions.', + 'InvalidPromiseIndex': '{{promise_idx}} does not correspond to existing promises', + 'InvalidPromiseResultIndex': 'Accessed invalid promise result index: {{result_idx}}', + 'Deserialization': 'Error happened while deserializing the module', + 'MethodNotFound': 'Contract method is not found', + 'InvalidRegisterId': 'Accessed invalid register id: {{register_id}}', + 'InvalidReceiptIndex': 'VM Logic returned an invalid receipt index: {{receipt_index}}', + 'EmptyMethodName': 'Method name is empty in contract call', + 'CannotReturnJointPromise': 'Returning joint promise is currently prohibited', + 'StackHeightInstrumentation': 'Stack instrumentation failed', + 'CodeDoesNotExist': 'Cannot find contract code for account {{account_id}}', + 'MethodInvalidSignature': 'Invalid method signature', + 'IntegerOverflow': 'Integer overflow happened during contract execution', + 'MemoryAccessViolation': 'MemoryAccessViolation', + 'InvalidIteratorIndex': 'Iterator index {{iterator_index}} does not exist', + 'IteratorWasInvalidated': 'Iterator {{iterator_index}} was invalidated after its creation by performing a mutable operation on trie', + 'InvalidAccountId': 'VM Logic returned an invalid account id', + 'Serialization': 'Error happened while serializing the module', + 'CannotAppendActionToJointPromise': 'Actions can only be appended to non-joint promise.', + 'InternalMemoryDeclared': 'Internal memory declaration has been found in the module', + 'Instantiate': 'Error happened during instantiation', + 'ProhibitedInView': '{{method_name}} is not allowed in view calls', + 'InvalidMethodName': 'VM Logic returned an invalid method name', + 'BadUTF8': 'String encoding is bad UTF-8 sequence', + 'BalanceExceeded': 'Exceeded the account balance', + 'LinkError': 'Wasm contract link error: {{msg}}', + 'InvalidPublicKey': 'VM Logic provided an invalid public key', + 'ActorNoPermission': 'Actor {{actor_id}} doesn\'t have permission to account {{account_id}} to complete the action', + 'LackBalanceForState': 'The account {{account_id}} wouldn\'t have enough balance to cover storage, required to have {{amount}} yoctoNEAR more', + 'ReceiverMismatch': 'Wrong AccessKey used for transaction: transaction is sent to receiver_id={{tx_receiver}}, but is signed with function call access key that restricted to only use with receiver_id={{ak_receiver}}. Either change receiver_id in your transaction or switch to use a FullAccessKey.', + 'CostOverflow': 'Transaction gas or balance cost is too high', + 'InvalidSignature': 'Transaction is not signed with the given public key', + 'AccessKeyNotFound': 'Signer "{{account_id}}" doesn\'t have access key with the given public_key {{public_key}}', + 'NotEnoughBalance': 'Sender {{signer_id}} does not have enough balance {{#formatNear}}{{balance}}{{/formatNear}} for operation costing {{#formatNear}}{{cost}}{{/formatNear}}', + 'NotEnoughAllowance': 'Access Key {account_id}:{public_key} does not have enough balance {{#formatNear}}{{allowance}}{{/formatNear}} for transaction costing {{#formatNear}}{{cost}}{{/formatNear}}', + 'Expired': 'Transaction has expired', + 'DeleteAccountStaking': 'Account {{account_id}} is staking and can not be deleted', + 'SignerDoesNotExist': 'Signer {{signer_id}} does not exist', + 'TriesToStake': 'Account {{account_id}} tried to stake {{#formatNear}}{{stake}}{{/formatNear}}, but has staked {{#formatNear}}{{locked}}{{/formatNear}} and only has {{#formatNear}}{{balance}}{{/formatNear}}', + 'AddKeyAlreadyExists': 'The public key {{public_key}} is already used for an existing access key', + 'InvalidSigner': 'Invalid signer account ID {{signer_id}} according to requirements', + 'CreateAccountNotAllowed': 'The new account_id {{account_id}} can\'t be created by {{predecessor_id}}', + 'RequiresFullAccess': 'The transaction contains more then one action, but it was signed with an access key which allows transaction to apply only one specific action. To apply more then one actions TX must be signed with a full access key', + 'TriesToUnstake': 'Account {{account_id}} is not yet staked, but tried to unstake', + 'InvalidNonce': 'Transaction nonce {{tx_nonce}} must be larger than nonce of the used access key {{ak_nonce}}', + 'AccountAlreadyExists': 'Can\'t create a new account {{account_id}}, because it already exists', + 'InvalidChain': 'Transaction parent block hash doesn\'t belong to the current chain', + 'AccountDoesNotExist': 'Can\'t complete the action because account {{account_id}} doesn\'t exist', + 'MethodNameMismatch': 'Transaction method name {{method_name}} isn\'t allowed by the access key', + 'DeleteAccountHasRent': 'Account {{account_id}} can\'t be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover the rent', + 'DeleteAccountHasEnoughBalance': 'Account {{account_id}} can\'t be deleted. It has {{#formatNear}}{{balance}}{{/formatNear}}, which is enough to cover it\'s storage', + 'InvalidReceiver': 'Invalid receiver account ID {{receiver_id}} according to requirements', + 'DeleteKeyDoesNotExist': 'Account {{account_id}} tries to remove an access key that doesn\'t exist', + 'Timeout': 'Timeout exceeded', + 'Closed': 'Connection closed' +}; diff --git a/packages/utils/src/errors/rpc_error_schema.json b/packages/utils/src/errors/rpc_error_schema.json deleted file mode 100644 index e5ac6cec6..000000000 --- a/packages/utils/src/errors/rpc_error_schema.json +++ /dev/null @@ -1,869 +0,0 @@ -{ - "schema": { - "BadUTF16": { - "name": "BadUTF16", - "subtypes": [], - "props": {} - }, - "BadUTF8": { - "name": "BadUTF8", - "subtypes": [], - "props": {} - }, - "BalanceExceeded": { - "name": "BalanceExceeded", - "subtypes": [], - "props": {} - }, - "BreakpointTrap": { - "name": "BreakpointTrap", - "subtypes": [], - "props": {} - }, - "CacheError": { - "name": "CacheError", - "subtypes": [ - "ReadError", - "WriteError", - "DeserializationError", - "SerializationError" - ], - "props": {} - }, - "CallIndirectOOB": { - "name": "CallIndirectOOB", - "subtypes": [], - "props": {} - }, - "CannotAppendActionToJointPromise": { - "name": "CannotAppendActionToJointPromise", - "subtypes": [], - "props": {} - }, - "CannotReturnJointPromise": { - "name": "CannotReturnJointPromise", - "subtypes": [], - "props": {} - }, - "CodeDoesNotExist": { - "name": "CodeDoesNotExist", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "CompilationError": { - "name": "CompilationError", - "subtypes": [ - "CodeDoesNotExist", - "PrepareError", - "WasmerCompileError" - ], - "props": {} - }, - "ContractSizeExceeded": { - "name": "ContractSizeExceeded", - "subtypes": [], - "props": { - "limit": "", - "size": "" - } - }, - "Deprecated": { - "name": "Deprecated", - "subtypes": [], - "props": { - "method_name": "" - } - }, - "Deserialization": { - "name": "Deserialization", - "subtypes": [], - "props": {} - }, - "DeserializationError": { - "name": "DeserializationError", - "subtypes": [], - "props": {} - }, - "EmptyMethodName": { - "name": "EmptyMethodName", - "subtypes": [], - "props": {} - }, - "FunctionCallError": { - "name": "FunctionCallError", - "subtypes": [ - "CompilationError", - "LinkError", - "MethodResolveError", - "WasmTrap", - "WasmUnknownError", - "HostError", - "EvmError" - ], - "props": {} - }, - "GasExceeded": { - "name": "GasExceeded", - "subtypes": [], - "props": {} - }, - "GasInstrumentation": { - "name": "GasInstrumentation", - "subtypes": [], - "props": {} - }, - "GasLimitExceeded": { - "name": "GasLimitExceeded", - "subtypes": [], - "props": {} - }, - "GenericTrap": { - "name": "GenericTrap", - "subtypes": [], - "props": {} - }, - "GuestPanic": { - "name": "GuestPanic", - "subtypes": [], - "props": { - "panic_msg": "" - } - }, - "HostError": { - "name": "HostError", - "subtypes": [ - "BadUTF16", - "BadUTF8", - "GasExceeded", - "GasLimitExceeded", - "BalanceExceeded", - "EmptyMethodName", - "GuestPanic", - "IntegerOverflow", - "InvalidPromiseIndex", - "CannotAppendActionToJointPromise", - "CannotReturnJointPromise", - "InvalidPromiseResultIndex", - "InvalidRegisterId", - "IteratorWasInvalidated", - "MemoryAccessViolation", - "InvalidReceiptIndex", - "InvalidIteratorIndex", - "InvalidAccountId", - "InvalidMethodName", - "InvalidPublicKey", - "ProhibitedInView", - "NumberOfLogsExceeded", - "KeyLengthExceeded", - "ValueLengthExceeded", - "TotalLogLengthExceeded", - "NumberPromisesExceeded", - "NumberInputDataDependenciesExceeded", - "ReturnedValueLengthExceeded", - "ContractSizeExceeded", - "Deprecated" - ], - "props": {} - }, - "IllegalArithmetic": { - "name": "IllegalArithmetic", - "subtypes": [], - "props": {} - }, - "IncorrectCallIndirectSignature": { - "name": "IncorrectCallIndirectSignature", - "subtypes": [], - "props": {} - }, - "Instantiate": { - "name": "Instantiate", - "subtypes": [], - "props": {} - }, - "IntegerOverflow": { - "name": "IntegerOverflow", - "subtypes": [], - "props": {} - }, - "InternalMemoryDeclared": { - "name": "InternalMemoryDeclared", - "subtypes": [], - "props": {} - }, - "InvalidAccountId": { - "name": "InvalidAccountId", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "InvalidIteratorIndex": { - "name": "InvalidIteratorIndex", - "subtypes": [], - "props": { - "iterator_index": "" - } - }, - "InvalidMethodName": { - "name": "InvalidMethodName", - "subtypes": [], - "props": {} - }, - "InvalidPromiseIndex": { - "name": "InvalidPromiseIndex", - "subtypes": [], - "props": { - "promise_idx": "" - } - }, - "InvalidPromiseResultIndex": { - "name": "InvalidPromiseResultIndex", - "subtypes": [], - "props": { - "result_idx": "" - } - }, - "InvalidPublicKey": { - "name": "InvalidPublicKey", - "subtypes": [], - "props": {} - }, - "InvalidReceiptIndex": { - "name": "InvalidReceiptIndex", - "subtypes": [], - "props": { - "receipt_index": "" - } - }, - "InvalidRegisterId": { - "name": "InvalidRegisterId", - "subtypes": [], - "props": { - "register_id": "" - } - }, - "IteratorWasInvalidated": { - "name": "IteratorWasInvalidated", - "subtypes": [], - "props": { - "iterator_index": "" - } - }, - "KeyLengthExceeded": { - "name": "KeyLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "LinkError": { - "name": "LinkError", - "subtypes": [], - "props": { - "msg": "" - } - }, - "Memory": { - "name": "Memory", - "subtypes": [], - "props": {} - }, - "MemoryAccessViolation": { - "name": "MemoryAccessViolation", - "subtypes": [], - "props": {} - }, - "MemoryOutOfBounds": { - "name": "MemoryOutOfBounds", - "subtypes": [], - "props": {} - }, - "MethodEmptyName": { - "name": "MethodEmptyName", - "subtypes": [], - "props": {} - }, - "MethodInvalidSignature": { - "name": "MethodInvalidSignature", - "subtypes": [], - "props": {} - }, - "MethodNotFound": { - "name": "MethodNotFound", - "subtypes": [], - "props": {} - }, - "MethodResolveError": { - "name": "MethodResolveError", - "subtypes": [ - "MethodEmptyName", - "MethodUTF8Error", - "MethodNotFound", - "MethodInvalidSignature" - ], - "props": {} - }, - "MethodUTF8Error": { - "name": "MethodUTF8Error", - "subtypes": [], - "props": {} - }, - "MisalignedAtomicAccess": { - "name": "MisalignedAtomicAccess", - "subtypes": [], - "props": {} - }, - "NumberInputDataDependenciesExceeded": { - "name": "NumberInputDataDependenciesExceeded", - "subtypes": [], - "props": { - "limit": "", - "number_of_input_data_dependencies": "" - } - }, - "NumberOfLogsExceeded": { - "name": "NumberOfLogsExceeded", - "subtypes": [], - "props": { - "limit": "" - } - }, - "NumberPromisesExceeded": { - "name": "NumberPromisesExceeded", - "subtypes": [], - "props": { - "limit": "", - "number_of_promises": "" - } - }, - "PrepareError": { - "name": "PrepareError", - "subtypes": [ - "Serialization", - "Deserialization", - "InternalMemoryDeclared", - "GasInstrumentation", - "StackHeightInstrumentation", - "Instantiate", - "Memory" - ], - "props": {} - }, - "ProhibitedInView": { - "name": "ProhibitedInView", - "subtypes": [], - "props": { - "method_name": "" - } - }, - "ReadError": { - "name": "ReadError", - "subtypes": [], - "props": {} - }, - "ReturnedValueLengthExceeded": { - "name": "ReturnedValueLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "Serialization": { - "name": "Serialization", - "subtypes": [], - "props": {} - }, - "SerializationError": { - "name": "SerializationError", - "subtypes": [], - "props": { - "hash": "" - } - }, - "StackHeightInstrumentation": { - "name": "StackHeightInstrumentation", - "subtypes": [], - "props": {} - }, - "StackOverflow": { - "name": "StackOverflow", - "subtypes": [], - "props": {} - }, - "TotalLogLengthExceeded": { - "name": "TotalLogLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "Unreachable": { - "name": "Unreachable", - "subtypes": [], - "props": {} - }, - "ValueLengthExceeded": { - "name": "ValueLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "WasmTrap": { - "name": "WasmTrap", - "subtypes": [ - "Unreachable", - "IncorrectCallIndirectSignature", - "MemoryOutOfBounds", - "CallIndirectOOB", - "IllegalArithmetic", - "MisalignedAtomicAccess", - "BreakpointTrap", - "StackOverflow", - "GenericTrap" - ], - "props": {} - }, - "WasmUnknownError": { - "name": "WasmUnknownError", - "subtypes": [], - "props": {} - }, - "WasmerCompileError": { - "name": "WasmerCompileError", - "subtypes": [], - "props": { - "msg": "" - } - }, - "WriteError": { - "name": "WriteError", - "subtypes": [], - "props": {} - }, - "AccessKeyNotFound": { - "name": "AccessKeyNotFound", - "subtypes": [], - "props": { - "account_id": "", - "public_key": "" - } - }, - "AccountAlreadyExists": { - "name": "AccountAlreadyExists", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "AccountDoesNotExist": { - "name": "AccountDoesNotExist", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "ActionError": { - "name": "ActionError", - "subtypes": [ - "AccountAlreadyExists", - "AccountDoesNotExist", - "CreateAccountOnlyByRegistrar", - "CreateAccountNotAllowed", - "ActorNoPermission", - "DeleteKeyDoesNotExist", - "AddKeyAlreadyExists", - "DeleteAccountStaking", - "LackBalanceForState", - "TriesToUnstake", - "TriesToStake", - "InsufficientStake", - "FunctionCallError", - "NewReceiptValidationError", - "OnlyImplicitAccountCreationAllowed" - ], - "props": { - "index": "" - } - }, - "ActionsValidationError": { - "name": "ActionsValidationError", - "subtypes": [ - "DeleteActionMustBeFinal", - "TotalPrepaidGasExceeded", - "TotalNumberOfActionsExceeded", - "AddKeyMethodNamesNumberOfBytesExceeded", - "AddKeyMethodNameLengthExceeded", - "IntegerOverflow", - "InvalidAccountId", - "ContractSizeExceeded", - "FunctionCallMethodNameLengthExceeded", - "FunctionCallArgumentsLengthExceeded", - "UnsuitableStakingKey", - "FunctionCallZeroAttachedGas" - ], - "props": {} - }, - "ActorNoPermission": { - "name": "ActorNoPermission", - "subtypes": [], - "props": { - "account_id": "", - "actor_id": "" - } - }, - "AddKeyAlreadyExists": { - "name": "AddKeyAlreadyExists", - "subtypes": [], - "props": { - "account_id": "", - "public_key": "" - } - }, - "AddKeyMethodNameLengthExceeded": { - "name": "AddKeyMethodNameLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "AddKeyMethodNamesNumberOfBytesExceeded": { - "name": "AddKeyMethodNamesNumberOfBytesExceeded", - "subtypes": [], - "props": { - "limit": "", - "total_number_of_bytes": "" - } - }, - "BalanceMismatchError": { - "name": "BalanceMismatchError", - "subtypes": [], - "props": { - "final_accounts_balance": "", - "final_postponed_receipts_balance": "", - "incoming_receipts_balance": "", - "incoming_validator_rewards": "", - "initial_accounts_balance": "", - "initial_postponed_receipts_balance": "", - "new_delayed_receipts_balance": "", - "other_burnt_amount": "", - "outgoing_receipts_balance": "", - "processed_delayed_receipts_balance": "", - "slashed_burnt_amount": "", - "tx_burnt_amount": "" - } - }, - "CostOverflow": { - "name": "CostOverflow", - "subtypes": [], - "props": {} - }, - "CreateAccountNotAllowed": { - "name": "CreateAccountNotAllowed", - "subtypes": [], - "props": { - "account_id": "", - "predecessor_id": "" - } - }, - "CreateAccountOnlyByRegistrar": { - "name": "CreateAccountOnlyByRegistrar", - "subtypes": [], - "props": { - "account_id": "", - "predecessor_id": "", - "registrar_account_id": "" - } - }, - "DeleteAccountStaking": { - "name": "DeleteAccountStaking", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "DeleteActionMustBeFinal": { - "name": "DeleteActionMustBeFinal", - "subtypes": [], - "props": {} - }, - "DeleteKeyDoesNotExist": { - "name": "DeleteKeyDoesNotExist", - "subtypes": [], - "props": { - "account_id": "", - "public_key": "" - } - }, - "DepositWithFunctionCall": { - "name": "DepositWithFunctionCall", - "subtypes": [], - "props": {} - }, - "Expired": { - "name": "Expired", - "subtypes": [], - "props": {} - }, - "FunctionCallArgumentsLengthExceeded": { - "name": "FunctionCallArgumentsLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "FunctionCallMethodNameLengthExceeded": { - "name": "FunctionCallMethodNameLengthExceeded", - "subtypes": [], - "props": { - "length": "", - "limit": "" - } - }, - "FunctionCallZeroAttachedGas": { - "name": "FunctionCallZeroAttachedGas", - "subtypes": [], - "props": {} - }, - "InsufficientStake": { - "name": "InsufficientStake", - "subtypes": [], - "props": { - "account_id": "", - "minimum_stake": "", - "stake": "" - } - }, - "InvalidAccessKeyError": { - "name": "InvalidAccessKeyError", - "subtypes": [ - "AccessKeyNotFound", - "ReceiverMismatch", - "MethodNameMismatch", - "RequiresFullAccess", - "NotEnoughAllowance", - "DepositWithFunctionCall" - ], - "props": {} - }, - "InvalidChain": { - "name": "InvalidChain", - "subtypes": [], - "props": {} - }, - "InvalidDataReceiverId": { - "name": "InvalidDataReceiverId", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "InvalidNonce": { - "name": "InvalidNonce", - "subtypes": [], - "props": { - "ak_nonce": "", - "tx_nonce": "" - } - }, - "InvalidPredecessorId": { - "name": "InvalidPredecessorId", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "InvalidReceiverId": { - "name": "InvalidReceiverId", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "InvalidSignature": { - "name": "InvalidSignature", - "subtypes": [], - "props": {} - }, - "InvalidSignerId": { - "name": "InvalidSignerId", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "InvalidTxError": { - "name": "InvalidTxError", - "subtypes": [ - "InvalidAccessKeyError", - "InvalidSignerId", - "SignerDoesNotExist", - "InvalidNonce", - "InvalidReceiverId", - "InvalidSignature", - "NotEnoughBalance", - "LackBalanceForState", - "CostOverflow", - "InvalidChain", - "Expired", - "ActionsValidation" - ], - "props": {} - }, - "LackBalanceForState": { - "name": "LackBalanceForState", - "subtypes": [], - "props": { - "account_id": "", - "amount": "" - } - }, - "MethodNameMismatch": { - "name": "MethodNameMismatch", - "subtypes": [], - "props": { - "method_name": "" - } - }, - "NotEnoughAllowance": { - "name": "NotEnoughAllowance", - "subtypes": [], - "props": { - "account_id": "", - "allowance": "", - "cost": "", - "public_key": "" - } - }, - "NotEnoughBalance": { - "name": "NotEnoughBalance", - "subtypes": [], - "props": { - "balance": "", - "cost": "", - "signer_id": "" - } - }, - "OnlyImplicitAccountCreationAllowed": { - "name": "OnlyImplicitAccountCreationAllowed", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "ReceiptValidationError": { - "name": "ReceiptValidationError", - "subtypes": [ - "InvalidPredecessorId", - "InvalidReceiverId", - "InvalidSignerId", - "InvalidDataReceiverId", - "ReturnedValueLengthExceeded", - "NumberInputDataDependenciesExceeded", - "ActionsValidation" - ], - "props": {} - }, - "ReceiverMismatch": { - "name": "ReceiverMismatch", - "subtypes": [], - "props": { - "ak_receiver": "", - "tx_receiver": "" - } - }, - "RequiresFullAccess": { - "name": "RequiresFullAccess", - "subtypes": [], - "props": {} - }, - "SignerDoesNotExist": { - "name": "SignerDoesNotExist", - "subtypes": [], - "props": { - "signer_id": "" - } - }, - "TotalNumberOfActionsExceeded": { - "name": "TotalNumberOfActionsExceeded", - "subtypes": [], - "props": { - "limit": "", - "total_number_of_actions": "" - } - }, - "TotalPrepaidGasExceeded": { - "name": "TotalPrepaidGasExceeded", - "subtypes": [], - "props": { - "limit": "", - "total_prepaid_gas": "" - } - }, - "TriesToStake": { - "name": "TriesToStake", - "subtypes": [], - "props": { - "account_id": "", - "balance": "", - "locked": "", - "stake": "" - } - }, - "TriesToUnstake": { - "name": "TriesToUnstake", - "subtypes": [], - "props": { - "account_id": "" - } - }, - "TxExecutionError": { - "name": "TxExecutionError", - "subtypes": [ - "ActionError", - "InvalidTxError" - ], - "props": {} - }, - "UnsuitableStakingKey": { - "name": "UnsuitableStakingKey", - "subtypes": [], - "props": { - "public_key": "" - } - }, - "Closed": { - "name": "Closed", - "subtypes": [], - "props": {} - }, - "InternalError": { - "name": "InternalError", - "subtypes": [], - "props": {} - }, - "ServerError": { - "name": "ServerError", - "subtypes": [ - "TxExecutionError", - "Timeout", - "Closed", - "InternalError" - ], - "props": {} - }, - "Timeout": { - "name": "Timeout", - "subtypes": [], - "props": {} - } - } -} \ No newline at end of file diff --git a/packages/utils/src/errors/rpc_error_schema.ts b/packages/utils/src/errors/rpc_error_schema.ts new file mode 100644 index 000000000..05ceff6d7 --- /dev/null +++ b/packages/utils/src/errors/rpc_error_schema.ts @@ -0,0 +1,869 @@ +export default { + 'schema': { + 'BadUTF16': { + 'name': 'BadUTF16', + 'subtypes': [], + 'props': {} + }, + 'BadUTF8': { + 'name': 'BadUTF8', + 'subtypes': [], + 'props': {} + }, + 'BalanceExceeded': { + 'name': 'BalanceExceeded', + 'subtypes': [], + 'props': {} + }, + 'BreakpointTrap': { + 'name': 'BreakpointTrap', + 'subtypes': [], + 'props': {} + }, + 'CacheError': { + 'name': 'CacheError', + 'subtypes': [ + 'ReadError', + 'WriteError', + 'DeserializationError', + 'SerializationError' + ], + 'props': {} + }, + 'CallIndirectOOB': { + 'name': 'CallIndirectOOB', + 'subtypes': [], + 'props': {} + }, + 'CannotAppendActionToJointPromise': { + 'name': 'CannotAppendActionToJointPromise', + 'subtypes': [], + 'props': {} + }, + 'CannotReturnJointPromise': { + 'name': 'CannotReturnJointPromise', + 'subtypes': [], + 'props': {} + }, + 'CodeDoesNotExist': { + 'name': 'CodeDoesNotExist', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'CompilationError': { + 'name': 'CompilationError', + 'subtypes': [ + 'CodeDoesNotExist', + 'PrepareError', + 'WasmerCompileError' + ], + 'props': {} + }, + 'ContractSizeExceeded': { + 'name': 'ContractSizeExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'size': '' + } + }, + 'Deprecated': { + 'name': 'Deprecated', + 'subtypes': [], + 'props': { + 'method_name': '' + } + }, + 'Deserialization': { + 'name': 'Deserialization', + 'subtypes': [], + 'props': {} + }, + 'DeserializationError': { + 'name': 'DeserializationError', + 'subtypes': [], + 'props': {} + }, + 'EmptyMethodName': { + 'name': 'EmptyMethodName', + 'subtypes': [], + 'props': {} + }, + 'FunctionCallError': { + 'name': 'FunctionCallError', + 'subtypes': [ + 'CompilationError', + 'LinkError', + 'MethodResolveError', + 'WasmTrap', + 'WasmUnknownError', + 'HostError', + 'EvmError' + ], + 'props': {} + }, + 'GasExceeded': { + 'name': 'GasExceeded', + 'subtypes': [], + 'props': {} + }, + 'GasInstrumentation': { + 'name': 'GasInstrumentation', + 'subtypes': [], + 'props': {} + }, + 'GasLimitExceeded': { + 'name': 'GasLimitExceeded', + 'subtypes': [], + 'props': {} + }, + 'GenericTrap': { + 'name': 'GenericTrap', + 'subtypes': [], + 'props': {} + }, + 'GuestPanic': { + 'name': 'GuestPanic', + 'subtypes': [], + 'props': { + 'panic_msg': '' + } + }, + 'HostError': { + 'name': 'HostError', + 'subtypes': [ + 'BadUTF16', + 'BadUTF8', + 'GasExceeded', + 'GasLimitExceeded', + 'BalanceExceeded', + 'EmptyMethodName', + 'GuestPanic', + 'IntegerOverflow', + 'InvalidPromiseIndex', + 'CannotAppendActionToJointPromise', + 'CannotReturnJointPromise', + 'InvalidPromiseResultIndex', + 'InvalidRegisterId', + 'IteratorWasInvalidated', + 'MemoryAccessViolation', + 'InvalidReceiptIndex', + 'InvalidIteratorIndex', + 'InvalidAccountId', + 'InvalidMethodName', + 'InvalidPublicKey', + 'ProhibitedInView', + 'NumberOfLogsExceeded', + 'KeyLengthExceeded', + 'ValueLengthExceeded', + 'TotalLogLengthExceeded', + 'NumberPromisesExceeded', + 'NumberInputDataDependenciesExceeded', + 'ReturnedValueLengthExceeded', + 'ContractSizeExceeded', + 'Deprecated' + ], + 'props': {} + }, + 'IllegalArithmetic': { + 'name': 'IllegalArithmetic', + 'subtypes': [], + 'props': {} + }, + 'IncorrectCallIndirectSignature': { + 'name': 'IncorrectCallIndirectSignature', + 'subtypes': [], + 'props': {} + }, + 'Instantiate': { + 'name': 'Instantiate', + 'subtypes': [], + 'props': {} + }, + 'IntegerOverflow': { + 'name': 'IntegerOverflow', + 'subtypes': [], + 'props': {} + }, + 'InternalMemoryDeclared': { + 'name': 'InternalMemoryDeclared', + 'subtypes': [], + 'props': {} + }, + 'InvalidAccountId': { + 'name': 'InvalidAccountId', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'InvalidIteratorIndex': { + 'name': 'InvalidIteratorIndex', + 'subtypes': [], + 'props': { + 'iterator_index': '' + } + }, + 'InvalidMethodName': { + 'name': 'InvalidMethodName', + 'subtypes': [], + 'props': {} + }, + 'InvalidPromiseIndex': { + 'name': 'InvalidPromiseIndex', + 'subtypes': [], + 'props': { + 'promise_idx': '' + } + }, + 'InvalidPromiseResultIndex': { + 'name': 'InvalidPromiseResultIndex', + 'subtypes': [], + 'props': { + 'result_idx': '' + } + }, + 'InvalidPublicKey': { + 'name': 'InvalidPublicKey', + 'subtypes': [], + 'props': {} + }, + 'InvalidReceiptIndex': { + 'name': 'InvalidReceiptIndex', + 'subtypes': [], + 'props': { + 'receipt_index': '' + } + }, + 'InvalidRegisterId': { + 'name': 'InvalidRegisterId', + 'subtypes': [], + 'props': { + 'register_id': '' + } + }, + 'IteratorWasInvalidated': { + 'name': 'IteratorWasInvalidated', + 'subtypes': [], + 'props': { + 'iterator_index': '' + } + }, + 'KeyLengthExceeded': { + 'name': 'KeyLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'LinkError': { + 'name': 'LinkError', + 'subtypes': [], + 'props': { + 'msg': '' + } + }, + 'Memory': { + 'name': 'Memory', + 'subtypes': [], + 'props': {} + }, + 'MemoryAccessViolation': { + 'name': 'MemoryAccessViolation', + 'subtypes': [], + 'props': {} + }, + 'MemoryOutOfBounds': { + 'name': 'MemoryOutOfBounds', + 'subtypes': [], + 'props': {} + }, + 'MethodEmptyName': { + 'name': 'MethodEmptyName', + 'subtypes': [], + 'props': {} + }, + 'MethodInvalidSignature': { + 'name': 'MethodInvalidSignature', + 'subtypes': [], + 'props': {} + }, + 'MethodNotFound': { + 'name': 'MethodNotFound', + 'subtypes': [], + 'props': {} + }, + 'MethodResolveError': { + 'name': 'MethodResolveError', + 'subtypes': [ + 'MethodEmptyName', + 'MethodUTF8Error', + 'MethodNotFound', + 'MethodInvalidSignature' + ], + 'props': {} + }, + 'MethodUTF8Error': { + 'name': 'MethodUTF8Error', + 'subtypes': [], + 'props': {} + }, + 'MisalignedAtomicAccess': { + 'name': 'MisalignedAtomicAccess', + 'subtypes': [], + 'props': {} + }, + 'NumberInputDataDependenciesExceeded': { + 'name': 'NumberInputDataDependenciesExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'number_of_input_data_dependencies': '' + } + }, + 'NumberOfLogsExceeded': { + 'name': 'NumberOfLogsExceeded', + 'subtypes': [], + 'props': { + 'limit': '' + } + }, + 'NumberPromisesExceeded': { + 'name': 'NumberPromisesExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'number_of_promises': '' + } + }, + 'PrepareError': { + 'name': 'PrepareError', + 'subtypes': [ + 'Serialization', + 'Deserialization', + 'InternalMemoryDeclared', + 'GasInstrumentation', + 'StackHeightInstrumentation', + 'Instantiate', + 'Memory' + ], + 'props': {} + }, + 'ProhibitedInView': { + 'name': 'ProhibitedInView', + 'subtypes': [], + 'props': { + 'method_name': '' + } + }, + 'ReadError': { + 'name': 'ReadError', + 'subtypes': [], + 'props': {} + }, + 'ReturnedValueLengthExceeded': { + 'name': 'ReturnedValueLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'Serialization': { + 'name': 'Serialization', + 'subtypes': [], + 'props': {} + }, + 'SerializationError': { + 'name': 'SerializationError', + 'subtypes': [], + 'props': { + 'hash': '' + } + }, + 'StackHeightInstrumentation': { + 'name': 'StackHeightInstrumentation', + 'subtypes': [], + 'props': {} + }, + 'StackOverflow': { + 'name': 'StackOverflow', + 'subtypes': [], + 'props': {} + }, + 'TotalLogLengthExceeded': { + 'name': 'TotalLogLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'Unreachable': { + 'name': 'Unreachable', + 'subtypes': [], + 'props': {} + }, + 'ValueLengthExceeded': { + 'name': 'ValueLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'WasmTrap': { + 'name': 'WasmTrap', + 'subtypes': [ + 'Unreachable', + 'IncorrectCallIndirectSignature', + 'MemoryOutOfBounds', + 'CallIndirectOOB', + 'IllegalArithmetic', + 'MisalignedAtomicAccess', + 'BreakpointTrap', + 'StackOverflow', + 'GenericTrap' + ], + 'props': {} + }, + 'WasmUnknownError': { + 'name': 'WasmUnknownError', + 'subtypes': [], + 'props': {} + }, + 'WasmerCompileError': { + 'name': 'WasmerCompileError', + 'subtypes': [], + 'props': { + 'msg': '' + } + }, + 'WriteError': { + 'name': 'WriteError', + 'subtypes': [], + 'props': {} + }, + 'AccessKeyNotFound': { + 'name': 'AccessKeyNotFound', + 'subtypes': [], + 'props': { + 'account_id': '', + 'public_key': '' + } + }, + 'AccountAlreadyExists': { + 'name': 'AccountAlreadyExists', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'AccountDoesNotExist': { + 'name': 'AccountDoesNotExist', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'ActionError': { + 'name': 'ActionError', + 'subtypes': [ + 'AccountAlreadyExists', + 'AccountDoesNotExist', + 'CreateAccountOnlyByRegistrar', + 'CreateAccountNotAllowed', + 'ActorNoPermission', + 'DeleteKeyDoesNotExist', + 'AddKeyAlreadyExists', + 'DeleteAccountStaking', + 'LackBalanceForState', + 'TriesToUnstake', + 'TriesToStake', + 'InsufficientStake', + 'FunctionCallError', + 'NewReceiptValidationError', + 'OnlyImplicitAccountCreationAllowed' + ], + 'props': { + 'index': '' + } + }, + 'ActionsValidationError': { + 'name': 'ActionsValidationError', + 'subtypes': [ + 'DeleteActionMustBeFinal', + 'TotalPrepaidGasExceeded', + 'TotalNumberOfActionsExceeded', + 'AddKeyMethodNamesNumberOfBytesExceeded', + 'AddKeyMethodNameLengthExceeded', + 'IntegerOverflow', + 'InvalidAccountId', + 'ContractSizeExceeded', + 'FunctionCallMethodNameLengthExceeded', + 'FunctionCallArgumentsLengthExceeded', + 'UnsuitableStakingKey', + 'FunctionCallZeroAttachedGas' + ], + 'props': {} + }, + 'ActorNoPermission': { + 'name': 'ActorNoPermission', + 'subtypes': [], + 'props': { + 'account_id': '', + 'actor_id': '' + } + }, + 'AddKeyAlreadyExists': { + 'name': 'AddKeyAlreadyExists', + 'subtypes': [], + 'props': { + 'account_id': '', + 'public_key': '' + } + }, + 'AddKeyMethodNameLengthExceeded': { + 'name': 'AddKeyMethodNameLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'AddKeyMethodNamesNumberOfBytesExceeded': { + 'name': 'AddKeyMethodNamesNumberOfBytesExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'total_number_of_bytes': '' + } + }, + 'BalanceMismatchError': { + 'name': 'BalanceMismatchError', + 'subtypes': [], + 'props': { + 'final_accounts_balance': '', + 'final_postponed_receipts_balance': '', + 'incoming_receipts_balance': '', + 'incoming_validator_rewards': '', + 'initial_accounts_balance': '', + 'initial_postponed_receipts_balance': '', + 'new_delayed_receipts_balance': '', + 'other_burnt_amount': '', + 'outgoing_receipts_balance': '', + 'processed_delayed_receipts_balance': '', + 'slashed_burnt_amount': '', + 'tx_burnt_amount': '' + } + }, + 'CostOverflow': { + 'name': 'CostOverflow', + 'subtypes': [], + 'props': {} + }, + 'CreateAccountNotAllowed': { + 'name': 'CreateAccountNotAllowed', + 'subtypes': [], + 'props': { + 'account_id': '', + 'predecessor_id': '' + } + }, + 'CreateAccountOnlyByRegistrar': { + 'name': 'CreateAccountOnlyByRegistrar', + 'subtypes': [], + 'props': { + 'account_id': '', + 'predecessor_id': '', + 'registrar_account_id': '' + } + }, + 'DeleteAccountStaking': { + 'name': 'DeleteAccountStaking', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'DeleteActionMustBeFinal': { + 'name': 'DeleteActionMustBeFinal', + 'subtypes': [], + 'props': {} + }, + 'DeleteKeyDoesNotExist': { + 'name': 'DeleteKeyDoesNotExist', + 'subtypes': [], + 'props': { + 'account_id': '', + 'public_key': '' + } + }, + 'DepositWithFunctionCall': { + 'name': 'DepositWithFunctionCall', + 'subtypes': [], + 'props': {} + }, + 'Expired': { + 'name': 'Expired', + 'subtypes': [], + 'props': {} + }, + 'FunctionCallArgumentsLengthExceeded': { + 'name': 'FunctionCallArgumentsLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'FunctionCallMethodNameLengthExceeded': { + 'name': 'FunctionCallMethodNameLengthExceeded', + 'subtypes': [], + 'props': { + 'length': '', + 'limit': '' + } + }, + 'FunctionCallZeroAttachedGas': { + 'name': 'FunctionCallZeroAttachedGas', + 'subtypes': [], + 'props': {} + }, + 'InsufficientStake': { + 'name': 'InsufficientStake', + 'subtypes': [], + 'props': { + 'account_id': '', + 'minimum_stake': '', + 'stake': '' + } + }, + 'InvalidAccessKeyError': { + 'name': 'InvalidAccessKeyError', + 'subtypes': [ + 'AccessKeyNotFound', + 'ReceiverMismatch', + 'MethodNameMismatch', + 'RequiresFullAccess', + 'NotEnoughAllowance', + 'DepositWithFunctionCall' + ], + 'props': {} + }, + 'InvalidChain': { + 'name': 'InvalidChain', + 'subtypes': [], + 'props': {} + }, + 'InvalidDataReceiverId': { + 'name': 'InvalidDataReceiverId', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'InvalidNonce': { + 'name': 'InvalidNonce', + 'subtypes': [], + 'props': { + 'ak_nonce': '', + 'tx_nonce': '' + } + }, + 'InvalidPredecessorId': { + 'name': 'InvalidPredecessorId', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'InvalidReceiverId': { + 'name': 'InvalidReceiverId', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'InvalidSignature': { + 'name': 'InvalidSignature', + 'subtypes': [], + 'props': {} + }, + 'InvalidSignerId': { + 'name': 'InvalidSignerId', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'InvalidTxError': { + 'name': 'InvalidTxError', + 'subtypes': [ + 'InvalidAccessKeyError', + 'InvalidSignerId', + 'SignerDoesNotExist', + 'InvalidNonce', + 'InvalidReceiverId', + 'InvalidSignature', + 'NotEnoughBalance', + 'LackBalanceForState', + 'CostOverflow', + 'InvalidChain', + 'Expired', + 'ActionsValidation' + ], + 'props': {} + }, + 'LackBalanceForState': { + 'name': 'LackBalanceForState', + 'subtypes': [], + 'props': { + 'account_id': '', + 'amount': '' + } + }, + 'MethodNameMismatch': { + 'name': 'MethodNameMismatch', + 'subtypes': [], + 'props': { + 'method_name': '' + } + }, + 'NotEnoughAllowance': { + 'name': 'NotEnoughAllowance', + 'subtypes': [], + 'props': { + 'account_id': '', + 'allowance': '', + 'cost': '', + 'public_key': '' + } + }, + 'NotEnoughBalance': { + 'name': 'NotEnoughBalance', + 'subtypes': [], + 'props': { + 'balance': '', + 'cost': '', + 'signer_id': '' + } + }, + 'OnlyImplicitAccountCreationAllowed': { + 'name': 'OnlyImplicitAccountCreationAllowed', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'ReceiptValidationError': { + 'name': 'ReceiptValidationError', + 'subtypes': [ + 'InvalidPredecessorId', + 'InvalidReceiverId', + 'InvalidSignerId', + 'InvalidDataReceiverId', + 'ReturnedValueLengthExceeded', + 'NumberInputDataDependenciesExceeded', + 'ActionsValidation' + ], + 'props': {} + }, + 'ReceiverMismatch': { + 'name': 'ReceiverMismatch', + 'subtypes': [], + 'props': { + 'ak_receiver': '', + 'tx_receiver': '' + } + }, + 'RequiresFullAccess': { + 'name': 'RequiresFullAccess', + 'subtypes': [], + 'props': {} + }, + 'SignerDoesNotExist': { + 'name': 'SignerDoesNotExist', + 'subtypes': [], + 'props': { + 'signer_id': '' + } + }, + 'TotalNumberOfActionsExceeded': { + 'name': 'TotalNumberOfActionsExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'total_number_of_actions': '' + } + }, + 'TotalPrepaidGasExceeded': { + 'name': 'TotalPrepaidGasExceeded', + 'subtypes': [], + 'props': { + 'limit': '', + 'total_prepaid_gas': '' + } + }, + 'TriesToStake': { + 'name': 'TriesToStake', + 'subtypes': [], + 'props': { + 'account_id': '', + 'balance': '', + 'locked': '', + 'stake': '' + } + }, + 'TriesToUnstake': { + 'name': 'TriesToUnstake', + 'subtypes': [], + 'props': { + 'account_id': '' + } + }, + 'TxExecutionError': { + 'name': 'TxExecutionError', + 'subtypes': [ + 'ActionError', + 'InvalidTxError' + ], + 'props': {} + }, + 'UnsuitableStakingKey': { + 'name': 'UnsuitableStakingKey', + 'subtypes': [], + 'props': { + 'public_key': '' + } + }, + 'Closed': { + 'name': 'Closed', + 'subtypes': [], + 'props': {} + }, + 'InternalError': { + 'name': 'InternalError', + 'subtypes': [], + 'props': {} + }, + 'ServerError': { + 'name': 'ServerError', + 'subtypes': [ + 'TxExecutionError', + 'Timeout', + 'Closed', + 'InternalError' + ], + 'props': {} + }, + 'Timeout': { + 'name': 'Timeout', + 'subtypes': [], + 'props': {} + } + } +}; \ No newline at end of file diff --git a/packages/utils/src/errors/rpc_errors.ts b/packages/utils/src/errors/rpc_errors.ts index a86498bf7..127f22fcc 100644 --- a/packages/utils/src/errors/rpc_errors.ts +++ b/packages/utils/src/errors/rpc_errors.ts @@ -2,8 +2,9 @@ import { TypedError } from '@near-js/types'; import Mustache from 'mustache'; import { formatNearAmount } from '../format.js'; -import messages from './error_messages.json'; -import schema from './rpc_error_schema.json'; +// TODO get JSON files working with CJS and ESM +import messages from './error_messages.js'; +import schema from './rpc_error_schema.js'; const mustacheHelpers = { formatNear: () => (n, render) => formatNearAmount(render(n)) From 5960b5174ddf881fb186415d0dff5cabe9c980e6 Mon Sep 17 00:00:00 2001 From: Andy Haynes Date: Tue, 7 Mar 2023 14:05:05 -0800 Subject: [PATCH 84/84] test: fix accounts tests --- packages/accounts/test/providers.test.js | 4 +++- packages/accounts/test/test-utils.js | 5 +++-- packages/wallet-account/test/wallet_account.test.js | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/accounts/test/providers.test.js b/packages/accounts/test/providers.test.js index 6cea5f654..0b87c550b 100644 --- a/packages/accounts/test/providers.test.js +++ b/packages/accounts/test/providers.test.js @@ -1,13 +1,15 @@ +import { jest } from '@jest/globals'; import { JsonRpcProvider } from '@near-js/providers'; import BN from 'bn.js'; import base58 from 'bs58'; +import getConfig from './config.js'; import testUtils from './test-utils.js'; jest.setTimeout(20000); const withProvider = (fn) => { - const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test')); + const config = Object.assign(getConfig(process.env.NODE_ENV || 'test')); const provider = new JsonRpcProvider(config.nodeUrl); return () => fn(provider); }; diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 68cc0a643..f0d281103 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -4,6 +4,7 @@ import BN from 'bn.js'; import { promises as fs } from 'fs'; import { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } from '../lib/esm'; +import getConfig from './config.js'; const networkId = 'unittest'; @@ -20,7 +21,7 @@ const RANDOM_ACCOUNT_LENGTH = 40; async function setUpTestConnection() { const keyStore = new InMemoryKeyStore(); - const config = Object.assign(require('./config')(process.env.NODE_ENV || 'test'), { + const config = Object.assign(getConfig(process.env.NODE_ENV || 'test'), { networkId, keyStore }); @@ -112,7 +113,7 @@ function waitFor(fn) { return _waitFor(); } -module.exports = { +export default { setUpTestConnection, networkId, generateUniqueString, diff --git a/packages/wallet-account/test/wallet_account.test.js b/packages/wallet-account/test/wallet_account.test.js index 46c678559..33d441dd0 100644 --- a/packages/wallet-account/test/wallet_account.test.js +++ b/packages/wallet-account/test/wallet_account.test.js @@ -7,7 +7,7 @@ import { baseDecode, deserialize } from 'borsh'; import localStorage from 'localstorage-memory'; import url from 'url'; -import { WalletConnection } from '../lib/wallet_account'; +import { WalletConnection } from '../lib/esm'; const { functionCall, transfer } = actionCreators;