diff --git a/.changeset/config.json b/.changeset/config.json index 125cdb8971..ebca3ed7b7 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,7 +2,7 @@ "$schema": "https://unpkg.com/@changesets/config@2.1.1/schema.json", "changelog": ["@changesets/changelog-github", { "repo": "near/near-api-js" }], "commit": false, - "fixed": [], + "fixed": [["@near-js/*"]], "linked": [], "access": "public", "baseBranch": "master", diff --git a/.changeset/rotten-cases-dance.md b/.changeset/rotten-cases-dance.md new file mode 100644 index 0000000000..4fb27eaf74 --- /dev/null +++ b/.changeset/rotten-cases-dance.md @@ -0,0 +1,14 @@ +--- +"@near-js/wallet-account": major +"@near-js/transactions": major +"near-api-js": major +"@near-js/providers": major +"@near-js/accounts": major +"@near-js/cookbook": major +"@near-js/signers": major +"@near-js/client": major +"@near-js/types": major +"@near-js/tokens": major +--- + +Major update for Signer and Account APIs to streamline development diff --git a/package.json b/package.json index 014ca010e8..4a80d4c8f2 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,6 @@ }, "resolutions": { "near-sandbox": "0.0.18", - "near-api-js": "4.0.0" + "near-api-js": "workspace:*" } } diff --git a/packages/accounts/package.json b/packages/accounts/package.json index a7ebdde3ce..b888311a3d 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -2,15 +2,16 @@ "name": "@near-js/accounts", "version": "1.4.1", "description": "Classes encapsulating account-specific functionality", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc", "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc --fix", - "test": "jest" + "test": "DEFAULT_FINALITY=optimistic jest" }, "keywords": [], "author": "", @@ -22,6 +23,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/tokens": "workspace:*", "@noble/hashes": "1.7.1", "borsh": "1.0.0", "depd": "2.0.0", @@ -33,22 +35,33 @@ "@jest/globals": "^29.7.0", "@near-js/keystores": "workspace:*", "@scure/base": "^1.2.4", + "@types/depd": "^1.1.37", "@types/json-schema": "^7.0.15", "@types/node": "20.0.0", "build": "workspace:*", "jest": "29.7.0", "near-hello": "0.5.1", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "semver": "7.7.1", "ts-jest": "29.2.6", "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } \ No newline at end of file diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 47fe2245f8..f46d32aefe 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -1,40 +1,52 @@ -import { PublicKey } from '@near-js/crypto'; -import { exponentialBackoff } from '@near-js/providers'; +import { PublicKey } from "@near-js/crypto"; +import { exponentialBackoff, Provider } from "@near-js/providers"; import { actionCreators, Action, buildDelegateAction, - signDelegateAction, - signTransaction, SignedDelegate, SignedTransaction, + createTransaction, stringifyJsonOrBytes, -} from '@near-js/transactions'; + DelegateAction, +} from "@near-js/transactions"; import { PositionalArgsError, FinalExecutionOutcome, TypedError, - ErrorContext, - AccountView, AccessKeyView, AccessKeyViewRaw, AccessKeyList, AccessKeyInfoView, FunctionCallPermissionView, BlockReference, -} from '@near-js/types'; + ContractCodeView, + ContractStateView, + ErrorContext, + TxExecutionStatus, + AccountView, + Finality, +} from "@near-js/types"; import { baseDecode, - baseEncode, Logger, - parseResultError, DEFAULT_FUNCTION_CALL_GAS, + baseEncode, + parseResultError, printTxOutcomeLogsAndFailures, -} from '@near-js/utils'; + getTransactionLastResult, +} from "@near-js/utils"; -import { Connection } from './connection'; -import { viewFunction, viewState } from './utils'; -import { ChangeFunctionCallOptions, IntoConnection, ViewFunctionCallOptions } from './interface'; +import { SignedMessage, Signer } from "@near-js/signers"; +import { Connection } from "./connection"; +import { viewFunction, viewState } from "./utils"; +import { + ChangeFunctionCallOptions, + ViewFunctionCallOptions, +} from "./interface"; + +import { NEAR } from "@near-js/tokens"; +import type { NativeToken, FungibleToken } from "@near-js/tokens"; const { addKey, @@ -45,18 +57,23 @@ const { 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; +// Environment value is used in tests since near-sandbox runs old version of nearcore that doesn't work with near-final finality +const DEFAULT_FINALITY = process.env.DEFAULT_FINALITY as Finality || "near-final"; +export const DEFAULT_WAIT_STATUS: TxExecutionStatus = "EXECUTED_OPTIMISTIC"; -// 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 AccountState { + balance: { + total: bigint; + usedOnStorage: bigint; + locked: bigint; + available: bigint; + } + storageUsage: number; + codeHash: string; +} export interface AccountBalance { total: string; @@ -109,236 +126,615 @@ interface SignedDelegateOptions { } /** - * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. + * This class allows to access common account information. + * If a {@link Signer} is provider, then the account can + * be used to perform all common actions such as + * transferring tokens and calling functions */ -export class Account implements IntoConnection { - readonly connection: Connection; - readonly accountId: string; +export class Account { + public readonly accountId: string; + public readonly provider: Provider; + private signer?: Signer; - constructor(connection: Connection, accountId: string) { - this.connection = connection; + constructor(accountId: string, provider: Provider, signer?: Signer) { this.accountId = accountId; + this.provider = provider; + this.signer = signer; + } + + /** + * Allows to set the signer used to control the account + * + * @param signer holds the private key and can sign Transactions + */ + public setSigner(signer: Signer): void { + this.signer = signer; } - getConnection(): Connection { - return this.connection; + public getSigner(): Signer | undefined { + return this.signer; } /** - * 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) + * Returns an overview of the account's state, including the account's + * balance, storage usage, and code hash */ - async state(): Promise { - return this.connection.provider.query({ - request_type: 'view_account', - account_id: this.accountId, - finality: 'optimistic' + public async getState(): Promise { + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: DEFAULT_FINALITY, }); + const state = await this.provider.viewAccount(this.accountId, { + finality: DEFAULT_FINALITY, + }); + + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); + const usedOnStorage = BigInt(state.storage_usage) * costPerByte; + const locked = BigInt(state.locked); + const total = BigInt(state.amount) + locked; + const available = + total - (locked > usedOnStorage ? locked : usedOnStorage); + + return { + balance: { + total, usedOnStorage, locked, available + }, + storageUsage: state.storage_usage, + codeHash: state.code_hash, + }; } /** - * 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 "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} + * Calls {@link Provider.viewAccessKey} to retrieve information for a + * specific key in the account */ - 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; + public async getAccessKey( + publicKey: PublicKey | string + ): Promise { + return this.provider.viewAccessKey(this.accountId, publicKey, { + finality: DEFAULT_FINALITY, + }); + } - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = block.header.hash; + /** + * Calls {@link Provider.viewAccessKeyList} to retrieve the account's keys + */ + public async getAccessKeyList(): Promise { + return this.provider.viewAccessKeyList(this.accountId, { + finality: DEFAULT_FINALITY, + }); + } + + /** + * Calls {@link Provider.viewContractCode} to retrieve the account's + * contract code and its hash + */ + public async getContractCode(): Promise { + return this.provider.viewContractCode(this.accountId, { + finality: DEFAULT_FINALITY, + }); + } - const nonce = accessKey.nonce + 1n; - return await signTransaction( - receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId + /** + * Calls {@link Provider.viewContractState} to retrieve the keys and values + * stored on the account's contract + */ + public async getContractState(prefix?: string): Promise { + return this.provider.viewContractState(this.accountId, prefix, { + finality: DEFAULT_FINALITY, + }); + } + + /** + * Create a transaction that can be later signed with a {@link Signer} + * + * @param receiverId Account against which to perform the actions + * @param actions Actions to perform + * @param publicKey The public part of the key that will be used to sign the transaction + */ + public async createTransaction( + receiverId: string, + actions: Action[], + publicKey: PublicKey | string + ) { + if (!publicKey) throw new Error("Please provide a public key"); + + const pk = PublicKey.from(publicKey); + + const accessKey = await this.getAccessKey(pk); + + const block = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, + }); + const recentBlockHash = block.header.hash; + + const nonce = BigInt(accessKey.nonce) + 1n; + + return createTransaction( + this.accountId, + pk, + receiverId, + nonce + 1n, + actions, + baseDecode(recentBlockHash) ); } /** - * Sign a transaction to perform a list of actions and broadcast it using the RPC API. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } - * - * @param options The options for signing and sending the transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be performed in the transaction. - * @param options.returnError Whether to return an error if the transaction fails. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. + * Create a signed transaction ready to be broadcast by a {@link Provider} */ - 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') { - Logger.warn(`Retrying transaction ${receiverId}:${baseEncode(txHash)} with new nonce.`); - delete this.accessKeyByPublicKeyCache[publicKey.toString()]; - return null; - } - if (error.type === 'Expired') { - Logger.warn(`Retrying transaction ${receiverId}:${baseEncode(txHash)} due to expired block hash`); - return null; - } + public async createSignedTransaction( + receiverId: string, + actions: Action[] + ): Promise { + if (!this.signer) throw new Error("Please set a signer"); - error.context = new ErrorContext(baseEncode(txHash)); - throw error; - } + const tx = await this.createTransaction( + receiverId, + actions, + await this.signer.getPublicKey() + ); + + const [, signedTx] = await this.signer.signTransaction(tx); + + return signedTx; + } + + /** + * Create a meta transaction ready to be signed by a {@link Signer} + * + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the meta transaction + * @param blockHeightTtl number of blocks after which a meta transaction will expire if not processed + */ + public async createMetaTransaction( + receiverId: string, + actions: Action[], + blockHeightTtl: number = 200, + publicKey: PublicKey | string + ): Promise { + if (!publicKey) throw new Error(`Please provide a public key`); + + const pk = PublicKey.from(publicKey); + + const accessKey = await this.getAccessKey(pk); + const nonce = BigInt(accessKey.nonce) + 1n; + + const { header } = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, }); - 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 }); + const maxBlockHeight = BigInt(header.height) + BigInt(blockHeightTtl); - // 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; + return buildDelegateAction({ + receiverId, + senderId: this.accountId, + actions, + publicKey: pk, + nonce, + maxBlockHeight, + }); } - /** @hidden */ - accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; + /** + * Create a signed MetaTransaction that can be broadcasted to a relayer + * + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the meta transaction + * @param blockHeightTtl number of blocks after which a meta transaction will expire if not processed + */ + public async createSignedMetaTransaction( + receiverId: string, + actions: Action[], + blockHeightTtl: number = 200 + ): Promise<[Uint8Array, SignedDelegate]> { + if (!this.signer) throw new Error(`Please set a signer`); + + const delegateAction = await this.createMetaTransaction( + receiverId, + actions, + blockHeightTtl, + await this.signer.getPublicKey() + ); + + return this.signer.signDelegateAction(delegateAction); + } /** - * Finds the {@link AccessKeyView} associated with the accounts {@link PublicKey} stored in the {@link "@near-js/keystores".keystore.KeyStore | Keystore}. + * Creates a transaction, signs it and broadcast it to the network * - * @todo Find matching access key based on transaction (i.e. receiverId and actions) + * @param receiverId The NEAR account ID of the transaction receiver. + * @param actions The list of actions to be performed in the transaction. + * @param throwOnFailure Whether to throw an error if the transaction fails. + * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. * - * @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'); - } + async signAndSendTransaction({ + receiverId, + actions, + waitUntil = DEFAULT_WAIT_STATUS, + throwOnFailure = true, + }: { + receiverId: string; + actions: Action[]; + waitUntil?: TxExecutionStatus; + throwOnFailure?: boolean; + }): Promise { + const signedTx = await this.createSignedTransaction( + receiverId, + actions + ); - const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()]; - if (cachedAccessKey !== undefined) { - return { publicKey, accessKey: cachedAccessKey }; + const result = await this.provider.sendTransactionUntil(signedTx, waitUntil); + + if (throwOnFailure && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { + throw parseResultError(result); } - try { - const rawAccessKey = await this.connection.provider.query({ - request_type: 'view_access_key', - account_id: this.accountId, - public_key: publicKey.toString(), - finality: 'optimistic' - }); + return result + } - // store nonce as BigInt to preserve precision on big number - const accessKey = { - ...rawAccessKey, - nonce: BigInt(rawAccessKey.nonce || 0) - }; - // 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()] }; - } + async signAndSendTransactions({ + transactions, waitUntil = DEFAULT_WAIT_STATUS, throwOnFailure = true + }: { + transactions: { receiverId: string; actions: Action[] }[], waitUntil?: TxExecutionStatus, throwOnFailure?: boolean + }): Promise { + if (!this.signer) throw new Error("Please set a signer"); - this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; - return { publicKey, accessKey }; - } catch (e) { - if (e.type == 'AccessKeyDoesNotExist') { - return null; - } + const results = await Promise.all( + transactions.map(async ({ receiverId, actions }) => { + return this.signAndSendTransaction({ + receiverId, + actions, + waitUntil, + throwOnFailure + }) + }) + ); - throw e; + return results + } + + /** + * Creates an account of the form ., e.g. ana.testnet or ana.near + * + * @param newAccountId the new account to create (e.g. ana.near) + * @param publicKey the public part of the key that will control the account + * @param nearToTransfer how much NEAR to transfer to the account in yoctoNEAR + * + */ + public async createTopLevelAccount( + newAccountId: string, + publicKey: PublicKey | string, + nearToTransfer: bigint | string | number + ): Promise { + const splitted = newAccountId.split("."); + if (splitted.length != 2) { + throw new Error( + "newAccountId needs to be of the form ." + ); } + + const TLA = splitted[1]; + return this.signAndSendTransaction({ + receiverId: TLA, + actions: [ + functionCall( + "create_account", + { + new_account_id: newAccountId, + new_public_key: publicKey.toString(), + }, + BigInt("60000000000000"), + BigInt(nearToTransfer) + ), + ], + }); } /** - * Create a new account and deploy a contract to it + * Creates a sub account of this account. For example, if the account is + * ana.near, you can create sub.ana.near. + * + * @param accountOrPrefix a prefix (e.g. `sub`) or the full sub-account (`sub.ana.near`) + * @param publicKey the public part of the key that will control the account + * @param nearToTransfer how much NEAR to transfer to the account * - * @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: bigint): Promise { - const accessKey = fullAccessKey(); - await this.signAndSendTransaction({ - receiverId: contractId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey), deployContract(data)] + public async createSubAccount( + accountOrPrefix: string, + publicKey: PublicKey | string, + nearToTransfer: bigint | string | number + ): Promise { + if (!this.signer) throw new Error("Please set a signer"); + + const newAccountId = accountOrPrefix.includes(".") + ? accountOrPrefix + : `${accountOrPrefix}.${this.accountId}`; + + if (newAccountId.length > 64) { + throw new Error(`Accounts cannot exceed 64 characters`); + } + + if (!newAccountId.endsWith(this.accountId)) { + throw new Error(`New account must end up with ${this.accountId}`); + } + + const actions = [ + createAccount(), + transfer(BigInt(nearToTransfer)), + addKey(PublicKey.from(publicKey), fullAccessKey()), + ]; + + return this.signAndSendTransaction({ + receiverId: newAccountId, + actions, }); - const contractAccount = new Account(this.connection, contractId); - return contractAccount; } /** - * @param receiverId NEAR account receiving Ⓝ - * @param amount Amount to send in yoctoⓃ + * Deletes the account, transferring all remaining NEAR to a beneficiary + * account + * + * Important: Deleting an account does not transfer FTs or NFTs + * + * @param beneficiaryId Will receive the account's remaining balance */ - async sendMoney(receiverId: string, amount: bigint): Promise { + public async deleteAccount( + beneficiaryId: string + ): Promise { return this.signAndSendTransaction({ - receiverId, - actions: [transfer(amount)] + receiverId: this.accountId, + actions: [deleteAccount(beneficiaryId)], }); } /** - * @param newAccountId NEAR account name to be created - * @param publicKey A public key created from the masterAccount + * Deploy a smart contract in the account + * + * @param code The compiled contract code bytes */ - async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: bigint): Promise { - const accessKey = fullAccessKey(); + public async deployContract( + code: Uint8Array + ): Promise { return this.signAndSendTransaction({ - receiverId: newAccountId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey)] + receiverId: this.accountId, + actions: [deployContract(code)], }); } /** - * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted + * + * @param publicKey The key to add to the account + * @param contractId The contract that this key can call + * @param methodNames The methods this key is allowed to call + * @param allowance The amount of NEAR this key can expend in gas + * @param opts + * @returns */ - async deleteAccount(beneficiaryId: string) { - Logger.log('Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting.'); + public async addFunctionCallAccessKey( + publicKey: PublicKey | string, + contractId: string, + methodNames: string[], + allowance?: bigint | string | number + ): Promise { return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deleteAccount(beneficiaryId)] + actions: [ + addKey( + PublicKey.from(publicKey), + functionCallAccessKey( + contractId, + methodNames, + BigInt(allowance) + ) + ), + ] }); } /** - * @param data The compiled contract code + * Add a full access key to the account + * + * @param publicKey The public key to be added + * @param opts + * @returns {Promise} */ - async deployContract(data: Uint8Array): Promise { + public async addFullAccessKey( + publicKey: PublicKey | string + ): Promise { + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: [ + addKey( + PublicKey.from(publicKey), + fullAccessKey() + ), + ], + } + ); + } + + /** + * @param publicKey The public key to be deleted + * @returns {Promise} + */ + public async deleteKey( + publicKey: PublicKey | string + ): Promise { return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deployContract(data)] + actions: [deleteKey(PublicKey.from(publicKey))], }); } + /** + * Call a function on a smart contract + * + * @param options + * @param options.contractId The contract in which to call the function + * @param options.methodName The method that will be called + * @param options.args Arguments, either as a valid JSON Object or a raw Uint8Array + * @param options.deposit (optional) Amount of NEAR Tokens to attach to the call (default 0) + * @param options.gas (optional) Amount of GAS to use attach to the call (default 30TGas) + * @returns + */ + public async callFunction({ + contractId, + methodName, + args = {}, + deposit = "0", + gas = DEFAULT_FUNCTION_CALL_GAS, + }: { + contractId: string; + methodName: string; + args: Uint8Array | Record; + deposit?: bigint | string | number; + gas?: bigint | string | number; + }): Promise { + const result = await this.signAndSendTransaction({ + receiverId: contractId, + actions: [ + functionCall(methodName, args, BigInt(gas), BigInt(deposit)), + ], + }); + + return getTransactionLastResult(result); + } + + /** + * @param options + * @param options.message The message to be signed (e.g. "authenticating") + * @param options.recipient Who will receive the message (e.g. auth.app.com) + * @param options.nonce A challenge sent by the recipient + * @param options.callbackUrl (optional) Deprecated parameter used only by browser wallets + * @returns + */ + public async signNep413Message({ + message, + recipient, + nonce, + callbackUrl, + }: { + message: string; + recipient: string; + nonce: Uint8Array; + callbackUrl?: string; + }): Promise { + if (!this.signer) throw new Error("Please set a signer"); + return this.signer.signNep413Message( + message, + this.accountId, + recipient, + nonce, + callbackUrl + ); + } + + /** + * + * @param token The token to check the balance of. Defaults to Native NEAR. + * @returns The available balance of the account in units (e.g. yoctoNEAR). + */ + public async getBalance( + token: NativeToken | FungibleToken = NEAR + ): Promise { + return token.getBalance(this); + } + + /** + * Transfers a token to the specified receiver. + * + * Supports sending either the native NEAR token or any supported Fungible Token (FT). + * + * @param amount - The amount of tokens to transfer in units (e.g. yoctoNEAR). + * @param receiverId - The NEAR account ID of the receiver. + * @param token - The token to transfer. Defaults to Native NEAR. + * + */ + public async transfer( + { + receiverId, + amount, + token = NEAR + }: { + receiverId: string; + amount: bigint | string | number; + token?: NativeToken | FungibleToken; + } + ): Promise { + return token.transfer({ from: this, receiverId, amount }); + } + + + // DEPRECATED FUNCTIONS BELLOW - Please remove in next release + + /** + * @deprecated please use {@link Account.createSignedMetaTransaction} instead + * + * Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance + * + * @param options Options for the transaction. + * @param options.actions Actions to be included in the meta transaction + * @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction + * @param options.receiverId Receiver account of the meta transaction + */ + public async signedDelegate({ + actions, + blockHeightTtl, + receiverId, + }: SignedDelegateOptions): Promise { + const { header } = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, + }); + + if (!this.signer) throw new Error(`Please set a signer`); + + const pk = await this.signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); + + const delegateAction = buildDelegateAction({ + actions, + maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl), + nonce: BigInt(accessKey.nonce) + 1n, + publicKey: pk, + receiverId, + senderId: this.accountId, + }); + + const [, signedDelegate] = await this.signer.signDelegateAction( + delegateAction + ); + + return signedDelegate; + } + + /** + * @deprecated + */ + public getConnection(): Connection { + return new Connection("", this.provider, this.signer); + } + /** @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)]); + 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(); + } } - /** + /** + * @deprecated please use {@link callFunction} instead + * * Execute a function call. * @param options The options for the function call. * @param options.contractId The NEAR account ID of the smart contract. @@ -349,254 +745,540 @@ export class Account implements IntoConnection { * @param options.walletMeta Metadata for wallet integration. * @param options.walletCallbackUrl The callback URL for wallet integration. * @param options.stringify A function to convert input arguments into bytes array - * @param options.jsContract Whether the contract is from JS SDK, automatically encodes args from JS SDK to binary. * @returns {Promise} A promise that resolves to the final execution outcome of the function call. */ - async functionCall({ contractId, methodName, args = {}, gas = DEFAULT_FUNCTION_CALL_GAS, attachedDeposit, walletMeta, walletCallbackUrl, stringify, jsContract }: ChangeFunctionCallOptions): Promise { + async functionCall({ + contractId, + methodName, + args = {}, + gas = DEFAULT_FUNCTION_CALL_GAS, + attachedDeposit, + walletMeta, + walletCallbackUrl, + stringify, + }: 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]; - } + const stringifyArg = + stringify === undefined ? stringifyJsonOrBytes : stringify; + const functionCallArgs = [ + methodName, + args, + gas, + attachedDeposit, + stringifyArg, + false, + ]; - return this.signAndSendTransaction({ - receiverId: jsContract ? this.connection.jsvmAccountId : contractId, + return this.signAndSendTransactionLegacy({ + receiverId: contractId, // eslint-disable-next-line prefer-spread actions: [functionCall.apply(void 0, functionCallArgs)], walletMeta, - walletCallbackUrl + 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 + * @deprecated use instead {@link Provider.viewTransactionStatus} + */ + public async getTransactionStatus( + txHash: string | Uint8Array + ): Promise { + return this.provider.viewTransactionStatus( + txHash, + this.accountId, // accountId is used to determine on which shard to look for a tx + "EXECUTED_OPTIMISTIC" + ); + } + + /** + * @deprecated use ${@link createSignedTransaction} + * 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 "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} + */ + public async signTransaction( + receiverId: string, + actions: Action[], + opts?: { signer: Signer } + ): Promise<[Uint8Array, SignedTransaction]> { + const signer = opts?.signer || this.signer; + + if (!signer) throw new Error(`Please set a signer`); + + const pk = await signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); + + const block = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, + }); + const recentBlockHash = block.header.hash; + + const nonce = BigInt(accessKey.nonce) + 1n; + + const tx = createTransaction( + this.accountId, + pk, + receiverId, + nonce + 1n, + actions, + baseDecode(recentBlockHash) + ); + + return signer.signTransaction(tx); + } + + /** + * @deprecated instead please create a transaction with + * the actions bellow and broadcast it to the network + * 1. createAccount + * 2. transfer some tokens + * 3. deployContract + * 4. (optional) addKey + * 5. (optional) functionCall to an initialization function + * + * Create a new account and deploy a contract to it * @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 + * @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 addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: bigint): 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)] + async createAndDeployContract( + contractId: string, + publicKey: string | PublicKey, + data: Uint8Array, + amount: bigint + ): Promise { + const accessKey = fullAccessKey(); + await this.signAndSendTransactionLegacy({ + receiverId: contractId, + actions: [ + createAccount(), + transfer(amount), + addKey(PublicKey.from(publicKey), accessKey), + deployContract(data), + ], }); + return new Account(contractId, this.provider); } /** - * @param publicKey The public key to be deleted - * @returns {Promise} + * @deprecated please instead use {@link transfer} + * + * @param receiverId NEAR account receiving Ⓝ + * @param amount Amount to send in yoctoⓃ */ - async deleteKey(publicKey: string | PublicKey): Promise { - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [deleteKey(PublicKey.from(publicKey))] + async sendMoney( + receiverId: string, + amount: bigint + ): Promise { + return this.signAndSendTransactionLegacy({ + receiverId, + actions: [transfer(amount)], }); } /** - * @see [https://near-nodes.io/validator/staking-and-delegation](https://near-nodes.io/validator/staking-and-delegation) + * @deprecated please instead use {@link createTopLevelAccount} * - * @param publicKey The public key for the account that's staking - * @param amount The account to stake in yoctoⓃ + * @param newAccountId NEAR account name to be created + * @param publicKey A public key created from the masterAccount */ - async stake(publicKey: string | PublicKey, amount: bigint): Promise { - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: [stake(amount, PublicKey.from(publicKey))] + async createAccount( + newAccountId: string, + publicKey: string | PublicKey, + amount: bigint + ): Promise { + return this.signAndSendTransactionLegacy({ + receiverId: newAccountId, + actions: [ + createAccount(), + transfer(amount), + addKey(PublicKey.from(publicKey), fullAccessKey()), + ], }); } /** - * Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance + * @deprecated please instead use {@link signAndSendTransaction} + * + * Sign a transaction to perform a list of actions and broadcast it using the RPC API. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } + * + * @param options The options for signing and sending the transaction. + * @param options.receiverId The NEAR account ID of the transaction receiver. + * @param options.actions The list of actions to be performed in the transaction. + * @param options.returnError Whether to return an error if the transaction fails. + * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. * - * @param options Options for the transaction. - * @param options.actions Actions to be included in the meta transaction - * @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction - * @param options.receiverId Receiver account of the meta transaction */ - async signedDelegate({ - actions, - blockHeightTtl, - receiverId, - }: SignedDelegateOptions): Promise { - const { provider, signer } = this.connection; - const { header } = await provider.block({ finality: 'final' }); - const { accessKey, publicKey } = await this.findAccessKey(null, null); + async signAndSendTransactionLegacy( + { receiverId, actions, returnError }: SignAndSendTransactionOptions, + opts?: { signer: Signer } + ): Promise { + let txHash, signedTx; - const delegateAction = buildDelegateAction({ - actions, - maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl), - nonce: BigInt(accessKey.nonce) + 1n, - publicKey, - receiverId, - senderId: this.accountId, - }); + // 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; - const { signedDelegateAction } = await signDelegateAction({ - delegateAction, - signer: { - sign: async (message) => { - const { signature } = await signer.signMessage( - message, - delegateAction.senderId, - this.connection.networkId - ); - - return signature; - }, + // 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, + opts + ); + + try { + return await this.provider.sendTransaction(signedTx); + } catch (error) { + if (error.type === "InvalidNonce") { + Logger.warn( + `Retrying transaction ${receiverId}:${baseEncode( + txHash + )} with new nonce.` + ); + return null; + } + if (error.type === "Expired") { + Logger.warn( + `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, }); - return signedDelegateAction; + // 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 */ - private validateArgs(args: any) { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; - if (isUint8Array) { - return; + accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; + + /** + * @deprecated, accounts will no longer handle keystores + * + * Finds the {@link AccessKeyView} associated with the accounts {@link PublicKey} stored in the {@link "@near-js/keystores".keystore.KeyStore | Keystore}. + * + * @param receiverId currently unused + * @param actions currently unused + * @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 }> { + if (!this.signer) throw new Error(`Please set a signer`); + + const publicKey = await this.signer.getPublicKey(); + if (!publicKey) { + throw new TypedError( + `no matching key pair found in ${this.signer.constructor.name}`, + "PublicKeyNotFound" + ); } - if (Array.isArray(args) || typeof args !== 'object') { - throw new PositionalArgsError(); + const cachedAccessKey = + this.accessKeyByPublicKeyCache[publicKey.toString()]; + if (cachedAccessKey !== undefined) { + return { publicKey, accessKey: cachedAccessKey }; + } + + try { + const rawAccessKey = await this.provider.query({ + request_type: "view_access_key", + account_id: this.accountId, + public_key: publicKey.toString(), + finality: DEFAULT_FINALITY, + }); + + // store nonce as BigInt to preserve precision on big number + const accessKey = { + ...rawAccessKey, + nonce: BigInt(rawAccessKey.nonce || 0), + }; + // 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; } } + /** + * @deprecated please use {@link addFullAccessKey} or {@link addFunctionAccessKey} + * + * @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?: bigint + ): Promise { + if (!methodNames) { + methodNames = []; + } + if (!Array.isArray(methodNames)) { + methodNames = [methodNames]; + } + let accessKey; + if (!contractId) { + accessKey = fullAccessKey(); + } else { + accessKey = functionCallAccessKey(contractId, methodNames, amount); + } + return this.signAndSendTransactionLegacy({ + receiverId: this.accountId, + actions: [addKey(PublicKey.from(publicKey), accessKey)], + }); + } + /** * 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) * + * @deprecated please use {@link Provider.callFunction} instead + * * @param options Function call options. * @param options.contractId NEAR account where the contract is deployed * @param options.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code * @param options.args Any arguments to the view contract method, wrapped in JSON * @param options.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 options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param options.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. - * @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). + * @param options.blockQuery specifies which block to query state at. By default returns last DEFAULT_FINALITY block (i.e. not necessarily finalized). * @returns {Promise} */ - async viewFunction(options: ViewFunctionCallOptions): Promise { - return await viewFunction(this.connection, options); + return await viewFunction(this.getConnection(), options); } /** + * @deprecated please use {@link getContractState} instead + * * 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). + * @param blockQuery specifies which block to query state at. By default returns last DEFAULT_FINALITY block (i.e. not necessarily finalized). */ - async viewState(prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' }): Promise> { - return await viewState(this.connection, this.accountId, prefix, blockQuery); + async viewState( + prefix: string | Uint8Array, + blockQuery: BlockReference = { finality: DEFAULT_FINALITY } + ): Promise> { + return await viewState( + this.getConnection(), + this.accountId, + prefix, + blockQuery + ); } /** * 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) + * + * @deprecated */ async getAccessKeys(): Promise { - const response = await this.connection.provider.query({ - request_type: 'view_access_key_list', + const response = await this.provider.query({ + request_type: "view_access_key_list", account_id: this.accountId, - finality: 'optimistic' + finality: DEFAULT_FINALITY, }); // Replace raw nonce into a new BigInt - return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: BigInt(key.access_key.nonce) } })); + return response?.keys?.map((key) => ({ + ...key, + access_key: { + ...key.access_key, + nonce: BigInt(key.access_key.nonce), + }, + })); } /** + * @deprecated + * * 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[] }> { + 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); + .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 + * @deprecated please use {@link getState} instead + * + * 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.provider.query({ + request_type: 'view_account', + account_id: this.accountId, + finality: 'optimistic' + }); + } + + /** + * @deprecated please use {@link getState} instead + * */ async getAccountBalance(): Promise { - const protocolConfig = await this.connection.provider.experimental_protocolConfig({ finality: 'final' }); + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: DEFAULT_FINALITY, + }); const state = await this.state(); - const costPerByte = BigInt(protocolConfig.runtime_config.storage_amount_per_byte); + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); const stateStaked = BigInt(state.storage_usage) * costPerByte; const staked = BigInt(state.locked); const totalBalance = BigInt(state.amount) + staked; - const availableBalance = totalBalance - (staked > stateStaked ? staked : stateStaked); + const availableBalance = + totalBalance - (staked > stateStaked ? staked : stateStaked); return { total: totalBalance.toString(), stateStaked: stateStaked.toString(), staked: staked.toString(), - available: availableBalance.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. - * + * + * @deprecated + * * 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 block = await this.provider.block({ + finality: DEFAULT_FINALITY, + }); 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 { current_validators, next_validators, current_proposals } = + await this.provider.validators(epochId); const pools: Set = new Set(); - [...current_validators, ...next_validators, ...current_proposals] - .forEach((validator) => pools.add(validator.account_id)); + [ + ...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 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') { + if ( + result.status === "rejected" && + result.reason.type === "TimeoutError" + ) { return true; } return false; @@ -604,29 +1286,37 @@ export class Account implements IntoConnection { // When RPC is down and return timeout error, throw error if (hasTimeoutError) { - throw new Error('Failed to get delegated stake balance'); + 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 = BigInt(state.value); - if (currentBN !== 0n) { + const summary = results.reduce( + (result, state, index) => { + const validatorId = uniquePools[index]; + if (state.status === "fulfilled") { + const currentBN = BigInt(state.value); + if (currentBN !== 0n) { + return { + ...result, + stakedValidators: [ + ...result.stakedValidators, + { validatorId, amount: currentBN.toString() }, + ], + total: result.total + currentBN, + }; + } + } + if (state.status === "rejected") { return { ...result, - stakedValidators: [...result.stakedValidators, { validatorId, amount: currentBN.toString() }], - total: result.total + currentBN, + failedValidators: [ + ...result.failedValidators, + { validatorId, error: state.reason }, + ], }; } - } - if (state.status === 'rejected') { - return { - ...result, - failedValidators: [...result.failedValidators, { validatorId, error: state.reason }], - }; - } - return result; - }, - { stakedValidators: [], failedValidators: [], total: 0n }); + return result; + }, + { stakedValidators: [], failedValidators: [], total: 0n } + ); return { ...summary, diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts deleted file mode 100644 index 6edc03a3c0..0000000000 --- a/packages/accounts/src/account_2fa.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { PublicKey } from '@near-js/crypto'; -import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; -import { actionCreators } from '@near-js/transactions'; -import { Logger } from '@near-js/utils' - -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'; - -const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; - -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 "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} - * - * @param options Options for the transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - 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 - - /** - * Deploy a multisig contract with 2FA and handle the deployment process. - * @param contractBytes - The bytecode of the multisig contract. - * @returns {Promise} A promise that resolves to the final execution outcome of the deployment. - */ - async deployMultisig(contractBytes: Uint8Array) { - const { accountId } = this; - - const seedOrLedgerKey = (await this.getRecoveryMethods()).data - // @ts-ignore - .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); - - // @ts-ignore - 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)); - Logger.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'); - } - } - - /** - * Disable 2FA with the option to clean up contract state. - * @param options Options for disabling 2FA. - * @param options.contractBytes The bytecode of the contract to deploy. - * @param options.cleanupContractBytes The bytecode of the cleanup contract (optional). - * @returns {Promise} A promise that resolves to the final execution outcome of the operation. - */ - 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); - } - - /** - * Retrieves cleanup actions for disabling 2FA. - * @param cleanupContractBytes - The bytecode of the cleanup contract. - * @returns {Promise} - A promise that resolves to an array of cleanup 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, 0n) - ] : []; - } - - /** - * Retrieves key conversion actions for disabling 2FA. - * @returns {Promise} - A promise that resolves to an array of key conversion actions. - */ - 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'); - }); - // @ts-ignore - 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-manipulation/res/state_cleanup.wasm?raw=true} - * @returns {Promise} A promise that resolves to the final execution outcome of the operation. - */ - 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), - ]; - Logger.log('disabling 2fa for', this.accountId); - return await this.signAndSendTransaction({ - receiverId: this.accountId, - actions - }); - } - - /** - * Default implementation for sending the 2FA code. - * @returns {Promise} - A promise that resolves to the request ID. - */ - 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".'); - } - - /** - * Prompts the user to enter and verify the 2FA code. - * @returns {Promise} - A promise that resolves to the verification result. - */ - 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) { - Logger.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; - } - } - - /** - * Verify the 2FA code using the default method. - * @param securityCode - The security code to verify. - * @returns {Promise} A promise that resolves to the verification result. - */ - 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 - }); - } - - /** - * Retrieves recovery methods for the account. - * @returns {Promise<{ accountId: string, data: any }>} - A promise that resolves to recovery methods data. - */ - async getRecoveryMethods() { - const { accountId } = this; - return { - accountId, - data: await this.postSignedJson('/account/recoveryMethods', { accountId }) - }; - } - - /** - * Gets the 2FA method (kind and detail). - * @returns {Promise<{ kind: string, detail: string }>} A promise that resolves to the 2FA method. - */ - async get2faMethod() { - let { data } = await this.getRecoveryMethods(); - // @ts-ignore - if (data && data.length) { - // @ts-ignore - data = data.find((m) => m.kind.indexOf('2fa-') === 0); - } - if (!data) return null; - // @ts-ignore - const { kind, detail } = data; - return { kind, detail }; - } - - /** - * Generates a signature for the latest finalized block. - * @returns {Promise<{ blockNumber: string, blockNumberSignature: string }>} - A promise that resolves to the signature information. - */ - 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 }; - } - - /** - * Sends a signed JSON request to a specified path. - * @param path - The path for the request. - * @param body - The request body. - * @returns {Promise} - A promise that resolves to the response from the helper. - */ - async postSignedJson(path, body) { - return await fetch(this.helperUrl + path, { - body: JSON.stringify({ - ...body, - ...(await this.signatureFor()), - }), - method: 'POST', - }); - } -} - -// helpers -const toPK = (pk) => PublicKey.from(pk); diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts deleted file mode 100644 index a7d92a5486..0000000000 --- a/packages/accounts/src/account_multisig.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { Action, actionCreators } from '@near-js/transactions'; -import { FinalExecutionOutcome } from '@near-js/types'; -import { Logger } from '@near-js/utils'; - -import { Account, SignAndSendTransactionOptions } from './account'; -import { Connection } from './connection'; -import { - MULTISIG_ALLOWANCE, - MULTISIG_CHANGE_METHODS, - MULTISIG_DEPOSIT, - MULTISIG_GAS, - MULTISIG_STORAGE_KEY, -} from './constants'; -import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types'; - -const { deployContract, functionCall } = actionCreators; - -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; - - /** - * Constructs an instance of the `AccountMultisig` class. - * @param connection The NEAR connection object. - * @param accountId The NEAR account ID. - * @param options Additional options for the multisig account. - * @param options.storage Storage to store data related to multisig operations. - * @param options.onAddRequestResult Callback function to handle the result of adding a request. - */ - constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId); - this.storage = options.storage; - this.onAddRequestResult = options.onAddRequestResult; - } - - /** - * Sign and send a transaction with the multisig account as the sender. - * @param receiverId - The NEAR account ID of the transaction receiver. - * @param actions - The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - async signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise { - return super.signAndSendTransaction({ receiverId, actions }); - } - - /** - * Sign and send a multisig transaction to add a request and confirm it. - * @param options Options for the multisig transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - 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. - * @param contractBytes The bytecode of the multisig contract. - * @returns {Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }>} A promise that resolves to the status of the code and state. - */ - 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; - } - } - - /** - * Delete a multisig request by its ID. - * @param request_id The ID of the multisig request to be deleted. - * @returns {Promise} A promise that resolves to the final execution outcome of the deletion. - */ - deleteRequest(request_id) { - return super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }); - } - - /** - * Delete all multisig requests associated with the account. - * @returns {Promise} A promise that resolves when all requests are deleted. - */ - async deleteAllRequests() { - const request_ids = await this.getRequestIds(); - if(request_ids.length) { - await Promise.all(request_ids.map((id) => this.deleteRequest(id))); - } - } - - /** - * Delete unconfirmed multisig requests associated with the account. - * @returns {Promise} A promise that resolves when unconfirmed requests are deleted. - */ - 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) { - Logger.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; - } -} - -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 index dc9a82f58d..1f2d598c51 100644 --- a/packages/accounts/src/connection.ts +++ b/packages/accounts/src/connection.ts @@ -1,6 +1,11 @@ -import { Signer, InMemorySigner } from '@near-js/signers'; -import { Provider, JsonRpcProvider, FailoverRpcProvider } from '@near-js/providers'; -import { IntoConnection } from './interface'; +import { Signer, KeyPairSigner } from "@near-js/signers"; +import { + Provider, + JsonRpcProvider, + FailoverRpcProvider, +} from "@near-js/providers"; +import { IntoConnection } from "./interface"; +import depd from 'depd'; /** * @param config Contains connection info details @@ -10,12 +15,16 @@ function getProvider(config: any): Provider { switch (config.type) { case undefined: return config; - case 'JsonRpcProvider': return new JsonRpcProvider({ ...config.args }); - case 'FailoverRpcProvider': { - const providers = (config?.args || []).map((arg) => new JsonRpcProvider(arg)); + case "JsonRpcProvider": + return new JsonRpcProvider({ ...config.args }); + case "FailoverRpcProvider": { + const providers = (config?.args || []).map( + (arg) => new JsonRpcProvider(arg) + ); return new FailoverRpcProvider(providers); } - default: throw new Error(`Unknown provider type ${config.type}`); + default: + throw new Error(`Unknown provider type ${config.type}`); } } @@ -27,27 +36,35 @@ function getSigner(config: any): Signer { switch (config.type) { case undefined: return config; - case 'InMemorySigner': { - return new InMemorySigner(config.keyStore); + case "KeyPairSigner": { + return new KeyPairSigner(config.keyPair); } - default: throw new Error(`Unknown signer type ${config.type}`); + default: + throw new Error(`Unknown signer type ${config.type}`); } } /** + * @deprecated + * * Connects an account to a given network via a given provider */ export class Connection implements IntoConnection { readonly networkId: string; readonly provider: Provider; readonly signer: Signer; - readonly jsvmAccountId: string; - constructor(networkId: string, provider: Provider, signer: Signer, jsvmAccountId: string) { + constructor( + networkId: string, + provider: Provider, + signer: Signer, + ) { + const deprecate = depd('new Connection(networkId, provider, signer)'); + deprecate('`Connection` is no longer used anywhere, please switch to constructing `Account` without it - use `new Account(accountId, provider, signer)`'); + this.networkId = networkId; this.provider = provider; this.signer = signer; - this.jsvmAccountId = jsvmAccountId; } getConnection(): Connection { @@ -60,6 +77,10 @@ export class Connection implements IntoConnection { static fromConfig(config: any): Connection { const provider = getProvider(config.provider); const signer = getSigner(config.signer); - return new Connection(config.networkId, provider, signer, config.jsvmAccountId); + return new Connection( + config.networkId, + provider, + signer, + ); } } diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index f8fe945e67..5b1b456e34 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -4,13 +4,11 @@ export { AccountAuthorizedApp, SignAndSendTransactionOptions } from './account'; -export { Account2FA } from './account_2fa'; export { AccountCreator, LocalAccountCreator, UrlAccountCreator, } from './account_creator'; -export { AccountMultisig } from './account_multisig'; export { Connection } from './connection'; export { MULTISIG_STORAGE_KEY, diff --git a/packages/accounts/src/interface.ts b/packages/accounts/src/interface.ts index bc69771793..0c8b222d35 100644 --- a/packages/accounts/src/interface.ts +++ b/packages/accounts/src/interface.ts @@ -26,10 +26,6 @@ export interface FunctionCallOptions { * 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 { diff --git a/packages/accounts/src/local-view-execution/index.ts b/packages/accounts/src/local-view-execution/index.ts index 2f62c87320..9f8bb1e2eb 100644 --- a/packages/accounts/src/local-view-execution/index.ts +++ b/packages/accounts/src/local-view-execution/index.ts @@ -1,4 +1,4 @@ -import { BlockReference, ContractCodeView } from '@near-js/types'; +import { BlockReference, ContractCodeViewRaw } from '@near-js/types'; import { printTxOutcomeLogs } from '@near-js/utils'; import { FunctionCallOptions } from '../interface'; import { Storage } from './storage'; @@ -22,7 +22,7 @@ export class LocalViewExecution { } private async fetchContractCode(contractId: string, blockQuery: BlockReference) { - const result = await this.connection.provider.query({ + const result = await this.connection.provider.query({ request_type: 'view_code', account_id: contractId, ...blockQuery, diff --git a/packages/accounts/src/utils.ts b/packages/accounts/src/utils.ts index 6a0bdca49b..a5565760b9 100644 --- a/packages/accounts/src/utils.ts +++ b/packages/accounts/src/utils.ts @@ -3,10 +3,10 @@ import { BlockReference, CodeResult, PositionalArgsError, -} from '@near-js/types'; -import { Connection } from './connection'; -import { printTxOutcomeLogs } from '@near-js/utils'; -import { ViewFunctionCallOptions } from './interface'; +} from "@near-js/types"; +import { Connection } from "./connection"; +import { printTxOutcomeLogs } from "@near-js/utils"; +import { ViewFunctionCallOptions } from "./interface"; function parseJsonFromRawResponse(response: Uint8Array): any { return JSON.parse(Buffer.from(response).toString()); @@ -17,20 +17,17 @@ function bytesJsonStringify(input: any): Buffer { } export function validateArgs(args: any) { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; + const isUint8Array = + args.byteLength !== undefined && args.byteLength === args.length; if (isUint8Array) { return; } - if (Array.isArray(args) || typeof args !== 'object') { + if (Array.isArray(args) || typeof args !== "object") { throw new PositionalArgsError(); } } -export function encodeJSContractArgs(contractId: string, method: string, args) { - return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]); -} - /** * Returns the state (key value pairs) of account's contract based on the key prefix. * Pass an empty string for prefix if you would like to return the entire state. @@ -41,21 +38,25 @@ export function encodeJSContractArgs(contractId: string, method: string, args) { * @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). */ -export async function viewState(connection: Connection, accountId: string, prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' }): Promise> { +export async function viewState( + connection: Connection, + accountId: string, + prefix: string | Uint8Array, + blockQuery: BlockReference = { finality: "optimistic" } +): Promise> { const { values } = await connection.provider.query({ - request_type: 'view_state', + request_type: "view_state", ...blockQuery, account_id: accountId, - prefix_base64: Buffer.from(prefix).toString('base64') + prefix_base64: Buffer.from(prefix).toString("base64"), }); return values.map(({ key, value }) => ({ - key: Buffer.from(key, 'base64'), - value: Buffer.from(value, 'base64') + key: Buffer.from(key, "base64"), + value: Buffer.from(value, "base64"), })); } - /** * 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) @@ -66,40 +67,39 @@ export async function viewState(connection: Connection, accountId: string, prefi * @param options.args Any arguments to the view contract method, wrapped in JSON * @param options.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 options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param options.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. * @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). * @returns {Promise} */ -export async function viewFunction(connection: Connection, { - contractId, - methodName, - args = {}, - parse = parseJsonFromRawResponse, - stringify = bytesJsonStringify, - jsContract = false, - blockQuery = { finality: 'optimistic' } -}: ViewFunctionCallOptions): Promise { - let encodedArgs; - +export async function viewFunction( + connection: Connection, + { + contractId, + methodName, + args = {}, + parse = parseJsonFromRawResponse, + stringify = bytesJsonStringify, + blockQuery = { finality: "optimistic" }, + }: ViewFunctionCallOptions +): Promise { validateArgs(args); - if (jsContract) { - encodedArgs = encodeJSContractArgs(contractId, methodName, Object.keys(args).length > 0 ? JSON.stringify(args) : ''); - } else { - encodedArgs = stringify(args); - } + const encodedArgs = stringify(args); const result = await connection.provider.query({ - request_type: 'call_function', + request_type: "call_function", ...blockQuery, - account_id: jsContract ? connection.jsvmAccountId : contractId, - method_name: jsContract ? 'view_js_contract' : methodName, - args_base64: encodedArgs.toString('base64') + account_id: contractId, + method_name: 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)); -} \ No newline at end of file + return ( + result.result && + result.result.length > 0 && + parse(Buffer.from(result.result)) + ); +} diff --git a/packages/accounts/test/account.access_key.test.ts b/packages/accounts/test/account.access_key.test.ts index cbf801d866..89cfec07d8 100644 --- a/packages/accounts/test/account.access_key.test.ts +++ b/packages/accounts/test/account.access_key.test.ts @@ -1,10 +1,14 @@ -import { beforeAll, beforeEach, expect, jest, test } from '@jest/globals'; +import { afterAll, beforeAll, beforeEach, expect, jest, test } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; -import { createAccount, deployContract, generateUniqueString, networkId, setUpTestConnection } from './test-utils'; +import { createAccount, deployContract, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Account } from '../src'; +import { KeyPairSigner } from '@near-js/signers'; + +import { Worker } from 'near-workspaces'; let nearjs; -let workingAccount; +let workingAccount: Account; let contractId; let contract; @@ -14,9 +18,16 @@ beforeAll(async () => { nearjs = await setUpTestConnection(); }); +afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + beforeEach(async () => { try { - contractId = generateUniqueString('test'); workingAccount = await createAccount(nearjs); contract = await deployContract(nearjs.accountCreator.masterAccount, contractId); @@ -27,36 +38,42 @@ beforeEach(async () => { test('make function call using access key', async() => { const keyPair = KeyPair.fromRandom('ed25519'); - await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', '2000000000000000000000000'); + await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', 2000000000000000000000000n); - // Override in the key store the workingAccount key to the given access key. - await nearjs.connection.signer.keyStore.setKey(networkId, workingAccount.accountId, keyPair); const setCallValue = generateUniqueString('setCallPrefix'); - await contract.setValue({ args: { value: setCallValue } }); + await contract.setValue({ + // Override signer in the workingAccount to the given access key. + signerAccount: new Account(workingAccount.accountId, workingAccount.provider, new KeyPairSigner(keyPair)), + args: { value: setCallValue }, + }); expect(await contract.getValue()).toEqual(setCallValue); }); test('remove access key no longer works', async() => { + const near = await setUpTestConnection(); + const keyPair = KeyPair.fromRandom('ed25519'); const publicKey = keyPair.getPublicKey(); - await nearjs.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000); - await nearjs.accountCreator.masterAccount.deleteKey(publicKey); - // Override in the key store the workingAccount key to the given access key. - await nearjs.connection.signer.keyStore.setKey(networkId, nearjs.accountCreator.masterAccount.accountId, keyPair); + await near.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000n); + await near.accountCreator.masterAccount.deleteKey(publicKey); + // Override account in the Contract to the masterAccount with the given access key. + contract.account = new Account(near.accountCreator.masterAccount.accountId, near.accountCreator.masterAccount.provider, new KeyPairSigner(keyPair)); + let failed = true; try { await contract.setValue({ args: { value: 'test' } }); failed = false; } catch (e) { - expect(e.message).toEqual(`Can not sign transactions for account ${nearjs.accountCreator.masterAccount.accountId} on network ${networkId}, no matching key pair exists for this account`); - expect(e.type).toEqual('KeyNotFound'); + expect(e.message).toEqual(`Can't complete the action because access key ${keyPair.getPublicKey().toString()} doesn't exist`); + expect(e.type).toEqual('AccessKeyDoesNotExist'); } if (!failed) { throw new Error('should throw an error'); } - nearjs = await setUpTestConnection(); + const worker = near.worker as Worker; + await worker.tearDown(); }); test('view account details after adding access keys', async() => { @@ -94,22 +111,5 @@ test('loading account after adding a full key', async() => { 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'); + 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(networkId, nearjs.accountCreator.masterAccount.accountId, ''); - let failed = true; - try { - await contract.setValue({ args: { value: 'test' } }); - failed = false; - } catch (e) { - expect(e.message).toEqual(`no matching key pair found in ${nearjs.connection.signer}`); - expect(e.type).toEqual('PublicKeyNotFound'); - } - - if (!failed) { - throw new Error('should throw an error'); - } -}); \ No newline at end of file diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index d8ffd908d1..f40ef8ab8f 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; -import { KeyType } from '@near-js/crypto'; +import { KeyPair, KeyType } from '@near-js/crypto'; import { getTransactionLastResult, Logger } from '@near-js/utils'; import { actionCreators } from '@near-js/transactions'; import { BlockResult, TypedError } from '@near-js/types'; @@ -7,9 +7,12 @@ import * as fs from 'fs'; import { Account, Contract } from '../src'; import { createAccount, generateUniqueString, HELLO_WASM_PATH, HELLO_WASM_BALANCE, networkId, setUpTestConnection } from './test-utils'; +import { InMemoryKeyStore } from '@near-js/keystores'; + +import { Worker } from 'near-workspaces'; let nearjs; -let workingAccount; +let workingAccount: Account; jest.setTimeout(50000); @@ -20,86 +23,92 @@ beforeAll(async () => { afterAll(async () => { await workingAccount.deleteAccount(workingAccount.accountId); + + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); }); test('view pre-defined account works and returns correct name', async () => { - const status = await workingAccount.state(); - expect(status.code_hash).toEqual('11111111111111111111111111111111'); + const status = await workingAccount.getState(); + expect(status.codeHash).toEqual('11111111111111111111111111111111'); }); test('create account and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE'; - const { amount } = await workingAccount.state(); - const newAmount = BigInt(amount) / 10n; + const { balance: { total } } = await workingAccount.getState(); + const newAmount = total / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); - const newAccount = new Account(nearjs.connection, newAccountName); - const state = await newAccount.state(); - expect(state.amount).toEqual(newAmount.toString()); + const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); + const state = await newAccount.getState(); + expect(state.balance.total.toString()).toEqual(newAmount.toString()); }); test('create account with a secp256k1 key and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = 'secp256k1:45KcWwYt6MYRnnWFSxyQVkuu9suAzxoSkUMEnFNBi9kDayTo5YPUaqMWUrf7YHUDNMMj3w75vKuvfAMgfiFXBy28'; - const { amount } = await workingAccount.state(); - const newAmount = BigInt(amount) / 10n; + const { balance: { total } } = await workingAccount.getState(); + const newAmount = total / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); - const newAccount = new Account(nearjs.connection, newAccountName); - const state = await newAccount.state(); - expect(state.amount).toEqual(newAmount.toString()); + const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); + const state = await newAccount.getState(); + expect(state.balance.total.toString()).toEqual(newAmount.toString()); }); -test('Secp256k1 send money', async() => { +test('Secp256k1 send money', async () => { const sender = await createAccount(nearjs, KeyType.SECP256K1); const receiver = await createAccount(nearjs, KeyType.SECP256K1); - const { amount: receiverAmount } = await receiver.state(); + const { balance: { total } } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); - const state = await receiver.state(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + const state = await receiver.getState(); + expect(state.balance.total).toEqual(total + 10000n); }); -test('send money', async() => { +test('send money', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.state(); + const { balance: { total } } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); - const state = await receiver.state(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + const state = await receiver.getState(); + expect(state.balance.total).toEqual(total + 10000n); }); -test('send money through signAndSendTransaction', async() => { +test('send money through signAndSendTransaction', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.state(); + const { balance: { total } } = await receiver.getState(); await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [actionCreators.transfer(10000n)], }); - const state = await receiver.state(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + const state = await receiver.getState(); + expect(state.balance.total).toEqual(total + 10000n); }); -test('delete account', async() => { +test('delete account', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); await sender.deleteAccount(receiver.accountId); // @ts-expect-error test input const reloaded = new Account(sender.connection, sender); - await expect(reloaded.state()).rejects.toThrow(); + await expect(reloaded.getState()).rejects.toThrow(); }); test('multiple parallel transactions', async () => { const PARALLEL_NUMBER = 5; // @ts-expect-error test input await Promise.all(new Array(PARALLEL_NUMBER).fill().map(async (_, i) => { - const account = new Account(workingAccount.connection, workingAccount.accountId); + const account = new Account(workingAccount.accountId, workingAccount.provider, workingAccount.getSigner()); // 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, BigInt(i)); })); }); -test('findAccessKey returns the same access key when fetched simultaneously', async() => { +test('findAccessKey returns the same access key when fetched simultaneously', async () => { const account = await createAccount(nearjs); const [key1, key2] = await Promise.all([ @@ -120,8 +129,8 @@ describe('errors', () => { log: (...args) => { logs.push(args.join(' ')); }, - warn: () => {}, - error: () => {}, + warn: () => { }, + error: () => { }, }; Logger.overrideLogger(custom); @@ -129,11 +138,10 @@ describe('errors', () => { beforeEach(async () => { logs = []; - }); - test('create existing account', async() => { - await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100)) + test('create existing account', async () => { + await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100n)) .rejects.toThrow(/Can't create a new account .+, because it already exists/); }); }); @@ -144,7 +152,9 @@ describe('with deploy contract', () => { let contract; beforeAll(async () => { - const newPublicKey = await nearjs.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + await (nearjs.keyStore as InMemoryKeyStore).setKey(networkId, contractId, keyPair); + const newPublicKey = keyPair.getPublicKey(); const data = fs.readFileSync(HELLO_WASM_PATH); await nearjs.accountCreator.masterAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); // @ts-expect-error test input @@ -157,8 +167,8 @@ describe('with deploy contract', () => { log: (...args) => { logs.push(args.join(' ')); }, - warn: () => {}, - error: () => {}, + warn: () => { }, + error: () => { }, }; Logger.overrideLogger(custom); @@ -185,7 +195,7 @@ describe('with deploy contract', () => { 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() => { + 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 @@ -206,7 +216,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue); }); - test('view contract state', async() => { + test('view contract state', async () => { const setCallValue = generateUniqueString('setCallPrefix'); await workingAccount.functionCall({ contractId, @@ -214,22 +224,22 @@ describe('with deploy contract', () => { args: { value: setCallValue } }); - const contractAccount = new Account(nearjs.connection, contractId); + const contractAccount = new Account(contractId, nearjs.connection.provider, nearjs.connection.signer); 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() => { + 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 + 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() => { + test('make function calls via contract', async () => { const result = await contract.hello({ name: 'trex' }); expect(result).toEqual('hello trex'); @@ -239,7 +249,7 @@ describe('with deploy contract', () => { expect(await contract.getValue()).toEqual(setCallValue); }); - test('view function calls by block Id and finality', async() => { + test('view function calls by block Id and finality', async () => { const setCallValue1 = generateUniqueString('setCallPrefix'); const result1 = await contract.setValue({ args: { value: setCallValue1 } }); expect(result1).toEqual(setCallValue1); @@ -256,7 +266,7 @@ describe('with deploy contract', () => { methodName: 'getValue' })).toEqual(setCallValue1); - const block1 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const block1 = await workingAccount.provider.block({ finality: 'optimistic' }); const blockHash1 = block1.header.hash; const blockIndex1 = block1.header.height; @@ -301,7 +311,7 @@ describe('with deploy contract', () => { blockQuery: { blockId: blockIndex1 }, })).toEqual(setCallValue1); - const block2 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const block2 = await workingAccount.provider.block({ finality: 'optimistic' }); const blockHash2 = block2.header.hash; const blockIndex2 = block2.header.height; @@ -318,7 +328,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue2); }); - test('make function calls via contract with gas', async() => { + test('make function calls via contract with gas', async () => { const setCallValue = generateUniqueString('setCallPrefix'); const result2 = await contract.setValue({ args: { value: setCallValue }, @@ -371,7 +381,7 @@ describe('with deploy contract', () => { })).toEqual('hello world'); }); - test('make viewFunction call with object format', async() => { + 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 @@ -380,7 +390,7 @@ describe('with deploy contract', () => { expect(result).toEqual('hello trex'); }); - test('get total stake balance and validator responses', async() => { + 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, @@ -396,7 +406,7 @@ describe('with deploy contract', () => { num_produced_blocks: 7, num_produced_chunks: 18, public_key: 'ed25519:5QzHuNZ4stznMwf3xbDfYGUbjVt8w48q8hinDRmVx41z', - shards: [ 1 ], + shards: [1], stake: '73527610191458905577047103204' }, { @@ -407,7 +417,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '74531922534760985104659653178' }, { @@ -418,7 +428,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '0' }, ], @@ -428,7 +438,7 @@ describe('with deploy contract', () => { }, }; - const account = new Account(mockConnection, 'test.near'); + const account = new Account('test.near', mockConnection.provider, mockConnection.signer); // mock internal functions that are being used on getActiveDelegatedStakeBalance account.viewFunction = async ({ methodName, ...args }) => { if (methodName === 'get_account_total_balance') { @@ -441,7 +451,7 @@ describe('with deploy contract', () => { return await account.viewFunction({ methodName, ...args }); } }; - account.connection.provider.block = async () => { + account.provider.block = async () => { return Promise.resolve({ header: { hash: 'dontcare' } } as BlockResult); }; const result = await account.getActiveDelegatedStakeBalance(); @@ -468,7 +478,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '0' }, ], @@ -478,7 +488,7 @@ describe('with deploy contract', () => { }, }; - const account = new Account(mockConnection, 'test.near'); + const account = new Account('test.near', mockConnection.provider, mockConnection.signer); // mock internal functions that are being used on getActiveDelegatedStakeBalance account.viewFunction = async ({ methodName, ...args }) => { if (methodName === 'get_account_total_balance') { @@ -491,13 +501,13 @@ describe('with deploy contract', () => { return await account.viewFunction({ methodName, ...args }); } }; - account.connection.provider.block = async () => { + account.provider.block = async () => { return Promise.resolve({ header: { hash: 'dontcare' } } as BlockResult); }; try { await account.getActiveDelegatedStakeBalance(); - } catch(e) { + } catch (e) { expect(e).toEqual(new Error(ERROR_MESSAGE)); } }); diff --git a/packages/accounts/test/account_multisig.test.ts b/packages/accounts/test/account_multisig.test.ts deleted file mode 100644 index 996a32e5db..0000000000 --- a/packages/accounts/test/account_multisig.test.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { beforeAll, describe, expect, jest, test } from '@jest/globals'; -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 * as fs from 'fs'; -import semver from 'semver'; - -import { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS, MultisigStateStatus } from '../src'; -import { createAccount, setUpTestConnection } from './test-utils'; - -const { functionCall, transfer } = actionCreators; - -let nearjs; -let startFromVersion; - -jest.setTimeout(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: any = 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) - }); - account2fa.checkMultisigCodeAndStateStatus = () => ({ codeStatus: 1, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }); - await account2fa.deployMultisig(fs.readFileSync('./test/wasm/multisig.wasm')); - return account2fa; -}; - -beforeAll(async () => { - nearjs = await setUpTestConnection(); - const 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 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, - // @ts-expect-error test input - ({ 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 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, BigInt(parseNearAmount('0.25'))); - account = await getAccount2FA(account); - const keys = await account.getAccessKeys(); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); - }); - - test('add app key', async() => { - let account = await 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, BigInt(parseNearAmount('0.25'))); - const keys = await account.getAccessKeys(); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); - }); - - test('send money', async() => { - let sender = await createAccount(nearjs); - let receiver = await createAccount(nearjs); - sender = await getAccount2FA(sender); - receiver = await getAccount2FA(receiver); - const { amount: receiverAmount } = await receiver.state(); - await sender.sendMoney(receiver.accountId, BigInt(parseNearAmount('1'))); - const state = await receiver.state(); - expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount)+ BigInt(parseNearAmount('0.9').toString())); - }); - - test('send money through signAndSendTransaction', async() => { - let sender = await createAccount(nearjs); - let receiver = await createAccount(nearjs); - sender = await getAccount2FA(sender); - receiver = await getAccount2FA(receiver); - const { amount: receiverAmount } = await receiver.state(); - await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [transfer(BigInt(parseNearAmount('1')))] }); - const state = await receiver.state(); - expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount) + BigInt(parseNearAmount('0.9').toString())); - }); - -}); diff --git a/packages/accounts/test/config.js b/packages/accounts/test/config.js index f1ecd95bdc..e77cbbae08 100644 --- a/packages/accounts/test/config.js +++ b/packages/accounts/test/config.js @@ -1,7 +1,6 @@ import { Worker } from 'near-workspaces'; import fs from 'fs'; -let worker; module.exports = async function getConfig(env) { switch (env) { case 'production': @@ -37,14 +36,15 @@ module.exports = async function getConfig(env) { }; case 'test': case 'ci': { - if (!worker) worker = await Worker.init(); + const worker = await Worker.init(); const keyFile = fs.readFileSync(`${worker.rootAccount.manager.config.homeDir}/validator_key.json`); const keyPair = JSON.parse(keyFile.toString()); return { networkId: worker.config.network, nodeUrl: worker.manager.config.rpcAddr, masterAccount: worker.rootAccount._accountId, - secretKey: keyPair.secret_key || keyPair.private_key + secretKey: keyPair.secret_key || keyPair.private_key, + worker: worker }; } default: diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index bcee4ad006..32054178b8 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -1,15 +1,17 @@ -import { afterEach, beforeAll, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, afterEach, beforeAll, describe, expect, jest, test } from '@jest/globals'; import { PositionalArgsError } from '@near-js/types'; import { Contract, Account } from '../src'; import { deployContractGuestBook, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Worker } from 'near-workspaces'; + const account = Object.setPrototypeOf({ getConnection() { return {}; }, - viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery }) { - return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + viewFunction({ contractId, methodName, args, parse, stringify, blockQuery }) { + return { this: this, contractId, methodName, args, parse, stringify, blockQuery }; }, functionCall() { return this; @@ -122,11 +124,19 @@ describe('local view execution', () => { await contract.add_message({ text: 'first message' }); await contract.add_message({ text: 'second message' }); - const block = await contract.account.connection.provider.block({ finality: 'optimistic' }); + const block = await contract.connection.provider.block({ finality: 'optimistic' }); - contract.account.connection.provider.query = jest.fn(contract.account.connection.provider.query); + contract.connection.provider.query = jest.fn(contract.connection.provider.query); blockQuery = { blockId: block.header.height }; }); + + afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); + }); afterEach(() => { jest.clearAllMocks(); @@ -135,7 +145,7 @@ describe('local view execution', () => { test('calls total_messages() function using RPC provider', async () => { const totalMessages = await contract.total_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).toHaveBeenCalledWith({ + expect(contract.connection.provider.query).toHaveBeenCalledWith({ request_type: 'view_code', account_id: contract.contractId, ...blockQuery, @@ -146,14 +156,14 @@ describe('local view execution', () => { test('calls total_messages() function using cache data', async () => { const totalMessages = await contract.total_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).not.toHaveBeenCalled(); + expect(contract.connection.provider.query).not.toHaveBeenCalled(); expect(totalMessages).toBe(2); }); test('calls get_messages() function using cache data', async () => { const messages = await contract.get_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).not.toHaveBeenCalled(); + expect(contract.connection.provider.query).not.toHaveBeenCalled(); expect(messages.length).toBe(2); expect(messages[0].text).toEqual('first message'); expect(messages[1].text).toEqual('second message'); @@ -161,7 +171,7 @@ describe('local view execution', () => { test('local execution fails and fallbacks to normal RPC call', async () => { // @ts-expect-error test input - const _contract: any = new Contract(contract.account, contract.contractId, { viewMethods: ['get_msg'], useLocalViewExecution: true }); + const _contract: any = new Contract(nearjs.accountCreator.masterAccount, contract.contractId, { viewMethods: ['get_msg'], useLocalViewExecution: true }); _contract.account.viewFunction = jest.fn(_contract.account.viewFunction); try { @@ -186,7 +196,10 @@ describe('contract without account', () => { beforeAll(async () => { nearjs = await setUpTestConnection(); const contractId = generateUniqueString('guestbook'); - await deployContractGuestBook(nearjs.accountCreator.masterAccount, contractId); + await deployContractGuestBook( + nearjs.accountCreator.masterAccount, + contractId + ); // @ts-expect-error test input contract = new Contract(nearjs.connection, contractId, { @@ -195,6 +208,14 @@ describe('contract without account', () => { }); }); + afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); + }); + test('view & change methods work', async () => { const totalMessagesBefore = await contract.total_messages({}); expect(totalMessagesBefore).toBe(0); @@ -203,13 +224,13 @@ describe('contract without account', () => { signerAccount: nearjs.accountCreator.masterAccount, args: { text: 'first message', - } + }, }); await contract.add_message({ signerAccount: nearjs.accountCreator.masterAccount, args: { text: 'second message', - } + }, }); const totalMessagesAfter = await contract.total_messages({}); @@ -223,7 +244,11 @@ describe('contract without account', () => { test('fails to call add_message() without signerAccount', async () => { await expect( - contract.add_message({ text: 'third message' }) + contract.add_message({ + args: { + text: 'third message', + }, + }) ).rejects.toThrow(/signerAccount must be specified/); }); }); diff --git a/packages/accounts/test/contract_abi.test.ts b/packages/accounts/test/contract_abi.test.ts index e172795375..e0f070a3ef 100644 --- a/packages/accounts/test/contract_abi.test.ts +++ b/packages/accounts/test/contract_abi.test.ts @@ -97,8 +97,8 @@ const account = Object.setPrototypeOf({ getConnection() { return {}; }, - viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery }) { - return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + viewFunction({ contractId, methodName, args, parse, stringify, blockQuery }) { + return { this: this, contractId, methodName, args, parse, stringify, blockQuery }; }, functionCall() { return this; diff --git a/packages/accounts/test/promise.test.ts b/packages/accounts/test/promise.test.ts index 8b90e305d0..e95992090d 100644 --- a/packages/accounts/test/promise.test.ts +++ b/packages/accounts/test/promise.test.ts @@ -1,5 +1,6 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; import { deployContract, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Worker } from 'near-workspaces'; let nearjs; @@ -11,6 +12,14 @@ beforeAll(async () => { nearjs = await setUpTestConnection(); }); +afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + describe('with promises', () => { let contract, contract1, contract2; let oldLog; diff --git a/packages/accounts/test/providers.test.ts b/packages/accounts/test/providers.test.ts index eb966b30d5..9e2fba6f08 100644 --- a/packages/accounts/test/providers.test.ts +++ b/packages/accounts/test/providers.test.ts @@ -1,10 +1,12 @@ -import { beforeAll, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; import { ErrorMessages } from '@near-js/utils'; import { base58 } from '@scure/base'; import { createAccount, deployContract, generateUniqueString, setUpTestConnection, sleep, waitFor } from './test-utils'; +import { Worker } from 'near-workspaces'; + jest.setTimeout(60000); let provider; @@ -16,6 +18,14 @@ beforeAll(async () => { provider = near.connection.provider; }); +afterAll(async () => { + const worker = near.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + describe('providers', () => { test('txStatus with string hash and buffer hash', async () => { const sender = await createAccount(near); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 98485a59e0..931fb922f0 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -6,6 +6,7 @@ import path from 'path'; import { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } from '../src'; import Config from './config'; +import { KeyPairSigner } from '@near-js/signers'; Logger.overrideLogger(new ConsoleLogger(['error', 'fatal'])) @@ -68,12 +69,15 @@ export async function setUpTestConnection() { const connection = Connection.fromConfig({ networkId: config.networkId, provider: { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, - signer: { type: 'InMemorySigner', keyStore: config.keyStore }, + signer: { type: 'KeyPairSigner', keyPair: await keyStore.getKey(networkId, config.masterAccount) }, }); return { - accountCreator: new LocalAccountCreator(new Account(connection, config.masterAccount), BigInt('500000000000000000000000000')), + accountCreator: new LocalAccountCreator(new Account(config.masterAccount, connection.provider, connection.signer), BigInt('500000000000000000000000000')), connection, + keyStore, + // return worker, so we can gracefully shut down tests + worker: config.worker || undefined }; } @@ -85,16 +89,25 @@ export function generateUniqueString(prefix) { return result + '.test.near'; } -export async function createAccount({ accountCreator, connection }, keyType = KeyType.ED25519) { +export async function createAccount({ accountCreator, connection, keyStore }, keyType = KeyType.ED25519) { const newAccountName = generateUniqueString('test'); - const newPublicKey = await connection.signer.createKey(newAccountName, networkId, keyType); + + const keyPair = KeyPair.fromRandom(Object.values(KeyType)[keyType]); + await keyStore.setKey(networkId, newAccountName, keyPair); + + const newPublicKey = keyPair.getPublicKey(); await accountCreator.createAccount(newAccountName, newPublicKey); - return new Account(connection, newAccountName); + + return new Account(newAccountName, connection.provider, new KeyPairSigner(keyPair)); } -export async function createAccountMultisig({ accountCreator, connection }, options) { +export async function createAccountMultisig({ accountCreator, connection, keyStore }, options) { const newAccountName = generateUniqueString('test'); - const newPublicKey = await connection.signer.createKey(newAccountName, networkId); + + const keyPair = KeyPair.fromRandom('ed25519'); + await keyStore.setKey(networkId, newAccountName, keyPair); + const newPublicKey = keyPair.getPublicKey(); + await accountCreator.createAccount(newAccountName, newPublicKey); // add a confirm key for multisig (contract helper sim) @@ -103,7 +116,7 @@ export async function createAccountMultisig({ accountCreator, connection }, opti const { publicKey } = confirmKeyPair; const accountMultisig = new AccountMultisig(connection, newAccountName, options); accountMultisig.useConfirmKey = async () => { - await connection.signer.setKey(networkId, options.masterAccount, confirmKeyPair); + await keyStore.setKey(networkId, options.masterAccount, confirmKeyPair); }; accountMultisig.getRecoveryMethods = () => ({ data: [] }); accountMultisig.postSignedJson = async (path) => { @@ -119,17 +132,21 @@ export async function createAccountMultisig({ accountCreator, connection }, opti } export async function deployContract(workingAccount, contractId) { - const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + const newPublicKey = keyPair.getPublicKey(); + const data = fs.readFileSync(HELLO_WASM_PATH); await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); return new Contract(workingAccount, contractId, HELLO_WASM_METHODS); } export async function deployContractGuestBook(workingAccount, contractId) { - const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + const newPublicKey = keyPair.getPublicKey(); + const data = fs.readFileSync(GUESTBOOK_WASM_PATH); const account = await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); - return new Contract(account, contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); + return new Contract(new Account(contractId, account.provider, new KeyPairSigner(keyPair)), contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); } export function sleep(time) { diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json index a353a73d9f..e641ec4821 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.json @@ -6,5 +6,5 @@ }, "files": [ "src/index.ts" - ] + ], } diff --git a/packages/client/package.json b/packages/client/package.json index d1a41eed2f..6948cfe7d8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -2,8 +2,9 @@ "name": "@near-js/client", "version": "0.0.4", "description": "", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -19,6 +20,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/accounts": "workspace:*", "@noble/hashes": "1.7.1" }, "keywords": [], @@ -30,11 +32,23 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0", + "@near-js/accounts": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/client/src/interfaces/dependencies.ts b/packages/client/src/interfaces/dependencies.ts index f8124e5e63..9babf28e42 100644 --- a/packages/client/src/interfaces/dependencies.ts +++ b/packages/client/src/interfaces/dependencies.ts @@ -1,29 +1,16 @@ -import type { PublicKey } from '@near-js/crypto'; - -import type { RpcQueryProvider } from './providers'; -import { FullAccessKey, FunctionCallAccessKey } from './view'; +import { Provider } from '@near-js/providers'; +import { Signer } from '@near-js/signers'; interface Dependent { deps: T; } -export interface MessageSigner { - getPublicKey(): Promise; - signMessage(m: Uint8Array): Promise; -} - -export interface AccessKeySigner extends MessageSigner { - getAccessKey(ignoreCache?: boolean): Promise; - getNonce(ignoreCache?: boolean): Promise; - getSigningAccount(): string; -} - interface RpcProviderDependent { - rpcProvider: RpcQueryProvider; + rpcProvider: Provider; } interface SignerDependent { - signer: MessageSigner; + signer: Signer; } export interface RpcProviderDependency extends Dependent {} diff --git a/packages/client/src/interfaces/index.ts b/packages/client/src/interfaces/index.ts index a3827e4c93..36d525630a 100644 --- a/packages/client/src/interfaces/index.ts +++ b/packages/client/src/interfaces/index.ts @@ -1,4 +1,3 @@ export * from './dependencies'; -export * from './providers'; export * from './transactions'; export * from './view'; diff --git a/packages/client/src/interfaces/providers.ts b/packages/client/src/interfaces/providers.ts deleted file mode 100644 index b418de9af0..0000000000 --- a/packages/client/src/interfaces/providers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - BlockReference, - BlockResult, - ChunkId, - ChunkResult, - FinalExecutionOutcome, - QueryResponseKind, - TxExecutionStatus, -} from '@near-js/types'; -import type { SignedTransaction } from '@near-js/transactions'; - -interface GetTransactionParams { - transactionHash: string; - account: string; - includeReceipts?: boolean; - waitUntil?: TxExecutionStatus; -} - -export interface RpcQueryProvider { - block(block: BlockReference): Promise; - chunk(chunkId: ChunkId): Promise; - getTransaction(params: GetTransactionParams): Promise; - sendTransaction(transaction: SignedTransaction): Promise; - query(...args: any[]): Promise; -} diff --git a/packages/client/src/providers.ts b/packages/client/src/providers.ts index 152f56c3bc..8e670b6bd8 100644 --- a/packages/client/src/providers.ts +++ b/packages/client/src/providers.ts @@ -1,6 +1,5 @@ -import { FailoverRpcProvider, JsonRpcProvider } from '@near-js/providers'; +import { FailoverRpcProvider, JsonRpcProvider, Provider } from '@near-js/providers'; -import type { RpcQueryProvider } from './interfaces'; import { PAGODA_RPC_ARCHIVAL_ENDPOINTS_TESTNET, PAGODA_RPC_ENDPOINTS_MAINNET, @@ -26,24 +25,12 @@ export function getEndpointsByNetwork(network: string) { * Initialize a failover RPC provider capable of retrying requests against a set of endpoints * @param urls RPC endpoint URLs */ -export function createRpcClientWrapper(urls: string[]): RpcQueryProvider { +export function createRpcClientWrapper(urls: string[]): Provider { if (!urls) { throw new Error('at least one RPC endpoint URL required'); } - const provider = new FailoverRpcProvider(urls.map((url) => new JsonRpcProvider({ url }))); - return { - block: (block) => provider.block(block), - chunk: (chunkId) => provider.chunk(chunkId), - getTransaction: ({ transactionHash, account, includeReceipts, waitUntil}) => { - if (includeReceipts) { - return provider.txStatusReceipts(transactionHash, account, waitUntil); - } - return provider.txStatus(transactionHash, account, waitUntil); - }, - sendTransaction: (transaction) => provider.sendTransaction(transaction), - query: (params) => provider.query(params), - }; + return new FailoverRpcProvider(urls.map((url) => new JsonRpcProvider({ url }))); } /** diff --git a/packages/client/src/signing/index.ts b/packages/client/src/signing/index.ts index 01e03a91e1..4f51eaecea 100644 --- a/packages/client/src/signing/index.ts +++ b/packages/client/src/signing/index.ts @@ -1,5 +1 @@ -export { - getSignerFromKeyPair, - getSignerFromKeystore, - getSignerFromPrivateKey, -} from './signers'; +export * from './signers'; \ No newline at end of file diff --git a/packages/client/src/signing/signers.ts b/packages/client/src/signing/signers.ts index 22aeee7039..8e9cb2378b 100644 --- a/packages/client/src/signing/signers.ts +++ b/packages/client/src/signing/signers.ts @@ -1,43 +1,23 @@ -import { - KeyPair, - type KeyPairString, -} from '@near-js/crypto'; +import { KeyPair, type KeyPairString } from "@near-js/crypto"; import { KeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; - -import type { - AccessKeySigner, - FullAccessKey, - FunctionCallAccessKey, - MessageSigner, - SignerDependency, - ViewAccountParams, -} from '../interfaces'; -import { getAccessKey } from '../view'; -import { sha256 } from '@noble/hashes/sha256'; +import { KeyPairSigner, Signer } from "@near-js/signers"; /** * Initialize a message signer from a KeyPair * @param keyPair used to sign transactions */ -export function getSignerFromKeyPair(keyPair: KeyPair): MessageSigner { - return { - async getPublicKey() { - return keyPair.getPublicKey(); - }, - async signMessage(m) { - const hashedMessage = new Uint8Array(sha256(m)); - return keyPair.sign(hashedMessage).signature; - } - }; +export function getSignerFromKeyPair(keyPair: KeyPair): Signer { + return new KeyPairSigner(keyPair); } /** * Initialize a message singer from a private key string * @param privateKey string representation of the private key used to sign transactions */ -export function getSignerFromPrivateKey(privateKey: KeyPairString): MessageSigner { - return getSignerFromKeyPair(KeyPair.fromString(privateKey)); +export function getSignerFromPrivateKey( + privateKey: KeyPairString +): Signer { + return KeyPairSigner.fromSecretKey(privateKey); } /** @@ -46,65 +26,7 @@ export function getSignerFromPrivateKey(privateKey: KeyPairString): MessageSigne * @param network to sign transactions on * @param keyStore used to store the signing key */ -export function getSignerFromKeystore(account: string, network: string, keyStore: KeyStore): MessageSigner { - const signer = new InMemorySigner(keyStore); - - return { - async getPublicKey() { - return signer.getPublicKey(account, network); - }, - async signMessage(m) { - const { signature } = await signer.signMessage(m, account, network); - return signature; - } - }; -} - -/** - * Initialize a signer that caches the access key and increments the nonce - * @param account access key owner - * @param rpcProvider RPC provider instance - * @param deps sign-and-send dependencies - */ -export function getAccessKeySigner({ account, blockReference, deps: { rpcProvider, signer } }: ViewAccountParams & SignerDependency): AccessKeySigner { - let accessKey: FullAccessKey | FunctionCallAccessKey; - let nonce: bigint | undefined; - - return { - async getAccessKey(ignoreCache = false) { - if (!accessKey || ignoreCache) { - accessKey = await getAccessKey({ - account, - blockReference: blockReference || { finality: 'optimistic' }, - publicKey: (await signer.getPublicKey()).toString(), - deps: { rpcProvider }, - }); - - nonce = accessKey.nonce + 1n; - } - - return accessKey; - }, - async getNonce(ignoreCache = false) { - if (!nonce || ignoreCache) { - await this.getAccessKey(true); - } - - return nonce; - }, - getPublicKey() { - return signer.getPublicKey(); - }, - getSigningAccount() { - return account; - }, - signMessage(m: Uint8Array) { - // Nonce can be uninitialized here if it is provided explicitly by developer (see toSignedDelegateAction), - // we don't need to track it here in that case. - if (nonce) { - nonce += 1n; - } - return signer.signMessage(m); - } - }; +export async function getSignerFromKeystore(account: string, network: string, keyStore: KeyStore): Promise { + const keyPair = await keyStore.getKey(network, account); + return new KeyPairSigner(keyPair); } diff --git a/packages/client/src/transactions/composers/signed_transaction_composer.ts b/packages/client/src/transactions/composers/signed_transaction_composer.ts index a768d5e68d..173342092a 100644 --- a/packages/client/src/transactions/composers/signed_transaction_composer.ts +++ b/packages/client/src/transactions/composers/signed_transaction_composer.ts @@ -1,35 +1,22 @@ -import { - buildDelegateAction, - signDelegateAction, -} from '@near-js/transactions'; import type { BlockReference, SerializedReturnValue } from '@near-js/types'; import { getTransactionLastResult } from '@near-js/utils'; import { DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL } from '../../constants'; import { - AccessKeySigner, - MessageSigner, MetaTransactionOptions, - RpcQueryProvider, SignedTransactionOptions, TransactionOptions, } from '../../interfaces'; import { signTransaction } from '../sign_and_send'; import { TransactionComposer } from './transaction_composer'; -import { getAccessKeySigner } from '../../signing/signers'; +import { Account } from '@near-js/accounts'; export class SignedTransactionComposer extends TransactionComposer { - messageSigner: MessageSigner; - rpcProvider: RpcQueryProvider; - signer: AccessKeySigner; + account: Account; constructor({ deps, ...baseOptions }: SignedTransactionOptions) { super(baseOptions); - this.messageSigner = deps.signer; - this.rpcProvider = deps.rpcProvider; - if (this.sender) { - this.signer = getAccessKeySigner({ account: this.sender, deps }); - } + this.account = new Account(this.sender, deps.rpcProvider, deps.signer); } /** @@ -47,26 +34,16 @@ export class SignedTransactionComposer extends TransactionComposer { async toSignedDelegateAction(transaction?: MetaTransactionOptions) { let maxBlockHeight = transaction?.maxBlockHeight; if (!maxBlockHeight) { - const { header } = await this.rpcProvider.block({ finality: 'final' }); + const { header } = await this.account.provider.viewBlock({ finality: 'final' }); const ttl = transaction?.blockHeightTtl || DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL; maxBlockHeight = BigInt(header.height) + ttl; } - const delegateAction = buildDelegateAction({ + return this.account.signedDelegate({ actions: this.actions, - maxBlockHeight, - nonce: transaction?.nonce || this.nonce || await this.signer.getNonce(), - publicKey: transaction?.publicKey || this.publicKey || await this.signer.getPublicKey(), receiverId: transaction?.receiver || this.receiver, - senderId: transaction?.sender || this.sender, - }); - - const { signedDelegateAction } = await signDelegateAction({ - delegateAction, - signer: { sign: (m) => this.signer.signMessage(m) }, - }); - - return signedDelegateAction; + blockHeightTtl: Number(maxBlockHeight) + }) } /** @@ -77,16 +54,8 @@ export class SignedTransactionComposer extends TransactionComposer { * @private */ private verifySigner(signingAccount: string) { - if (!this.signer) { - this.signer = getAccessKeySigner({ - account: signingAccount, - deps: { rpcProvider: this.rpcProvider, signer: this.messageSigner }, - }); - } - - const signerAccount = this.signer.getSigningAccount(); - if (signingAccount !== signerAccount) { - throw new Error(`Cannot sign transaction as ${signingAccount} with AccessKeySigner for ${signerAccount}`); + if (signingAccount !== this.account.accountId) { + throw new Error(`Cannot sign transaction as ${signingAccount} with Account for ${this.account.accountId}`); } } @@ -99,7 +68,7 @@ export class SignedTransactionComposer extends TransactionComposer { this.verifySigner(transaction.signerId); return signTransaction({ transaction, - deps: { signer: this.signer }, + deps: { signer: this.account.getSigner() }, }); } @@ -110,12 +79,11 @@ export class SignedTransactionComposer extends TransactionComposer { async signAndSend(blockReference: BlockReference = { finality: 'final' }) { this.verifySigner(this.sender); const { signedTransaction } = await this.toSignedTransaction({ - nonce: this.nonce || await this.signer.getNonce(), - publicKey: this.publicKey || await this.signer.getPublicKey(), - blockHash: this.blockHash || (await this.rpcProvider.block(blockReference))?.header?.hash, + publicKey: this.publicKey || await this.account.getSigner().getPublicKey(), + blockHash: this.blockHash || (await this.account.provider.viewBlock(blockReference))?.header?.hash, }); - const outcome = await this.rpcProvider.sendTransaction(signedTransaction); + const outcome = await this.account.provider.sendTransaction(signedTransaction); return { outcome, result: getTransactionLastResult(outcome) as T, diff --git a/packages/client/src/transactions/sign_and_send.ts b/packages/client/src/transactions/sign_and_send.ts index c555d3f6f5..c8d3f2901d 100644 --- a/packages/client/src/transactions/sign_and_send.ts +++ b/packages/client/src/transactions/sign_and_send.ts @@ -1,6 +1,4 @@ -import { Signature, SignedTransaction } from '@near-js/transactions'; import { getTransactionLastResult } from '@near-js/utils'; -import { sha256 } from '@noble/hashes/sha256'; import type { SignTransactionParams, SignAndSendTransactionParams } from '../interfaces'; import { getNonce } from '../view'; @@ -15,17 +13,10 @@ const DEFAULT_FINALITY: BlockReference = { finality: 'final' }; * @param signer MessageSigner */ export async function signTransaction({ transaction, deps: { signer } }: SignTransactionParams) { - const encodedTx = transaction.encode(); - const signedTransaction = new SignedTransaction({ - transaction, - signature: new Signature({ - keyType: transaction.publicKey.keyType, - data: await signer.signMessage(encodedTx), - }), - }); + const [txHash, signedTransaction] = await signer.signTransaction(transaction); return { - encodedTransactionHash: new Uint8Array(sha256(encodedTx)), + encodedTransactionHash: txHash, signedTransaction, }; } diff --git a/packages/client/src/view.ts b/packages/client/src/view.ts index 9f7ad6cf15..b65cf6ebc5 100644 --- a/packages/client/src/view.ts +++ b/packages/client/src/view.ts @@ -3,7 +3,7 @@ import type { AccessKeyView, AccountView, CodeResult, - ContractCodeView, + ContractCodeViewRaw, QueryResponseKind, SerializedReturnValue, StakedAccount, @@ -56,6 +56,7 @@ export function query({ deps: { rpcProvider }, }: QueryParams): Promise { return rpcProvider.query({ + // @ts-expect-error request_type isn't just a string, but a set of methods request_type: request, account_id: account, ...(blockReference ? blockReference : DEFAULT_VIEW_BLOCK_REFERENCE), @@ -207,7 +208,7 @@ export async function getAccessKeys({ account, blockReference, deps }: ViewAccou * @param deps readonly RPC dependencies */ export async function getContractCode({ account, blockReference, deps }: ViewAccountParams) { - const { code_base64, hash } = await query({ + const { code_base64, hash } = await query({ request: RequestType.ViewCode, account, blockReference, diff --git a/packages/cookbook/accounts/access-keys/create-full-access-key.ts b/packages/cookbook/accounts/access-keys/create-full-access-key.ts index f86c7224cc..b4066a89fd 100644 --- a/packages/cookbook/accounts/access-keys/create-full-access-key.ts +++ b/packages/cookbook/accounts/access-keys/create-full-access-key.ts @@ -20,7 +20,7 @@ export default async function createFullAccessKey(accountId: string) { const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'testnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'testnet', keystore); const previousKey = await signer.getPublicKey(); // create a new key from random data diff --git a/packages/cookbook/accounts/access-keys/create-function-access-key.ts b/packages/cookbook/accounts/access-keys/create-function-access-key.ts index 41085275f4..2c342a90d1 100644 --- a/packages/cookbook/accounts/access-keys/create-function-access-key.ts +++ b/packages/cookbook/accounts/access-keys/create-function-access-key.ts @@ -21,7 +21,7 @@ export default async function createFunctionCallAccessKey(accountId: string, con // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for ACCOUNT_ID - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/accounts/access-keys/delete-access-key.ts b/packages/cookbook/accounts/access-keys/delete-access-key.ts index bfc32f67aa..48b9d5581a 100644 --- a/packages/cookbook/accounts/access-keys/delete-access-key.ts +++ b/packages/cookbook/accounts/access-keys/delete-access-key.ts @@ -19,7 +19,7 @@ export default async function deleteAccessKeyCookbook(accountId: string, publicK // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for ACCOUNT_ID - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); // parse the target key from its string representation const keyPair = parseKeyPair(publicKey as KeyPairString); diff --git a/packages/cookbook/accounts/create-mainnet-account.ts b/packages/cookbook/accounts/create-mainnet-account.ts index 14c07b5f29..8039b25e00 100644 --- a/packages/cookbook/accounts/create-mainnet-account.ts +++ b/packages/cookbook/accounts/create-mainnet-account.ts @@ -19,7 +19,7 @@ export default async function createMainnetAccountCookbook(accountId: string, ne const rpcProvider = getMainnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'mainnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'mainnet', keystore); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/accounts/create-testnet-account.ts b/packages/cookbook/accounts/create-testnet-account.ts index cd3e41d454..e44082d527 100644 --- a/packages/cookbook/accounts/create-testnet-account.ts +++ b/packages/cookbook/accounts/create-testnet-account.ts @@ -19,7 +19,7 @@ export default async function createTestnetAccountCookbook(accountId: string, ne const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'testnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'testnet', keystore); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/package.json b/packages/cookbook/package.json index 515d268f64..30a1d7d292 100644 --- a/packages/cookbook/package.json +++ b/packages/cookbook/package.json @@ -26,7 +26,8 @@ "traverseBlocks": "tsx -e \"import f from './transactions/traverse-blocks.ts'; f(...process.argv.slice(1));\"", "unwrapNear": "tsx -e \"import f from './utils/unwrap-near.ts'; f(...process.argv.slice(1));\"", "verifySignature": "tsx -e \"import f from './utils/verify-signature.ts'; f(...process.argv.slice(1));\"", - "wrapNear": "tsx -e \"import f from './utils/wrap-near.ts'; f(...process.argv.slice(1));\"" + "wrapNear": "tsx -e \"import f from './utils/wrap-near.ts'; f(...process.argv.slice(1));\"", + "v2": "tsx account_v2.ts" }, "devDependencies": { "@near-js/client": "workspace:*", diff --git a/packages/cookbook/transactions/batch-transactions.ts b/packages/cookbook/transactions/batch-transactions.ts index 612363faa9..49b28a9c4d 100644 --- a/packages/cookbook/transactions/batch-transactions.ts +++ b/packages/cookbook/transactions/batch-transactions.ts @@ -23,7 +23,7 @@ export default async function batchTransactions(accountId: string = CONTRACT_NAM // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const { result } = await SignedTransactionComposer.init({ sender: accountId, diff --git a/packages/cookbook/transactions/get-tx-status.ts b/packages/cookbook/transactions/get-tx-status.ts index bc4a609b0a..03e30ec353 100644 --- a/packages/cookbook/transactions/get-tx-status.ts +++ b/packages/cookbook/transactions/get-tx-status.ts @@ -10,11 +10,7 @@ export default async function getTransactionStatus(accountId: string = ACCOUNT_I // initialize testnet RPC provider const rpcProvider = getTestnetRpcArchivalProvider(); - const result = await rpcProvider.getTransaction({ - account: accountId, - transactionHash, - includeReceipts: true, // toggle flag to include/exclude the `receipts` field - }); + const result = await rpcProvider.viewTransactionStatusWithReceipts(transactionHash, accountId, 'FINAL'); console.log(JSON.stringify(result, null, 2)); } diff --git a/packages/cookbook/transactions/meta-transaction-relayer.ts b/packages/cookbook/transactions/meta-transaction-relayer.ts index 14e8820b56..7aceb086a3 100644 --- a/packages/cookbook/transactions/meta-transaction-relayer.ts +++ b/packages/cookbook/transactions/meta-transaction-relayer.ts @@ -24,7 +24,7 @@ export default async function sendMetaTransactionViaRelayer(signerAccountId: str // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const signedDelegate = await SignedTransactionComposer.init({ sender: signerAccountId, diff --git a/packages/cookbook/transactions/meta-transaction.ts b/packages/cookbook/transactions/meta-transaction.ts index 71593d5a36..e1f63b3780 100644 --- a/packages/cookbook/transactions/meta-transaction.ts +++ b/packages/cookbook/transactions/meta-transaction.ts @@ -28,7 +28,7 @@ export default async function metaTransaction(signerAccountId: string = SIGNER_A // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore( + const signer = await getSignerFromKeystore( signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')) @@ -46,7 +46,7 @@ export default async function metaTransaction(signerAccountId: string = SIGNER_A .toSignedDelegateAction(); // initialize the relayer's signer - const relayerSigner = getSignerFromKeystore( + const relayerSigner = await getSignerFromKeystore( senderAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')) diff --git a/packages/cookbook/utils/calculate-gas.ts b/packages/cookbook/utils/calculate-gas.ts index e35866fdd8..4a3d93e284 100644 --- a/packages/cookbook/utils/calculate-gas.ts +++ b/packages/cookbook/utils/calculate-gas.ts @@ -22,7 +22,7 @@ export default async function calculateGas(accountId: string, method = METHOD_NA // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const { outcome: { diff --git a/packages/cookbook/utils/deploy-contract.ts b/packages/cookbook/utils/deploy-contract.ts index 654d280767..0c86c70991 100644 --- a/packages/cookbook/utils/deploy-contract.ts +++ b/packages/cookbook/utils/deploy-contract.ts @@ -20,7 +20,7 @@ export default async function deployContractCookbook(accountId: string, wasmPath // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); await deployContract({ account: accountId, diff --git a/packages/cookbook/utils/unwrap-near.ts b/packages/cookbook/utils/unwrap-near.ts index b88926e6fc..c7ad39b21a 100644 --- a/packages/cookbook/utils/unwrap-near.ts +++ b/packages/cookbook/utils/unwrap-near.ts @@ -22,7 +22,7 @@ export default async function unwrapNear(accountId: string, unwrapAmount: bigint // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const getStorageBalance = () => view({ account: wrapContract, diff --git a/packages/cookbook/utils/verify-signature.ts b/packages/cookbook/utils/verify-signature.ts index 11e56ec686..0795a77cbf 100644 --- a/packages/cookbook/utils/verify-signature.ts +++ b/packages/cookbook/utils/verify-signature.ts @@ -9,7 +9,7 @@ export default async function verifySignature(accountId: string = ACCOUNT_ID, tr // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); - const { transaction: { public_key, signature } } = await rpcProvider.getTransaction({ transactionHash, account: accountId }); + const { transaction: { public_key, signature } } = await rpcProvider.viewTransactionStatus(transactionHash, accountId, 'FINAL'); const hashBytes = baseDecode(transactionHash); const publicKeyBytes = baseDecode(public_key.slice('ed25519:'.length)); diff --git a/packages/cookbook/utils/wrap-near.ts b/packages/cookbook/utils/wrap-near.ts index 8e8087e2c6..e40c2501d0 100644 --- a/packages/cookbook/utils/wrap-near.ts +++ b/packages/cookbook/utils/wrap-near.ts @@ -22,7 +22,7 @@ export default async function wrapNear(accountId: string, wrapAmount: bigint, wr // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const getStorageBalance = () => view({ account: wrapContract, diff --git a/packages/crypto/package.json b/packages/crypto/package.json index e4a7b7d4cf..b97973d5ab 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -2,8 +2,9 @@ "name": "@near-js/crypto", "version": "1.4.2", "description": "Abstractions around NEAR-compatible elliptical curves and cryptographic keys", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -33,11 +34,17 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 0a25f0b3b9..dfdb378975 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -2,8 +2,9 @@ "name": "@near-js/keystores-node", "version": "0.1.2", "description": "KeyStore implementation for working with keys in the local filesystem", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -29,11 +30,17 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/keystores/package.json b/packages/keystores/package.json index ed159e1f6a..3d81e476ec 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -2,8 +2,9 @@ "name": "@near-js/keystores", "version": "0.2.2", "description": "Key storage and management implementations", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -28,11 +29,17 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/types": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/keystores/src/keystore.ts b/packages/keystores/src/keystore.ts index ad0f88d0e3..4ed7210e87 100644 --- a/packages/keystores/src/keystore.ts +++ b/packages/keystores/src/keystore.ts @@ -1,8 +1,7 @@ import { KeyPair } from '@near-js/crypto'; /** - * KeyStores are passed to {@link "@near-js/wallet-account".near.Near | Near} via {@link "@near-js/wallet-account".near.NearConfig | NearConfig} - * and are used by the {@link "@near-js/signers".in_memory_signer.InMemorySigner | InMemorySigner} to sign transactions. + * KeyStores are used by {@link "@near-js/signers".Signer} to sign transactions. * */ export abstract class KeyStore { diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index e04aa82816..aaf2287d26 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -21,7 +21,6 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", - "@near-js/wallet-account": "workspace:*", "@noble/curves": "1.8.1", "borsh": "1.0.0", "depd": "2.0.0", @@ -36,11 +35,23 @@ "in-publish": "2.0.0", "jest": "29.7.0", "near-hello": "0.5.1", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "rimraf": "^6.0.1", "semver": "7.7.1", "ts-jest": "29.2.6" }, + "peerDependencies": { + "@near-js/accounts": "^2.0.0", + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/keystores-browser": "^2.0.0", + "@near-js/keystores-node": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "keywords": [], "license": "(MIT AND Apache-2.0)", "scripts": { diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index d1883e140e..9caac34c26 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -6,4 +6,4 @@ export { FunctionCallOptions, ChangeFunctionCallOptions, ViewFunctionCallOptions, -} from '@near-js/accounts'; +} from "@near-js/accounts"; diff --git a/packages/near-api-js/src/account_multisig.ts b/packages/near-api-js/src/account_multisig.ts deleted file mode 100644 index 8671cf5956..0000000000 --- a/packages/near-api-js/src/account_multisig.ts +++ /dev/null @@ -1,12 +0,0 @@ -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/common-index.ts b/packages/near-api-js/src/common-index.ts index 6b0dd99bd4..4413ac0176 100644 --- a/packages/near-api-js/src/common-index.ts +++ b/packages/near-api-js/src/common-index.ts @@ -5,19 +5,13 @@ import * as transactions from './transaction'; import * as validators from './validators'; import { Account } from './account'; -import * as multisig from './account_multisig'; import * as accountCreator from './account_creator'; import { Connection } from './connection'; -import { Signer, InMemorySigner } from './signer'; +import { Signer, KeyPairSigner } from './signer'; import { Contract } from './contract'; import { KeyPair } from './utils/key_pair'; import { Near } from './near'; -import { - ConnectedWalletAccount, - WalletConnection -} from './wallet-account'; - export { accountCreator, providers, @@ -25,16 +19,12 @@ export { transactions, validators, - multisig, Account, Connection, Contract, - InMemorySigner, + KeyPairSigner, Signer, KeyPair, Near, - - ConnectedWalletAccount, - WalletConnection }; diff --git a/packages/near-api-js/src/near.ts b/packages/near-api-js/src/near.ts index c75aa3c7de..4b542169f3 100644 --- a/packages/near-api-js/src/near.ts +++ b/packages/near-api-js/src/near.ts @@ -1 +1,144 @@ -export { Near, NearConfig } from '@near-js/wallet-account'; +/** + * This module contains the main class developers will use to interact with NEAR. + * The {@link Near} class is used to interact with {@link "@near-js/accounts".account.Account | Account} through the {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | 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 { PublicKey } from '@near-js/crypto'; +import { KeyStore } from '@near-js/keystores'; +import { Signer } from '@near-js/signers'; +import { LoggerService } from '@near-js/utils'; +import { Provider } from '@near-js/providers'; + +export interface NearConfig { + /** Holds {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} 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 UrlAccountCreator} + */ + helperUrl?: string; + + /** + * The balance transferred from the {@link NearConfig#masterAccount} to a created account + * @see {@link LocalAccountCreator} + */ + initialBalance?: string; + + /** + * The account to use when creating new accounts + * @see {@link LocalAccountCreator} + */ + masterAccount?: string; + + /** + * {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} are stored in a {@link KeyStore} under the `networkId` namespace. + */ + networkId: string; + + /** + * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} + */ + nodeUrl: string; + + /** + * NEAR RPC API headers. Can be used to pass API KEY and other parameters. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | 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; + + /** + * Backward-compatibility for older versions + */ + deps?: { keyStore: KeyStore }; + /** + * Specifies the logger to use. Pass `false` to turn off logging. + */ + logger?: LoggerService | false; + /** + * Specifies NEAR RPC API connections, and is used to make JSON RPC calls to interact with NEAR + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} + * @see {@link "@near-js/providers".json-rpc-provider.FailoverRpcProvider | FailoverRpcProvider} + * @see [List of available and public JSON RPC endpoints](https://docs.near.org/api/rpc/providers) + */ + provider?: Provider; +} + +/** + * 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: config.provider || { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, + signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore || config.deps?.keyStore }, + }); + + 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 ? BigInt(config.initialBalance) : 500000000000000000000000000n; + this.accountCreator = new LocalAccountCreator(new Account(config.masterAccount, this.connection.provider, this.connection.signer), 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(accountId, this.connection.provider, this.connection.signer); + return account; + } + + /** + * Create an account using the {@link AccountCreator}. Either: + * * using a masterAccount with {@link LocalAccountCreator} + * * using the helperUrl with {@link 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(accountId, this.connection.provider, this.connection.signer); + } +} diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index 0195a07204..d93b138f33 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/signers'; +export { KeyPairSigner, Signer } from "@near-js/signers"; diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index e8fc48f07b..dcc5fc10ea 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -15,7 +15,6 @@ export { Transfer, SCHEMA, createTransaction, - signTransaction, Signature, SignedTransaction, Transaction, @@ -33,7 +32,7 @@ export const deleteAccount = (beneficiaryId: string) => actionCreators.deleteAcc export const deleteKey = (publicKey: PublicKey) => actionCreators.deleteKey(publicKey); export const deployContract = (code: Uint8Array) => actionCreators.deployContract(code); export const fullAccessKey = () => actionCreators.fullAccessKey(); -export const functionCall = (methodName: string, args: object | Uint8Array, gas: bigint, deposit: bigint, stringify?: typeof stringifyJsonOrBytes, jsContract?: boolean) => actionCreators.functionCall(methodName, args, gas, deposit, stringify, jsContract); +export const functionCall = (methodName: string, args: object | Uint8Array, gas: bigint, deposit: bigint, stringify?: typeof stringifyJsonOrBytes) => actionCreators.functionCall(methodName, args, gas, deposit, stringify); export const functionCallAccessKey = (receiverId: string, methodNames: string[], allowance?: bigint) => actionCreators.functionCallAccessKey(receiverId, methodNames, allowance); export const stake = (stake: bigint, publicKey: PublicKey) => actionCreators.stake(stake, publicKey); export const transfer = (deposit: bigint) => actionCreators.transfer(deposit); diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts deleted file mode 100644 index 172cdfa31f..0000000000 --- a/packages/near-api-js/src/wallet-account.ts +++ /dev/null @@ -1 +0,0 @@ -export { ConnectedWalletAccount, WalletConnection } from '@near-js/wallet-account'; diff --git a/packages/providers/package.json b/packages/providers/package.json index 5707002c91..85ac696594 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -2,8 +2,9 @@ "name": "@near-js/providers", "version": "1.0.3", "description": "Library of implementations for interfacing with the NEAR blockchain", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -19,6 +20,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/crypto": "workspace:*", "borsh": "1.0.0", "exponential-backoff": "^3.1.2" }, @@ -27,11 +29,17 @@ "@types/node": "20.0.0", "build": "workspace:*", "jest": "29.7.0", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "ts-jest": "29.2.6", "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0", + "@near-js/crypto": "^2.0.0" + }, "optionalDependencies": { "node-fetch": "2.6.7" }, @@ -40,6 +48,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index 1d3d36eca1..f4d4075c3f 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -27,16 +27,25 @@ import { QueryResponseKind, TypedError, RpcQueryRequest, + AccessKeyView, + AccessKeyList, + AccountView, + ContractCodeView, + ContractStateView, + CallContractViewFunctionResultRaw, + ExecutionOutcomeReceiptDetail, + FinalityReference, } from '@near-js/types'; import { SignedTransaction } from '@near-js/transactions'; import { Provider } from './provider'; import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; /** * 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 FailoverRpcProvider extends Provider { +export class FailoverRpcProvider implements Provider { /** @hidden */ readonly providers: Provider[]; @@ -46,8 +55,6 @@ export class FailoverRpcProvider extends Provider { * @param providers list of providers */ constructor(providers: Provider[]) { - super(); - if (providers.length === 0) { throw new Error('At least one provider must be specified'); } @@ -108,6 +115,70 @@ export class FailoverRpcProvider extends Provider { return this.withBackoff((currentProvider) => currentProvider.status()); } + async getNetworkId(): Promise { + return this.withBackoff((currentProvider) => currentProvider.getNetworkId()); + } + + public async viewAccessKey(accountId: string, publicKey: PublicKey, finalityQuery?: FinalityReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccessKey(accountId, publicKey, finalityQuery)); + } + + public async viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccessKeyList(accountId, finalityQuery)); + } + + public async viewAccount(accountId: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccount(accountId, blockQuery)); + } + + public async viewContractCode(accountId: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewContractCode(accountId, blockQuery)); + } + + public async viewContractState(accountId: string, prefix?: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewContractState(accountId, prefix, blockQuery)); + } + + public async callFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.callFunction(accountId, method, args, blockQuery)); + } + + public async callFunctionRaw(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.callFunctionRaw(accountId, method, args, blockQuery)); + } + + public async viewBlock(blockQuery: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewBlock(blockQuery)); + } + + public async viewChunk(chunkId: ChunkId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewChunk(chunkId)); + } + + public async viewGasPrice(blockId?: BlockId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewGasPrice(blockId)); + } + + public async viewNodeStatus(): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewNodeStatus()); + } + + public async viewValidators(blockId?: BlockId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewValidators(blockId)); + } + + public async viewTransactionStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionStatus(txHash, accountId, waitUntil)); + } + + public async viewTransactionStatusWithReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise>> { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionStatusWithReceipts(txHash, accountId, waitUntil)); + } + + public async viewTransactionReceipt(receiptId: string): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionReceipt(receiptId)); + } + async sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise { return this.withBackoff((currentProvider) => currentProvider.sendTransactionUntil(signedTransaction, waitUntil)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 0cbe3eb5ea..7da225faeb 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -32,6 +32,17 @@ import { NodeStatusResult, QueryResponseKind, TypedError, + AccessKeyViewRaw, + AccessKeyView, + FinalityReference, + AccessKeyList, + AccountView, + AccountViewRaw, + ContractCodeViewRaw, + ContractCodeView, + ContractStateView, + CallContractViewFunctionResultRaw, + ExecutionOutcomeReceiptDetail, } from '@near-js/types'; import { encodeTransaction, @@ -41,6 +52,7 @@ import { import { Provider } from './provider'; import { ConnectionInfo, fetchJsonRpc, retryConfig } from './fetch_json'; import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; /** @hidden */ // Default number of retries before giving up on a request. @@ -74,18 +86,20 @@ type RequestOptions = { * 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 { +export class JsonRpcProvider implements Provider { /** @hidden */ readonly connection: ConnectionInfo; /** @hidden */ readonly options: RequestOptions; + /** @hidden */ + private networkId: string | undefined; + /** * @param connectionInfo Connection info */ constructor(connectionInfo: ConnectionInfo, options?: Partial) { - super(); this.connection = connectionInfo || { url: '' }; const defaultOptions: RequestOptions = { retries: REQUEST_RETRY_NUMBER, @@ -93,6 +107,203 @@ export class JsonRpcProvider extends Provider { backoff: REQUEST_RETRY_WAIT_BACKOFF }; this.options = Object.assign({}, defaultOptions, options); + this.networkId = undefined; + } + + public async getNetworkId(): Promise { + if (this.networkId) return this.networkId; + + const { chain_id } = await this.viewNodeStatus(); + + this.networkId = chain_id; + + return this.networkId; + } + + public async viewAccessKey( + accountId: string, + publicKey: PublicKey | string, + finalityQuery: FinalityReference = { finality: 'final' } + ): Promise { + const data = await (this as Provider).query({ + ...finalityQuery, + request_type: 'view_access_key', + account_id: accountId, + public_key: publicKey.toString(), + }); + + return { + ...data, + nonce: BigInt(data.nonce), + }; + } + + public async viewAccessKeyList( + accountId: string, + finalityQuery: FinalityReference = { finality: 'final' } + ): Promise { + return (this as Provider).query({ + ...finalityQuery, + request_type: 'view_access_key_list', + account_id: accountId, + }); + } + + public async viewAccount( + accountId: string, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const data = await (this as Provider).query({ + ...blockQuery, + request_type: 'view_account', + account_id: accountId, + }); + + return { + ...data, + amount: BigInt(data.amount), + locked: BigInt(data.locked), + }; + } + + public async viewContractCode( + contractId: string, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const data = await (this as Provider).query({ + ...blockQuery, + request_type: 'view_code', + account_id: contractId, + }); + + return { + ...data, + code: new Uint8Array(Buffer.from(data.code_base64, 'base64')), + }; + } + + public async viewContractState( + contractId: string, + prefix?: string, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const prefixBase64 = Buffer.from(prefix || '').toString('base64'); + + return (this as Provider).query({ + ...blockQuery, + request_type: 'view_state', + account_id: contractId, + prefix_base64: prefixBase64, + }); + } + + public async callFunction( + contractId: string, + method: string, + args: Record, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString('base64'); + + const data = await ( + this as Provider + ).query({ + ...blockQuery, + request_type: 'call_function', + account_id: contractId, + method_name: method, + args_base64: argsBase64, + }); + + if (data.result.length === 0) { + return undefined; + } + + return JSON.parse(Buffer.from(data.result).toString()); + } + + public async callFunctionRaw( + contractId: string, + method: string, + args: Record, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString('base64'); + + return await ( + this as Provider + ).query({ + ...blockQuery, + request_type: 'call_function', + account_id: contractId, + method_name: method, + args_base64: argsBase64, + }); + } + + public async viewBlock(blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc('block', { block_id: blockId, finality }); + } + + public async viewChunk(chunkId: ChunkId): Promise { + return this.sendJsonRpc('chunk', [chunkId]); + } + + public async viewGasPrice(blockId?: BlockId): Promise { + return this.sendJsonRpc('gas_price', [blockId || null]); + } + + public async viewNodeStatus(): Promise { + return this.sendJsonRpc('status', []); + } + + public async viewValidators( + blockId?: BlockId + ): Promise { + return this.sendJsonRpc('validators', [blockId || null]); + } + + public async viewTransactionStatus( + txHash: Uint8Array | string, + accountId: string, + waitUntil: TxExecutionStatus + ): Promise { + const encodedTxHash = + typeof txHash === 'string' ? txHash : baseEncode(txHash); + + return this.sendJsonRpc('tx', { + tx_hash: encodedTxHash, + sender_account_id: accountId, + wait_until: waitUntil, + }); + } + + public async viewTransactionStatusWithReceipts( + txHash: Uint8Array | string, + accountId: string, + waitUntil: TxExecutionStatus + ): Promise< + FinalExecutionOutcome & + Required> + > { + const encodedTxHash = + typeof txHash === 'string' ? txHash : baseEncode(txHash); + + return this.sendJsonRpc('EXPERIMENTAL_tx_status', { + tx_hash: encodedTxHash, + sender_account_id: accountId, + wait_until: waitUntil, + }); + } + + public async viewTransactionReceipt( + receiptId: string + ): Promise { + return this.sendJsonRpc('EXPERIMENTAL_receipt', { + receipt_id: receiptId, + }); } /** diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 1b9846ed9e..221fe9ee29 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -5,14 +5,15 @@ import { SignedTransaction } from '@near-js/transactions'; import { + AccessKeyView, + AccessKeyList, AccessKeyWithPublicKey, + AccountView, BlockChangeResult, BlockId, BlockReference, BlockResult, ChangeResult, - ChunkId, - ChunkResult, FinalExecutionOutcome, GasPrice, LightClientProof, @@ -24,33 +25,72 @@ import { QueryResponseKind, RpcQueryRequest, EpochValidatorInfo, + ExecutionOutcomeReceiptDetail, + TxExecutionStatus, + ContractCodeView, + ContractStateView, + ChunkId, + ChunkResult, + FinalityReference, + CallContractViewFunctionResultRaw, } from '@near-js/types'; -import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; /** @hidden */ -export abstract class Provider { - abstract status(): Promise; - - abstract sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise; - abstract sendTransaction(signedTransaction: SignedTransaction): Promise; - abstract sendTransactionAsync(signedTransaction: SignedTransaction): Promise; - abstract txStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; - abstract txStatusReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): 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 | null): Promise; - abstract experimental_protocolConfig(blockReference: BlockReference): Promise; - abstract lightClientProof(request: LightClientProofRequest): Promise; - abstract nextLightClientBlock(request: NextLightClientBlockRequest): 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; +export interface Provider { + /** @deprecated use {@link viewNodeStatus} */ + status(): Promise; + + getNetworkId(): Promise; + + viewAccessKey(accountId: string, publicKey: PublicKey | string, finalityQuery?: FinalityReference): Promise; + viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise; + + viewAccount(accountId: string, blockQuery?: BlockReference): Promise; + viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; + viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; + callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + callFunctionRaw(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + + viewBlock(blockQuery: BlockReference): Promise; + viewChunk(chunkId: ChunkId): Promise; + + viewGasPrice(blockId?: BlockId): Promise; + + viewNodeStatus(): Promise; + viewValidators(blockId?: BlockId): Promise; + + viewTransactionStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + viewTransactionStatusWithReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise>>; + viewTransactionReceipt(receiptId: string): Promise; + + sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise; + sendTransaction(signedTransaction: SignedTransaction): Promise; + sendTransactionAsync(signedTransaction: SignedTransaction): Promise; + + /** @deprecated use {@link viewTransactionStatus} */ + txStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + /** @deprecated use {@link viewTransactionStatusWithReceipts} */ + txStatusReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + + query(params: RpcQueryRequest): Promise; + query(path: string, data: string): Promise; + + /** @deprecated use {@link viewBlock} */ + block(blockQuery: BlockId | BlockReference): Promise; + blockChanges(blockQuery: BlockId | BlockReference): Promise; + /** @deprecated use {@link viewChunk} */ + chunk(chunkId: ChunkId): Promise; + /** @deprecated use {@link viewValidators} */ + validators(blockId: BlockId | null): Promise; + experimental_protocolConfig(blockReference: BlockReference): Promise; + lightClientProof(request: LightClientProofRequest): Promise; + nextLightClientBlock(request: NextLightClientBlockRequest): Promise; + /** @deprecated use {@link viewGasPrice} */ + gasPrice(blockId: BlockId): Promise; + accessKeyChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], BlockQuery: BlockId | BlockReference): Promise; + accountChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + contractStateChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference, keyPrefix: string): Promise; + contractCodeChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; } diff --git a/packages/signers/package.json b/packages/signers/package.json index 50103e6653..d995188912 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -2,8 +2,9 @@ "name": "@near-js/signers", "version": "0.2.2", "description": "Modules for cryptographically signing messages", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -18,7 +19,9 @@ "dependencies": { "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", - "@noble/hashes": "1.7.1" + "@near-js/transactions": "workspace:*", + "@noble/hashes": "1.7.1", + "borsh": "1.0.0" }, "devDependencies": { "@jest/globals": "^29.7.0", @@ -29,11 +32,18 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/transactions": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/signers/src/in_memory_signer.ts b/packages/signers/src/in_memory_signer.ts deleted file mode 100644 index 564633deed..0000000000 --- a/packages/signers/src/in_memory_signer.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { KeyPair, PublicKey, Signature, KeyType } from '@near-js/crypto'; -import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; -import { sha256 } from '@noble/hashes/sha256'; - -import { Signer } from './signer'; - -/** - * 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, keyType?: KeyType): Promise { - const keyPair = keyType === KeyType.SECP256K1 ? KeyPair.fromRandom('secp256k1') : 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(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/signers/src/index.ts b/packages/signers/src/index.ts index 9a5ebfb0bc..11dd2b730c 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 { KeyPairSigner } from './key_pair_signer'; +export { Signer, SignedMessage } from './signer'; diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts new file mode 100644 index 0000000000..aca76040db --- /dev/null +++ b/packages/signers/src/key_pair_signer.ts @@ -0,0 +1,130 @@ +import { KeyPair, PublicKey, KeyPairString, KeyType } from '@near-js/crypto'; +import { sha256 } from '@noble/hashes/sha256'; + +import { + Nep413MessageSchema, + SignedMessage, + Signer, + SignMessageParams, +} from './signer'; +import { + Transaction, + SignedTransaction, + encodeTransaction, + Signature, + DelegateAction, + SignedDelegate, + encodeDelegateAction, +} from '@near-js/transactions'; +import { serialize } from 'borsh'; + +/** + * Signs using in memory key store. + */ +export class KeyPairSigner extends Signer { + private readonly key: KeyPair; + + constructor(key: KeyPair) { + super(); + this.key = key; + } + + public static fromSecretKey(encodedKey: KeyPairString): KeyPairSigner { + const key = KeyPair.fromString(encodedKey); + + return new KeyPairSigner(key); + } + + public async getPublicKey(): Promise { + return this.key.getPublicKey(); + } + + public async signNep413Message( + message: string, + accountId: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: string + ): Promise { + if (nonce.length !== 32) + throw new Error('Nonce must be exactly 32 bytes long'); + + const pk = this.key.getPublicKey(); + + // 2**31 + 413 == 2147484061 + const PREFIX = 2147484061; + const serializedPrefix = serialize('u32', PREFIX); + + const params: SignMessageParams = { + message, + recipient, + nonce, + callbackUrl + }; + const serializedParams = serialize(Nep413MessageSchema, params); + + const serializedMessage = new Uint8Array( + serializedPrefix.length + serializedParams.length + ); + serializedMessage.set(serializedPrefix); + serializedMessage.set(serializedParams, serializedPrefix.length); + + const hash = new Uint8Array(sha256(serializedMessage)); + + const { signature } = this.key.sign(hash); + + return { + accountId: accountId, + publicKey: pk, + signature: signature, + }; + } + + public async signTransaction( + transaction: Transaction + ): Promise<[Uint8Array, SignedTransaction]> { + const pk = this.key.getPublicKey(); + + if (transaction.publicKey.toString() !== pk.toString()) + throw new Error('The public key doesn\'t match the signer\'s key'); + + const message = encodeTransaction(transaction); + const hash = new Uint8Array(sha256(message)); + + const { signature } = this.key.sign(hash); + const signedTx = new SignedTransaction({ + transaction, + signature: new Signature({ + keyType: transaction.publicKey.ed25519Key + ? KeyType.ED25519 + : KeyType.SECP256K1, + data: signature, + }), + }); + + return [hash, signedTx]; + } + + public async signDelegateAction( + delegateAction: DelegateAction + ): Promise<[Uint8Array, SignedDelegate]> { + const pk = this.key.getPublicKey(); + + if (delegateAction.publicKey.toString() !== pk.toString()) + throw new Error('The public key doesn\'t match the signer\'s key'); + + const message = encodeDelegateAction(delegateAction); + const hash = new Uint8Array(sha256(message)); + + const { signature } = this.key.sign(message); + const signedDelegate = new SignedDelegate({ + delegateAction, + signature: new Signature({ + keyType: pk.keyType, + data: signature, + }), + }); + + return [hash, signedDelegate]; + } +} diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index c0e8dbceaf..1ac4ee6218 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,29 +1,64 @@ -import { Signature, PublicKey, KeyType } from '@near-js/crypto'; +import { PublicKey } from '@near-js/crypto'; +import { + DelegateAction, + SignedDelegate, + SignedTransaction, + Transaction, +} from '@near-js/transactions'; +import { Schema } from 'borsh'; + +export interface SignMessageParams { + message: string; // The message that wants to be transmitted. + recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com"). + nonce: Uint8Array; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS). + callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`. +} + +export interface SignedMessage { + accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near") + publicKey: PublicKey; // The public counterpart of the key used to sign, expressed as a string with format ":" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y") + signature: Uint8Array; // The base64 representation of the signature. + state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The same state passed in SignMessageParams. +} + +export const Nep413MessageSchema: Schema = { + struct: { + message: 'string', + nonce: { array: { type: 'u8', len: 32 } }, + recipient: 'string', + callbackUrl: { option: 'string' }, + }, +}; /** * 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. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) + * Returns public key for given signer */ - abstract createKey(accountId: string, networkId?: string, keyType?: KeyType): Promise; + public abstract getPublicKey(): Promise; /** - * Returns public key for given account / network. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) + * Signs given message according to NEP-413 requirements + * @see https://github.com/near/NEPs/blob/master/neps/nep-0413.md + * + * @param params + * @param accountId */ - abstract getPublicKey(accountId?: string, networkId?: string): Promise; + public abstract signNep413Message( + message: string, + accountId: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: 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; + public abstract signTransaction( + transaction: Transaction + ): Promise<[Uint8Array, SignedTransaction]>; + + public abstract signDelegateAction( + delegateAction: DelegateAction + ): Promise<[Uint8Array, SignedDelegate]>; } diff --git a/packages/signers/test/key_pair_signer.test.ts b/packages/signers/test/key_pair_signer.test.ts new file mode 100644 index 0000000000..1c8f1ec535 --- /dev/null +++ b/packages/signers/test/key_pair_signer.test.ts @@ -0,0 +1,257 @@ +import { expect, test } from '@jest/globals'; +import { TextEncoder } from 'util'; + +import { KeyPairSigner } from '../src'; +import { KeyPair, PublicKey } from '@near-js/crypto'; +import { + createTransaction, + encodeTransaction, + actionCreators, + decodeSignedTransaction, + buildDelegateAction, +} from '@near-js/transactions'; + +global.TextEncoder = TextEncoder; + +test('test sign transaction with different public key', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF' + ) + ); + + const transaction = createTransaction( + 'signer', + // the key is different from what signer operates + KeyPair.fromString( + 'ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV' + ).getPublicKey(), + 'receiver', + 1n, + [], + new Uint8Array(new Array(32)) + ); + + await expect(() => signer.signTransaction(transaction)).rejects.toThrow( + /The public key doesn't match the signer's key/ + ); +}); + +test('test sign transaction with relevant public key', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF' + ) + ); + + const transaction = createTransaction( + 'signer', + await signer.getPublicKey(), + 'receiver', + 1n, + [], + new Uint8Array(new Array(32)) + ); + + const [hash, { signature }] = await signer.signTransaction(transaction); + + expect(Buffer.from(hash).toString('hex')).toBe( + '2571e3539ab5556e39441913e66abd07e634fb9850434006a719306100e641a2' + ); + + expect(Buffer.from(signature.signature.data).toString('hex')).toBe( + 'bfe2858d227e3116076a8e5ea9c5bef923c7755f19f0137d1acd9bb67973f1b8a7f83dfc0be23e307e106c8807eaa6e14c0fcb46c42acdf293c4a6a81a27fc05' + ); +}); + +test('serialize and sign transfer tx object', async () => { + const keyPair = KeyPair.fromString( + 'ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv' + ); + const signer = new KeyPairSigner(keyPair); + + const actions = [actionCreators.transfer(1n)]; + const blockHash = new Uint8Array([ + 15, 164, 115, 253, 38, 144, 29, 242, 150, 190, 106, 220, 76, 196, 223, + 52, 208, 64, 239, 162, 67, 82, 36, 182, 152, 105, 16, 230, 48, 194, 254, + 246, + ]); + const transaction = createTransaction( + 'test.near', + PublicKey.fromString( + 'ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC' + ), + 'whatever.near', + 1, + actions, + blockHash + ); + + const [, signedTx] = await signer.signTransaction(transaction); + + expect( + Buffer.from(signedTx.signature.ed25519Signature!.data).toString( + 'base64' + ) + ).toEqual( + 'lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ==' + ); + const serialized = encodeTransaction(signedTx); + expect(Buffer.from(serialized).toString('hex')).toEqual( + '09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01' + ); + + const deserialized = decodeSignedTransaction(serialized); + expect(encodeTransaction(deserialized)).toEqual(serialized); +}); + +test('test sign NEP-413 message with callback url', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' + ) + ); + + const { signature } = await signer.signNep413Message( + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', + new Uint8Array( + Buffer.from( + 'KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=', + 'base64' + ) + ), + 'http://localhost:3000' + ); + + const expectedSignature = new Uint8Array( + Buffer.from( + 'zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==', + 'base64' + ) + ); + + expect(signature).toStrictEqual(expectedSignature); +}); + +test('test sign NEP-413 message without callback url', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' + ) + ); + + const { signature } = await signer.signNep413Message( + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', + new Uint8Array( + Buffer.from( + 'KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=', + 'base64' + ) + ) + ); + + const expectedSignature = new Uint8Array( + Buffer.from( + 'NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==', + 'base64' + ) + ); + + expect(signature).toStrictEqual(expectedSignature); +}); + +test('test sign NEP-413 message throws error on invalid nonce', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' + ) + ); + + await expect(() => + signer.signNep413Message( + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', + new Uint8Array(new Array(28)) + ) + ).rejects.toThrow(); +}); + +test('test getPublicKey returns correct public key', async () => { + const keyPair = KeyPair.fromString( + 'ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB' + ); + const signer = new KeyPairSigner(keyPair); + + const publicKey = (await signer.getPublicKey()).toString(); + expect(publicKey).toBe( + 'ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ' + ); +}); + +test('test static fromSecretKey creates a corresponding KeyPair', async () => { + const signer = KeyPairSigner.fromSecretKey( + 'ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB' + ); + + const publicKey = (await signer.getPublicKey()).toString(); + expect(publicKey).toBe( + 'ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ' + ); +}); + +test('test sign delegate action', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' + ) + ); + + const delegateAction = buildDelegateAction({ + receiverId: 'receiver.testnet', + senderId: 'sender.testnet', + nonce: 1n, + maxBlockHeight: 1848319858n, + actions: [], + publicKey: await signer.getPublicKey(), + }); + + const [hash, { signature }] = await signer.signDelegateAction( + delegateAction + ); + + expect(Buffer.from(hash).toString('hex')).toBe( + '6d35575b3566fddf04f79317c3e574eb89c5bc228c5e755f2a3179e164dbb36b' + ); + + expect(Buffer.from(signature.signature.data).toString('hex')).toBe( + '77e5e92877d64ae27b1facc5ddf09205863b2af7a8264d2d01a44a9684975a17f2b8b9d706fbe3c3ffa2cd4377b49f20614245549dc061bcaccc0ad3abd81c01' + ); +}); + +test('test sign delegate action with wrong public key', async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' + ) + ); + + const delegateAction = buildDelegateAction({ + receiverId: 'receiver.testnet', + senderId: 'sender.testnet', + nonce: 1n, + maxBlockHeight: 1848319858n, + actions: [], + publicKey: KeyPair.fromString( + 'ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV' + ).getPublicKey(), + }); + + await expect(() => + signer.signDelegateAction(delegateAction) + ).rejects.toThrow(); +}); diff --git a/packages/signers/test/signer.test.ts b/packages/signers/test/signer.test.ts deleted file mode 100644 index f810f966e3..0000000000 --- a/packages/signers/test/signer.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { expect, test } from '@jest/globals'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { TextEncoder } from 'util'; - -import { InMemorySigner } from '../src'; - -global.TextEncoder = TextEncoder; - -test('test no key', async () => { - const signer = new InMemorySigner(new InMemoryKeyStore()); - // @ts-expect-error test input - await expect(signer.signMessage('message', 'user', 'network')) - .rejects.toThrow(/Key for user not found in network/); -}); diff --git a/packages/tokens/README.md b/packages/tokens/README.md new file mode 100644 index 0000000000..80f8998c36 --- /dev/null +++ b/packages/tokens/README.md @@ -0,0 +1,8 @@ +# @near-js/tokens + +A collection of standard tokens + +# 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/jest.config.ts b/packages/tokens/jest.config.ts similarity index 100% rename from packages/wallet-account/jest.config.ts rename to packages/tokens/jest.config.ts diff --git a/packages/tokens/package.json b/packages/tokens/package.json new file mode 100644 index 0000000000..33da6e7177 --- /dev/null +++ b/packages/tokens/package.json @@ -0,0 +1,52 @@ +{ + "name": "@near-js/tokens", + "version": "0.1.0", + "description": "Modules with tokens", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", + "scripts": { + "build": "pnpm compile:esm && pnpm compile:cjs", + "compile:esm": "tsc -p tsconfig.json", + "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", + "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc", + "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc --fix", + "test": "jest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@jest/globals": "^29.7.0", + "@near-js/types": "workspace:*", + "@types/node": "20.0.0", + "build": "workspace:*", + "jest": "29.7.0", + "ts-jest": "29.2.6", + "tsconfig": "workspace:*", + "typescript": "5.4.5" + }, + "files": [ + "lib" + ], + "exports": { + ".": { + "require": "./lib/commonjs/index.cjs", + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" + }, + "./mainnet": { + "require": "./lib/commonjs/mainnet/index.cjs", + "import": "./lib/commonjs/mainnet/index.cjs", + "types": "./lib/esm/mainnet/index.d.ts", + "default": "./lib/esm/mainnet/index.js" + }, + "./testnet": { + "require": "./lib/commonjs/testnet/index.cjs", + "import": "./lib/commonjs/testnet/index.cjs", + "types": "./lib/esm/testnet/index.d.ts", + "default": "./lib/esm/testnet/index.js" + } + } +} diff --git a/packages/tokens/src/ft/format.ts b/packages/tokens/src/ft/format.ts new file mode 100644 index 0000000000..8208e98d27 --- /dev/null +++ b/packages/tokens/src/ft/format.ts @@ -0,0 +1,81 @@ +export const MAX_NOMINATION_EXP = 24; + +/** + * Convert internal indivisible units to formatted human-readable amount + */ +export function formatAmount( + units: string | number | bigint, + fracDigits: number +): string { + units = cleanupUnits(units); + + const wholeStr = units.substring(0, units.length - fracDigits) || '0'; + const fractionStr = units + .substring(units.length - fracDigits) + .substring(0, fracDigits) + .padStart(fracDigits, '0'); + + return trimTrailingZeroes(`${wholeStr}.${fractionStr}`); +} + +/** + * Convert human readable amount to internal indivisible units. + */ +export function parseAmount(amount: string, fracDigits: number): string { + amount = cleanupAmount(amount); + const split = amount.split('.'); + + if (split.length > 2) { + throw new Error( + `Cannot parse amount '${amount}' as it contains more than a single dot` + ); + } + + const wholePart = split[0]; + const fracPart = split[1] || ''; + + if (fracPart.length > MAX_NOMINATION_EXP) { + throw new Error( + `Cannot parse amount '${amount}' as it exceeds maximum decimal precision of ${MAX_NOMINATION_EXP}` + ); + } + + return trimLeadingZeroes( + wholePart + fracPart.substring(0, fracDigits).padEnd(fracDigits, '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; +} + +function cleanupUnits(amount: string | number | bigint): string { + return BigInt(amount).toString(); +} diff --git a/packages/tokens/src/ft/index.ts b/packages/tokens/src/ft/index.ts new file mode 100644 index 0000000000..9d0964f636 --- /dev/null +++ b/packages/tokens/src/ft/index.ts @@ -0,0 +1,189 @@ +import { formatAmount, parseAmount } from './format'; +import type { AccountLike } from '@near-js/types'; + +interface FTMetadata { + spec?: string; + name: string; + decimals: number; + symbol: string; + icon?: string; +} + +abstract class BaseFT { + public readonly metadata: FTMetadata; + + constructor(metadata: FTMetadata) { + this.metadata = metadata; + } + + /** + * Converts a decimal number to indivisible units + * + * @param amount The amount in decimal format (e.g. "1.234") + * @returns The amount in indivisible units (e.g. "1234") + */ + public toUnits(amount: string | number): bigint { + const units = parseAmount(amount.toString(), this.metadata.decimals); + return BigInt(units); + } + + /** + * Converts indivisible units to a decimal number (represented as a string) + * + * @param units The amount in indivisible units (e.g. "1234") + * @returns The amount as a decimal string (e.g. "1.234") + */ + public toDecimal(amount: bigint | string | number): string { + return formatAmount(amount, this.metadata.decimals); + } + + /** + * Get the available balance of an account in indivisible units + * + * @param account The account to get the balance of + * @returns + */ + abstract getBalance(account: AccountLike): Promise; + + /** + * Transfer tokens from one account to another + * + * @param param + * @param param.from The Account that will transfer the tokens + * @param param.receiverId The AccountID that will receive the tokens + * @param param.amount The amount of tokens to transfer in the smallest unit + */ + abstract transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise +} + +export class NativeToken extends BaseFT { + constructor(metadata: FTMetadata) { + super(metadata); + } + + public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { + return from.signAndSendTransaction({ + receiverId, + actions: [{ transfer: { deposit: amount.toString() } }], + }); + } + + public async getBalance(account: AccountLike): Promise { + const { balance: { available } } = await account.getState(); + return available; + } +} + +export class FungibleToken extends BaseFT { + public readonly accountId: string; + + constructor(accountId: string, metadata: FTMetadata) { + metadata.spec = metadata.spec || 'ft-1.0.0'; + super(metadata); + this.accountId = accountId; + } + + public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: 'ft_transfer', + args: { + amount: amount.toString(), + receiver_id: receiverId, + }, + gas: '30000000000000', + deposit: 1, + }); + } + + public async getBalance(account: AccountLike): Promise { + const { result: balance } = await account.provider.callFunction( + this.accountId, + 'ft_balance_of', + { + account_id: account.accountId, + }, + ); + return BigInt(balance); + } + + /** + * Transfer tokens and call a function on the receiver contract, + * only works if the receiver implements the `ft_on_transfer` method + * + * @param param + * @param param.from The Account that will transfer the tokens + * @param param.receiverId The AccountID that will receive the tokens + * @param param.units The amount of tokens to transfer in the smallest unit + * @param param.msg The message to send to the `ft_on_transfer` method + */ + public async transferCall({ from, receiverId, amount, msg }: { from: AccountLike, receiverId: string, amount: bigint, msg: string }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: 'ft_transfer_call', + args: { + receiver_id: receiverId, + amount: amount.toString(), + msg, + }, + gas: '30000000000000', + deposit: 1, + }); + } + + /** + * Register an account to the fungible token contract by paying a storage deposit + * + * @param param + * @param param.accountIdToRegister The AccountID to register + * @param param.fundingAccount The Account that will fund the registration + */ + public async registerAccount({ accountIdToRegister, fundingAccount }: { accountIdToRegister: AccountLike, fundingAccount: AccountLike }): Promise { + + const { result } = await fundingAccount.provider.callFunction( + this.accountId, + 'storage_balance_bounds', + {} + ); + + const requiredDeposit = result.min as string; + + return fundingAccount.callFunction({ + contractId: this.accountId, + methodName: 'storage_deposit', + args: { + account_id: accountIdToRegister, + registration_only: true, + }, + gas: '30000000000000', + deposit: requiredDeposit, + }); + } + + /** + * Unregister an account from the fungible token contract by paying a storage deposit + * + * @param param + * @param param.account The Account to unregister + * @param param.force Whether to remove the account without claiming the storage deposit + */ + public async unregisterAccount({ account, force = false }: { account: AccountLike, force: boolean }): Promise { + return account.callFunction({ + contractId: this.accountId, + methodName: 'storage_unregister', + args: { force }, + gas: '30000000000000', + deposit: 1, + }); + } +} + +/** + * The NEAR token is the native token of the NEAR blockchain + */ +export const NEAR = new NativeToken({ + name: 'NEAR', + decimals: 24, + symbol: 'NEAR', + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' +}); \ No newline at end of file diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts new file mode 100644 index 0000000000..51622afc8f --- /dev/null +++ b/packages/tokens/src/index.ts @@ -0,0 +1,4 @@ +export { NEAR, NativeToken, FungibleToken } from "./ft" +export { NonFungibleToken, NFTContract } from "./nft" +export * as mainnet from "./mainnet" +export * as testnet from "./testnet" \ No newline at end of file diff --git a/packages/tokens/src/mainnet/index.ts b/packages/tokens/src/mainnet/index.ts new file mode 100644 index 0000000000..ed06b4c6fd --- /dev/null +++ b/packages/tokens/src/mainnet/index.ts @@ -0,0 +1,30 @@ +import { FungibleToken } from '../ft'; + +export const wNEAR = new FungibleToken( + 'wrap.near', + { + name: 'Wrapped NEAR fungible token', + decimals: 24, + symbol: 'wNEAR', + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' + }); + +export const USDC = new FungibleToken( + '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', + { + name: 'USDC', + decimals: 6, + symbol: 'USDC', + icon: 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A' + } +); + +export const USDT = new FungibleToken( + 'usdt.tether-token.near', + { + name: 'Tether USD', + decimals: 6, + symbol: 'USDt', + icon: 'data:image/svg+xml,%3Csvg width=\'111\' height=\'90\' viewBox=\'0 0 111 90\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath fill-rule=\'evenodd\' clip-rule=\'evenodd\' d=\'M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z\' fill=\'%23009393\'/%3E%3C/svg%3E' + } +); \ No newline at end of file diff --git a/packages/tokens/src/nft/index.ts b/packages/tokens/src/nft/index.ts new file mode 100644 index 0000000000..3cd2ae03b1 --- /dev/null +++ b/packages/tokens/src/nft/index.ts @@ -0,0 +1,77 @@ +import type { AccountLike } from '@near-js/types'; + +interface ContractMetadata { + spec?: string; + name: string; + symbol: string; + icon?: string; + baseUri?: string; + reference?: string; + referenceHash?: string; +} + +interface NFTMetadata { + title?: string; + description?: string; + media?: string; + mediaHash?: string; + copies?: number; + issuedAt?: string; + expiresAt?: string; + startsAt?: string; + updatedAt?: string; + extra?: string; + reference?: string; + referenceHash?: string; +} + +export class NFTContract { + public readonly metadata: ContractMetadata; + public readonly accountId: string; + + constructor(accountId: string, metadata: ContractMetadata) { + metadata.spec = metadata.spec || 'nft-1.0.0'; + this.metadata = metadata; + this.accountId = accountId; + } + + transfer({ + from, + receiverId, + tokenId + }: { + from: AccountLike; + receiverId: string; + tokenId: string; + }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: 'nft_transfer', + args: { + receiver_id: receiverId, + token_id: tokenId + }, + deposit: 1, + gas: 30000000000000 + }); + } +} + +export class NonFungibleToken { + public readonly contractId: string; + public readonly tokenId: string; + public readonly ownerId: string; + public readonly metadata: NFTMetadata; + + constructor( + contractId: string, + tokenId: string, + ownerId: string, + metadata: NFTMetadata + ) { + this.contractId = contractId; + this.tokenId = tokenId; + this.ownerId = ownerId; + this.metadata = metadata; + } +} \ No newline at end of file diff --git a/packages/tokens/src/testnet/index.ts b/packages/tokens/src/testnet/index.ts new file mode 100644 index 0000000000..4a79138274 --- /dev/null +++ b/packages/tokens/src/testnet/index.ts @@ -0,0 +1,30 @@ +import { FungibleToken } from '../ft'; + +export const wNEAR = new FungibleToken( + 'wrap.testnet', + { + name: 'Wrapped NEAR fungible token', + decimals: 24, + symbol: 'wNEAR', + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' + }); + +export const USDC = new FungibleToken( + 'usdc.fakes.testnet', + { + name: 'USDC', + decimals: 6, + symbol: 'USDC', + icon: 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A' + } +); + +export const USDT = new FungibleToken( + 'usdt.fakes.testnet', + { + name: 'Tether USD', + decimals: 6, + symbol: 'USDt', + icon: 'data:image/svg+xml,%3Csvg width=\'111\' height=\'90\' viewBox=\'0 0 111 90\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath fill-rule=\'evenodd\' clip-rule=\'evenodd\' d=\'M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z\' fill=\'%23009393\'/%3E%3C/svg%3E' + } +); \ No newline at end of file diff --git a/packages/tokens/test/fungible_token.test.ts b/packages/tokens/test/fungible_token.test.ts new file mode 100644 index 0000000000..bc4870c86f --- /dev/null +++ b/packages/tokens/test/fungible_token.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from '@jest/globals'; +import { FungibleToken } from '../src'; + +const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST', name: 'Test Token' }); + +test('test props are accessible', () => { + expect(FT.accountId).toBe('ft.testnet'); + + expect(FT.metadata.decimals).toBe(6); + expect(FT.metadata.symbol).toBe('TEST'); +}); + +test('test toUnits parses formatted amount', () => { + expect(FT.toUnits('1.234')).toBe(BigInt('1234000')); + expect(FT.toUnits(1.234)).toBe(BigInt('1234000')); + + expect(FT.toUnits('1.234567')).toBe(BigInt('1234567')); + expect(FT.toUnits(1.234567)).toBe(BigInt('1234567')); + + // exceeds the precision, rounding down + expect(FT.toUnits('1.2345678')).toBe(BigInt('1234567')); + expect(FT.toUnits(1.2345678)).toBe(BigInt('1234567')); + + expect(FT.toUnits('1')).toBe(BigInt('1000000')); + expect(FT.toUnits(1)).toBe(BigInt('1000000')); + + expect(FT.toUnits('00001.234')).toBe(BigInt('1234000')); + + expect(FT.toUnits('0000000000000.576919')).toBe(BigInt('576919')); + + // exceeds the precision, rounding down + expect(FT.toUnits('0000000000000.5769191111')).toBe(BigInt('576919')); + expect(FT.toUnits('.5769191111')).toBe(BigInt('576919')); + + expect(FT.toUnits('0')).toBe(BigInt('0')); +}); + +test('test toUnits parses formatted amount with comma', () => { + expect(FT.toUnits('1,234')).toBe(BigInt('1234000')); + expect(FT.toUnits('1,234567')).toBe(BigInt('1234567')); + // exceeds the precision, rounding down + expect(FT.toUnits('1,2345678')).toBe(BigInt('1234567')); + expect(FT.toUnits('00001,234')).toBe(BigInt('1234000')); + expect(FT.toUnits('0000000000000,576919')).toBe(BigInt('576919')); + // exceeds the precision, rounding down + expect(FT.toUnits('0000000000000,5769191111')).toBe(BigInt('576919')); +}); + +test('test toUnits fails on precision of more than 24 decimals', () => { + // there's 25 ones after the point + expect(() => FT.toUnits('0.1111111111111111111111111')).toThrow(); +}); + +test('test toUnits fails on invalid numeric string', () => { + expect(() => FT.toUnits('2.1.3')).toThrow(); +}); + +test('test toUnits fails on non-numeric symbols', () => { + expect(() => FT.toUnits('1.24n')).toThrow(); + + expect(() => FT.toUnits('abc192.31')).toThrow(); + + expect(() => FT.toUnits('abcdefg')).toThrow(); +}); + +test('test toDecimal formats units', () => { + expect(FT.toDecimal('1000000')).toBe('1'); + expect(FT.toDecimal(1_000_000)).toBe('1'); + expect(FT.toDecimal(BigInt(1_000_000))).toBe('1'); + + expect(FT.toDecimal('1000001')).toBe('1.000001'); + expect(FT.toDecimal(1_000_001)).toBe('1.000001'); + expect(FT.toDecimal(BigInt(1_000_001))).toBe('1.000001'); + + expect(FT.toDecimal('1234567')).toBe('1.234567'); + expect(FT.toDecimal(1_234_567)).toBe('1.234567'); + expect(FT.toDecimal(BigInt(1_234_567))).toBe('1.234567'); + + expect(FT.toDecimal('12345678')).toBe('12.345678'); + expect(FT.toDecimal(12_345_678)).toBe('12.345678'); + expect(FT.toDecimal(BigInt(12_345_678))).toBe('12.345678'); + + expect(FT.toDecimal('710')).toBe('0.00071'); + expect(FT.toDecimal(710)).toBe('0.00071'); + expect(FT.toDecimal(BigInt(710))).toBe('0.00071'); + + expect(FT.toDecimal('1')).toBe('0.000001'); + expect(FT.toDecimal(1)).toBe('0.000001'); + expect(FT.toDecimal(BigInt(1))).toBe('0.000001'); + + expect(FT.toDecimal('0')).toBe('0'); + expect(FT.toDecimal(0)).toBe('0'); + expect(FT.toDecimal(BigInt(0))).toBe('0'); +}); + +test('test toDecimal fails on non-integer units', () => { + expect(() => FT.toDecimal('0.1')).toThrow(); + expect(() => FT.toDecimal(0.1)).toThrow(); +}); + +test('test toDecimal fails on non-numeric symbols', () => { + expect(() => FT.toDecimal('1.24n')).toThrow(); + + expect(() => FT.toDecimal('abc192.31')).toThrow(); + + expect(() => FT.toDecimal('abcdefg')).toThrow(); +}); diff --git a/packages/tokens/test/native_token.test.ts b/packages/tokens/test/native_token.test.ts new file mode 100644 index 0000000000..8e37f1b0a3 --- /dev/null +++ b/packages/tokens/test/native_token.test.ts @@ -0,0 +1,105 @@ +import { expect, test } from '@jest/globals'; +import { NEAR } from '../src'; + +test('test toUnits parses formatted amount', () => { + expect(NEAR.toUnits('1.234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits(1.234)).toBe(BigInt('1234000000000000000000000')); + + expect(NEAR.toUnits('1.234567')).toBe(BigInt('1234567000000000000000000')); + expect(NEAR.toUnits(1.234567)).toBe(BigInt('1234567000000000000000000')); + + expect(NEAR.toUnits('1')).toBe(BigInt('1000000000000000000000000')); + expect(NEAR.toUnits(1)).toBe(BigInt('1000000000000000000000000')); + + expect(NEAR.toUnits('00001.234')).toBe(BigInt('1234000000000000000000000')); + + expect(NEAR.toUnits('0000000000000.576919')).toBe( + BigInt('576919000000000000000000') + ); + + // exceeds the precision, rounding down + expect(NEAR.toUnits('0000000000000.5769191111')).toBe( + BigInt('576919111100000000000000') + ); + expect(NEAR.toUnits('.5769191111')).toBe( + BigInt('576919111100000000000000') + ); + + expect(NEAR.toUnits('0')).toBe(BigInt('0')); +}); + +test('test toUnits parses formatted amount with comma', () => { + expect(NEAR.toUnits('1,234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits('1,234567')).toBe(BigInt('1234567000000000000000000')); + + expect(NEAR.toUnits('00001,234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits('0000000000000,576919')).toBe( + BigInt('576919000000000000000000') + ); + // exceeds the precision, rounding down + expect(NEAR.toUnits('0000000000000,5769191111')).toBe( + BigInt('576919111100000000000000') + ); +}); + +test('test toUnits fails on precision of more than 24 decimals', () => { + // there's 25 ones after the point + expect(() => NEAR.toUnits('0.1111111111111111111111111')).toThrow(); +}); + +test('test toUnits fails on invalid numeric string', () => { + expect(() => NEAR.toUnits('2.1.3')).toThrow(); +}); + +test('test toUnits fails on non-numeric symbols', () => { + expect(() => NEAR.toUnits('1.24n')).toThrow(); + + expect(() => NEAR.toUnits('abc192.31')).toThrow(); + + expect(() => NEAR.toUnits('abcdefg')).toThrow(); +}); + +test('test toDecimal formats units', () => { + expect(NEAR.toDecimal('1000000')).toBe('0.000000000000000001'); + expect(NEAR.toDecimal(1_000_000)).toBe('0.000000000000000001'); + expect(NEAR.toDecimal(BigInt(1_000_000))).toBe('0.000000000000000001'); + + expect(NEAR.toDecimal('1000001')).toBe('0.000000000000000001000001'); + expect(NEAR.toDecimal(1_000_001)).toBe('0.000000000000000001000001'); + expect(NEAR.toDecimal(BigInt(1_000_001))).toBe('0.000000000000000001000001'); + + expect(NEAR.toDecimal('1234567')).toBe('0.000000000000000001234567'); + expect(NEAR.toDecimal(1_234_567)).toBe('0.000000000000000001234567'); + expect(NEAR.toDecimal(BigInt(1_234_567))).toBe('0.000000000000000001234567'); + + expect(NEAR.toDecimal('12345678')).toBe('0.000000000000000012345678'); + expect(NEAR.toDecimal(12_345_678)).toBe('0.000000000000000012345678'); + expect(NEAR.toDecimal(BigInt(12_345_678))).toBe( + '0.000000000000000012345678' + ); + + expect(NEAR.toDecimal('710')).toBe('0.00000000000000000000071'); + expect(NEAR.toDecimal(710)).toBe('0.00000000000000000000071'); + expect(NEAR.toDecimal(BigInt(710))).toBe('0.00000000000000000000071'); + + expect(NEAR.toDecimal('1')).toBe('0.000000000000000000000001'); + expect(NEAR.toDecimal(1)).toBe('0.000000000000000000000001'); + expect(NEAR.toDecimal(BigInt(1))).toBe('0.000000000000000000000001'); + + expect(NEAR.toDecimal('0')).toBe('0'); + expect(NEAR.toDecimal(0)).toBe('0'); + expect(NEAR.toDecimal(BigInt(0))).toBe('0'); +}); + +test('test toDecimal fails on non-integer units', () => { + expect(() => NEAR.toDecimal('0.1')).toThrow(); + expect(() => NEAR.toDecimal(0.1)).toThrow(); +}); + +test('test toDecimal fails on non-numeric symbols', () => { + expect(() => NEAR.toDecimal('1.24n')).toThrow(); + + expect(() => NEAR.toDecimal('abc192.31')).toThrow(); + + expect(() => NEAR.toDecimal('abcdefg')).toThrow(); +}); diff --git a/packages/wallet-account/tsconfig.cjs.json b/packages/tokens/tsconfig.cjs.json similarity index 76% rename from packages/wallet-account/tsconfig.cjs.json rename to packages/tokens/tsconfig.cjs.json index 83abd57c4f..ae3708b61c 100644 --- a/packages/wallet-account/tsconfig.cjs.json +++ b/packages/tokens/tsconfig.cjs.json @@ -4,7 +4,5 @@ "outDir": "./lib/commonjs", "lib": ["es2022", "dom"] }, - "files": [ - "src/index.ts" - ] + "include": ["src/**/*"] } diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json new file mode 100644 index 0000000000..f3aef3e523 --- /dev/null +++ b/packages/tokens/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "include": ["src/**/*"] +} diff --git a/packages/wallet-account/typedoc.json b/packages/tokens/typedoc.json similarity index 100% rename from packages/wallet-account/typedoc.json rename to packages/tokens/typedoc.json diff --git a/packages/transactions/package.json b/packages/transactions/package.json index ef1d35176d..cfebf7d493 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -2,8 +2,9 @@ "name": "@near-js/transactions", "version": "1.3.3", "description": "Functions and data types for transactions on NEAR", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -17,7 +18,6 @@ "license": "ISC", "dependencies": { "@near-js/crypto": "workspace:*", - "@near-js/signers": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", "@noble/hashes": "1.7.1", @@ -33,11 +33,18 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index 0c0bff7c6f..79629d989c 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -93,7 +93,6 @@ export function stringifyJsonOrBytes(args: any): Buffer { * @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. */ function functionCall( methodName: string, @@ -101,14 +100,7 @@ function functionCall( gas = 0n, deposit = 0n, stringify = stringifyJsonOrBytes, - jsContract = false ): Action { - if (jsContract) { - return new Action({ - functionCall: new FunctionCall({ methodName, args: args as Uint8Array, gas, deposit }), - }); - } - return new Action({ functionCall: new FunctionCall({ methodName, diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts index 84a063eaba..eaa50a4219 100644 --- a/packages/transactions/src/index.ts +++ b/packages/transactions/src/index.ts @@ -3,5 +3,4 @@ export * from './actions'; export * from './create_transaction'; export * from './delegate'; export * from './schema'; -export * from './sign'; export * from './signature'; diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts deleted file mode 100644 index fd51c2faf3..0000000000 --- a/packages/transactions/src/sign.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Signer } from '@near-js/signers'; -import { sha256 } from '@noble/hashes/sha256'; - -import { Action, SignedDelegate } from './actions'; -import { createTransaction } from './create_transaction'; -import type { DelegateAction } from './delegate'; -import { encodeDelegateAction, encodeTransaction, SignedTransaction, Transaction } from './schema'; -import { Signature } from './signature'; -import { KeyType } from '@near-js/crypto'; - -interface MessageSigner { - sign(message: Uint8Array): Promise; -} - -interface SignDelegateOptions { - delegateAction: DelegateAction; - signer: MessageSigner; -} - -export interface SignedDelegateWithHash { - hash: Uint8Array; - signedDelegateAction: SignedDelegate; -} - -/** - * 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 = encodeTransaction(transaction); - const hash = new Uint8Array(sha256(message)); - const signature = await signer.signMessage(message, accountId, networkId); - const keyType = transaction.publicKey.ed25519Key ? KeyType.ED25519 : KeyType.SECP256K1; - const signedTx = new SignedTransaction({ - transaction, - signature: new Signature({ 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: bigint, 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); - } -} - -/** - * Sign a delegate action - * @options SignDelegate options - * @param options.delegateAction Delegate action to be signed by the meta transaction sender - * @param options.signer Signer instance for the meta transaction sender - */ -export async function signDelegateAction({ delegateAction, signer }: SignDelegateOptions): Promise { - const message = encodeDelegateAction(delegateAction); - const signature = await signer.sign(message); - - const keyType = delegateAction.publicKey.ed25519Key ? KeyType.ED25519 : KeyType.SECP256K1; - const signedDelegateAction = new SignedDelegate({ - delegateAction, - signature: new Signature({ - keyType, - data: signature, - }), - }); - - return { - hash: new Uint8Array(sha256(message)), - signedDelegateAction, - }; -} diff --git a/packages/transactions/test/serialize.test.ts b/packages/transactions/test/serialize.test.ts index 1f5f801119..599520f865 100644 --- a/packages/transactions/test/serialize.test.ts +++ b/packages/transactions/test/serialize.test.ts @@ -1,8 +1,6 @@ import { describe, expect, test } from '@jest/globals'; import { KeyPair, PublicKey } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; -import { baseDecode, baseEncode } from '@near-js/utils'; +import { baseDecode } from '@near-js/utils'; import { deserialize, serialize } from 'borsh'; import * as fs from 'fs'; @@ -10,11 +8,9 @@ import * as fs from 'fs'; import { actionCreators, createTransaction, - decodeSignedTransaction, decodeTransaction, encodeTransaction, SCHEMA, - signTransaction, } from '../src'; const { @@ -61,25 +57,22 @@ test('deserialize delegate', async () => { expect(String(delegateAction.nonce)).toEqual('158895108000003'); }); -test('serialize and sign multi-action tx', async () => { - const keyStore = new InMemoryKeyStore(); +test('serialize multi-action tx', async () => { const keyPair = KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); - await keyStore.setKey('test', 'test.near', keyPair); - // @ts-expect-error test input - const publicKey = keyPair.publicKey; + const publicKey = keyPair.getPublicKey(); const actions = [ createAccount(), deployContract(new Uint8Array([1, 2, 3])), functionCall('qqq', new Uint8Array([1, 2, 3]), 1000n, 1000000n), transfer(123n), stake(1000000n, publicKey), - addKey(publicKey, functionCallAccessKey('zzz', ['www'], null)), + addKey(publicKey, functionCallAccessKey('zzz', ['www'], undefined)), deleteKey(publicKey), deleteAccount('123') ]; const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - const [hash, { transaction }] = await signTransaction('123', 1n, actions, blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); - expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); + const transaction = createTransaction('test.near', publicKey, '123', 1n, actions, blockHash); + // expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); const serialized = Buffer.from(serialize(SCHEMA.Transaction, transaction)); expect(serialized.toString('hex')).toEqual('09000000746573742e6e656172000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80100000000000000030000003132330fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef608000000000103000000010203020300000071717103000000010203e80300000000000040420f00000000000000000000000000037b0000000000000000000000000000000440420f00000000000000000000000000000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef805000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef800000000000000000000030000007a7a7a010000000300000077777706000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80703000000313233'); }); @@ -108,40 +101,6 @@ test('serialize transfer tx', async () => { expect(encodeTransaction(deserialized)).toEqual(serialized); }); -async function createKeyStore() { - const keyStore = new InMemoryKeyStore(); - const keyPair = KeyPair.fromString('ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv'); - await keyStore.setKey('test', 'test.near', keyPair); - return keyStore; -} - -async function verifySignedTransferTx(signedTx) { - expect(Buffer.from(signedTx.signature.ed25519Signature.data).toString('base64')).toEqual('lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ=='); - const serialized = encodeTransaction(signedTx); - expect(Buffer.from(serialized).toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01'); - - const deserialized = decodeSignedTransaction(serialized); - expect(encodeTransaction(deserialized)).toEqual(serialized); -} - -test('serialize and sign transfer tx', async () => { - const transaction = createTransferTx(); - const keyStore = await createKeyStore(); - - const [, signedTx] = await signTransaction(transaction.receiverId, transaction.nonce, transaction.actions, transaction.blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); - - verifySignedTransferTx(signedTx); -}); - -test('serialize and sign transfer tx object', async () => { - const transaction = createTransferTx(); - const keyStore = await createKeyStore(); - - const [, signedTx] = await signTransaction(transaction, new InMemorySigner(keyStore), 'test.near', 'test'); - - verifySignedTransferTx(signedTx); -}); - describe('roundtrip test', () => { const dataDir = './test/data'; const testFiles = fs.readdirSync(dataDir); diff --git a/packages/types/package.json b/packages/types/package.json index d778227a2c..907e8cb55f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -2,8 +2,9 @@ "name": "@near-js/types", "version": "0.3.1", "description": "TypeScript types for working with the Near JS API", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -27,6 +28,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/types/src/accounts.ts b/packages/types/src/accounts.ts new file mode 100644 index 0000000000..d4bf7391dd --- /dev/null +++ b/packages/types/src/accounts.ts @@ -0,0 +1,16 @@ +// TODO: Move Account and Provider to types +interface ProviderLike { + callFunction: (contractId, methodName, args, blockQuery?) => Promise; +} + +export interface AccountLike { + accountId: string; + provider: ProviderLike; + getState(): Promise; + signAndSendTransaction({ + receiverId, actions + }): Promise; + callFunction({ + contractId, methodName, args, gas, deposit + }): Promise; +} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b8f27f715c..61040b35ce 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,3 +1,4 @@ +export * from './accounts'; export * from './assignable'; export * from './enum'; export * from './errors'; diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index eac273dea6..772e9299bc 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -26,6 +26,7 @@ export { ChunkId, ChunkResult, Finality, + FinalityReference, GasPrice, MerkleNode, MerklePath, @@ -36,7 +37,7 @@ export { SyncInfo, TotalWeight, Transaction as ProviderTransaction, - TxExecutionStatus + TxExecutionStatus, } from './protocol'; export { CallFunctionRequest, @@ -53,8 +54,11 @@ export { AccessKeyView, AccessKeyViewRaw, AccountView, + AccountViewRaw, + AccountBalanceInfo, CodeResult, ContractCodeView, + ContractCodeViewRaw, ExecutionError, ExecutionOutcome, ExecutionOutcomeWithId, @@ -68,6 +72,9 @@ export { QueryResponseKind, SerializedReturnValue, ViewStateResult, + ExecutionOutcomeReceiptDetail, + ContractStateView, + CallContractViewFunctionResultRaw, } from './response'; export { CurrentEpochValidatorInfo, diff --git a/packages/types/src/provider/protocol.ts b/packages/types/src/provider/protocol.ts index c7a98235de..5afcc28117 100644 --- a/packages/types/src/provider/protocol.ts +++ b/packages/types/src/provider/protocol.ts @@ -11,17 +11,27 @@ export interface SyncInfo { syncing: boolean; } +interface ValidatorInfo { + account_id: string; + is_slashed: boolean; +} + interface Version { version: string; build: string; + rustc_version: string; } export interface NodeStatusResult { chain_id: string; rpc_addr: string; sync_info: SyncInfo; - validators: string[]; + validators: ValidatorInfo[]; version: Version; + protocol_version: number; + latest_protocol_version: number; + uptime_sec: number; + genesis_hash: string; } export type BlockHash = string; @@ -32,7 +42,12 @@ export type Finality = 'optimistic' | 'near-final' | 'final' export type TxExecutionStatus = 'NONE' | 'INCLUDED' | 'INCLUDED_FINAL' | 'EXECUTED' | 'FINAL' | 'EXECUTED_OPTIMISTIC'; -export type BlockReference = { blockId: BlockId } | { finality: Finality } | { sync_checkpoint: 'genesis' | 'earliest_available' } +export type FinalityReference = { finality: Finality }; + +export type BlockReference = { blockId: BlockId } | FinalityReference | { + /** @deprecated */ + sync_checkpoint: 'genesis' | 'earliest_available' +} export interface TotalWeight { num: number; diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index 7a4155cd42..76fc1e2dca 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -90,7 +90,7 @@ export interface QueryResponseKind { block_hash: BlockHash; } -export interface AccountView extends QueryResponseKind { +export interface AccountViewRaw extends QueryResponseKind { amount: string; locked: string; code_hash: string; @@ -98,6 +98,21 @@ export interface AccountView extends QueryResponseKind { storage_paid_at: BlockHeight; } +export interface AccountView extends QueryResponseKind { + amount: bigint; + locked: bigint; + code_hash: string; + storage_usage: number; + storage_paid_at: BlockHeight; +} + +export interface AccountBalanceInfo { + total: bigint; + usedOnStorage: bigint; + locked: bigint; + available: bigint; +} + interface StateItem { key: string; value: string; @@ -114,11 +129,36 @@ export interface CodeResult extends QueryResponseKind { logs: string[]; } -export interface ContractCodeView extends QueryResponseKind { +export interface CallContractViewFunctionResultRaw extends QueryResponseKind { + result: number[]; + logs: string[]; +} + +export interface CallContractViewFunctionResult extends QueryResponseKind { + result?: string | number | any; + logs: string[]; +} + +interface StateItemView { + key: string; + value: string; + proof: string[]; +} + +export interface ContractStateView extends QueryResponseKind { + values: StateItemView[]; +} + +export interface ContractCodeViewRaw extends QueryResponseKind { code_base64: string; hash: string; } +export interface ContractCodeView extends QueryResponseKind { + code: Uint8Array; + hash: string; +} + export interface FunctionCallPermissionView { FunctionCall: { allowance: string; diff --git a/packages/utils/package.json b/packages/utils/package.json index 054adc9bb8..00e7fa6552 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,8 +2,9 @@ "name": "@near-js/utils", "version": "1.1.0", "description": "Common methods and constants for the NEAR API JavaScript client", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -30,11 +31,16 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/types": "^2.0.0" + }, "files": [ "lib" ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/wallet-account/CHANGELOG.md b/packages/wallet-account/CHANGELOG.md deleted file mode 100644 index 013c5b3c37..0000000000 --- a/packages/wallet-account/CHANGELOG.md +++ /dev/null @@ -1,270 +0,0 @@ -# @near-js/wallet-account - -## 1.3.3 - -### Patch Changes - -- Updated dependencies [[`e408cfc`](https://github.com/near/near-api-js/commit/e408cfc4b4be4ea82e1e34314d9ee92885d03253), [`9cb5f56`](https://github.com/near/near-api-js/commit/9cb5f5621364c370fb2321f6a22dbee146f0f368)]: - - @near-js/providers@1.0.3 - - @near-js/transactions@1.3.3 - - @near-js/accounts@1.4.1 - -## 1.3.2 - -### Patch Changes - -- Updated dependencies [[`ef530cf6`](https://github.com/near/near-api-js/commit/ef530cf62b0ed0d7eb2a77482c4d908449a863c2), [`c85d12d3`](https://github.com/near/near-api-js/commit/c85d12d36b1d8cd9d02178506dcf9cf0598fe7a8)]: - - @near-js/accounts@1.4.0 - - @near-js/providers@1.0.2 - - @near-js/utils@1.1.0 - - @near-js/crypto@1.4.2 - - @near-js/transactions@1.3.2 - - @near-js/keystores@0.2.2 - - @near-js/signers@0.2.2 - -## 1.3.1 - -### Patch Changes - -- Updated dependencies [[`5b0bbbc1`](https://github.com/near/near-api-js/commit/5b0bbbc17ffe7d89d7767e405d2ca700dc2bba40)]: - - @near-js/crypto@1.4.1 - - @near-js/providers@1.0.1 - - @near-js/types@0.3.1 - - @near-js/utils@1.0.1 - - @near-js/accounts@1.3.1 - - @near-js/keystores@0.2.1 - - @near-js/signers@0.2.1 - - @near-js/transactions@1.3.1 - -## 1.3.0 - -### Minor Changes - -- [#1353](https://github.com/near/near-api-js/pull/1353) [`73690557`](https://github.com/near/near-api-js/commit/73690557c8e2a74386fca62f4ae123abe0651403) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Update to Node.js 20 LTS & pnpm 9.4, modularize packages, simplify dependencies, and update tests - - **Breaking Changes** - - - `near-api-js@5.0.0` - - - The following functions are no longer exported: - - `logWarning` - - `fetchJson` - - the unnamed wrapped `fetch` function exported from `setup-node-fetch.ts` - - The browser bundle is no longer being built in version 5; for browser support please use modules - - - `@near-js/providers@1.0.0` - - - The following functions are no longer exported: - - `fetchJson` - - - `@near-js/utils@1.0.0` - - The following functions are no longer exported: - - `logWarning` - -### Patch Changes - -- Updated dependencies [[`73690557`](https://github.com/near/near-api-js/commit/73690557c8e2a74386fca62f4ae123abe0651403)]: - - @near-js/accounts@1.3.0 - - @near-js/crypto@1.4.0 - - @near-js/keystores@0.2.0 - - @near-js/providers@1.0.0 - - @near-js/signers@0.2.0 - - @near-js/transactions@1.3.0 - - @near-js/types@0.3.0 - - @near-js/utils@1.0.0 - -## 1.2.3 - -### Patch Changes - -- [#1355](https://github.com/near/near-api-js/pull/1355) [`7d5a8244`](https://github.com/near/near-api-js/commit/7d5a8244a1683d7b5e82c4da1e40d834167a9a41) Thanks [@gtsonevv](https://github.com/gtsonevv)! - Add Secp256k1 support - -- Updated dependencies [[`9c7db11c`](https://github.com/near/near-api-js/commit/9c7db11c3c5c031a749dd72d5140f58056570e36), [`bad95007`](https://github.com/near/near-api-js/commit/bad95007edde4ed9d5989ded7f2032b9f15f5c23), [`7d5a8244`](https://github.com/near/near-api-js/commit/7d5a8244a1683d7b5e82c4da1e40d834167a9a41)]: - - @near-js/keystores@0.1.0 - - @near-js/utils@0.3.0 - - @near-js/crypto@1.3.0 - - @near-js/accounts@1.2.2 - - @near-js/signers@0.1.5 - - @near-js/transactions@1.2.3 - - @near-js/providers@0.2.3 - -## 1.2.2 - -### Patch Changes - -- Updated dependencies [[`ecdf1741`](https://github.com/near/near-api-js/commit/ecdf1741fb692e71202c541c5b3692790baa65f0), [`92a6f5be`](https://github.com/near/near-api-js/commit/92a6f5be3f4b7be6f3e9b55077025921c3aad2cb)]: - - @near-js/providers@0.2.2 - - @near-js/types@0.2.1 - - @near-js/accounts@1.2.1 - - @near-js/crypto@1.2.4 - - @near-js/keystores@0.0.12 - - @near-js/transactions@1.2.2 - - @near-js/utils@0.2.2 - - @near-js/signers@0.1.4 - -## 1.2.1 - -### Patch Changes - -- Updated dependencies [[`06baa81d`](https://github.com/near/near-api-js/commit/06baa81dc604cfe0463476de7a4dcdd39a6f716a)]: - - @near-js/accounts@1.2.0 - - @near-js/types@0.2.0 - - @near-js/crypto@1.2.3 - - @near-js/keystores@0.0.11 - - @near-js/providers@0.2.1 - - @near-js/transactions@1.2.1 - - @near-js/utils@0.2.1 - - @near-js/signers@0.1.3 - -## 1.2.0 - -### Minor Changes - -- [#1334](https://github.com/near/near-api-js/pull/1334) [`3f363113`](https://github.com/near/near-api-js/commit/3f363113e102d0acf29b7b2635acf6160a028cc3) Thanks [@denbite](https://github.com/denbite)! - Introduce FailoverRpcProvider that switches between providers in case of a failure of one of them - -### Patch Changes - -- [#1223](https://github.com/near/near-api-js/pull/1223) [`9060b781`](https://github.com/near/near-api-js/commit/9060b7811668d71bdf21170273a42842c3691f9b) Thanks [@gtsonevv](https://github.com/gtsonevv)! - Replace bn.js by BigInt. - -- Updated dependencies [[`9060b781`](https://github.com/near/near-api-js/commit/9060b7811668d71bdf21170273a42842c3691f9b), [`cc401a6c`](https://github.com/near/near-api-js/commit/cc401a6c893398e2185c35765ca316f68ac86074), [`3f363113`](https://github.com/near/near-api-js/commit/3f363113e102d0acf29b7b2635acf6160a028cc3), [`f739324b`](https://github.com/near/near-api-js/commit/f739324b2959712281d957eb26a09e5d68e32c80)]: - - @near-js/accounts@1.1.0 - - @near-js/transactions@1.2.0 - - @near-js/types@0.1.0 - - @near-js/utils@0.2.0 - - @near-js/crypto@1.2.2 - - @near-js/providers@0.2.0 - - @near-js/keystores@0.0.10 - - @near-js/signers@0.1.2 - -## 1.1.1 - -### Patch Changes - -- Updated dependencies [[`42dc7e2a`](https://github.com/near/near-api-js/commit/42dc7e2ac794e973987bed7b89da5ef2d3c6c8ac)]: - - @near-js/transactions@1.1.2 - - @near-js/accounts@1.0.4 - -## 1.1.0 - -### Minor Changes - -- [#1260](https://github.com/near/near-api-js/pull/1260) [`15885dd1`](https://github.com/near/near-api-js/commit/15885dd10ba9b562043a36dc80c449b7c3588313) Thanks [@denbite](https://github.com/denbite)! - Introduce methods to construct URLs to the wallet without instant redirect - -### Patch Changes - -- Updated dependencies [[`662cc13d`](https://github.com/near/near-api-js/commit/662cc13d7961c3bdabed3ad51b1c57958739a3e6), [`c4655576`](https://github.com/near/near-api-js/commit/c4655576bacb1d8b85030dca5b9443649621c8ee)]: - - @near-js/utils@0.1.0 - - @near-js/crypto@1.2.1 - - @near-js/accounts@1.0.3 - - @near-js/transactions@1.1.1 - - @near-js/keystores@0.0.9 - - @near-js/signers@0.1.1 - -## 1.0.2 - -### Patch Changes - -- Updated dependencies [[`1900c490`](https://github.com/near/near-api-js/commit/1900c49060c3ea8279448cead7347049a23f421f), [`c6cdc8c7`](https://github.com/near/near-api-js/commit/c6cdc8c724a6dd53114cc5f53fd58e57cea86b78)]: - - @near-js/crypto@1.2.0 - - @near-js/signers@0.1.0 - - @near-js/transactions@1.1.0 - - @near-js/accounts@1.0.2 - - @near-js/keystores@0.0.8 - -## 1.0.1 - -### Patch Changes - -- Updated dependencies [[`aeeb1502`](https://github.com/near/near-api-js/commit/aeeb15022a1c1deb99114eba0473739b0998fc50)]: - - @near-js/crypto@1.1.0 - - @near-js/accounts@1.0.1 - - @near-js/keystores@0.0.7 - - @near-js/signers@0.0.7 - - @near-js/transactions@1.0.1 - -## 1.0.0 - -### Major Changes - -- [#1168](https://github.com/near/near-api-js/pull/1168) [`61349aec`](https://github.com/near/near-api-js/commit/61349aeca3af830f702b24654e0f13cd428192d8) Thanks [@gagdiez](https://github.com/gagdiez)! - feat: updated borsh-js to v1.0.1 - -### Patch Changes - -- [#1215](https://github.com/near/near-api-js/pull/1215) [`ecf29e2d`](https://github.com/near/near-api-js/commit/ecf29e2d56611a7773c79d5bb5bd20c8b7e738ea) Thanks [@denbite](https://github.com/denbite)! - Internal logging library with capabilities for integration with modern logging libraries like Pino, Winston, etc - -- Updated dependencies [[`0f764ee0`](https://github.com/near/near-api-js/commit/0f764ee03b5747fbf8a971c7b04ef8326238a1d0), [`695220e7`](https://github.com/near/near-api-js/commit/695220e75bc43834a7700cfc5491a7eebd324947), [`ecf29e2d`](https://github.com/near/near-api-js/commit/ecf29e2d56611a7773c79d5bb5bd20c8b7e738ea), [`61349aec`](https://github.com/near/near-api-js/commit/61349aeca3af830f702b24654e0f13cd428192d8), [`cdd8d1c8`](https://github.com/near/near-api-js/commit/cdd8d1c8c37db641bd995b2c470ad0b4fdddb93f)]: - - @near-js/accounts@1.0.0 - - @near-js/utils@0.0.5 - - @near-js/crypto@1.0.0 - - @near-js/transactions@1.0.0 - - @near-js/keystores@0.0.6 - - @near-js/signers@0.0.6 - -## 0.0.7 - -### Patch Changes - -- Updated dependencies [[`40fa6465`](https://github.com/near/near-api-js/commit/40fa64654fdaf3b463122c35521a6f72282974f2)]: - - @near-js/crypto@0.0.5 - - @near-js/accounts@0.1.4 - - @near-js/keystores@0.0.5 - - @near-js/signers@0.0.5 - - @near-js/transactions@0.2.1 - -## 0.0.6 - -### Patch Changes - -- Updated dependencies [[`e21ff896`](https://github.com/near/near-api-js/commit/e21ff89601c858fb703169e3bb53c6d96cff5342), [`00b4d2ba`](https://github.com/near/near-api-js/commit/00b4d2ba3f9f3a1f90343e34cb9bde8cdb607ceb)]: - - @near-js/transactions@0.2.0 - - @near-js/accounts@0.1.3 - -## 0.0.5 - -### Patch Changes - -- [#1124](https://github.com/near/near-api-js/pull/1124) [`c1dcf3b8`](https://github.com/near/near-api-js/commit/c1dcf3b8207e7de358e1d711d55da938d5d9ff8d) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Allow use of legacy `deps.keyStore` path in NearConfig - -- Updated dependencies [[`bf81ddc1`](https://github.com/near/near-api-js/commit/bf81ddc11c958dece2244798bdfa6ab11e653940)]: - - @near-js/types@0.0.4 - - @near-js/accounts@0.1.2 - - @near-js/crypto@0.0.4 - - @near-js/keystores@0.0.4 - - @near-js/transactions@0.1.1 - - @near-js/utils@0.0.4 - - @near-js/signers@0.0.4 - -## 0.0.4 - -### Patch Changes - -- Updated dependencies []: - - @near-js/accounts@0.1.1 - -## 0.0.3 - -### Patch Changes - -- Updated dependencies [[`b713ae78`](https://github.com/near/near-api-js/commit/b713ae78969d530e7e69e21e315e3d3fdb5e68e9), [`bc53c32c`](https://github.com/near/near-api-js/commit/bc53c32c80694e6f22d9be97db44603591f0026b), [`b7b6c6a1`](https://github.com/near/near-api-js/commit/b7b6c6a1448050f60f6498f799654204f1998b91), [`d97d2a6e`](https://github.com/near/near-api-js/commit/d97d2a6e891350cdea418da2af2b2971bdf0467e), [`8c6bf391`](https://github.com/near/near-api-js/commit/8c6bf391a01af9adb81cb8731c45bdb17f5291c0)]: - - @near-js/types@0.0.3 - - @near-js/accounts@0.1.0 - - @near-js/transactions@0.1.0 - - @near-js/crypto@0.0.3 - - @near-js/keystores@0.0.3 - - @near-js/utils@0.0.3 - - @near-js/signers@0.0.3 - -## 0.0.2 - -### Patch Changes - -- [#1091](https://github.com/near/near-api-js/pull/1091) [`ca458cac`](https://github.com/near/near-api-js/commit/ca458cac683fab614b77eb5daa160e03b0640350) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Only include contents of lib/ in NPM packages - -- Updated dependencies [[`ca458cac`](https://github.com/near/near-api-js/commit/ca458cac683fab614b77eb5daa160e03b0640350)]: - - @near-js/accounts@0.0.2 - - @near-js/crypto@0.0.2 - - @near-js/keystores@0.0.2 - - @near-js/signers@0.0.2 - - @near-js/transactions@0.0.2 - - @near-js/types@0.0.2 - - @near-js/utils@0.0.2 diff --git a/packages/wallet-account/README.md b/packages/wallet-account/README.md deleted file mode 100644 index ad4a9f6635..0000000000 --- a/packages/wallet-account/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @near-js/wallet-account - -A collection of classes and types for working with accounts within browser-based wallets. - -## Modules - -- [Near](https://github.com/near/near-api-js/blob/master/packages/wallet-account/src/near.ts) a general purpose class for configuring and working with the NEAR blockchain -- [WalletAccount](https://github.com/near/near-api-js/blob/master/packages/wallet-account/src/wallet_account.ts) an [Account](https://github.com/near/near-api-js/blob/master/packages/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. diff --git a/packages/wallet-account/package.json b/packages/wallet-account/package.json deleted file mode 100644 index 038c5da39d..0000000000 --- a/packages/wallet-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@near-js/wallet-account", - "version": "1.3.3", - "description": "Dependencies for the NEAR API JavaScript client in the browser", - "main": "lib/esm/index.js", - "type": "module", - "scripts": { - "build": "pnpm compile:esm && pnpm compile:cjs", - "compile:esm": "tsc -p tsconfig.json", - "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", - "test": "jest" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@near-js/accounts": "workspace:*", - "@near-js/crypto": "workspace:*", - "@near-js/keystores": "workspace:*", - "@near-js/providers": "workspace:*", - "@near-js/signers": "workspace:*", - "@near-js/transactions": "workspace:*", - "@near-js/types": "workspace:*", - "@near-js/utils": "workspace:*", - "borsh": "1.0.0" - }, - "devDependencies": { - "@jest/globals": "^29.7.0", - "@types/node": "20.0.0", - "build": "workspace:*", - "jest": "29.7.0", - "localstorage-memory": "1.0.3", - "ts-jest": "29.2.6", - "tsconfig": "workspace:*", - "typescript": "5.4.5" - }, - "files": [ - "lib" - ], - "exports": { - "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" - } -} diff --git a/packages/wallet-account/src/index.ts b/packages/wallet-account/src/index.ts deleted file mode 100644 index ea0f840b63..0000000000 --- a/packages/wallet-account/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Near, NearConfig } from './near'; -export { ConnectedWalletAccount, WalletConnection } from './wallet_account'; \ No newline at end of file diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts deleted file mode 100644 index 30bef48c08..0000000000 --- a/packages/wallet-account/src/near.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * This module contains the main class developers will use to interact with NEAR. - * The {@link Near} class is used to interact with {@link "@near-js/accounts".account.Account | Account} through the {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | 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 { PublicKey } from '@near-js/crypto'; -import { KeyStore } from '@near-js/keystores'; -import { Signer } from '@near-js/signers'; -import { LoggerService } from '@near-js/utils'; -import { Provider } from '@near-js/providers'; - -export interface NearConfig { - /** Holds {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} 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 UrlAccountCreator} - */ - helperUrl?: string; - - /** - * The balance transferred from the {@link NearConfig#masterAccount} to a created account - * @see {@link LocalAccountCreator} - */ - initialBalance?: string; - - /** - * The account to use when creating new accounts - * @see {@link LocalAccountCreator} - */ - masterAccount?: string; - - /** - * {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} are stored in a {@link KeyStore} under the `networkId` namespace. - */ - networkId: string; - - /** - * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} - */ - nodeUrl: string; - - /** - * NEAR RPC API headers. Can be used to pass API KEY and other parameters. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | 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; - - /** - * Backward-compatibility for older versions - */ - deps?: { keyStore: KeyStore }; - /** - * Specifies the logger to use. Pass `false` to turn off logging. - */ - logger?: LoggerService | false; - /** - * Specifies NEAR RPC API connections, and is used to make JSON RPC calls to interact with NEAR - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} - * @see {@link "@near-js/providers".json-rpc-provider.FailoverRpcProvider | FailoverRpcProvider} - * @see [List of available and public JSON RPC endpoints](https://docs.near.org/api/rpc/providers) - */ - provider?: Provider; -} - -/** - * 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: config.provider || { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, - signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore || config.deps?.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 ? BigInt(config.initialBalance) : 500000000000000000000000000n; - 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 AccountCreator}. Either: - * * using a masterAccount with {@link LocalAccountCreator} - * * using the helperUrl with {@link 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/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts deleted file mode 100644 index ca60eed4b0..0000000000 --- a/packages/wallet-account/src/wallet_account.ts +++ /dev/null @@ -1,454 +0,0 @@ -/** - * 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 "@near-js/accounts".account.Account | Account} implementation that uses {@link WalletConnection} to get keys - * - * @module walletAccount - */ -import { - Account, - Connection, - SignAndSendTransactionOptions, -} from '@near-js/accounts'; -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'; -import { baseDecode } from '@near-js/utils'; -import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transactions'; -import { 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; - keyType: 'ed25519' | 'secp256k1' -} - -/** - * 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 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) { - 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; - 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 || ''; - } - - /** - * Constructs string URL 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'); - * // return string URL to the NEAR Wallet - * const url = await wallet.requestSignInUrl({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignInUrl({contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519'}: SignInOptions): Promise { - 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(keyType); - 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); - }); - } - - return newUrl.toString(); - } - - /** - * 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(options: SignInOptions) { - const url = await this.requestSignInUrl(options); - - window.location.assign(url); - } - - /** - * Constructs string URL to the wallet to sign a transaction or batch of transactions. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactionsUrl({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): string { - const currentUrl = new URL(window.location.href); - const newUrl = new URL('sign', this._walletBaseUrl); - - newUrl.searchParams.set('transactions', transactions - .map(transaction => serialize(SCHEMA.Transaction, transaction)) - .map(serialized => Buffer.from(serialized).toString('base64')) - .join(',')); - newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); - if (meta) newUrl.searchParams.set('meta', meta); - - return newUrl.toString(); - } - - /** - * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the wallet. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactions(options: RequestSignTransactionsOptions): void { - const url = this.requestSignTransactionsUrl(options); - - window.location.assign(url); - } - - /** - * @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} 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} - * @param options An optional options object - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions An array of transaction actions to be performed. - * @param options.walletMeta Additional metadata to be included in the wallet signing request. - * @param options.walletCallbackUrl URL to redirect upon completion of the wallet signing process. Default: current URL. - */ - 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 + 1n; - 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 TransactionQueue 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/wallet-account/test/wallet_account.test.ts b/packages/wallet-account/test/wallet_account.test.ts deleted file mode 100644 index 43a6a3667a..0000000000 --- a/packages/wallet-account/test/wallet_account.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createTransactions } from './wallet_account'; - -createTransactions(); diff --git a/packages/wallet-account/test/wallet_account.ts b/packages/wallet-account/test/wallet_account.ts deleted file mode 100644 index 820533eed4..0000000000 --- a/packages/wallet-account/test/wallet_account.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { beforeEach, describe, expect, it } from '@jest/globals'; -import { KeyPair } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; -import { actionCreators } from '@near-js/transactions'; -import localStorage from 'localstorage-memory'; -import { WalletConnection } from '../src'; - - -const { functionCall } = 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'; - -let lastTransaction; - -// @ts-ignore -global.window = { - localStorage -}; -// @ts-ignore -global.document = { - title: 'documentTitle' -}; - -let history; -let nearFake; -let walletConnection; -let keyStore = new InMemoryKeyStore(); -export const createTransactions = () => { - beforeEach(() => { - keyStore.clear(); - nearFake = { - config: { - networkId: 'networkId', - contractName: 'contractId', - walletUrl: 'http://example.com/wallet', - }, - connection: { - networkId: 'networkId', - signer: new InMemorySigner(keyStore) - }, - account() { - return { - state() {} - }; - } - }; - history = []; - Object.assign(global.window, { - location: { - href: 'http://example.com/location', - }, - history: { - replaceState: (state, title, url) => history.push([state, title, url]) - } - }); - walletConnection = new WalletConnection(nearFake, ''); - }); - - it('not signed in by default', () => { - expect(walletConnection.isSignedIn()).not.toBeTruthy(); - }); - - it('throws if non string appKeyPrefix', () => { - // @ts-ignore - expect(() => new WalletConnection(nearFake)).toThrow(/appKeyPrefix/); -// @ts-ignore - expect(() => new WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); - expect(() => new WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); - expect(() => new WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); - }); - - const BLOCK_HASH = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; - - 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; - } - } - } - throw new Error(`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 with 2fa access key', () => { - beforeEach(async () => { - let localKeyPair = KeyPair.fromRandom('ed25519'); - let walletKeyPair = KeyPair.fromRandom('ed25519'); - setupWalletConnectionForSigning({ - // @ts-ignore - allKeys: [ walletKeyPair.publicKey.toString() ], - accountAccessKeys: [{ - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: '1000000000', - receiver_id: 'signer.near', - method_names: [MULTISIG_HAS_METHOD] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }] - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it('V2', async () => { - const res = await walletConnection.account().signAndSendTransaction({ - receiverId: 'receiver.near', - actions: [functionCall('someMethod', new Uint8Array(), 1n, 1n)] - }); - - // 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'); - }); - }); - - describe('fails requests transaction signing without 2fa access key', () => { - beforeEach(async () => { - const localKeyPair = KeyPair.fromRandom('ed25519'); - const walletKeyPair = KeyPair.fromRandom('ed25519'); - setupWalletConnectionForSigning({ - // @ts-ignore - allKeys: [ walletKeyPair.publicKey.toString() ], - accountAccessKeys: [{ - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: '1000000000', - receiver_id: 'signer.near', - method_names: ['not_a_valid_2fa_method'] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }] - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it('V2', () => { - return expect( - walletConnection.account().signAndSendTransaction({ - receiverId: 'receiver.near', - actions: [functionCall('someMethod', new Uint8Array(), 1n, 1n)] - }) - ).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 = 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: [] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }] - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it.each([ - functionCall('someMethod', new Uint8Array(), 1n, 0n), - functionCall('someMethod', new Uint8Array(), 1n), - 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', - } - }] - } - }); - }); - }); -} \ No newline at end of file diff --git a/packages/wallet-account/test/wallet_accounts.test.ts b/packages/wallet-account/test/wallet_accounts.test.ts deleted file mode 100644 index e4f610a842..0000000000 --- a/packages/wallet-account/test/wallet_accounts.test.ts +++ /dev/null @@ -1,532 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; -import { KeyPair, PublicKey } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { baseDecode } from '@near-js/utils'; -import { InMemorySigner } from '@near-js/signers'; -import { actionCreators, createTransaction, SCHEMA } from '@near-js/transactions'; -import { deserialize } from 'borsh'; -import localStorage from 'localstorage-memory'; -import * as url from 'url'; -import { WalletConnection } from '../src'; - - -const { functionCall, transfer } = actionCreators; - -let lastRedirectUrl; - -// @ts-expect-error test input -global.window = { - localStorage -}; -// @ts-expect-error test input -global.document = { - title: 'documentTitle' -}; - -let history; -let nearFake; -let walletConnection; -let keyStore = new InMemoryKeyStore(); -describe("Wallet account tests", () => { - beforeEach(() => { - keyStore.clear(); - nearFake = { - config: { - networkId: 'networkId', - contractName: 'contractId', - walletUrl: 'http://example.com/wallet', - }, - connection: { - networkId: 'networkId', - signer: new 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 WalletConnection(nearFake, ''); - }); - - 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 WalletConnection(nearFake, "")).not.toThrowError(); - }); - - it("throws if non string appKeyPrefix in server context", () => { - // @ts-expect-error test input - expect(() => new WalletConnection(nearFake)).toThrow( - /appKeyPrefix/ - ); - // @ts-expect-error invalid type check - 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", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(serverWalletConnection.getAccountId()).toEqual(""); - }); - - it("returns false as isSignedIn", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(serverWalletConnection.isSignedIn()).toEqual(false); - }); - - it("throws explicit error when calling other methods on the instance", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(() => - serverWalletConnection.requestSignIn( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignInUrl( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignTransactions( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignTransactionsUrl( - "signInContract", - // @ts-expect-error test input - "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 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]) - // @ts-expect-error test input - ).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]) - // @ts-expect-error test input - ).publicKey.toString(), - }, - }); - }); - - it("can complete sign in", async () => { - const keyPair = KeyPair.fromRandom("ed25519"); - // @ts-expect-error test input - global.window.location.href = `http://example.com/location?account_id=near.account&public_key=${keyPair.publicKey}`; - await keyStore.setKey( - "networkId", - // @ts-expect-error test input - "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 = KeyPair.fromRandom("ed25519"); - // @ts-expect-error test input - global.window.location.href = `http://example.com/location?account_id=near2.account&public_key=${keyPair.publicKey}`; - await keyStore.setKey( - "networkId", - // @ts-expect-error test input - "pending_key" + keyPair.publicKey, - keyPair - ); - - const newWalletConn = new 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 = baseDecode(BLOCK_HASH); - function createTransferTx() { - const actions = [transfer(1n)]; - return createTransaction( - "test.near", - 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 - // @ts-expect-error test input - .split(",") - .map((txBase64) => - deserialize(SCHEMA.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; - } - } - } - throw new Error(`Unexpected query: ${JSON.stringify(params)}`); - }, - sendTransaction() { - 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 = KeyPair.fromRandom("ed25519"); - let transactions; - beforeEach(() => { - setupWalletConnectionForSigning({ - // @ts-expect-error test input - allKeys: ["no_such_access_key", keyPair.publicKey.toString()], - accountAccessKeys: [ - { - access_key: { - nonce: 1, - permission: "FullAccess", - }, - // @ts-expect-error test input - public_key: keyPair.publicKey.toString(), - }, - ], - }); - }); - - it("V2", async () => { - let failed = true; - try { - await walletConnection.account().signAndSendTransaction({ - receiverId: "receiver.near", - actions: [transfer(1n)], - walletCallbackUrl: "http://callback.com/callback", - }); - failed = false; - } catch (e) { - expect(e.message).toEqual( - "Failed to redirect to sign transaction" - ); - } - if (!failed) { - throw new Error("expected to throw"); - } - 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"); - const txData = transactions[0].publicKey.ed25519Key.data ? transactions[0].publicKey.ed25519Key.data : transactions[0].publicKey.secp256k1Key.data; - const publicKey = keyPair.getPublicKey(); - const keyPairData = publicKey.ed25519Key.data ? publicKey.ed25519Key.data : publicKey.secp256k1Key.data; - expect(Buffer.from(txData)).toEqual( - Buffer.from(keyPairData) - ); - }); - }); - - describe("requests transaction signing automatically when function call has attached deposit", () => { - beforeEach(async () => { - const localKeyPair = KeyPair.fromRandom("ed25519"); - const walletKeyPair = KeyPair.fromRandom("ed25519"); - setupWalletConnectionForSigning({ - // @ts-expect-error test input - allKeys: [walletKeyPair.publicKey.toString()], - accountAccessKeys: [ - { - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: "1000000000", - receiver_id: "receiver.near", - method_names: [], - }, - }, - }, - // @ts-expect-error test input - public_key: localKeyPair.publicKey.toString(), - }, - { - access_key: { - nonce: 1, - permission: "FullAccess", - }, - // @ts-expect-error test input - public_key: walletKeyPair.publicKey.toString(), - }, - ], - }); - await keyStore.setKey("networkId", "signer.near", localKeyPair); - }); - - it("V2", async () => { - let failed = true; - try { - await walletConnection.account().signAndSendTransaction({ - receiverId: "receiver.near", - actions: [ - functionCall( - "someMethod", - new Uint8Array(), - 1n, - 1n - ), - ], - walletCallbackUrl: "http://example.com/after", - walletMeta: "someStuff", - }); - failed = false; - } catch (e) { - expect(e.message).toEqual( - "Failed to redirect to sign transaction" - ); - } - - if (!failed) { - throw new Error("expected to throw"); - } - - const transactions = parseTransactionsFromUrl( - lastRedirectUrl, - "http://example.com/after" - ); - expect(transactions).toHaveLength(1); - }); - }); -}); diff --git a/packages/wallet-account/tsconfig.json b/packages/wallet-account/tsconfig.json deleted file mode 100644 index c228e5cdfe..0000000000 --- a/packages/wallet-account/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "tsconfig/browser.json", - "compilerOptions": { - "preserveSymlinks": false, - "outDir": "./lib/esm", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7df11efa0..92570fd405 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: near-sandbox: 0.0.18 - near-api-js: 4.0.0 + near-api-js: workspace:* importers: @@ -66,6 +66,9 @@ importers: '@near-js/signers': specifier: workspace:* version: link:../signers + '@near-js/tokens': + specifier: workspace:* + version: link:../tokens '@near-js/transactions': specifier: workspace:* version: link:../transactions @@ -103,6 +106,9 @@ importers: '@scure/base': specifier: ^1.2.4 version: 1.2.4 + '@types/depd': + specifier: ^1.1.37 + version: 1.1.37 '@types/json-schema': specifier: ^7.0.15 version: 7.0.15 @@ -119,8 +125,8 @@ importers: specifier: 0.5.1 version: 0.5.1 near-workspaces: - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + specifier: 5.0.0 + version: 5.0.0 semver: specifier: 7.7.1 version: 7.7.1 @@ -187,6 +193,9 @@ importers: packages/client: dependencies: + '@near-js/accounts': + specifier: workspace:* + version: link:../accounts '@near-js/crypto': specifier: workspace:* version: link:../crypto @@ -252,8 +261,8 @@ importers: specifier: 0.6.0 version: 0.6.0 near-api-js: - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + specifier: workspace:* + version: link:../near-api-js ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.9)(typescript@5.4.5) @@ -454,9 +463,6 @@ importers: '@near-js/utils': specifier: workspace:* version: link:../utils - '@near-js/wallet-account': - specifier: workspace:* - version: link:../wallet-account '@noble/curves': specifier: 1.8.1 version: 1.8.1 @@ -495,8 +501,8 @@ importers: specifier: 0.5.1 version: 0.5.1 near-workspaces: - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + specifier: 5.0.0 + version: 5.0.0 rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -509,6 +515,9 @@ importers: packages/providers: dependencies: + '@near-js/crypto': + specifier: workspace:* + version: link:../crypto '@near-js/transactions': specifier: workspace:* version: link:../transactions @@ -538,8 +547,8 @@ importers: specifier: 29.7.0 version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) near-workspaces: - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + specifier: 5.0.0 + version: 5.0.0 ts-jest: specifier: 29.2.6 version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) @@ -562,13 +571,46 @@ importers: '@near-js/keystores': specifier: workspace:* version: link:../keystores + '@near-js/transactions': + specifier: workspace:* + version: link:../transactions '@noble/hashes': specifier: 1.7.1 version: 1.7.1 + borsh: + specifier: 1.0.0 + version: 1.0.0 + devDependencies: + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 + '@types/node': + specifier: 20.0.0 + version: 20.0.0 + build: + specifier: workspace:* + version: link:../build + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) + ts-jest: + specifier: 29.2.6 + version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: 5.4.5 + version: 5.4.5 + + packages/tokens: devDependencies: '@jest/globals': specifier: ^29.7.0 version: 29.7.0 + '@near-js/types': + specifier: workspace:* + version: link:../types '@types/node': specifier: 20.0.0 version: 20.0.0 @@ -593,9 +635,6 @@ importers: '@near-js/crypto': specifier: workspace:* version: link:../crypto - '@near-js/signers': - specifier: workspace:* - version: link:../signers '@near-js/types': specifier: workspace:* version: link:../types @@ -694,61 +733,6 @@ importers: specifier: 5.4.5 version: 5.4.5 - packages/wallet-account: - dependencies: - '@near-js/accounts': - specifier: workspace:* - version: link:../accounts - '@near-js/crypto': - specifier: workspace:* - version: link:../crypto - '@near-js/keystores': - specifier: workspace:* - version: link:../keystores - '@near-js/providers': - specifier: workspace:* - version: link:../providers - '@near-js/signers': - specifier: workspace:* - version: link:../signers - '@near-js/transactions': - specifier: workspace:* - version: link:../transactions - '@near-js/types': - specifier: workspace:* - version: link:../types - '@near-js/utils': - specifier: workspace:* - version: link:../utils - borsh: - specifier: 1.0.0 - version: 1.0.0 - devDependencies: - '@jest/globals': - specifier: ^29.7.0 - version: 29.7.0 - '@types/node': - specifier: 20.0.0 - version: 20.0.0 - build: - specifier: workspace:* - version: link:../build - jest: - specifier: 29.7.0 - version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) - localstorage-memory: - specifier: 1.0.3 - version: 1.0.3 - ts-jest: - specifier: 29.2.6 - version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) - tsconfig: - specifier: workspace:* - version: link:../tsconfig - typescript: - specifier: 5.4.5 - version: 5.4.5 - packages: '@ampproject/remapping@2.3.0': @@ -1436,54 +1420,10 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@near-js/accounts@1.1.0': - resolution: {integrity: sha512-YYudGgVepuWsLfusslj423IqH31A1YNS10ETgrg5zT8HEYUPpkevsdCAKslaihjQUAV6TVZ6bb3BjT7skoDMOg==} - - '@near-js/crypto@1.2.2': - resolution: {integrity: sha512-a/1SICBPg6zo+VGOy+HT448+uHXmlYnP4vwNUdJnmn0yMksi30G1N8BSU5tnP1SX904EASu1pdqSMWslERrCQw==} - - '@near-js/keystores-browser@0.0.10': - resolution: {integrity: sha512-JX9WbpPnlKQCxEXJJ/AZHj2z2yuR/UnHcUPszz+Q2v1moL5wETBSuwe6jQOfmYZoM+vGU2Vh4fz3V8Ml/Oz3bw==} - - '@near-js/keystores-node@0.0.10': - resolution: {integrity: sha512-fM5T+1pe1zHsaBwTW0JzPd5U93u6oGzUheg8Xpj8vEjSvISd9kreY14IQkbtXCtzsTsiIel05mIqHgmg35QNjg==} - - '@near-js/keystores@0.0.10': - resolution: {integrity: sha512-rzGMkqY7EcIbUPrcSjK1RJi3dTXusfmZcxg2Jcc9u7VVjEjP/HVnvVaazsYC1la7812VSrDPxqHsZRzWSFxtMA==} - - '@near-js/providers@0.2.0': - resolution: {integrity: sha512-K7RJTkVbn6SD68p/8TkxFjp6jhxNQrkjZ+etQxrBRAMS6kupLTobV+9AfAc1OaORO47X873p4BRhbm9KhCdmZg==} - - '@near-js/signers@0.1.2': - resolution: {integrity: sha512-Echz+ldAFUDGntiBcnCZN4scHBIccz6xVC0Zt9cZu9I4SGKrWga0vNfAwXGVGep8YCEu8MwI9lE2B5n2ouc6kg==} - - '@near-js/transactions@1.2.0': - resolution: {integrity: sha512-I9UVVPg0HHQUpL17tb9L1HgTMG5+KREI2mNQlvJhF5uE6DrI2tC/O4rQf3HZOUVWpUhOiXSKRfSXpgZq5TbXaw==} - - '@near-js/types@0.1.0': - resolution: {integrity: sha512-uQTB3G7251cKCFhM4poAgypTODb83jrqD5A5B0Nr89TAGbsYM2ozXJyffJpsWedbYhK527Jx/BFgs+Jzf3oO5g==} - - '@near-js/utils@0.2.0': - resolution: {integrity: sha512-Ul0NoOiV/vW6hnkYVMwRNbP18hB7mCRkqgSe1a2Qoe+3xFOMnjLVVod3Y1l/QXgp4yNDrLOd6r4PDPI72eKUww==} - - '@near-js/wallet-account@1.2.0': - resolution: {integrity: sha512-3y0VxE2R2FxAHFJpRkhqIp+ZTc+/n42u3qSH0vzTTihzL2zfGF6z9Y2ArNQ7lQKNyiXd0vsFdAIwVcX5pEfq3A==} - - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.8.1': resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - - '@noble/hashes@1.3.3': - resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} - engines: {node: '>= 16'} - '@noble/hashes@1.7.1': resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} @@ -1577,6 +1517,9 @@ packages: '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} + '@types/depd@1.1.37': + resolution: {integrity: sha512-PkEYFHnqDFgs+bJXJX0L8mq7sn3DWh+TP0m8BBJUJfZ2WcjRm7jd7Cq68jIJt+c31R1gX0cwSK1ZXOECvN97Rg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -1809,14 +1752,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base-x@2.0.6: - resolution: {integrity: sha512-UAmjxz9KbK+YIi66xej+pZVo/vxUOh49ubEvZW5egCbxhur05pBb+hwuireQwKO4nDpsNm64/jEei17LEpsr5g==} - engines: {node: '>=4.5.0'} - deprecated: use 3.0.0 instead, safe-buffer has been merged and release for compatability - - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1831,15 +1766,12 @@ packages: bn.js@4.12.1: resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - - borsh@0.5.0: - resolution: {integrity: sha512-p9w/qGBeeFdUf2GPBPHdX5JQyez8K5VtoFN7PqSfmR+cVUMSmcwAKhP9n2aXoDSKbtS7xZlZt3MVnrJL7GdYhg==} - borsh@1.0.0: resolution: {integrity: sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==} + borsh@2.0.0: + resolution: {integrity: sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1862,12 +1794,6 @@ packages: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} - bs58@4.0.0: - resolution: {integrity: sha512-/jcGuUuSebyxwLLfKrbKnCJttxRf9PM51EnHTwmFKBxl4z1SGkoAhrfd6uZKE0dcjQTfm6XzTP8DPr1tzE4KIw==} - - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -3046,15 +2972,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - near-abi@0.1.1: - resolution: {integrity: sha512-RVDI8O+KVxRpC3KycJ1bpfVj9Zv+xvq9PlW1yIFl46GhrnLw83/72HqHGjGDjQ8DtltkcpSjY9X3YIGZ+1QyzQ==} - near-abi@0.2.0: resolution: {integrity: sha512-kCwSf/3fraPU2zENK18sh+kKG4uKbEUEQdyWQkmW8ZofmLarObIz2+zAYjA1teDZLeMvEQew3UysnPDXgjneaA==} - near-api-js@4.0.0: - resolution: {integrity: sha512-Gh4Lq9LXFDNtEHGXeqYFjbvS9lodX34srmFAxhOgGqjklK5QArrT7bTifWA9mi3QGe3MqwwwfAIVONbeZ0qSpg==} - near-hello@0.5.1: resolution: {integrity: sha512-k7S8VFyESWgkKYDso99B4XbxAdo0VX9b3+GAaO5PvMgQjNr/6o09PHRywg/NkBQpf+ZYj7nNpJcyrNJGQsvA3w==} @@ -3062,13 +2982,9 @@ packages: resolution: {integrity: sha512-6/AjLOzGLQsSzTHavrRe7CVMU5+CUnTg/7g66a7+jkP22ff5CupnwXrUE14iJ2QyyhRZr7VuQQj/G+glUmaNQg==} hasBin: true - near-units@0.1.9: - resolution: {integrity: sha512-xiuBjpNsi+ywiu7P6iWRZdgFm7iCr/cfWlVO6+e5uaAqH4mE1rrurElyrL91llNDSnMwogd9XmlZOw5KbbHNsA==} - hasBin: true - - near-workspaces@4.0.0: - resolution: {integrity: sha512-ArFhJ9MqesRaNN1cDKSVKm3rmltQbe+fK2DzGn16ESebfR7bkhgc1XjLPtBxJp4WiYS0k2ZRsQyy5YLzCLV2Og==} - engines: {node: '>= 14.0.0', npm: '>= 6.0.0'} + near-workspaces@5.0.0: + resolution: {integrity: sha512-fktOa9A6gge8JPa+Gp6vMJSF3mE3H6dEMvVPTa0bXx9cNp142bAAderX8dZLtN1fYduPFkV3yb+OGM8V0d6JdA==} + engines: {node: '>=20.15.0', pnpm: '>= 9.15'} node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} @@ -3529,9 +3445,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -4591,7 +4504,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -4628,7 +4541,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -4665,7 +4578,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -4834,107 +4747,10 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@near-js/accounts@1.1.0(encoding@0.1.13)': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - depd: 2.0.0 - is-my-json-valid: 2.20.6 - lru_map: 0.4.1 - near-abi: 0.1.1 - transitivePeerDependencies: - - encoding - - '@near-js/crypto@1.2.2': - dependencies: - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@noble/curves': 1.2.0 - borsh: 1.0.0 - randombytes: 2.1.0 - - '@near-js/keystores-browser@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - - '@near-js/keystores-node@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - - '@near-js/keystores@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/types': 0.1.0 - - '@near-js/providers@0.2.0(encoding@0.1.13)': - dependencies: - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - http-errors: 1.7.2 - optionalDependencies: - node-fetch: 2.6.7(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - '@near-js/signers@0.1.2': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@noble/hashes': 1.3.3 - - '@near-js/transactions@1.2.0': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/signers': 0.1.2 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@noble/hashes': 1.3.3 - borsh: 1.0.0 - - '@near-js/types@0.1.0': {} - - '@near-js/utils@0.2.0': - dependencies: - '@near-js/types': 0.1.0 - bs58: 4.0.0 - depd: 2.0.0 - mustache: 4.0.0 - - '@near-js/wallet-account@1.2.0(encoding@0.1.13)': - dependencies: - '@near-js/accounts': 1.1.0(encoding@0.1.13) - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - transitivePeerDependencies: - - encoding - - '@noble/curves@1.2.0': - dependencies: - '@noble/hashes': 1.3.2 - '@noble/curves@1.8.1': dependencies: '@noble/hashes': 1.7.1 - '@noble/hashes@1.3.2': {} - - '@noble/hashes@1.3.3': {} - '@noble/hashes@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -5044,6 +4860,10 @@ snapshots: dependencies: '@types/node': 20.0.0 + '@types/depd@1.1.37': + dependencies: + '@types/node': 20.0.0 + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.0.0 @@ -5323,14 +5143,6 @@ snapshots: balanced-match@1.0.2: {} - base-x@2.0.6: - dependencies: - safe-buffer: 5.2.1 - - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - base64-js@1.5.1: {} base64url@3.0.1: {} @@ -5341,16 +5153,10 @@ snapshots: bn.js@4.12.1: {} - bn.js@5.2.1: {} - - borsh@0.5.0: - dependencies: - bn.js: 5.2.1 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - borsh@1.0.0: {} + borsh@2.0.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -5377,14 +5183,6 @@ snapshots: dependencies: fast-json-stable-stringify: 2.1.0 - bs58@4.0.0: - dependencies: - base-x: 2.0.6 - - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -6372,7 +6170,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6403,7 +6201,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6434,7 +6232,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6465,7 +6263,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6496,7 +6294,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6969,36 +6767,10 @@ snapshots: natural-compare@1.4.0: {} - near-abi@0.1.1: - dependencies: - '@types/json-schema': 7.0.15 - near-abi@0.2.0: dependencies: '@types/json-schema': 7.0.15 - near-api-js@4.0.0(encoding@0.1.13): - dependencies: - '@near-js/accounts': 1.1.0(encoding@0.1.13) - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@near-js/keystores-browser': 0.0.10 - '@near-js/keystores-node': 0.0.10 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@near-js/wallet-account': 1.2.0(encoding@0.1.13) - '@noble/curves': 1.2.0 - borsh: 1.0.0 - depd: 2.0.0 - http-errors: 1.7.2 - near-abi: 0.1.1 - node-fetch: 2.6.7(encoding@0.1.13) - transitivePeerDependencies: - - encoding - near-hello@0.5.1: {} near-sandbox@0.0.18: @@ -7006,30 +6778,21 @@ snapshots: got: 11.8.6 tar: 6.2.1 - near-units@0.1.9: - dependencies: - bn.js: 5.2.1 - - near-workspaces@4.0.0(encoding@0.1.13): + near-workspaces@5.0.0: dependencies: + '@scure/base': 1.2.4 base64url: 3.0.1 - bn.js: 5.2.1 - borsh: 0.5.0 - bs58: 4.0.1 + borsh: 2.0.0 callsites: 4.2.0 fs-extra: 10.1.0 js-sha256: 0.9.0 - near-api-js: 4.0.0(encoding@0.1.13) + near-api-js: link:packages/near-api-js near-sandbox: 0.0.18 - near-units: 0.1.9 node-port-check: 2.0.1 promisify-child-process: 4.1.2 proper-lockfile: 4.1.2 pure-uuid: 1.8.1 - rimraf: 3.0.2 temp-dir: 2.0.0 - transitivePeerDependencies: - - encoding node-addon-api@5.1.0: {} @@ -7424,8 +7187,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - text-encoding-utf-8@1.0.2: {} - text-extensions@2.4.0: {} text-table@0.2.0: {} diff --git a/typedoc.json b/typedoc.json index 47bca6adcf..b40dfc5bbb 100644 --- a/typedoc.json +++ b/typedoc.json @@ -11,6 +11,7 @@ "packages/keystores-node", "packages/providers", "packages/signers", + "packages/tokens", "packages/transactions", "packages/types", "packages/utils",