diff --git a/packages/client/.eslintrc.yml b/packages/client/.eslintrc.yml new file mode 100644 index 0000000000..3de14a5ee7 --- /dev/null +++ b/packages/client/.eslintrc.yml @@ -0,0 +1,19 @@ +env: + es6: true + node: true +extends: + - 'eslint:recommended' + - 'plugin:@typescript-eslint/eslint-recommended' + - 'plugin:@typescript-eslint/recommended' +parser: '@typescript-eslint/parser' +rules: + no-inner-declarations: off + indent: + - error + - 2 + - SwitchCase: 1 + '@typescript-eslint/no-explicit-any': off + +parserOptions: + ecmaVersion: 2018 + sourceType: module diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000000..d5bf76d821 --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,192 @@ +# @near-js/client + +This package provides a simple interface for interacting with the Near blockchain. As a modern, tree-shakeable package, +it is intended to replace usage of `near-api-js`. + +### Installation +```shell +# npm +npm i -s @near-js/client + +# pnpm +pnpm add @near-js/client +``` + +### Usage + + +### Dependency Initialization +This package uses a common interface for specifying dependencies for RPC providers and cryptographic signing, allowing +a more flexible approach to composing functionality. + +#### RPC provider +RPC providers are required for any blockchain interaction, e.g. block queries, transaction publishing. + +This interface makes use of the `FallbackRpcProvider`, making it configurable with multiple RPC endpoint URLs +which can be cycled through when the current URL returns an error. + +Specifying by URL endpoints: +```ts +import { getProviderByEndpoints } from '@near-js/client'; + +const rpcProvider = getProviderByEndpoints('https://rpc.tld', 'https://fallback-rpc.tld'); +``` + +Specifying by network (uses default RPC endpoints specified in code): +```ts +import { getProviderByNetwork } from '@near-js/client'; + +const rpcProvider = getProviderByNetwork('testnet'); +``` + +Shortcut methods: +```ts +import { getTestnetRpcProvider } from '@near-js/client'; + +// equivalent to getProviderByNetwork('testnet'); +const rpcProvider = getTestnetRpcProvider(); +``` + +#### Message Signer +Implementers of the `MessageSigner` interface can be initialized from the existing `KeyStore` classes or using private keys. + +Using an existing keystore: +```ts +import { getSignerFromKeystore } from '@near-js/client'; +import { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; + +const keystore = new BrowserLocalStorageKeyStore(); +const signer = getSignerFromKeystore('account.near', 'mainnet', keystore); +``` + + +Using a private key string: +```ts +import { getSignerFromKeystore } from '@near-js/client'; +import { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; + +const signer = getSignerFromPrivateKey('ed25519:...'); +``` + +An access key-based signer is also available. Initialized using a `MessageSigner` implementer, this signer caches the +access key record and maintains a local, auto-incremented value for its nonce: +```ts +import { getAccessKeySigner, getMainnetRpcProvider, getSignerFromPrivateKey } from '@near-js/client'; +import { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; + +const keystore = new BrowserLocalStorageKeyStore(); +const signer = getSignerFromKeystore('account.near', 'mainnet', keystore); +const accessKeySigner = getAccessKeySigner({ + accout: 'account.near', + deps: { + signer, + rpcProvider: getMainnetRpcProvider(), + }, +}); +``` + +### View Methods +Several functions are provided for working with builtin account methods and smart contract view methods. + +Executing a view method on a smart contract and return the entire response: +```ts +import { callViewMethod, getTestnetRpcProvider } from '@near-js/client'; + +const response = await callViewMethod({ + account: 'guest-book.testnet', + method: 'getMessages', + deps: { rpcProvider: getTestnetRpcProvider() }, +}); +``` + +Shorthand function to call the method and return only the parsed value: +```ts +import { getTestnetRpcProvider, view } from '@near-js/client'; + +interface GuestBookMessage { + premium: boolean; + sender: string; + text: string; +} + +// parse the returned buffer and parse as JSON +const data = await view({ + account: 'guest-book.testnet', + method: 'getMessages', + deps: { rpcProvider: getTestnetRpcProvider() }, +}); +``` + +Client method for requesting access keys: +```ts +import { getAccessKeys, getTestnetRpcProvider } from '@near-js/client'; + +const { fullAccessKeys, functionCallAccessKeys } = await getAccessKeys({ + account: 'account.testnet', + deps: { rpcProvider: getTestnetRpcProvider() }, +}); +``` + +### Transactions +New `*Composer` classes facilitate transaction building and signing: +```ts +import { + getSignerFromKeystore, + getTestnetRpcProvider, + SignedTransactionComposer, +} from '@near-js/client'; +import { KeyPairEd25519 } from '@near-js/crypto'; +import { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; + +const keystore = new BrowserLocalStorageKeyStore(); +const signer = getSignerFromKeystore('account.testnet', 'testnet', keystore); + +const oldPublicKey = await signer.getPublicKey(); +const newKeyPair = KeyPairEd25519.fromRandom(); + +const composer = SignedTransactionComposer.init({ + sender: 'account.testnet', + receiver: 'receiver.testnet', + deps: { + signer, + rpcProvider: getMainnetRpcProvider(), + } +}); + +// add new key and remove old key in single transaction +await composer + .addFullAccessKey(newKeyPair.publicKey) + .deleteKey(oldPublicKey.toString()) + .signAndSend(); + +keystore.setKey('testnet', 'account.testnet', newKeyPair); +``` + +For convenience, there are also functions wrapping individual actions, e.g. `transfer`: +```ts +import { + getSignerFromKeystore, + getTestnetRpcProvider, + transfer, +} from '@near-js/client'; +import { KeyPairEd25519 } from '@near-js/crypto'; +import { BrowserLocalStorageKeyStore } from '@near-js/keystores-browser'; + +const keystore = new BrowserLocalStorageKeyStore(); +const signer = getSignerFromKeystore('account.testnet', 'testnet', keystore); + +await transfer({ + sender: 'account.testnet', + receiver: 'receiver.testnet', + amount: 1000n, // in yoctoNear + deps: { + rpcProvider: getTestnetRpcProvider(), + signer, + } +}); +``` + +# 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/client/jest.config.ts b/packages/client/jest.config.ts new file mode 100644 index 0000000000..3b66313239 --- /dev/null +++ b/packages/client/jest.config.ts @@ -0,0 +1,13 @@ +export default { + preset: 'ts-jest', + collectCoverage: true, + testEnvironment: 'node', + testRegex: "(/tests/.*|(\\.|/)(test|spec))\\.[jt]sx?$", + transform: { + '^.+\\.[tj]s$': ['ts-jest', { + tsconfig: { + allowJs: true, + }, + }], + }, +}; diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000000..70fe6ef10c --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,41 @@ +{ + "name": "@near-js/client", + "version": "0.0.1", + "description": "", + "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", + "lint": "eslint -c .eslintrc.yml src/**/*.ts --no-eslintrc --no-error-on-unmatched-pattern", + "lint:fix": "eslint -c .eslintrc.yml src/**/*.ts --no-eslintrc --no-error-on-unmatched-pattern --fix" + }, + "dependencies": { + "@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:*", + "@noble/hashes": "1.3.3", + "isomorphic-fetch": "3.0.0" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/node": "20.0.0", + "build": "workspace:*", + "tsconfig": "workspace:*", + "typescript": "5.4.5" + }, + "files": [ + "lib" + ], + "exports": { + "require": "./lib/commonjs/index.cjs", + "import": "./lib/esm/index.js" + } +} diff --git a/packages/client/src/constants.ts b/packages/client/src/constants.ts new file mode 100644 index 0000000000..4c07f22cb1 --- /dev/null +++ b/packages/client/src/constants.ts @@ -0,0 +1,22 @@ +export const DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL = 100n; +export const MAX_GAS = 300000000000000n; + +export const PAGODA_RPC_ENDPOINTS_MAINNET = [ + 'https://rpc.near.org', + 'https://rpc.mainnet.pagoda.co', +]; + +export const PAGODA_RPC_ARCHIVAL_ENDPOINTS_MAINNET = [ + 'https://archival-rpc.near.org', +]; + +export const PAGODA_RPC_ENDPOINTS_TESTNET = [ + 'https://rpc.testnet.near.org', + 'https://rpc.testnet.pagoda.co', +]; + +export const PAGODA_RPC_ARCHIVAL_ENDPOINTS_TESTNET = [ + 'https://archival-rpc.testnet.near.org', +]; + +export const KITWALLET_FUNDED_TESTNET_ACCOUNT_ENDPOINT = 'https://testnet-api.kitwallet.app/account'; diff --git a/packages/client/src/crypto.ts b/packages/client/src/crypto.ts new file mode 100644 index 0000000000..be060a46f1 --- /dev/null +++ b/packages/client/src/crypto.ts @@ -0,0 +1,17 @@ +import { CurveType, KeyPair, KeyPairString } from '@near-js/crypto'; + +/** + * Generate a random key pair for the specified elliptic curve + * @param curve elliptic curve (e.g. `ed25519`) + */ +export function generateRandomKeyPair(curve: CurveType) { + return KeyPair.fromRandom(curve); +} + +/** + * Parse a signing key pair from a private key string + * @param privateKey private key string + */ +export function parseKeyPair(privateKey: KeyPairString) { + return KeyPair.fromString(privateKey); +} diff --git a/packages/client/src/funded_account.ts b/packages/client/src/funded_account.ts new file mode 100644 index 0000000000..25e3e2f02b --- /dev/null +++ b/packages/client/src/funded_account.ts @@ -0,0 +1,38 @@ +import type { FinalExecutionOutcome } from '@near-js/types'; +import fetch from 'isomorphic-fetch'; + +import { KITWALLET_FUNDED_TESTNET_ACCOUNT_ENDPOINT } from './constants'; +import { NewAccountParams } from './interfaces'; + +interface CreateFundedTestnetAccountParams extends NewAccountParams { + endpointUrl?: string; +} + +/** + * Create a new funded testnet account via faucet REST endpoint + * (e.g. create `new.testnet` with a preset amount of Near) + * @param endpointUrl REST endpoint for the funded testnet account creation (defaults to the current Near Contract Helper endpoint) + * @param newAccount name of the created account + * @param newPublicKey public key for the created account's initial full access key + */ +export async function createFundedTestnetAccount({ + newAccount, + newPublicKey, + endpointUrl = KITWALLET_FUNDED_TESTNET_ACCOUNT_ENDPOINT, +}: CreateFundedTestnetAccountParams) { + const res = await fetch(endpointUrl, { + method: 'POST', + body: JSON.stringify({ + newAccountId: newAccount, + newAccountPublicKey: newPublicKey, + }), + headers: { 'Content-Type': 'application/json' }, + }); + + const { ok, status } = res; + if (!ok) { + throw new Error(`Failed to create account on ${endpointUrl}: ${status}`); + } + + return await res.json() as FinalExecutionOutcome; +} diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts new file mode 100644 index 0000000000..eb2f5f5621 --- /dev/null +++ b/packages/client/src/index.ts @@ -0,0 +1,10 @@ +export { formatNearAmount } from '@near-js/utils'; + +export * from './constants'; +export * from './crypto'; +export * from './funded_account'; +export * from './interfaces'; +export * from './providers'; +export * from './signing'; +export * from './transactions'; +export * from './view'; diff --git a/packages/client/src/interfaces/dependencies.ts b/packages/client/src/interfaces/dependencies.ts new file mode 100644 index 0000000000..f8124e5e63 --- /dev/null +++ b/packages/client/src/interfaces/dependencies.ts @@ -0,0 +1,33 @@ +import type { PublicKey } from '@near-js/crypto'; + +import type { RpcQueryProvider } from './providers'; +import { FullAccessKey, FunctionCallAccessKey } from './view'; + +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; +} + +interface SignerDependent { + signer: MessageSigner; +} + +export interface RpcProviderDependency extends Dependent {} + +export interface SignerDependency extends Dependent {} + +export interface SignAndSendTransactionDependency extends Dependent {} diff --git a/packages/client/src/interfaces/index.ts b/packages/client/src/interfaces/index.ts new file mode 100644 index 0000000000..a3827e4c93 --- /dev/null +++ b/packages/client/src/interfaces/index.ts @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000000..b418de9af0 --- /dev/null +++ b/packages/client/src/interfaces/providers.ts @@ -0,0 +1,25 @@ +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/interfaces/transactions.ts b/packages/client/src/interfaces/transactions.ts new file mode 100644 index 0000000000..0cb4a50dad --- /dev/null +++ b/packages/client/src/interfaces/transactions.ts @@ -0,0 +1,97 @@ +import type { Transaction } from '@near-js/transactions'; + +import type { TransactionComposer } from '../transactions'; +import type { SignAndSendTransactionDependency, SignerDependency } from './dependencies'; +import type { RpcProviderQueryParams } from './view'; +import { BlockHash } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; + +export interface SignAndSendParams extends SignAndSendTransactionDependency, RpcProviderQueryParams {} + +export interface ExternalActionTransaction extends SignAndSendParams { + receiver: string; + sender: string; +} + +export interface ReflexiveActionTransaction extends SignAndSendParams { + account: string; +} + +export interface SignAndSendComposerParams extends SignAndSendParams, RpcProviderQueryParams { + composer: TransactionComposer; +} + +export interface SignAndSendTransactionParams extends SignAndSendTransactionDependency { + transaction: Transaction; +} + +export interface FunctionCallParams extends ExternalActionTransaction { + method: string; + args?: T; + deposit?: bigint; + gas?: bigint; +} + +export interface TransferParams extends ExternalActionTransaction { + amount: bigint; +} + +export interface StakeParams extends ReflexiveActionTransaction { + amount: bigint; + publicKey: string; +} + +export interface DeleteAccountParams extends ReflexiveActionTransaction { + beneficiaryId: string; +} + +export interface DeployContractParams extends ReflexiveActionTransaction { + code: Uint8Array +} + +interface AddAccessKeyParams extends ReflexiveActionTransaction { + publicKey: string; +} + +export interface ModifyAccessKeyParams extends AddAccessKeyParams {} + +export interface AddFunctionCallAccessKeyParams extends AddAccessKeyParams { + contract: string; + methodNames: string[]; + allowance?: bigint; +} + +export interface SignTransactionParams extends SignerDependency { + transaction: Transaction; +} + +export interface NewAccountParams { + newAccount: string; + newPublicKey: string; +} + +export interface CreateAccountParams extends SignAndSendParams, NewAccountParams { + account: string; + initialBalance: bigint; +} + +export interface CreateTopLevelAccountParams extends CreateAccountParams { + contract: string +} + +export interface TransactionOptions { + blockHash?: BlockHash; + nonce?: bigint; + publicKey?: PublicKey; + receiver?: string; + sender?: string; +} + +export interface MetaTransactionOptions extends TransactionOptions { + blockHeightTtl?: bigint; + maxBlockHeight?: bigint; +} + +export interface SignedTransactionOptions extends TransactionOptions, SignAndSendTransactionDependency { +} + diff --git a/packages/client/src/interfaces/view.ts b/packages/client/src/interfaces/view.ts new file mode 100644 index 0000000000..59890bbbef --- /dev/null +++ b/packages/client/src/interfaces/view.ts @@ -0,0 +1,55 @@ +import type { BlockReference } from '@near-js/types'; + +import { RpcProviderDependency } from './dependencies'; + +export interface RpcProviderQueryParams { + blockReference?: BlockReference; +} + +export interface ViewBaseParams extends RpcProviderDependency, RpcProviderQueryParams { +} + +export interface ViewAccountParams extends ViewBaseParams { + account: string; +} + +export interface ViewValidatorStakeParams extends ViewAccountParams { + validator: string; +} + +export interface ViewParams extends ViewAccountParams { + method: string; + args?: T; +} + +export interface ViewContractStateParams extends ViewAccountParams { + prefix: string | Uint8Array; +} + +export interface ViewAccessKeyParams extends ViewAccountParams { + publicKey: string; +} + +interface AccessKey { + nonce: bigint; + publicKey: string; +} + +export interface FullAccessKey extends AccessKey {} +export interface FunctionCallAccessKey extends AccessKey { + contract: string; + methods: string[]; + allowance: bigint; +} + +export interface AccessKeys { + fullAccessKeys: FullAccessKey[]; + functionCallAccessKeys: FunctionCallAccessKey[]; +} + +export interface AccountState { + availableBalance: bigint; + codeHash: string; + locked: bigint; + storageUsed: bigint; +} diff --git a/packages/client/src/providers.ts b/packages/client/src/providers.ts new file mode 100644 index 0000000000..152f56c3bc --- /dev/null +++ b/packages/client/src/providers.ts @@ -0,0 +1,84 @@ +import { FailoverRpcProvider, JsonRpcProvider } from '@near-js/providers'; + +import type { RpcQueryProvider } from './interfaces'; +import { + PAGODA_RPC_ARCHIVAL_ENDPOINTS_TESTNET, + PAGODA_RPC_ENDPOINTS_MAINNET, + PAGODA_RPC_ENDPOINTS_TESTNET, +} from './constants'; + +/** + * Get the set of public endpoints for the provided network + * @param network target blockchain network (e.g. `mainnet`) + */ +export function getEndpointsByNetwork(network: string) { + switch (network) { + case 'testnet': + return PAGODA_RPC_ENDPOINTS_TESTNET; + case 'mainnet': + return PAGODA_RPC_ENDPOINTS_MAINNET; + default: + return null; + } +} + +/** + * 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 { + 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), + }; +} + +/** + * Initialize a failover RPC provider for the given network + * @param network target blockchain network (e.g. `mainnet`) + */ +export function getProviderByNetwork(network: string) { + return createRpcClientWrapper(getEndpointsByNetwork(network)); +} + +/** + * Initialize a failover RPC provider for a set of RPC endpoint URLs + * @param urls RPC endpoint URLs + */ +export function getProviderByEndpoints(...urls: string[]) { + return createRpcClientWrapper(urls); +} + +/** + * Initialize a testnet RPC provider + */ +export function getTestnetRpcProvider() { + return getProviderByNetwork('testnet'); +} + +/** + * Initialize a testnet archival RPC provider + */ +export function getTestnetRpcArchivalProvider() { + return createRpcClientWrapper(PAGODA_RPC_ARCHIVAL_ENDPOINTS_TESTNET); +} + +/** + * Initialize a mainnet RPC provider + */ +export function getMainnetRpcProvider() { + return getProviderByNetwork('mainnet'); +} diff --git a/packages/client/src/signing/index.ts b/packages/client/src/signing/index.ts new file mode 100644 index 0000000000..01e03a91e1 --- /dev/null +++ b/packages/client/src/signing/index.ts @@ -0,0 +1,5 @@ +export { + getSignerFromKeyPair, + getSignerFromKeystore, + getSignerFromPrivateKey, +} from './signers'; diff --git a/packages/client/src/signing/signers.ts b/packages/client/src/signing/signers.ts new file mode 100644 index 0000000000..723fe5d036 --- /dev/null +++ b/packages/client/src/signing/signers.ts @@ -0,0 +1,104 @@ +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'; + +/** + * 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) { + return keyPair.sign(m).signature; + } + }; +} + +/** + * 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)); +} + +/** + * Initialize a message signer from a keystore instance + * @param account used to sign transactions + * @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; + + 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 += 1n; + return signer.signMessage(m); + } + }; +} diff --git a/packages/client/src/transactions/actions.ts b/packages/client/src/transactions/actions.ts new file mode 100644 index 0000000000..04357a44ad --- /dev/null +++ b/packages/client/src/transactions/actions.ts @@ -0,0 +1,123 @@ +import type { + AddFunctionCallAccessKeyParams, + DeleteAccountParams, + DeployContractParams, + FunctionCallParams, + ModifyAccessKeyParams, + StakeParams, + TransferParams, +} from '../interfaces'; +import { SignedTransactionComposer } from './composers'; + +/** + * Make a function call against a contract + * @param sender transaction signer + * @param receiver target account/contract + * @param method method to be invoked + * @param args method arguments + * @param gas attached gas + * @param deposit attached deposit in yN + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function functionCall({ sender, receiver, method, args, gas, deposit, blockReference, deps }: FunctionCallParams) { + return SignedTransactionComposer.init({ sender, receiver, deps }) + .functionCall(method, args, gas, deposit) + .signAndSend(blockReference); +} + +/** + * Send Near from one account to another + * @param sender account sending Near + * @param receiver account receiving Near + * @param amount Near to send in yN + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function transfer({ sender, receiver, amount, blockReference, deps }: TransferParams) { + return SignedTransactionComposer.init({ sender, receiver, deps }) + .transfer(amount) + .signAndSend(blockReference); +} + +/** + * Stake Near with the specified validator + * @param account account staking Near + * @param amount Near to stake in yN + * @param publicKey public key for the target validator + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function stake({ account, amount, publicKey, blockReference, deps }: StakeParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .stake(amount, publicKey) + .signAndSend(blockReference); +} + +/** + * Add a full access key to an account + * @param account account to which the FAK is added + * @param publicKey public key string for the new FAK + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function addFullAccessKey({ account, publicKey, blockReference, deps }: ModifyAccessKeyParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .addFullAccessKey(publicKey) + .signAndSend(blockReference); +} + +/** + * Add a function call access key to an account + * @param account account to which the access key is added + * @param publicKey public key string for the new access key + * @param contract contract on which methods may be invoked + * @param methodNames set of methods which may be invoked + * @param allowance maximum amount of Near which can be attached to a transaction signed with this key + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function addFunctionCallAccessKey({ account, publicKey, contract, methodNames, allowance, blockReference, deps }: AddFunctionCallAccessKeyParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .addFunctionCallAccessKey(publicKey, contract, methodNames, allowance) + .signAndSend(blockReference); +} + +/** + * Remove the specified access key from an account + * @param account account from which the access key will be removed + * @param publicKey public key string of the access key to be removed + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function deleteAccessKey({ account, publicKey, blockReference, deps }: ModifyAccessKeyParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .deleteKey(publicKey) + .signAndSend(blockReference); +} + +/** + * Delete an account; account funds will be transferred to the designated beneficiary + * @param account account from which the access key will be removed + * @param publicKey public key string of the access key to be removed + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function deleteAccount({ account, beneficiaryId, blockReference, deps }: DeleteAccountParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .deleteAccount(beneficiaryId) + .signAndSend(blockReference); +} + +/** + * Deploy contract code to an account + * @param account account to which the contract code will be deployed + * @param code WASM code as byte array + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export function deployContract({ account, code, blockReference, deps }: DeployContractParams) { + return SignedTransactionComposer.init({ sender: account, receiver: account, deps }) + .deployContract(code) + .signAndSend(blockReference); +} diff --git a/packages/client/src/transactions/composers/index.ts b/packages/client/src/transactions/composers/index.ts new file mode 100644 index 0000000000..26ea6b7efa --- /dev/null +++ b/packages/client/src/transactions/composers/index.ts @@ -0,0 +1,2 @@ +export * from './signed_transaction_composer'; +export * from './transaction_composer'; \ No newline at end of file diff --git a/packages/client/src/transactions/composers/signed_transaction_composer.ts b/packages/client/src/transactions/composers/signed_transaction_composer.ts new file mode 100644 index 0000000000..a768d5e68d --- /dev/null +++ b/packages/client/src/transactions/composers/signed_transaction_composer.ts @@ -0,0 +1,124 @@ +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'; + +export class SignedTransactionComposer extends TransactionComposer { + messageSigner: MessageSigner; + rpcProvider: RpcQueryProvider; + signer: AccessKeySigner; + + constructor({ deps, ...baseOptions }: SignedTransactionOptions) { + super(baseOptions); + this.messageSigner = deps.signer; + this.rpcProvider = deps.rpcProvider; + if (this.sender) { + this.signer = getAccessKeySigner({ account: this.sender, deps }); + } + } + + /** + * Initialize the composer + * @param options signed composer configuration + */ + static init(options: SignedTransactionOptions) { + return new SignedTransactionComposer(options); + } + + /** + * Return a signed delegate action encapsulating the composed transaction for inclusion in a meta transaction + * @param transaction meta transaction configuration + */ + async toSignedDelegateAction(transaction?: MetaTransactionOptions) { + let maxBlockHeight = transaction?.maxBlockHeight; + if (!maxBlockHeight) { + const { header } = await this.rpcProvider.block({ finality: 'final' }); + const ttl = transaction?.blockHeightTtl || DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL; + maxBlockHeight = BigInt(header.height) + ttl; + } + + const delegateAction = buildDelegateAction({ + 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; + } + + /** + * Verify the transaction's signer matches the account mapped to the AccessKeySigner. + * Initialize the signer if not already done (i.e. for lazy setting of the transaction signer). + * Throw an error if there is a mismatch between the current AccessKeySigner and the transaction's specified signer. + * @param signingAccount + * @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}`); + } + } + + /** + * Return a signed transaction from the composed transaction + * @param transactionOptions transaction configuration to override values set at composer initialization + */ + async toSignedTransaction(transactionOptions?: TransactionOptions) { + const transaction = this.toTransaction(transactionOptions); + this.verifySigner(transaction.signerId); + return signTransaction({ + transaction, + deps: { signer: this.signer }, + }); + } + + /** + * Sign and send the composed transaction + * @param blockReference block to use for determining hash + */ + 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, + }); + + const outcome = await this.rpcProvider.sendTransaction(signedTransaction); + return { + outcome, + result: getTransactionLastResult(outcome) as T, + }; + } +} diff --git a/packages/client/src/transactions/composers/transaction_composer.ts b/packages/client/src/transactions/composers/transaction_composer.ts new file mode 100644 index 0000000000..ed58242481 --- /dev/null +++ b/packages/client/src/transactions/composers/transaction_composer.ts @@ -0,0 +1,165 @@ +import { PublicKey } from '@near-js/crypto'; +import { + Action, + actionCreators, + DelegateAction, + Signature, + Transaction, +} from '@near-js/transactions'; +import { baseDecode, DEFAULT_FUNCTION_CALL_GAS } from '@near-js/utils'; +import { BlockHash } from '@near-js/types'; +import { TransactionOptions } from '../../interfaces'; + +export class TransactionComposer { + protected actions: Action[] = []; + receiver: string | undefined; + sender: string | undefined; + blockHash: BlockHash | undefined; + nonce: bigint | undefined; + publicKey: PublicKey | undefined; + + constructor(transaction: TransactionOptions) { + this.receiver = transaction.receiver; + this.sender = transaction.sender; + this.blockHash = transaction.blockHash; + this.nonce = transaction.nonce; + this.publicKey = transaction.publicKey; + } + + /** + * Initialize the composer + * @param transaction composer configuration + */ + static init(transaction: TransactionOptions) { + return new TransactionComposer(transaction); + } + + /** + * Validate and return the object used for Transaction instantiation + * @param transaction transaction values to override composed transaction fields + * @private + */ + private buildTransactionObject(transaction?: TransactionOptions) { + const tx = { + actions: this.actions, + blockHash: baseDecode(transaction?.blockHash || this.blockHash), + nonce: transaction?.nonce || this.nonce, + publicKey: transaction?.publicKey || this.publicKey, + receiverId: transaction?.receiver || this.receiver, + signerId: transaction?.sender || this.sender, + }; + + if (!tx.actions.length || !tx.blockHash || !tx.nonce || !tx.publicKey || !tx.receiverId || !tx.signerId) { + throw new Error(`invalid transaction: ${JSON.stringify(tx)}`); + } + + return tx; + } + + + /** + * Return a Transaction instance from the composed transaction + * @param transaction transaction configuration to override values set at composer initialization + */ + toTransaction(transaction?: TransactionOptions): Transaction { + return new Transaction(this.buildTransactionObject(transaction)); + } + + /** + * Add an action to add a full access key + * @param publicKey string representation of the public key on the new access key + */ + addFullAccessKey(publicKey: string): this { + this.actions.push(actionCreators.addKey(PublicKey.from(publicKey), actionCreators.fullAccessKey())); + return this; + } + + /** + * Add an action to create a function call access key + * @param publicKey string representation of the public key on the new access key + * @param contractId permitted contract + * @param methodNames set of permitted methods + * @param allowance max allowable balance attached to transactions signed with this key + */ + addFunctionCallAccessKey(publicKey: string, contractId: string, methodNames: string[], allowance?: bigint): this { + const accessKey = actionCreators.functionCallAccessKey(contractId, methodNames, allowance); + this.actions.push(actionCreators.addKey(PublicKey.from(publicKey), accessKey)); + return this; + } + + /** + * Add an action to create a sub-account for the transaction recipient + */ + createAccount(): this { + this.actions.push(actionCreators.createAccount()); + return this; + } + + /** + * Add an action to delete the account signing the composed transaction + * @param beneficiaryId designated recipient account for any remaining balance on the deleted account + */ + deleteAccount(beneficiaryId: string): this { + this.actions.push(actionCreators.deleteAccount(beneficiaryId)); + return this; + } + + /** + * Add an action to delete the specified access key + * @param publicKey string representation of the public key on the access key to be deleted + */ + deleteKey(publicKey: string): this { + this.actions.push(actionCreators.deleteKey(PublicKey.from(publicKey))); + return this; + } + + /** + * Add an action to deploy code to a contract + * @param code compiled smart contract binary + */ + deployContract(code: Uint8Array): this { + this.actions.push(actionCreators.deployContract(code)); + return this; + } + + /** + * Add an action to invoke a smart contract method + * @param method name of the method to be executed + * @param args named arguments to the invocation + * @param gas amount of gas (in yN) included to cover execution cost + * @param deposit amount of Near (in yN) to attach to the invocation + */ + functionCall(method: string, args: object, gas: bigint = DEFAULT_FUNCTION_CALL_GAS * BigInt(10), deposit = BigInt(0)): this { + this.actions.push(actionCreators.functionCall(method, args, gas, deposit)); + return this; + } + + /** + * Add an action wrapping a delegate action for inclusion in meta transaction + * @param delegateAction delegate action encapsulating the set of actions to be executed on the requesting account's behalf + * @param signature signature of the delegate action signed by the requesting account + */ + signedDelegate(delegateAction: DelegateAction, signature: Signature): this { + this.actions.push(actionCreators.signedDelegate({ delegateAction, signature })); + return this; + } + + /** + * Add an action to stake Near with a validator + * @param stake amount of Near (in yN) to stake + * @param publicKey string representation of the validator's key + */ + stake(stake: bigint, publicKey: string): this { + this.actions.push(actionCreators.stake(stake, PublicKey.from(publicKey))); + return this; + } + + /** + * Add an action to transfer Near to another account + * @param deposit amount of Near (in yN) to transfer + */ + transfer(deposit: bigint): this { + this.actions.push(actionCreators.transfer(deposit)); + return this; + } +} diff --git a/packages/client/src/transactions/create_account.ts b/packages/client/src/transactions/create_account.ts new file mode 100644 index 0000000000..04d5c5506c --- /dev/null +++ b/packages/client/src/transactions/create_account.ts @@ -0,0 +1,52 @@ +import type { + CreateAccountParams, + CreateTopLevelAccountParams, +} from '../interfaces'; +import { SignedTransactionComposer } from './composers'; +import { functionCall } from './actions'; + +/** + * Create a new top-level account using an existing account + * (e.g. create `new.near` by signing with `creator.near`) + * @param account name of the account creating and funding the account + * @param contract root contract for the target network (e.g. `near`) + * @param newAccount name of the created account + * @param newPublicKey public key for the created account's initial full access key + * @param initialBalance initial account balance in yN + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export async function createTopLevelAccount({ account, contract, newAccount, newPublicKey, initialBalance, blockReference, deps }: CreateTopLevelAccountParams) { + return functionCall({ + sender: account, + receiver: contract, + method: 'create_account', + args: { + new_account_id: newAccount, + new_public_key: newPublicKey, + }, + deposit: initialBalance, + blockReference, + deps, + }); +} + +/** + * Create a new sub-account under an existing account + * (e.g. create `sub.new.near` by signing with `new.near`) + * @param account name of the existing account under which the new account is created + * @param contract root contract for the target network (e.g. `testnet`) + * @param newAccount name of the created account + * @param newPublicKey public key for the created account's initial full access key + * @param initialBalance initial account balance in yN + * @param blockReference block ID/finality + * @param deps sign-and-send dependencies + */ +export async function createSubAccount({ account, newAccount, newPublicKey, initialBalance, blockReference, deps }: CreateAccountParams) { + return SignedTransactionComposer.init({ sender: account, receiver: newAccount, deps }) + .createAccount() + .transfer(initialBalance) + .addFullAccessKey(newPublicKey) + .signAndSend(blockReference); +} + diff --git a/packages/client/src/transactions/index.ts b/packages/client/src/transactions/index.ts new file mode 100644 index 0000000000..15ef0dbfa3 --- /dev/null +++ b/packages/client/src/transactions/index.ts @@ -0,0 +1,4 @@ +export * from './actions'; +export * from './composers'; +export * from './create_account'; +export * from './sign_and_send'; diff --git a/packages/client/src/transactions/sign_and_send.ts b/packages/client/src/transactions/sign_and_send.ts new file mode 100644 index 0000000000..c555d3f6f5 --- /dev/null +++ b/packages/client/src/transactions/sign_and_send.ts @@ -0,0 +1,61 @@ +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'; +import { BlockReference } from '@near-js/types'; +import { SerializedReturnValue } from '@near-js/types/lib/esm/provider/response'; + +const DEFAULT_FINALITY: BlockReference = { finality: 'final' }; + +/** + * Sign a transaction, returning the signed transaction and encoded hash + * @param transaction Transaction instance + * @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), + }), + }); + + return { + encodedTransactionHash: new Uint8Array(sha256(encodedTx)), + signedTransaction, + }; +} + +/** + * Sign a transaction and publish to RPC + * @param transaction Transaction instance to sign and publish + * @param deps sign-and-send dependencies + */ +export async function signAndSendTransaction({ transaction, deps: { rpcProvider, signer } }: SignAndSendTransactionParams) { + const { signedTransaction } = await signTransaction({ transaction, deps: { signer } }); + const outcome = await rpcProvider.sendTransaction(signedTransaction); + return { + outcome, + result: getTransactionLastResult(outcome) as T, + }; +} + +/** + * Get the current nonce for an access key given an account and MessageSigner instance + * @param account owner of the access key + * @param blockReference block ID/finality + * @param rpcProvider RPC provider instance + * @param deps sign-and-send dependencies + */ +export async function getSignerNonce({ account, blockReference = DEFAULT_FINALITY, deps: { rpcProvider, signer } }) { + return getNonce({ + account, + publicKey: (await signer.getPublicKey()).toString(), + blockReference, + deps: { rpcProvider }, + }); +} diff --git a/packages/client/src/view.ts b/packages/client/src/view.ts new file mode 100644 index 0000000000..9f7ad6cf15 --- /dev/null +++ b/packages/client/src/view.ts @@ -0,0 +1,284 @@ +import type { + AccessKeyList, + AccessKeyView, + AccountView, + CodeResult, + ContractCodeView, + QueryResponseKind, + SerializedReturnValue, + StakedAccount, + ViewStateResult, +} from '@near-js/types'; + +import type { + AccountState, + FunctionCallAccessKey, + FullAccessKey, + RpcProviderDependency, + RpcProviderQueryParams, + ViewAccessKeyParams, + ViewAccountParams, + ViewContractStateParams, + ViewParams, + ViewValidatorStakeParams, +} from './interfaces'; + +const DEFAULT_VIEW_BLOCK_REFERENCE = { finality: 'optimistic' }; + +enum RequestType { + CallFunction = 'call_function', + ViewAccessKey = 'view_access_key', + ViewAccessKeyList = 'view_access_key_list', + ViewAccount = 'view_account', + ViewCode = 'view_code', + ViewState = 'view_state', +} + +interface QueryParams extends RpcProviderDependency, RpcProviderQueryParams { + account: string; + request: string; + args?: object; +} + +/** + * Make a readonly request to an RPC endpoint targeting a specific account/contract + * @param account target account/contract being queried + * @param request type of request (e.g. `call_function`) + * @param args named arguments passed in the request body + * @param blockReference block ID/finality + * @param rpcProvider RPC provider instance + */ +export function query({ + account, + request, + args = {}, + blockReference, + deps: { rpcProvider }, +}: QueryParams): Promise { + return rpcProvider.query({ + request_type: request, + account_id: account, + ...(blockReference ? blockReference : DEFAULT_VIEW_BLOCK_REFERENCE), + ...args, + }); +} + +/** + * Call a view method on an account/contract, returning the raw response + * @param account target account/contract being queried + * @param method name of the method being invoked + * @param args named arguments passed in the request body + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export function callViewMethod({ account, method, args = {}, blockReference, deps }: ViewParams) { + return query({ + request: RequestType.CallFunction, + account, + args: { + args_base64: Buffer.isBuffer(args) ? args : Buffer.from(JSON.stringify(args)).toString('base64'), + method_name: method, + }, + blockReference, + deps, + }); +} + +/** + * Call a view method on an account/contract, parsing the returned data + * NB if the data returned is a byte array, this method will convert it + * to string - use `await (viewRaw(...)).result` to get the buffer + * @param account target account/contract being queried + * @param method name of the method being invoked + * @param args named arguments passed in the request body + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function view({ account, method, args = {}, blockReference, deps }: ViewParams): Promise { + const { result } = await callViewMethod({ account, method, args, blockReference, deps }); + const stringResult = Buffer.from(result).toString(); + try { + return JSON.parse(stringResult); + } catch { + const numeric = +stringResult; + if (isNaN(numeric)) { + return stringResult as T; + } + + return (Number.isSafeInteger(numeric) ? numeric : BigInt(numeric)) as T; + } +} + +/** + * Get metadata for the specified access key + * @param account target account/contract being queried + * @param publicKey public key string to be queried + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getAccessKey({ account, publicKey, blockReference, deps }: ViewAccessKeyParams) { + const { nonce, permission } = await query({ + request: RequestType.ViewAccessKey, + account, + args: { + public_key: publicKey, + }, + blockReference, + deps, + }); + + if (permission === 'FullAccess') { + return { + nonce: BigInt(nonce), + publicKey, + } as FullAccessKey; + } + const { FunctionCall: { allowance, receiver_id, method_names } } = permission; + return { + allowance: BigInt(allowance), + contract: receiver_id, + methods: method_names, + nonce: BigInt(nonce), + publicKey, + } as FunctionCallAccessKey; +} + +/** + * Get account metadata (e.g. balance, storage) + * @param account target account/contract being queried + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getAccountState({ account, blockReference, deps }: ViewAccountParams) { + const accountState = await query({ + request: RequestType.ViewAccount, + account, + blockReference, + deps, + }); + + return { + availableBalance: BigInt(accountState.amount), + codeHash: accountState.code_hash, + locked: BigInt(accountState.locked), + storageUsed: BigInt(accountState.storage_usage), + } as AccountState; +} + +/** + * Get list of access keys for the specified account/contract + * @param account target account/contract being queried + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getAccessKeys({ account, blockReference, deps }: ViewAccountParams) { + const { keys } = await query({ + request: RequestType.ViewAccessKeyList, + account, + blockReference, + deps, + }); + + return keys.reduce((accessKeys, { access_key: { nonce, permission }, public_key: publicKey }) => { + if (permission === 'FullAccess') { + accessKeys.fullAccessKeys.push({ + nonce, + publicKey, + }); + } else { + const { FunctionCall: { allowance, receiver_id, method_names } } = permission; + accessKeys.functionCallAccessKeys.push({ + allowance: BigInt(allowance), + contract: receiver_id, + methods: method_names, + nonce, + publicKey, + }); + } + + return accessKeys; + }, { fullAccessKeys: [] as FullAccessKey[], functionCallAccessKeys: [] as FunctionCallAccessKey[] }); +} + +/** + * Get the code for the contract deployed to the target account + * @param account target account/contract being queried + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getContractCode({ account, blockReference, deps }: ViewAccountParams) { + const { code_base64, hash } = await query({ + request: RequestType.ViewCode, + account, + blockReference, + deps, + }); + + return { code: Buffer.from(code_base64, 'base64').toString(), code_base64, hash }; +} + +/** + * Get the state on the contract deployed to the target account in key-value pairs + * @param account target account/contract being queried + * @param prefix target prefix filter (empty string/buffer returns all keys) + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + * @returns object key-value pairs + */ +export async function getContractState({ account, prefix, blockReference, deps }: ViewContractStateParams) { + const { values } = await query({ + request: RequestType.ViewState, + account, + args: { + prefix_base64: Buffer.from(prefix).toString('base64'), + }, + blockReference, + deps, + }); + + return values.reduce((state, { key, value }) => ({ + ...state, + [key]: value, + }), {}); +} + +/** + * Get the nonce for the specified access key + * @param account target account/contract being queried + * @param publicKey public key string to be queried + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getNonce({ account, publicKey, blockReference, deps }: ViewAccessKeyParams) { + const { nonce } = await getAccessKey({ + account, + publicKey, + blockReference, + deps, + }); + + return BigInt(nonce); +} + +/** + * Get the balance staked with a validator + * @param account the account staking Near with the validator + * @param validator contract with which Near is staked + * @param blockReference block ID/finality + * @param deps readonly RPC dependencies + */ +export async function getStakedBalance({ account, validator, blockReference, deps }: ViewValidatorStakeParams) { + const staked = await view({ + account: validator, + blockReference, + method: 'get_account', + args: { account_id: account }, + deps, + }); + + return { + canWithdraw: staked.can_withdraw, + stakedBalance: BigInt(staked.staked_balance), + unstakedBalance: BigInt(staked.unstaked_balance), + }; +} diff --git a/packages/cookbook/tsconfig.cjs.json b/packages/client/tsconfig.cjs.json similarity index 100% rename from packages/cookbook/tsconfig.cjs.json rename to packages/client/tsconfig.cjs.json diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000000..75b9dc47ae --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm", + }, + "files": [ + "src/index.ts" + ] +} \ No newline at end of file diff --git a/packages/client/typedoc.json b/packages/client/typedoc.json new file mode 100644 index 0000000000..61b70ef7b6 --- /dev/null +++ b/packages/client/typedoc.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "../../typedoc.json" + ], + "entryPoints": [ + "src" + ], + "entryPointStrategy": "expand", +} \ No newline at end of file diff --git a/packages/cookbook/accounts/access-keys/create-full-access-key.js b/packages/cookbook/accounts/access-keys/create-full-access-key.js deleted file mode 100644 index 8f566a0fe6..0000000000 --- a/packages/cookbook/accounts/access-keys/create-full-access-key.js +++ /dev/null @@ -1,25 +0,0 @@ -const { KeyPair, keyStores, connect } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -const ACCOUNT_ID = "example.testnet"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -createFullAccessKey(ACCOUNT_ID); - -async function createFullAccessKey(accountId) { - const keyPair = KeyPair.fromRandom("ed25519"); - const publicKey = keyPair.publicKey.toString(); - const near = await connect(config); - const account = await near.account(accountId); - await keyStore.setKey(config.networkId, publicKey, keyPair); - await account.addKey(publicKey); -} diff --git a/packages/cookbook/accounts/access-keys/create-full-access-key.ts b/packages/cookbook/accounts/access-keys/create-full-access-key.ts new file mode 100644 index 0000000000..f86c7224cc --- /dev/null +++ b/packages/cookbook/accounts/access-keys/create-full-access-key.ts @@ -0,0 +1,49 @@ +import { + addFullAccessKey, + generateRandomKeyPair, + getSignerFromKeystore, + getTestnetRpcProvider, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + + +export default async function createFullAccessKey(accountId: string) { + if (!accountId) { + console.log(chalk`{red pnpm createFullAccessKey -- ACCOUNT_ID}`); + return; + } + + // initialize testnet RPC provider + 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 previousKey = await signer.getPublicKey(); + + // create a new key from random data + const keyPair = generateRandomKeyPair('ed25519'); + + // add the generated key to the account as a Full Access Key (FAK) + await addFullAccessKey({ + account: accountId, + publicKey: keyPair.getPublicKey().toString(), + deps: { + rpcProvider, + signer, + }, + }); + + // overwrite the key used to sign the transaction + // above with the new FAK in the key store + await keystore.setKey('testnet', accountId, keyPair); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Added new full access key and set in keystore}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Previous Key} {white |} {bold.yellow ${previousKey.toString()}}`); + console.log(chalk`{bold.white New Key} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/access-keys/create-function-access-key.js b/packages/cookbook/accounts/access-keys/create-function-access-key.js deleted file mode 100644 index 0352bf5542..0000000000 --- a/packages/cookbook/accounts/access-keys/create-function-access-key.js +++ /dev/null @@ -1,30 +0,0 @@ -const { KeyPair, keyStores, connect } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -const ACCOUNT_ID = "example.testnet"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -addFunctionAccessKey(ACCOUNT_ID); - -async function addFunctionAccessKey(accountId) { - const keyPair = KeyPair.fromRandom("ed25519"); - const publicKey = keyPair.publicKey.toString(); - const near = await connect(config); - const account = await near.account(accountId); - await keyStore.setKey(config.networkId, publicKey, keyPair); - await account.addKey( - publicKey, // public key for new account - "example-account.testnet", // contract this key is allowed to call (optional) - "example_method", // methods this key is allowed to call (optional) - "2500000000000" // allowance key can use to call methods (optional) - ); -} diff --git a/packages/cookbook/accounts/access-keys/create-function-access-key.ts b/packages/cookbook/accounts/access-keys/create-function-access-key.ts new file mode 100644 index 0000000000..41085275f4 --- /dev/null +++ b/packages/cookbook/accounts/access-keys/create-function-access-key.ts @@ -0,0 +1,47 @@ +import { + addFunctionCallAccessKey, + generateRandomKeyPair, + getSignerFromKeystore, + getTestnetRpcProvider, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +const CONTRACT_NAME = 'example-account.testnet'; +const METHOD_NAMES = ['example_method']; + +export default async function createFunctionCallAccessKey(accountId: string, contract = CONTRACT_NAME, methods = METHOD_NAMES) { + if (!accountId) { + console.log(chalk`{red pnpm createFunctionCallAccessKey -- ACCOUNT_ID}`); + return; + } + + // 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'))); + + // create a new key from random data + const keyPair = generateRandomKeyPair('ed25519'); + + // add the generated key to the account as a Function Call Access Key + await addFunctionCallAccessKey({ + account: accountId, + publicKey: keyPair.getPublicKey().toString(), + contract, + methodNames: methods, + allowance: 2500000000000n, + deps: { + rpcProvider, + signer, + }, + }); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Added new function call access key}` ); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white New Key} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}} {white |} {bold.yellow ${contract}} {white |} {bold.yellow ${methods}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/access-keys/delete-access-key.js b/packages/cookbook/accounts/access-keys/delete-access-key.js deleted file mode 100644 index 7cebf9d1c1..0000000000 --- a/packages/cookbook/accounts/access-keys/delete-access-key.js +++ /dev/null @@ -1,25 +0,0 @@ -const { keyStores, connect } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -// NOTE: replace "example.testnet" with your accountId -const ACCOUNT_ID = "example.testnet"; -// NOTE: replace this PK with the one that you are trying to delete -const PUBLIC_KEY = "ed25519:4yLYjwC3Rd8EkhKwVPoAdy6EUcCVSz2ZixFtsCeuBEZD"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -deleteAccessKey(ACCOUNT_ID, PUBLIC_KEY); - -async function deleteAccessKey(accountId, publicKey) { - const near = await connect(config); - const account = await near.account(accountId); - await account.deleteKey(publicKey); -} diff --git a/packages/cookbook/accounts/access-keys/delete-access-key.ts b/packages/cookbook/accounts/access-keys/delete-access-key.ts new file mode 100644 index 0000000000..bfc32f67aa --- /dev/null +++ b/packages/cookbook/accounts/access-keys/delete-access-key.ts @@ -0,0 +1,42 @@ +import { + deleteAccessKey, + getSignerFromKeystore, + getTestnetRpcProvider, + parseKeyPair, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import { type KeyPairString } from '@near-js/crypto'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +export default async function deleteAccessKeyCookbook(accountId: string, publicKey: string) { + if (!accountId) { + console.log(chalk`{red pnpm deleteAccessKey -- ACCOUNT_ID [PUBLIC_KEY]}`); + return; + } + + // 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'))); + + // parse the target key from its string representation + const keyPair = parseKeyPair(publicKey as KeyPairString); + + // add the generated key to the account as a Function Call Access Key + await deleteAccessKey({ + account: accountId, + publicKey: keyPair.getPublicKey().toString(), + deps: { + rpcProvider, + signer, + }, + }); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Deleted access key}` ); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Deleted Key} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/create-funded-testnet-account.ts b/packages/cookbook/accounts/create-funded-testnet-account.ts new file mode 100644 index 0000000000..428d14e925 --- /dev/null +++ b/packages/cookbook/accounts/create-funded-testnet-account.ts @@ -0,0 +1,34 @@ +import { + createFundedTestnetAccount, + generateRandomKeyPair, +} from '@near-js/client'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; + +export default async function createFundedTestnetAccountCookbook(accountId: string) { + if (!accountId) { + console.log(chalk`{red pnpm createFundedTestnetAccount -- ACCOUNT_ID}`); + return; + } + + // initialize the transaction signer using a pre-existing key for ACCOUNT_ID + const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); + + // create new keypair and persist it to filesystem keystore + const keyPair = generateRandomKeyPair('ed25519'); + await keystore.setKey('testnet', accountId, keyPair); + + // call funded testnet creation endpoint + await createFundedTestnetAccount({ + newAccount: accountId, + newPublicKey: keyPair.getPublicKey().toString(), + }); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Created funded testnet account}` ); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white New Account} {white |} {bold.yellow ${accountId}} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/create-mainnet-account.js b/packages/cookbook/accounts/create-mainnet-account.js deleted file mode 100644 index ddbcfa586e..0000000000 --- a/packages/cookbook/accounts/create-mainnet-account.js +++ /dev/null @@ -1,44 +0,0 @@ -const HELP = `Please run this script in the following format: - - node create-mainnet-account.js CREATOR_ACCOUNT.near NEW_ACCOUNT.near AMOUNT" -`; - -const { connect, KeyPair, keyStores, utils } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "mainnet", - nodeUrl: "https://rpc.mainnet.near.org", -}; - -if (process.argv.length !== 5) { - console.info(HELP); - process.exit(1); -} - -createAccount(process.argv[2], process.argv[3], process.argv[4]); - -async function createAccount(creatorAccountId, newAccountId, amount) { - const near = await connect(config); - const creatorAccount = await near.account(creatorAccountId); - const keyPair = KeyPair.fromRandom("ed25519"); - const publicKey = keyPair.publicKey.toString(); - await keyStore.setKey(config.networkId, newAccountId, keyPair); - - return await creatorAccount.functionCall({ - contractId: "near", - methodName: "create_account", - args: { - new_account_id: newAccountId, - new_public_key: publicKey, - }, - gas: "300000000000000", - attachedDeposit: utils.format.parseNearAmount(amount), - }); -} diff --git a/packages/cookbook/accounts/create-mainnet-account.ts b/packages/cookbook/accounts/create-mainnet-account.ts new file mode 100644 index 0000000000..14c07b5f29 --- /dev/null +++ b/packages/cookbook/accounts/create-mainnet-account.ts @@ -0,0 +1,49 @@ +import { + createTopLevelAccount, + generateRandomKeyPair, + getMainnetRpcProvider, + getSignerFromKeystore, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +export default async function createMainnetAccountCookbook(accountId: string, newAccountId?: string) { + if (!accountId) { + console.log(chalk`{red pnpm createMainnetAccount -- CREATOR_ACCOUNT_ID [NEW_ACCOUNT_ID]}`); + return; + } + + // initialize testnet RPC provider + 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); + + // create a new key from random data + const keyPair = generateRandomKeyPair('ed25519'); + + const newAccount = newAccountId || `${new Date().valueOf()}-${Math.ceil(Math.random() * 10e12)}.near`; + + await createTopLevelAccount({ + account: accountId, + contract: 'mainnet', + initialBalance: 100n, + newAccount, + newPublicKey: keyPair.getPublicKey().toString(), + deps: { + rpcProvider, + signer, + }, + }); + + // save new account in plaintext filesystem keystore + await keystore.setKey('mainnet', accountId, keyPair); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Created mainnet account}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white New Account} {white |} {bold.yellow ${accountId}} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/create-testnet-account.js b/packages/cookbook/accounts/create-testnet-account.js deleted file mode 100644 index 1a2c5f1b15..0000000000 --- a/packages/cookbook/accounts/create-testnet-account.js +++ /dev/null @@ -1,44 +0,0 @@ -const HELP = `Please run this script in the following format: - - node create-testnet-account.js CREATOR_ACCOUNT.testnet NEW_ACCOUNT.testnet AMOUNT -`; - -const { connect, KeyPair, keyStores, utils } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -if (process.argv.length !== 5) { - console.info(HELP); - process.exit(1); -} - -createAccount(process.argv[2], process.argv[3], process.argv[4]); - -async function createAccount(creatorAccountId, newAccountId, amount) { - const near = await connect({ ...config, keyStore }); - const creatorAccount = await near.account(creatorAccountId); - const keyPair = KeyPair.fromRandom("ed25519"); - const publicKey = keyPair.publicKey.toString(); - await keyStore.setKey(config.networkId, newAccountId, keyPair); - - return await creatorAccount.functionCall({ - contractId: "testnet", - methodName: "create_account", - args: { - new_account_id: newAccountId, - new_public_key: publicKey, - }, - gas: "300000000000000", - attachedDeposit: utils.format.parseNearAmount(amount), - }); -} diff --git a/packages/cookbook/accounts/create-testnet-account.ts b/packages/cookbook/accounts/create-testnet-account.ts new file mode 100644 index 0000000000..cd3e41d454 --- /dev/null +++ b/packages/cookbook/accounts/create-testnet-account.ts @@ -0,0 +1,49 @@ +import { + createTopLevelAccount, + generateRandomKeyPair, + getSignerFromKeystore, + getTestnetRpcProvider, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +export default async function createTestnetAccountCookbook(accountId: string, newAccountId?: string) { + if (!accountId) { + console.log(chalk`{red pnpm createTestnetAccount -- CREATOR_ACCOUNT_ID [NEW_ACCOUNT_ID]}`); + return; + } + + // initialize testnet RPC provider + 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); + + // create a new key from random data + const keyPair = generateRandomKeyPair('ed25519'); + + const newAccount = newAccountId || `${new Date().valueOf()}-${Math.ceil(Math.random() * 10e12)}.testnet`; + + await createTopLevelAccount({ + account: accountId, + contract: 'testnet', + initialBalance: 100n, + newAccount, + newPublicKey: keyPair.getPublicKey().toString(), + deps: { + rpcProvider, + signer, + }, + }); + + // save new account in plaintext filesystem keystore + await keystore.setKey('testnet', accountId, keyPair); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Created testnet account}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white New Account} {white |} {bold.yellow ${accountId}} {white |} {bold.yellow ${keyPair.getPublicKey().toString()}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/accounts/index.ts b/packages/cookbook/accounts/index.ts new file mode 100644 index 0000000000..5f40f3dcde --- /dev/null +++ b/packages/cookbook/accounts/index.ts @@ -0,0 +1,6 @@ +export * from './access-keys/create-full-access-key'; +export * from './access-keys/create-function-access-key'; +export * from './access-keys/delete-access-key'; +export * from './create-funded-testnet-account'; +export * from './create-mainnet-account'; +export * from './create-testnet-account'; \ No newline at end of file diff --git a/packages/cookbook/index.ts b/packages/cookbook/index.ts new file mode 100644 index 0000000000..1bef8a034d --- /dev/null +++ b/packages/cookbook/index.ts @@ -0,0 +1,3 @@ +export * from './accounts'; +export * from './transactions'; +export * from './utils'; diff --git a/packages/cookbook/package.json b/packages/cookbook/package.json index cb8a6806de..271d469e28 100644 --- a/packages/cookbook/package.json +++ b/packages/cookbook/package.json @@ -3,17 +3,43 @@ "private": true, "version": "1.0.22", "description": "", - "main": "main.js", + "main": "index.ts", + "type": "module", "author": "", "license": "ISC", - "dependencies": { - "@near-js/accounts": "workspace:*", + "scripts": { + "build": "tsc -p tsconfig.json", + "addFullAccessKey": "tsx -e \"import f from './accounts/access-keys/create-full-access-key.ts'; f(...process.argv.slice(1));\"", + "addFunctionCallAccessKey": "tsx -e \"import f from './accounts/access-keys/create-function-access-key.ts'; f(...process.argv.slice(1));\"", + "batchTransactions": "tsx -e \"import f from './transactions/batch-transactions.ts'; f(...process.argv.slice(1));\"", + "calculateGas": "tsx -e \"import f from './utils/calculate-gas.ts'; f(...process.argv.slice(1));\"", + "checkAccountExists": "tsx -e \"import f from './utils/check-account-existence.ts'; f(...process.argv.slice(1));\"", + "createFundedTestnetAccount": "tsx -e \"import f from './accounts/create-funded-testnet-account.ts'; f(...process.argv.slice(1));\"", + "createMainnetAccount": "tsx -e \"import f from './accounts/create-mainnet-account.ts'; f(...process.argv.slice(1));\"", + "createTestnetAccount": "tsx -e \"import f from './accounts/create-testnet-account.ts'; f(...process.argv.slice(1));\"", + "deleteAccessKey": "tsx -e \"import f from './accounts/access-keys/delete-access-key.ts'; f(...process.argv.slice(1));\"", + "deployContract": "tsx -e \"import f from './utils/deploy-contract.ts'; f(...process.argv.slice(1));\"", + "getState": "tsx -e \"import f from './utils/get-state.ts'; f(...process.argv.slice(1));\"", + "getTransactionStatus": "tsx -e \"import f from './transactions/get-tx-status.ts'; f(...process.argv.slice(1));\"", + "metaTransaction": "tsx -e \"import f from './transactions/meta-transaction.ts'; f(...process.argv.slice(1));\"", + "metaTransactionRelayer": "tsx -e \"import f from './transactions/meta-transaction-relayer.ts'; f(...process.argv.slice(1));\"", + "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));\"" + }, + "devDependencies": { + "@near-js/client": "workspace:*", + "@near-js/crypto": "workspace:*", "@near-js/keystores-node": "workspace:*", - "@near-js/providers": "workspace:*", - "@near-js/signers": "workspace:*", "@near-js/transactions": "workspace:*", + "@near-js/utils": "workspace:*", + "build": "workspace:*", "chalk": "4.1.1", "homedir": "0.6.0", - "near-api-js": "workspace:*" + "near-api-js": "workspace:*", + "ts-node": "^10.9.2", + "tsconfig": "workspace:*", + "typescript": "5.4.5" } } diff --git a/packages/cookbook/transactions/batch-transactions.js b/packages/cookbook/transactions/batch-transactions.js deleted file mode 100644 index 29bcd2326f..0000000000 --- a/packages/cookbook/transactions/batch-transactions.js +++ /dev/null @@ -1,41 +0,0 @@ -const { connect, transactions, keyStores } = require("near-api-js"); -const fs = require("fs"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -// NOTE: replace "example" with your accountId -const CONTRACT_NAME = "contract.example.testnet"; -const WHITELIST_ACCOUNT_ID = "whitelisted-account.example.testnet"; -const WASM_PATH = path.join(__dirname, "../utils/wasm-files/staking_pool_factory.wasm"); - -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -sendTransactions(); - -async function sendTransactions() { - const near = await connect({ ...config, keyStore }); - const account = await near.account(CONTRACT_NAME); - const newArgs = { staking_pool_whitelist_account_id: WHITELIST_ACCOUNT_ID }; - const result = await account.signAndSendTransaction({ - receiverId: CONTRACT_NAME, - actions: [ - transactions.deployContract(fs.readFileSync(WASM_PATH)), - transactions.functionCall( - "new", - Buffer.from(JSON.stringify(newArgs)), - 10000000000000, - "0" - ), - ], - }); - - console.log(result); -} diff --git a/packages/cookbook/transactions/batch-transactions.ts b/packages/cookbook/transactions/batch-transactions.ts new file mode 100644 index 0000000000..612363faa9 --- /dev/null +++ b/packages/cookbook/transactions/batch-transactions.ts @@ -0,0 +1,46 @@ +import { + getSignerFromKeystore, + getTestnetRpcProvider, + SignedTransactionComposer, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +// NOTE: replace "example" with your accountId +const CONTRACT_NAME = 'contract.example.testnet'; +const WHITELIST_ACCOUNT_ID = 'whitelisted-account.example.testnet'; +const WASM_PATH = join(__dirname, '../utils/wasm-files/staking_pool_factory.wasm'); + +export default async function batchTransactions(accountId: string = CONTRACT_NAME, whitelistAccountId: string = WHITELIST_ACCOUNT_ID, wasmPath: string = WASM_PATH) { + if (!accountId || !whitelistAccountId || !wasmPath) { + console.log(chalk`{red pnpm batchTransactions -- [ACCOUNT_ID] [WHITELIST_ACCOUNT_ID] [WASM_PATH]}`); + return; + } + + // 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 { result } = await SignedTransactionComposer.init({ + sender: accountId, + receiver: accountId, + deps: { rpcProvider, signer }, + }) + .deployContract(await readFile(wasmPath)) + .functionCall( + 'new', + Buffer.from(JSON.stringify({ staking_pool_whitelist_account_id: WHITELIST_ACCOUNT_ID })), + 10000000000000n, + ) + .signAndSend(); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Deployed contract at ${wasmPath} to ${accountId}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Call result} {white |} {bold.yellow ${JSON.stringify(result, null, 2)}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/transactions/get-tx-status.js b/packages/cookbook/transactions/get-tx-status.js deleted file mode 100644 index d6a20ffaeb..0000000000 --- a/packages/cookbook/transactions/get-tx-status.js +++ /dev/null @@ -1,18 +0,0 @@ -// demonstrates how to get a transaction status -const { providers } = require("near-api-js"); - -//network config (replace testnet with mainnet or betanet) -const provider = new providers.JsonRpcProvider( - "https://archival-rpc.testnet.near.org" -); - -const TX_HASH = "9av2U6cova7LZPA9NPij6CTUrpBbgPG6LKVkyhcCqtk3"; -// account ID associated with the transaction -const ACCOUNT_ID = "sender.testnet"; - -getState(TX_HASH, ACCOUNT_ID); - -async function getState(txHash, accountId) { - const result = await provider.txStatus(txHash, accountId); - console.log("Result: ", result); -} diff --git a/packages/cookbook/transactions/get-tx-status.ts b/packages/cookbook/transactions/get-tx-status.ts new file mode 100644 index 0000000000..bc4a609b0a --- /dev/null +++ b/packages/cookbook/transactions/get-tx-status.ts @@ -0,0 +1,20 @@ +import { + getTestnetRpcArchivalProvider, +} from '@near-js/client'; + +const TX_HASH = '9av2U6cova7LZPA9NPij6CTUrpBbgPG6LKVkyhcCqtk3'; +// account ID associated with the transaction +const ACCOUNT_ID = 'sender.testnet'; + +export default async function getTransactionStatus(accountId: string = ACCOUNT_ID, transactionHash: string = TX_HASH) { + // 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 + }); + + console.log(JSON.stringify(result, null, 2)); +} diff --git a/packages/cookbook/transactions/index.ts b/packages/cookbook/transactions/index.ts new file mode 100644 index 0000000000..6b78b23e7c --- /dev/null +++ b/packages/cookbook/transactions/index.ts @@ -0,0 +1,5 @@ +export * from './batch-transactions'; +export * from './get-tx-status'; +export * from './meta-transaction'; +export * from './meta-transaction-relayer'; +export * from './traverse-blocks'; diff --git a/packages/cookbook/transactions/meta-transaction-relayer.js b/packages/cookbook/transactions/meta-transaction-relayer.js deleted file mode 100644 index a0f5054fb1..0000000000 --- a/packages/cookbook/transactions/meta-transaction-relayer.js +++ /dev/null @@ -1,51 +0,0 @@ -const { Account } = require('@near-js/accounts'); -const { UnencryptedFileSystemKeyStore } = require('@near-js/keystores-node'); -const { JsonRpcProvider, fetchJson } = require('@near-js/providers'); -const { InMemorySigner } = require('@near-js/signers'); -const { actionCreators, encodeSignedDelegate } = require('@near-js/transactions'); -const os = require('os'); -const path = require('path'); - -const { transfer } = actionCreators; - -async function sendNearThroughRelayer({ amount, receiverId, senderAccount }) { - const signedDelegate = await senderAccount.signedDelegate({ - actions: [transfer(amount)], - blockHeightTtl: 60, - receiverId - }); - - return fetchJson( - 'https://relayer.org/relay', - JSON.stringify(Array.from(encodeSignedDelegate(signedDelegate))) - ); -} - -module.exports = { - sendNearThroughRelayer, -}; - -if (require.main === module) { - (async function () { - const networkId = 'testnet'; - const provider = new JsonRpcProvider({ url: 'https://rpc.testnet.near.org' }); - - const CREDENTIALS_DIR = '.near-credentials'; - const credentialsPath = path.join(os.homedir(), CREDENTIALS_DIR); - - const RECEIVER_ACCOUNT_ID = 'receiver.testnet'; // the ultimate recipient of the meta transaction execution - const SENDER_ACCOUNT_ID = 'sender.testnet'; // the account requesting the transaction be executed - - const senderAccount = new Account({ - networkId, - provider, - signer: new InMemorySigner(new UnencryptedFileSystemKeyStore(credentialsPath)) - }, SENDER_ACCOUNT_ID); - - console.log(await sendNearThroughRelayer({ - amount: BigInt('1000000000'), - receiverId: RECEIVER_ACCOUNT_ID, - senderAccount, - })); - }()); -} \ No newline at end of file diff --git a/packages/cookbook/transactions/meta-transaction-relayer.ts b/packages/cookbook/transactions/meta-transaction-relayer.ts new file mode 100644 index 0000000000..14e8820b56 --- /dev/null +++ b/packages/cookbook/transactions/meta-transaction-relayer.ts @@ -0,0 +1,46 @@ +import { + getSignerFromKeystore, + getTestnetRpcProvider, + SignedTransactionComposer, +} from '@near-js/client'; +import { encodeSignedDelegate } from '@near-js/transactions'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; + +/** + * Submit a transaction to a relayer + * @param signerAccountId account requesting the transaction's execution + * @param receiverAccountId recipient of the transaction + * @param relayerUrl URL processing relayer requests + */ +export default async function sendMetaTransactionViaRelayer(signerAccountId: string, receiverAccountId: string, relayerUrl: string) { + if (!signerAccountId || !receiverAccountId) { + console.log(chalk`{red pnpm metaTransaction -- SENDER_ACCOUNT_ID RECEIVER_ACCOUNT_ID RELAYER_URL}`); + return; + } + + // 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 signedDelegate = await SignedTransactionComposer.init({ + sender: signerAccountId, + receiver: receiverAccountId, + deps: { + rpcProvider, + signer, + }, + }) + .transfer(100n) + .toSignedDelegateAction({ blockHeightTtl: 60n }); + + // @ts-ignore global + const res = await fetch(relayerUrl, { + method: 'POST', + body: JSON.stringify(Array.from(encodeSignedDelegate(signedDelegate))), + }); + console.log(await res.json()); +} diff --git a/packages/cookbook/transactions/meta-transaction.js b/packages/cookbook/transactions/meta-transaction.js deleted file mode 100644 index a37a8c3562..0000000000 --- a/packages/cookbook/transactions/meta-transaction.js +++ /dev/null @@ -1,60 +0,0 @@ -const { Account } = require('@near-js/accounts'); -const { UnencryptedFileSystemKeyStore } = require('@near-js/keystores-node'); -const { JsonRpcProvider } = require('@near-js/providers'); -const { InMemorySigner } = require('@near-js/signers'); -const { actionCreators } = require('@near-js/transactions'); -const os = require('os'); -const path = require('path'); - -const { signedDelegate, transfer } = actionCreators; - -async function sendNearViaMetaTransaction({ amount, receiverId, senderAccount, signingAccount }) { - const delegate = await senderAccount.signedDelegate({ - actions: [transfer(amount)], - blockHeightTtl: 60, - receiverId, - }); - - return signingAccount.signAndSendTransaction({ - actions: [signedDelegate(delegate)], - receiverId: delegate.delegateAction.senderId, - }); -} - -module.exports = { - sendNearViaMetaTransaction, -}; - -if (require.main === module) { - (async function () { - const networkId = 'testnet'; - const provider = new JsonRpcProvider({ url: 'https://rpc.testnet.near.org' }); - - const CREDENTIALS_DIR = '.near-credentials'; - const credentialsPath = path.join(os.homedir(), CREDENTIALS_DIR); - - // access keys are required for the sender and signer - const RECEIVER_ACCOUNT_ID = 'receiver.testnet'; // the ultimate recipient of the meta transaction execution - const SENDER_ACCOUNT_ID = 'sender.testnet'; // the account requesting the transaction be executed - const SIGNER_ACCOUNT_ID = 'signer.testnet'; // the account executing the meta transaction on behalf (e.g. as a relayer) of the sender - - const senderAccount = new Account({ - networkId, - provider, - signer: new InMemorySigner(new UnencryptedFileSystemKeyStore(credentialsPath)) - }, SENDER_ACCOUNT_ID); - - const signingAccount = new Account({ - networkId, - provider, - signer: new InMemorySigner(new UnencryptedFileSystemKeyStore(credentialsPath)) - }, SIGNER_ACCOUNT_ID); - - console.log(await sendNearViaMetaTransaction({ - amount: BigInt('1000000000'), - receiverId: RECEIVER_ACCOUNT_ID, - senderAccount, - signingAccount, - })); - }()); -} \ No newline at end of file diff --git a/packages/cookbook/transactions/meta-transaction.ts b/packages/cookbook/transactions/meta-transaction.ts new file mode 100644 index 0000000000..71593d5a36 --- /dev/null +++ b/packages/cookbook/transactions/meta-transaction.ts @@ -0,0 +1,63 @@ +import { + getSignerFromKeystore, + getTestnetRpcProvider, + SignedTransactionComposer, +} from '@near-js/client'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; + +// access keys are required for the sender and signer +const RECEIVER_ACCOUNT_ID = 'receiver.testnet'; // the ultimate recipient of the meta transaction execution +const SENDER_ACCOUNT_ID = 'sender.testnet'; // the account requesting the transaction be executed +const SIGNER_ACCOUNT_ID = 'signer.testnet'; // the account executing the meta transaction on behalf (e.g. as a relayer) of the sender + +/** + * Sign and send a meta transaction + * @param signerAccountId the account that wants actions executed on their behalf + * @param receiverAccountId ultimate recipient of the transaction + * @param senderAccountId the account (i.e. relayer) executing the transaction on behalf of the signer + */ +export default async function metaTransaction(signerAccountId: string = SIGNER_ACCOUNT_ID, receiverAccountId: string = RECEIVER_ACCOUNT_ID, senderAccountId: string = SENDER_ACCOUNT_ID): Promise { + if (!signerAccountId || !receiverAccountId || !senderAccountId) { + console.log(chalk`{red pnpm metaTransaction -- [SIGNER_ACCOUNT_ID] [RECEIVER_ACCOUNT_ID] [SENDER_ACCOUNT_ID]}`); + return; + } + + // 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 { delegateAction, signature } = await SignedTransactionComposer.init({ + sender: signerAccountId, + receiver: receiverAccountId, + deps: { + rpcProvider, + signer, + }, + }) + .transfer(100n) + .toSignedDelegateAction(); + + // initialize the relayer's signer + const relayerSigner = getSignerFromKeystore( + senderAccountId, + 'testnet', + new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')) + ); + + // sign the outer transaction using the relayer's key + return SignedTransactionComposer.init({ + sender: senderAccountId, + receiver: receiverAccountId, + deps: { rpcProvider, signer: relayerSigner }, + }) + .signedDelegate(delegateAction, signature) + .signAndSend(); +} diff --git a/packages/cookbook/transactions/get-tx-detail.js b/packages/cookbook/transactions/traverse-blocks.ts similarity index 51% rename from packages/cookbook/transactions/get-tx-detail.js rename to packages/cookbook/transactions/traverse-blocks.ts index 7c6bc39bd4..9a37bf1d65 100644 --- a/packages/cookbook/transactions/get-tx-detail.js +++ b/packages/cookbook/transactions/traverse-blocks.ts @@ -1,8 +1,7 @@ -const { connect, keyStores } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); +import { + getTestnetRpcArchivalProvider, +} from '@near-js/client'; -const CREDENTIALS_DIR = ".near-credentials"; // block hash of query start (oldest block) const START_BLOCK_HASH = "GZ8vKdcgsavkEndkDWHCjuhyqSR2TGnp9VDZbTzd6ufG"; // block hash of query end (newest block) @@ -10,40 +9,23 @@ const END_BLOCK_HASH = "8aEcKhF7N1Jyw84e6vHW6Hzp3Ep7mSXJ6Rvnsy5qGJPF"; // contract ID or account ID you want to find transactions details for const CONTRACT_ID = "relayer.ropsten.testnet"; -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -// NOTE: we're using the archival rpc to look back in time for a specific set -// of transactions. For a full list of what nodes are available, visit: -// https://docs.near.org/docs/develop/node/intro/types-of-node -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://archival-rpc.testnet.near.org", -}; - -getTransactions(START_BLOCK_HASH, END_BLOCK_HASH, CONTRACT_ID); - -async function getTransactions(startBlock, endBlock, accountId) { - const near = await connect(config); +export default async function traverseBlocks(startBlockHash: string = START_BLOCK_HASH, endBlockHash: string = END_BLOCK_HASH, contractId: string = CONTRACT_ID) { + // initialize testnet RPC provider + const rpcProvider = getTestnetRpcArchivalProvider(); // creates an array of block hashes for given range const blockArr = []; - let blockHash = endBlock; + let blockHash = endBlockHash; do { - const currentBlock = await getBlockByID(blockHash); + const currentBlock = await rpcProvider.block({ blockId: blockHash }); blockArr.push(currentBlock.header.hash); blockHash = currentBlock.header.prev_hash; console.log("working...", blockHash); - } while (blockHash !== startBlock); + } while (blockHash !== startBlockHash); // returns block details based on hashes in array const blockDetails = await Promise.all( - blockArr.map((blockId) => - near.connection.provider.block({ - blockId, - }) - ) + blockArr.map((blockId) => rpcProvider.block({ blockId })) ); // returns an array of chunk hashes from block details @@ -53,14 +35,14 @@ async function getTransactions(startBlock, endBlock, accountId) { //returns chunk details based from the array of hashes const chunkDetails = await Promise.all( - chunkHashArr.map(chunk => near.connection.provider.chunk(chunk)) + chunkHashArr.map(chunk => rpcProvider.chunk(chunk)) ); // checks chunk details for transactions // if there are transactions in the chunk we // find ones associated with passed accountId const transactions = chunkDetails.flatMap((chunk) => - (chunk.transactions || []).filter((tx) => tx.signer_id === accountId) + (chunk.transactions || []).filter((tx) => tx.signer_id === contractId) ); //creates transaction links from matchingTxs @@ -71,11 +53,3 @@ async function getTransactions(startBlock, endBlock, accountId) { console.log("MATCHING TRANSACTIONS: ", transactions); console.log("TRANSACTION LINKS: ", txsLinks); } - -async function getBlockByID(blockID) { - const near = await connect(config); - const blockInfoByHeight = await near.connection.provider.block({ - blockId: blockID, - }); - return blockInfoByHeight; -} diff --git a/packages/cookbook/tsconfig.json b/packages/cookbook/tsconfig.json index cddbc6a829..56d9083e21 100644 --- a/packages/cookbook/tsconfig.json +++ b/packages/cookbook/tsconfig.json @@ -1,11 +1,9 @@ { + "extends": "tsconfig/esm.json", "compilerOptions": { - "module": "commonjs", - "allowJs": true, - "outDir": "./dist", - }, - "include": [ - "**/*.js", - "**/*.ts" + "noEmit": true + }, + "files": [ + "index.ts" ] } \ No newline at end of file diff --git a/packages/cookbook/utils/calculate-gas.js b/packages/cookbook/utils/calculate-gas.js deleted file mode 100644 index 438916f8a8..0000000000 --- a/packages/cookbook/utils/calculate-gas.js +++ /dev/null @@ -1,65 +0,0 @@ -const { connect, keyStores, utils } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); -const chalk = require("chalk"); - -const CREDENTIALS_DIR = ".near-credentials"; -const ACCOUNT_ID = "near-example.testnet"; -const CONTRACT_ID = "guest-book.testnet"; -const METHOD_NAME = "addMessage"; -const MAX_GAS = "300000000000000"; -const ATTACHED_DEPOSIT = "0"; - -const args = { - text: "Howdy!", -}; - -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -calculateGas(CONTRACT_ID, METHOD_NAME, args, ATTACHED_DEPOSIT); - -async function calculateGas(contractId, methodName, args, depositAmount) { - const near = await connect(config); - const account = await near.account(ACCOUNT_ID); - const result = await account.functionCall({ - contractId, - methodName, - args, - gas: MAX_GAS, - attachedDeposit: utils.format.parseNearAmount(depositAmount), - }); - const { totalGasBurned, totalTokensBurned } = result.receipts_outcome.reduce( - (acc, receipt) => { - acc.totalGasBurned += receipt.outcome.gas_burnt; - acc.totalTokensBurned += utils.format.formatNearAmount( - receipt.outcome.tokens_burnt - ); - return acc; - }, - { - totalGasBurned: result.transaction_outcome.outcome.gas_burnt, - totalTokensBurned: utils.format.formatNearAmount( - result.transaction_outcome.outcome.tokens_burnt - ), - } - ); - - console.log(chalk`{white ------------------------------------------------------------------------ }`) - console.log(chalk`{bold.green RESULTS} {white for: [ {bold.blue ${METHOD_NAME}} ] called on contract: [ {bold.blue ${CONTRACT_ID}} ]}` ) - console.log(chalk`{white ------------------------------------------------------------------------ }`) - console.log(chalk`{bold.white Gas Burnt} {white |} {bold.yellow ${totalGasBurned}}`); - console.log(chalk`{bold.white Tokens Burnt} {white |} {bold.yellow ${totalTokensBurned}}`); - console.log(chalk`{white ------------------------------------------------------------------------ }`) - - return { - totalTokensBurned, - totalGasBurned, - }; -} diff --git a/packages/cookbook/utils/calculate-gas.ts b/packages/cookbook/utils/calculate-gas.ts new file mode 100644 index 0000000000..e35866fdd8 --- /dev/null +++ b/packages/cookbook/utils/calculate-gas.ts @@ -0,0 +1,64 @@ +import { + formatNearAmount, + functionCall, + getSignerFromKeystore, + getTestnetRpcProvider, + MAX_GAS, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +const CONTRACT_ID = "guest-book.testnet"; +const METHOD_NAME = "addMessage"; + +export default async function calculateGas(accountId: string, method = METHOD_NAME, contract = CONTRACT_ID) { + if (!accountId) { + console.log(chalk`{red pnpm calculateGas -- ACCOUNT_ID}`); + return; + } + + // 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 { + outcome: { + receipts_outcome: receiptsOutcome, + transaction_outcome: { outcome: transactionOutcome }, + }, + } = await functionCall({ + sender: accountId, + receiver: contract, + method, + args: { + text: 'Howdy!', + }, + gas: MAX_GAS, + deps: { + rpcProvider, + signer, + }, + }); + + const { totalGasBurned, totalTokensBurned } = receiptsOutcome.reduce( + (acc, receipt) => { + acc.totalGasBurned += receipt.outcome.gas_burnt; + acc.totalTokensBurned += BigInt(receipt.outcome.tokens_burnt); + return acc; + }, + { + totalGasBurned: transactionOutcome.gas_burnt, + totalTokensBurned: BigInt(transactionOutcome.tokens_burnt), + } + ); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white for: [ {bold.blue ${method}} ] called on contract: [ {bold.blue ${contract}} ]}` ); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Gas Burnt} {white |} {bold.yellow ${formatNearAmount(totalGasBurned.toString())}}`); + console.log(chalk`{bold.white Tokens Burnt} {white |} {bold.yellow ${formatNearAmount(totalTokensBurned.toString())}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/utils/check-account-existence.js b/packages/cookbook/utils/check-account-existence.js deleted file mode 100644 index ded71038b6..0000000000 --- a/packages/cookbook/utils/check-account-existence.js +++ /dev/null @@ -1,28 +0,0 @@ -// Demonstrates checking if an account exists -const { providers } = require("near-api-js"); - -const provider = new providers.JsonRpcProvider( - "https://archival-rpc.testnet.near.org" -); - -async function accountExists() { - let rawResult; - - for (const account_id of ["does-not-exist.mike.testnet", "mike.testnet"]) { - let succeeded = true; - try { - rawResult = await provider.query({ - request_type: "view_account", - account_id: account_id, - finality: "final", - }); - } catch (e) { - if (e.type === 'AccountDoesNotExist') { - succeeded = false; - } - } - console.log(succeeded ? `The account ${account_id} exists.` : `There is no account ${account_id}.`) - } -} - -accountExists(); diff --git a/packages/cookbook/utils/check-account-existence.ts b/packages/cookbook/utils/check-account-existence.ts new file mode 100644 index 0000000000..0cad0ab3ba --- /dev/null +++ b/packages/cookbook/utils/check-account-existence.ts @@ -0,0 +1,28 @@ +import { + getAccountState, + getTestnetRpcProvider, +} from '@near-js/client'; + +export default async function accountExists() { + const isRegisteredAccount = async (account: string) => { + try { + await getAccountState({ + account, + deps: { + rpcProvider: getTestnetRpcProvider(), + }, + }); + } catch (e) { + if (e.type === 'AccountDoesNotExist') { + return false; + } + } + + return true; + }; + + for (const account of ['does-not-exist.mike.testnet', 'mike.testnet']) { + const succeeded = await isRegisteredAccount(account); + console.log(succeeded ? `The account ${account} exists.` : `There is no account ${account}.`); + } +} diff --git a/packages/cookbook/utils/deploy-contract.js b/packages/cookbook/utils/deploy-contract.js deleted file mode 100644 index 9674b689de..0000000000 --- a/packages/cookbook/utils/deploy-contract.js +++ /dev/null @@ -1,25 +0,0 @@ -const { keyStores, connect } = require("near-api-js"); -const fs = require("fs"); -const path = require("path"); -const homedir = require("os").homedir(); - -const CREDENTIALS_DIR = ".near-credentials"; -const ACCOUNT_ID = "near-example.testnet"; -const WASM_PATH = path.join(__dirname, "/wasm-files/status_message.wasm"); -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -deployContract(ACCOUNT_ID, WASM_PATH); - -async function deployContract(accountId, wasmPath) { - const near = await connect(config); - const account = await near.account(accountId); - const result = await account.deployContract(fs.readFileSync(wasmPath)); - console.log(result); -} diff --git a/packages/cookbook/utils/deploy-contract.ts b/packages/cookbook/utils/deploy-contract.ts new file mode 100644 index 0000000000..654d280767 --- /dev/null +++ b/packages/cookbook/utils/deploy-contract.ts @@ -0,0 +1,39 @@ +import { + deployContract, + getSignerFromKeystore, + getTestnetRpcProvider, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import * as fs from 'node:fs/promises'; + +const WASM_PATH = join(__dirname, '/wasm-files/status_message.wasm'); + +export default async function deployContractCookbook(accountId: string, wasmPath: string = WASM_PATH) { + if (!accountId) { + console.log(chalk`{red pnpm deployContract -- ACCOUNT_ID [WASM_PATH]}`); + return; + } + + // 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'))); + + await deployContract({ + account: accountId, + code: await fs.readFile(wasmPath), + deps: { + rpcProvider, + signer, + }, + }); + + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Deployed contract}` ); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Contract Deployed} {white |} {bold.yellow WASM at ${wasmPath} deployed to ${accountId}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/utils/get-state.js b/packages/cookbook/utils/get-state.js deleted file mode 100644 index 557e0091a0..0000000000 --- a/packages/cookbook/utils/get-state.js +++ /dev/null @@ -1,23 +0,0 @@ -// demonstrates how to query the state without setting -// up an account. (View methods only) -const { providers } = require("near-api-js"); -//network config (replace testnet with mainnet or betanet) -const provider = new providers.JsonRpcProvider( - "https://rpc.testnet.near.org" -); - -getState(); - -async function getState() { - const rawResult = await provider.query({ - request_type: "call_function", - account_id: "guest-book.testnet", - method_name: "getMessages", - args_base64: "e30=", - finality: "optimistic", - }); - - // format result - const res = JSON.parse(Buffer.from(rawResult.result).toString()); - console.log(res); -} diff --git a/packages/cookbook/utils/get-state.ts b/packages/cookbook/utils/get-state.ts new file mode 100644 index 0000000000..28e9097a80 --- /dev/null +++ b/packages/cookbook/utils/get-state.ts @@ -0,0 +1,15 @@ +import { getTestnetRpcProvider, view } from '@near-js/client'; + +export default async function getState() { + // initialize testnet RPC provider + const rpcProvider = getTestnetRpcProvider(); + + // call view method without parameters + const result = await view({ + account: 'guest-book.testnet', + method: 'getMessages', + deps: { rpcProvider }, + }); + + console.log(result); +} diff --git a/packages/cookbook/utils/index.ts b/packages/cookbook/utils/index.ts new file mode 100644 index 0000000000..0ca463d46b --- /dev/null +++ b/packages/cookbook/utils/index.ts @@ -0,0 +1,7 @@ +export * from './calculate-gas'; +export * from './check-account-existence'; +export * from './deploy-contract'; +export * from './get-state'; +export * from './unwrap-near'; +export * from './verify-signature'; +export * from './wrap-near'; diff --git a/packages/cookbook/utils/unwrap-near.js b/packages/cookbook/utils/unwrap-near.js deleted file mode 100644 index 947843aa73..0000000000 --- a/packages/cookbook/utils/unwrap-near.js +++ /dev/null @@ -1,29 +0,0 @@ -const { connect, keyStores, utils } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const WRAP_NEAR_CONTRACT_ID = "wrap.near"; - -const credentialsPath = path.join(homedir, ".near-credentials"); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "mainnet", - nodeUrl: "https://rpc.mainnet.near.org", -}; - -// Unwrap 1 wNEAR to NEAR -unwrapNear("example.near", "1"); - -async function unwrapNear(accountId, unwrapAmount) { - const near = await connect(config); - const account = await near.account(accountId); - - return account.functionCall({ - contractId: WRAP_NEAR_CONTRACT_ID, - methodName: "near_withdraw", // method to withdraw wNEAR for NEAR - args: { amount: utils.format.parseNearAmount(unwrapAmount) }, - attachedDeposit: "1", // attach one yoctoNEAR - }); - } \ No newline at end of file diff --git a/packages/cookbook/utils/unwrap-near.ts b/packages/cookbook/utils/unwrap-near.ts new file mode 100644 index 0000000000..b88926e6fc --- /dev/null +++ b/packages/cookbook/utils/unwrap-near.ts @@ -0,0 +1,55 @@ +import { + functionCall, + getSignerFromKeystore, + getTestnetRpcProvider, + view, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import { formatNearAmount, parseNearAmount } from '@near-js/utils'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +// On mainnet it's wrap.near, by the way +const WRAP_NEAR_CONTRACT_ID = "wrap.testnet"; + +export default async function unwrapNear(accountId: string, unwrapAmount: bigint, wrapContract = WRAP_NEAR_CONTRACT_ID) { + if (!accountId || !unwrapAmount) { + console.log(chalk`{red pnpm unwrapNear -- ACCOUNT_ID UNWRAP_AMOUNT [WRAP_CONTRACT]}`); + return; + } + + // 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 getStorageBalance = () => view({ + account: wrapContract, + method: 'storage_balance_of', + args: { account_id: accountId }, + deps: { rpcProvider }, + }) as Promise<{ available: string, total: string }>; + + const { total: preTotal, available: preAvailable } = (await getStorageBalance()) || {}; + + await functionCall({ + sender: accountId, + receiver: wrapContract, + method: 'near_withdraw', + args: { amount: parseNearAmount(unwrapAmount.toString()) }, + deposit: 1n, + deps: { + rpcProvider, + signer, + }, + }); + + const { total: postTotal, available: postAvailable } = await getStorageBalance(); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Unwrapped ${unwrapAmount}yN with ${wrapContract}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Starting Balance} {white |} {bold.yellow ${formatNearAmount(preAvailable)} / ${formatNearAmount(preTotal)}}`); + console.log(chalk`{bold.white Ending Balance} {white |} {bold.yellow ${formatNearAmount(postAvailable)} / ${formatNearAmount(postTotal)}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/cookbook/utils/verify-signature.js b/packages/cookbook/utils/verify-signature.js deleted file mode 100644 index b42c5068e5..0000000000 --- a/packages/cookbook/utils/verify-signature.js +++ /dev/null @@ -1,30 +0,0 @@ -const { keyStores } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -const ACCOUNT_ID = "near-example.testnet"; -const CREDENTIALS_DIR = ".near-credentials"; - -const credentialsPath = path.join(homedir, CREDENTIALS_DIR); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -verifySignature(); - -async function verifySignature() { - const keyPair = await keyStore.getKey(config.networkId, ACCOUNT_ID); - const msg = Buffer.from("hi"); - - const { signature } = keyPair.sign(msg); - - const isValid = keyPair.verify(msg, signature); - - console.log("Signature Valid?:", isValid); - - return isValid; -} diff --git a/packages/cookbook/utils/verify-signature.ts b/packages/cookbook/utils/verify-signature.ts new file mode 100644 index 0000000000..11e56ec686 --- /dev/null +++ b/packages/cookbook/utils/verify-signature.ts @@ -0,0 +1,22 @@ +import { getTestnetRpcProvider } from '@near-js/client'; +import { KeyType, PublicKey } from '@near-js/crypto'; +import { baseDecode } from '@near-js/utils'; + +const ACCOUNT_ID = 'gornt.testnet'; +const TX_HASH = '4tMHzHU5p9dXc4WqopReNZ2TMJxZyu913zK4Fn9nMRoB'; + +export default async function verifySignature(accountId: string = ACCOUNT_ID, transactionHash: string = TX_HASH) { + // initialize testnet RPC provider + const rpcProvider = getTestnetRpcProvider(); + + const { transaction: { public_key, signature } } = await rpcProvider.getTransaction({ transactionHash, account: accountId }); + + const hashBytes = baseDecode(transactionHash); + const publicKeyBytes = baseDecode(public_key.slice('ed25519:'.length)); + const signatureBytes = baseDecode(signature.slice('ed25519:'.length)); + const publicKey = new PublicKey({ keyType: KeyType.ED25519, data: publicKeyBytes }); + + const isVerified = publicKey.verify(hashBytes, signatureBytes); + + console.log(isVerified); +} diff --git a/packages/cookbook/utils/wrap-near.js b/packages/cookbook/utils/wrap-near.js deleted file mode 100644 index 1dc5edbfea..0000000000 --- a/packages/cookbook/utils/wrap-near.js +++ /dev/null @@ -1,67 +0,0 @@ -const HELP = `To convert N $NEAR to wNEAR, run this script in the following format: - - node wrap-near.js YOU.near N - -`; - -const { connect, keyStores, transactions, utils } = require("near-api-js"); -const path = require("path"); -const homedir = require("os").homedir(); - -// On mainnet it's wrap.near, by the way -const WRAP_NEAR_CONTRACT_ID = "wrap.testnet"; - -const credentialsPath = path.join(homedir, ".near-credentials"); -const keyStore = new keyStores.UnencryptedFileSystemKeyStore(credentialsPath); - -const config = { - keyStore, - networkId: "testnet", - nodeUrl: "https://rpc.testnet.near.org", -}; - -if (process.argv.length !== 4) { - console.info(HELP); - process.exit(1); -} - -wrapNear(process.argv[2], process.argv[3]); - -async function wrapNear(accountId, wrapAmount) { - const near = await connect(config); - const account = await near.account(accountId); - - const actions = [ - transactions.functionCall( - "near_deposit", // contract method to deposit NEAR for wNEAR - {}, - 30000000000000, // attached gas - utils.format.parseNearAmount(wrapAmount) // amount of NEAR to deposit and wrap - ), - ]; - - // check if storage has been paid (the account has a wNEAR account) - const storage = await account.viewFunction({ - contractId: WRAP_NEAR_CONTRACT_ID, - methodName: "storage_balance_of", - args: { account_id: accountId }, - }); - - // if storage hasn't been paid, pay for storage (create an account) - if (!storage) { - actions.unshift( - transactions.functionCall( - "storage_deposit", // method to create an account - {}, - 30000000000000, // attached gas - utils.format.parseNearAmount('0.00125') // account creation costs 0.00125 NEAR for storage - ) - ); - } - - // send batched transaction - return account.signAndSendTransaction({ - receiverId: WRAP_NEAR_CONTRACT_ID, - actions, - }); -} diff --git a/packages/cookbook/utils/wrap-near.ts b/packages/cookbook/utils/wrap-near.ts new file mode 100644 index 0000000000..8e8087e2c6 --- /dev/null +++ b/packages/cookbook/utils/wrap-near.ts @@ -0,0 +1,56 @@ +import { + formatNearAmount, + getSignerFromKeystore, + getTestnetRpcProvider, + SignedTransactionComposer, + view, +} from '@near-js/client'; +import { UnencryptedFileSystemKeyStore } from '@near-js/keystores-node'; +import chalk from 'chalk'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +// On mainnet it's wrap.near, by the way +const WRAP_NEAR_CONTRACT_ID = "wrap.testnet"; + +export default async function wrapNear(accountId: string, wrapAmount: bigint, wrapContract = WRAP_NEAR_CONTRACT_ID) { + if (!accountId || !wrapAmount) { + console.log(chalk`{red pnpm wrapNear -- ACCOUNT_ID WRAP_AMOUNT [WRAP_CONTRACT]}`); + return; + } + + // 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 getStorageBalance = () => view({ + account: wrapContract, + method: 'storage_balance_of', + args: { account_id: accountId }, + deps: { rpcProvider }, + }) as Promise<{ available: string, total: string }>; + + const wrapTransaction = SignedTransactionComposer.init({ + sender: accountId, + receiver: wrapContract, + deps: { rpcProvider, signer }, + }); + + const { total: preTotal, available: preAvailable } = (await getStorageBalance()) || {}; + const _30tgas = BigInt(3e13); + if (!preTotal) { + wrapTransaction.functionCall('storage_deposit', {}, _30tgas, BigInt(1.25e21)); + } + wrapTransaction.functionCall('near_deposit', {}, _30tgas, BigInt(wrapAmount)); + + await wrapTransaction.signAndSend(); + + const { total: postTotal, available: postAvailable } = await getStorageBalance(); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.green RESULTS} {white Wrapped ${wrapAmount}yN with ${wrapContract}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); + console.log(chalk`{bold.white Starting Balance} {white |} {bold.yellow ${formatNearAmount(preAvailable)} / ${formatNearAmount(preTotal)}}`); + console.log(chalk`{bold.white Ending Balance} {white |} {bold.yellow ${formatNearAmount(postAvailable)} / ${formatNearAmount(postTotal)}}`); + console.log(chalk`{white ------------------------------------------------------------------------ }`); +} diff --git a/packages/crypto/src/constants.ts b/packages/crypto/src/constants.ts index d028d3523f..17cec790e1 100644 --- a/packages/crypto/src/constants.ts +++ b/packages/crypto/src/constants.ts @@ -9,3 +9,9 @@ export const KeySize = { ED25519_PUBLIC_KEY: 32, SECP256k1_PUBLIC_KEY: 64, }; + +export type CurveType = + 'ed25519' | 'ED25519' + | 'secp256k1' | 'SECP256K1'; + +export type KeyPairString = `ed25519:${string}` | `secp256k1:${string}`; diff --git a/packages/crypto/src/index.ts b/packages/crypto/src/index.ts index 45e13a274e..4a3af364f8 100644 --- a/packages/crypto/src/index.ts +++ b/packages/crypto/src/index.ts @@ -1,5 +1,5 @@ -export { KeyType } from './constants'; -export { KeyPair, KeyPairString } from './key_pair'; +export { CurveType, KeyPairString, KeyType } from './constants'; +export { KeyPair } from './key_pair'; export { Signature } from './key_pair_base'; export { KeyPairEd25519 } from './key_pair_ed25519'; export { KeyPairSecp256k1 } from './key_pair_secp256k1'; diff --git a/packages/crypto/src/key_pair.ts b/packages/crypto/src/key_pair.ts index 28273b2e71..c8565fee32 100644 --- a/packages/crypto/src/key_pair.ts +++ b/packages/crypto/src/key_pair.ts @@ -1,15 +1,14 @@ +import { CurveType, KeyPairString } from './constants'; import { KeyPairBase } from './key_pair_base'; import { KeyPairEd25519 } from './key_pair_ed25519'; import { KeyPairSecp256k1 } from './key_pair_secp256k1'; -export type KeyPairString = `ed25519:${string}` | `secp256k1:${string}`; - export abstract class KeyPair extends KeyPairBase { /** * @param curve Name of elliptical curve, case-insensitive * @returns Random KeyPair based on the curve */ - static fromRandom(curve: 'ed25519' | 'secp256k1'): KeyPair { + static fromRandom(curve: CurveType): KeyPair { switch (curve.toUpperCase()) { case 'ED25519': return KeyPairEd25519.fromRandom(); case 'SECP256K1': return KeyPairSecp256k1.fromRandom(); diff --git a/packages/crypto/src/key_pair_base.ts b/packages/crypto/src/key_pair_base.ts index f4939d8274..73aa796163 100644 --- a/packages/crypto/src/key_pair_base.ts +++ b/packages/crypto/src/key_pair_base.ts @@ -1,4 +1,4 @@ -import { KeyPairString } from './key_pair'; +import type { KeyPairString } from './constants'; import { PublicKey } from './public_key'; export interface Signature { diff --git a/packages/crypto/src/key_pair_ed25519.ts b/packages/crypto/src/key_pair_ed25519.ts index 2aabb1d7e6..e676710401 100644 --- a/packages/crypto/src/key_pair_ed25519.ts +++ b/packages/crypto/src/key_pair_ed25519.ts @@ -2,10 +2,9 @@ import { baseEncode, baseDecode } from '@near-js/utils'; import { ed25519 } from '@noble/curves/ed25519'; import randombytes from 'randombytes'; -import { KeySize, KeyType } from './constants'; +import { KeyPairString, KeySize, KeyType } from './constants'; import { KeyPairBase, Signature } from './key_pair_base'; import { PublicKey } from './public_key'; -import { KeyPairString } from './key_pair'; /** * This class provides key pair functionality for Ed25519 curve: diff --git a/packages/crypto/src/key_pair_secp256k1.ts b/packages/crypto/src/key_pair_secp256k1.ts index 2983f5cf08..07ee6d592e 100644 --- a/packages/crypto/src/key_pair_secp256k1.ts +++ b/packages/crypto/src/key_pair_secp256k1.ts @@ -1,10 +1,11 @@ -import { KeySize, KeyType } from './constants'; +import { baseDecode, baseEncode } from '@near-js/utils'; +import randombytes from 'randombytes'; +import secp256k1 from 'secp256k1'; + +import { KeyPairString, KeySize, KeyType } from './constants'; import { KeyPairBase, Signature } from './key_pair_base'; import { PublicKey } from './public_key'; -import secp256k1 from 'secp256k1'; -import randombytes from 'randombytes'; -import { KeyPairString } from './key_pair'; -import { baseDecode, baseEncode } from '@near-js/utils'; + /** * This class provides key pair functionality for secp256k1 curve: * generating key pairs, encoding key pairs, signing and verifying. diff --git a/packages/crypto/src/public_key.ts b/packages/crypto/src/public_key.ts index fbb5c89c35..349175f26c 100644 --- a/packages/crypto/src/public_key.ts +++ b/packages/crypto/src/public_key.ts @@ -58,6 +58,7 @@ abstract class Enum { /** * PublicKey representation that has type and bytes of the key. */ + export class PublicKey extends Enum { enum: string; ed25519Key?: ED25519PublicKey; diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index bccaac8b3e..a315641b87 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -51,7 +51,7 @@ "test": "jest test --passWithNoTests", "lint": "concurrently \"pnpm:lint:*(!fix) --no-error-on-unmatched-pattern\"", "lint:src": "eslint --ext .ts src", - "lint:fix": "concurrently \"pnpm:lint:*:fix\"", + "lint:fix": "pnpm lint:src:fix && pnpm lint:test:fix", "lint:src:fix": "eslint --ext .ts --fix src", "lint:test:fix": "eslint --ext .js --fix test", "prefuzz": "pnpm build", diff --git a/packages/near-api-js/test/.eslintrc.yml b/packages/near-api-js/test/.eslintrc.yml index 0fae1d994f..efc762f364 100644 --- a/packages/near-api-js/test/.eslintrc.yml +++ b/packages/near-api-js/test/.eslintrc.yml @@ -1,4 +1,4 @@ -extends: '../../../.eslintrc.js.yml' +extends: '../../../.eslintrc.base.yml' env: jest: true globals: diff --git a/packages/near-api-js/test/test-utils.js b/packages/near-api-js/test/test-utils.js index 275cf0b7e8..8348d5cf23 100644 --- a/packages/near-api-js/test/test-utils.js +++ b/packages/near-api-js/test/test-utils.js @@ -5,7 +5,7 @@ const nearApi = require('../src/index'); const networkId = 'unittest'; const HELLO_WASM_PATH = process.env.HELLO_WASM_PATH || 'node_modules/near-hello/dist/main.wasm'; -const HELLO_WASM_BALANCE = BigInt('10000000000000000000000000'); +const HELLO_WASM_BALANCE = 10000000000000000000000000n; const HELLO_WASM_METHODS = { viewMethods: ['getValue', 'getLastResult'], changeMethods: ['setValue', 'callPromise'] @@ -25,7 +25,7 @@ async function setUpTestConnection() { if (config.masterAccount) { // full accessKey on ci-testnet, dedicated rpc for tests. const secretKey = config.secretKey || 'ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'; - await keyStore.setKey(networkId, config.masterAccount, KeyPair.fromString(secretKey)); + await keyStore.setKey(networkId, config.masterAccount, nearApi.utils.KeyPair.fromString(secretKey)); } return nearApi.connect(config); } @@ -65,7 +65,7 @@ async function createAccountMultisig(near, options) { accountMultisig.getRecoveryMethods = () => ({ data: [] }); accountMultisig.postSignedJson = async (path) => { switch (path) { - case '/2fa/getAccessKey': return { publicKey }; + case '/2fa/getAccessKey': return { publicKey }; } }; await accountMultisig.deployMultisig(new Uint8Array([...(await fs.readFile(MULTISIG_WASM_PATH))])); diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index dbcf225b57..1d3d36eca1 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -88,7 +88,8 @@ export class FailoverRpcProvider extends Provider { const result = await getResult(this.currentProvider); if (result) return result; - } catch { + } catch (e) { + console.error(e); this.switchToNextProvider(); } } diff --git a/packages/providers/src/fetch_json.ts b/packages/providers/src/fetch_json.ts index fe83961208..9701aca52d 100644 --- a/packages/providers/src/fetch_json.ts +++ b/packages/providers/src/fetch_json.ts @@ -13,7 +13,7 @@ const retryConfig = { return true; } - if (['FetchError', 'Failed to fetch'].includes(e.toString())) { + if (e.toString().includes('FetchError') || e.toString().includes('Failed to fetch')) { return true; } diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index d22b4125a6..eac273dea6 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -66,11 +66,13 @@ export { FinalExecutionStatusBasic, FunctionCallPermissionView, QueryResponseKind, + SerializedReturnValue, ViewStateResult, } from './response'; export { CurrentEpochValidatorInfo, EpochValidatorInfo, NextEpochValidatorInfo, - ValidatorStakeView, + StakedAccount, + ValidatorStakeView } from './validator'; diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index 6c1ca85231..7a4155cd42 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -4,6 +4,8 @@ */ import { BlockHash, BlockHeight, MerklePath, TxExecutionStatus } from './protocol'; +export type SerializedReturnValue = string | number | bigint | boolean | object; + export enum ExecutionStatusBasic { Unknown = 'Unknown', Pending = 'Pending', @@ -46,6 +48,27 @@ export interface ExecutionOutcome { status: ExecutionStatus | ExecutionStatusBasic; } +export type ReceiptAction = + { Transfer: { deposit: string }}; + +export interface ExecutionOutcomeReceiptDetail { + predecessor_id: string; + receipt: { + Action: ExecutionOutcomeReceiptAction + }; + receipt_id: string; + receiver_id: string; +} + +export interface ExecutionOutcomeReceiptAction { + actions: ReceiptAction[]; + gas_price: string; + input_data_ids: string[]; + output_data_receivers: string[]; + signer_id: string; + signer_public_key: string; +} + export interface ExecutionOutcomeWithIdView { proof: MerklePath; block_hash: string; @@ -59,6 +82,7 @@ export interface FinalExecutionOutcome { transaction: any; transaction_outcome: ExecutionOutcomeWithId; receipts_outcome: ExecutionOutcomeWithId[]; + receipts?: ExecutionOutcomeReceiptDetail[]; } export interface QueryResponseKind { diff --git a/packages/types/src/provider/validator.ts b/packages/types/src/provider/validator.ts index abd2f1d55f..ec5606305b 100644 --- a/packages/types/src/provider/validator.ts +++ b/packages/types/src/provider/validator.ts @@ -43,3 +43,10 @@ export interface EpochValidatorInfo { // Epoch start height. epoch_start_height: number; } + +export interface StakedAccount { + account_id: string; + unstaked_balance: string; + staked_balance: string; + can_withdraw: boolean; +} diff --git a/packages/utils/src/provider.ts b/packages/utils/src/provider.ts index 24530eda52..56a270548c 100644 --- a/packages/utils/src/provider.ts +++ b/packages/utils/src/provider.ts @@ -1,7 +1,7 @@ import { FinalExecutionOutcome } from '@near-js/types'; /** @hidden */ -export function getTransactionLastResult(txResult: FinalExecutionOutcome): any { +export function getTransactionLastResult(txResult: FinalExecutionOutcome): Exclude { if (typeof txResult.status === 'object' && typeof txResult.status.SuccessValue === 'string') { const value = Buffer.from(txResult.status.SuccessValue, 'base64').toString(); try { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2be411fb0..fafe83ccdb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 2.24.4 '@commitlint/cli': specifier: 19.3.0 - version: 19.3.0(@types/node@20.5.1)(typescript@5.4.5) + version: 19.3.0(@types/node@22.5.5)(typescript@5.4.5) '@commitlint/config-conventional': specifier: 19.2.2 version: 19.2.2 @@ -32,7 +32,7 @@ importers: version: 6.21.0(eslint@8.20.0)(typescript@5.4.5) commitlint: specifier: 19.3.0 - version: 19.3.0(@types/node@20.5.1)(typescript@5.4.5) + version: 19.3.0(@types/node@22.5.5)(typescript@5.4.5) eslint: specifier: 8.20.0 version: 8.20.0 @@ -191,14 +191,14 @@ importers: packages/build: {} - packages/cookbook: + packages/client: dependencies: - '@near-js/accounts': + '@near-js/crypto': specifier: workspace:* - version: link:../accounts - '@near-js/keystores-node': + version: link:../crypto + '@near-js/keystores': specifier: workspace:* - version: link:../keystores-node + version: link:../keystores '@near-js/providers': specifier: workspace:* version: link:../providers @@ -208,6 +208,52 @@ importers: '@near-js/transactions': specifier: workspace:* version: link:../transactions + '@near-js/types': + specifier: workspace:* + version: link:../types + '@near-js/utils': + specifier: workspace:* + version: link:../utils + '@noble/hashes': + specifier: 1.3.3 + version: 1.3.3 + isomorphic-fetch: + specifier: 3.0.0 + version: 3.0.0(encoding@0.1.13) + devDependencies: + '@types/node': + specifier: 20.0.0 + version: 20.0.0 + build: + specifier: workspace:* + version: link:../build + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: 5.4.5 + version: 5.4.5 + + packages/cookbook: + devDependencies: + '@near-js/client': + specifier: workspace:* + version: link:../client + '@near-js/crypto': + specifier: workspace:* + version: link:../crypto + '@near-js/keystores-node': + specifier: workspace:* + version: link:../keystores-node + '@near-js/transactions': + specifier: workspace:* + version: link:../transactions + '@near-js/utils': + specifier: workspace:* + version: link:../utils + build: + specifier: workspace:* + version: link:../build chalk: specifier: 4.1.1 version: 4.1.1 @@ -217,6 +263,15 @@ importers: near-api-js: specifier: 4.0.0 version: 4.0.0(encoding@0.1.13) + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.5.5)(typescript@5.4.5) + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: 5.4.5 + version: 5.4.5 packages/crypto: dependencies: @@ -325,13 +380,13 @@ importers: version: link:../build jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + version: 29.7.0(@types/node@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) localstorage-memory: specifier: 1.0.3 version: 1.0.3 ts-jest: specifier: 29.1.5 - version: 29.1.5(@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.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)))(typescript@5.4.5) + version: 29.1.5(@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@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)))(typescript@5.4.5) tsconfig: specifier: workspace:* version: link:../tsconfig @@ -1339,8 +1394,8 @@ packages: '@types/node@20.0.0': resolution: {integrity: sha512-cD2uPTDnQQCVpmRefonO98/PPijuOnnEy5oytWJFPY1N9aJCz2wJ5kSGWO+zJoed2cY2JxQh6yBuUq4vIn61hw==} - '@types/node@20.5.1': - resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==} + '@types/node@22.5.5': + resolution: {integrity: sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -2560,6 +2615,9 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} + isomorphic-unfetch@3.1.0: resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} @@ -3841,6 +3899,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + unfetch@4.2.0: resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} @@ -3900,6 +3961,9 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -4406,11 +4470,11 @@ snapshots: human-id: 1.0.2 prettier: 2.8.8 - '@commitlint/cli@19.3.0(@types/node@20.5.1)(typescript@5.4.5)': + '@commitlint/cli@19.3.0(@types/node@22.5.5)(typescript@5.4.5)': dependencies: '@commitlint/format': 19.3.0 '@commitlint/lint': 19.2.2 - '@commitlint/load': 19.2.0(@types/node@20.5.1)(typescript@5.4.5) + '@commitlint/load': 19.2.0(@types/node@22.5.5)(typescript@5.4.5) '@commitlint/read': 19.2.1 '@commitlint/types': 19.0.3 execa: 8.0.1 @@ -4457,7 +4521,7 @@ snapshots: '@commitlint/rules': 19.0.3 '@commitlint/types': 19.0.3 - '@commitlint/load@19.2.0(@types/node@20.5.1)(typescript@5.4.5)': + '@commitlint/load@19.2.0(@types/node@22.5.5)(typescript@5.4.5)': dependencies: '@commitlint/config-validator': 19.0.3 '@commitlint/execute-rule': 19.0.0 @@ -4465,7 +4529,7 @@ snapshots: '@commitlint/types': 19.0.3 chalk: 5.3.0 cosmiconfig: 9.0.0(typescript@5.4.5) - cosmiconfig-typescript-loader: 5.0.0(@types/node@20.5.1)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5) + cosmiconfig-typescript-loader: 5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5) lodash.isplainobject: 4.0.6 lodash.merge: 4.6.2 lodash.uniq: 4.5.0 @@ -4520,7 +4584,6 @@ snapshots: '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.20.0)': dependencies: @@ -4657,7 +4720,7 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5))': + '@jest/core@29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@8.0.2) @@ -4671,7 +4734,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4835,7 +4898,6 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.4.15 - optional: true '@manypkg/find-root@1.1.0': dependencies: @@ -5001,17 +5063,13 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tsconfig/node10@1.0.11': - optional: true + '@tsconfig/node10@1.0.11': {} - '@tsconfig/node12@1.0.11': - optional: true + '@tsconfig/node12@1.0.11': {} - '@tsconfig/node14@1.0.3': - optional: true + '@tsconfig/node14@1.0.3': {} - '@tsconfig/node16@1.0.4': - optional: true + '@tsconfig/node16@1.0.4': {} '@types/babel__core@7.20.5': dependencies: @@ -5081,7 +5139,9 @@ snapshots: '@types/node@20.0.0': {} - '@types/node@20.5.1': {} + '@types/node@22.5.5': + dependencies: + undici-types: 6.19.8 '@types/normalize-package-data@2.4.4': {} @@ -5199,7 +5259,6 @@ snapshots: acorn-walk@8.3.3: dependencies: acorn: 8.12.0 - optional: true acorn@8.12.0: {} @@ -5246,8 +5305,7 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - arg@4.1.3: - optional: true + arg@4.1.3: {} argparse@1.0.10: dependencies: @@ -5545,9 +5603,9 @@ snapshots: color-name@1.1.4: {} - commitlint@19.3.0(@types/node@20.5.1)(typescript@5.4.5): + commitlint@19.3.0(@types/node@22.5.5)(typescript@5.4.5): dependencies: - '@commitlint/cli': 19.3.0(@types/node@20.5.1)(typescript@5.4.5) + '@commitlint/cli': 19.3.0(@types/node@22.5.5)(typescript@5.4.5) '@commitlint/types': 19.0.3 transitivePeerDependencies: - '@types/node' @@ -5589,9 +5647,9 @@ snapshots: convert-source-map@2.0.0: {} - cosmiconfig-typescript-loader@5.0.0(@types/node@20.5.1)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5): + cosmiconfig-typescript-loader@5.0.0(@types/node@22.5.5)(cosmiconfig@9.0.0(typescript@5.4.5))(typescript@5.4.5): dependencies: - '@types/node': 20.5.1 + '@types/node': 22.5.5 cosmiconfig: 9.0.0(typescript@5.4.5) jiti: 1.21.6 typescript: 5.4.5 @@ -5635,13 +5693,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.1 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5650,8 +5708,7 @@ snapshots: - supports-color - ts-node - create-require@1.1.1: - optional: true + create-require@1.1.1: {} cross-spawn@5.1.0: dependencies: @@ -5756,8 +5813,7 @@ snapshots: diff-sequences@29.6.3: {} - diff@4.0.2: - optional: true + diff@4.0.2: {} dir-glob@3.0.1: dependencies: @@ -6484,6 +6540,13 @@ snapshots: isexe@2.0.0: {} + isomorphic-fetch@3.0.0(encoding@0.1.13): + dependencies: + node-fetch: 2.6.7(encoding@0.1.13) + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding + isomorphic-unfetch@3.1.0(encoding@0.1.13): dependencies: node-fetch: 2.6.7(encoding@0.1.13) @@ -6612,16 +6675,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.1 - create-jest: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6726,7 +6789,7 @@ snapshots: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.0.0)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)): dependencies: '@babel/core': 7.24.7 '@jest/test-sequencer': 29.7.0 @@ -6752,12 +6815,12 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.0.0 - ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@22.5.5)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.5.1)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@22.5.5)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)): dependencies: '@babel/core': 7.24.7 '@jest/test-sequencer': 29.7.0 @@ -6782,8 +6845,8 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.5.1 - ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.4.5) + '@types/node': 22.5.5 + ts-node: 10.9.2(@types/node@22.5.5)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -7031,12 +7094,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@20.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)): + jest@29.7.0(@types/node@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) optionalDependencies: node-notifier: 8.0.2 transitivePeerDependencies: @@ -7957,11 +8020,11 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.7) - ts-jest@29.1.5(@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.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)))(typescript@5.4.5): + ts-jest@29.1.5(@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@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)))(typescript@5.4.5): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.5.1)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5)) + jest: 29.7.0(@types/node@22.5.5)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -8013,14 +8076,14 @@ snapshots: yn: 3.1.1 optional: true - ts-node@10.9.2(@types/node@20.5.1)(typescript@5.4.5): + ts-node@10.9.2(@types/node@22.5.5)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.5.1 + '@types/node': 22.5.5 acorn: 8.12.0 acorn-walk: 8.3.3 arg: 4.1.3 @@ -8030,7 +8093,6 @@ snapshots: typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - optional: true tslib@2.6.3: {} @@ -8168,6 +8230,8 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + undici-types@6.19.8: {} + unfetch@4.2.0: {} unicorn-magic@0.1.0: {} @@ -8189,8 +8253,7 @@ snapshots: uuid@8.3.2: optional: true - v8-compile-cache-lib@3.0.1: - optional: true + v8-compile-cache-lib@3.0.1: {} v8-compile-cache@2.4.0: {} @@ -8227,6 +8290,8 @@ snapshots: webidl-conversions@3.0.1: {} + whatwg-fetch@3.6.20: {} + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -8333,8 +8398,7 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: - optional: true + yn@3.1.1: {} yocto-queue@0.1.0: {}