From bb94ee206bc397524a34313e47ba15c8e3b5215b Mon Sep 17 00:00:00 2001 From: denbite Date: Sun, 23 Mar 2025 17:38:59 +0100 Subject: [PATCH 01/57] feat!: define completely fresh abstractions of `Signer`, `Account` and `Contract` --- package.json | 2 +- packages/cookbook/account_v2.js | 45 ++++ packages/cookbook/account_v2.ts | 60 +++++ packages/cookbook/index.ts | 1 + packages/cookbook/package.json | 3 +- packages/near-api-js/src/account.ts | 356 +++++++++++++++++++++++++++- packages/near-api-js/src/signer.ts | 110 ++++++++- pnpm-lock.yaml | 207 +--------------- 8 files changed, 581 insertions(+), 203 deletions(-) create mode 100644 packages/cookbook/account_v2.js create mode 100644 packages/cookbook/account_v2.ts diff --git a/package.json b/package.json index 014ca010e8..4a80d4c8f2 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,6 @@ }, "resolutions": { "near-sandbox": "0.0.18", - "near-api-js": "4.0.0" + "near-api-js": "workspace:*" } } diff --git a/packages/cookbook/account_v2.js b/packages/cookbook/account_v2.js new file mode 100644 index 0000000000..4fbf452b99 --- /dev/null +++ b/packages/cookbook/account_v2.js @@ -0,0 +1,45 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +function main() { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + console.log('cook'); + return [2 /*return*/]; + }); + }); +} +main(); diff --git a/packages/cookbook/account_v2.ts b/packages/cookbook/account_v2.ts new file mode 100644 index 0000000000..fd8d721549 --- /dev/null +++ b/packages/cookbook/account_v2.ts @@ -0,0 +1,60 @@ +import { ContractV2, PublicAccountV2 } from "near-api-js/lib/account"; +import { JsonRpcProvider } from "near-api-js/lib/providers"; +import { KeyPairSigner } from "near-api-js/lib/signer"; +import { KeyPair } from "@near-js/crypto"; + +async function main(): Promise { + const provider = new JsonRpcProvider({ + url: "https://test.rpc.fastnear.com", + }); + + const account = new PublicAccountV2("denbite.testnet", provider); + + const info = await account.getInformation(); + + console.log("info", info); + + await account.getAccessKeys(); + + // ---------------------------------------------- + + // use real key here + const key = KeyPair.fromRandom("ED25519"); + const signer = new KeyPairSigner([key], provider); + + const signerAccount = account.intoSignerAccount(signer); + + await signerAccount.transfer("guille.testnet", 1_000_000_000n); + + const code = new Uint8Array(); + await signerAccount.deployContract(code); + + await signerAccount.addFullAccessKey( + KeyPair.fromRandom("ED25519").getPublicKey() + ); + + await signerAccount.deleteAccount("guille.testnet"); + + // ---------------------------------------------- + + // or account.intoContract() + const contract = new ContractV2("ref-finance.testnet", provider); + + await contract.callReadFunction("tokens_by_owner", { + owner_id: signerAccount.accountId, + }); + + await contract.callWriteFunction( + signerAccount, + "swap_token", + { + token_in: "usdt.testnet", + amount_in: 1_000_000_000, + token_out: "wrap.testnet", + }, + 1n, // deposit 1 yoctoNear + 100_000_000_000n // 100 TGas + ); +} + +main(); diff --git a/packages/cookbook/index.ts b/packages/cookbook/index.ts index 1bef8a034d..5033343655 100644 --- a/packages/cookbook/index.ts +++ b/packages/cookbook/index.ts @@ -1,3 +1,4 @@ export * from './accounts'; export * from './transactions'; export * from './utils'; +export * from './account_v2'; diff --git a/packages/cookbook/package.json b/packages/cookbook/package.json index 515d268f64..30a1d7d292 100644 --- a/packages/cookbook/package.json +++ b/packages/cookbook/package.json @@ -26,7 +26,8 @@ "traverseBlocks": "tsx -e \"import f from './transactions/traverse-blocks.ts'; f(...process.argv.slice(1));\"", "unwrapNear": "tsx -e \"import f from './utils/unwrap-near.ts'; f(...process.argv.slice(1));\"", "verifySignature": "tsx -e \"import f from './utils/verify-signature.ts'; f(...process.argv.slice(1));\"", - "wrapNear": "tsx -e \"import f from './utils/wrap-near.ts'; f(...process.argv.slice(1));\"" + "wrapNear": "tsx -e \"import f from './utils/wrap-near.ts'; f(...process.argv.slice(1));\"", + "v2": "tsx account_v2.ts" }, "devDependencies": { "@near-js/client": "workspace:*", diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index d1883e140e..e03f4dbb9d 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -1,3 +1,36 @@ +import { Provider } from "@near-js/providers"; +import { SignerV2 } from "./signer"; +import { + Action, + createTransaction, + SignedTransaction, + Transaction, +} from "@near-js/transactions"; +import { + addKey, + createAccount, + deleteAccount, + deleteKey, + deployContract, + fullAccessKey, + functionCall, + functionCallAccessKey, + transfer, +} from "./transaction"; +import { + AccessKeyList, + AccessKeyView, + AccessKeyViewRaw, + AccountView, + BlockHash, + CodeResult, + ContractCodeView, + FinalExecutionOutcome, + ViewStateResult, +} from "@near-js/types"; +import { PublicKey } from "@near-js/crypto"; +import { baseDecode } from "@near-js/utils"; + export { Account, AccountBalance, @@ -6,4 +39,325 @@ export { FunctionCallOptions, ChangeFunctionCallOptions, ViewFunctionCallOptions, -} from '@near-js/accounts'; +} from "@near-js/accounts"; + +export class PublicAccountV2 { + public readonly accountId: string; + public readonly provider: Provider; + + constructor(accountId: string, provider: Provider) { + this.accountId = accountId; + this.provider = provider; + } + + public intoSignerAccount(signer: SignerV2): SignerAccountV2 { + return new SignerAccountV2(this.accountId, this.provider, signer); + } + + public intoContract(): ContractV2 { + return new ContractV2(this.accountId, this.provider); + } + + public async getInformation(): Promise { + return this.provider.query({ + request_type: "view_account", + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getAccessKey(pk: PublicKey): Promise { + return this.provider.query({ + request_type: "view_access_key", + public_key: pk.toString(), + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getAccessKeys(): Promise { + return this.provider.query({ + request_type: "view_access_key_list", + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getTransactionStatus( + txHash: string + ): Promise { + return this.provider.txStatus( + txHash, + this.accountId, + "EXECUTED_OPTIMISTIC" + ); + } +} + +export class ContractV2 extends PublicAccountV2 { + constructor(accountId: string, provider: Provider) { + super(accountId, provider); + } + + public async getCode(): Promise { + return this.provider.query({ + request_type: "view_code", + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getState(prefix: string = ""): Promise { + const prefixBase64 = Buffer.from(prefix).toString("base64"); + + return this.provider.query({ + request_type: "view_state", + account_id: this.accountId, + finality: "optimistic", + prefix_base64: prefixBase64, + }); + } + + public async callReadFunction( + methodName: string, + args: Record = {} + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); + + return this.provider.query({ + request_type: "call_function", + account_id: this.accountId, + method_name: methodName, + args_base64: argsBase64, + finality: "optimistic", + }); + } + + public async callWriteFunction( + signerAccount: SignerAccountV2, + methodName: string, + args: Record = {}, + deposit: bigint = 0n, + gas: bigint = 30_000_000_000_000n + ): Promise { + const actions = [functionCall(methodName, args, gas, deposit)]; + + const transaction = await signerAccount.constructTransaction( + this.accountId, + actions + ); + + return signerAccount.signAndSendTransaction(transaction); + } +} + +export class SignerAccountV2 extends PublicAccountV2 { + public readonly signer: SignerV2; + + constructor(accountId: string, provider: Provider, signer: SignerV2) { + super(accountId, provider); + + this.signer = signer; + } + + public intoDelegateAccount(): any { + throw new Error(`Not implemented yet!`); + } + + // TODO: Find matching access key based on transaction (i.e. receiverId and actions) + protected async matchAccessKeyBasedOnReceiverAndActions( + publicKeys: Array, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + receiverId: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + actions: Array + ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView } | undefined> { + const pk = publicKeys.at(0)!; + + const rawAccessKey = await this.provider.query({ + request_type: "view_access_key", + account_id: this.accountId, + public_key: pk.toString(), + finality: "optimistic", + }); + + // store nonce as BigInt to preserve precision on big number + const accessKey = { + ...rawAccessKey, + nonce: BigInt(rawAccessKey.nonce || 0), + }; + + return { publicKey: pk, accessKey }; + } + + protected async findRecentBlockHash(): Promise { + const block = await this.provider.block({ finality: "final" }); + return block.header.hash; + } + + public async constructTransaction( + receiverId: string, + actions: Array + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const publicKeys = await this.signer.getPublicKeys(); + + if (publicKeys.length === 0) + throw new Error(`No public keys available!`); + + const signerKey = await this.matchAccessKeyBasedOnReceiverAndActions( + publicKeys, + receiverId, + actions + ); + + if (!signerKey) throw new Error(`No public key found`); + + const recentBlockHash = await this.findRecentBlockHash(); + + return createTransaction( + this.accountId, + signerKey.publicKey, + receiverId, + signerKey.accessKey.nonce + 1n, + actions, + baseDecode(recentBlockHash) + ); + } + + // Actions + public async createSubAccount( + name: string, + pk: PublicKey, + amount: bigint + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const accountName = `${name}.${this.accountId}`; + + const actions = [ + createAccount(), + transfer(amount), + addKey(pk, fullAccessKey()), + ]; + + const transaction = await this.constructTransaction( + accountName, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + + public async deleteAccount( + beneficiaryId: string + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [deleteAccount(beneficiaryId)]; + + const transaction = await this.constructTransaction( + this.accountId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + + public async addFullAccessKey( + pk: PublicKey + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [addKey(pk, fullAccessKey())]; + + const transaction = await this.constructTransaction( + this.accountId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + + public async addFunctionAccessKey( + pk: PublicKey, + receiverId: string, + methodNames: string[], + allowance?: bigint + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [ + addKey( + pk, + functionCallAccessKey(receiverId, methodNames, allowance) + ), + ]; + + const transaction = await this.constructTransaction( + this.accountId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + public async deleteKey(pk: PublicKey): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [deleteKey(pk)]; + + const transaction = await this.constructTransaction( + this.accountId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + + public async transfer( + receiverId: string, + amount: bigint + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [transfer(amount)]; + + const transaction = await this.constructTransaction( + receiverId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + public async deployContract( + code: Uint8Array + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + const actions = [deployContract(code)]; + + const transaction = await this.constructTransaction( + this.accountId, + actions + ); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } + + public async signTransaction( + transaction: Transaction + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + return this.signer.signTransaction(transaction); + } + + public async signAndSendTransaction( + transaction: Transaction + ): Promise { + if (!this.signer) throw new Error(`Signer is required!`); + + return this.signer.signAndSendTransaction(transaction, this.provider); + } +} diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index 0195a07204..616df2b111 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1 +1,109 @@ -export { InMemorySigner, Signer } from '@near-js/signers'; +import { KeyPair, PublicKey } from "@near-js/crypto"; +import { Provider } from "@near-js/providers"; +import { + encodeTransaction, + Signature, + SignedTransaction, + Transaction, +} from "@near-js/transactions"; +import { FinalExecutionOutcome } from "@near-js/types"; + +export { InMemorySigner, Signer } from "@near-js/signers"; + +export interface SignMessageParams { + message: string; // The message that wants to be transmitted. + recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com"). + nonce: Uint8Array; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS). + callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`. + state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes. +} + +export interface SignedMessage { + accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near") + publicKey: string; // The public counterpart of the key used to sign, expressed as a string with format ":" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y") + signature: string; // The base64 representation of the signature. + state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The same state passed in SignMessageParams. +} + +/** + * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. + */ +export abstract class SignerV2 { + abstract getPublicKeys(): Promise>; + + abstract signNep413Message( + params: SignMessageParams, + accountId: string + ): Promise; + + abstract signTransaction( + transaction: Transaction + ): Promise; + + /** + * This method is required for compatibility with WalletSelector + */ + abstract signAndSendTransaction( + transaction: Transaction, + // not sure if this Provider should be here + provider?: Provider + ): Promise; +} + +export class KeyPairSigner extends SignerV2 { + constructor( + private readonly keys: Array, + private readonly provider: Provider + ) { + super(); + } + + public async getPublicKeys(): Promise> { + return this.keys.map((key) => key.getPublicKey()); + } + + public async signNep413Message( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + params: SignMessageParams, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + accountId: string + ): Promise { + throw new Error(`Not implemented!`); + } + + public async signTransaction( + transaction: Transaction + ): Promise { + const message = encodeTransaction(transaction); + + const keyPair = this.keys.find( + (key) => key.getPublicKey() === transaction.publicKey + ); + + if (!keyPair) + throw new Error( + `Transaction can't be signed with ${transaction.publicKey} as this key doesn't belong to a Signer` + ); + + const { signature } = keyPair.sign(message); + + return new SignedTransaction({ + transaction, + signature: new Signature({ + keyType: keyPair.getPublicKey().keyType, + data: signature, + }), + }); + } + + public async signAndSendTransaction( + transaction: Transaction, + provider?: Provider + ): Promise { + const signedTx = await this.signTransaction(transaction); + + const rpcProvider = provider || this.provider; + + return rpcProvider.sendTransaction(signedTx); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7df11efa0..c918aea37b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,7 +6,7 @@ settings: overrides: near-sandbox: 0.0.18 - near-api-js: 4.0.0 + near-api-js: workspace:* importers: @@ -120,7 +120,7 @@ importers: version: 0.5.1 near-workspaces: specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + version: 4.0.0 semver: specifier: 7.7.1 version: 7.7.1 @@ -252,8 +252,8 @@ importers: specifier: 0.6.0 version: 0.6.0 near-api-js: - specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + specifier: workspace:* + version: link:../near-api-js ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@22.13.9)(typescript@5.4.5) @@ -496,7 +496,7 @@ importers: version: 0.5.1 near-workspaces: specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + version: 4.0.0 rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -539,7 +539,7 @@ importers: version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) near-workspaces: specifier: 4.0.0 - version: 4.0.0(encoding@0.1.13) + version: 4.0.0 ts-jest: specifier: 29.2.6 version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) @@ -1436,54 +1436,10 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@near-js/accounts@1.1.0': - resolution: {integrity: sha512-YYudGgVepuWsLfusslj423IqH31A1YNS10ETgrg5zT8HEYUPpkevsdCAKslaihjQUAV6TVZ6bb3BjT7skoDMOg==} - - '@near-js/crypto@1.2.2': - resolution: {integrity: sha512-a/1SICBPg6zo+VGOy+HT448+uHXmlYnP4vwNUdJnmn0yMksi30G1N8BSU5tnP1SX904EASu1pdqSMWslERrCQw==} - - '@near-js/keystores-browser@0.0.10': - resolution: {integrity: sha512-JX9WbpPnlKQCxEXJJ/AZHj2z2yuR/UnHcUPszz+Q2v1moL5wETBSuwe6jQOfmYZoM+vGU2Vh4fz3V8Ml/Oz3bw==} - - '@near-js/keystores-node@0.0.10': - resolution: {integrity: sha512-fM5T+1pe1zHsaBwTW0JzPd5U93u6oGzUheg8Xpj8vEjSvISd9kreY14IQkbtXCtzsTsiIel05mIqHgmg35QNjg==} - - '@near-js/keystores@0.0.10': - resolution: {integrity: sha512-rzGMkqY7EcIbUPrcSjK1RJi3dTXusfmZcxg2Jcc9u7VVjEjP/HVnvVaazsYC1la7812VSrDPxqHsZRzWSFxtMA==} - - '@near-js/providers@0.2.0': - resolution: {integrity: sha512-K7RJTkVbn6SD68p/8TkxFjp6jhxNQrkjZ+etQxrBRAMS6kupLTobV+9AfAc1OaORO47X873p4BRhbm9KhCdmZg==} - - '@near-js/signers@0.1.2': - resolution: {integrity: sha512-Echz+ldAFUDGntiBcnCZN4scHBIccz6xVC0Zt9cZu9I4SGKrWga0vNfAwXGVGep8YCEu8MwI9lE2B5n2ouc6kg==} - - '@near-js/transactions@1.2.0': - resolution: {integrity: sha512-I9UVVPg0HHQUpL17tb9L1HgTMG5+KREI2mNQlvJhF5uE6DrI2tC/O4rQf3HZOUVWpUhOiXSKRfSXpgZq5TbXaw==} - - '@near-js/types@0.1.0': - resolution: {integrity: sha512-uQTB3G7251cKCFhM4poAgypTODb83jrqD5A5B0Nr89TAGbsYM2ozXJyffJpsWedbYhK527Jx/BFgs+Jzf3oO5g==} - - '@near-js/utils@0.2.0': - resolution: {integrity: sha512-Ul0NoOiV/vW6hnkYVMwRNbP18hB7mCRkqgSe1a2Qoe+3xFOMnjLVVod3Y1l/QXgp4yNDrLOd6r4PDPI72eKUww==} - - '@near-js/wallet-account@1.2.0': - resolution: {integrity: sha512-3y0VxE2R2FxAHFJpRkhqIp+ZTc+/n42u3qSH0vzTTihzL2zfGF6z9Y2ArNQ7lQKNyiXd0vsFdAIwVcX5pEfq3A==} - - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.8.1': resolution: {integrity: sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==} engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - - '@noble/hashes@1.3.3': - resolution: {integrity: sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==} - engines: {node: '>= 16'} - '@noble/hashes@1.7.1': resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==} engines: {node: ^14.21.3 || >=16} @@ -1809,11 +1765,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base-x@2.0.6: - resolution: {integrity: sha512-UAmjxz9KbK+YIi66xej+pZVo/vxUOh49ubEvZW5egCbxhur05pBb+hwuireQwKO4nDpsNm64/jEei17LEpsr5g==} - engines: {node: '>=4.5.0'} - deprecated: use 3.0.0 instead, safe-buffer has been merged and release for compatability - base-x@3.0.11: resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} @@ -1862,9 +1813,6 @@ packages: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} - bs58@4.0.0: - resolution: {integrity: sha512-/jcGuUuSebyxwLLfKrbKnCJttxRf9PM51EnHTwmFKBxl4z1SGkoAhrfd6uZKE0dcjQTfm6XzTP8DPr1tzE4KIw==} - bs58@4.0.1: resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} @@ -3046,15 +2994,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - near-abi@0.1.1: - resolution: {integrity: sha512-RVDI8O+KVxRpC3KycJ1bpfVj9Zv+xvq9PlW1yIFl46GhrnLw83/72HqHGjGDjQ8DtltkcpSjY9X3YIGZ+1QyzQ==} - near-abi@0.2.0: resolution: {integrity: sha512-kCwSf/3fraPU2zENK18sh+kKG4uKbEUEQdyWQkmW8ZofmLarObIz2+zAYjA1teDZLeMvEQew3UysnPDXgjneaA==} - near-api-js@4.0.0: - resolution: {integrity: sha512-Gh4Lq9LXFDNtEHGXeqYFjbvS9lodX34srmFAxhOgGqjklK5QArrT7bTifWA9mi3QGe3MqwwwfAIVONbeZ0qSpg==} - near-hello@0.5.1: resolution: {integrity: sha512-k7S8VFyESWgkKYDso99B4XbxAdo0VX9b3+GAaO5PvMgQjNr/6o09PHRywg/NkBQpf+ZYj7nNpJcyrNJGQsvA3w==} @@ -4834,107 +4776,10 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@near-js/accounts@1.1.0(encoding@0.1.13)': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - depd: 2.0.0 - is-my-json-valid: 2.20.6 - lru_map: 0.4.1 - near-abi: 0.1.1 - transitivePeerDependencies: - - encoding - - '@near-js/crypto@1.2.2': - dependencies: - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@noble/curves': 1.2.0 - borsh: 1.0.0 - randombytes: 2.1.0 - - '@near-js/keystores-browser@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - - '@near-js/keystores-node@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - - '@near-js/keystores@0.0.10': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/types': 0.1.0 - - '@near-js/providers@0.2.0(encoding@0.1.13)': - dependencies: - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - http-errors: 1.7.2 - optionalDependencies: - node-fetch: 2.6.7(encoding@0.1.13) - transitivePeerDependencies: - - encoding - - '@near-js/signers@0.1.2': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@noble/hashes': 1.3.3 - - '@near-js/transactions@1.2.0': - dependencies: - '@near-js/crypto': 1.2.2 - '@near-js/signers': 0.1.2 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@noble/hashes': 1.3.3 - borsh: 1.0.0 - - '@near-js/types@0.1.0': {} - - '@near-js/utils@0.2.0': - dependencies: - '@near-js/types': 0.1.0 - bs58: 4.0.0 - depd: 2.0.0 - mustache: 4.0.0 - - '@near-js/wallet-account@1.2.0(encoding@0.1.13)': - dependencies: - '@near-js/accounts': 1.1.0(encoding@0.1.13) - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - borsh: 1.0.0 - transitivePeerDependencies: - - encoding - - '@noble/curves@1.2.0': - dependencies: - '@noble/hashes': 1.3.2 - '@noble/curves@1.8.1': dependencies: '@noble/hashes': 1.7.1 - '@noble/hashes@1.3.2': {} - - '@noble/hashes@1.3.3': {} - '@noble/hashes@1.7.1': {} '@nodelib/fs.scandir@2.1.5': @@ -5323,10 +5168,6 @@ snapshots: balanced-match@1.0.2: {} - base-x@2.0.6: - dependencies: - safe-buffer: 5.2.1 - base-x@3.0.11: dependencies: safe-buffer: 5.2.1 @@ -5377,10 +5218,6 @@ snapshots: dependencies: fast-json-stable-stringify: 2.1.0 - bs58@4.0.0: - dependencies: - base-x: 2.0.6 - bs58@4.0.1: dependencies: base-x: 3.0.11 @@ -6969,36 +6806,10 @@ snapshots: natural-compare@1.4.0: {} - near-abi@0.1.1: - dependencies: - '@types/json-schema': 7.0.15 - near-abi@0.2.0: dependencies: '@types/json-schema': 7.0.15 - near-api-js@4.0.0(encoding@0.1.13): - dependencies: - '@near-js/accounts': 1.1.0(encoding@0.1.13) - '@near-js/crypto': 1.2.2 - '@near-js/keystores': 0.0.10 - '@near-js/keystores-browser': 0.0.10 - '@near-js/keystores-node': 0.0.10 - '@near-js/providers': 0.2.0(encoding@0.1.13) - '@near-js/signers': 0.1.2 - '@near-js/transactions': 1.2.0 - '@near-js/types': 0.1.0 - '@near-js/utils': 0.2.0 - '@near-js/wallet-account': 1.2.0(encoding@0.1.13) - '@noble/curves': 1.2.0 - borsh: 1.0.0 - depd: 2.0.0 - http-errors: 1.7.2 - near-abi: 0.1.1 - node-fetch: 2.6.7(encoding@0.1.13) - transitivePeerDependencies: - - encoding - near-hello@0.5.1: {} near-sandbox@0.0.18: @@ -7010,7 +6821,7 @@ snapshots: dependencies: bn.js: 5.2.1 - near-workspaces@4.0.0(encoding@0.1.13): + near-workspaces@4.0.0: dependencies: base64url: 3.0.1 bn.js: 5.2.1 @@ -7019,7 +6830,7 @@ snapshots: callsites: 4.2.0 fs-extra: 10.1.0 js-sha256: 0.9.0 - near-api-js: 4.0.0(encoding@0.1.13) + near-api-js: link:packages/near-api-js near-sandbox: 0.0.18 near-units: 0.1.9 node-port-check: 2.0.1 @@ -7028,8 +6839,6 @@ snapshots: pure-uuid: 1.8.1 rimraf: 3.0.2 temp-dir: 2.0.0 - transitivePeerDependencies: - - encoding node-addon-api@5.1.0: {} From 6c30b9cd691738d078a6bbcba37fd9bb16cee284 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Tue, 25 Mar 2025 13:25:02 +0100 Subject: [PATCH 02/57] wip: deciding on interface --- packages/near-api-js/src/account.ts | 99 ++++++++++++----------------- packages/near-api-js/src/signer.ts | 47 +++----------- 2 files changed, 50 insertions(+), 96 deletions(-) diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index e03f4dbb9d..ad6072551c 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -151,12 +151,16 @@ export class ContractV2 extends PublicAccountV2 { } } +// PublicAccount + +// Wallet -> accountId, signer, provider +// Signer -> signNep413Message, signTransaction + export class SignerAccountV2 extends PublicAccountV2 { - public readonly signer: SignerV2; + public signer: SignerV2; constructor(accountId: string, provider: Provider, signer: SignerV2) { super(accountId, provider); - this.signer = signer; } @@ -164,35 +168,8 @@ export class SignerAccountV2 extends PublicAccountV2 { throw new Error(`Not implemented yet!`); } - // TODO: Find matching access key based on transaction (i.e. receiverId and actions) - protected async matchAccessKeyBasedOnReceiverAndActions( - publicKeys: Array, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - receiverId: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - actions: Array - ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView } | undefined> { - const pk = publicKeys.at(0)!; - - const rawAccessKey = await this.provider.query({ - request_type: "view_access_key", - account_id: this.accountId, - public_key: pk.toString(), - finality: "optimistic", - }); - - // store nonce as BigInt to preserve precision on big number - const accessKey = { - ...rawAccessKey, - nonce: BigInt(rawAccessKey.nonce || 0), - }; - - return { publicKey: pk, accessKey }; - } - - protected async findRecentBlockHash(): Promise { - const block = await this.provider.block({ finality: "final" }); - return block.header.hash; + public setSigner(signer: SignerV2): void { + this.signer = signer; } public async constructTransaction( @@ -201,26 +178,19 @@ export class SignerAccountV2 extends PublicAccountV2 { ): Promise { if (!this.signer) throw new Error(`Signer is required!`); - const publicKeys = await this.signer.getPublicKeys(); - - if (publicKeys.length === 0) - throw new Error(`No public keys available!`); - - const signerKey = await this.matchAccessKeyBasedOnReceiverAndActions( - publicKeys, - receiverId, - actions - ); + const block = await this.provider.block({ finality: "final" }); + const recentBlockHash = block.header.hash; - if (!signerKey) throw new Error(`No public key found`); + const publicKey = this.signer.getPublicKey(); - const recentBlockHash = await this.findRecentBlockHash(); + // get the nonce for the public key for this account + const nonce = this.provider.getNonce(this.accountId, publicKey); return createTransaction( this.accountId, - signerKey.publicKey, + publicKey, receiverId, - signerKey.accessKey.nonce + 1n, + nonce + 1n, actions, baseDecode(recentBlockHash) ); @@ -247,7 +217,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async deleteAccount( @@ -262,7 +232,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async addFullAccessKey( @@ -277,7 +247,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async addFunctionAccessKey( @@ -300,7 +270,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async deleteKey(pk: PublicKey): Promise { if (!this.signer) throw new Error(`Signer is required!`); @@ -312,7 +282,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async transfer( @@ -328,7 +298,7 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async deployContract( code: Uint8Array @@ -342,22 +312,35 @@ export class SignerAccountV2 extends PublicAccountV2 { actions ); - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } public async signTransaction( transaction: Transaction ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - return this.signer.signTransaction(transaction); } public async signAndSendTransaction( - transaction: Transaction + transaction: Transaction | { receiverId: string; actions: Array } ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - return this.signer.signAndSendTransaction(transaction, this.provider); + return this.signAndSendTransaction(transaction); } + + + // public async signAndSendTransaction({ + // receiverId: string, + // actions: Array + // }): Promise { + // } + + // public async signAndSendTransactions( + // transactions: Array, + // ): Promise { + // } + + // public async signAndSendTransactions( + // transactions: Array<{receiverId: string, actions: Array}>, + // ): Promise { + // } } diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index 616df2b111..d4c3df14a8 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1,12 +1,10 @@ import { KeyPair, PublicKey } from "@near-js/crypto"; -import { Provider } from "@near-js/providers"; import { encodeTransaction, Signature, SignedTransaction, Transaction, } from "@near-js/transactions"; -import { FinalExecutionOutcome } from "@near-js/types"; export { InMemorySigner, Signer } from "@near-js/signers"; @@ -29,7 +27,7 @@ export interface SignedMessage { * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. */ export abstract class SignerV2 { - abstract getPublicKeys(): Promise>; + abstract getPublicKey(): PublicKey; abstract signNep413Message( params: SignMessageParams, @@ -39,27 +37,20 @@ export abstract class SignerV2 { abstract signTransaction( transaction: Transaction ): Promise; - - /** - * This method is required for compatibility with WalletSelector - */ - abstract signAndSendTransaction( - transaction: Transaction, - // not sure if this Provider should be here - provider?: Provider - ): Promise; } export class KeyPairSigner extends SignerV2 { + public readonly key: KeyPair; + constructor( - private readonly keys: Array, - private readonly provider: Provider + key: KeyPair, ) { super(); + this.key = key; } - public async getPublicKeys(): Promise> { - return this.keys.map((key) => key.getPublicKey()); + public getPublicKey(): PublicKey { + return this.key.getPublicKey(); } public async signNep413Message( @@ -76,34 +67,14 @@ export class KeyPairSigner extends SignerV2 { ): Promise { const message = encodeTransaction(transaction); - const keyPair = this.keys.find( - (key) => key.getPublicKey() === transaction.publicKey - ); - - if (!keyPair) - throw new Error( - `Transaction can't be signed with ${transaction.publicKey} as this key doesn't belong to a Signer` - ); - - const { signature } = keyPair.sign(message); + const { signature } = this.key.sign(message); return new SignedTransaction({ transaction, signature: new Signature({ - keyType: keyPair.getPublicKey().keyType, + keyType: this.key.getPublicKey().keyType, data: signature, }), }); } - - public async signAndSendTransaction( - transaction: Transaction, - provider?: Provider - ): Promise { - const signedTx = await this.signTransaction(transaction); - - const rpcProvider = provider || this.provider; - - return rpcProvider.sendTransaction(signedTx); - } } From 751723086c3d5203a232e118f5e77f990ef0c49c Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 3 Apr 2025 21:26:53 +0200 Subject: [PATCH 03/57] chore: remove of sandbox code --- packages/cookbook/account_v2.js | 45 ---- packages/cookbook/account_v2.ts | 60 ----- packages/cookbook/index.ts | 1 - packages/near-api-js/src/account.ts | 337 ---------------------------- 4 files changed, 443 deletions(-) delete mode 100644 packages/cookbook/account_v2.js delete mode 100644 packages/cookbook/account_v2.ts diff --git a/packages/cookbook/account_v2.js b/packages/cookbook/account_v2.js deleted file mode 100644 index 4fbf452b99..0000000000 --- a/packages/cookbook/account_v2.js +++ /dev/null @@ -1,45 +0,0 @@ -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (g && (g = 0, op[0] && (_ = 0)), _) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -function main() { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - console.log('cook'); - return [2 /*return*/]; - }); - }); -} -main(); diff --git a/packages/cookbook/account_v2.ts b/packages/cookbook/account_v2.ts deleted file mode 100644 index fd8d721549..0000000000 --- a/packages/cookbook/account_v2.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ContractV2, PublicAccountV2 } from "near-api-js/lib/account"; -import { JsonRpcProvider } from "near-api-js/lib/providers"; -import { KeyPairSigner } from "near-api-js/lib/signer"; -import { KeyPair } from "@near-js/crypto"; - -async function main(): Promise { - const provider = new JsonRpcProvider({ - url: "https://test.rpc.fastnear.com", - }); - - const account = new PublicAccountV2("denbite.testnet", provider); - - const info = await account.getInformation(); - - console.log("info", info); - - await account.getAccessKeys(); - - // ---------------------------------------------- - - // use real key here - const key = KeyPair.fromRandom("ED25519"); - const signer = new KeyPairSigner([key], provider); - - const signerAccount = account.intoSignerAccount(signer); - - await signerAccount.transfer("guille.testnet", 1_000_000_000n); - - const code = new Uint8Array(); - await signerAccount.deployContract(code); - - await signerAccount.addFullAccessKey( - KeyPair.fromRandom("ED25519").getPublicKey() - ); - - await signerAccount.deleteAccount("guille.testnet"); - - // ---------------------------------------------- - - // or account.intoContract() - const contract = new ContractV2("ref-finance.testnet", provider); - - await contract.callReadFunction("tokens_by_owner", { - owner_id: signerAccount.accountId, - }); - - await contract.callWriteFunction( - signerAccount, - "swap_token", - { - token_in: "usdt.testnet", - amount_in: 1_000_000_000, - token_out: "wrap.testnet", - }, - 1n, // deposit 1 yoctoNear - 100_000_000_000n // 100 TGas - ); -} - -main(); diff --git a/packages/cookbook/index.ts b/packages/cookbook/index.ts index 5033343655..1bef8a034d 100644 --- a/packages/cookbook/index.ts +++ b/packages/cookbook/index.ts @@ -1,4 +1,3 @@ export * from './accounts'; export * from './transactions'; export * from './utils'; -export * from './account_v2'; diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index ad6072551c..9caac34c26 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -1,36 +1,3 @@ -import { Provider } from "@near-js/providers"; -import { SignerV2 } from "./signer"; -import { - Action, - createTransaction, - SignedTransaction, - Transaction, -} from "@near-js/transactions"; -import { - addKey, - createAccount, - deleteAccount, - deleteKey, - deployContract, - fullAccessKey, - functionCall, - functionCallAccessKey, - transfer, -} from "./transaction"; -import { - AccessKeyList, - AccessKeyView, - AccessKeyViewRaw, - AccountView, - BlockHash, - CodeResult, - ContractCodeView, - FinalExecutionOutcome, - ViewStateResult, -} from "@near-js/types"; -import { PublicKey } from "@near-js/crypto"; -import { baseDecode } from "@near-js/utils"; - export { Account, AccountBalance, @@ -40,307 +7,3 @@ export { ChangeFunctionCallOptions, ViewFunctionCallOptions, } from "@near-js/accounts"; - -export class PublicAccountV2 { - public readonly accountId: string; - public readonly provider: Provider; - - constructor(accountId: string, provider: Provider) { - this.accountId = accountId; - this.provider = provider; - } - - public intoSignerAccount(signer: SignerV2): SignerAccountV2 { - return new SignerAccountV2(this.accountId, this.provider, signer); - } - - public intoContract(): ContractV2 { - return new ContractV2(this.accountId, this.provider); - } - - public async getInformation(): Promise { - return this.provider.query({ - request_type: "view_account", - account_id: this.accountId, - finality: "optimistic", - }); - } - - public async getAccessKey(pk: PublicKey): Promise { - return this.provider.query({ - request_type: "view_access_key", - public_key: pk.toString(), - account_id: this.accountId, - finality: "optimistic", - }); - } - - public async getAccessKeys(): Promise { - return this.provider.query({ - request_type: "view_access_key_list", - account_id: this.accountId, - finality: "optimistic", - }); - } - - public async getTransactionStatus( - txHash: string - ): Promise { - return this.provider.txStatus( - txHash, - this.accountId, - "EXECUTED_OPTIMISTIC" - ); - } -} - -export class ContractV2 extends PublicAccountV2 { - constructor(accountId: string, provider: Provider) { - super(accountId, provider); - } - - public async getCode(): Promise { - return this.provider.query({ - request_type: "view_code", - account_id: this.accountId, - finality: "optimistic", - }); - } - - public async getState(prefix: string = ""): Promise { - const prefixBase64 = Buffer.from(prefix).toString("base64"); - - return this.provider.query({ - request_type: "view_state", - account_id: this.accountId, - finality: "optimistic", - prefix_base64: prefixBase64, - }); - } - - public async callReadFunction( - methodName: string, - args: Record = {} - ): Promise { - const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); - - return this.provider.query({ - request_type: "call_function", - account_id: this.accountId, - method_name: methodName, - args_base64: argsBase64, - finality: "optimistic", - }); - } - - public async callWriteFunction( - signerAccount: SignerAccountV2, - methodName: string, - args: Record = {}, - deposit: bigint = 0n, - gas: bigint = 30_000_000_000_000n - ): Promise { - const actions = [functionCall(methodName, args, gas, deposit)]; - - const transaction = await signerAccount.constructTransaction( - this.accountId, - actions - ); - - return signerAccount.signAndSendTransaction(transaction); - } -} - -// PublicAccount - -// Wallet -> accountId, signer, provider -// Signer -> signNep413Message, signTransaction - -export class SignerAccountV2 extends PublicAccountV2 { - public signer: SignerV2; - - constructor(accountId: string, provider: Provider, signer: SignerV2) { - super(accountId, provider); - this.signer = signer; - } - - public intoDelegateAccount(): any { - throw new Error(`Not implemented yet!`); - } - - public setSigner(signer: SignerV2): void { - this.signer = signer; - } - - public async constructTransaction( - receiverId: string, - actions: Array - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const block = await this.provider.block({ finality: "final" }); - const recentBlockHash = block.header.hash; - - const publicKey = this.signer.getPublicKey(); - - // get the nonce for the public key for this account - const nonce = this.provider.getNonce(this.accountId, publicKey); - - return createTransaction( - this.accountId, - publicKey, - receiverId, - nonce + 1n, - actions, - baseDecode(recentBlockHash) - ); - } - - // Actions - public async createSubAccount( - name: string, - pk: PublicKey, - amount: bigint - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const accountName = `${name}.${this.accountId}`; - - const actions = [ - createAccount(), - transfer(amount), - addKey(pk, fullAccessKey()), - ]; - - const transaction = await this.constructTransaction( - accountName, - actions - ); - - return this.signAndSendTransaction(transaction); - } - - public async deleteAccount( - beneficiaryId: string - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [deleteAccount(beneficiaryId)]; - - const transaction = await this.constructTransaction( - this.accountId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - - public async addFullAccessKey( - pk: PublicKey - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [addKey(pk, fullAccessKey())]; - - const transaction = await this.constructTransaction( - this.accountId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - - public async addFunctionAccessKey( - pk: PublicKey, - receiverId: string, - methodNames: string[], - allowance?: bigint - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [ - addKey( - pk, - functionCallAccessKey(receiverId, methodNames, allowance) - ), - ]; - - const transaction = await this.constructTransaction( - this.accountId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - public async deleteKey(pk: PublicKey): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [deleteKey(pk)]; - - const transaction = await this.constructTransaction( - this.accountId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - - public async transfer( - receiverId: string, - amount: bigint - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [transfer(amount)]; - - const transaction = await this.constructTransaction( - receiverId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - public async deployContract( - code: Uint8Array - ): Promise { - if (!this.signer) throw new Error(`Signer is required!`); - - const actions = [deployContract(code)]; - - const transaction = await this.constructTransaction( - this.accountId, - actions - ); - - return this.signAndSendTransaction(transaction); - } - - public async signTransaction( - transaction: Transaction - ): Promise { - return this.signer.signTransaction(transaction); - } - - public async signAndSendTransaction( - transaction: Transaction | { receiverId: string; actions: Array } - ): Promise { - return this.signAndSendTransaction(transaction); - } - - - // public async signAndSendTransaction({ - // receiverId: string, - // actions: Array - // }): Promise { - // } - - // public async signAndSendTransactions( - // transactions: Array, - // ): Promise { - // } - - // public async signAndSendTransactions( - // transactions: Array<{receiverId: string, actions: Array}>, - // ): Promise { - // } -} From 3af9c185db5e44ab637dc0b84abeea714cf7a17a Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 3 Apr 2025 21:40:03 +0200 Subject: [PATCH 04/57] feat: introduce KeyPairSigner & update `Account` to use Signer directly --- packages/accounts/src/account.ts | 747 ++++++++++++++---- packages/accounts/src/account_2fa.ts | 18 +- packages/accounts/src/account_multisig.ts | 2 +- packages/accounts/src/connection.ts | 45 +- packages/accounts/src/index.ts | 1 + packages/client/src/signing/signers.ts | 45 +- packages/near-api-js/src/account.ts | 1 + packages/near-api-js/src/common-index.ts | 4 +- packages/near-api-js/src/signer.ts | 81 +- packages/near-api-js/src/transaction.ts | 1 - packages/signers/package.json | 1 + packages/signers/src/in_memory_signer.ts | 80 -- packages/signers/src/index.ts | 2 +- packages/signers/src/key_pair_signer.ts | 87 ++ packages/signers/src/signer.ts | 57 +- packages/transactions/package.json | 1 - packages/transactions/src/sign.ts | 47 +- packages/wallet-account/src/near.ts | 6 +- packages/wallet-account/src/wallet_account.ts | 16 +- pnpm-lock.yaml | 6 +- 20 files changed, 806 insertions(+), 442 deletions(-) delete mode 100644 packages/signers/src/in_memory_signer.ts create mode 100644 packages/signers/src/key_pair_signer.ts diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 47fe2245f8..87722be924 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -1,20 +1,18 @@ -import { PublicKey } from '@near-js/crypto'; -import { exponentialBackoff } from '@near-js/providers'; +import { PublicKey } from "@near-js/crypto"; +import { exponentialBackoff, Provider } from "@near-js/providers"; import { actionCreators, Action, buildDelegateAction, - signDelegateAction, - signTransaction, SignedDelegate, SignedTransaction, + createTransaction, stringifyJsonOrBytes, -} from '@near-js/transactions'; +} from "@near-js/transactions"; import { PositionalArgsError, FinalExecutionOutcome, TypedError, - ErrorContext, AccountView, AccessKeyView, AccessKeyViewRaw, @@ -22,19 +20,28 @@ import { AccessKeyInfoView, FunctionCallPermissionView, BlockReference, -} from '@near-js/types'; + ContractCodeView, + ViewStateResult, + CodeResult, + ErrorContext, +} from "@near-js/types"; import { baseDecode, - baseEncode, Logger, - parseResultError, DEFAULT_FUNCTION_CALL_GAS, + baseEncode, + parseResultError, printTxOutcomeLogsAndFailures, -} from '@near-js/utils'; +} from "@near-js/utils"; -import { Connection } from './connection'; -import { viewFunction, viewState } from './utils'; -import { ChangeFunctionCallOptions, IntoConnection, ViewFunctionCallOptions } from './interface'; +import { Signer } from "@near-js/signers"; +import { Connection } from "./connection"; +import { viewFunction, viewState } from "./utils"; +import { + ChangeFunctionCallOptions, + IntoConnection, + ViewFunctionCallOptions, +} from "./interface"; const { addKey, @@ -108,31 +115,136 @@ interface SignedDelegateOptions { receiverId: string; } +export class PublicAccount { + public readonly accountId: string; + public readonly provider: Provider; + + constructor(accountId: string, provider: Provider) { + this.accountId = accountId; + this.provider = provider; + } + + public withSigner(signer: Signer): Account { + return new Account(this.accountId, this.provider, signer); + } + + /** + * Returns basic NEAR account information via the `view_account` RPC query method + * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + */ + public async getInformation(): Promise { + return this.provider.query({ + request_type: "view_account", + account_id: this.accountId, + finality: "optimistic", + }); + } + + /** + * Returns basic NEAR account information via the `view_account` RPC query method + * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + */ + public async getAccessKey(pk: PublicKey): Promise { + return this.provider.query({ + request_type: "view_access_key", + public_key: pk.toString(), + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getAccessKeyList(): Promise { + return this.provider.query({ + request_type: "view_access_key_list", + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getContractCode(): Promise { + return this.provider.query({ + request_type: "view_code", + account_id: this.accountId, + finality: "optimistic", + }); + } + + public async getContractState( + prefix: string = "" + ): Promise { + const prefixBase64 = Buffer.from(prefix).toString("base64"); + + return this.provider.query({ + request_type: "view_state", + account_id: this.accountId, + finality: "optimistic", + prefix_base64: prefixBase64, + }); + } + + public async getTransactionStatus( + txHash: string + ): Promise { + return this.provider.txStatus( + txHash, + this.accountId, + "EXECUTED_OPTIMISTIC" + ); + } + + /** + * Invoke a contract view function using the RPC API. + * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) + * + * @returns {Promise} + */ + public async invokeReadFunction( + contractId: string, + methodName: string, + args: Record = {} + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); + + const { result } = await this.provider.query({ + request_type: "call_function", + account_id: contractId, + method_name: methodName, + args_base64: argsBase64, + finality: "optimistic", + }); + + if (result.length === 0) return undefined; + + return JSON.parse(Buffer.from(result).toString()); + } +} + /** * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. */ -export class Account implements IntoConnection { - readonly connection: Connection; - readonly accountId: string; +export class Account extends PublicAccount implements IntoConnection { + protected readonly signer: Signer; - constructor(connection: Connection, accountId: string) { - this.connection = connection; - this.accountId = accountId; + constructor(accountId: string, provider: Provider, signer: Signer) { + super(accountId, provider); + this.signer = signer; } - getConnection(): Connection { - return this.connection; + public getConnection(): Connection { + return new Connection('', this.provider, this.signer, ''); } /** * Returns basic NEAR account information via the `view_account` RPC query method * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + * + * @deprecated */ async state(): Promise { - return this.connection.provider.query({ - request_type: 'view_account', + return this.provider.query({ + request_type: "view_account", account_id: this.accountId, - finality: 'optimistic' + finality: "optimistic", }); } @@ -142,70 +254,111 @@ export class Account implements IntoConnection { * @param actions list of actions to perform as part of the transaction * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} */ - protected async signTransaction(receiverId: string, actions: Action[]): Promise<[Uint8Array, SignedTransaction]> { - const accessKeyInfo = await this.findAccessKey(receiverId, actions); - if (!accessKeyInfo) { - throw new TypedError(`Can not sign transactions for account ${this.accountId} on network ${this.connection.networkId}, no matching key pair exists for this account`, 'KeyNotFound'); - } - const { accessKey } = accessKeyInfo; + public async signTransaction( + receiverId: string, + actions: Action[] + ): Promise<[Uint8Array, SignedTransaction]> { + const pk = await this.signer.getPublicKey(); - const block = await this.connection.provider.block({ finality: 'final' }); - const blockHash = block.header.hash; + const accessKey = await this.getAccessKey(pk); + + const block = await this.provider.block({ + finality: "final", + }); + const recentBlockHash = block.header.hash; + + const nonce = BigInt(accessKey.nonce) + 1n; - const nonce = accessKey.nonce + 1n; - return await signTransaction( - receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId + const tx = createTransaction( + this.accountId, + pk, + receiverId, + nonce + 1n, + actions, + baseDecode(recentBlockHash) ); + + return this.signer.signTransaction(tx); } /** * Sign a transaction to perform a list of actions and broadcast it using the RPC API. * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } - * + * * @param options The options for signing and sending the transaction. * @param options.receiverId The NEAR account ID of the transaction receiver. * @param options.actions The list of actions to be performed in the transaction. * @param options.returnError Whether to return an error if the transaction fails. * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. */ - async signAndSendTransaction({ receiverId, actions, returnError }: SignAndSendTransactionOptions): Promise { + async signAndSendTransaction({ + receiverId, + actions, + returnError, + }: SignAndSendTransactionOptions): Promise { let txHash, signedTx; // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) - const result = await exponentialBackoff(TX_NONCE_RETRY_WAIT, TX_NONCE_RETRY_NUMBER, TX_NONCE_RETRY_WAIT_BACKOFF, async () => { - [txHash, signedTx] = await this.signTransaction(receiverId, actions); - const publicKey = signedTx.transaction.publicKey; - - try { - return await this.connection.provider.sendTransaction(signedTx); - } catch (error) { - if (error.type === 'InvalidNonce') { - Logger.warn(`Retrying transaction ${receiverId}:${baseEncode(txHash)} with new nonce.`); - delete this.accessKeyByPublicKeyCache[publicKey.toString()]; - return null; - } - if (error.type === 'Expired') { - Logger.warn(`Retrying transaction ${receiverId}:${baseEncode(txHash)} due to expired block hash`); - return null; - } + const result = await exponentialBackoff( + TX_NONCE_RETRY_WAIT, + TX_NONCE_RETRY_NUMBER, + TX_NONCE_RETRY_WAIT_BACKOFF, + async () => { + [txHash, signedTx] = await this.signTransaction( + receiverId, + actions + ); - error.context = new ErrorContext(baseEncode(txHash)); - throw error; + try { + return await this.provider.sendTransaction(signedTx); + } catch (error) { + if (error.type === "InvalidNonce") { + Logger.warn( + `Retrying transaction ${receiverId}:${baseEncode( + txHash + )} with new nonce.` + ); + return null; + } + if (error.type === "Expired") { + Logger.warn( + `Retrying transaction ${receiverId}:${baseEncode( + txHash + )} due to expired block hash` + ); + return null; + } + + error.context = new ErrorContext(baseEncode(txHash)); + throw error; + } } - }); + ); if (!result) { // TODO: This should have different code actually, as means "transaction not submitted for sure" - throw new TypedError('nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.', 'RetriesExceeded'); + throw new TypedError( + "nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.", + "RetriesExceeded" + ); } printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result }); // Should be falsy if result.status.Failure is null - if (!returnError && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { + if ( + !returnError && + typeof result.status === "object" && + typeof result.status.Failure === "object" && + result.status.Failure !== null + ) { // if error data has error_message and error_type properties, we consider that node returned an error in the old format - if (result.status.Failure.error_message && result.status.Failure.error_type) { + if ( + result.status.Failure.error_message && + result.status.Failure.error_type + ) { throw new TypedError( `Transaction ${result.transaction_outcome.id} failed. ${result.status.Failure.error_message}`, - result.status.Failure.error_type); + result.status.Failure.error_type + ); } else { throw parseResultError(result); } @@ -222,48 +375,60 @@ export class Account implements IntoConnection { * * @todo Find matching access key based on transaction (i.e. receiverId and actions) * + * @deprecated + * * @param receiverId currently unused (see todo) * @param actions currently unused (see todo) * @returns `{ publicKey PublicKey; accessKey: AccessKeyView }` */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - async findAccessKey(receiverId: string, actions: Action[]): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { - // TODO: Find matching access key based on transaction (i.e. receiverId and actions) - const publicKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); + async findAccessKey( + receiverId: string, + actions: Action[] + ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { + const publicKey = await this.signer.getPublicKey(); if (!publicKey) { - throw new TypedError(`no matching key pair found in ${this.connection.signer}`, 'PublicKeyNotFound'); + throw new TypedError( + `no matching key pair found in ${this.signer.constructor.name}`, + "PublicKeyNotFound" + ); } - const cachedAccessKey = this.accessKeyByPublicKeyCache[publicKey.toString()]; + const cachedAccessKey = + this.accessKeyByPublicKeyCache[publicKey.toString()]; if (cachedAccessKey !== undefined) { return { publicKey, accessKey: cachedAccessKey }; } try { - const rawAccessKey = await this.connection.provider.query({ - request_type: 'view_access_key', + const rawAccessKey = await this.provider.query({ + request_type: "view_access_key", account_id: this.accountId, public_key: publicKey.toString(), - finality: 'optimistic' + finality: "optimistic", }); // store nonce as BigInt to preserve precision on big number const accessKey = { ...rawAccessKey, - nonce: BigInt(rawAccessKey.nonce || 0) + nonce: BigInt(rawAccessKey.nonce || 0), }; // this function can be called multiple times and retrieve the same access key // this checks to see if the access key was already retrieved and cached while // the above network call was in flight. To keep nonce values in line, we return // the cached access key. if (this.accessKeyByPublicKeyCache[publicKey.toString()]) { - return { publicKey, accessKey: this.accessKeyByPublicKeyCache[publicKey.toString()] }; + return { + publicKey, + accessKey: + this.accessKeyByPublicKeyCache[publicKey.toString()], + }; } this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; return { publicKey, accessKey }; } catch (e) { - if (e.type == 'AccessKeyDoesNotExist') { + if (e.type == "AccessKeyDoesNotExist") { return null; } @@ -274,71 +439,156 @@ export class Account implements IntoConnection { /** * Create a new account and deploy a contract to it * + * @deprecated + * * @param contractId NEAR account where the contract is deployed * @param publicKey The public key to add to the created contract account * @param data The compiled contract code * @param amount of NEAR to transfer to the created contract account. Transfer enough to pay for storage https://docs.near.org/docs/concepts/storage-staking */ - async createAndDeployContract(contractId: string, publicKey: string | PublicKey, data: Uint8Array, amount: bigint): Promise { + async createAndDeployContract( + contractId: string, + publicKey: string | PublicKey, + data: Uint8Array, + amount: bigint + ): Promise { const accessKey = fullAccessKey(); await this.signAndSendTransaction({ receiverId: contractId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey), deployContract(data)] + actions: [ + createAccount(), + transfer(amount), + addKey(PublicKey.from(publicKey), accessKey), + deployContract(data), + ], }); - const contractAccount = new Account(this.connection, contractId); - return contractAccount; + return new PublicAccount(contractId, this.provider); } /** + * @deprecated + * * @param receiverId NEAR account receiving Ⓝ * @param amount Amount to send in yoctoⓃ */ - async sendMoney(receiverId: string, amount: bigint): Promise { + async sendMoney( + receiverId: string, + amount: bigint + ): Promise { return this.signAndSendTransaction({ receiverId, - actions: [transfer(amount)] + actions: [transfer(amount)], }); } /** + * @deprecated + * * @param newAccountId NEAR account name to be created * @param publicKey A public key created from the masterAccount */ - async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: bigint): Promise { + async createAccount( + newAccountId: string, + publicKey: string | PublicKey, + amount: bigint + ): Promise { const accessKey = fullAccessKey(); return this.signAndSendTransaction({ receiverId: newAccountId, - actions: [createAccount(), transfer(amount), addKey(PublicKey.from(publicKey), accessKey)] + actions: [ + createAccount(), + transfer(amount), + addKey(PublicKey.from(publicKey), accessKey), + ], + }); + } + + public async createSubAccount( + accountOrPrefix: string, + pk: PublicKey, + amount: bigint + ): Promise { + const newAccountId = accountOrPrefix.includes(".") + ? accountOrPrefix + : `${accountOrPrefix}.${this.accountId}`; + const actions = [ + createAccount(), + transfer(amount), + addKey(pk, fullAccessKey()), + ]; + + return this.signAndSendTransaction({ + receiverId: newAccountId, + actions: actions, + }); + } + + public async createSubAccountAndDeployContract( + accountOrPrefix: string, + pk: PublicKey, + amount: bigint, + code: Uint8Array + ): Promise { + const newAccountId = accountOrPrefix.includes(".") + ? accountOrPrefix + : `${accountOrPrefix}.${this.accountId}`; + const actions = [ + createAccount(), + transfer(amount), + addKey(pk, fullAccessKey()), + deployContract(code), + ]; + + return this.signAndSendTransaction({ + receiverId: newAccountId, + actions: actions, }); } /** * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted */ - async deleteAccount(beneficiaryId: string) { - Logger.log('Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting.'); + public async deleteAccount( + beneficiaryId: string + ): Promise { + Logger.log( + "Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting." + ); + + const actions = [deleteAccount(beneficiaryId)]; + return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deleteAccount(beneficiaryId)] + actions: actions, }); } /** - * @param data The compiled contract code + * @param code The compiled contract code bytes */ - async deployContract(data: Uint8Array): Promise { + public async deployContract( + code: Uint8Array + ): Promise { + const actions = [deployContract(code)]; + return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deployContract(data)] + actions: actions, }); } /** @hidden */ private encodeJSContractArgs(contractId: string, method: string, args) { - return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]); + return Buffer.concat([ + Buffer.from(contractId), + Buffer.from([0]), + Buffer.from(method), + Buffer.from([0]), + Buffer.from(args), + ]); } - /** + /** * Execute a function call. * @param options The options for the function call. * @param options.contractId The NEAR account ID of the smart contract. @@ -352,28 +602,61 @@ export class Account implements IntoConnection { * @param options.jsContract Whether the contract is from JS SDK, automatically encodes args from JS SDK to binary. * @returns {Promise} A promise that resolves to the final execution outcome of the function call. */ - async functionCall({ contractId, methodName, args = {}, gas = DEFAULT_FUNCTION_CALL_GAS, attachedDeposit, walletMeta, walletCallbackUrl, stringify, jsContract }: ChangeFunctionCallOptions): Promise { + async functionCall({ + contractId, + methodName, + args = {}, + gas = DEFAULT_FUNCTION_CALL_GAS, + attachedDeposit, + walletMeta, + walletCallbackUrl, + stringify, + jsContract, + }: ChangeFunctionCallOptions): Promise { this.validateArgs(args); let functionCallArgs; if (jsContract) { - const encodedArgs = this.encodeJSContractArgs(contractId, methodName, JSON.stringify(args)); - functionCallArgs = ['call_js_contract', encodedArgs, gas, attachedDeposit, null, true]; + const encodedArgs = this.encodeJSContractArgs( + contractId, + methodName, + JSON.stringify(args) + ); + functionCallArgs = [ + "call_js_contract", + encodedArgs, + gas, + attachedDeposit, + null, + true, + ]; } else { - const stringifyArg = stringify === undefined ? stringifyJsonOrBytes : stringify; - functionCallArgs = [methodName, args, gas, attachedDeposit, stringifyArg, false]; + const stringifyArg = + stringify === undefined ? stringifyJsonOrBytes : stringify; + functionCallArgs = [ + methodName, + args, + gas, + attachedDeposit, + stringifyArg, + false, + ]; } return this.signAndSendTransaction({ - receiverId: jsContract ? this.connection.jsvmAccountId : contractId, + receiverId: jsContract + ? process.env.NEAR_JSVM_ACCOUNT_ID + : contractId, // eslint-disable-next-line prefer-spread actions: [functionCall.apply(void 0, functionCallArgs)], walletMeta, - walletCallbackUrl + walletCallbackUrl, }); } /** + * @deprecated + * * @see [https://docs.near.org/concepts/basics/accounts/access-keys](https://docs.near.org/concepts/basics/accounts/access-keys) * @todo expand this API to support more options. * @param publicKey A public key to be associated with the contract @@ -381,7 +664,12 @@ export class Account implements IntoConnection { * @param methodNames The method names on the contract that should be allowed to be called. Pass null for no method names and '' or [] for any method names. * @param amount Payment in yoctoⓃ that is sent to the contract during this function call */ - async addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: bigint): Promise { + async addKey( + publicKey: string | PublicKey, + contractId?: string, + methodNames?: string | string[], + amount?: bigint + ): Promise { if (!methodNames) { methodNames = []; } @@ -396,7 +684,37 @@ export class Account implements IntoConnection { } return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [addKey(PublicKey.from(publicKey), accessKey)] + actions: [addKey(PublicKey.from(publicKey), accessKey)], + }); + } + + public async addFullAccessKey( + pk: PublicKey + ): Promise { + const actions = [addKey(pk, fullAccessKey())]; + + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: actions, + }); + } + + public async addFunctionAccessKey( + pk: PublicKey, + receiverId: string, + methodNames: string[] = [], + allowance?: bigint + ): Promise { + const actions = [ + addKey( + pk, + functionCallAccessKey(receiverId, methodNames, allowance) + ), + ]; + + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: actions, }); } @@ -404,10 +722,26 @@ export class Account implements IntoConnection { * @param publicKey The public key to be deleted * @returns {Promise} */ - async deleteKey(publicKey: string | PublicKey): Promise { + public async deleteKey( + publicKey: string | PublicKey + ): Promise { + const actions = [deleteKey(PublicKey.from(publicKey))]; + return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deleteKey(PublicKey.from(publicKey))] + actions: actions, + }); + } + + public async transfer( + receiverId: string, + amount: bigint + ): Promise { + const actions = [transfer(amount)]; + + return this.signAndSendTransaction({ + receiverId: receiverId, + actions: actions, }); } @@ -417,10 +751,15 @@ export class Account implements IntoConnection { * @param publicKey The public key for the account that's staking * @param amount The account to stake in yoctoⓃ */ - async stake(publicKey: string | PublicKey, amount: bigint): Promise { + public async stake( + publicKey: string | PublicKey, + amount: bigint + ): Promise { + const actions = [stake(amount, PublicKey.from(publicKey))]; + return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [stake(amount, PublicKey.from(publicKey))] + actions: actions, }); } @@ -432,50 +771,42 @@ export class Account implements IntoConnection { * @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction * @param options.receiverId Receiver account of the meta transaction */ - async signedDelegate({ + public async signedDelegate({ actions, blockHeightTtl, receiverId, }: SignedDelegateOptions): Promise { - const { provider, signer } = this.connection; - const { header } = await provider.block({ finality: 'final' }); - const { accessKey, publicKey } = await this.findAccessKey(null, null); + const { header } = await this.provider.block({ finality: "final" }); + + const pk = await this.signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); const delegateAction = buildDelegateAction({ actions, maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl), nonce: BigInt(accessKey.nonce) + 1n, - publicKey, + publicKey: pk, receiverId, senderId: this.accountId, }); - const { signedDelegateAction } = await signDelegateAction({ - delegateAction, - signer: { - sign: async (message) => { - const { signature } = await signer.signMessage( - message, - delegateAction.senderId, - this.connection.networkId - ); - - return signature; - }, - } - }); + const [, signedDelegate] = await this.signer.signDelegate( + delegateAction + ); - return signedDelegateAction; + return signedDelegate; } /** @hidden */ private validateArgs(args: any) { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; + const isUint8Array = + args.byteLength !== undefined && args.byteLength === args.length; if (isUint8Array) { return; } - if (Array.isArray(args) || typeof args !== 'object') { + if (Array.isArray(args) || typeof args !== "object") { throw new PositionalArgsError(); } } @@ -484,6 +815,8 @@ export class Account implements IntoConnection { * Invoke a contract view function using the RPC API. * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) * + * @deprecated + * * @param options Function call options. * @param options.contractId NEAR account where the contract is deployed * @param options.methodName The view-only method (no state mutations) name on the contract as it is written in the contract code @@ -496,7 +829,10 @@ export class Account implements IntoConnection { */ async viewFunction(options: ViewFunctionCallOptions): Promise { - return await viewFunction(this.connection, options); + return await viewFunction( + new Connection("", this.provider, this.signer, ""), + options + ); } /** @@ -504,39 +840,62 @@ export class Account implements IntoConnection { * Pass an empty string for prefix if you would like to return the entire state. * @see [https://docs.near.org/api/rpc/contracts#view-contract-state](https://docs.near.org/api/rpc/contracts#view-contract-state) * + * @deprecated + * * @param prefix allows to filter which keys should be returned. Empty prefix means all keys. String prefix is utf-8 encoded. * @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). */ - async viewState(prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' }): Promise> { - return await viewState(this.connection, this.accountId, prefix, blockQuery); + async viewState( + prefix: string | Uint8Array, + blockQuery: BlockReference = { finality: "optimistic" } + ): Promise> { + return await viewState( + new Connection("", this.provider, this.signer, ""), + this.accountId, + prefix, + blockQuery + ); } /** * Get all access keys for the account * @see [https://docs.near.org/api/rpc/access-keys#view-access-key-list](https://docs.near.org/api/rpc/access-keys#view-access-key-list) + * + * @deprecated */ async getAccessKeys(): Promise { - const response = await this.connection.provider.query({ - request_type: 'view_access_key_list', + const response = await this.provider.query({ + request_type: "view_access_key_list", account_id: this.accountId, - finality: 'optimistic' + finality: "optimistic", }); // Replace raw nonce into a new BigInt - return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: BigInt(key.access_key.nonce) } })); + return response?.keys?.map((key) => ({ + ...key, + access_key: { + ...key.access_key, + nonce: BigInt(key.access_key.nonce), + }, + })); } /** * Returns a list of authorized apps * @todo update the response value to return all the different keys, not just app keys. + * + * @deprecated */ - async getAccountDetails(): Promise<{ authorizedApps: AccountAuthorizedApp[] }> { + async getAccountDetails(): Promise<{ + authorizedApps: AccountAuthorizedApp[]; + }> { // TODO: update the response value to return all the different keys, not just app keys. // Also if we need this function, or getAccessKeys is good enough. const accessKeys = await this.getAccessKeys(); const authorizedApps = accessKeys - .filter(item => item.access_key.permission !== 'FullAccess') - .map(item => { - const perm = (item.access_key.permission as FunctionCallPermissionView); + .filter((item) => item.access_key.permission !== "FullAccess") + .map((item) => { + const perm = item.access_key + .permission as FunctionCallPermissionView; return { contractId: perm.FunctionCall.receiver_id, amount: perm.FunctionCall.allowance, @@ -548,55 +907,70 @@ export class Account implements IntoConnection { /** * Returns calculated account balance + * + * @deprecated */ async getAccountBalance(): Promise { - const protocolConfig = await this.connection.provider.experimental_protocolConfig({ finality: 'final' }); + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: "final", + }); const state = await this.state(); - const costPerByte = BigInt(protocolConfig.runtime_config.storage_amount_per_byte); + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); const stateStaked = BigInt(state.storage_usage) * costPerByte; const staked = BigInt(state.locked); const totalBalance = BigInt(state.amount) + staked; - const availableBalance = totalBalance - (staked > stateStaked ? staked : stateStaked); + const availableBalance = + totalBalance - (staked > stateStaked ? staked : stateStaked); return { total: totalBalance.toString(), stateStaked: stateStaked.toString(), staked: staked.toString(), - available: availableBalance.toString() + available: availableBalance.toString(), }; } /** * Returns the NEAR tokens balance and validators of a given account that is delegated to the staking pools that are part of the validators set in the current epoch. - * + * + * @deprecated + * * NOTE: If the tokens are delegated to a staking pool that is currently on pause or does not have enough tokens to participate in validation, they won't be accounted for. * @returns {Promise} */ async getActiveDelegatedStakeBalance(): Promise { - const block = await this.connection.provider.block({ finality: 'final' }); + const block = await this.provider.block({ finality: "final" }); const blockHash = block.header.hash; const epochId = block.header.epoch_id; - const { current_validators, next_validators, current_proposals } = await this.connection.provider.validators(epochId); + const { current_validators, next_validators, current_proposals } = + await this.provider.validators(epochId); const pools: Set = new Set(); - [...current_validators, ...next_validators, ...current_proposals] - .forEach((validator) => pools.add(validator.account_id)); + [ + ...current_validators, + ...next_validators, + ...current_proposals, + ].forEach((validator) => pools.add(validator.account_id)); const uniquePools = [...pools]; - const promises = uniquePools - .map((validator) => ( - this.viewFunction({ - contractId: validator, - methodName: 'get_account_total_balance', - args: { account_id: this.accountId }, - blockQuery: { blockId: blockHash } - }) - )); + const promises = uniquePools.map((validator) => + this.viewFunction({ + contractId: validator, + methodName: "get_account_total_balance", + args: { account_id: this.accountId }, + blockQuery: { blockId: blockHash }, + }) + ); const results = await Promise.allSettled(promises); const hasTimeoutError = results.some((result) => { - if (result.status === 'rejected' && result.reason.type === 'TimeoutError') { + if ( + result.status === "rejected" && + result.reason.type === "TimeoutError" + ) { return true; } return false; @@ -604,33 +978,66 @@ export class Account implements IntoConnection { // When RPC is down and return timeout error, throw error if (hasTimeoutError) { - throw new Error('Failed to get delegated stake balance'); + throw new Error("Failed to get delegated stake balance"); } - const summary = results.reduce((result, state, index) => { - const validatorId = uniquePools[index]; - if (state.status === 'fulfilled') { - const currentBN = BigInt(state.value); - if (currentBN !== 0n) { + const summary = results.reduce( + (result, state, index) => { + const validatorId = uniquePools[index]; + if (state.status === "fulfilled") { + const currentBN = BigInt(state.value); + if (currentBN !== 0n) { + return { + ...result, + stakedValidators: [ + ...result.stakedValidators, + { validatorId, amount: currentBN.toString() }, + ], + total: result.total + currentBN, + }; + } + } + if (state.status === "rejected") { return { ...result, - stakedValidators: [...result.stakedValidators, { validatorId, amount: currentBN.toString() }], - total: result.total + currentBN, + failedValidators: [ + ...result.failedValidators, + { validatorId, error: state.reason }, + ], }; } - } - if (state.status === 'rejected') { - return { - ...result, - failedValidators: [...result.failedValidators, { validatorId, error: state.reason }], - }; - } - return result; - }, - { stakedValidators: [], failedValidators: [], total: 0n }); + return result; + }, + { stakedValidators: [], failedValidators: [], total: 0n } + ); return { ...summary, total: summary.total.toString(), }; } + + /** + * Execute a function call + * + * @param contractId + * @param methodName + * @param args + * @param deposit + * @param gas + * @returns + */ + public async invokeWriteFunction( + contractId: string, + methodName: string, + args: Record = {}, + deposit: bigint = 0n, + gas: bigint = DEFAULT_FUNCTION_CALL_GAS + ): Promise { + const actions = [functionCall(methodName, args, gas, deposit)]; + + return this.signAndSendTransaction({ + receiverId: contractId, + actions: actions, + }); + } } diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts index 6edc03a3c0..ad539b4072 100644 --- a/packages/accounts/src/account_2fa.ts +++ b/packages/accounts/src/account_2fa.ts @@ -1,4 +1,4 @@ -import { PublicKey } from '@near-js/crypto'; +import { KeyPair, PublicKey } from '@near-js/crypto'; import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; import { actionCreators } from '@near-js/transactions'; import { Logger } from '@near-js/utils' @@ -13,6 +13,7 @@ import { MULTISIG_GAS, } from './constants'; import { MultisigStateStatus } from './types'; +import { KeyPairSigner } from '@near-js/signers'; const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; @@ -104,9 +105,9 @@ export class Account2FA extends AccountMultisig { case MultisigStateStatus.VALID_STATE: return await super.signAndSendTransactionWithAccount(accountId, actions); case MultisigStateStatus.INVALID_STATE: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState'); + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account has existing state.`, 'ContractHasExistingState'); default: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } } @@ -152,7 +153,7 @@ export class Account2FA extends AccountMultisig { return []; } throw cause == 'TOO_LARGE_CONTRACT_STATE' - ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account has existing state.`, 'ContractHasExistingState') + ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account has existing state.`, 'ContractHasExistingState') : error; }); @@ -196,7 +197,7 @@ export class Account2FA extends AccountMultisig { async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.connection.networkId}, the account state could not be verified.`, 'ContractStateUnknown'); + throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account state could not be verified.`, 'ContractStateUnknown'); } let deleteAllRequestsError; @@ -316,10 +317,11 @@ export class Account2FA extends AccountMultisig { * @returns {Promise<{ blockNumber: string, blockNumberSignature: string }>} - A promise that resolves to the signature information. */ async signatureFor() { - const { accountId } = this; - const block = await this.connection.provider.block({ finality: 'final' }); + const block = await this.getConnection().provider.block({ finality: 'final' }); const blockNumber = block.header.height.toString(); - const signed = await this.connection.signer.signMessage(Buffer.from(blockNumber), accountId, this.connection.networkId); + // @ts-expect-error keyPair isn't public + const keyPair = (this.getConnection().signer as KeyPairSigner).keyPair as KeyPair; + const signed = keyPair.sign(Buffer.from(blockNumber)); const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); return { blockNumber, blockNumberSignature }; } diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts index a7d92a5486..cb4314e4aa 100644 --- a/packages/accounts/src/account_multisig.ts +++ b/packages/accounts/src/account_multisig.ts @@ -39,7 +39,7 @@ export class AccountMultisig extends Account { * @param options.onAddRequestResult Callback function to handle the result of adding a request. */ constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId); + super(accountId, connection.provider, connection.signer); this.storage = options.storage; this.onAddRequestResult = options.onAddRequestResult; } diff --git a/packages/accounts/src/connection.ts b/packages/accounts/src/connection.ts index dc9a82f58d..83020c77be 100644 --- a/packages/accounts/src/connection.ts +++ b/packages/accounts/src/connection.ts @@ -1,6 +1,10 @@ -import { Signer, InMemorySigner } from '@near-js/signers'; -import { Provider, JsonRpcProvider, FailoverRpcProvider } from '@near-js/providers'; -import { IntoConnection } from './interface'; +import { Signer, KeyPairSigner } from "@near-js/signers"; +import { + Provider, + JsonRpcProvider, + FailoverRpcProvider, +} from "@near-js/providers"; +import { IntoConnection } from "./interface"; /** * @param config Contains connection info details @@ -10,12 +14,16 @@ function getProvider(config: any): Provider { switch (config.type) { case undefined: return config; - case 'JsonRpcProvider': return new JsonRpcProvider({ ...config.args }); - case 'FailoverRpcProvider': { - const providers = (config?.args || []).map((arg) => new JsonRpcProvider(arg)); + case "JsonRpcProvider": + return new JsonRpcProvider({ ...config.args }); + case "FailoverRpcProvider": { + const providers = (config?.args || []).map( + (arg) => new JsonRpcProvider(arg) + ); return new FailoverRpcProvider(providers); } - default: throw new Error(`Unknown provider type ${config.type}`); + default: + throw new Error(`Unknown provider type ${config.type}`); } } @@ -27,10 +35,11 @@ function getSigner(config: any): Signer { switch (config.type) { case undefined: return config; - case 'InMemorySigner': { - return new InMemorySigner(config.keyStore); + case "KeyPairSigner": { + return new KeyPairSigner(config.keyPair); } - default: throw new Error(`Unknown signer type ${config.type}`); + default: + throw new Error(`Unknown signer type ${config.type}`); } } @@ -43,11 +52,16 @@ export class Connection implements IntoConnection { readonly signer: Signer; readonly jsvmAccountId: string; - constructor(networkId: string, provider: Provider, signer: Signer, jsvmAccountId: string) { + constructor( + networkId: string, + provider: Provider, + signer: Signer, + jsvmAccountId: string + ) { this.networkId = networkId; this.provider = provider; this.signer = signer; - this.jsvmAccountId = jsvmAccountId; + this.jsvmAccountId = jsvmAccountId; } getConnection(): Connection { @@ -60,6 +74,11 @@ export class Connection implements IntoConnection { static fromConfig(config: any): Connection { const provider = getProvider(config.provider); const signer = getSigner(config.signer); - return new Connection(config.networkId, provider, signer, config.jsvmAccountId); + return new Connection( + config.networkId, + provider, + signer, + config.jsvmAccountId + ); } } diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index f8fe945e67..c94bde5a18 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -1,4 +1,5 @@ export { + PublicAccount, Account, AccountBalance, AccountAuthorizedApp, diff --git a/packages/client/src/signing/signers.ts b/packages/client/src/signing/signers.ts index 22aeee7039..3f4c7c5921 100644 --- a/packages/client/src/signing/signers.ts +++ b/packages/client/src/signing/signers.ts @@ -1,9 +1,5 @@ -import { - KeyPair, - type KeyPairString, -} from '@near-js/crypto'; +import { KeyPair, type KeyPairString } from "@near-js/crypto"; import { KeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; import type { AccessKeySigner, @@ -12,9 +8,9 @@ import type { MessageSigner, SignerDependency, ViewAccountParams, -} from '../interfaces'; -import { getAccessKey } from '../view'; -import { sha256 } from '@noble/hashes/sha256'; +} from "../interfaces"; +import { getAccessKey } from "../view"; +import { sha256 } from "@noble/hashes/sha256"; /** * Initialize a message signer from a KeyPair @@ -26,9 +22,9 @@ export function getSignerFromKeyPair(keyPair: KeyPair): MessageSigner { return keyPair.getPublicKey(); }, async signMessage(m) { - const hashedMessage = new Uint8Array(sha256(m)); + const hashedMessage = new Uint8Array(sha256(m)); return keyPair.sign(hashedMessage).signature; - } + }, }; } @@ -36,7 +32,9 @@ export function getSignerFromKeyPair(keyPair: KeyPair): MessageSigner { * 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 { +export function getSignerFromPrivateKey( + privateKey: KeyPairString +): MessageSigner { return getSignerFromKeyPair(KeyPair.fromString(privateKey)); } @@ -47,14 +45,19 @@ export function getSignerFromPrivateKey(privateKey: KeyPairString): MessageSigne * @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); + const keyPair = await keyStore.getKey(network, account); + + return keyPair.getPublicKey(); }, async signMessage(m) { - const { signature } = await signer.signMessage(m, account, network); + /** + * @todo migrate to KeyPairSigner someday + */ + const keyPair = await keyStore.getKey(network, account); + + const { signature } = keyPair.sign(m); return signature; } }; @@ -66,7 +69,11 @@ export function getSignerFromKeystore(account: string, network: string, keyStore * @param rpcProvider RPC provider instance * @param deps sign-and-send dependencies */ -export function getAccessKeySigner({ account, blockReference, deps: { rpcProvider, signer } }: ViewAccountParams & SignerDependency): AccessKeySigner { +export function getAccessKeySigner({ + account, + blockReference, + deps: { rpcProvider, signer }, +}: ViewAccountParams & SignerDependency): AccessKeySigner { let accessKey: FullAccessKey | FunctionCallAccessKey; let nonce: bigint | undefined; @@ -75,7 +82,9 @@ export function getAccessKeySigner({ account, blockReference, deps: { rpcProvide if (!accessKey || ignoreCache) { accessKey = await getAccessKey({ account, - blockReference: blockReference || { finality: 'optimistic' }, + blockReference: blockReference || { + finality: "optimistic", + }, publicKey: (await signer.getPublicKey()).toString(), deps: { rpcProvider }, }); @@ -105,6 +114,6 @@ export function getAccessKeySigner({ account, blockReference, deps: { rpcProvide nonce += 1n; } return signer.signMessage(m); - } + }, }; } diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 9caac34c26..56ea6f9015 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -1,4 +1,5 @@ export { + PublicAccount, Account, AccountBalance, AccountAuthorizedApp, diff --git a/packages/near-api-js/src/common-index.ts b/packages/near-api-js/src/common-index.ts index 6b0dd99bd4..fb4253f186 100644 --- a/packages/near-api-js/src/common-index.ts +++ b/packages/near-api-js/src/common-index.ts @@ -8,7 +8,7 @@ import { Account } from './account'; import * as multisig from './account_multisig'; import * as accountCreator from './account_creator'; import { Connection } from './connection'; -import { Signer, InMemorySigner } from './signer'; +import { Signer, KeyPairSigner } from './signer'; import { Contract } from './contract'; import { KeyPair } from './utils/key_pair'; import { Near } from './near'; @@ -29,7 +29,7 @@ export { Account, Connection, Contract, - InMemorySigner, + KeyPairSigner, Signer, KeyPair, diff --git a/packages/near-api-js/src/signer.ts b/packages/near-api-js/src/signer.ts index d4c3df14a8..d93b138f33 100644 --- a/packages/near-api-js/src/signer.ts +++ b/packages/near-api-js/src/signer.ts @@ -1,80 +1 @@ -import { KeyPair, PublicKey } from "@near-js/crypto"; -import { - encodeTransaction, - Signature, - SignedTransaction, - Transaction, -} from "@near-js/transactions"; - -export { InMemorySigner, Signer } from "@near-js/signers"; - -export interface SignMessageParams { - message: string; // The message that wants to be transmitted. - recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com"). - nonce: Uint8Array; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS). - callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`. - state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes. -} - -export interface SignedMessage { - accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near") - publicKey: string; // The public counterpart of the key used to sign, expressed as a string with format ":" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y") - signature: string; // The base64 representation of the signature. - state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The same state passed in SignMessageParams. -} - -/** - * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. - */ -export abstract class SignerV2 { - abstract getPublicKey(): PublicKey; - - abstract signNep413Message( - params: SignMessageParams, - accountId: string - ): Promise; - - abstract signTransaction( - transaction: Transaction - ): Promise; -} - -export class KeyPairSigner extends SignerV2 { - public readonly key: KeyPair; - - constructor( - key: KeyPair, - ) { - super(); - this.key = key; - } - - public getPublicKey(): PublicKey { - return this.key.getPublicKey(); - } - - public async signNep413Message( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - params: SignMessageParams, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - accountId: string - ): Promise { - throw new Error(`Not implemented!`); - } - - public async signTransaction( - transaction: Transaction - ): Promise { - const message = encodeTransaction(transaction); - - const { signature } = this.key.sign(message); - - return new SignedTransaction({ - transaction, - signature: new Signature({ - keyType: this.key.getPublicKey().keyType, - data: signature, - }), - }); - } -} +export { KeyPairSigner, Signer } from "@near-js/signers"; diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index e8fc48f07b..76a02c76ee 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -15,7 +15,6 @@ export { Transfer, SCHEMA, createTransaction, - signTransaction, Signature, SignedTransaction, Transaction, diff --git a/packages/signers/package.json b/packages/signers/package.json index 50103e6653..403868cc32 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -18,6 +18,7 @@ "dependencies": { "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", + "@near-js/transactions": "workspace:*", "@noble/hashes": "1.7.1" }, "devDependencies": { diff --git a/packages/signers/src/in_memory_signer.ts b/packages/signers/src/in_memory_signer.ts deleted file mode 100644 index 564633deed..0000000000 --- a/packages/signers/src/in_memory_signer.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { KeyPair, PublicKey, Signature, KeyType } from '@near-js/crypto'; -import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; -import { sha256 } from '@noble/hashes/sha256'; - -import { Signer } from './signer'; - -/** - * Signs using in memory key store. - */ -export class InMemorySigner extends Signer { - readonly keyStore: KeyStore; - - constructor(keyStore: KeyStore) { - super(); - this.keyStore = keyStore; - } - - /** - * Creates a single account Signer instance with account, network and keyPair provided. - * - * Intended to be useful for temporary keys (e.g. claiming a Linkdrop). - * - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @param accountId The NEAR account to assign the key pair to - * @param keyPair The keyPair to use for signing - */ - static async fromKeyPair(networkId: string, accountId: string, keyPair: KeyPair): Promise { - const keyStore = new InMemoryKeyStore(); - await keyStore.setKey(networkId, accountId, keyPair); - return new InMemorySigner(keyStore); - } - - /** - * Creates a public key for the account given - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async createKey(accountId: string, networkId: string, keyType?: KeyType): Promise { - const keyPair = keyType === KeyType.SECP256K1 ? KeyPair.fromRandom('secp256k1') : KeyPair.fromRandom('ed25519'); - await this.keyStore.setKey(networkId, accountId, keyPair); - return keyPair.getPublicKey(); - } - - /** - * Gets the existing public key for a given account - * @param accountId The NEAR account to assign a public key to - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} Returns the public key or null if not found - */ - async getPublicKey(accountId?: string, networkId?: string): Promise { - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - return null; - } - return keyPair.getPublicKey(); - } - - /** - * @param message A message to be signed, typically a serialized transaction - * @param accountId the NEAR account signing the message - * @param networkId The targeted network. (ex. default, betanet, etc…) - * @returns {Promise} - */ - async signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise { - const hash = new Uint8Array(sha256(message)); - if (!accountId) { - throw new Error('InMemorySigner requires provided account id'); - } - const keyPair = await this.keyStore.getKey(networkId, accountId); - if (keyPair === null) { - throw new Error(`Key for ${accountId} not found in ${networkId}`); - } - return keyPair.sign(hash); - } - - toString(): string { - return `InMemorySigner(${this.keyStore})`; - } -} diff --git a/packages/signers/src/index.ts b/packages/signers/src/index.ts index 9a5ebfb0bc..14251facc5 100644 --- a/packages/signers/src/index.ts +++ b/packages/signers/src/index.ts @@ -1,2 +1,2 @@ -export { InMemorySigner } from './in_memory_signer'; +export { KeyPairSigner } from './key_pair_signer'; export { Signer } from './signer'; diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts new file mode 100644 index 0000000000..a26f6c5a99 --- /dev/null +++ b/packages/signers/src/key_pair_signer.ts @@ -0,0 +1,87 @@ +import { KeyPair, PublicKey, KeyPairString, KeyType } from "@near-js/crypto"; +import { sha256 } from "@noble/hashes/sha256"; + +import { SignedMessage, Signer } from "./signer"; +import { + Transaction, + SignedTransaction, + encodeTransaction, + Signature, + DelegateAction, + SignedDelegate, + encodeDelegateAction, +} from "@near-js/transactions"; + +/** + * Signs using in memory key store. + */ +export class KeyPairSigner extends Signer { + private readonly key: KeyPair; + + constructor(key: KeyPair) { + super(); + this.key = key; + } + + public static fromSecretKey(encodedKey: KeyPairString): KeyPairSigner { + const key = KeyPair.fromString(encodedKey); + + return new KeyPairSigner(key); + } + + public async getPublicKey(): Promise { + return this.key.getPublicKey(); + } + + public async signNep413Message(): Promise { + throw new Error("Not implemented!"); + } + + public async signTransaction( + transaction: Transaction + ): Promise<[Uint8Array, SignedTransaction]> { + const pk = this.key.getPublicKey(); + + if (transaction.publicKey !== pk) + throw new Error("The public key doesn't match the signer's key"); + + const message = encodeTransaction(transaction); + const hash = new Uint8Array(sha256(message)); + + const { signature } = this.key.sign(hash); + const signedTx = new SignedTransaction({ + transaction, + signature: new Signature({ + keyType: transaction.publicKey.ed25519Key + ? KeyType.ED25519 + : KeyType.SECP256K1, + data: signature, + }), + }); + + return [hash, signedTx]; + } + + public async signDelegate( + delegateAction: DelegateAction + ): Promise<[Uint8Array, SignedDelegate]> { + const pk = this.key.getPublicKey(); + + if (delegateAction.publicKey !== pk) + throw new Error("The public key doesn't match the signer's key"); + + const message = encodeDelegateAction(delegateAction); + const hash = new Uint8Array(sha256(message)); + + const { signature } = this.key.sign(message); + const signedDelegate = new SignedDelegate({ + delegateAction, + signature: new Signature({ + keyType: pk.keyType, + data: signature, + }), + }); + + return [hash, signedDelegate]; + } +} diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index c0e8dbceaf..158d6263bf 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,29 +1,52 @@ -import { Signature, PublicKey, KeyType } from '@near-js/crypto'; +import { Signature, PublicKey } from '@near-js/crypto'; +import { + DelegateAction, + SignedDelegate, + SignedTransaction, + Transaction, +} from '@near-js/transactions'; + +export interface SignMessageParams { + message: string; // The message that wants to be transmitted. + recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com"). + nonce: Uint8Array; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS). + callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`. + state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes. +} + +export interface SignedMessage { + accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near") + publicKey: PublicKey; // The public counterpart of the key used to sign, expressed as a string with format ":" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y") + signature: Signature; // The base64 representation of the signature. + state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The same state passed in SignMessageParams. +} /** * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. */ export abstract class Signer { - /** - * Creates new key and returns public key. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) + * Returns public key for given signer */ - abstract createKey(accountId: string, networkId?: string, keyType?: KeyType): Promise; + public abstract getPublicKey(): Promise; /** - * Returns public key for given account / network. - * @param accountId accountId to retrieve from. - * @param networkId The targeted network. (ex. default, betanet, etc…) + * Signs given message according to NEP-413 requirements + * @see https://github.com/near/NEPs/blob/master/neps/nep-0413.md + * + * @param params + * @param accountId */ - abstract getPublicKey(accountId?: string, networkId?: string): Promise; + public abstract signNep413Message( + params: SignMessageParams, + accountId: string + ): Promise; - /** - * Signs given message, by first hashing with sha256. - * @param message message to sign. - * @param accountId accountId to use for signing. - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ - abstract signMessage(message: Uint8Array, accountId?: string, networkId?: string): Promise; + public abstract signTransaction( + transaction: Transaction + ): Promise<[Uint8Array, SignedTransaction]>; + + public abstract signDelegate( + delegateAction: DelegateAction + ): Promise<[Uint8Array, SignedDelegate]>; } diff --git a/packages/transactions/package.json b/packages/transactions/package.json index ef1d35176d..fe83164b5c 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -17,7 +17,6 @@ "license": "ISC", "dependencies": { "@near-js/crypto": "workspace:*", - "@near-js/signers": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", "@noble/hashes": "1.7.1", diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts index fd51c2faf3..8228281e09 100644 --- a/packages/transactions/src/sign.ts +++ b/packages/transactions/src/sign.ts @@ -1,10 +1,8 @@ -import { Signer } from '@near-js/signers'; import { sha256 } from '@noble/hashes/sha256'; -import { Action, SignedDelegate } from './actions'; -import { createTransaction } from './create_transaction'; +import { SignedDelegate } from './actions'; import type { DelegateAction } from './delegate'; -import { encodeDelegateAction, encodeTransaction, SignedTransaction, Transaction } from './schema'; +import { encodeDelegateAction } from './schema'; import { Signature } from './signature'; import { KeyType } from '@near-js/crypto'; @@ -22,38 +20,6 @@ export interface SignedDelegateWithHash { signedDelegateAction: SignedDelegate; } -/** - * Signs a given transaction from an account with given keys, applied to the given network - * @param transaction The Transaction object to sign - * @param signer The {Signer} object that assists with signing keys - * @param accountId The human-readable NEAR account name - * @param networkId The targeted network. (ex. default, betanet, etc…) - */ -async function signTransactionObject(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]> { - const message = encodeTransaction(transaction); - const hash = new Uint8Array(sha256(message)); - const signature = await signer.signMessage(message, accountId, networkId); - const keyType = transaction.publicKey.ed25519Key ? KeyType.ED25519 : KeyType.SECP256K1; - const signedTx = new SignedTransaction({ - transaction, - signature: new Signature({ keyType, data: signature.signature }) - }); - return [hash, signedTx]; -} - -export async function signTransaction(transaction: Transaction, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; -export async function signTransaction(receiverId: string, nonce: bigint, actions: Action[], blockHash: Uint8Array, signer: Signer, accountId?: string, networkId?: string): Promise<[Uint8Array, SignedTransaction]>; -export async function signTransaction(...args): Promise<[Uint8Array, SignedTransaction]> { - if (args[0].constructor === Transaction) { - const [ transaction, signer, accountId, networkId ] = args; - return signTransactionObject(transaction, signer, accountId, networkId); - } else { - const [ receiverId, nonce, actions, blockHash, signer, accountId, networkId ] = args; - const publicKey = await signer.getPublicKey(accountId, networkId); - const transaction = createTransaction(accountId, publicKey, receiverId, nonce, actions, blockHash); - return signTransactionObject(transaction, signer, accountId, networkId); - } -} /** * Sign a delegate action @@ -61,11 +27,16 @@ export async function signTransaction(...args): Promise<[Uint8Array, SignedTrans * @param options.delegateAction Delegate action to be signed by the meta transaction sender * @param options.signer Signer instance for the meta transaction sender */ -export async function signDelegateAction({ delegateAction, signer }: SignDelegateOptions): Promise { +export async function signDelegateAction({ + delegateAction, + signer, +}: SignDelegateOptions): Promise { const message = encodeDelegateAction(delegateAction); const signature = await signer.sign(message); - const keyType = delegateAction.publicKey.ed25519Key ? KeyType.ED25519 : KeyType.SECP256K1; + const keyType = delegateAction.publicKey.ed25519Key + ? KeyType.ED25519 + : KeyType.SECP256K1; const signedDelegateAction = new SignedDelegate({ delegateAction, signature: new Signature({ diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts index 30bef48c08..748cbc173b 100644 --- a/packages/wallet-account/src/near.ts +++ b/packages/wallet-account/src/near.ts @@ -115,7 +115,7 @@ export class Near { // TODO: figure out better way of specifiying initial balance. // Hardcoded number below must be enough to pay the gas cost to dev-deploy with near-shell for multiple times const initialBalance = config.initialBalance ? BigInt(config.initialBalance) : 500000000000000000000000000n; - this.accountCreator = new LocalAccountCreator(new Account(this.connection, config.masterAccount), initialBalance); + this.accountCreator = new LocalAccountCreator(new Account(config.masterAccount, this.connection.provider, this.connection.signer), initialBalance); } else if (config.helperUrl) { this.accountCreator = new UrlAccountCreator(this.connection, config.helperUrl); } else { @@ -127,7 +127,7 @@ export class Near { * @param accountId near accountId used to interact with the network. */ async account(accountId: string): Promise { - const account = new Account(this.connection, accountId); + const account = new Account(accountId, this.connection.provider, this.connection.signer); return account; } @@ -145,6 +145,6 @@ export class Near { throw new Error('Must specify account creator, either via masterAccount or helperUrl configuration settings.'); } await this.accountCreator.createAccount(accountId, publicKey); - return new Account(this.connection, accountId); + return new Account(accountId, this.connection.provider, this.connection.signer); } } diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index ca60eed4b0..e818e46d0c 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -11,8 +11,8 @@ import { SignAndSendTransactionOptions, } from '@near-js/accounts'; import { KeyPair, PublicKey } from '@near-js/crypto'; -import { KeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; +import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; +import { KeyPairSigner } from '@near-js/signers'; import { FinalExecutionOutcome } from '@near-js/types'; import { baseDecode } from '@near-js/utils'; import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transactions'; @@ -115,7 +115,11 @@ export class WalletConnection { this._networkId = near.config.networkId; this._walletBaseUrl = near.config.walletUrl; appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; - this._keyStore = (near.connection.signer as InMemorySigner).keyStore; + // @ts-expect-error keyPair isn't public + const keyPair = (near.connection.signer as KeyPairSigner).keyPair as KeyPair; + const keyStore = new InMemoryKeyStore(); + keyStore.setKey(near.connection.networkId, this._authData.accountId, keyPair); + this._keyStore = keyStore; this._authData = authData || { allKeys: [] }; this._authDataKey = authDataKey; if (!this.isSignedIn()) { @@ -332,7 +336,7 @@ export class ConnectedWalletAccount extends Account { walletConnection: WalletConnection; constructor(walletConnection: WalletConnection, connection: Connection, accountId: string) { - super(connection, accountId); + super(accountId, connection.provider, connection.signer); this.walletConnection = walletConnection; } @@ -348,7 +352,7 @@ export class ConnectedWalletAccount extends Account { * @param options.walletCallbackUrl URL to redirect upon completion of the wallet signing process. Default: current URL. */ async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { - const localKey = await this.connection.signer.getPublicKey(this.accountId, this.connection.networkId); + const localKey = await this.signer.getPublicKey(); let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); if (!accessKey) { throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); @@ -366,7 +370,7 @@ export class ConnectedWalletAccount extends Account { } } - const block = await this.connection.provider.block({ finality: 'final' }); + const block = await this.provider.block({ finality: 'final' }); const blockHash = baseDecode(block.header.hash); const publicKey = PublicKey.from(accessKey.public_key); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c918aea37b..8905709ee1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -562,6 +562,9 @@ importers: '@near-js/keystores': specifier: workspace:* version: link:../keystores + '@near-js/transactions': + specifier: workspace:* + version: link:../transactions '@noble/hashes': specifier: 1.7.1 version: 1.7.1 @@ -593,9 +596,6 @@ importers: '@near-js/crypto': specifier: workspace:* version: link:../crypto - '@near-js/signers': - specifier: workspace:* - version: link:../signers '@near-js/types': specifier: workspace:* version: link:../types From 7903ff7d104e3189d9889027355d3120b0cf970c Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 5 Apr 2025 10:05:52 +0200 Subject: [PATCH 05/57] test: update tests to align with changes in Account and Signer --- .../accounts/test/account.access_key.test.ts | 45 +++------ packages/accounts/test/account.test.ts | 53 ++++++---- .../accounts/test/account_multisig.test.ts | 10 +- packages/accounts/test/contract.test.ts | 57 ++++++----- packages/accounts/test/test-utils.js | 37 ++++--- packages/signers/test/key_pair_signer.test.ts | 98 +++++++++++++++++++ packages/signers/test/signer.test.ts | 14 --- packages/transactions/test/serialize.test.ts | 53 ++-------- .../wallet-account/test/wallet_account.ts | 20 ++-- .../test/wallet_accounts.test.ts | 5 +- 10 files changed, 233 insertions(+), 159 deletions(-) create mode 100644 packages/signers/test/key_pair_signer.test.ts delete mode 100644 packages/signers/test/signer.test.ts diff --git a/packages/accounts/test/account.access_key.test.ts b/packages/accounts/test/account.access_key.test.ts index cbf801d866..c454a0bbad 100644 --- a/packages/accounts/test/account.access_key.test.ts +++ b/packages/accounts/test/account.access_key.test.ts @@ -1,10 +1,12 @@ import { beforeAll, beforeEach, expect, jest, test } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; -import { createAccount, deployContract, generateUniqueString, networkId, setUpTestConnection } from './test-utils'; +import { createAccount, deployContract, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Account } from '../src'; +import { KeyPairSigner } from '@near-js/signers'; let nearjs; -let workingAccount; +let workingAccount: Account; let contractId; let contract; @@ -16,7 +18,6 @@ beforeAll(async () => { beforeEach(async () => { try { - contractId = generateUniqueString('test'); workingAccount = await createAccount(nearjs); contract = await deployContract(nearjs.accountCreator.masterAccount, contractId); @@ -27,12 +28,14 @@ beforeEach(async () => { test('make function call using access key', async() => { const keyPair = KeyPair.fromRandom('ed25519'); - await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', '2000000000000000000000000'); + await workingAccount.addKey(keyPair.getPublicKey(), contractId, '', 2000000000000000000000000n); - // Override in the key store the workingAccount key to the given access key. - await nearjs.connection.signer.keyStore.setKey(networkId, workingAccount.accountId, keyPair); const setCallValue = generateUniqueString('setCallPrefix'); - await contract.setValue({ args: { value: setCallValue } }); + await contract.setValue({ + // Override signer in the workingAccount to the given access key. + signerAccount: workingAccount.withSigner(new KeyPairSigner(keyPair)), + args: { value: setCallValue }, + }); expect(await contract.getValue()).toEqual(setCallValue); }); @@ -41,15 +44,16 @@ test('remove access key no longer works', async() => { const publicKey = keyPair.getPublicKey(); await nearjs.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000); await nearjs.accountCreator.masterAccount.deleteKey(publicKey); - // Override in the key store the workingAccount key to the given access key. - await nearjs.connection.signer.keyStore.setKey(networkId, nearjs.accountCreator.masterAccount.accountId, keyPair); + // Override account in the Contract to the masterAccount with the given access key. + contract.account = (nearjs.accountCreator.masterAccount as Account).withSigner(new KeyPairSigner(keyPair)); + let failed = true; try { await contract.setValue({ args: { value: 'test' } }); failed = false; } catch (e) { - expect(e.message).toEqual(`Can not sign transactions for account ${nearjs.accountCreator.masterAccount.accountId} on network ${networkId}, no matching key pair exists for this account`); - expect(e.type).toEqual('KeyNotFound'); + expect(e.message).toEqual(`Can't complete the action because access key ${keyPair.getPublicKey().toString()} doesn't exist`); + expect(e.type).toEqual('AccessKeyDoesNotExist'); } if (!failed) { @@ -94,22 +98,5 @@ test('loading account after adding a full key', async() => { expect(accessKeys.length).toBe(2); const addedKey = accessKeys.find(item => item.public_key == keyPair.getPublicKey().toString()); expect(addedKey).toBeTruthy(); - expect(addedKey.access_key.permission).toEqual('FullAccess'); + expect(addedKey!.access_key.permission).toEqual('FullAccess'); }); - -test('load invalid key pair', async() => { - // Override in the key store with invalid key pair - await nearjs.connection.signer.keyStore.setKey(networkId, nearjs.accountCreator.masterAccount.accountId, ''); - let failed = true; - try { - await contract.setValue({ args: { value: 'test' } }); - failed = false; - } catch (e) { - expect(e.message).toEqual(`no matching key pair found in ${nearjs.connection.signer}`); - expect(e.type).toEqual('PublicKeyNotFound'); - } - - if (!failed) { - throw new Error('should throw an error'); - } -}); \ No newline at end of file diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index d8ffd908d1..a2dfe49159 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -1,5 +1,5 @@ import { afterAll, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; -import { KeyType } from '@near-js/crypto'; +import { KeyPair, KeyType } from '@near-js/crypto'; import { getTransactionLastResult, Logger } from '@near-js/utils'; import { actionCreators } from '@near-js/transactions'; import { BlockResult, TypedError } from '@near-js/types'; @@ -7,6 +7,7 @@ import * as fs from 'fs'; import { Account, Contract } from '../src'; import { createAccount, generateUniqueString, HELLO_WASM_PATH, HELLO_WASM_BALANCE, networkId, setUpTestConnection } from './test-utils'; +import { InMemoryKeyStore } from '@near-js/keystores'; let nearjs; let workingAccount; @@ -33,7 +34,7 @@ test('create account and then view account returns the created account', async ( const { amount } = await workingAccount.state(); const newAmount = BigInt(amount) / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); - const newAccount = new Account(nearjs.connection, newAccountName); + const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.state(); expect(state.amount).toEqual(newAmount.toString()); }); @@ -44,7 +45,7 @@ test('create account with a secp256k1 key and then view account returns the crea const { amount } = await workingAccount.state(); const newAmount = BigInt(amount) / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); - const newAccount = new Account(nearjs.connection, newAccountName); + const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.state(); expect(state.amount).toEqual(newAmount.toString()); }); @@ -92,7 +93,7 @@ test('multiple parallel transactions', async () => { const PARALLEL_NUMBER = 5; // @ts-expect-error test input await Promise.all(new Array(PARALLEL_NUMBER).fill().map(async (_, i) => { - const account = new Account(workingAccount.connection, workingAccount.accountId); + const account = new Account(workingAccount.accountId, workingAccount.provider, workingAccount.signer); // NOTE: Need to have different transactions outside of nonce, or they all succeed by being identical // TODO: Check if randomization of exponential back off helps to do more transactions without exceeding retries await account.sendMoney(account.accountId, BigInt(i)); @@ -144,11 +145,13 @@ describe('with deploy contract', () => { let contract; beforeAll(async () => { - const newPublicKey = await nearjs.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + await (nearjs.keyStore as InMemoryKeyStore).setKey(networkId, contractId, keyPair); + const newPublicKey = keyPair.getPublicKey(); const data = fs.readFileSync(HELLO_WASM_PATH); await nearjs.accountCreator.masterAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); // @ts-expect-error test input - contract = new Contract(nearjs.accountCreator.masterAccount, contractId, { + contract = new Contract(nearjs.connection, contractId, { viewMethods: ['hello', 'getValue', 'returnHiWithLogs'], changeMethods: ['setValue', 'generateLogs', 'triggerAssert', 'testSetRemove', 'crossContract'] }); @@ -170,6 +173,7 @@ describe('with deploy contract', () => { test('cross-contact assertion and panic', async () => { await expect(contract.crossContract({ + signerAccount: workingAccount, args: {}, gas: 300000000000000 })).rejects.toThrow(/Smart contract panicked: expected to fail./); @@ -214,7 +218,7 @@ describe('with deploy contract', () => { args: { value: setCallValue } }); - const contractAccount = new Account(nearjs.connection, contractId); + const contractAccount = new Account(contractId, nearjs.connection.provider, nearjs.connection.signer); const state = (await contractAccount.viewState('')).map(({ key, value }) => [key.toString('utf-8'), value.toString('utf-8')]); expect(state).toEqual([['name', setCallValue]]); }); @@ -234,14 +238,14 @@ describe('with deploy contract', () => { expect(result).toEqual('hello trex'); const setCallValue = generateUniqueString('setCallPrefix'); - const result2 = await contract.setValue({ args: { value: setCallValue } }); + const result2 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue } }); expect(result2).toEqual(setCallValue); expect(await contract.getValue()).toEqual(setCallValue); }); test('view function calls by block Id and finality', async() => { const setCallValue1 = generateUniqueString('setCallPrefix'); - const result1 = await contract.setValue({ args: { value: setCallValue1 } }); + const result1 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue1 } }); expect(result1).toEqual(setCallValue1); expect(await contract.getValue()).toEqual(setCallValue1); @@ -256,7 +260,7 @@ describe('with deploy contract', () => { methodName: 'getValue' })).toEqual(setCallValue1); - const block1 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const block1 = await workingAccount.provider.block({ finality: 'optimistic' }); const blockHash1 = block1.header.hash; const blockIndex1 = block1.header.height; @@ -273,7 +277,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue1); const setCallValue2 = generateUniqueString('setCallPrefix'); - const result2 = await contract.setValue({ args: { value: setCallValue2 } }); + const result2 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue2 } }); expect(result2).toEqual(setCallValue2); expect(await contract.getValue()).toEqual(setCallValue2); @@ -301,7 +305,7 @@ describe('with deploy contract', () => { blockQuery: { blockId: blockIndex1 }, })).toEqual(setCallValue1); - const block2 = await workingAccount.connection.provider.block({ finality: 'optimistic' }); + const block2 = await workingAccount.provider.block({ finality: 'optimistic' }); const blockHash2 = block2.header.hash; const blockIndex2 = block2.header.height; @@ -321,6 +325,7 @@ describe('with deploy contract', () => { test('make function calls via contract with gas', async() => { const setCallValue = generateUniqueString('setCallPrefix'); const result2 = await contract.setValue({ + signerAccount: workingAccount, args: { value: setCallValue }, gas: 1000000 * 1000000 }); @@ -329,7 +334,10 @@ describe('with deploy contract', () => { }); test('can get logs from method result', async () => { - await contract.generateLogs(); + await contract.generateLogs({ + signerAccount: workingAccount, + args: {} + }); expect(logs.length).toEqual(3); expect(logs[0].substr(0, 8)).toEqual('Receipt:'); expect(logs.slice(1)).toEqual([`\tLog [${contractId}]: log1`, `\tLog [${contractId}]: log2`]); @@ -342,20 +350,24 @@ describe('with deploy contract', () => { }); test('can get assert message from method result', async () => { - await expect(contract.triggerAssert()).rejects.toThrow(/Smart contract panicked: expected to fail.+/); + await expect(() => contract.triggerAssert({ + signerAccount: workingAccount, + args: {} + })).rejects.toThrow(/Smart contract panicked: expected to fail.+/); expect(logs[1]).toEqual(`\tLog [${contractId}]: log before assert`); expect(logs[2]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ABORT: expected to fail, filename: \\"assembly/index.ts" line: \\d+ col: \\d+$`)); }); test('test set/remove', async () => { await contract.testSetRemove({ + signerAccount: workingAccount, args: { value: '123' } }); }); test('can have view methods only', async () => { // @ts-expect-error test input - const contract: any = new Contract(workingAccount, contractId, { + const contract: any = new Contract(nearjs.connection, contractId, { viewMethods: ['hello'], }); expect(await contract.hello({ name: 'world' })).toEqual('hello world'); @@ -363,10 +375,11 @@ describe('with deploy contract', () => { test('can have change methods only', async () => { // @ts-expect-error test input - const contract: any = new Contract(workingAccount, contractId, { + const contract: any = new Contract(nearjs.connection, contractId, { changeMethods: ['hello'], }); expect(await contract.hello({ + signerAccount: workingAccount, args: { name: 'world' } })).toEqual('hello world'); }); @@ -428,7 +441,7 @@ describe('with deploy contract', () => { }, }; - const account = new Account(mockConnection, 'test.near'); + const account = new Account('test.near', mockConnection.provider, mockConnection.signer); // mock internal functions that are being used on getActiveDelegatedStakeBalance account.viewFunction = async ({ methodName, ...args }) => { if (methodName === 'get_account_total_balance') { @@ -441,7 +454,7 @@ describe('with deploy contract', () => { return await account.viewFunction({ methodName, ...args }); } }; - account.connection.provider.block = async () => { + account.provider.block = async () => { return Promise.resolve({ header: { hash: 'dontcare' } } as BlockResult); }; const result = await account.getActiveDelegatedStakeBalance(); @@ -478,7 +491,7 @@ describe('with deploy contract', () => { }, }; - const account = new Account(mockConnection, 'test.near'); + const account = new Account('test.near', mockConnection.provider, mockConnection.signer); // mock internal functions that are being used on getActiveDelegatedStakeBalance account.viewFunction = async ({ methodName, ...args }) => { if (methodName === 'get_account_total_balance') { @@ -491,7 +504,7 @@ describe('with deploy contract', () => { return await account.viewFunction({ methodName, ...args }); } }; - account.connection.provider.block = async () => { + account.provider.block = async () => { return Promise.resolve({ header: { hash: 'dontcare' } } as BlockResult); }; diff --git a/packages/accounts/test/account_multisig.test.ts b/packages/accounts/test/account_multisig.test.ts index 996a32e5db..2320a08fb4 100644 --- a/packages/accounts/test/account_multisig.test.ts +++ b/packages/accounts/test/account_multisig.test.ts @@ -1,7 +1,7 @@ import { beforeAll, describe, expect, jest, test } from '@jest/globals'; import { parseNearAmount } from '@near-js/utils'; import { KeyPair } from '@near-js/crypto'; -import { InMemorySigner } from '@near-js/signers'; +import { KeyPairSigner } from '@near-js/signers'; import { actionCreators } from '@near-js/transactions'; import * as fs from 'fs'; import semver from 'semver'; @@ -20,7 +20,7 @@ const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) = // modifiers to functions replaces contract helper (CH) const { accountId } = account; const keys = await account.getAccessKeys(); - const account2fa: any = new Account2FA(nearjs.connection, accountId, { + const account2fa: any = new Account2FA(account.getConnection(), accountId, { // skip this (not using CH) getCode: () => {}, sendCode: () => {}, @@ -28,17 +28,13 @@ const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) = verifyCode: () => ({ }), // TODO: Is there any content needed in result? onAddRequestResult: async () => { const { requestId } = account2fa.getRequest(); - // set confirmKey as signer - const originalSigner = nearjs.connection.signer; - nearjs.connection.signer = await InMemorySigner.fromKeyPair(nearjs.connection.networkId, accountId, account2fa.confirmKey); // 2nd confirmation signing with confirmKey from Account instance - await account.signAndSendTransaction({ + await account.withSigner(new KeyPairSigner(account2fa.confirmKey)).signAndSendTransaction({ receiverId: accountId, actions: [ functionCall('confirm', { request_id: requestId }, MULTISIG_GAS, MULTISIG_DEPOSIT) ] }); - nearjs.connection.signer = originalSigner; } }); account2fa.confirmKey = KeyPair.fromRandom('ed25519'); diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index bcee4ad006..0b00385f8d 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -119,12 +119,18 @@ describe('local view execution', () => { nearjs = await setUpTestConnection(); contract = await deployContractGuestBook(nearjs.accountCreator.masterAccount, generateUniqueString('guestbook')); - await contract.add_message({ text: 'first message' }); - await contract.add_message({ text: 'second message' }); + await contract.add_message({ + signerAccount: nearjs.accountCreator.masterAccount, + args: { text: "first message" }, + }); + await contract.add_message({ + signerAccount: nearjs.accountCreator.masterAccount, + args: { text: "second message" }, + }); - const block = await contract.account.connection.provider.block({ finality: 'optimistic' }); + const block = await contract.connection.provider.block({ finality: 'optimistic' }); - contract.account.connection.provider.query = jest.fn(contract.account.connection.provider.query); + contract.connection.provider.query = jest.fn(contract.connection.provider.query); blockQuery = { blockId: block.header.height }; }); @@ -135,7 +141,7 @@ describe('local view execution', () => { test('calls total_messages() function using RPC provider', async () => { const totalMessages = await contract.total_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).toHaveBeenCalledWith({ + expect(contract.connection.provider.query).toHaveBeenCalledWith({ request_type: 'view_code', account_id: contract.contractId, ...blockQuery, @@ -146,14 +152,14 @@ describe('local view execution', () => { test('calls total_messages() function using cache data', async () => { const totalMessages = await contract.total_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).not.toHaveBeenCalled(); + expect(contract.connection.provider.query).not.toHaveBeenCalled(); expect(totalMessages).toBe(2); }); test('calls get_messages() function using cache data', async () => { const messages = await contract.get_messages({}, { blockQuery }); - expect(contract.account.connection.provider.query).not.toHaveBeenCalled(); + expect(contract.connection.provider.query).not.toHaveBeenCalled(); expect(messages.length).toBe(2); expect(messages[0].text).toEqual('first message'); expect(messages[1].text).toEqual('second message'); @@ -161,7 +167,7 @@ describe('local view execution', () => { test('local execution fails and fallbacks to normal RPC call', async () => { // @ts-expect-error test input - const _contract: any = new Contract(contract.account, contract.contractId, { viewMethods: ['get_msg'], useLocalViewExecution: true }); + const _contract: any = new Contract(nearjs.accountCreator.masterAccount, contract.contractId, { viewMethods: ['get_msg'], useLocalViewExecution: true }); _contract.account.viewFunction = jest.fn(_contract.account.viewFunction); try { @@ -177,7 +183,7 @@ describe('local view execution', () => { }); }); -describe('contract without account', () => { +describe("contract without account", () => { let nearjs; let contract; @@ -185,31 +191,34 @@ describe('contract without account', () => { beforeAll(async () => { nearjs = await setUpTestConnection(); - const contractId = generateUniqueString('guestbook'); - await deployContractGuestBook(nearjs.accountCreator.masterAccount, contractId); + const contractId = generateUniqueString("guestbook"); + await deployContractGuestBook( + nearjs.accountCreator.masterAccount, + contractId + ); // @ts-expect-error test input contract = new Contract(nearjs.connection, contractId, { - viewMethods: ['total_messages', 'get_messages'], - changeMethods: ['add_message'], + viewMethods: ["total_messages", "get_messages"], + changeMethods: ["add_message"], }); }); - test('view & change methods work', async () => { + test("view & change methods work", async () => { const totalMessagesBefore = await contract.total_messages({}); expect(totalMessagesBefore).toBe(0); await contract.add_message({ signerAccount: nearjs.accountCreator.masterAccount, args: { - text: 'first message', - } + text: "first message", + }, }); await contract.add_message({ signerAccount: nearjs.accountCreator.masterAccount, args: { - text: 'second message', - } + text: "second message", + }, }); const totalMessagesAfter = await contract.total_messages({}); @@ -217,13 +226,17 @@ describe('contract without account', () => { const messages = await contract.get_messages({}); expect(messages.length).toBe(2); - expect(messages[0].text).toEqual('first message'); - expect(messages[1].text).toEqual('second message'); + expect(messages[0].text).toEqual("first message"); + expect(messages[1].text).toEqual("second message"); }); - test('fails to call add_message() without signerAccount', async () => { + test("fails to call add_message() without signerAccount", async () => { await expect( - contract.add_message({ text: 'third message' }) + contract.add_message({ + args: { + text: "third message", + }, + }) ).rejects.toThrow(/signerAccount must be specified/); }); }); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 98485a59e0..164e836881 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -6,6 +6,7 @@ import path from 'path'; import { Account, AccountMultisig, Contract, Connection, LocalAccountCreator } from '../src'; import Config from './config'; +import { KeyPairSigner } from '@near-js/signers'; Logger.overrideLogger(new ConsoleLogger(['error', 'fatal'])) @@ -68,12 +69,13 @@ export async function setUpTestConnection() { const connection = Connection.fromConfig({ networkId: config.networkId, provider: { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, - signer: { type: 'InMemorySigner', keyStore: config.keyStore }, + signer: { type: 'KeyPairSigner', keyPair: await keyStore.getKey(networkId, config.masterAccount) }, }); return { - accountCreator: new LocalAccountCreator(new Account(connection, config.masterAccount), BigInt('500000000000000000000000000')), + accountCreator: new LocalAccountCreator(new Account(config.masterAccount, connection.provider, connection.signer), BigInt('500000000000000000000000000')), connection, + keyStore }; } @@ -85,16 +87,25 @@ export function generateUniqueString(prefix) { return result + '.test.near'; } -export async function createAccount({ accountCreator, connection }, keyType = KeyType.ED25519) { +export async function createAccount({ accountCreator, connection, keyStore }, keyType = KeyType.ED25519) { const newAccountName = generateUniqueString('test'); - const newPublicKey = await connection.signer.createKey(newAccountName, networkId, keyType); + + const keyPair = KeyPair.fromRandom(Object.values(KeyType)[keyType]); + await keyStore.setKey(networkId, newAccountName, keyPair); + + const newPublicKey = keyPair.getPublicKey(); await accountCreator.createAccount(newAccountName, newPublicKey); - return new Account(connection, newAccountName); + + return new Account(newAccountName, connection.provider, new KeyPairSigner(keyPair)); } -export async function createAccountMultisig({ accountCreator, connection }, options) { +export async function createAccountMultisig({ accountCreator, connection, keyStore }, options) { const newAccountName = generateUniqueString('test'); - const newPublicKey = await connection.signer.createKey(newAccountName, networkId); + + const keyPair = KeyPair.fromRandom('ed25519'); + await keyStore.setKey(networkId, newAccountName, keyPair); + const newPublicKey = keyPair.getPublicKey(); + await accountCreator.createAccount(newAccountName, newPublicKey); // add a confirm key for multisig (contract helper sim) @@ -103,7 +114,7 @@ export async function createAccountMultisig({ accountCreator, connection }, opti const { publicKey } = confirmKeyPair; const accountMultisig = new AccountMultisig(connection, newAccountName, options); accountMultisig.useConfirmKey = async () => { - await connection.signer.setKey(networkId, options.masterAccount, confirmKeyPair); + await keyStore.setKey(networkId, options.masterAccount, confirmKeyPair); }; accountMultisig.getRecoveryMethods = () => ({ data: [] }); accountMultisig.postSignedJson = async (path) => { @@ -119,17 +130,21 @@ export async function createAccountMultisig({ accountCreator, connection }, opti } export async function deployContract(workingAccount, contractId) { - const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + const newPublicKey = keyPair.getPublicKey(); + const data = fs.readFileSync(HELLO_WASM_PATH); await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); return new Contract(workingAccount, contractId, HELLO_WASM_METHODS); } export async function deployContractGuestBook(workingAccount, contractId) { - const newPublicKey = await workingAccount.connection.signer.createKey(contractId, networkId); + const keyPair = KeyPair.fromRandom('ed25519'); + const newPublicKey = keyPair.getPublicKey(); + const data = fs.readFileSync(GUESTBOOK_WASM_PATH); const account = await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); - return new Contract(account, contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); + return new Contract(new Connection('', account.provider, {}, ''), contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); } export function sleep(time) { diff --git a/packages/signers/test/key_pair_signer.test.ts b/packages/signers/test/key_pair_signer.test.ts new file mode 100644 index 0000000000..40245c9f45 --- /dev/null +++ b/packages/signers/test/key_pair_signer.test.ts @@ -0,0 +1,98 @@ +import { expect, test } from "@jest/globals"; +import { TextEncoder } from "util"; + +import { KeyPairSigner } from "../src"; +import { KeyPair, PublicKey } from "@near-js/crypto"; +import { createTransaction, encodeTransaction, actionCreators, decodeSignedTransaction } from "@near-js/transactions"; + +global.TextEncoder = TextEncoder; + +test("test throws if transaction gets signed with different public key", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" + ) + ); + + const transaction = createTransaction( + "signer", + // the key is different from what signer operates + KeyPair.fromString( + "ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV" + ).getPublicKey(), + "receiver", + 1n, + [], + new Uint8Array(new Array(32)) + ); + + await expect(() => signer.signTransaction(transaction)).rejects.toThrow( + /The public key doesn\'t match the signer\'s key/ + ); +}); + +test("test transaction gets signed with relevant public key", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" + ) + ); + + const transaction = createTransaction( + "signer", + await signer.getPublicKey(), + "receiver", + 1n, + [], + new Uint8Array(new Array(32)) + ); + + const [hash, { signature }] = await signer.signTransaction(transaction); + + expect(Buffer.from(hash).toString("hex")).toBe( + "2571e3539ab5556e39441913e66abd07e634fb9850434006a719306100e641a2" + ); + + expect(Buffer.from(signature.signature.data).toString("hex")).toBe( + "bfe2858d227e3116076a8e5ea9c5bef923c7755f19f0137d1acd9bb67973f1b8a7f83dfc0be23e307e106c8807eaa6e14c0fcb46c42acdf293c4a6a81a27fc05" + ); +}); + +test("serialize and sign transfer tx object", async () => { + const keyPair = KeyPair.fromString( + "ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv" + ); + const signer = new KeyPairSigner(keyPair); + + const actions = [actionCreators.transfer(1n)]; + const blockHash = new Uint8Array([ + 15, 164, 115, 253, 38, 144, 29, 242, 150, 190, 106, 220, 76, 196, 223, + 52, 208, 64, 239, 162, 67, 82, 36, 182, 152, 105, 16, 230, 48, 194, 254, + 246, + ]); + const transaction = createTransaction( + "test.near", + PublicKey.fromString("ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"), + "whatever.near", + 1, + actions, + blockHash + ); + + const [, signedTx] = await signer.signTransaction(transaction); + + expect( + Buffer.from(signedTx.signature.ed25519Signature!.data).toString( + "base64" + ) + ).toEqual( + "lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ==" + ); + const serialized = encodeTransaction(signedTx); + expect(Buffer.from(serialized).toString("hex")).toEqual( + "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01" + ); + + const deserialized = decodeSignedTransaction(serialized); + expect(encodeTransaction(deserialized)).toEqual(serialized); +}); \ No newline at end of file diff --git a/packages/signers/test/signer.test.ts b/packages/signers/test/signer.test.ts deleted file mode 100644 index f810f966e3..0000000000 --- a/packages/signers/test/signer.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { expect, test } from '@jest/globals'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { TextEncoder } from 'util'; - -import { InMemorySigner } from '../src'; - -global.TextEncoder = TextEncoder; - -test('test no key', async () => { - const signer = new InMemorySigner(new InMemoryKeyStore()); - // @ts-expect-error test input - await expect(signer.signMessage('message', 'user', 'network')) - .rejects.toThrow(/Key for user not found in network/); -}); diff --git a/packages/transactions/test/serialize.test.ts b/packages/transactions/test/serialize.test.ts index 1f5f801119..599520f865 100644 --- a/packages/transactions/test/serialize.test.ts +++ b/packages/transactions/test/serialize.test.ts @@ -1,8 +1,6 @@ import { describe, expect, test } from '@jest/globals'; import { KeyPair, PublicKey } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; -import { baseDecode, baseEncode } from '@near-js/utils'; +import { baseDecode } from '@near-js/utils'; import { deserialize, serialize } from 'borsh'; import * as fs from 'fs'; @@ -10,11 +8,9 @@ import * as fs from 'fs'; import { actionCreators, createTransaction, - decodeSignedTransaction, decodeTransaction, encodeTransaction, SCHEMA, - signTransaction, } from '../src'; const { @@ -61,25 +57,22 @@ test('deserialize delegate', async () => { expect(String(delegateAction.nonce)).toEqual('158895108000003'); }); -test('serialize and sign multi-action tx', async () => { - const keyStore = new InMemoryKeyStore(); +test('serialize multi-action tx', async () => { const keyPair = KeyPair.fromString('ed25519:2wyRcSwSuHtRVmkMCGjPwnzZmQLeXLzLLyED1NDMt4BjnKgQL6tF85yBx6Jr26D2dUNeC716RBoTxntVHsegogYw'); - await keyStore.setKey('test', 'test.near', keyPair); - // @ts-expect-error test input - const publicKey = keyPair.publicKey; + const publicKey = keyPair.getPublicKey(); const actions = [ createAccount(), deployContract(new Uint8Array([1, 2, 3])), functionCall('qqq', new Uint8Array([1, 2, 3]), 1000n, 1000000n), transfer(123n), stake(1000000n, publicKey), - addKey(publicKey, functionCallAccessKey('zzz', ['www'], null)), + addKey(publicKey, functionCallAccessKey('zzz', ['www'], undefined)), deleteKey(publicKey), deleteAccount('123') ]; const blockHash = baseDecode('244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'); - const [hash, { transaction }] = await signTransaction('123', 1n, actions, blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); - expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); + const transaction = createTransaction('test.near', publicKey, '123', 1n, actions, blockHash); + // expect(baseEncode(hash)).toEqual('Fo3MJ9XzKjnKuDuQKhDAC6fra5H2UWawRejFSEpPNk3Y'); const serialized = Buffer.from(serialize(SCHEMA.Transaction, transaction)); expect(serialized.toString('hex')).toEqual('09000000746573742e6e656172000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80100000000000000030000003132330fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef608000000000103000000010203020300000071717103000000010203e80300000000000040420f00000000000000000000000000037b0000000000000000000000000000000440420f00000000000000000000000000000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef805000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef800000000000000000000030000007a7a7a010000000300000077777706000f56a5f028dfc089ec7c39c1183b321b4d8f89ba5bec9e1762803cc2491f6ef80703000000313233'); }); @@ -108,40 +101,6 @@ test('serialize transfer tx', async () => { expect(encodeTransaction(deserialized)).toEqual(serialized); }); -async function createKeyStore() { - const keyStore = new InMemoryKeyStore(); - const keyPair = KeyPair.fromString('ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv'); - await keyStore.setKey('test', 'test.near', keyPair); - return keyStore; -} - -async function verifySignedTransferTx(signedTx) { - expect(Buffer.from(signedTx.signature.ed25519Signature.data).toString('base64')).toEqual('lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ=='); - const serialized = encodeTransaction(signedTx); - expect(Buffer.from(serialized).toString('hex')).toEqual('09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01'); - - const deserialized = decodeSignedTransaction(serialized); - expect(encodeTransaction(deserialized)).toEqual(serialized); -} - -test('serialize and sign transfer tx', async () => { - const transaction = createTransferTx(); - const keyStore = await createKeyStore(); - - const [, signedTx] = await signTransaction(transaction.receiverId, transaction.nonce, transaction.actions, transaction.blockHash, new InMemorySigner(keyStore), 'test.near', 'test'); - - verifySignedTransferTx(signedTx); -}); - -test('serialize and sign transfer tx object', async () => { - const transaction = createTransferTx(); - const keyStore = await createKeyStore(); - - const [, signedTx] = await signTransaction(transaction, new InMemorySigner(keyStore), 'test.near', 'test'); - - verifySignedTransferTx(signedTx); -}); - describe('roundtrip test', () => { const dataDir = './test/data'; const testFiles = fs.readdirSync(dataDir); diff --git a/packages/wallet-account/test/wallet_account.ts b/packages/wallet-account/test/wallet_account.ts index 820533eed4..ab88d92cb6 100644 --- a/packages/wallet-account/test/wallet_account.ts +++ b/packages/wallet-account/test/wallet_account.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; import { InMemoryKeyStore } from '@near-js/keystores'; -import { InMemorySigner } from '@near-js/signers'; +import { KeyPairSigner } from '@near-js/signers'; import { actionCreators } from '@near-js/transactions'; import localStorage from 'localstorage-memory'; import { WalletConnection } from '../src'; @@ -34,10 +34,11 @@ export const createTransactions = () => { networkId: 'networkId', contractName: 'contractId', walletUrl: 'http://example.com/wallet', + keyStore: keyStore }, connection: { networkId: 'networkId', - signer: new InMemorySigner(keyStore) + signer: new KeyPairSigner(KeyPair.fromRandom('ed25519')) }, account() { return { @@ -72,7 +73,7 @@ export const createTransactions = () => { const BLOCK_HASH = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; - function setupWalletConnectionForSigning({ allKeys, accountAccessKeys }) { + function setupWalletConnectionForSigning({ allKeys, accountAccessKeys, signer }) { walletConnection._authData = { allKeys: allKeys, accountId: 'signer.near' @@ -88,7 +89,7 @@ export const createTransactions = () => { if (params.request_type === 'view_access_key' && params.account_id === 'signer.near') { for (let accessKey of accountAccessKeys) { if (accessKey.public_key === params.public_key) { - return accessKey; + return accessKey.access_key; } } } @@ -109,6 +110,8 @@ export const createTransactions = () => { }; } }; + + nearFake.connection.signer = signer; } describe('requests transaction signing with 2fa access key', () => { @@ -131,7 +134,8 @@ export const createTransactions = () => { }, // @ts-ignore public_key: localKeyPair.publicKey.toString() - }] + }], + signer: new KeyPairSigner(localKeyPair) }); await keyStore.setKey('networkId', 'signer.near', localKeyPair); }); @@ -168,7 +172,8 @@ export const createTransactions = () => { }, // @ts-ignore public_key: localKeyPair.publicKey.toString() - }] + }], + signer: new KeyPairSigner(localKeyPair) }); await keyStore.setKey('networkId', 'signer.near', localKeyPair); }); @@ -201,7 +206,8 @@ export const createTransactions = () => { }, // @ts-ignore public_key: localKeyPair.publicKey.toString() - }] + }], + signer: new KeyPairSigner(localKeyPair) }); await keyStore.setKey('networkId', 'signer.near', localKeyPair); }); diff --git a/packages/wallet-account/test/wallet_accounts.test.ts b/packages/wallet-account/test/wallet_accounts.test.ts index e4f610a842..08d703bb35 100644 --- a/packages/wallet-account/test/wallet_accounts.test.ts +++ b/packages/wallet-account/test/wallet_accounts.test.ts @@ -2,7 +2,7 @@ import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; import { KeyPair, PublicKey } from '@near-js/crypto'; import { InMemoryKeyStore } from '@near-js/keystores'; import { baseDecode } from '@near-js/utils'; -import { InMemorySigner } from '@near-js/signers'; +import { KeyPairSigner } from '@near-js/signers'; import { actionCreators, createTransaction, SCHEMA } from '@near-js/transactions'; import { deserialize } from 'borsh'; import localStorage from 'localstorage-memory'; @@ -35,10 +35,11 @@ describe("Wallet account tests", () => { networkId: 'networkId', contractName: 'contractId', walletUrl: 'http://example.com/wallet', + keyStore: keyStore }, connection: { networkId: 'networkId', - signer: new InMemorySigner(keyStore) + signer: new KeyPairSigner(KeyPair.fromRandom('ed25519')) }, account() { return { From 149c9857d2f97cbea3549f1896129c3e1271213b Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 5 Apr 2025 10:15:27 +0200 Subject: [PATCH 06/57] fix: update minor things to pass tests --- packages/signers/src/key_pair_signer.ts | 4 ++-- packages/wallet-account/src/wallet_account.ts | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts index a26f6c5a99..1a08b5dfde 100644 --- a/packages/signers/src/key_pair_signer.ts +++ b/packages/signers/src/key_pair_signer.ts @@ -42,7 +42,7 @@ export class KeyPairSigner extends Signer { ): Promise<[Uint8Array, SignedTransaction]> { const pk = this.key.getPublicKey(); - if (transaction.publicKey !== pk) + if (transaction.publicKey.toString() !== pk.toString()) throw new Error("The public key doesn't match the signer's key"); const message = encodeTransaction(transaction); @@ -67,7 +67,7 @@ export class KeyPairSigner extends Signer { ): Promise<[Uint8Array, SignedDelegate]> { const pk = this.key.getPublicKey(); - if (delegateAction.publicKey !== pk) + if (delegateAction.publicKey.toString() !== pk.toString()) throw new Error("The public key doesn't match the signer's key"); const message = encodeDelegateAction(delegateAction); diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts index e818e46d0c..c1c5a97325 100644 --- a/packages/wallet-account/src/wallet_account.ts +++ b/packages/wallet-account/src/wallet_account.ts @@ -12,13 +12,12 @@ import { } from '@near-js/accounts'; import { KeyPair, PublicKey } from '@near-js/crypto'; import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; -import { KeyPairSigner } from '@near-js/signers'; import { FinalExecutionOutcome } from '@near-js/types'; import { baseDecode } from '@near-js/utils'; import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transactions'; import { serialize } from 'borsh'; -import { Near } from './near'; +import { Near, NearConfig } from './near'; const LOGIN_WALLET_URL_SUFFIX = '/login/'; const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; @@ -115,11 +114,7 @@ export class WalletConnection { this._networkId = near.config.networkId; this._walletBaseUrl = near.config.walletUrl; appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; - // @ts-expect-error keyPair isn't public - const keyPair = (near.connection.signer as KeyPairSigner).keyPair as KeyPair; - const keyStore = new InMemoryKeyStore(); - keyStore.setKey(near.connection.networkId, this._authData.accountId, keyPair); - this._keyStore = keyStore; + this._keyStore = (near.config as NearConfig).keyStore || new InMemoryKeyStore(); this._authData = authData || { allKeys: [] }; this._authDataKey = authDataKey; if (!this.isSignedIn()) { From 0263973966e4885c30f02ed421eb50d124102e4c Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 5 Apr 2025 11:18:24 +0200 Subject: [PATCH 07/57] chore: update naming of a few Account methods --- packages/accounts/src/account.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 87722be924..c44160da99 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -198,7 +198,7 @@ export class PublicAccount { * * @returns {Promise} */ - public async invokeReadFunction( + public async callReadFunction( contractId: string, methodName: string, args: Record = {} @@ -1026,7 +1026,7 @@ export class Account extends PublicAccount implements IntoConnection { * @param gas * @returns */ - public async invokeWriteFunction( + public async callFunction( contractId: string, methodName: string, args: Record = {}, From 7fd68a300cb1911761ed2b577725eda75bf59698 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 7 Apr 2025 20:21:38 +0200 Subject: [PATCH 08/57] feat: fill in Provider with all RPC methods available out there --- .../src/local-view-execution/index.ts | 4 +- packages/client/src/view.ts | 4 +- packages/providers/package.json | 1 + .../providers/src/failover-rpc-provider.ts | 69 ++++++- packages/providers/src/json-rpc-provider.ts | 189 +++++++++++++++++- packages/providers/src/provider.ts | 93 ++++++--- packages/types/src/provider/index.ts | 9 +- packages/types/src/provider/protocol.ts | 19 +- packages/types/src/provider/response.ts | 37 +++- 9 files changed, 383 insertions(+), 42 deletions(-) diff --git a/packages/accounts/src/local-view-execution/index.ts b/packages/accounts/src/local-view-execution/index.ts index 2f62c87320..9f8bb1e2eb 100644 --- a/packages/accounts/src/local-view-execution/index.ts +++ b/packages/accounts/src/local-view-execution/index.ts @@ -1,4 +1,4 @@ -import { BlockReference, ContractCodeView } from '@near-js/types'; +import { BlockReference, ContractCodeViewRaw } from '@near-js/types'; import { printTxOutcomeLogs } from '@near-js/utils'; import { FunctionCallOptions } from '../interface'; import { Storage } from './storage'; @@ -22,7 +22,7 @@ export class LocalViewExecution { } private async fetchContractCode(contractId: string, blockQuery: BlockReference) { - const result = await this.connection.provider.query({ + const result = await this.connection.provider.query({ request_type: 'view_code', account_id: contractId, ...blockQuery, diff --git a/packages/client/src/view.ts b/packages/client/src/view.ts index 9f7ad6cf15..7e26e878ba 100644 --- a/packages/client/src/view.ts +++ b/packages/client/src/view.ts @@ -3,7 +3,7 @@ import type { AccessKeyView, AccountView, CodeResult, - ContractCodeView, + ContractCodeViewRaw, QueryResponseKind, SerializedReturnValue, StakedAccount, @@ -207,7 +207,7 @@ export async function getAccessKeys({ account, blockReference, deps }: ViewAccou * @param deps readonly RPC dependencies */ export async function getContractCode({ account, blockReference, deps }: ViewAccountParams) { - const { code_base64, hash } = await query({ + const { code_base64, hash } = await query({ request: RequestType.ViewCode, account, blockReference, diff --git a/packages/providers/package.json b/packages/providers/package.json index 5707002c91..bd20229017 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -19,6 +19,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/crypto": "workspace:*", "borsh": "1.0.0", "exponential-backoff": "^3.1.2" }, diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index 1d3d36eca1..6cd61882f3 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -27,16 +27,25 @@ import { QueryResponseKind, TypedError, RpcQueryRequest, + AccessKeyView, + AccessKeyList, + AccountView, + ContractCodeView, + ContractStateView, + CallContractViewFunctionResult, + ExecutionOutcomeReceiptDetail, + FinalityReference, } from '@near-js/types'; import { SignedTransaction } from '@near-js/transactions'; import { Provider } from './provider'; import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; /** * Client class to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). * @see [https://github.com/near/nearcore/tree/master/chain/jsonrpc](https://github.com/near/nearcore/tree/master/chain/jsonrpc) */ -export class FailoverRpcProvider extends Provider { +export class FailoverRpcProvider implements Provider { /** @hidden */ readonly providers: Provider[]; @@ -46,8 +55,6 @@ export class FailoverRpcProvider extends Provider { * @param providers list of providers */ constructor(providers: Provider[]) { - super(); - if (providers.length === 0) { throw new Error('At least one provider must be specified'); } @@ -108,6 +115,62 @@ export class FailoverRpcProvider extends Provider { return this.withBackoff((currentProvider) => currentProvider.status()); } + public async viewAccessKey(accountId: string, publicKey: PublicKey, finalityQuery?: FinalityReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccessKey(accountId, publicKey, finalityQuery)); + } + + public async viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccessKeyList(accountId, finalityQuery)); + } + + public async viewAccount(accountId: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccount(accountId, blockQuery)); + } + + public async viewContractCode(accountId: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewContractCode(accountId, blockQuery)); + } + + public async viewContractState(accountId: string, prefix?: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewContractState(accountId, prefix, blockQuery)); + } + + public async callContractViewFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.callContractViewFunction(accountId, method, args, blockQuery)); + } + + public async viewBlock(blockQuery: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewBlock(blockQuery)); + } + + public async viewChunk(chunkId: ChunkId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewChunk(chunkId)); + } + + public async viewGasPrice(blockId?: BlockId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewGasPrice(blockId)); + } + + public async viewNodeStatus(): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewNodeStatus()); + } + + public async viewValidators(blockId?: BlockId): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewValidators(blockId)); + } + + public async viewTransactionStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionStatus(txHash, accountId, waitUntil)); + } + + public async viewTransactionStatusWithReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise>> { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionStatusWithReceipts(txHash, accountId, waitUntil)); + } + + public async viewTransactionReceipt(receiptId: string): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewTransactionReceipt(receiptId)); + } + async sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise { return this.withBackoff((currentProvider) => currentProvider.sendTransactionUntil(signedTransaction, waitUntil)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 0cbe3eb5ea..ccfa4569d2 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -32,6 +32,18 @@ import { NodeStatusResult, QueryResponseKind, TypedError, + AccessKeyViewRaw, + AccessKeyView, + FinalityReference, + AccessKeyList, + AccountView, + AccountViewRaw, + ContractCodeViewRaw, + ContractCodeView, + ContractStateView, + CallContractViewFunctionResultRaw, + CallContractViewFunctionResult, + ExecutionOutcomeReceiptDetail, } from '@near-js/types'; import { encodeTransaction, @@ -41,6 +53,7 @@ import { import { Provider } from './provider'; import { ConnectionInfo, fetchJsonRpc, retryConfig } from './fetch_json'; import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from "@near-js/crypto"; /** @hidden */ // Default number of retries before giving up on a request. @@ -74,7 +87,7 @@ type RequestOptions = { * Client class to interact with the [NEAR RPC API](https://docs.near.org/api/rpc/introduction). * @see [https://github.com/near/nearcore/tree/master/chain/jsonrpc](https://github.com/near/nearcore/tree/master/chain/jsonrpc) */ -export class JsonRpcProvider extends Provider { +export class JsonRpcProvider implements Provider { /** @hidden */ readonly connection: ConnectionInfo; @@ -85,7 +98,6 @@ export class JsonRpcProvider extends Provider { * @param connectionInfo Connection info */ constructor(connectionInfo: ConnectionInfo, options?: Partial) { - super(); this.connection = connectionInfo || { url: '' }; const defaultOptions: RequestOptions = { retries: REQUEST_RETRY_NUMBER, @@ -95,6 +107,179 @@ export class JsonRpcProvider extends Provider { this.options = Object.assign({}, defaultOptions, options); } + public async viewAccessKey( + accountId: string, + publicKey: PublicKey | string, + finalityQuery: FinalityReference = { finality: "final" } + ): Promise { + const data = await (this as Provider).query({ + ...finalityQuery, + request_type: "view_access_key", + account_id: accountId, + public_key: publicKey.toString(), + }); + + return { + ...data, + nonce: BigInt(data.nonce), + }; + } + + public async viewAccessKeyList( + accountId: string, + finalityQuery: FinalityReference = { finality: "final" } + ): Promise { + return (this as Provider).query({ + ...finalityQuery, + request_type: "view_access_key_list", + account_id: accountId, + }); + } + + public async viewAccount( + accountId: string, + blockQuery: BlockReference = { finality: "final" } + ): Promise { + const data = await (this as Provider).query({ + ...blockQuery, + request_type: "view_account", + account_id: accountId, + }); + + return { + ...data, + amount: BigInt(data.amount), + locked: BigInt(data.locked), + }; + } + + public async viewContractCode( + contractId: string, + blockQuery: BlockReference = { finality: "final" } + ): Promise { + const data = await (this as Provider).query({ + ...blockQuery, + request_type: "view_code", + account_id: contractId, + }); + + return { + ...data, + code: new Uint8Array(Buffer.from(data.code_base64, "base64")), + }; + } + + public async viewContractState( + contractId: string, + prefix?: string, + blockQuery: BlockReference = { finality: "final" } + ): Promise { + const prefixBase64 = Buffer.from(prefix || "").toString("base64"); + + return (this as Provider).query({ + ...blockQuery, + request_type: "view_state", + account_id: contractId, + prefix_base64: prefixBase64, + }); + } + + public async callContractViewFunction( + contractId: string, + method: string, + args: Record, + blockQuery: BlockReference = { finality: "final" } + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); + + const data = await ( + this as Provider + ).query({ + ...blockQuery, + request_type: "call_function", + account_id: contractId, + method_name: method, + args_base64: argsBase64, + }); + + if (data.result.length === 0) { + return { + ...data, + result: undefined, + }; + } + + return { + ...data, + result: Buffer.from(data.result).toString(), + }; + } + + public async viewBlock(blockQuery: BlockReference): Promise { + const { finality } = blockQuery as any; + const { blockId } = blockQuery as any; + return this.sendJsonRpc("block", { block_id: blockId, finality }); + } + + public async viewChunk(chunkId: ChunkId): Promise { + return this.sendJsonRpc("chunk", [chunkId]); + } + + public async viewGasPrice(blockId?: BlockId): Promise { + return this.sendJsonRpc("gas_price", [blockId || null]); + } + + public async viewNodeStatus(): Promise { + return this.sendJsonRpc("status", []); + } + + public async viewValidators( + blockId?: BlockId + ): Promise { + return this.sendJsonRpc("validators", [blockId || null]); + } + + public async viewTransactionStatus( + txHash: Uint8Array | string, + accountId: string, + waitUntil: TxExecutionStatus + ): Promise { + const encodedTxHash = + typeof txHash === "string" ? txHash : baseEncode(txHash); + + return this.sendJsonRpc("tx", { + tx_hash: encodedTxHash, + sender_account_id: accountId, + wait_until: waitUntil, + }); + } + + public async viewTransactionStatusWithReceipts( + txHash: Uint8Array | string, + accountId: string, + waitUntil: TxExecutionStatus + ): Promise< + FinalExecutionOutcome & + Required> + > { + const encodedTxHash = + typeof txHash === "string" ? txHash : baseEncode(txHash); + + return this.sendJsonRpc("EXPERIMENTAL_tx_status", { + tx_hash: encodedTxHash, + sender_account_id: accountId, + wait_until: waitUntil, + }); + } + + public async viewTransactionReceipt( + receiptId: string + ): Promise { + return this.sendJsonRpc("EXPERIMENTAL_receipt", { + receipt_id: receiptId, + }); + } + /** * Gets the RPC's status * @see [https://docs.near.org/docs/develop/front-end/rpc#general-validator-status](https://docs.near.org/docs/develop/front-end/rpc#general-validator-status) diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 1b9846ed9e..d5a30fd4d6 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -5,14 +5,15 @@ import { SignedTransaction } from '@near-js/transactions'; import { + AccessKeyView, + AccessKeyList, AccessKeyWithPublicKey, + AccountView, BlockChangeResult, BlockId, BlockReference, BlockResult, ChangeResult, - ChunkId, - ChunkResult, FinalExecutionOutcome, GasPrice, LightClientProof, @@ -24,33 +25,69 @@ import { QueryResponseKind, RpcQueryRequest, EpochValidatorInfo, + ExecutionOutcomeReceiptDetail, + TxExecutionStatus, + ContractCodeView, + ContractStateView, + CallContractViewFunctionResult, + ChunkId, + ChunkResult, + FinalityReference, } from '@near-js/types'; -import { TxExecutionStatus } from '@near-js/types'; +import { PublicKey } from '@near-js/crypto'; /** @hidden */ -export abstract class Provider { - abstract status(): Promise; - - abstract sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise; - abstract sendTransaction(signedTransaction: SignedTransaction): Promise; - abstract sendTransactionAsync(signedTransaction: SignedTransaction): Promise; - abstract txStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; - abstract txStatusReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; - abstract query(params: RpcQueryRequest): Promise; - abstract query(path: string, data: string): Promise; - // TODO: BlockQuery type? - abstract block(blockQuery: BlockId | BlockReference): Promise; - abstract blockChanges(blockQuery: BlockId | BlockReference): Promise; - abstract chunk(chunkId: ChunkId): Promise; - // TODO: Use BlockQuery? - abstract validators(blockId: BlockId | null): Promise; - abstract experimental_protocolConfig(blockReference: BlockReference): Promise; - abstract lightClientProof(request: LightClientProofRequest): Promise; - abstract nextLightClientBlock(request: NextLightClientBlockRequest): Promise; - abstract gasPrice(blockId: BlockId): Promise; - abstract accessKeyChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; - abstract singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], BlockQuery: BlockId | BlockReference): Promise; - abstract accountChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; - abstract contractStateChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference, keyPrefix: string): Promise; - abstract contractCodeChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; +export interface Provider { +/** @deprecated use {@link viewNodeStatus} */ + status(): Promise; + + viewAccessKey(accountId: string, publicKey: PublicKey | string, finalityQuery?: FinalityReference): Promise; + viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise; + + viewAccount(accountId: string, blockQuery?: BlockReference): Promise; + viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; + viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; + callContractViewFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + + viewBlock(blockQuery: BlockReference): Promise; + viewChunk(chunkId: ChunkId): Promise; + + viewGasPrice(blockId?: BlockId): Promise; + + viewNodeStatus(): Promise; + viewValidators(blockId?: BlockId): Promise; + + viewTransactionStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + viewTransactionStatusWithReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise>>; + viewTransactionReceipt(receiptId: string): Promise; + + sendTransactionUntil(signedTransaction: SignedTransaction, waitUntil: TxExecutionStatus): Promise; + sendTransaction(signedTransaction: SignedTransaction): Promise; + sendTransactionAsync(signedTransaction: SignedTransaction): Promise; + + /** @deprecated use {@link viewTransactionStatus} */ + txStatus(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + /** @deprecated use {@link viewTransactionStatusWithReceipts} */ + txStatusReceipts(txHash: Uint8Array | string, accountId: string, waitUntil: TxExecutionStatus): Promise; + + query(params: RpcQueryRequest): Promise; + query(path: string, data: string): Promise; + + /** @deprecated use {@link viewBlock} */ + block(blockQuery: BlockId | BlockReference): Promise; + blockChanges(blockQuery: BlockId | BlockReference): Promise; + /** @deprecated use {@link viewChunk} */ + chunk(chunkId: ChunkId): Promise; + /** @deprecated use {@link viewValidators} */ + validators(blockId: BlockId | null): Promise; + experimental_protocolConfig(blockReference: BlockReference): Promise; + lightClientProof(request: LightClientProofRequest): Promise; + nextLightClientBlock(request: NextLightClientBlockRequest): Promise; + /** @deprecated use {@link viewGasPrice} */ + gasPrice(blockId: BlockId): Promise; + accessKeyChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + singleAccessKeyChanges(accessKeyArray: AccessKeyWithPublicKey[], BlockQuery: BlockId | BlockReference): Promise; + accountChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; + contractStateChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference, keyPrefix: string): Promise; + contractCodeChanges(accountIdArray: string[], BlockQuery: BlockId | BlockReference): Promise; } diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index eac273dea6..42f0620610 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -26,6 +26,7 @@ export { ChunkId, ChunkResult, Finality, + FinalityReference, GasPrice, MerkleNode, MerklePath, @@ -36,7 +37,7 @@ export { SyncInfo, TotalWeight, Transaction as ProviderTransaction, - TxExecutionStatus + TxExecutionStatus, } from './protocol'; export { CallFunctionRequest, @@ -53,8 +54,10 @@ export { AccessKeyView, AccessKeyViewRaw, AccountView, + AccountViewRaw, CodeResult, ContractCodeView, + ContractCodeViewRaw, ExecutionError, ExecutionOutcome, ExecutionOutcomeWithId, @@ -68,6 +71,10 @@ export { QueryResponseKind, SerializedReturnValue, ViewStateResult, + ExecutionOutcomeReceiptDetail, + ContractStateView, + CallContractViewFunctionResultRaw, + CallContractViewFunctionResult } from './response'; export { CurrentEpochValidatorInfo, diff --git a/packages/types/src/provider/protocol.ts b/packages/types/src/provider/protocol.ts index c7a98235de..5afcc28117 100644 --- a/packages/types/src/provider/protocol.ts +++ b/packages/types/src/provider/protocol.ts @@ -11,17 +11,27 @@ export interface SyncInfo { syncing: boolean; } +interface ValidatorInfo { + account_id: string; + is_slashed: boolean; +} + interface Version { version: string; build: string; + rustc_version: string; } export interface NodeStatusResult { chain_id: string; rpc_addr: string; sync_info: SyncInfo; - validators: string[]; + validators: ValidatorInfo[]; version: Version; + protocol_version: number; + latest_protocol_version: number; + uptime_sec: number; + genesis_hash: string; } export type BlockHash = string; @@ -32,7 +42,12 @@ export type Finality = 'optimistic' | 'near-final' | 'final' export type TxExecutionStatus = 'NONE' | 'INCLUDED' | 'INCLUDED_FINAL' | 'EXECUTED' | 'FINAL' | 'EXECUTED_OPTIMISTIC'; -export type BlockReference = { blockId: BlockId } | { finality: Finality } | { sync_checkpoint: 'genesis' | 'earliest_available' } +export type FinalityReference = { finality: Finality }; + +export type BlockReference = { blockId: BlockId } | FinalityReference | { + /** @deprecated */ + sync_checkpoint: 'genesis' | 'earliest_available' +} export interface TotalWeight { num: number; diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index 7a4155cd42..d4083158e7 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -90,7 +90,7 @@ export interface QueryResponseKind { block_hash: BlockHash; } -export interface AccountView extends QueryResponseKind { +export interface AccountViewRaw extends QueryResponseKind { amount: string; locked: string; code_hash: string; @@ -98,6 +98,14 @@ export interface AccountView extends QueryResponseKind { storage_paid_at: BlockHeight; } +export interface AccountView extends QueryResponseKind { + amount: bigint; + locked: bigint; + code_hash: string; + storage_usage: number; + storage_paid_at: BlockHeight; +} + interface StateItem { key: string; value: string; @@ -114,11 +122,36 @@ export interface CodeResult extends QueryResponseKind { logs: string[]; } -export interface ContractCodeView extends QueryResponseKind { +export interface CallContractViewFunctionResultRaw extends QueryResponseKind { + result: number[]; + logs: string[]; +} + +export interface CallContractViewFunctionResult extends QueryResponseKind { + result?: string; + logs: string[]; +} + +interface StateItemView { + key: string; + value: string; + proof: string[]; +} + +export interface ContractStateView extends QueryResponseKind { + values: StateItemView[]; +} + +export interface ContractCodeViewRaw extends QueryResponseKind { code_base64: string; hash: string; } +export interface ContractCodeView extends QueryResponseKind { + code: Uint8Array; + hash: string; +} + export interface FunctionCallPermissionView { FunctionCall: { allowance: string; From 4e36c23c7e4888897217eaac82d6f6229da9d716 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 7 Apr 2025 20:59:13 +0200 Subject: [PATCH 09/57] feat: add new method to Account for creating top-level accounts --- packages/accounts/src/account.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index c44160da99..bbe2528b02 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -503,6 +503,36 @@ export class Account extends PublicAccount implements IntoConnection { }); } + public async createTopLevelAccount( + account: string, + pk: PublicKey, + amount: bigint + ): Promise { + const topAccount = account.split(".").at(-1); + + if (!topAccount) + throw new Error( + `Failed to parse top account out of the name for new account` + ); + + const actions = [ + functionCall( + "create_account", + { + new_account_id: account, + new_public_key: pk.toString(), + }, + BigInt(60_000_000_000_000), + amount + ), + ]; + + return this.signAndSendTransaction({ + receiverId: topAccount, + actions: actions, + }); + } + public async createSubAccount( accountOrPrefix: string, pk: PublicKey, From be58a5ff1d30f37b7fb4b1ed69c20502f193dda0 Mon Sep 17 00:00:00 2001 From: denbite Date: Wed, 9 Apr 2025 21:10:54 +0200 Subject: [PATCH 10/57] fix: update `@near-js/client` and `cookbook` examples to align with recent changes --- packages/accounts/src/account.ts | 2 +- packages/client/package.json | 1 + .../client/src/interfaces/dependencies.ts | 21 +--- packages/client/src/interfaces/index.ts | 1 - packages/client/src/interfaces/providers.ts | 25 ----- packages/client/src/providers.ts | 19 +--- packages/client/src/signing/index.ts | 6 +- packages/client/src/signing/signers.ts | 103 ++---------------- .../composers/signed_transaction_composer.ts | 58 +++------- .../client/src/transactions/sign_and_send.ts | 13 +-- packages/client/src/view.ts | 1 + .../access-keys/create-full-access-key.ts | 2 +- .../access-keys/create-function-access-key.ts | 2 +- .../accounts/access-keys/delete-access-key.ts | 2 +- .../accounts/create-mainnet-account.ts | 2 +- .../accounts/create-testnet-account.ts | 2 +- .../transactions/batch-transactions.ts | 2 +- .../cookbook/transactions/get-tx-status.ts | 6 +- .../transactions/meta-transaction-relayer.ts | 2 +- .../cookbook/transactions/meta-transaction.ts | 4 +- packages/cookbook/utils/calculate-gas.ts | 2 +- packages/cookbook/utils/deploy-contract.ts | 2 +- packages/cookbook/utils/unwrap-near.ts | 2 +- packages/cookbook/utils/verify-signature.ts | 2 +- packages/cookbook/utils/wrap-near.ts | 2 +- packages/transactions/src/index.ts | 1 - packages/transactions/src/sign.ts | 52 --------- 27 files changed, 49 insertions(+), 288 deletions(-) delete mode 100644 packages/client/src/interfaces/providers.ts delete mode 100644 packages/transactions/src/sign.ts diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index bbe2528b02..6315e4ac10 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -223,7 +223,7 @@ export class PublicAccount { * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. */ export class Account extends PublicAccount implements IntoConnection { - protected readonly signer: Signer; + public readonly signer: Signer; constructor(accountId: string, provider: Provider, signer: Signer) { super(accountId, provider); diff --git a/packages/client/package.json b/packages/client/package.json index d1a41eed2f..868169d68c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -19,6 +19,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/accounts": "workspace:*", "@noble/hashes": "1.7.1" }, "keywords": [], diff --git a/packages/client/src/interfaces/dependencies.ts b/packages/client/src/interfaces/dependencies.ts index f8124e5e63..9babf28e42 100644 --- a/packages/client/src/interfaces/dependencies.ts +++ b/packages/client/src/interfaces/dependencies.ts @@ -1,29 +1,16 @@ -import type { PublicKey } from '@near-js/crypto'; - -import type { RpcQueryProvider } from './providers'; -import { FullAccessKey, FunctionCallAccessKey } from './view'; +import { Provider } from '@near-js/providers'; +import { Signer } from '@near-js/signers'; interface Dependent { deps: T; } -export interface MessageSigner { - getPublicKey(): Promise; - signMessage(m: Uint8Array): Promise; -} - -export interface AccessKeySigner extends MessageSigner { - getAccessKey(ignoreCache?: boolean): Promise; - getNonce(ignoreCache?: boolean): Promise; - getSigningAccount(): string; -} - interface RpcProviderDependent { - rpcProvider: RpcQueryProvider; + rpcProvider: Provider; } interface SignerDependent { - signer: MessageSigner; + signer: Signer; } export interface RpcProviderDependency extends Dependent {} diff --git a/packages/client/src/interfaces/index.ts b/packages/client/src/interfaces/index.ts index a3827e4c93..36d525630a 100644 --- a/packages/client/src/interfaces/index.ts +++ b/packages/client/src/interfaces/index.ts @@ -1,4 +1,3 @@ export * from './dependencies'; -export * from './providers'; export * from './transactions'; export * from './view'; diff --git a/packages/client/src/interfaces/providers.ts b/packages/client/src/interfaces/providers.ts deleted file mode 100644 index b418de9af0..0000000000 --- a/packages/client/src/interfaces/providers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { - BlockReference, - BlockResult, - ChunkId, - ChunkResult, - FinalExecutionOutcome, - QueryResponseKind, - TxExecutionStatus, -} from '@near-js/types'; -import type { SignedTransaction } from '@near-js/transactions'; - -interface GetTransactionParams { - transactionHash: string; - account: string; - includeReceipts?: boolean; - waitUntil?: TxExecutionStatus; -} - -export interface RpcQueryProvider { - block(block: BlockReference): Promise; - chunk(chunkId: ChunkId): Promise; - getTransaction(params: GetTransactionParams): Promise; - sendTransaction(transaction: SignedTransaction): Promise; - query(...args: any[]): Promise; -} diff --git a/packages/client/src/providers.ts b/packages/client/src/providers.ts index 152f56c3bc..8e670b6bd8 100644 --- a/packages/client/src/providers.ts +++ b/packages/client/src/providers.ts @@ -1,6 +1,5 @@ -import { FailoverRpcProvider, JsonRpcProvider } from '@near-js/providers'; +import { FailoverRpcProvider, JsonRpcProvider, Provider } from '@near-js/providers'; -import type { RpcQueryProvider } from './interfaces'; import { PAGODA_RPC_ARCHIVAL_ENDPOINTS_TESTNET, PAGODA_RPC_ENDPOINTS_MAINNET, @@ -26,24 +25,12 @@ export function getEndpointsByNetwork(network: string) { * Initialize a failover RPC provider capable of retrying requests against a set of endpoints * @param urls RPC endpoint URLs */ -export function createRpcClientWrapper(urls: string[]): RpcQueryProvider { +export function createRpcClientWrapper(urls: string[]): Provider { if (!urls) { throw new Error('at least one RPC endpoint URL required'); } - const provider = new FailoverRpcProvider(urls.map((url) => new JsonRpcProvider({ url }))); - return { - block: (block) => provider.block(block), - chunk: (chunkId) => provider.chunk(chunkId), - getTransaction: ({ transactionHash, account, includeReceipts, waitUntil}) => { - if (includeReceipts) { - return provider.txStatusReceipts(transactionHash, account, waitUntil); - } - return provider.txStatus(transactionHash, account, waitUntil); - }, - sendTransaction: (transaction) => provider.sendTransaction(transaction), - query: (params) => provider.query(params), - }; + return new FailoverRpcProvider(urls.map((url) => new JsonRpcProvider({ url }))); } /** diff --git a/packages/client/src/signing/index.ts b/packages/client/src/signing/index.ts index 01e03a91e1..4f51eaecea 100644 --- a/packages/client/src/signing/index.ts +++ b/packages/client/src/signing/index.ts @@ -1,5 +1 @@ -export { - getSignerFromKeyPair, - getSignerFromKeystore, - getSignerFromPrivateKey, -} from './signers'; +export * from './signers'; \ No newline at end of file diff --git a/packages/client/src/signing/signers.ts b/packages/client/src/signing/signers.ts index 3f4c7c5921..8e9cb2378b 100644 --- a/packages/client/src/signing/signers.ts +++ b/packages/client/src/signing/signers.ts @@ -1,31 +1,13 @@ import { KeyPair, type KeyPairString } from "@near-js/crypto"; import { KeyStore } from '@near-js/keystores'; - -import type { - AccessKeySigner, - FullAccessKey, - FunctionCallAccessKey, - MessageSigner, - SignerDependency, - ViewAccountParams, -} from "../interfaces"; -import { getAccessKey } from "../view"; -import { sha256 } from "@noble/hashes/sha256"; +import { KeyPairSigner, Signer } from "@near-js/signers"; /** * Initialize a message signer from a KeyPair * @param keyPair used to sign transactions */ -export function getSignerFromKeyPair(keyPair: KeyPair): MessageSigner { - return { - async getPublicKey() { - return keyPair.getPublicKey(); - }, - async signMessage(m) { - const hashedMessage = new Uint8Array(sha256(m)); - return keyPair.sign(hashedMessage).signature; - }, - }; +export function getSignerFromKeyPair(keyPair: KeyPair): Signer { + return new KeyPairSigner(keyPair); } /** @@ -34,8 +16,8 @@ export function getSignerFromKeyPair(keyPair: KeyPair): MessageSigner { */ export function getSignerFromPrivateKey( privateKey: KeyPairString -): MessageSigner { - return getSignerFromKeyPair(KeyPair.fromString(privateKey)); +): Signer { + return KeyPairSigner.fromSecretKey(privateKey); } /** @@ -44,76 +26,7 @@ export function getSignerFromPrivateKey( * @param network to sign transactions on * @param keyStore used to store the signing key */ -export function getSignerFromKeystore(account: string, network: string, keyStore: KeyStore): MessageSigner { - return { - async getPublicKey() { - const keyPair = await keyStore.getKey(network, account); - - return keyPair.getPublicKey(); - }, - async signMessage(m) { - /** - * @todo migrate to KeyPairSigner someday - */ - const keyPair = await keyStore.getKey(network, account); - - const { signature } = keyPair.sign(m); - return signature; - } - }; -} - -/** - * Initialize a signer that caches the access key and increments the nonce - * @param account access key owner - * @param rpcProvider RPC provider instance - * @param deps sign-and-send dependencies - */ -export function getAccessKeySigner({ - account, - blockReference, - deps: { rpcProvider, signer }, -}: ViewAccountParams & SignerDependency): AccessKeySigner { - let accessKey: FullAccessKey | FunctionCallAccessKey; - let nonce: bigint | undefined; - - return { - async getAccessKey(ignoreCache = false) { - if (!accessKey || ignoreCache) { - accessKey = await getAccessKey({ - account, - blockReference: blockReference || { - finality: "optimistic", - }, - publicKey: (await signer.getPublicKey()).toString(), - deps: { rpcProvider }, - }); - - nonce = accessKey.nonce + 1n; - } - - return accessKey; - }, - async getNonce(ignoreCache = false) { - if (!nonce || ignoreCache) { - await this.getAccessKey(true); - } - - return nonce; - }, - getPublicKey() { - return signer.getPublicKey(); - }, - getSigningAccount() { - return account; - }, - signMessage(m: Uint8Array) { - // Nonce can be uninitialized here if it is provided explicitly by developer (see toSignedDelegateAction), - // we don't need to track it here in that case. - if (nonce) { - nonce += 1n; - } - return signer.signMessage(m); - }, - }; +export async function getSignerFromKeystore(account: string, network: string, keyStore: KeyStore): Promise { + const keyPair = await keyStore.getKey(network, account); + return new KeyPairSigner(keyPair); } diff --git a/packages/client/src/transactions/composers/signed_transaction_composer.ts b/packages/client/src/transactions/composers/signed_transaction_composer.ts index a768d5e68d..79bcd42e27 100644 --- a/packages/client/src/transactions/composers/signed_transaction_composer.ts +++ b/packages/client/src/transactions/composers/signed_transaction_composer.ts @@ -1,35 +1,22 @@ -import { - buildDelegateAction, - signDelegateAction, -} from '@near-js/transactions'; import type { BlockReference, SerializedReturnValue } from '@near-js/types'; import { getTransactionLastResult } from '@near-js/utils'; import { DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL } from '../../constants'; import { - AccessKeySigner, - MessageSigner, MetaTransactionOptions, - RpcQueryProvider, SignedTransactionOptions, TransactionOptions, } from '../../interfaces'; import { signTransaction } from '../sign_and_send'; import { TransactionComposer } from './transaction_composer'; -import { getAccessKeySigner } from '../../signing/signers'; +import { Account } from '@near-js/accounts'; export class SignedTransactionComposer extends TransactionComposer { - messageSigner: MessageSigner; - rpcProvider: RpcQueryProvider; - signer: AccessKeySigner; + account: Account; constructor({ deps, ...baseOptions }: SignedTransactionOptions) { super(baseOptions); - this.messageSigner = deps.signer; - this.rpcProvider = deps.rpcProvider; - if (this.sender) { - this.signer = getAccessKeySigner({ account: this.sender, deps }); - } + this.account = new Account(this.sender, deps.rpcProvider, deps.signer); } /** @@ -47,26 +34,16 @@ export class SignedTransactionComposer extends TransactionComposer { async toSignedDelegateAction(transaction?: MetaTransactionOptions) { let maxBlockHeight = transaction?.maxBlockHeight; if (!maxBlockHeight) { - const { header } = await this.rpcProvider.block({ finality: 'final' }); + const { header } = await this.account.provider.viewBlock({ finality: 'final' }); const ttl = transaction?.blockHeightTtl || DEFAULT_META_TRANSACTION_BLOCK_HEIGHT_TTL; maxBlockHeight = BigInt(header.height) + ttl; } - const delegateAction = buildDelegateAction({ + return this.account.signedDelegate({ actions: this.actions, - maxBlockHeight, - nonce: transaction?.nonce || this.nonce || await this.signer.getNonce(), - publicKey: transaction?.publicKey || this.publicKey || await this.signer.getPublicKey(), receiverId: transaction?.receiver || this.receiver, - senderId: transaction?.sender || this.sender, - }); - - const { signedDelegateAction } = await signDelegateAction({ - delegateAction, - signer: { sign: (m) => this.signer.signMessage(m) }, - }); - - return signedDelegateAction; + blockHeightTtl: Number(maxBlockHeight) + }) } /** @@ -77,16 +54,8 @@ export class SignedTransactionComposer extends TransactionComposer { * @private */ private verifySigner(signingAccount: string) { - if (!this.signer) { - this.signer = getAccessKeySigner({ - account: signingAccount, - deps: { rpcProvider: this.rpcProvider, signer: this.messageSigner }, - }); - } - - const signerAccount = this.signer.getSigningAccount(); - if (signingAccount !== signerAccount) { - throw new Error(`Cannot sign transaction as ${signingAccount} with AccessKeySigner for ${signerAccount}`); + if (signingAccount !== this.account.accountId) { + throw new Error(`Cannot sign transaction as ${signingAccount} with Account for ${this.account.accountId}`); } } @@ -99,7 +68,7 @@ export class SignedTransactionComposer extends TransactionComposer { this.verifySigner(transaction.signerId); return signTransaction({ transaction, - deps: { signer: this.signer }, + deps: { signer: this.account.signer }, }); } @@ -110,12 +79,11 @@ export class SignedTransactionComposer extends TransactionComposer { async signAndSend(blockReference: BlockReference = { finality: 'final' }) { this.verifySigner(this.sender); const { signedTransaction } = await this.toSignedTransaction({ - nonce: this.nonce || await this.signer.getNonce(), - publicKey: this.publicKey || await this.signer.getPublicKey(), - blockHash: this.blockHash || (await this.rpcProvider.block(blockReference))?.header?.hash, + publicKey: this.publicKey || await this.account.signer.getPublicKey(), + blockHash: this.blockHash || (await this.account.provider.viewBlock(blockReference))?.header?.hash, }); - const outcome = await this.rpcProvider.sendTransaction(signedTransaction); + const outcome = await this.account.provider.sendTransaction(signedTransaction); return { outcome, result: getTransactionLastResult(outcome) as T, diff --git a/packages/client/src/transactions/sign_and_send.ts b/packages/client/src/transactions/sign_and_send.ts index c555d3f6f5..c8d3f2901d 100644 --- a/packages/client/src/transactions/sign_and_send.ts +++ b/packages/client/src/transactions/sign_and_send.ts @@ -1,6 +1,4 @@ -import { Signature, SignedTransaction } from '@near-js/transactions'; import { getTransactionLastResult } from '@near-js/utils'; -import { sha256 } from '@noble/hashes/sha256'; import type { SignTransactionParams, SignAndSendTransactionParams } from '../interfaces'; import { getNonce } from '../view'; @@ -15,17 +13,10 @@ const DEFAULT_FINALITY: BlockReference = { finality: 'final' }; * @param signer MessageSigner */ export async function signTransaction({ transaction, deps: { signer } }: SignTransactionParams) { - const encodedTx = transaction.encode(); - const signedTransaction = new SignedTransaction({ - transaction, - signature: new Signature({ - keyType: transaction.publicKey.keyType, - data: await signer.signMessage(encodedTx), - }), - }); + const [txHash, signedTransaction] = await signer.signTransaction(transaction); return { - encodedTransactionHash: new Uint8Array(sha256(encodedTx)), + encodedTransactionHash: txHash, signedTransaction, }; } diff --git a/packages/client/src/view.ts b/packages/client/src/view.ts index 7e26e878ba..b65cf6ebc5 100644 --- a/packages/client/src/view.ts +++ b/packages/client/src/view.ts @@ -56,6 +56,7 @@ export function query({ deps: { rpcProvider }, }: QueryParams): Promise { return rpcProvider.query({ + // @ts-expect-error request_type isn't just a string, but a set of methods request_type: request, account_id: account, ...(blockReference ? blockReference : DEFAULT_VIEW_BLOCK_REFERENCE), diff --git a/packages/cookbook/accounts/access-keys/create-full-access-key.ts b/packages/cookbook/accounts/access-keys/create-full-access-key.ts index f86c7224cc..b4066a89fd 100644 --- a/packages/cookbook/accounts/access-keys/create-full-access-key.ts +++ b/packages/cookbook/accounts/access-keys/create-full-access-key.ts @@ -20,7 +20,7 @@ export default async function createFullAccessKey(accountId: string) { const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'testnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'testnet', keystore); const previousKey = await signer.getPublicKey(); // create a new key from random data diff --git a/packages/cookbook/accounts/access-keys/create-function-access-key.ts b/packages/cookbook/accounts/access-keys/create-function-access-key.ts index 41085275f4..2c342a90d1 100644 --- a/packages/cookbook/accounts/access-keys/create-function-access-key.ts +++ b/packages/cookbook/accounts/access-keys/create-function-access-key.ts @@ -21,7 +21,7 @@ export default async function createFunctionCallAccessKey(accountId: string, con // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for ACCOUNT_ID - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/accounts/access-keys/delete-access-key.ts b/packages/cookbook/accounts/access-keys/delete-access-key.ts index bfc32f67aa..48b9d5581a 100644 --- a/packages/cookbook/accounts/access-keys/delete-access-key.ts +++ b/packages/cookbook/accounts/access-keys/delete-access-key.ts @@ -19,7 +19,7 @@ export default async function deleteAccessKeyCookbook(accountId: string, publicK // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for ACCOUNT_ID - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); // parse the target key from its string representation const keyPair = parseKeyPair(publicKey as KeyPairString); diff --git a/packages/cookbook/accounts/create-mainnet-account.ts b/packages/cookbook/accounts/create-mainnet-account.ts index 14c07b5f29..8039b25e00 100644 --- a/packages/cookbook/accounts/create-mainnet-account.ts +++ b/packages/cookbook/accounts/create-mainnet-account.ts @@ -19,7 +19,7 @@ export default async function createMainnetAccountCookbook(accountId: string, ne const rpcProvider = getMainnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'mainnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'mainnet', keystore); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/accounts/create-testnet-account.ts b/packages/cookbook/accounts/create-testnet-account.ts index cd3e41d454..e44082d527 100644 --- a/packages/cookbook/accounts/create-testnet-account.ts +++ b/packages/cookbook/accounts/create-testnet-account.ts @@ -19,7 +19,7 @@ export default async function createTestnetAccountCookbook(accountId: string, ne const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` const keystore = new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')); - const signer = getSignerFromKeystore(accountId, 'testnet', keystore); + const signer = await getSignerFromKeystore(accountId, 'testnet', keystore); // create a new key from random data const keyPair = generateRandomKeyPair('ed25519'); diff --git a/packages/cookbook/transactions/batch-transactions.ts b/packages/cookbook/transactions/batch-transactions.ts index 612363faa9..49b28a9c4d 100644 --- a/packages/cookbook/transactions/batch-transactions.ts +++ b/packages/cookbook/transactions/batch-transactions.ts @@ -23,7 +23,7 @@ export default async function batchTransactions(accountId: string = CONTRACT_NAM // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const { result } = await SignedTransactionComposer.init({ sender: accountId, diff --git a/packages/cookbook/transactions/get-tx-status.ts b/packages/cookbook/transactions/get-tx-status.ts index bc4a609b0a..03e30ec353 100644 --- a/packages/cookbook/transactions/get-tx-status.ts +++ b/packages/cookbook/transactions/get-tx-status.ts @@ -10,11 +10,7 @@ export default async function getTransactionStatus(accountId: string = ACCOUNT_I // initialize testnet RPC provider const rpcProvider = getTestnetRpcArchivalProvider(); - const result = await rpcProvider.getTransaction({ - account: accountId, - transactionHash, - includeReceipts: true, // toggle flag to include/exclude the `receipts` field - }); + const result = await rpcProvider.viewTransactionStatusWithReceipts(transactionHash, accountId, 'FINAL'); console.log(JSON.stringify(result, null, 2)); } diff --git a/packages/cookbook/transactions/meta-transaction-relayer.ts b/packages/cookbook/transactions/meta-transaction-relayer.ts index 14e8820b56..7aceb086a3 100644 --- a/packages/cookbook/transactions/meta-transaction-relayer.ts +++ b/packages/cookbook/transactions/meta-transaction-relayer.ts @@ -24,7 +24,7 @@ export default async function sendMetaTransactionViaRelayer(signerAccountId: str // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const signedDelegate = await SignedTransactionComposer.init({ sender: signerAccountId, diff --git a/packages/cookbook/transactions/meta-transaction.ts b/packages/cookbook/transactions/meta-transaction.ts index 71593d5a36..e1f63b3780 100644 --- a/packages/cookbook/transactions/meta-transaction.ts +++ b/packages/cookbook/transactions/meta-transaction.ts @@ -28,7 +28,7 @@ export default async function metaTransaction(signerAccountId: string = SIGNER_A // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore( + const signer = await getSignerFromKeystore( signerAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')) @@ -46,7 +46,7 @@ export default async function metaTransaction(signerAccountId: string = SIGNER_A .toSignedDelegateAction(); // initialize the relayer's signer - const relayerSigner = getSignerFromKeystore( + const relayerSigner = await getSignerFromKeystore( senderAccountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials')) diff --git a/packages/cookbook/utils/calculate-gas.ts b/packages/cookbook/utils/calculate-gas.ts index e35866fdd8..4a3d93e284 100644 --- a/packages/cookbook/utils/calculate-gas.ts +++ b/packages/cookbook/utils/calculate-gas.ts @@ -22,7 +22,7 @@ export default async function calculateGas(accountId: string, method = METHOD_NA // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const { outcome: { diff --git a/packages/cookbook/utils/deploy-contract.ts b/packages/cookbook/utils/deploy-contract.ts index 654d280767..0c86c70991 100644 --- a/packages/cookbook/utils/deploy-contract.ts +++ b/packages/cookbook/utils/deploy-contract.ts @@ -20,7 +20,7 @@ export default async function deployContractCookbook(accountId: string, wasmPath // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); await deployContract({ account: accountId, diff --git a/packages/cookbook/utils/unwrap-near.ts b/packages/cookbook/utils/unwrap-near.ts index b88926e6fc..c7ad39b21a 100644 --- a/packages/cookbook/utils/unwrap-near.ts +++ b/packages/cookbook/utils/unwrap-near.ts @@ -22,7 +22,7 @@ export default async function unwrapNear(accountId: string, unwrapAmount: bigint // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const getStorageBalance = () => view({ account: wrapContract, diff --git a/packages/cookbook/utils/verify-signature.ts b/packages/cookbook/utils/verify-signature.ts index 11e56ec686..0795a77cbf 100644 --- a/packages/cookbook/utils/verify-signature.ts +++ b/packages/cookbook/utils/verify-signature.ts @@ -9,7 +9,7 @@ export default async function verifySignature(accountId: string = ACCOUNT_ID, tr // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); - const { transaction: { public_key, signature } } = await rpcProvider.getTransaction({ transactionHash, account: accountId }); + const { transaction: { public_key, signature } } = await rpcProvider.viewTransactionStatus(transactionHash, accountId, 'FINAL'); const hashBytes = baseDecode(transactionHash); const publicKeyBytes = baseDecode(public_key.slice('ed25519:'.length)); diff --git a/packages/cookbook/utils/wrap-near.ts b/packages/cookbook/utils/wrap-near.ts index 8e8087e2c6..e40c2501d0 100644 --- a/packages/cookbook/utils/wrap-near.ts +++ b/packages/cookbook/utils/wrap-near.ts @@ -22,7 +22,7 @@ export default async function wrapNear(accountId: string, wrapAmount: bigint, wr // initialize testnet RPC provider const rpcProvider = getTestnetRpcProvider(); // initialize the transaction signer using a pre-existing key for `accountId` - const signer = getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); + const signer = await getSignerFromKeystore(accountId, 'testnet', new UnencryptedFileSystemKeyStore(join(homedir(), '.near-credentials'))); const getStorageBalance = () => view({ account: wrapContract, diff --git a/packages/transactions/src/index.ts b/packages/transactions/src/index.ts index 84a063eaba..eaa50a4219 100644 --- a/packages/transactions/src/index.ts +++ b/packages/transactions/src/index.ts @@ -3,5 +3,4 @@ export * from './actions'; export * from './create_transaction'; export * from './delegate'; export * from './schema'; -export * from './sign'; export * from './signature'; diff --git a/packages/transactions/src/sign.ts b/packages/transactions/src/sign.ts deleted file mode 100644 index 8228281e09..0000000000 --- a/packages/transactions/src/sign.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { sha256 } from '@noble/hashes/sha256'; - -import { SignedDelegate } from './actions'; -import type { DelegateAction } from './delegate'; -import { encodeDelegateAction } from './schema'; -import { Signature } from './signature'; -import { KeyType } from '@near-js/crypto'; - -interface MessageSigner { - sign(message: Uint8Array): Promise; -} - -interface SignDelegateOptions { - delegateAction: DelegateAction; - signer: MessageSigner; -} - -export interface SignedDelegateWithHash { - hash: Uint8Array; - signedDelegateAction: SignedDelegate; -} - - -/** - * Sign a delegate action - * @options SignDelegate options - * @param options.delegateAction Delegate action to be signed by the meta transaction sender - * @param options.signer Signer instance for the meta transaction sender - */ -export async function signDelegateAction({ - delegateAction, - signer, -}: SignDelegateOptions): Promise { - const message = encodeDelegateAction(delegateAction); - const signature = await signer.sign(message); - - const keyType = delegateAction.publicKey.ed25519Key - ? KeyType.ED25519 - : KeyType.SECP256K1; - const signedDelegateAction = new SignedDelegate({ - delegateAction, - signature: new Signature({ - keyType, - data: signature, - }), - }); - - return { - hash: new Uint8Array(sha256(message)), - signedDelegateAction, - }; -} From a8e1046d4c184700bed93229f81e7875fca11b27 Mon Sep 17 00:00:00 2001 From: denbite Date: Wed, 9 Apr 2025 21:15:06 +0200 Subject: [PATCH 11/57] chore: add changeset --- .changeset/rotten-cases-dance.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/rotten-cases-dance.md diff --git a/.changeset/rotten-cases-dance.md b/.changeset/rotten-cases-dance.md new file mode 100644 index 0000000000..97d65c25c4 --- /dev/null +++ b/.changeset/rotten-cases-dance.md @@ -0,0 +1,13 @@ +--- +"@near-js/wallet-account": major +"@near-js/transactions": major +"near-api-js": major +"@near-js/providers": major +"@near-js/accounts": major +"@near-js/cookbook": major +"@near-js/signers": major +"@near-js/client": major +"@near-js/types": major +--- + +Major update for Signer and Account APIs to streamline development From 9360fc907519c632f3a25e7c1bbabb8ce4c75b56 Mon Sep 17 00:00:00 2001 From: denbite Date: Sun, 13 Apr 2025 20:31:48 +0200 Subject: [PATCH 12/57] fix: export PublicAccount a part of `near-api-js` --- packages/near-api-js/src/common-index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/near-api-js/src/common-index.ts b/packages/near-api-js/src/common-index.ts index fb4253f186..dc2ba1f010 100644 --- a/packages/near-api-js/src/common-index.ts +++ b/packages/near-api-js/src/common-index.ts @@ -4,7 +4,7 @@ import * as utils from './utils'; import * as transactions from './transaction'; import * as validators from './validators'; -import { Account } from './account'; +import { Account, PublicAccount } from './account'; import * as multisig from './account_multisig'; import * as accountCreator from './account_creator'; import { Connection } from './connection'; @@ -26,6 +26,7 @@ export { validators, multisig, + PublicAccount, Account, Connection, Contract, From 57d7257c755a4c07289b8cc1f0c073066f9a54b2 Mon Sep 17 00:00:00 2001 From: denbite Date: Sun, 13 Apr 2025 20:32:47 +0200 Subject: [PATCH 13/57] fix: add getBalance to PublicAccount --- packages/accounts/src/account.ts | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 6315e4ac10..9d9fbddfd2 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -72,6 +72,13 @@ export interface AccountBalance { available: string; } +export interface AccountBalanceInfo { + total: bigint; + stateStaked: bigint; + staked: bigint; + available: bigint; +} + export interface AccountAuthorizedApp { contractId: string; amount: string; @@ -140,6 +147,32 @@ export class PublicAccount { }); } + /** + * Returns calculated account balance + */ + async getBalance(): Promise { + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: "final", + }); + const state = await this.getInformation(); + + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); + const stateStaked = BigInt(state.storage_usage) * costPerByte; + const totalBalance = BigInt(state.amount) + state.locked; + const availableBalance = + totalBalance - + (state.locked > stateStaked ? state.locked : stateStaked); + + return { + total: totalBalance, + stateStaked: stateStaked, + staked: state.locked, + available: availableBalance, + }; + } + /** * Returns basic NEAR account information via the `view_account` RPC query method * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) From c6b8217d8967bc8c335e67d5e53bd437974f264d Mon Sep 17 00:00:00 2001 From: denbite Date: Sun, 13 Apr 2025 20:46:50 +0200 Subject: [PATCH 14/57] fix: for numeric parameters accept BigInt, number and string & for keys - PublicKey and string --- packages/accounts/src/account.ts | 60 ++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 9d9fbddfd2..0282f38a78 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -177,7 +177,9 @@ export class PublicAccount { * Returns basic NEAR account information via the `view_account` RPC query method * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) */ - public async getAccessKey(pk: PublicKey): Promise { + public async getAccessKey( + pk: PublicKey | string + ): Promise { return this.provider.query({ request_type: "view_access_key", public_key: pk.toString(), @@ -538,8 +540,8 @@ export class Account extends PublicAccount implements IntoConnection { public async createTopLevelAccount( account: string, - pk: PublicKey, - amount: bigint + pk: PublicKey | string, + amount: bigint | string | number ): Promise { const topAccount = account.split(".").at(-1); @@ -556,7 +558,7 @@ export class Account extends PublicAccount implements IntoConnection { new_public_key: pk.toString(), }, BigInt(60_000_000_000_000), - amount + BigInt(amount) ), ]; @@ -568,16 +570,16 @@ export class Account extends PublicAccount implements IntoConnection { public async createSubAccount( accountOrPrefix: string, - pk: PublicKey, - amount: bigint + pk: PublicKey | string, + amount: bigint | string | number ): Promise { const newAccountId = accountOrPrefix.includes(".") ? accountOrPrefix : `${accountOrPrefix}.${this.accountId}`; const actions = [ createAccount(), - transfer(amount), - addKey(pk, fullAccessKey()), + transfer(BigInt(amount)), + addKey(PublicKey.from(pk), fullAccessKey()), ]; return this.signAndSendTransaction({ @@ -588,8 +590,8 @@ export class Account extends PublicAccount implements IntoConnection { public async createSubAccountAndDeployContract( accountOrPrefix: string, - pk: PublicKey, - amount: bigint, + pk: PublicKey | string, + amount: bigint | string | number, code: Uint8Array ): Promise { const newAccountId = accountOrPrefix.includes(".") @@ -597,8 +599,8 @@ export class Account extends PublicAccount implements IntoConnection { : `${accountOrPrefix}.${this.accountId}`; const actions = [ createAccount(), - transfer(amount), - addKey(pk, fullAccessKey()), + transfer(BigInt(amount)), + addKey(PublicKey.from(pk), fullAccessKey()), deployContract(code), ]; @@ -752,9 +754,9 @@ export class Account extends PublicAccount implements IntoConnection { } public async addFullAccessKey( - pk: PublicKey + pk: PublicKey | string ): Promise { - const actions = [addKey(pk, fullAccessKey())]; + const actions = [addKey(PublicKey.from(pk), fullAccessKey())]; return this.signAndSendTransaction({ receiverId: this.accountId, @@ -763,15 +765,19 @@ export class Account extends PublicAccount implements IntoConnection { } public async addFunctionAccessKey( - pk: PublicKey, + pk: PublicKey | string, receiverId: string, - methodNames: string[] = [], - allowance?: bigint + methodNames: string[], + allowance?: bigint | string | number ): Promise { const actions = [ addKey( - pk, - functionCallAccessKey(receiverId, methodNames, allowance) + PublicKey.from(pk), + functionCallAccessKey( + receiverId, + methodNames, + BigInt(allowance) + ) ), ]; @@ -798,9 +804,9 @@ export class Account extends PublicAccount implements IntoConnection { public async transfer( receiverId: string, - amount: bigint + amount: bigint | string | number ): Promise { - const actions = [transfer(amount)]; + const actions = [transfer(BigInt(amount))]; return this.signAndSendTransaction({ receiverId: receiverId, @@ -816,9 +822,9 @@ export class Account extends PublicAccount implements IntoConnection { */ public async stake( publicKey: string | PublicKey, - amount: bigint + amount: bigint | string | number ): Promise { - const actions = [stake(amount, PublicKey.from(publicKey))]; + const actions = [stake(BigInt(amount), PublicKey.from(publicKey))]; return this.signAndSendTransaction({ receiverId: this.accountId, @@ -1093,10 +1099,12 @@ export class Account extends PublicAccount implements IntoConnection { contractId: string, methodName: string, args: Record = {}, - deposit: bigint = 0n, - gas: bigint = DEFAULT_FUNCTION_CALL_GAS + deposit: bigint | string | number = 0n, + gas: bigint | string | number = DEFAULT_FUNCTION_CALL_GAS ): Promise { - const actions = [functionCall(methodName, args, gas, deposit)]; + const actions = [ + functionCall(methodName, args, BigInt(gas), BigInt(deposit)), + ]; return this.signAndSendTransaction({ receiverId: contractId, From 609f735d6dac6ee4d1dcb371b30ae4f9df42831b Mon Sep 17 00:00:00 2001 From: denbite Date: Sun, 13 Apr 2025 20:47:22 +0200 Subject: [PATCH 15/57] chore: deprecate `Account.functionCall` method --- packages/accounts/src/account.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 0282f38a78..c535d23b0b 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -140,9 +140,7 @@ export class PublicAccount { * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) */ public async getInformation(): Promise { - return this.provider.query({ - request_type: "view_account", - account_id: this.accountId, + return this.provider.viewAccount(this.accountId, { finality: "optimistic", }); } @@ -654,6 +652,8 @@ export class Account extends PublicAccount implements IntoConnection { } /** + * @deprecated + * * Execute a function call. * @param options The options for the function call. * @param options.contractId The NEAR account ID of the smart contract. From 71bb8583e2b26625263f59d4b086a19ab73774d5 Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 15 Apr 2025 21:03:22 +0200 Subject: [PATCH 16/57] fix: use high-level `Provider` methods inside of `PublicAccount` instead of low-level `query` --- packages/accounts/src/account.ts | 59 ++++++------------- .../wallet-account/test/wallet_account.ts | 9 ++- 2 files changed, 26 insertions(+), 42 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index c535d23b0b..b5ae63091c 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -21,9 +21,9 @@ import { FunctionCallPermissionView, BlockReference, ContractCodeView, - ViewStateResult, - CodeResult, + ContractStateView, ErrorContext, + CallContractViewFunctionResult, } from "@near-js/types"; import { baseDecode, @@ -175,52 +175,36 @@ export class PublicAccount { * Returns basic NEAR account information via the `view_account` RPC query method * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) */ - public async getAccessKey( - pk: PublicKey | string - ): Promise { - return this.provider.query({ - request_type: "view_access_key", - public_key: pk.toString(), - account_id: this.accountId, + public async getAccessKey(pk: PublicKey | string): Promise { + return this.provider.viewAccessKey(this.accountId, pk, { finality: "optimistic", }); } public async getAccessKeyList(): Promise { - return this.provider.query({ - request_type: "view_access_key_list", - account_id: this.accountId, + return this.provider.viewAccessKeyList(this.accountId, { finality: "optimistic", }); } public async getContractCode(): Promise { - return this.provider.query({ - request_type: "view_code", - account_id: this.accountId, + return this.provider.viewContractCode(this.accountId, { finality: "optimistic", }); } - public async getContractState( - prefix: string = "" - ): Promise { - const prefixBase64 = Buffer.from(prefix).toString("base64"); - - return this.provider.query({ - request_type: "view_state", - account_id: this.accountId, + public async getContractState(prefix?: string): Promise { + return this.provider.viewContractState(this.accountId, prefix, { finality: "optimistic", - prefix_base64: prefixBase64, }); } public async getTransactionStatus( - txHash: string + txHash: string | Uint8Array ): Promise { - return this.provider.txStatus( + return this.provider.viewTransactionStatus( txHash, - this.accountId, + this.accountId, // accountId is used to determine on which shard to look for a tx "EXECUTED_OPTIMISTIC" ); } @@ -235,20 +219,13 @@ export class PublicAccount { contractId: string, methodName: string, args: Record = {} - ): Promise { - const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); - - const { result } = await this.provider.query({ - request_type: "call_function", - account_id: contractId, - method_name: methodName, - args_base64: argsBase64, - finality: "optimistic", - }); - - if (result.length === 0) return undefined; - - return JSON.parse(Buffer.from(result).toString()); + ): Promise { + return this.provider.callContractViewFunction( + contractId, + methodName, + args, + { finality: "optimistic" } + ); } } diff --git a/packages/wallet-account/test/wallet_account.ts b/packages/wallet-account/test/wallet_account.ts index ab88d92cb6..6557e71670 100644 --- a/packages/wallet-account/test/wallet_account.ts +++ b/packages/wallet-account/test/wallet_account.ts @@ -108,7 +108,14 @@ export const createTransactions = () => { hash: BLOCK_HASH } }; - } + }, + viewAccessKey(accountId, pk) { + return this.query({ + request_type: "view_access_key", + account_id: accountId, + public_key: pk.toString(), + }); + }, }; nearFake.connection.signer = signer; From bb32d25c4141daa9a308b0008c0093f434fbbfcb Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 15 Apr 2025 21:04:53 +0200 Subject: [PATCH 17/57] ci: update lock file --- pnpm-lock.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8905709ee1..63d3b9014f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -187,6 +187,9 @@ importers: packages/client: dependencies: + '@near-js/accounts': + specifier: workspace:* + version: link:../accounts '@near-js/crypto': specifier: workspace:* version: link:../crypto @@ -509,6 +512,9 @@ importers: packages/providers: dependencies: + '@near-js/crypto': + specifier: workspace:* + version: link:../crypto '@near-js/transactions': specifier: workspace:* version: link:../transactions From 9982061bfdf1f1b83469ec6a7345d77ab8f100dd Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 15 Apr 2025 21:05:41 +0200 Subject: [PATCH 18/57] chore: update tests --- packages/accounts/test/account.test.ts | 25 ++++++++----------------- packages/accounts/test/contract.test.ts | 10 ++-------- packages/accounts/test/test-utils.js | 2 +- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index a2dfe49159..c9debca7ff 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -151,7 +151,7 @@ describe('with deploy contract', () => { const data = fs.readFileSync(HELLO_WASM_PATH); await nearjs.accountCreator.masterAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); // @ts-expect-error test input - contract = new Contract(nearjs.connection, contractId, { + contract = new Contract(nearjs.accountCreator.masterAccount, contractId, { viewMethods: ['hello', 'getValue', 'returnHiWithLogs'], changeMethods: ['setValue', 'generateLogs', 'triggerAssert', 'testSetRemove', 'crossContract'] }); @@ -173,7 +173,6 @@ describe('with deploy contract', () => { test('cross-contact assertion and panic', async () => { await expect(contract.crossContract({ - signerAccount: workingAccount, args: {}, gas: 300000000000000 })).rejects.toThrow(/Smart contract panicked: expected to fail./); @@ -238,14 +237,14 @@ describe('with deploy contract', () => { expect(result).toEqual('hello trex'); const setCallValue = generateUniqueString('setCallPrefix'); - const result2 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue } }); + const result2 = await contract.setValue({ args: { value: setCallValue } }); expect(result2).toEqual(setCallValue); expect(await contract.getValue()).toEqual(setCallValue); }); test('view function calls by block Id and finality', async() => { const setCallValue1 = generateUniqueString('setCallPrefix'); - const result1 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue1 } }); + const result1 = await contract.setValue({ args: { value: setCallValue1 } }); expect(result1).toEqual(setCallValue1); expect(await contract.getValue()).toEqual(setCallValue1); @@ -277,7 +276,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue1); const setCallValue2 = generateUniqueString('setCallPrefix'); - const result2 = await contract.setValue({ signerAccount: workingAccount, args: { value: setCallValue2 } }); + const result2 = await contract.setValue({ args: { value: setCallValue2 } }); expect(result2).toEqual(setCallValue2); expect(await contract.getValue()).toEqual(setCallValue2); @@ -325,7 +324,6 @@ describe('with deploy contract', () => { test('make function calls via contract with gas', async() => { const setCallValue = generateUniqueString('setCallPrefix'); const result2 = await contract.setValue({ - signerAccount: workingAccount, args: { value: setCallValue }, gas: 1000000 * 1000000 }); @@ -334,10 +332,7 @@ describe('with deploy contract', () => { }); test('can get logs from method result', async () => { - await contract.generateLogs({ - signerAccount: workingAccount, - args: {} - }); + await contract.generateLogs(); expect(logs.length).toEqual(3); expect(logs[0].substr(0, 8)).toEqual('Receipt:'); expect(logs.slice(1)).toEqual([`\tLog [${contractId}]: log1`, `\tLog [${contractId}]: log2`]); @@ -350,10 +345,7 @@ describe('with deploy contract', () => { }); test('can get assert message from method result', async () => { - await expect(() => contract.triggerAssert({ - signerAccount: workingAccount, - args: {} - })).rejects.toThrow(/Smart contract panicked: expected to fail.+/); + await expect(contract.triggerAssert()).rejects.toThrow(/Smart contract panicked: expected to fail.+/); expect(logs[1]).toEqual(`\tLog [${contractId}]: log before assert`); expect(logs[2]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ABORT: expected to fail, filename: \\"assembly/index.ts" line: \\d+ col: \\d+$`)); }); @@ -367,7 +359,7 @@ describe('with deploy contract', () => { test('can have view methods only', async () => { // @ts-expect-error test input - const contract: any = new Contract(nearjs.connection, contractId, { + const contract: any = new Contract(workingAccount, contractId, { viewMethods: ['hello'], }); expect(await contract.hello({ name: 'world' })).toEqual('hello world'); @@ -375,11 +367,10 @@ describe('with deploy contract', () => { test('can have change methods only', async () => { // @ts-expect-error test input - const contract: any = new Contract(nearjs.connection, contractId, { + const contract: any = new Contract(workingAccount, contractId, { changeMethods: ['hello'], }); expect(await contract.hello({ - signerAccount: workingAccount, args: { name: 'world' } })).toEqual('hello world'); }); diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index 0b00385f8d..a612b8d094 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -119,14 +119,8 @@ describe('local view execution', () => { nearjs = await setUpTestConnection(); contract = await deployContractGuestBook(nearjs.accountCreator.masterAccount, generateUniqueString('guestbook')); - await contract.add_message({ - signerAccount: nearjs.accountCreator.masterAccount, - args: { text: "first message" }, - }); - await contract.add_message({ - signerAccount: nearjs.accountCreator.masterAccount, - args: { text: "second message" }, - }); + await contract.add_message({ text: "first message" }); + await contract.add_message({ text: "second message" }); const block = await contract.connection.provider.block({ finality: 'optimistic' }); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 164e836881..0691943fe4 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -144,7 +144,7 @@ export async function deployContractGuestBook(workingAccount, contractId) { const data = fs.readFileSync(GUESTBOOK_WASM_PATH); const account = await workingAccount.createAndDeployContract(contractId, newPublicKey, data, HELLO_WASM_BALANCE); - return new Contract(new Connection('', account.provider, {}, ''), contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); + return new Contract(new Account(contractId, account.provider, new KeyPairSigner(keyPair)), contractId, { viewMethods: ['total_messages', 'get_messages'], changeMethods: ['add_message'], useLocalViewExecution: true }); } export function sleep(time) { From 1b17e400949e8281bf2bf74047aa3d8e0adc6eb4 Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 15 Apr 2025 21:12:28 +0200 Subject: [PATCH 19/57] chore: minor update of a test --- packages/accounts/test/account.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index c9debca7ff..5476334213 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -352,7 +352,6 @@ describe('with deploy contract', () => { test('test set/remove', async () => { await contract.testSetRemove({ - signerAccount: workingAccount, args: { value: '123' } }); }); From ffddebd0a3a06d270f0119d78162c6ea2f25f98a Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 17 Apr 2025 01:01:27 +0200 Subject: [PATCH 20/57] feat: implement signNep413Message for KeyPairSigner --- packages/signers/package.json | 3 +- packages/signers/src/key_pair_signer.ts | 38 ++++++++- packages/signers/src/signer.ts | 17 +++- packages/signers/test/key_pair_signer.test.ts | 81 ++++++++++++++++++- pnpm-lock.yaml | 3 + 5 files changed, 131 insertions(+), 11 deletions(-) diff --git a/packages/signers/package.json b/packages/signers/package.json index 403868cc32..5eb081dfd6 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -19,7 +19,8 @@ "@near-js/crypto": "workspace:*", "@near-js/keystores": "workspace:*", "@near-js/transactions": "workspace:*", - "@noble/hashes": "1.7.1" + "@noble/hashes": "1.7.1", + "borsh": "1.0.0" }, "devDependencies": { "@jest/globals": "^29.7.0", diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts index 1a08b5dfde..0d55945595 100644 --- a/packages/signers/src/key_pair_signer.ts +++ b/packages/signers/src/key_pair_signer.ts @@ -1,7 +1,12 @@ import { KeyPair, PublicKey, KeyPairString, KeyType } from "@near-js/crypto"; import { sha256 } from "@noble/hashes/sha256"; -import { SignedMessage, Signer } from "./signer"; +import { + Nep413MessageSchema, + SignedMessage, + Signer, + SignMessageParams, +} from "./signer"; import { Transaction, SignedTransaction, @@ -11,6 +16,7 @@ import { SignedDelegate, encodeDelegateAction, } from "@near-js/transactions"; +import { serialize } from "borsh"; /** * Signs using in memory key store. @@ -33,8 +39,34 @@ export class KeyPairSigner extends Signer { return this.key.getPublicKey(); } - public async signNep413Message(): Promise { - throw new Error("Not implemented!"); + public async signNep413Message( + params: SignMessageParams, + accountId: string + ): Promise { + if (params.nonce.length !== 32) + throw new Error(`Nonce must be [u8; 32]`); + + const pk = this.key.getPublicKey(); + + // 2**31 + 413 == 2147484061 + const PREFIX = 2147484061; + const serializedPrefix = serialize("u32", PREFIX); + + const serializedParams = serialize(Nep413MessageSchema, params); + + const message = new Uint8Array(serializedPrefix.length + serializedParams.length); + message.set(serializedPrefix); + message.set(serializedParams, serializedPrefix.length) + + const hash = new Uint8Array(sha256(message)); + + const { signature } = this.key.sign(hash); + + return { + accountId: accountId, + publicKey: pk, + signature: signature, + }; } public async signTransaction( diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index 158d6263bf..2606a541d7 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,26 +1,35 @@ -import { Signature, PublicKey } from '@near-js/crypto'; +import { PublicKey } from "@near-js/crypto"; import { DelegateAction, SignedDelegate, SignedTransaction, Transaction, -} from '@near-js/transactions'; +} from "@near-js/transactions"; +import { Schema } from "borsh"; export interface SignMessageParams { message: string; // The message that wants to be transmitted. recipient: string; // The recipient to whom the message is destined (e.g. "alice.near" or "myapp.com"). nonce: Uint8Array; // A nonce that uniquely identifies this instance of the message, denoted as a 32 bytes array (a fixed `Buffer` in JS/TS). callbackUrl?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The URL to call after the signing process. Defaults to `window.location.href`. - state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). A state for authentication purposes. } export interface SignedMessage { accountId: string; // The account name to which the publicKey corresponds as plain text (e.g. "alice.near") publicKey: PublicKey; // The public counterpart of the key used to sign, expressed as a string with format ":" (e.g. "ed25519:6TupyNrcHGTt5XRLmHTc2KGaiSbjhQi1KHtCXTgbcr4Y") - signature: Signature; // The base64 representation of the signature. + signature: Uint8Array; // The base64 representation of the signature. state?: string; // Optional, applicable to browser wallets (e.g. MyNearWallet). The same state passed in SignMessageParams. } +export const Nep413MessageSchema: Schema = { + struct: { + message: "string", + nonce: { array: { type: "u8", len: 32 } }, + recipient: "string", + callbackUrl: { option: "string" }, + }, +}; + /** * General signing interface, can be used for in memory signing, RPC singing, external wallet, HSM, etc. */ diff --git a/packages/signers/test/key_pair_signer.test.ts b/packages/signers/test/key_pair_signer.test.ts index 40245c9f45..7faa00c089 100644 --- a/packages/signers/test/key_pair_signer.test.ts +++ b/packages/signers/test/key_pair_signer.test.ts @@ -3,7 +3,13 @@ import { TextEncoder } from "util"; import { KeyPairSigner } from "../src"; import { KeyPair, PublicKey } from "@near-js/crypto"; -import { createTransaction, encodeTransaction, actionCreators, decodeSignedTransaction } from "@near-js/transactions"; +import { + createTransaction, + encodeTransaction, + actionCreators, + decodeSignedTransaction, +} from "@near-js/transactions"; +import { SignMessageParams } from "../src/signer"; global.TextEncoder = TextEncoder; @@ -72,7 +78,9 @@ test("serialize and sign transfer tx object", async () => { ]); const transaction = createTransaction( "test.near", - PublicKey.fromString("ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC"), + PublicKey.fromString( + "ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC" + ), "whatever.near", 1, actions, @@ -95,4 +103,71 @@ test("serialize and sign transfer tx object", async () => { const deserialized = decodeSignedTransaction(serialized); expect(encodeTransaction(deserialized)).toEqual(serialized); -}); \ No newline at end of file +}); + +test("test sign NEP-413 message with callback url", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + ) + ); + + const message: SignMessageParams = { + message: "Hello NEAR!", + nonce: new Uint8Array( + Buffer.from( + "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", + "base64" + ) + ), + recipient: "example.near", + callbackUrl: "http://localhost:3000", + }; + + const { signature } = await signer.signNep413Message( + message, + "round-toad.testnet" + ); + + const expectedSignature = new Uint8Array( + Buffer.from( + "zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==", + "base64" + ) + ); + + expect(signature).toStrictEqual(expectedSignature); +}); + +test("test sign NEP-413 message without callback url", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + ) + ); + + const message: SignMessageParams = { + message: "Hello NEAR!", + nonce: new Uint8Array( + Buffer.from( + "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", + "base64" + ) + ), + recipient: "example.near", + }; + + const { signature } = await signer.signNep413Message( + message, + "round-toad.testnet" + ); + + const expectedSignature = new Uint8Array( + Buffer.from( + "NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==", + "base64" + ) + ); + + expect(signature).toStrictEqual(expectedSignature); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 63d3b9014f..b2f44d646d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -574,6 +574,9 @@ importers: '@noble/hashes': specifier: 1.7.1 version: 1.7.1 + borsh: + specifier: 1.0.0 + version: 1.0.0 devDependencies: '@jest/globals': specifier: ^29.7.0 From 95c3860620e39e6a31cddbea7377fd35f86cfc4a Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 17 Apr 2025 21:41:08 +0200 Subject: [PATCH 21/57] fix: update exports in package.json to resolve importing issues --- packages/accounts/package.json | 9 ++++++--- packages/client/package.json | 9 ++++++--- packages/crypto/package.json | 9 ++++++--- packages/keystores-node/package.json | 9 ++++++--- packages/keystores/package.json | 9 ++++++--- packages/providers/package.json | 9 ++++++--- packages/signers/package.json | 9 ++++++--- packages/transactions/package.json | 9 ++++++--- packages/types/package.json | 9 ++++++--- packages/utils/package.json | 9 ++++++--- 10 files changed, 60 insertions(+), 30 deletions(-) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index a7ebdde3ce..ec68e31c7f 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -2,8 +2,9 @@ "name": "@near-js/accounts", "version": "1.4.1", "description": "Classes encapsulating account-specific functionality", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -49,6 +50,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } \ No newline at end of file diff --git a/packages/client/package.json b/packages/client/package.json index 868169d68c..55e6f449e2 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -2,8 +2,9 @@ "name": "@near-js/client", "version": "0.0.4", "description": "", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -36,6 +37,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/crypto/package.json b/packages/crypto/package.json index e4a7b7d4cf..8be483ff76 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -2,8 +2,9 @@ "name": "@near-js/crypto", "version": "1.4.2", "description": "Abstractions around NEAR-compatible elliptical curves and cryptographic keys", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -38,6 +39,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 0a25f0b3b9..7424d8e6e5 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -2,8 +2,9 @@ "name": "@near-js/keystores-node", "version": "0.1.2", "description": "KeyStore implementation for working with keys in the local filesystem", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -34,6 +35,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/keystores/package.json b/packages/keystores/package.json index ed159e1f6a..badd35b014 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -2,8 +2,9 @@ "name": "@near-js/keystores", "version": "0.2.2", "description": "Key storage and management implementations", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -33,6 +34,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/providers/package.json b/packages/providers/package.json index bd20229017..096379e26f 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -2,8 +2,9 @@ "name": "@near-js/providers", "version": "1.0.3", "description": "Library of implementations for interfacing with the NEAR blockchain", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -41,6 +42,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/signers/package.json b/packages/signers/package.json index 5eb081dfd6..ae98a21838 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -2,8 +2,9 @@ "name": "@near-js/signers", "version": "0.2.2", "description": "Modules for cryptographically signing messages", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -36,6 +37,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/transactions/package.json b/packages/transactions/package.json index fe83164b5c..2e0c49f9af 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -2,8 +2,9 @@ "name": "@near-js/transactions", "version": "1.3.3", "description": "Functions and data types for transactions on NEAR", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -37,6 +38,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/types/package.json b/packages/types/package.json index d778227a2c..907e8cb55f 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -2,8 +2,9 @@ "name": "@near-js/types", "version": "0.3.1", "description": "TypeScript types for working with the Near JS API", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -27,6 +28,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } diff --git a/packages/utils/package.json b/packages/utils/package.json index 054adc9bb8..d63461af3b 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -2,8 +2,9 @@ "name": "@near-js/utils", "version": "1.1.0", "description": "Common methods and constants for the NEAR API JavaScript client", - "main": "lib/esm/index.js", - "type": "module", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", "scripts": { "build": "pnpm compile:esm && pnpm compile:cjs", "compile:esm": "tsc -p tsconfig.json", @@ -35,6 +36,8 @@ ], "exports": { "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" } } From 02d0cfbfccce8d88f0c1a67270c1e96edcaff8a9 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:04:37 +0200 Subject: [PATCH 22/57] fix: add `peerDependencies` to each workspace package --- packages/accounts/package.json | 8 ++++++++ packages/client/package.json | 10 ++++++++++ packages/crypto/package.json | 4 ++++ packages/keystores-node/package.json | 4 ++++ packages/keystores/package.json | 4 ++++ packages/near-api-js/package.json | 13 +++++++++++++ packages/providers/package.json | 6 ++++++ packages/signers/package.json | 5 +++++ packages/transactions/package.json | 5 +++++ packages/utils/package.json | 3 +++ 10 files changed, 62 insertions(+) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index ec68e31c7f..ec42bfbe0d 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -45,6 +45,14 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/client/package.json b/packages/client/package.json index 55e6f449e2..6948cfe7d8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -32,6 +32,16 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0", + "@near-js/accounts": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 8be483ff76..b97973d5ab 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -34,6 +34,10 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/keystores-node/package.json b/packages/keystores-node/package.json index 7424d8e6e5..dfdb378975 100644 --- a/packages/keystores-node/package.json +++ b/packages/keystores-node/package.json @@ -30,6 +30,10 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/keystores/package.json b/packages/keystores/package.json index badd35b014..3d81e476ec 100644 --- a/packages/keystores/package.json +++ b/packages/keystores/package.json @@ -29,6 +29,10 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/types": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index e04aa82816..855b86e48e 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -41,6 +41,19 @@ "semver": "7.7.1", "ts-jest": "29.2.6" }, + "peerDependencies": { + "@near-js/accounts": "^2.0.0", + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/keystores-browser": "^2.0.0", + "@near-js/keystores-node": "^2.0.0", + "@near-js/providers": "^2.0.0", + "@near-js/signers": "^2.0.0", + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0", + "@near-js/wallet-account": "^2.0.0" + }, "keywords": [], "license": "(MIT AND Apache-2.0)", "scripts": { diff --git a/packages/providers/package.json b/packages/providers/package.json index 096379e26f..d6eda97893 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -34,6 +34,12 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/transactions": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0", + "@near-js/crypto": "^2.0.0" + }, "optionalDependencies": { "node-fetch": "2.6.7" }, diff --git a/packages/signers/package.json b/packages/signers/package.json index ae98a21838..d995188912 100644 --- a/packages/signers/package.json +++ b/packages/signers/package.json @@ -32,6 +32,11 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/keystores": "^2.0.0", + "@near-js/transactions": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/transactions/package.json b/packages/transactions/package.json index 2e0c49f9af..cfebf7d493 100644 --- a/packages/transactions/package.json +++ b/packages/transactions/package.json @@ -33,6 +33,11 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/crypto": "^2.0.0", + "@near-js/types": "^2.0.0", + "@near-js/utils": "^2.0.0" + }, "files": [ "lib" ], diff --git a/packages/utils/package.json b/packages/utils/package.json index d63461af3b..00e7fa6552 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -31,6 +31,9 @@ "tsconfig": "workspace:*", "typescript": "5.4.5" }, + "peerDependencies": { + "@near-js/types": "^2.0.0" + }, "files": [ "lib" ], From bad4cf64dbce8badc7892de45e85994a7ab98b38 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:05:13 +0200 Subject: [PATCH 23/57] fix: update changeset config to have shared version across `@near-js/*` packages --- .changeset/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/config.json b/.changeset/config.json index 125cdb8971..ebca3ed7b7 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,7 +2,7 @@ "$schema": "https://unpkg.com/@changesets/config@2.1.1/schema.json", "changelog": ["@changesets/changelog-github", { "repo": "near/near-api-js" }], "commit": false, - "fixed": [], + "fixed": [["@near-js/*"]], "linked": [], "access": "public", "baseBranch": "master", From 03f390b3a81784809c52804099154f1a98fbe821 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:18:58 +0200 Subject: [PATCH 24/57] feat: add `getNetworkId` method to Provider --- packages/providers/src/failover-rpc-provider.ts | 4 ++++ packages/providers/src/json-rpc-provider.ts | 14 ++++++++++++++ packages/providers/src/provider.ts | 2 ++ 3 files changed, 20 insertions(+) diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index 6cd61882f3..c807fee35e 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -115,6 +115,10 @@ export class FailoverRpcProvider implements Provider { return this.withBackoff((currentProvider) => currentProvider.status()); } + async getNetworkId(): Promise { + return this.withBackoff((currentProvider) => currentProvider.getNetworkId()); + } + public async viewAccessKey(accountId: string, publicKey: PublicKey, finalityQuery?: FinalityReference): Promise { return this.withBackoff((currentProvider) => currentProvider.viewAccessKey(accountId, publicKey, finalityQuery)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index ccfa4569d2..72861ad3ff 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -94,6 +94,9 @@ export class JsonRpcProvider implements Provider { /** @hidden */ readonly options: RequestOptions; + /** @hidden */ + private networkId: string | undefined; + /** * @param connectionInfo Connection info */ @@ -105,6 +108,17 @@ export class JsonRpcProvider implements Provider { backoff: REQUEST_RETRY_WAIT_BACKOFF }; this.options = Object.assign({}, defaultOptions, options); + this.networkId = undefined; + } + + public async getNetworkId(): Promise { + if (this.networkId) return this.networkId; + + const { chain_id } = await this.viewNodeStatus(); + + this.networkId = chain_id; + + return this.networkId; } public async viewAccessKey( diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index d5a30fd4d6..9e8f57d5ab 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -41,6 +41,8 @@ export interface Provider { /** @deprecated use {@link viewNodeStatus} */ status(): Promise; + getNetworkId(): Promise; + viewAccessKey(accountId: string, publicKey: PublicKey | string, finalityQuery?: FinalityReference): Promise; viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise; From 26cc3ab6e2d1a11d72d87fe71b0abc45a768c81e Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:22:47 +0200 Subject: [PATCH 25/57] fix: update parameters of signNep413Message --- packages/signers/src/key_pair_signer.ts | 29 +++++++++++++++++-------- packages/signers/src/signer.ts | 9 +++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts index 0d55945595..87d51411b8 100644 --- a/packages/signers/src/key_pair_signer.ts +++ b/packages/signers/src/key_pair_signer.ts @@ -40,11 +40,14 @@ export class KeyPairSigner extends Signer { } public async signNep413Message( - params: SignMessageParams, - accountId: string + message: string, + accountId: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: string ): Promise { - if (params.nonce.length !== 32) - throw new Error(`Nonce must be [u8; 32]`); + if (nonce.length !== 32) + throw new Error(`Nonce must be exactly 32 bytes long`); const pk = this.key.getPublicKey(); @@ -52,13 +55,21 @@ export class KeyPairSigner extends Signer { const PREFIX = 2147484061; const serializedPrefix = serialize("u32", PREFIX); + const params: SignMessageParams = { + message, + recipient, + nonce, + callbackUrl + }; const serializedParams = serialize(Nep413MessageSchema, params); - const message = new Uint8Array(serializedPrefix.length + serializedParams.length); - message.set(serializedPrefix); - message.set(serializedParams, serializedPrefix.length) + const serializedMessage = new Uint8Array( + serializedPrefix.length + serializedParams.length + ); + serializedMessage.set(serializedPrefix); + serializedMessage.set(serializedParams, serializedPrefix.length); - const hash = new Uint8Array(sha256(message)); + const hash = new Uint8Array(sha256(serializedMessage)); const { signature } = this.key.sign(hash); @@ -94,7 +105,7 @@ export class KeyPairSigner extends Signer { return [hash, signedTx]; } - public async signDelegate( + public async signDelegateAction( delegateAction: DelegateAction ): Promise<[Uint8Array, SignedDelegate]> { const pk = this.key.getPublicKey(); diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index 2606a541d7..0bfb04faf3 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -47,15 +47,18 @@ export abstract class Signer { * @param accountId */ public abstract signNep413Message( - params: SignMessageParams, - accountId: string + message: string, + accountId: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: string ): Promise; public abstract signTransaction( transaction: Transaction ): Promise<[Uint8Array, SignedTransaction]>; - public abstract signDelegate( + public abstract signDelegateAction( delegateAction: DelegateAction ): Promise<[Uint8Array, SignedDelegate]>; } From 66adead35b87fbed3c0e610fd2e82ef5f5117cc2 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:23:38 +0200 Subject: [PATCH 26/57] feat: impl new methods signMetaTransaction and signMessage for Account --- packages/accounts/src/account.ts | 57 +++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index b5ae63091c..4e0fe98823 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -42,6 +42,8 @@ import { IntoConnection, ViewFunctionCallOptions, } from "./interface"; +import { randomBytes } from "crypto"; +import { SignedMessage } from "@near-js/signers/lib/esm/signer"; const { addKey, @@ -291,6 +293,57 @@ export class Account extends PublicAccount implements IntoConnection { return this.signer.signTransaction(tx); } + /** + * Create a signed transaction which can be broadcasted to the relayer + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the neta transaction + * @param blockHeightTtl number of blocks after which a meta transaction will expire if not processed + */ + public async signMetaTransaction( + receiverId: string, + actions: Action[], + blockHeightTtl: number = 200 + ): Promise<[Uint8Array, SignedDelegate]> { + const pk = await this.signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); + + const nonce = BigInt(accessKey.nonce) + 1n; + + const { header } = await this.provider.viewBlock({ + finality: "final", + }); + + const maxBlockHeight = BigInt(header.height) + BigInt(blockHeightTtl); + + const delegateAction = buildDelegateAction({ + receiverId: receiverId, + senderId: this.accountId, + actions: actions, + publicKey: pk, + nonce: nonce, + maxBlockHeight: maxBlockHeight, + }); + + return this.signer.signDelegateAction(delegateAction); + } + + public async signMessage( + message: string, + recipient: string, + callbackUrl?: string + ): Promise { + const nonce = new Uint8Array(randomBytes(32)); + + return this.signer.signNep413Message( + message, + this.accountId, + recipient, + nonce, + callbackUrl + ); + } + /** * Sign a transaction to perform a list of actions and broadcast it using the RPC API. * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } @@ -810,6 +863,8 @@ export class Account extends PublicAccount implements IntoConnection { } /** + * @deprecated please use {@link Account.signMetaTransaction} instead + * * Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance * * @param options Options for the transaction. @@ -837,7 +892,7 @@ export class Account extends PublicAccount implements IntoConnection { senderId: this.accountId, }); - const [, signedDelegate] = await this.signer.signDelegate( + const [, signedDelegate] = await this.signer.signDelegateAction( delegateAction ); From e46c7d4dc1382a27d749fa37b42305cc4465555b Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:24:00 +0200 Subject: [PATCH 27/57] fix: export SignedMessage from `@near-js/signers` --- packages/signers/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/signers/src/index.ts b/packages/signers/src/index.ts index 14251facc5..11dd2b730c 100644 --- a/packages/signers/src/index.ts +++ b/packages/signers/src/index.ts @@ -1,2 +1,2 @@ export { KeyPairSigner } from './key_pair_signer'; -export { Signer } from './signer'; +export { Signer, SignedMessage } from './signer'; From 978e40929a5d7387aa7ae347c71bc1dd42074b8c Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 21 Apr 2025 22:24:16 +0200 Subject: [PATCH 28/57] test: add tests for KeyPairSigner --- packages/signers/test/key_pair_signer.test.ts | 130 ++++++++++++++---- 1 file changed, 107 insertions(+), 23 deletions(-) diff --git a/packages/signers/test/key_pair_signer.test.ts b/packages/signers/test/key_pair_signer.test.ts index 7faa00c089..36e1a2c82a 100644 --- a/packages/signers/test/key_pair_signer.test.ts +++ b/packages/signers/test/key_pair_signer.test.ts @@ -8,12 +8,12 @@ import { encodeTransaction, actionCreators, decodeSignedTransaction, + buildDelegateAction, } from "@near-js/transactions"; -import { SignMessageParams } from "../src/signer"; global.TextEncoder = TextEncoder; -test("test throws if transaction gets signed with different public key", async () => { +test("test sign transaction with different public key", async () => { const signer = new KeyPairSigner( KeyPair.fromString( "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" @@ -37,7 +37,7 @@ test("test throws if transaction gets signed with different public key", async ( ); }); -test("test transaction gets signed with relevant public key", async () => { +test("test sign transaction with relevant public key", async () => { const signer = new KeyPairSigner( KeyPair.fromString( "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" @@ -112,21 +112,17 @@ test("test sign NEP-413 message with callback url", async () => { ) ); - const message: SignMessageParams = { - message: "Hello NEAR!", - nonce: new Uint8Array( + const { signature } = await signer.signNep413Message( + "Hello NEAR!", + "round-toad.testnet", + "example.near", + new Uint8Array( Buffer.from( "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", "base64" ) ), - recipient: "example.near", - callbackUrl: "http://localhost:3000", - }; - - const { signature } = await signer.signNep413Message( - message, - "round-toad.testnet" + "http://localhost:3000" ); const expectedSignature = new Uint8Array( @@ -146,20 +142,16 @@ test("test sign NEP-413 message without callback url", async () => { ) ); - const message: SignMessageParams = { - message: "Hello NEAR!", - nonce: new Uint8Array( + const { signature } = await signer.signNep413Message( + "Hello NEAR!", + "round-toad.testnet", + "example.near", + new Uint8Array( Buffer.from( "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", "base64" ) - ), - recipient: "example.near", - }; - - const { signature } = await signer.signNep413Message( - message, - "round-toad.testnet" + ) ); const expectedSignature = new Uint8Array( @@ -171,3 +163,95 @@ test("test sign NEP-413 message without callback url", async () => { expect(signature).toStrictEqual(expectedSignature); }); + +test("test sign NEP-413 message throws error on invalid nonce", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + ) + ); + + await expect(() => + signer.signNep413Message( + "Hello NEAR!", + "round-toad.testnet", + "example.near", + new Uint8Array(new Array(28)) + ) + ).rejects.toThrow(); +}); + +test("test getPublicKey returns correct public key", async () => { + const keyPair = KeyPair.fromString( + "ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB" + ); + const signer = new KeyPairSigner(keyPair); + + const publicKey = (await signer.getPublicKey()).toString(); + expect(publicKey).toBe( + "ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ" + ); +}); + +test("test static fromSecretKey creates a corresponding KeyPair", async () => { + const signer = KeyPairSigner.fromSecretKey( + "ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB" + ); + + const publicKey = (await signer.getPublicKey()).toString(); + expect(publicKey).toBe( + "ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ" + ); +}); + +test("test sign delegate action", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + ) + ); + + const delegateAction = buildDelegateAction({ + receiverId: "receiver.testnet", + senderId: "sender.testnet", + nonce: 1n, + maxBlockHeight: 1848319858n, + actions: [], + publicKey: await signer.getPublicKey(), + }); + + const [hash, { signature }] = await signer.signDelegateAction( + delegateAction + ); + + expect(Buffer.from(hash).toString("hex")).toBe( + "6d35575b3566fddf04f79317c3e574eb89c5bc228c5e755f2a3179e164dbb36b" + ); + + expect(Buffer.from(signature.signature.data).toString("hex")).toBe( + "77e5e92877d64ae27b1facc5ddf09205863b2af7a8264d2d01a44a9684975a17f2b8b9d706fbe3c3ffa2cd4377b49f20614245549dc061bcaccc0ad3abd81c01" + ); +}); + +test("test sign delegate action with wrong public key", async () => { + const signer = new KeyPairSigner( + KeyPair.fromString( + "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + ) + ); + + const delegateAction = buildDelegateAction({ + receiverId: "receiver.testnet", + senderId: "sender.testnet", + nonce: 1n, + maxBlockHeight: 1848319858n, + actions: [], + publicKey: KeyPair.fromString( + "ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV" + ).getPublicKey(), + }); + + await expect(() => + signer.signDelegateAction(delegateAction) + ).rejects.toThrow(); +}); From c12ad01f4fe9aaeb03f54163000809b876541dcb Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 22 Apr 2025 22:29:54 +0200 Subject: [PATCH 29/57] fix: move back read-only methods from PublicAccount to Account --- packages/accounts/src/account.ts | 141 +++++++++++------- packages/accounts/src/index.ts | 1 - .../accounts/test/account.access_key.test.ts | 4 +- .../accounts/test/account_multisig.test.ts | 6 +- packages/near-api-js/src/account.ts | 1 - packages/near-api-js/src/common-index.ts | 3 +- 6 files changed, 90 insertions(+), 66 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 4e0fe98823..78e8361c3c 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -87,6 +87,10 @@ export interface AccountAuthorizedApp { publicKey: string; } +interface SignerOptions { + signer?: Signer; +} + /** * Options used to initiate sining and sending transactions */ @@ -124,17 +128,22 @@ interface SignedDelegateOptions { receiverId: string; } -export class PublicAccount { +/** + * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. + */ +export class Account implements IntoConnection { public readonly accountId: string; public readonly provider: Provider; + public readonly signer?: Signer; - constructor(accountId: string, provider: Provider) { + constructor(accountId: string, provider: Provider, signer?: Signer) { this.accountId = accountId; this.provider = provider; + this.signer = signer; } - public withSigner(signer: Signer): Account { - return new Account(this.accountId, this.provider, signer); + public getConnection(): Connection { + return new Connection("", this.provider, this.signer, ""); } /** @@ -229,22 +238,6 @@ export class PublicAccount { { finality: "optimistic" } ); } -} - -/** - * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. - */ -export class Account extends PublicAccount implements IntoConnection { - public readonly signer: Signer; - - constructor(accountId: string, provider: Provider, signer: Signer) { - super(accountId, provider); - this.signer = signer; - } - - public getConnection(): Connection { - return new Connection('', this.provider, this.signer, ''); - } /** * Returns basic NEAR account information via the `view_account` RPC query method @@ -268,9 +261,14 @@ export class Account extends PublicAccount implements IntoConnection { */ public async signTransaction( receiverId: string, - actions: Action[] + actions: Action[], + opts?: SignerOptions ): Promise<[Uint8Array, SignedTransaction]> { - const pk = await this.signer.getPublicKey(); + const signer = opts?.signer || this.signer; + + if (!signer) throw new Error(`Signer is required`); + + const pk = await signer.getPublicKey(); const accessKey = await this.getAccessKey(pk); @@ -290,7 +288,7 @@ export class Account extends PublicAccount implements IntoConnection { baseDecode(recentBlockHash) ); - return this.signer.signTransaction(tx); + return signer.signTransaction(tx); } /** @@ -302,9 +300,14 @@ export class Account extends PublicAccount implements IntoConnection { public async signMetaTransaction( receiverId: string, actions: Action[], - blockHeightTtl: number = 200 + blockHeightTtl: number = 200, + opts?: SignerOptions ): Promise<[Uint8Array, SignedDelegate]> { - const pk = await this.signer.getPublicKey(); + const signer = opts?.signer || this.signer; + + if (!signer) throw new Error(`Signer is required`); + + const pk = await signer.getPublicKey(); const accessKey = await this.getAccessKey(pk); @@ -325,17 +328,22 @@ export class Account extends PublicAccount implements IntoConnection { maxBlockHeight: maxBlockHeight, }); - return this.signer.signDelegateAction(delegateAction); + return signer.signDelegateAction(delegateAction); } public async signMessage( message: string, recipient: string, - callbackUrl?: string + callbackUrl?: string, + opts?: SignerOptions ): Promise { + const signer = opts?.signer || this.signer; + + if (!signer) throw new Error(`Signer is required`); + const nonce = new Uint8Array(randomBytes(32)); - return this.signer.signNep413Message( + return signer.signNep413Message( message, this.accountId, recipient, @@ -358,7 +366,7 @@ export class Account extends PublicAccount implements IntoConnection { receiverId, actions, returnError, - }: SignAndSendTransactionOptions): Promise { + }: SignAndSendTransactionOptions, opts?: SignerOptions): Promise { let txHash, signedTx; // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) const result = await exponentialBackoff( @@ -368,7 +376,8 @@ export class Account extends PublicAccount implements IntoConnection { async () => { [txHash, signedTx] = await this.signTransaction( receiverId, - actions + actions, + opts ); try { @@ -404,7 +413,10 @@ export class Account extends PublicAccount implements IntoConnection { ); } - printTxOutcomeLogsAndFailures({ contractId: signedTx.transaction.receiverId, outcome: result }); + printTxOutcomeLogsAndFailures({ + contractId: signedTx.transaction.receiverId, + outcome: result, + }); // Should be falsy if result.status.Failure is null if ( @@ -449,6 +461,8 @@ export class Account extends PublicAccount implements IntoConnection { receiverId: string, actions: Action[] ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { + if (!this.signer) throw new Error(`Signer is required`); + const publicKey = await this.signer.getPublicKey(); if (!publicKey) { throw new TypedError( @@ -514,7 +528,7 @@ export class Account extends PublicAccount implements IntoConnection { publicKey: string | PublicKey, data: Uint8Array, amount: bigint - ): Promise { + ): Promise { const accessKey = fullAccessKey(); await this.signAndSendTransaction({ receiverId: contractId, @@ -525,7 +539,7 @@ export class Account extends PublicAccount implements IntoConnection { deployContract(data), ], }); - return new PublicAccount(contractId, this.provider); + return new Account(contractId, this.provider); } /** @@ -569,7 +583,8 @@ export class Account extends PublicAccount implements IntoConnection { public async createTopLevelAccount( account: string, pk: PublicKey | string, - amount: bigint | string | number + amount: bigint | string | number, + opts?: SignerOptions ): Promise { const topAccount = account.split(".").at(-1); @@ -593,13 +608,14 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: topAccount, actions: actions, - }); + }, opts); } public async createSubAccount( accountOrPrefix: string, pk: PublicKey | string, - amount: bigint | string | number + amount: bigint | string | number, + opts?: SignerOptions ): Promise { const newAccountId = accountOrPrefix.includes(".") ? accountOrPrefix @@ -613,14 +629,15 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: newAccountId, actions: actions, - }); + }, opts); } public async createSubAccountAndDeployContract( accountOrPrefix: string, pk: PublicKey | string, amount: bigint | string | number, - code: Uint8Array + code: Uint8Array, + opts?: SignerOptions ): Promise { const newAccountId = accountOrPrefix.includes(".") ? accountOrPrefix @@ -635,14 +652,15 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: newAccountId, actions: actions, - }); + }, opts); } /** * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted */ public async deleteAccount( - beneficiaryId: string + beneficiaryId: string, + opts?: SignerOptions ): Promise { Logger.log( "Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting." @@ -653,21 +671,22 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } /** * @param code The compiled contract code bytes */ public async deployContract( - code: Uint8Array + code: Uint8Array, + opts?: SignerOptions ): Promise { const actions = [deployContract(code)]; return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } /** @hidden */ @@ -784,21 +803,23 @@ export class Account extends PublicAccount implements IntoConnection { } public async addFullAccessKey( - pk: PublicKey | string + pk: PublicKey | string, + opts?: SignerOptions ): Promise { const actions = [addKey(PublicKey.from(pk), fullAccessKey())]; return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } public async addFunctionAccessKey( pk: PublicKey | string, receiverId: string, methodNames: string[], - allowance?: bigint | string | number + allowance?: bigint | string | number, + opts?: SignerOptions ): Promise { const actions = [ addKey( @@ -814,7 +835,7 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } /** @@ -822,26 +843,28 @@ export class Account extends PublicAccount implements IntoConnection { * @returns {Promise} */ public async deleteKey( - publicKey: string | PublicKey + publicKey: string | PublicKey, + opts?: SignerOptions ): Promise { const actions = [deleteKey(PublicKey.from(publicKey))]; return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } public async transfer( receiverId: string, - amount: bigint | string | number + amount: bigint | string | number, + opts?: SignerOptions ): Promise { const actions = [transfer(BigInt(amount))]; return this.signAndSendTransaction({ receiverId: receiverId, actions: actions, - }); + }, opts); } /** @@ -852,14 +875,15 @@ export class Account extends PublicAccount implements IntoConnection { */ public async stake( publicKey: string | PublicKey, - amount: bigint | string | number + amount: bigint | string | number, + opts?: SignerOptions ): Promise { const actions = [stake(BigInt(amount), PublicKey.from(publicKey))]; return this.signAndSendTransaction({ receiverId: this.accountId, actions: actions, - }); + }, opts); } /** @@ -879,6 +903,8 @@ export class Account extends PublicAccount implements IntoConnection { }: SignedDelegateOptions): Promise { const { header } = await this.provider.block({ finality: "final" }); + if (!this.signer) throw new Error(`Signer is required`); + const pk = await this.signer.getPublicKey(); const accessKey = await this.getAccessKey(pk); @@ -931,7 +957,7 @@ export class Account extends PublicAccount implements IntoConnection { async viewFunction(options: ViewFunctionCallOptions): Promise { return await viewFunction( - new Connection("", this.provider, this.signer, ""), + this.getConnection(), options ); } @@ -951,7 +977,7 @@ export class Account extends PublicAccount implements IntoConnection { blockQuery: BlockReference = { finality: "optimistic" } ): Promise> { return await viewState( - new Connection("", this.provider, this.signer, ""), + this.getConnection(), this.accountId, prefix, blockQuery @@ -1132,7 +1158,8 @@ export class Account extends PublicAccount implements IntoConnection { methodName: string, args: Record = {}, deposit: bigint | string | number = 0n, - gas: bigint | string | number = DEFAULT_FUNCTION_CALL_GAS + gas: bigint | string | number = DEFAULT_FUNCTION_CALL_GAS, + opts?: SignerOptions ): Promise { const actions = [ functionCall(methodName, args, BigInt(gas), BigInt(deposit)), @@ -1141,6 +1168,6 @@ export class Account extends PublicAccount implements IntoConnection { return this.signAndSendTransaction({ receiverId: contractId, actions: actions, - }); + }, opts); } } diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index c94bde5a18..f8fe945e67 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -1,5 +1,4 @@ export { - PublicAccount, Account, AccountBalance, AccountAuthorizedApp, diff --git a/packages/accounts/test/account.access_key.test.ts b/packages/accounts/test/account.access_key.test.ts index c454a0bbad..ad2c3c2c78 100644 --- a/packages/accounts/test/account.access_key.test.ts +++ b/packages/accounts/test/account.access_key.test.ts @@ -33,7 +33,7 @@ test('make function call using access key', async() => { const setCallValue = generateUniqueString('setCallPrefix'); await contract.setValue({ // Override signer in the workingAccount to the given access key. - signerAccount: workingAccount.withSigner(new KeyPairSigner(keyPair)), + signerAccount: new Account(workingAccount.accountId, workingAccount.provider, new KeyPairSigner(keyPair)), args: { value: setCallValue }, }); expect(await contract.getValue()).toEqual(setCallValue); @@ -45,7 +45,7 @@ test('remove access key no longer works', async() => { await nearjs.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000); await nearjs.accountCreator.masterAccount.deleteKey(publicKey); // Override account in the Contract to the masterAccount with the given access key. - contract.account = (nearjs.accountCreator.masterAccount as Account).withSigner(new KeyPairSigner(keyPair)); + contract.account = new Account(nearjs.accountCreator.masterAccount.accountId, nearjs.accountCreator.masterAccount.provider, new KeyPairSigner(keyPair)); let failed = true; try { diff --git a/packages/accounts/test/account_multisig.test.ts b/packages/accounts/test/account_multisig.test.ts index 2320a08fb4..51ee8e19fb 100644 --- a/packages/accounts/test/account_multisig.test.ts +++ b/packages/accounts/test/account_multisig.test.ts @@ -28,13 +28,13 @@ const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) = verifyCode: () => ({ }), // TODO: Is there any content needed in result? onAddRequestResult: async () => { const { requestId } = account2fa.getRequest(); - // 2nd confirmation signing with confirmKey from Account instance - await account.withSigner(new KeyPairSigner(account2fa.confirmKey)).signAndSendTransaction({ + // 2nd confirmation signing with confirmKey from Account instance + await account.signAndSendTransaction({ receiverId: accountId, actions: [ functionCall('confirm', { request_id: requestId }, MULTISIG_GAS, MULTISIG_DEPOSIT) ] - }); + }, {signer: new KeyPairSigner(account2fa.confirmKey)}); } }); account2fa.confirmKey = KeyPair.fromRandom('ed25519'); diff --git a/packages/near-api-js/src/account.ts b/packages/near-api-js/src/account.ts index 56ea6f9015..9caac34c26 100644 --- a/packages/near-api-js/src/account.ts +++ b/packages/near-api-js/src/account.ts @@ -1,5 +1,4 @@ export { - PublicAccount, Account, AccountBalance, AccountAuthorizedApp, diff --git a/packages/near-api-js/src/common-index.ts b/packages/near-api-js/src/common-index.ts index dc2ba1f010..fb4253f186 100644 --- a/packages/near-api-js/src/common-index.ts +++ b/packages/near-api-js/src/common-index.ts @@ -4,7 +4,7 @@ import * as utils from './utils'; import * as transactions from './transaction'; import * as validators from './validators'; -import { Account, PublicAccount } from './account'; +import { Account } from './account'; import * as multisig from './account_multisig'; import * as accountCreator from './account_creator'; import { Connection } from './connection'; @@ -26,7 +26,6 @@ export { validators, multisig, - PublicAccount, Account, Connection, Contract, From 63e6a6fdd2bbc31d2907a89b5f4a4f51a412e78a Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 22 Apr 2025 22:31:11 +0200 Subject: [PATCH 30/57] fix: remove deprecated jsvm --- packages/accounts/src/account.ts | 192 +++++++++---------- packages/accounts/src/connection.ts | 4 - packages/accounts/src/interface.ts | 4 - packages/accounts/src/utils.ts | 78 ++++---- packages/accounts/test/contract.test.ts | 4 +- packages/accounts/test/contract_abi.test.ts | 4 +- packages/near-api-js/src/transaction.ts | 2 +- packages/transactions/src/action_creators.ts | 8 - packages/wallet-account/src/near.ts | 6 - 9 files changed, 138 insertions(+), 164 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 78e8361c3c..1832bf45f5 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -143,7 +143,7 @@ export class Account implements IntoConnection { } public getConnection(): Connection { - return new Connection("", this.provider, this.signer, ""); + return new Connection("", this.provider, this.signer); } /** @@ -362,11 +362,10 @@ export class Account implements IntoConnection { * @param options.returnError Whether to return an error if the transaction fails. * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. */ - async signAndSendTransaction({ - receiverId, - actions, - returnError, - }: SignAndSendTransactionOptions, opts?: SignerOptions): Promise { + async signAndSendTransaction( + { receiverId, actions, returnError }: SignAndSendTransactionOptions, + opts?: SignerOptions + ): Promise { let txHash, signedTx; // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) const result = await exponentialBackoff( @@ -605,10 +604,13 @@ export class Account implements IntoConnection { ), ]; - return this.signAndSendTransaction({ - receiverId: topAccount, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: topAccount, + actions: actions, + }, + opts + ); } public async createSubAccount( @@ -626,10 +628,13 @@ export class Account implements IntoConnection { addKey(PublicKey.from(pk), fullAccessKey()), ]; - return this.signAndSendTransaction({ - receiverId: newAccountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: newAccountId, + actions: actions, + }, + opts + ); } public async createSubAccountAndDeployContract( @@ -649,10 +654,13 @@ export class Account implements IntoConnection { deployContract(code), ]; - return this.signAndSendTransaction({ - receiverId: newAccountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: newAccountId, + actions: actions, + }, + opts + ); } /** @@ -668,10 +676,13 @@ export class Account implements IntoConnection { const actions = [deleteAccount(beneficiaryId)]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } /** @@ -683,21 +694,13 @@ export class Account implements IntoConnection { ): Promise { const actions = [deployContract(code)]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); - } - - /** @hidden */ - private encodeJSContractArgs(contractId: string, method: string, args) { - return Buffer.concat([ - Buffer.from(contractId), - Buffer.from([0]), - Buffer.from(method), - Buffer.from([0]), - Buffer.from(args), - ]); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } /** @@ -713,7 +716,6 @@ export class Account implements IntoConnection { * @param options.walletMeta Metadata for wallet integration. * @param options.walletCallbackUrl The callback URL for wallet integration. * @param options.stringify A function to convert input arguments into bytes array - * @param options.jsContract Whether the contract is from JS SDK, automatically encodes args from JS SDK to binary. * @returns {Promise} A promise that resolves to the final execution outcome of the function call. */ async functionCall({ @@ -725,42 +727,22 @@ export class Account implements IntoConnection { walletMeta, walletCallbackUrl, stringify, - jsContract, }: ChangeFunctionCallOptions): Promise { this.validateArgs(args); - let functionCallArgs; - if (jsContract) { - const encodedArgs = this.encodeJSContractArgs( - contractId, - methodName, - JSON.stringify(args) - ); - functionCallArgs = [ - "call_js_contract", - encodedArgs, - gas, - attachedDeposit, - null, - true, - ]; - } else { - const stringifyArg = - stringify === undefined ? stringifyJsonOrBytes : stringify; - functionCallArgs = [ - methodName, - args, - gas, - attachedDeposit, - stringifyArg, - false, - ]; - } + const stringifyArg = + stringify === undefined ? stringifyJsonOrBytes : stringify; + const functionCallArgs = [ + methodName, + args, + gas, + attachedDeposit, + stringifyArg, + false, + ]; return this.signAndSendTransaction({ - receiverId: jsContract - ? process.env.NEAR_JSVM_ACCOUNT_ID - : contractId, + receiverId: contractId, // eslint-disable-next-line prefer-spread actions: [functionCall.apply(void 0, functionCallArgs)], walletMeta, @@ -808,10 +790,13 @@ export class Account implements IntoConnection { ): Promise { const actions = [addKey(PublicKey.from(pk), fullAccessKey())]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } public async addFunctionAccessKey( @@ -832,10 +817,13 @@ export class Account implements IntoConnection { ), ]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } /** @@ -848,10 +836,13 @@ export class Account implements IntoConnection { ): Promise { const actions = [deleteKey(PublicKey.from(publicKey))]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } public async transfer( @@ -861,10 +852,13 @@ export class Account implements IntoConnection { ): Promise { const actions = [transfer(BigInt(amount))]; - return this.signAndSendTransaction({ - receiverId: receiverId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: receiverId, + actions: actions, + }, + opts + ); } /** @@ -880,10 +874,13 @@ export class Account implements IntoConnection { ): Promise { const actions = [stake(BigInt(amount), PublicKey.from(publicKey))]; - return this.signAndSendTransaction({ - receiverId: this.accountId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: actions, + }, + opts + ); } /** @@ -950,16 +947,12 @@ export class Account implements IntoConnection { * @param options.args Any arguments to the view contract method, wrapped in JSON * @param options.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json. * @param options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param options.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. * @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). * @returns {Promise} */ async viewFunction(options: ViewFunctionCallOptions): Promise { - return await viewFunction( - this.getConnection(), - options - ); + return await viewFunction(this.getConnection(), options); } /** @@ -1165,9 +1158,12 @@ export class Account implements IntoConnection { functionCall(methodName, args, BigInt(gas), BigInt(deposit)), ]; - return this.signAndSendTransaction({ - receiverId: contractId, - actions: actions, - }, opts); + return this.signAndSendTransaction( + { + receiverId: contractId, + actions: actions, + }, + opts + ); } } diff --git a/packages/accounts/src/connection.ts b/packages/accounts/src/connection.ts index 83020c77be..084a397c15 100644 --- a/packages/accounts/src/connection.ts +++ b/packages/accounts/src/connection.ts @@ -50,18 +50,15 @@ export class Connection implements IntoConnection { readonly networkId: string; readonly provider: Provider; readonly signer: Signer; - readonly jsvmAccountId: string; constructor( networkId: string, provider: Provider, signer: Signer, - jsvmAccountId: string ) { this.networkId = networkId; this.provider = provider; this.signer = signer; - this.jsvmAccountId = jsvmAccountId; } getConnection(): Connection { @@ -78,7 +75,6 @@ export class Connection implements IntoConnection { config.networkId, provider, signer, - config.jsvmAccountId ); } } diff --git a/packages/accounts/src/interface.ts b/packages/accounts/src/interface.ts index bc69771793..0c8b222d35 100644 --- a/packages/accounts/src/interface.ts +++ b/packages/accounts/src/interface.ts @@ -26,10 +26,6 @@ export interface FunctionCallOptions { * Convert input arguments into bytes array. */ stringify?: (input: any) => Buffer; - /** - * Is contract from JS SDK, automatically encodes args from JS SDK to binary. - */ - jsContract?: boolean; } export interface ChangeFunctionCallOptions extends FunctionCallOptions { diff --git a/packages/accounts/src/utils.ts b/packages/accounts/src/utils.ts index 6a0bdca49b..a5565760b9 100644 --- a/packages/accounts/src/utils.ts +++ b/packages/accounts/src/utils.ts @@ -3,10 +3,10 @@ import { BlockReference, CodeResult, PositionalArgsError, -} from '@near-js/types'; -import { Connection } from './connection'; -import { printTxOutcomeLogs } from '@near-js/utils'; -import { ViewFunctionCallOptions } from './interface'; +} from "@near-js/types"; +import { Connection } from "./connection"; +import { printTxOutcomeLogs } from "@near-js/utils"; +import { ViewFunctionCallOptions } from "./interface"; function parseJsonFromRawResponse(response: Uint8Array): any { return JSON.parse(Buffer.from(response).toString()); @@ -17,20 +17,17 @@ function bytesJsonStringify(input: any): Buffer { } export function validateArgs(args: any) { - const isUint8Array = args.byteLength !== undefined && args.byteLength === args.length; + const isUint8Array = + args.byteLength !== undefined && args.byteLength === args.length; if (isUint8Array) { return; } - if (Array.isArray(args) || typeof args !== 'object') { + if (Array.isArray(args) || typeof args !== "object") { throw new PositionalArgsError(); } } -export function encodeJSContractArgs(contractId: string, method: string, args) { - return Buffer.concat([Buffer.from(contractId), Buffer.from([0]), Buffer.from(method), Buffer.from([0]), Buffer.from(args)]); -} - /** * Returns the state (key value pairs) of account's contract based on the key prefix. * Pass an empty string for prefix if you would like to return the entire state. @@ -41,21 +38,25 @@ export function encodeJSContractArgs(contractId: string, method: string, args) { * @param prefix allows to filter which keys should be returned. Empty prefix means all keys. String prefix is utf-8 encoded. * @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). */ -export async function viewState(connection: Connection, accountId: string, prefix: string | Uint8Array, blockQuery: BlockReference = { finality: 'optimistic' }): Promise> { +export async function viewState( + connection: Connection, + accountId: string, + prefix: string | Uint8Array, + blockQuery: BlockReference = { finality: "optimistic" } +): Promise> { const { values } = await connection.provider.query({ - request_type: 'view_state', + request_type: "view_state", ...blockQuery, account_id: accountId, - prefix_base64: Buffer.from(prefix).toString('base64') + prefix_base64: Buffer.from(prefix).toString("base64"), }); return values.map(({ key, value }) => ({ - key: Buffer.from(key, 'base64'), - value: Buffer.from(value, 'base64') + key: Buffer.from(key, "base64"), + value: Buffer.from(value, "base64"), })); } - /** * Invoke a contract view function using the RPC API. * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) @@ -66,40 +67,39 @@ export async function viewState(connection: Connection, accountId: string, prefi * @param options.args Any arguments to the view contract method, wrapped in JSON * @param options.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json. * @param options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param options.jsContract Is contract from JS SDK, automatically encodes args from JS SDK to binary. * @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). * @returns {Promise} */ -export async function viewFunction(connection: Connection, { - contractId, - methodName, - args = {}, - parse = parseJsonFromRawResponse, - stringify = bytesJsonStringify, - jsContract = false, - blockQuery = { finality: 'optimistic' } -}: ViewFunctionCallOptions): Promise { - let encodedArgs; - +export async function viewFunction( + connection: Connection, + { + contractId, + methodName, + args = {}, + parse = parseJsonFromRawResponse, + stringify = bytesJsonStringify, + blockQuery = { finality: "optimistic" }, + }: ViewFunctionCallOptions +): Promise { validateArgs(args); - if (jsContract) { - encodedArgs = encodeJSContractArgs(contractId, methodName, Object.keys(args).length > 0 ? JSON.stringify(args) : ''); - } else { - encodedArgs = stringify(args); - } + const encodedArgs = stringify(args); const result = await connection.provider.query({ - request_type: 'call_function', + request_type: "call_function", ...blockQuery, - account_id: jsContract ? connection.jsvmAccountId : contractId, - method_name: jsContract ? 'view_js_contract' : methodName, - args_base64: encodedArgs.toString('base64') + account_id: contractId, + method_name: methodName, + args_base64: encodedArgs.toString("base64"), }); if (result.logs) { printTxOutcomeLogs({ contractId, logs: result.logs }); } - return result.result && result.result.length > 0 && parse(Buffer.from(result.result)); -} \ No newline at end of file + return ( + result.result && + result.result.length > 0 && + parse(Buffer.from(result.result)) + ); +} diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index a612b8d094..19375dd6c6 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -8,8 +8,8 @@ const account = Object.setPrototypeOf({ getConnection() { return {}; }, - viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery }) { - return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + viewFunction({ contractId, methodName, args, parse, stringify, blockQuery }) { + return { this: this, contractId, methodName, args, parse, stringify, blockQuery }; }, functionCall() { return this; diff --git a/packages/accounts/test/contract_abi.test.ts b/packages/accounts/test/contract_abi.test.ts index e172795375..e0f070a3ef 100644 --- a/packages/accounts/test/contract_abi.test.ts +++ b/packages/accounts/test/contract_abi.test.ts @@ -97,8 +97,8 @@ const account = Object.setPrototypeOf({ getConnection() { return {}; }, - viewFunction({ contractId, methodName, args, parse, stringify, jsContract, blockQuery }) { - return { this: this, contractId, methodName, args, parse, stringify, jsContract, blockQuery }; + viewFunction({ contractId, methodName, args, parse, stringify, blockQuery }) { + return { this: this, contractId, methodName, args, parse, stringify, blockQuery }; }, functionCall() { return this; diff --git a/packages/near-api-js/src/transaction.ts b/packages/near-api-js/src/transaction.ts index 76a02c76ee..dcc5fc10ea 100644 --- a/packages/near-api-js/src/transaction.ts +++ b/packages/near-api-js/src/transaction.ts @@ -32,7 +32,7 @@ export const deleteAccount = (beneficiaryId: string) => actionCreators.deleteAcc export const deleteKey = (publicKey: PublicKey) => actionCreators.deleteKey(publicKey); export const deployContract = (code: Uint8Array) => actionCreators.deployContract(code); export const fullAccessKey = () => actionCreators.fullAccessKey(); -export const functionCall = (methodName: string, args: object | Uint8Array, gas: bigint, deposit: bigint, stringify?: typeof stringifyJsonOrBytes, jsContract?: boolean) => actionCreators.functionCall(methodName, args, gas, deposit, stringify, jsContract); +export const functionCall = (methodName: string, args: object | Uint8Array, gas: bigint, deposit: bigint, stringify?: typeof stringifyJsonOrBytes) => actionCreators.functionCall(methodName, args, gas, deposit, stringify); export const functionCallAccessKey = (receiverId: string, methodNames: string[], allowance?: bigint) => actionCreators.functionCallAccessKey(receiverId, methodNames, allowance); export const stake = (stake: bigint, publicKey: PublicKey) => actionCreators.stake(stake, publicKey); export const transfer = (deposit: bigint) => actionCreators.transfer(deposit); diff --git a/packages/transactions/src/action_creators.ts b/packages/transactions/src/action_creators.ts index 0c0bff7c6f..79629d989c 100644 --- a/packages/transactions/src/action_creators.ts +++ b/packages/transactions/src/action_creators.ts @@ -93,7 +93,6 @@ export function stringifyJsonOrBytes(args: any): Buffer { * @param gas max amount of gas that method call can use * @param deposit amount of NEAR (in yoctoNEAR) to send together with the call * @param stringify Convert input arguments into bytes array. - * @param jsContract Is contract from JS SDK, skips stringification of arguments. */ function functionCall( methodName: string, @@ -101,14 +100,7 @@ function functionCall( gas = 0n, deposit = 0n, stringify = stringifyJsonOrBytes, - jsContract = false ): Action { - if (jsContract) { - return new Action({ - functionCall: new FunctionCall({ methodName, args: args as Uint8Array, gas, deposit }), - }); - } - return new Action({ functionCall: new FunctionCall({ methodName, diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts index 748cbc173b..4b542169f3 100644 --- a/packages/wallet-account/src/near.ts +++ b/packages/wallet-account/src/near.ts @@ -68,11 +68,6 @@ export interface NearConfig { */ walletUrl?: string; - /** - * JVSM account ID for NEAR JS SDK - */ - jsvmAccountId?: string; - /** * Backward-compatibility for older versions */ @@ -108,7 +103,6 @@ export class Near { networkId: config.networkId, provider: config.provider || { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore || config.deps?.keyStore }, - jsvmAccountId: config.jsvmAccountId || `jsvm.${config.networkId}` }); if (config.masterAccount) { From 3d836d8acc0ebf644ef96b73c3b74da87c195b5f Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 22 Apr 2025 22:34:21 +0200 Subject: [PATCH 31/57] style: fix linting errors --- .../accounts/test/account_multisig.test.ts | 2 +- packages/accounts/test/contract.test.ts | 26 ++-- packages/providers/src/json-rpc-provider.ts | 54 +++---- packages/signers/src/key_pair_signer.ts | 18 +-- packages/signers/src/signer.ts | 14 +- packages/signers/test/key_pair_signer.test.ts | 142 +++++++++--------- 6 files changed, 128 insertions(+), 128 deletions(-) diff --git a/packages/accounts/test/account_multisig.test.ts b/packages/accounts/test/account_multisig.test.ts index 51ee8e19fb..ce27aa1ca1 100644 --- a/packages/accounts/test/account_multisig.test.ts +++ b/packages/accounts/test/account_multisig.test.ts @@ -34,7 +34,7 @@ const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) = actions: [ functionCall('confirm', { request_id: requestId }, MULTISIG_GAS, MULTISIG_DEPOSIT) ] - }, {signer: new KeyPairSigner(account2fa.confirmKey)}); + }, { signer: new KeyPairSigner(account2fa.confirmKey) }); } }); account2fa.confirmKey = KeyPair.fromRandom('ed25519'); diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index 19375dd6c6..07c0cff344 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -119,8 +119,8 @@ describe('local view execution', () => { nearjs = await setUpTestConnection(); contract = await deployContractGuestBook(nearjs.accountCreator.masterAccount, generateUniqueString('guestbook')); - await contract.add_message({ text: "first message" }); - await contract.add_message({ text: "second message" }); + await contract.add_message({ text: 'first message' }); + await contract.add_message({ text: 'second message' }); const block = await contract.connection.provider.block({ finality: 'optimistic' }); @@ -177,7 +177,7 @@ describe('local view execution', () => { }); }); -describe("contract without account", () => { +describe('contract without account', () => { let nearjs; let contract; @@ -185,7 +185,7 @@ describe("contract without account", () => { beforeAll(async () => { nearjs = await setUpTestConnection(); - const contractId = generateUniqueString("guestbook"); + const contractId = generateUniqueString('guestbook'); await deployContractGuestBook( nearjs.accountCreator.masterAccount, contractId @@ -193,25 +193,25 @@ describe("contract without account", () => { // @ts-expect-error test input contract = new Contract(nearjs.connection, contractId, { - viewMethods: ["total_messages", "get_messages"], - changeMethods: ["add_message"], + viewMethods: ['total_messages', 'get_messages'], + changeMethods: ['add_message'], }); }); - test("view & change methods work", async () => { + test('view & change methods work', async () => { const totalMessagesBefore = await contract.total_messages({}); expect(totalMessagesBefore).toBe(0); await contract.add_message({ signerAccount: nearjs.accountCreator.masterAccount, args: { - text: "first message", + text: 'first message', }, }); await contract.add_message({ signerAccount: nearjs.accountCreator.masterAccount, args: { - text: "second message", + text: 'second message', }, }); @@ -220,15 +220,15 @@ describe("contract without account", () => { const messages = await contract.get_messages({}); expect(messages.length).toBe(2); - expect(messages[0].text).toEqual("first message"); - expect(messages[1].text).toEqual("second message"); + expect(messages[0].text).toEqual('first message'); + expect(messages[1].text).toEqual('second message'); }); - test("fails to call add_message() without signerAccount", async () => { + test('fails to call add_message() without signerAccount', async () => { await expect( contract.add_message({ args: { - text: "third message", + text: 'third message', }, }) ).rejects.toThrow(/signerAccount must be specified/); diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 72861ad3ff..becd55ecde 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -53,7 +53,7 @@ import { import { Provider } from './provider'; import { ConnectionInfo, fetchJsonRpc, retryConfig } from './fetch_json'; import { TxExecutionStatus } from '@near-js/types'; -import { PublicKey } from "@near-js/crypto"; +import { PublicKey } from '@near-js/crypto'; /** @hidden */ // Default number of retries before giving up on a request. @@ -124,11 +124,11 @@ export class JsonRpcProvider implements Provider { public async viewAccessKey( accountId: string, publicKey: PublicKey | string, - finalityQuery: FinalityReference = { finality: "final" } + finalityQuery: FinalityReference = { finality: 'final' } ): Promise { const data = await (this as Provider).query({ ...finalityQuery, - request_type: "view_access_key", + request_type: 'view_access_key', account_id: accountId, public_key: publicKey.toString(), }); @@ -141,22 +141,22 @@ export class JsonRpcProvider implements Provider { public async viewAccessKeyList( accountId: string, - finalityQuery: FinalityReference = { finality: "final" } + finalityQuery: FinalityReference = { finality: 'final' } ): Promise { return (this as Provider).query({ ...finalityQuery, - request_type: "view_access_key_list", + request_type: 'view_access_key_list', account_id: accountId, }); } public async viewAccount( accountId: string, - blockQuery: BlockReference = { finality: "final" } + blockQuery: BlockReference = { finality: 'final' } ): Promise { const data = await (this as Provider).query({ ...blockQuery, - request_type: "view_account", + request_type: 'view_account', account_id: accountId, }); @@ -169,30 +169,30 @@ export class JsonRpcProvider implements Provider { public async viewContractCode( contractId: string, - blockQuery: BlockReference = { finality: "final" } + blockQuery: BlockReference = { finality: 'final' } ): Promise { const data = await (this as Provider).query({ ...blockQuery, - request_type: "view_code", + request_type: 'view_code', account_id: contractId, }); return { ...data, - code: new Uint8Array(Buffer.from(data.code_base64, "base64")), + code: new Uint8Array(Buffer.from(data.code_base64, 'base64')), }; } public async viewContractState( contractId: string, prefix?: string, - blockQuery: BlockReference = { finality: "final" } + blockQuery: BlockReference = { finality: 'final' } ): Promise { - const prefixBase64 = Buffer.from(prefix || "").toString("base64"); + const prefixBase64 = Buffer.from(prefix || '').toString('base64'); return (this as Provider).query({ ...blockQuery, - request_type: "view_state", + request_type: 'view_state', account_id: contractId, prefix_base64: prefixBase64, }); @@ -202,15 +202,15 @@ export class JsonRpcProvider implements Provider { contractId: string, method: string, args: Record, - blockQuery: BlockReference = { finality: "final" } + blockQuery: BlockReference = { finality: 'final' } ): Promise { - const argsBase64 = Buffer.from(JSON.stringify(args)).toString("base64"); + const argsBase64 = Buffer.from(JSON.stringify(args)).toString('base64'); const data = await ( this as Provider ).query({ ...blockQuery, - request_type: "call_function", + request_type: 'call_function', account_id: contractId, method_name: method, args_base64: argsBase64, @@ -232,25 +232,25 @@ export class JsonRpcProvider implements Provider { public async viewBlock(blockQuery: BlockReference): Promise { const { finality } = blockQuery as any; const { blockId } = blockQuery as any; - return this.sendJsonRpc("block", { block_id: blockId, finality }); + return this.sendJsonRpc('block', { block_id: blockId, finality }); } public async viewChunk(chunkId: ChunkId): Promise { - return this.sendJsonRpc("chunk", [chunkId]); + return this.sendJsonRpc('chunk', [chunkId]); } public async viewGasPrice(blockId?: BlockId): Promise { - return this.sendJsonRpc("gas_price", [blockId || null]); + return this.sendJsonRpc('gas_price', [blockId || null]); } public async viewNodeStatus(): Promise { - return this.sendJsonRpc("status", []); + return this.sendJsonRpc('status', []); } public async viewValidators( blockId?: BlockId ): Promise { - return this.sendJsonRpc("validators", [blockId || null]); + return this.sendJsonRpc('validators', [blockId || null]); } public async viewTransactionStatus( @@ -259,9 +259,9 @@ export class JsonRpcProvider implements Provider { waitUntil: TxExecutionStatus ): Promise { const encodedTxHash = - typeof txHash === "string" ? txHash : baseEncode(txHash); + typeof txHash === 'string' ? txHash : baseEncode(txHash); - return this.sendJsonRpc("tx", { + return this.sendJsonRpc('tx', { tx_hash: encodedTxHash, sender_account_id: accountId, wait_until: waitUntil, @@ -274,12 +274,12 @@ export class JsonRpcProvider implements Provider { waitUntil: TxExecutionStatus ): Promise< FinalExecutionOutcome & - Required> + Required> > { const encodedTxHash = - typeof txHash === "string" ? txHash : baseEncode(txHash); + typeof txHash === 'string' ? txHash : baseEncode(txHash); - return this.sendJsonRpc("EXPERIMENTAL_tx_status", { + return this.sendJsonRpc('EXPERIMENTAL_tx_status', { tx_hash: encodedTxHash, sender_account_id: accountId, wait_until: waitUntil, @@ -289,7 +289,7 @@ export class JsonRpcProvider implements Provider { public async viewTransactionReceipt( receiptId: string ): Promise { - return this.sendJsonRpc("EXPERIMENTAL_receipt", { + return this.sendJsonRpc('EXPERIMENTAL_receipt', { receipt_id: receiptId, }); } diff --git a/packages/signers/src/key_pair_signer.ts b/packages/signers/src/key_pair_signer.ts index 87d51411b8..aca76040db 100644 --- a/packages/signers/src/key_pair_signer.ts +++ b/packages/signers/src/key_pair_signer.ts @@ -1,12 +1,12 @@ -import { KeyPair, PublicKey, KeyPairString, KeyType } from "@near-js/crypto"; -import { sha256 } from "@noble/hashes/sha256"; +import { KeyPair, PublicKey, KeyPairString, KeyType } from '@near-js/crypto'; +import { sha256 } from '@noble/hashes/sha256'; import { Nep413MessageSchema, SignedMessage, Signer, SignMessageParams, -} from "./signer"; +} from './signer'; import { Transaction, SignedTransaction, @@ -15,8 +15,8 @@ import { DelegateAction, SignedDelegate, encodeDelegateAction, -} from "@near-js/transactions"; -import { serialize } from "borsh"; +} from '@near-js/transactions'; +import { serialize } from 'borsh'; /** * Signs using in memory key store. @@ -47,13 +47,13 @@ export class KeyPairSigner extends Signer { callbackUrl?: string ): Promise { if (nonce.length !== 32) - throw new Error(`Nonce must be exactly 32 bytes long`); + throw new Error('Nonce must be exactly 32 bytes long'); const pk = this.key.getPublicKey(); // 2**31 + 413 == 2147484061 const PREFIX = 2147484061; - const serializedPrefix = serialize("u32", PREFIX); + const serializedPrefix = serialize('u32', PREFIX); const params: SignMessageParams = { message, @@ -86,7 +86,7 @@ export class KeyPairSigner extends Signer { const pk = this.key.getPublicKey(); if (transaction.publicKey.toString() !== pk.toString()) - throw new Error("The public key doesn't match the signer's key"); + throw new Error('The public key doesn\'t match the signer\'s key'); const message = encodeTransaction(transaction); const hash = new Uint8Array(sha256(message)); @@ -111,7 +111,7 @@ export class KeyPairSigner extends Signer { const pk = this.key.getPublicKey(); if (delegateAction.publicKey.toString() !== pk.toString()) - throw new Error("The public key doesn't match the signer's key"); + throw new Error('The public key doesn\'t match the signer\'s key'); const message = encodeDelegateAction(delegateAction); const hash = new Uint8Array(sha256(message)); diff --git a/packages/signers/src/signer.ts b/packages/signers/src/signer.ts index 0bfb04faf3..1ac4ee6218 100644 --- a/packages/signers/src/signer.ts +++ b/packages/signers/src/signer.ts @@ -1,11 +1,11 @@ -import { PublicKey } from "@near-js/crypto"; +import { PublicKey } from '@near-js/crypto'; import { DelegateAction, SignedDelegate, SignedTransaction, Transaction, -} from "@near-js/transactions"; -import { Schema } from "borsh"; +} from '@near-js/transactions'; +import { Schema } from 'borsh'; export interface SignMessageParams { message: string; // The message that wants to be transmitted. @@ -23,10 +23,10 @@ export interface SignedMessage { export const Nep413MessageSchema: Schema = { struct: { - message: "string", - nonce: { array: { type: "u8", len: 32 } }, - recipient: "string", - callbackUrl: { option: "string" }, + message: 'string', + nonce: { array: { type: 'u8', len: 32 } }, + recipient: 'string', + callbackUrl: { option: 'string' }, }, }; diff --git a/packages/signers/test/key_pair_signer.test.ts b/packages/signers/test/key_pair_signer.test.ts index 36e1a2c82a..1c8f1ec535 100644 --- a/packages/signers/test/key_pair_signer.test.ts +++ b/packages/signers/test/key_pair_signer.test.ts @@ -1,53 +1,53 @@ -import { expect, test } from "@jest/globals"; -import { TextEncoder } from "util"; +import { expect, test } from '@jest/globals'; +import { TextEncoder } from 'util'; -import { KeyPairSigner } from "../src"; -import { KeyPair, PublicKey } from "@near-js/crypto"; +import { KeyPairSigner } from '../src'; +import { KeyPair, PublicKey } from '@near-js/crypto'; import { createTransaction, encodeTransaction, actionCreators, decodeSignedTransaction, buildDelegateAction, -} from "@near-js/transactions"; +} from '@near-js/transactions'; global.TextEncoder = TextEncoder; -test("test sign transaction with different public key", async () => { +test('test sign transaction with different public key', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" + 'ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF' ) ); const transaction = createTransaction( - "signer", + 'signer', // the key is different from what signer operates KeyPair.fromString( - "ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV" + 'ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV' ).getPublicKey(), - "receiver", + 'receiver', 1n, [], new Uint8Array(new Array(32)) ); await expect(() => signer.signTransaction(transaction)).rejects.toThrow( - /The public key doesn\'t match the signer\'s key/ + /The public key doesn't match the signer's key/ ); }); -test("test sign transaction with relevant public key", async () => { +test('test sign transaction with relevant public key', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF" + 'ed25519:sDa8GRWHy16zXzE5ALViy17miA7Lo39DWp8DY5HsTBEayarAWefzbgXmSpW4f8tQn3V8odsY7yGLGmgGaQN2BBF' ) ); const transaction = createTransaction( - "signer", + 'signer', await signer.getPublicKey(), - "receiver", + 'receiver', 1n, [], new Uint8Array(new Array(32)) @@ -55,18 +55,18 @@ test("test sign transaction with relevant public key", async () => { const [hash, { signature }] = await signer.signTransaction(transaction); - expect(Buffer.from(hash).toString("hex")).toBe( - "2571e3539ab5556e39441913e66abd07e634fb9850434006a719306100e641a2" + expect(Buffer.from(hash).toString('hex')).toBe( + '2571e3539ab5556e39441913e66abd07e634fb9850434006a719306100e641a2' ); - expect(Buffer.from(signature.signature.data).toString("hex")).toBe( - "bfe2858d227e3116076a8e5ea9c5bef923c7755f19f0137d1acd9bb67973f1b8a7f83dfc0be23e307e106c8807eaa6e14c0fcb46c42acdf293c4a6a81a27fc05" + expect(Buffer.from(signature.signature.data).toString('hex')).toBe( + 'bfe2858d227e3116076a8e5ea9c5bef923c7755f19f0137d1acd9bb67973f1b8a7f83dfc0be23e307e106c8807eaa6e14c0fcb46c42acdf293c4a6a81a27fc05' ); }); -test("serialize and sign transfer tx object", async () => { +test('serialize and sign transfer tx object', async () => { const keyPair = KeyPair.fromString( - "ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv" + 'ed25519:3hoMW1HvnRLSFCLZnvPzWeoGwtdHzke34B2cTHM8rhcbG3TbuLKtShTv3DvyejnXKXKBiV7YPkLeqUHN1ghnqpFv' ); const signer = new KeyPairSigner(keyPair); @@ -77,11 +77,11 @@ test("serialize and sign transfer tx object", async () => { 246, ]); const transaction = createTransaction( - "test.near", + 'test.near', PublicKey.fromString( - "ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC" + 'ed25519:Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC' ), - "whatever.near", + 'whatever.near', 1, actions, blockHash @@ -91,129 +91,129 @@ test("serialize and sign transfer tx object", async () => { expect( Buffer.from(signedTx.signature.ed25519Signature!.data).toString( - "base64" + 'base64' ) ).toEqual( - "lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ==" + 'lpqDMyGG7pdV5IOTJVJYBuGJo9LSu0tHYOlEQ+l+HE8i3u7wBZqOlxMQDtpuGRRNp+ig735TmyBwi6HY0CG9AQ==' ); const serialized = encodeTransaction(signedTx); - expect(Buffer.from(serialized).toString("hex")).toEqual( - "09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01" + expect(Buffer.from(serialized).toString('hex')).toEqual( + '09000000746573742e6e65617200917b3d268d4b58f7fec1b150bd68d69be3ee5d4cc39855e341538465bb77860d01000000000000000d00000077686174657665722e6e6561720fa473fd26901df296be6adc4cc4df34d040efa2435224b6986910e630c2fef601000000030100000000000000000000000000000000969a83332186ee9755e4839325525806e189a3d2d2bb4b4760e94443e97e1c4f22deeef0059a8e9713100eda6e19144da7e8a0ef7e539b20708ba1d8d021bd01' ); const deserialized = decodeSignedTransaction(serialized); expect(encodeTransaction(deserialized)).toEqual(serialized); }); -test("test sign NEP-413 message with callback url", async () => { +test('test sign NEP-413 message with callback url', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' ) ); const { signature } = await signer.signNep413Message( - "Hello NEAR!", - "round-toad.testnet", - "example.near", + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', new Uint8Array( Buffer.from( - "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", - "base64" + 'KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=', + 'base64' ) ), - "http://localhost:3000" + 'http://localhost:3000' ); const expectedSignature = new Uint8Array( Buffer.from( - "zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==", - "base64" + 'zzZQ/GwAjrZVrTIFlvmmQbDQHllfzrr8urVWHaRt5cPfcXaCSZo35c5LDpPpTKivR6BxLyb3lcPM0FfCW5lcBQ==', + 'base64' ) ); expect(signature).toStrictEqual(expectedSignature); }); -test("test sign NEP-413 message without callback url", async () => { +test('test sign NEP-413 message without callback url', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' ) ); const { signature } = await signer.signNep413Message( - "Hello NEAR!", - "round-toad.testnet", - "example.near", + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', new Uint8Array( Buffer.from( - "KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=", - "base64" + 'KNV0cOpvJ50D5vfF9pqWom8wo2sliQ4W+Wa7uZ3Uk6Y=', + 'base64' ) ) ); const expectedSignature = new Uint8Array( Buffer.from( - "NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==", - "base64" + 'NnJgPU1Ql7ccRTITIoOVsIfElmvH1RV7QAT4a9Vh6ShCOnjIzRwxqX54JzoQ/nK02p7VBMI2vJn48rpImIJwAw==', + 'base64' ) ); expect(signature).toStrictEqual(expectedSignature); }); -test("test sign NEP-413 message throws error on invalid nonce", async () => { +test('test sign NEP-413 message throws error on invalid nonce', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' ) ); await expect(() => signer.signNep413Message( - "Hello NEAR!", - "round-toad.testnet", - "example.near", + 'Hello NEAR!', + 'round-toad.testnet', + 'example.near', new Uint8Array(new Array(28)) ) ).rejects.toThrow(); }); -test("test getPublicKey returns correct public key", async () => { +test('test getPublicKey returns correct public key', async () => { const keyPair = KeyPair.fromString( - "ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB" + 'ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB' ); const signer = new KeyPairSigner(keyPair); const publicKey = (await signer.getPublicKey()).toString(); expect(publicKey).toBe( - "ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ" + 'ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ' ); }); -test("test static fromSecretKey creates a corresponding KeyPair", async () => { +test('test static fromSecretKey creates a corresponding KeyPair', async () => { const signer = KeyPairSigner.fromSecretKey( - "ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB" + 'ed25519:4RDn17Y8bm6FRg57BhW7eVnrHTF2nsmRfkj1nPXR1zYB' ); const publicKey = (await signer.getPublicKey()).toString(); expect(publicKey).toBe( - "ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ" + 'ed25519:Bpz2oUnMM8MM8trXmdAJW5sS1TtPkMot4cosa16ZeYFQ' ); }); -test("test sign delegate action", async () => { +test('test sign delegate action', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' ) ); const delegateAction = buildDelegateAction({ - receiverId: "receiver.testnet", - senderId: "sender.testnet", + receiverId: 'receiver.testnet', + senderId: 'sender.testnet', nonce: 1n, maxBlockHeight: 1848319858n, actions: [], @@ -224,30 +224,30 @@ test("test sign delegate action", async () => { delegateAction ); - expect(Buffer.from(hash).toString("hex")).toBe( - "6d35575b3566fddf04f79317c3e574eb89c5bc228c5e755f2a3179e164dbb36b" + expect(Buffer.from(hash).toString('hex')).toBe( + '6d35575b3566fddf04f79317c3e574eb89c5bc228c5e755f2a3179e164dbb36b' ); - expect(Buffer.from(signature.signature.data).toString("hex")).toBe( - "77e5e92877d64ae27b1facc5ddf09205863b2af7a8264d2d01a44a9684975a17f2b8b9d706fbe3c3ffa2cd4377b49f20614245549dc061bcaccc0ad3abd81c01" + expect(Buffer.from(signature.signature.data).toString('hex')).toBe( + '77e5e92877d64ae27b1facc5ddf09205863b2af7a8264d2d01a44a9684975a17f2b8b9d706fbe3c3ffa2cd4377b49f20614245549dc061bcaccc0ad3abd81c01' ); }); -test("test sign delegate action with wrong public key", async () => { +test('test sign delegate action with wrong public key', async () => { const signer = new KeyPairSigner( KeyPair.fromString( - "ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7" + 'ed25519:3FyRtUUMxiNT1g2ST6mbj7W1CN7KfQBbomawC7YG4A1zwHmw2TRsn1Wc8NaFcBCoJDu3zt3znJDSwKQ31oRaKXH7' ) ); const delegateAction = buildDelegateAction({ - receiverId: "receiver.testnet", - senderId: "sender.testnet", + receiverId: 'receiver.testnet', + senderId: 'sender.testnet', nonce: 1n, maxBlockHeight: 1848319858n, actions: [], publicKey: KeyPair.fromString( - "ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV" + 'ed25519:2Pm1R2qRtkbFErVrjqgtNutMqEVvrErQ3wSns6rN4jd7nnmzCbda4kwRCBAnBR7RWf2faRqVMuFaJzhJp1eYfhvV' ).getPublicKey(), }); From 674b55833b54295cf4b9c02412a9fda5a71e5791 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Mon, 28 Apr 2025 11:53:15 -0400 Subject: [PATCH 32/57] feat: change account interface and remove unnecesary accounts --- packages/accounts/src/account.ts | 1296 +++++++++-------- packages/accounts/src/account_2fa.ts | 347 ----- packages/accounts/src/account_multisig.ts | 261 ---- packages/accounts/src/index.ts | 2 - packages/accounts/test/account.test.ts | 30 +- .../accounts/test/account_multisig.test.ts | 134 -- .../composers/signed_transaction_composer.ts | 4 +- packages/keystores/src/keystore.ts | 3 +- packages/near-api-js/package.json | 4 +- packages/near-api-js/src/account_multisig.ts | 12 - packages/near-api-js/src/common-index.ts | 10 - packages/near-api-js/src/near.ts | 145 +- packages/near-api-js/src/wallet-account.ts | 1 - .../providers/src/failover-rpc-provider.ts | 4 +- packages/providers/src/json-rpc-provider.ts | 2 +- packages/providers/src/provider.ts | 2 +- packages/wallet-account/CHANGELOG.md | 270 ---- packages/wallet-account/README.md | 13 - packages/wallet-account/jest.config.ts | 13 - packages/wallet-account/package.json | 44 - packages/wallet-account/src/index.ts | 2 - packages/wallet-account/src/near.ts | 144 -- packages/wallet-account/src/wallet_account.ts | 453 ------ .../test/wallet_account.test.ts | 3 - .../wallet-account/test/wallet_account.ts | 245 ---- .../test/wallet_accounts.test.ts | 533 ------- packages/wallet-account/tsconfig.cjs.json | 10 - packages/wallet-account/tsconfig.json | 10 - packages/wallet-account/typedoc.json | 9 - 29 files changed, 834 insertions(+), 3172 deletions(-) delete mode 100644 packages/accounts/src/account_2fa.ts delete mode 100644 packages/accounts/src/account_multisig.ts delete mode 100644 packages/accounts/test/account_multisig.test.ts delete mode 100644 packages/near-api-js/src/account_multisig.ts delete mode 100644 packages/near-api-js/src/wallet-account.ts delete mode 100644 packages/wallet-account/CHANGELOG.md delete mode 100644 packages/wallet-account/README.md delete mode 100644 packages/wallet-account/jest.config.ts delete mode 100644 packages/wallet-account/package.json delete mode 100644 packages/wallet-account/src/index.ts delete mode 100644 packages/wallet-account/src/near.ts delete mode 100644 packages/wallet-account/src/wallet_account.ts delete mode 100644 packages/wallet-account/test/wallet_account.test.ts delete mode 100644 packages/wallet-account/test/wallet_account.ts delete mode 100644 packages/wallet-account/test/wallet_accounts.test.ts delete mode 100644 packages/wallet-account/tsconfig.cjs.json delete mode 100644 packages/wallet-account/tsconfig.json delete mode 100644 packages/wallet-account/typedoc.json diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 1832bf45f5..661db9ef12 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -8,6 +8,7 @@ import { SignedTransaction, createTransaction, stringifyJsonOrBytes, + DelegateAction, } from "@near-js/transactions"; import { PositionalArgsError, @@ -23,7 +24,7 @@ import { ContractCodeView, ContractStateView, ErrorContext, - CallContractViewFunctionResult, + TxExecutionStatus, } from "@near-js/types"; import { baseDecode, @@ -34,16 +35,13 @@ import { printTxOutcomeLogsAndFailures, } from "@near-js/utils"; -import { Signer } from "@near-js/signers"; +import { SignedMessage, Signer } from "@near-js/signers"; import { Connection } from "./connection"; import { viewFunction, viewState } from "./utils"; import { ChangeFunctionCallOptions, - IntoConnection, ViewFunctionCallOptions, } from "./interface"; -import { randomBytes } from "crypto"; -import { SignedMessage } from "@near-js/signers/lib/esm/signer"; const { addKey, @@ -54,18 +52,12 @@ const { fullAccessKey, functionCall, functionCallAccessKey, - stake, transfer, } = actionCreators; -// Default number of retries with different nonce before giving up on a transaction. -const TX_NONCE_RETRY_NUMBER = 12; - -// Default wait until next retry in millis. -const TX_NONCE_RETRY_WAIT = 500; - -// Exponential back off for waiting to retry. -const TX_NONCE_RETRY_WAIT_BACKOFF = 1.5; +// Default values to wait for +const DEFAULT_FINALITY = "near-final" +export const DEFAULT_WAIT_STATUS: TxExecutionStatus = "EXECUTED_OPTIMISTIC" export interface AccountBalance { total: string; @@ -76,8 +68,8 @@ export interface AccountBalance { export interface AccountBalanceInfo { total: bigint; - stateStaked: bigint; - staked: bigint; + usedOnStorage: bigint; + locked: bigint; available: bigint; } @@ -87,10 +79,6 @@ export interface AccountAuthorizedApp { publicKey: string; } -interface SignerOptions { - signer?: Signer; -} - /** * Options used to initiate sining and sending transactions */ @@ -129,12 +117,15 @@ interface SignedDelegateOptions { } /** - * This class provides common account related RPC calls including signing transactions with a {@link "@near-js/crypto".key_pair.KeyPair | KeyPair}. + * This class allows to access common account information. + * If a {@link Signer} is provider, then the account can + * be used to perform all common actions such as + * transferring tokens and calling functions */ -export class Account implements IntoConnection { +export class Account { public readonly accountId: string; public readonly provider: Provider; - public readonly signer?: Signer; + private signer?: Signer; constructor(accountId: string, provider: Provider, signer?: Signer) { this.accountId = accountId; @@ -142,381 +133,597 @@ export class Account implements IntoConnection { this.signer = signer; } - public getConnection(): Connection { - return new Connection("", this.provider, this.signer); + /** + * Allows to set the signer used to control the account + * + * @param signer holds the private key and can sign Transactions + */ + public setSigner(signer: Signer): void { + this.signer = signer; + } + + public getSigner(): Signer | undefined { + return this.signer; } /** - * Returns basic NEAR account information via the `view_account` RPC query method - * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + * Calls {@link Provider.viewAccount} to retrieve the account's balance, + * locked tokens, storage usage, and code hash */ - public async getInformation(): Promise { + public async getState(): Promise { return this.provider.viewAccount(this.accountId, { - finality: "optimistic", + finality: DEFAULT_FINALITY, }); } /** - * Returns calculated account balance + * Returns information on the account's balance including the total + * balance, the amount locked for storage and the amount available */ - async getBalance(): Promise { - const protocolConfig = await this.provider.experimental_protocolConfig({ - finality: "final", - }); - const state = await this.getInformation(); + public async getBalance(): Promise { + const state = await this.getState(); - const costPerByte = BigInt( - protocolConfig.runtime_config.storage_amount_per_byte - ); - const stateStaked = BigInt(state.storage_usage) * costPerByte; + const usedOnStorage = BigInt(state.storage_usage) * BigInt(1E19); const totalBalance = BigInt(state.amount) + state.locked; const availableBalance = totalBalance - - (state.locked > stateStaked ? state.locked : stateStaked); + (state.locked > usedOnStorage ? state.locked : usedOnStorage); return { total: totalBalance, - stateStaked: stateStaked, - staked: state.locked, + usedOnStorage, + locked: state.locked, available: availableBalance, }; } /** - * Returns basic NEAR account information via the `view_account` RPC query method - * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) + * Calls {@link Provider.viewAccessKey} to retrieve information for a + * specific key in the account */ - public async getAccessKey(pk: PublicKey | string): Promise { - return this.provider.viewAccessKey(this.accountId, pk, { - finality: "optimistic", + public async getAccessKey(publicKey: PublicKey): Promise { + return this.provider.viewAccessKey(this.accountId, publicKey, { + finality: DEFAULT_FINALITY, }); } + /** + * Calls {@link Provider.viewAccessKeyList} to retrieve the account's keys + */ public async getAccessKeyList(): Promise { return this.provider.viewAccessKeyList(this.accountId, { - finality: "optimistic", + finality: DEFAULT_FINALITY, }); } + /** + * Calls {@link Provider.viewContractCode} to retrieve the account's + * contract code and its hash + */ public async getContractCode(): Promise { return this.provider.viewContractCode(this.accountId, { - finality: "optimistic", - }); - } - - public async getContractState(prefix?: string): Promise { - return this.provider.viewContractState(this.accountId, prefix, { - finality: "optimistic", + finality: DEFAULT_FINALITY, }); } - public async getTransactionStatus( - txHash: string | Uint8Array - ): Promise { - return this.provider.viewTransactionStatus( - txHash, - this.accountId, // accountId is used to determine on which shard to look for a tx - "EXECUTED_OPTIMISTIC" - ); - } - - /** - * Invoke a contract view function using the RPC API. - * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) - * - * @returns {Promise} - */ - public async callReadFunction( - contractId: string, - methodName: string, - args: Record = {} - ): Promise { - return this.provider.callContractViewFunction( - contractId, - methodName, - args, - { finality: "optimistic" } - ); - } - /** - * Returns basic NEAR account information via the `view_account` RPC query method - * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) - * - * @deprecated + * Calls {@link Provider.viewContractState} to retrieve the keys and values + * stored on the account's contract */ - async state(): Promise { - return this.provider.query({ - request_type: "view_account", - account_id: this.accountId, - finality: "optimistic", + public async getContractState(prefix?: string): Promise { + return this.provider.viewContractState(this.accountId, prefix, { + finality: DEFAULT_FINALITY, }); } /** - * Create a signed transaction which can be broadcast to the network - * @param receiverId NEAR account receiving the transaction - * @param actions list of actions to perform as part of the transaction - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} + * Create a transaction that can be later signed with a {@link Signer} + * + * @param receiverId Account against which to perform the actions + * @param actions Actions to perform + * @param publicKey The public part of the key that will be used to sign the transaction */ - public async signTransaction( + public async createTransaction( receiverId: string, actions: Action[], - opts?: SignerOptions - ): Promise<[Uint8Array, SignedTransaction]> { - const signer = opts?.signer || this.signer; - - if (!signer) throw new Error(`Signer is required`); - - const pk = await signer.getPublicKey(); + publicKey: PublicKey + ) { + if (!publicKey) throw new Error("Please provide a public key") - const accessKey = await this.getAccessKey(pk); + const accessKey = await this.getAccessKey(publicKey); - const block = await this.provider.block({ - finality: "final", + const block = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, }); const recentBlockHash = block.header.hash; const nonce = BigInt(accessKey.nonce) + 1n; - const tx = createTransaction( + return createTransaction( this.accountId, - pk, + publicKey, receiverId, nonce + 1n, actions, baseDecode(recentBlockHash) ); + } - return signer.signTransaction(tx); + /** + * Create a signed transaction ready to be broadcast by a {@link Provider} + */ + public async createSignedTransaction( + receiverId: string, + actions: Action[], + ): Promise { + if (!this.signer) throw new Error("Please set a signer") + + const tx = await this.createTransaction( + receiverId, + actions, + await this.signer.getPublicKey() + ) + + return this.signer.signTransaction(tx)[1] } /** - * Create a signed transaction which can be broadcasted to the relayer + * Create a meta transaction ready to be signed by a {@link Signer} + * * @param receiverId NEAR account receiving the transaction - * @param actions list of actions to perform as part of the neta transaction + * @param actions list of actions to perform as part of the meta transaction * @param blockHeightTtl number of blocks after which a meta transaction will expire if not processed */ - public async signMetaTransaction( + public async createMetaTransaction( receiverId: string, actions: Action[], blockHeightTtl: number = 200, - opts?: SignerOptions - ): Promise<[Uint8Array, SignedDelegate]> { - const signer = opts?.signer || this.signer; - - if (!signer) throw new Error(`Signer is required`); - - const pk = await signer.getPublicKey(); - - const accessKey = await this.getAccessKey(pk); + publicKey: PublicKey + ): Promise { + if (!publicKey) throw new Error(`Please provide a public key`); + const accessKey = await this.getAccessKey(publicKey); const nonce = BigInt(accessKey.nonce) + 1n; const { header } = await this.provider.viewBlock({ - finality: "final", + finality: DEFAULT_FINALITY, }); const maxBlockHeight = BigInt(header.height) + BigInt(blockHeightTtl); - const delegateAction = buildDelegateAction({ - receiverId: receiverId, + return buildDelegateAction({ + receiverId, senderId: this.accountId, - actions: actions, - publicKey: pk, - nonce: nonce, - maxBlockHeight: maxBlockHeight, + actions, + publicKey, + nonce, + maxBlockHeight, }); - - return signer.signDelegateAction(delegateAction); } - public async signMessage( - message: string, - recipient: string, - callbackUrl?: string, - opts?: SignerOptions - ): Promise { - const signer = opts?.signer || this.signer; - - if (!signer) throw new Error(`Signer is required`); + /** + * Create a signed MetaTransaction that can be broadcasted to a relayer + * + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the meta transaction + * @param blockHeightTtl number of blocks after which a meta transaction will expire if not processed + */ + public async createSignedMetaTransaction( + receiverId: string, + actions: Action[], + blockHeightTtl: number = 200, + ): Promise<[Uint8Array, SignedDelegate]> { + if (!this.signer) throw new Error(`Please set a signer`); - const nonce = new Uint8Array(randomBytes(32)); + const delegateAction = await this.createMetaTransaction( + receiverId, + actions, + blockHeightTtl, + await this.signer.getPublicKey() + ) - return signer.signNep413Message( - message, - this.accountId, - recipient, - nonce, - callbackUrl - ); + return this.signer.signDelegateAction(delegateAction); } /** - * Sign a transaction to perform a list of actions and broadcast it using the RPC API. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } - * - * @param options The options for signing and sending the transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be performed in the transaction. - * @param options.returnError Whether to return an error if the transaction fails. + * Creates a transaction, signs it and broadcast it to the network + * + * @param receiverId The NEAR account ID of the transaction receiver. + * @param actions The list of actions to be performed in the transaction. + * @param returnError Whether to return an error if the transaction fails. * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. + * */ - async signAndSendTransaction( - { receiverId, actions, returnError }: SignAndSendTransactionOptions, - opts?: SignerOptions + async signAndSendTransaction({ receiverId, actions, waitUntil = DEFAULT_WAIT_STATUS }: + { + receiverId: string, + actions: Action[], + waitUntil?: TxExecutionStatus + } ): Promise { - let txHash, signedTx; - // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) - const result = await exponentialBackoff( - TX_NONCE_RETRY_WAIT, - TX_NONCE_RETRY_NUMBER, - TX_NONCE_RETRY_WAIT_BACKOFF, - async () => { - [txHash, signedTx] = await this.signTransaction( - receiverId, - actions, - opts - ); - - try { - return await this.provider.sendTransaction(signedTx); - } catch (error) { - if (error.type === "InvalidNonce") { - Logger.warn( - `Retrying transaction ${receiverId}:${baseEncode( - txHash - )} with new nonce.` - ); - return null; - } - if (error.type === "Expired") { - Logger.warn( - `Retrying transaction ${receiverId}:${baseEncode( - txHash - )} due to expired block hash` - ); - return null; - } + const signedTx = await this.createSignedTransaction( + receiverId, + actions + ) + return await this.provider.sendTransactionUntil(signedTx, waitUntil); + } - error.context = new ErrorContext(baseEncode(txHash)); - throw error; - } - } - ); - if (!result) { - // TODO: This should have different code actually, as means "transaction not submitted for sure" - throw new TypedError( - "nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.", - "RetriesExceeded" - ); + /** + * Creates an account of the form ., e.g. ana.testnet or ana.near + * + * @param newAccountId the new account to create (e.g. ana.near) + * @param publicKey the public part of the key that will control the account + * @param amountToTransfer how much NEAR to transfer to the account + * + */ + public async createTopLevelAccount( + newAccountId: string, + publicKey: PublicKey | string, + amountToTransfer: bigint | string | number, + ): Promise { + const splitted = newAccountId.split("."); + if (splitted.length != 2) { + throw new Error("newAccountId needs to be of the form .") } - printTxOutcomeLogsAndFailures({ - contractId: signedTx.transaction.receiverId, - outcome: result, - }); - - // Should be falsy if result.status.Failure is null - if ( - !returnError && - typeof result.status === "object" && - typeof result.status.Failure === "object" && - result.status.Failure !== null - ) { - // if error data has error_message and error_type properties, we consider that node returned an error in the old format - if ( - result.status.Failure.error_message && - result.status.Failure.error_type - ) { - throw new TypedError( - `Transaction ${result.transaction_outcome.id} failed. ${result.status.Failure.error_message}`, - result.status.Failure.error_type - ); - } else { - throw parseResultError(result); - } - } - // TODO: if Tx is Unknown or Started. - return result; + const TLA = splitted[1]; + return await this.callFunction({ + contractId: TLA, + methodName: "create_account", + args: { + new_account_id: newAccountId, + new_public_key: publicKey.toString(), + }, + gas: BigInt("60000000000000"), + deposit: BigInt(amountToTransfer) + }) } - /** @hidden */ - accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; - /** - * Finds the {@link AccessKeyView} associated with the accounts {@link PublicKey} stored in the {@link "@near-js/keystores".keystore.KeyStore | Keystore}. - * - * @todo Find matching access key based on transaction (i.e. receiverId and actions) - * - * @deprecated - * - * @param receiverId currently unused (see todo) - * @param actions currently unused (see todo) - * @returns `{ publicKey PublicKey; accessKey: AccessKeyView }` + * Creates a sub account of this account. For example, if the account is + * ana.near, you can create sub.ana.near. + * + * @param accountOrPrefix a prefix (e.g. `sub`) or the full sub-account (`sub.ana.near`) + * @param publicKey the public part of the key that will control the account + * @param amountToTransfer how much NEAR to transfer to the account + * */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async findAccessKey( - receiverId: string, - actions: Action[] - ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { - if (!this.signer) throw new Error(`Signer is required`); + public async createSubAccount( + accountOrPrefix: string, + publicKey: PublicKey | string, + amountToTransfer: bigint | string | number, + ): Promise { + if (!this.signer) throw new Error("Please set a signer") - const publicKey = await this.signer.getPublicKey(); - if (!publicKey) { - throw new TypedError( - `no matching key pair found in ${this.signer.constructor.name}`, - "PublicKeyNotFound" - ); + const newAccountId = accountOrPrefix.includes(".") + ? accountOrPrefix + : `${accountOrPrefix}.${this.accountId}`; + + if (newAccountId.length > 64) { + throw new Error(`Accounts cannot exceed 64 characters`) } - const cachedAccessKey = - this.accessKeyByPublicKeyCache[publicKey.toString()]; - if (cachedAccessKey !== undefined) { - return { publicKey, accessKey: cachedAccessKey }; + if (!newAccountId.endsWith(this.accountId)) { + throw new Error(`New account must end up with ${this.accountId}`) } - try { - const rawAccessKey = await this.provider.query({ - request_type: "view_access_key", - account_id: this.accountId, - public_key: publicKey.toString(), - finality: "optimistic", - }); + const actions = [ + createAccount(), + transfer(BigInt(amountToTransfer)), + addKey(PublicKey.from(publicKey), fullAccessKey()), + ]; - // store nonce as BigInt to preserve precision on big number - const accessKey = { - ...rawAccessKey, - nonce: BigInt(rawAccessKey.nonce || 0), - }; - // this function can be called multiple times and retrieve the same access key - // this checks to see if the access key was already retrieved and cached while - // the above network call was in flight. To keep nonce values in line, we return - // the cached access key. - if (this.accessKeyByPublicKeyCache[publicKey.toString()]) { - return { - publicKey, - accessKey: - this.accessKeyByPublicKeyCache[publicKey.toString()], - }; - } + return this.signAndSendTransaction({ receiverId: newAccountId, actions }) + } - this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; - return { publicKey, accessKey }; - } catch (e) { - if (e.type == "AccessKeyDoesNotExist") { - return null; - } + /** + * Deletes the account, transferring all remaining NEAR to a beneficiary + * account + * + * Important: Deleting an account does not transfer FTs or NFTs + * + * @param beneficiaryId Will receive the account's remaining balance + */ + public async deleteAccount( + beneficiaryId: string, + ): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deleteAccount(beneficiaryId)] + }) + } - throw e; - } + /** + * Deploy a smart contract in the account + * + * @param code The compiled contract code bytes + */ + public async deployContract( + code: Uint8Array, + ): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deployContract(code)] + }); } /** - * Create a new account and deploy a contract to it + * + * @param publicKey The key to add to the account + * @param contractId The contract that this key can call + * @param methodNames The methods this key is allowed to call + * @param allowance The amount of NEAR this key can expend in gas + * @param opts + * @returns + */ + public async addFunctionCallKey( + publicKey: PublicKey, + contractId: string, + methodNames: string[], + allowance?: bigint | string | number, + ): Promise { + const actions = [ + addKey( + publicKey, + functionCallAccessKey( + contractId, + methodNames, + BigInt(allowance) + ) + ), + ]; + + return this.signAndSendTransaction({ receiverId: this.accountId, actions }); + } + + /** + * @param publicKey The public key to be deleted + * @returns {Promise} + */ + public async deleteKey( + publicKey: PublicKey + ): Promise { + return this.signAndSendTransaction({ + receiverId: this.accountId, + actions: [deleteKey(publicKey)] + }) + } + + /** + * Transfer NEAR Tokens to another account + * + * @param receiverId The NEAR account that will receive the Ⓝ balance + * @param amount Amount to send in yoctoⓃ + */ + public async transferNEAR( + receiverId: string, + amount: bigint | string | number, + ): Promise { + return this.signAndSendTransaction({ + receiverId, + actions: [transfer(BigInt(amount))] + }) + } + + /** + * Call a function on a smart contract * + * @param options + * @param options.contractId The contract in which to call the function + * @param options.methodName The method that will be called + * @param options.args Arguments, either as a valid JSON Object or a raw Uint8Array + * @param options.deposit (optional) Amount of NEAR Tokens to attach to the call (default 0) + * @param options.gas (optional) Amount of GAS to use attach to the call (default 30TGas) + * @returns + */ + public async callFunction({ + contractId, + methodName, + args = {}, + deposit = "0", + gas = DEFAULT_FUNCTION_CALL_GAS + }: { + contractId: string, + methodName: string, + args: Uint8Array | Record, + deposit: bigint | string | number, + gas: bigint | string | number + } + ): Promise { + return this.signAndSendTransaction({ + receiverId: contractId, + actions: [functionCall(methodName, args, BigInt(gas), BigInt(deposit))] + }); + } + + /** + * @param options + * @param options.message The message to be signed (e.g. "authenticating") + * @param options.recipient Who will receive the message (e.g. auth.app.com) + * @param options.nonce A challenge sent by the recipient + * @param options.callbackUrl (optional) Deprecated parameter used only by browser wallets + * @returns + */ + public async signNep413Message({ message, recipient, nonce, callbackUrl }: { + message: string, + recipient: string, + nonce: Uint8Array, + callbackUrl?: string + }): Promise { + if (!this.signer) throw new Error("Please set a signer") + return this.signer.signNep413Message(message, this.accountId, recipient, nonce, callbackUrl) + } + + // DEPRECATED FUNCTIONS BELLOW - Please remove in next release + + /** + * @deprecated please use {@link Account.createSignedMetaTransaction} instead + * + * Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance + * + * @param options Options for the transaction. + * @param options.actions Actions to be included in the meta transaction + * @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction + * @param options.receiverId Receiver account of the meta transaction + */ + public async signedDelegate({ + actions, + blockHeightTtl, + receiverId, + }: SignedDelegateOptions): Promise { + const { header } = await this.provider.viewBlock({ finality: DEFAULT_FINALITY }); + + if (!this.signer) throw new Error(`Please set a signer`); + + const pk = await this.signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); + + const delegateAction = buildDelegateAction({ + actions, + maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl), + nonce: BigInt(accessKey.nonce) + 1n, + publicKey: pk, + receiverId, + senderId: this.accountId, + }); + + const [, signedDelegate] = await this.signer.signDelegateAction( + delegateAction + ); + + return signedDelegate; + } + + /** * @deprecated + */ + public getConnection(): Connection { + return new Connection("", this.provider, this.signer); + } + + /** @hidden */ + private validateArgs(args: any) { + const isUint8Array = + args.byteLength !== undefined && args.byteLength === args.length; + if (isUint8Array) { + return; + } + + if (Array.isArray(args) || typeof args !== "object") { + throw new PositionalArgsError(); + } + } + + /** + * @deprecated please use callFunction instead * + * Execute a function call. + * @param options The options for the function call. + * @param options.contractId The NEAR account ID of the smart contract. + * @param options.methodName The name of the method to be called on the smart contract. + * @param options.args The arguments to be passed to the method. + * @param options.gas The maximum amount of gas to be used for the function call. + * @param options.attachedDeposit The amount of NEAR tokens to be attached to the function call. + * @param options.walletMeta Metadata for wallet integration. + * @param options.walletCallbackUrl The callback URL for wallet integration. + * @param options.stringify A function to convert input arguments into bytes array + * @returns {Promise} A promise that resolves to the final execution outcome of the function call. + */ + async functionCall({ + contractId, + methodName, + args = {}, + gas = DEFAULT_FUNCTION_CALL_GAS, + attachedDeposit, + walletMeta, + walletCallbackUrl, + stringify, + }: ChangeFunctionCallOptions): Promise { + this.validateArgs(args); + + const stringifyArg = + stringify === undefined ? stringifyJsonOrBytes : stringify; + const functionCallArgs = [ + methodName, + args, + gas, + attachedDeposit, + stringifyArg, + false, + ]; + + return this.signAndSendTransactionLegacy({ + receiverId: contractId, + // eslint-disable-next-line prefer-spread + actions: [functionCall.apply(void 0, functionCallArgs)], + walletMeta, + walletCallbackUrl, + }); + } + + /** + * @deprecated use instead {@link Provider.viewTransactionStatus} + */ + public async getTransactionStatus( + txHash: string | Uint8Array + ): Promise { + return this.provider.viewTransactionStatus( + txHash, + this.accountId, // accountId is used to determine on which shard to look for a tx + "EXECUTED_OPTIMISTIC" + ); + } + + /** + * @deprecated use ${@link createSignedTransaction} + * Create a signed transaction which can be broadcast to the network + * @param receiverId NEAR account receiving the transaction + * @param actions list of actions to perform as part of the transaction + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} + */ + public async signTransaction( + receiverId: string, + actions: Action[], + opts?: { signer: Signer } + ): Promise<[Uint8Array, SignedTransaction]> { + const signer = opts?.signer || this.signer; + + if (!signer) throw new Error(`Please set a signer`); + + const pk = await signer.getPublicKey(); + + const accessKey = await this.getAccessKey(pk); + + const block = await this.provider.viewBlock({ + finality: DEFAULT_FINALITY, + }); + const recentBlockHash = block.header.hash; + + const nonce = BigInt(accessKey.nonce) + 1n; + + const tx = createTransaction( + this.accountId, + pk, + receiverId, + nonce + 1n, + actions, + baseDecode(recentBlockHash) + ); + + return signer.signTransaction(tx); + } + + /** + * @deprecated instead please create a transaction with + * the actions bellow and broadcast it to the network + * 1. createAccount + * 2. transfer some tokens + * 3. deployContract + * 4. (optional) addKey + * 5. (optional) functionCall to an initialization function + * + * Create a new account and deploy a contract to it * @param contractId NEAR account where the contract is deployed * @param publicKey The public key to add to the created contract account * @param data The compiled contract code @@ -529,7 +736,7 @@ export class Account implements IntoConnection { amount: bigint ): Promise { const accessKey = fullAccessKey(); - await this.signAndSendTransaction({ + await this.signAndSendTransactionLegacy({ receiverId: contractId, actions: [ createAccount(), @@ -542,7 +749,7 @@ export class Account implements IntoConnection { } /** - * @deprecated + * @deprecated please instead use {@link transfer} * * @param receiverId NEAR account receiving Ⓝ * @param amount Amount to send in yoctoⓃ @@ -551,14 +758,14 @@ export class Account implements IntoConnection { receiverId: string, amount: bigint ): Promise { - return this.signAndSendTransaction({ + return this.signAndSendTransactionLegacy({ receiverId, actions: [transfer(amount)], }); } /** - * @deprecated + * @deprecated please instead use {@link createTopLevelAccount} * * @param newAccountId NEAR account name to be created * @param publicKey A public key created from the masterAccount @@ -568,190 +775,189 @@ export class Account implements IntoConnection { publicKey: string | PublicKey, amount: bigint ): Promise { - const accessKey = fullAccessKey(); - return this.signAndSendTransaction({ + return this.signAndSendTransactionLegacy({ receiverId: newAccountId, actions: [ createAccount(), transfer(amount), - addKey(PublicKey.from(publicKey), accessKey), + addKey(PublicKey.from(publicKey), fullAccessKey()), ], }); } - public async createTopLevelAccount( - account: string, - pk: PublicKey | string, - amount: bigint | string | number, - opts?: SignerOptions + /** + * @deprecated please instead use {@link signAndSendTransaction} + * + * Sign a transaction to perform a list of actions and broadcast it using the RPC API. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider } + * + * @param options The options for signing and sending the transaction. + * @param options.receiverId The NEAR account ID of the transaction receiver. + * @param options.actions The list of actions to be performed in the transaction. + * @param options.returnError Whether to return an error if the transaction fails. + * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. + * + */ + async signAndSendTransactionLegacy( + { receiverId, actions, returnError }: SignAndSendTransactionOptions, + opts?: { signer: Signer } ): Promise { - const topAccount = account.split(".").at(-1); + let txHash, signedTx; - if (!topAccount) - throw new Error( - `Failed to parse top account out of the name for new account` - ); + // Default number of retries with different nonce before giving up on a transaction. + const TX_NONCE_RETRY_NUMBER = 12; - const actions = [ - functionCall( - "create_account", - { - new_account_id: account, - new_public_key: pk.toString(), - }, - BigInt(60_000_000_000_000), - BigInt(amount) - ), - ]; + // Default wait until next retry in millis. + const TX_NONCE_RETRY_WAIT = 500; - return this.signAndSendTransaction( - { - receiverId: topAccount, - actions: actions, - }, - opts - ); - } + // Exponential back off for waiting to retry. + const TX_NONCE_RETRY_WAIT_BACKOFF = 1.5; - public async createSubAccount( - accountOrPrefix: string, - pk: PublicKey | string, - amount: bigint | string | number, - opts?: SignerOptions - ): Promise { - const newAccountId = accountOrPrefix.includes(".") - ? accountOrPrefix - : `${accountOrPrefix}.${this.accountId}`; - const actions = [ - createAccount(), - transfer(BigInt(amount)), - addKey(PublicKey.from(pk), fullAccessKey()), - ]; + // TODO: TX_NONCE (different constants for different uses of exponentialBackoff?) + const result = await exponentialBackoff( + TX_NONCE_RETRY_WAIT, + TX_NONCE_RETRY_NUMBER, + TX_NONCE_RETRY_WAIT_BACKOFF, + async () => { + [txHash, signedTx] = await this.signTransaction( + receiverId, + actions, + opts + ); - return this.signAndSendTransaction( - { - receiverId: newAccountId, - actions: actions, - }, - opts + try { + return await this.provider.sendTransaction(signedTx); + } catch (error) { + if (error.type === "InvalidNonce") { + Logger.warn( + `Retrying transaction ${receiverId}:${baseEncode( + txHash + )} with new nonce.` + ); + return null; + } + if (error.type === "Expired") { + Logger.warn( + `Retrying transaction ${receiverId}:${baseEncode( + txHash + )} due to expired block hash` + ); + return null; + } + + error.context = new ErrorContext(baseEncode(txHash)); + throw error; + } + } ); - } + if (!result) { + // TODO: This should have different code actually, as means "transaction not submitted for sure" + throw new TypedError( + "nonce retries exceeded for transaction. This usually means there are too many parallel requests with the same access key.", + "RetriesExceeded" + ); + } - public async createSubAccountAndDeployContract( - accountOrPrefix: string, - pk: PublicKey | string, - amount: bigint | string | number, - code: Uint8Array, - opts?: SignerOptions - ): Promise { - const newAccountId = accountOrPrefix.includes(".") - ? accountOrPrefix - : `${accountOrPrefix}.${this.accountId}`; - const actions = [ - createAccount(), - transfer(BigInt(amount)), - addKey(PublicKey.from(pk), fullAccessKey()), - deployContract(code), - ]; + printTxOutcomeLogsAndFailures({ + contractId: signedTx.transaction.receiverId, + outcome: result, + }); - return this.signAndSendTransaction( - { - receiverId: newAccountId, - actions: actions, - }, - opts - ); + // Should be falsy if result.status.Failure is null + if ( + !returnError && + typeof result.status === "object" && + typeof result.status.Failure === "object" && + result.status.Failure !== null + ) { + // if error data has error_message and error_type properties, we consider that node returned an error in the old format + if ( + result.status.Failure.error_message && + result.status.Failure.error_type + ) { + throw new TypedError( + `Transaction ${result.transaction_outcome.id} failed. ${result.status.Failure.error_message}`, + result.status.Failure.error_type + ); + } else { + throw parseResultError(result); + } + } + // TODO: if Tx is Unknown or Started. + return result; } + /** @hidden */ + accessKeyByPublicKeyCache: { [key: string]: AccessKeyView } = {}; + /** - * @param beneficiaryId The NEAR account that will receive the remaining Ⓝ balance from the account being deleted + * @deprecated, accounts will no longer handle keystores + * + * Finds the {@link AccessKeyView} associated with the accounts {@link PublicKey} stored in the {@link "@near-js/keystores".keystore.KeyStore | Keystore}. + * + * @param receiverId currently unused + * @param actions currently unused + * @returns `{ publicKey PublicKey; accessKey: AccessKeyView }` */ - public async deleteAccount( - beneficiaryId: string, - opts?: SignerOptions - ): Promise { - Logger.log( - "Deleting an account does not automatically transfer NFTs and FTs to the beneficiary address. Ensure to transfer assets before deleting." - ); - - const actions = [deleteAccount(beneficiaryId)]; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async findAccessKey( + receiverId: string, + actions: Action[] + ): Promise<{ publicKey: PublicKey; accessKey: AccessKeyView }> { + if (!this.signer) throw new Error(`Please set a signer`); - return this.signAndSendTransaction( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } + const publicKey = await this.signer.getPublicKey(); + if (!publicKey) { + throw new TypedError( + `no matching key pair found in ${this.signer.constructor.name}`, + "PublicKeyNotFound" + ); + } - /** - * @param code The compiled contract code bytes - */ - public async deployContract( - code: Uint8Array, - opts?: SignerOptions - ): Promise { - const actions = [deployContract(code)]; + const cachedAccessKey = + this.accessKeyByPublicKeyCache[publicKey.toString()]; + if (cachedAccessKey !== undefined) { + return { publicKey, accessKey: cachedAccessKey }; + } - return this.signAndSendTransaction( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } + try { + const rawAccessKey = await this.provider.query({ + request_type: "view_access_key", + account_id: this.accountId, + public_key: publicKey.toString(), + finality: DEFAULT_FINALITY, + }); - /** - * @deprecated - * - * Execute a function call. - * @param options The options for the function call. - * @param options.contractId The NEAR account ID of the smart contract. - * @param options.methodName The name of the method to be called on the smart contract. - * @param options.args The arguments to be passed to the method. - * @param options.gas The maximum amount of gas to be used for the function call. - * @param options.attachedDeposit The amount of NEAR tokens to be attached to the function call. - * @param options.walletMeta Metadata for wallet integration. - * @param options.walletCallbackUrl The callback URL for wallet integration. - * @param options.stringify A function to convert input arguments into bytes array - * @returns {Promise} A promise that resolves to the final execution outcome of the function call. - */ - async functionCall({ - contractId, - methodName, - args = {}, - gas = DEFAULT_FUNCTION_CALL_GAS, - attachedDeposit, - walletMeta, - walletCallbackUrl, - stringify, - }: ChangeFunctionCallOptions): Promise { - this.validateArgs(args); + // store nonce as BigInt to preserve precision on big number + const accessKey = { + ...rawAccessKey, + nonce: BigInt(rawAccessKey.nonce || 0), + }; + // this function can be called multiple times and retrieve the same access key + // this checks to see if the access key was already retrieved and cached while + // the above network call was in flight. To keep nonce values in line, we return + // the cached access key. + if (this.accessKeyByPublicKeyCache[publicKey.toString()]) { + return { + publicKey, + accessKey: + this.accessKeyByPublicKeyCache[publicKey.toString()], + }; + } - const stringifyArg = - stringify === undefined ? stringifyJsonOrBytes : stringify; - const functionCallArgs = [ - methodName, - args, - gas, - attachedDeposit, - stringifyArg, - false, - ]; + this.accessKeyByPublicKeyCache[publicKey.toString()] = accessKey; + return { publicKey, accessKey }; + } catch (e) { + if (e.type == "AccessKeyDoesNotExist") { + return null; + } - return this.signAndSendTransaction({ - receiverId: contractId, - // eslint-disable-next-line prefer-spread - actions: [functionCall.apply(void 0, functionCallArgs)], - walletMeta, - walletCallbackUrl, - }); + throw e; + } } /** - * @deprecated + * @deprecated please use {@link addFullAccessKey} or {@link addFunctionAccessKey} * * @see [https://docs.near.org/concepts/basics/accounts/access-keys](https://docs.near.org/concepts/basics/accounts/access-keys) * @todo expand this API to support more options. @@ -778,7 +984,7 @@ export class Account implements IntoConnection { } else { accessKey = functionCallAccessKey(contractId, methodNames, amount); } - return this.signAndSendTransaction({ + return this.signAndSendTransactionLegacy({ receiverId: this.accountId, actions: [addKey(PublicKey.from(publicKey), accessKey)], }); @@ -786,95 +992,11 @@ export class Account implements IntoConnection { public async addFullAccessKey( pk: PublicKey | string, - opts?: SignerOptions + opts?: { signer: Signer } ): Promise { const actions = [addKey(PublicKey.from(pk), fullAccessKey())]; - return this.signAndSendTransaction( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } - - public async addFunctionAccessKey( - pk: PublicKey | string, - receiverId: string, - methodNames: string[], - allowance?: bigint | string | number, - opts?: SignerOptions - ): Promise { - const actions = [ - addKey( - PublicKey.from(pk), - functionCallAccessKey( - receiverId, - methodNames, - BigInt(allowance) - ) - ), - ]; - - return this.signAndSendTransaction( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } - - /** - * @param publicKey The public key to be deleted - * @returns {Promise} - */ - public async deleteKey( - publicKey: string | PublicKey, - opts?: SignerOptions - ): Promise { - const actions = [deleteKey(PublicKey.from(publicKey))]; - - return this.signAndSendTransaction( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } - - public async transfer( - receiverId: string, - amount: bigint | string | number, - opts?: SignerOptions - ): Promise { - const actions = [transfer(BigInt(amount))]; - - return this.signAndSendTransaction( - { - receiverId: receiverId, - actions: actions, - }, - opts - ); - } - - /** - * @see [https://near-nodes.io/validator/staking-and-delegation](https://near-nodes.io/validator/staking-and-delegation) - * - * @param publicKey The public key for the account that's staking - * @param amount The account to stake in yoctoⓃ - */ - public async stake( - publicKey: string | PublicKey, - amount: bigint | string | number, - opts?: SignerOptions - ): Promise { - const actions = [stake(BigInt(amount), PublicKey.from(publicKey))]; - - return this.signAndSendTransaction( + return this.signAndSendTransactionLegacy( { receiverId: this.accountId, actions: actions, @@ -883,63 +1005,11 @@ export class Account implements IntoConnection { ); } - /** - * @deprecated please use {@link Account.signMetaTransaction} instead - * - * Compose and sign a SignedDelegate action to be executed in a transaction on behalf of this Account instance - * - * @param options Options for the transaction. - * @param options.actions Actions to be included in the meta transaction - * @param options.blockHeightTtl Number of blocks past the current block height for which the SignedDelegate action may be included in a meta transaction - * @param options.receiverId Receiver account of the meta transaction - */ - public async signedDelegate({ - actions, - blockHeightTtl, - receiverId, - }: SignedDelegateOptions): Promise { - const { header } = await this.provider.block({ finality: "final" }); - - if (!this.signer) throw new Error(`Signer is required`); - - const pk = await this.signer.getPublicKey(); - - const accessKey = await this.getAccessKey(pk); - - const delegateAction = buildDelegateAction({ - actions, - maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl), - nonce: BigInt(accessKey.nonce) + 1n, - publicKey: pk, - receiverId, - senderId: this.accountId, - }); - - const [, signedDelegate] = await this.signer.signDelegateAction( - delegateAction - ); - - return signedDelegate; - } - - /** @hidden */ - private validateArgs(args: any) { - const isUint8Array = - args.byteLength !== undefined && args.byteLength === args.length; - if (isUint8Array) { - return; - } - - if (Array.isArray(args) || typeof args !== "object") { - throw new PositionalArgsError(); - } - } - /** * Invoke a contract view function using the RPC API. * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) * - * @deprecated + * @deprecated please use {@link Provider.callFunction} instead * * @param options Function call options. * @param options.contractId NEAR account where the contract is deployed @@ -947,27 +1017,26 @@ export class Account implements IntoConnection { * @param options.args Any arguments to the view contract method, wrapped in JSON * @param options.parse Parse the result of the call. Receives a Buffer (bytes array) and converts it to any object. By default result will be treated as json. * @param options.stringify Convert input arguments into a bytes array. By default the input is treated as a JSON. - * @param options.blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). + * @param options.blockQuery specifies which block to query state at. By default returns last DEFAULT_FINALITY block (i.e. not necessarily finalized). * @returns {Promise} */ - async viewFunction(options: ViewFunctionCallOptions): Promise { return await viewFunction(this.getConnection(), options); } /** + * @deprecated please use {@link getContractState} instead + * * Returns the state (key value pairs) of this account's contract based on the key prefix. * Pass an empty string for prefix if you would like to return the entire state. * @see [https://docs.near.org/api/rpc/contracts#view-contract-state](https://docs.near.org/api/rpc/contracts#view-contract-state) - * - * @deprecated - * + * * @param prefix allows to filter which keys should be returned. Empty prefix means all keys. String prefix is utf-8 encoded. - * @param blockQuery specifies which block to query state at. By default returns last "optimistic" block (i.e. not necessarily finalized). + * @param blockQuery specifies which block to query state at. By default returns last DEFAULT_FINALITY block (i.e. not necessarily finalized). */ async viewState( prefix: string | Uint8Array, - blockQuery: BlockReference = { finality: "optimistic" } + blockQuery: BlockReference = { finality: DEFAULT_FINALITY } ): Promise> { return await viewState( this.getConnection(), @@ -987,7 +1056,7 @@ export class Account implements IntoConnection { const response = await this.provider.query({ request_type: "view_access_key_list", account_id: this.accountId, - finality: "optimistic", + finality: DEFAULT_FINALITY, }); // Replace raw nonce into a new BigInt return response?.keys?.map((key) => ({ @@ -1032,9 +1101,9 @@ export class Account implements IntoConnection { */ async getAccountBalance(): Promise { const protocolConfig = await this.provider.experimental_protocolConfig({ - finality: "final", + finality: DEFAULT_FINALITY, }); - const state = await this.state(); + const state = await this.getState(); const costPerByte = BigInt( protocolConfig.runtime_config.storage_amount_per_byte @@ -1062,7 +1131,7 @@ export class Account implements IntoConnection { * @returns {Promise} */ async getActiveDelegatedStakeBalance(): Promise { - const block = await this.provider.block({ finality: "final" }); + const block = await this.provider.viewBlock({ finality: DEFAULT_FINALITY }); const blockHash = block.header.hash; const epochId = block.header.epoch_id; const { current_validators, next_validators, current_proposals } = @@ -1135,35 +1204,4 @@ export class Account implements IntoConnection { total: summary.total.toString(), }; } - - /** - * Execute a function call - * - * @param contractId - * @param methodName - * @param args - * @param deposit - * @param gas - * @returns - */ - public async callFunction( - contractId: string, - methodName: string, - args: Record = {}, - deposit: bigint | string | number = 0n, - gas: bigint | string | number = DEFAULT_FUNCTION_CALL_GAS, - opts?: SignerOptions - ): Promise { - const actions = [ - functionCall(methodName, args, BigInt(gas), BigInt(deposit)), - ]; - - return this.signAndSendTransaction( - { - receiverId: contractId, - actions: actions, - }, - opts - ); - } } diff --git a/packages/accounts/src/account_2fa.ts b/packages/accounts/src/account_2fa.ts deleted file mode 100644 index ad539b4072..0000000000 --- a/packages/accounts/src/account_2fa.ts +++ /dev/null @@ -1,347 +0,0 @@ -import { KeyPair, PublicKey } from '@near-js/crypto'; -import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@near-js/types'; -import { actionCreators } from '@near-js/transactions'; -import { Logger } from '@near-js/utils' - -import { SignAndSendTransactionOptions } from './account'; -import { AccountMultisig } from './account_multisig'; -import { Connection } from './connection'; -import { - MULTISIG_CHANGE_METHODS, - MULTISIG_CONFIRM_METHODS, - MULTISIG_DEPOSIT, - MULTISIG_GAS, -} from './constants'; -import { MultisigStateStatus } from './types'; -import { KeyPairSigner } from '@near-js/signers'; - -const { addKey, deleteKey, deployContract, fullAccessKey, functionCall, functionCallAccessKey } = actionCreators; - -type sendCodeFunction = () => Promise; -type getCodeFunction = (method: any) => Promise; -type verifyCodeFunction = (securityCode: any) => Promise; - -export class Account2FA extends AccountMultisig { - /******************************** - Account2FA has options object where you can provide callbacks for: - - sendCode: how to send the 2FA code in case you don't use NEAR Contract Helper - - getCode: how to get code from user (use this to provide custom UI/UX for prompt of 2FA code) - - onResult: the tx result after it's been confirmed by NEAR Contract Helper - ********************************/ - public sendCode: sendCodeFunction; - public getCode: getCodeFunction; - public verifyCode: verifyCodeFunction; - public onConfirmResult: (any) => any; - public helperUrl = 'https://helper.testnet.near.org'; - - constructor(connection: Connection, accountId: string, options: any) { - super(connection, accountId, options); - this.helperUrl = options.helperUrl || this.helperUrl; - this.storage = options.storage; - this.sendCode = options.sendCode || this.sendCodeDefault; - this.getCode = options.getCode || this.getCodeDefault; - this.verifyCode = options.verifyCode || this.verifyCodeDefault; - this.onConfirmResult = options.onConfirmResult; - } - - /** - * Sign a transaction to preform a list of actions and broadcast it using the RPC API. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider.sendTransaction | JsonRpcProvider.sendTransaction} - * - * @param options Options for the transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { - await super.signAndSendTransaction({ receiverId, actions }); - // TODO: Should following override onRequestResult in superclass instead of doing custom signAndSendTransaction? - await this.sendCode(); - const result = await this.promptAndVerify(); - if (this.onConfirmResult) { - await this.onConfirmResult(result); - } - return result; - } - - // default helpers for CH deployments of multisig - - /** - * Deploy a multisig contract with 2FA and handle the deployment process. - * @param contractBytes - The bytecode of the multisig contract. - * @returns {Promise} A promise that resolves to the final execution outcome of the deployment. - */ - async deployMultisig(contractBytes: Uint8Array) { - const { accountId } = this; - - const seedOrLedgerKey = (await this.getRecoveryMethods()).data - // @ts-ignore - .filter(({ kind, publicKey }) => (kind === 'phrase' || kind === 'ledger') && publicKey !== null) - .map((rm) => rm.publicKey); - - const fak2lak = (await this.getAccessKeys()) - .filter(({ public_key, access_key: { permission } }) => permission === 'FullAccess' && !seedOrLedgerKey.includes(public_key)) - .map((ak) => ak.public_key) - .map(toPK); - - // @ts-ignore - const confirmOnlyKey = toPK((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - - const newArgs = Buffer.from(JSON.stringify({ 'num_confirmations': 2 })); - - const actions = [ - ...fak2lak.map((pk) => deleteKey(pk)), - ...fak2lak.map((pk) => addKey(pk, functionCallAccessKey(accountId, MULTISIG_CHANGE_METHODS, null))), - addKey(confirmOnlyKey, functionCallAccessKey(accountId, MULTISIG_CONFIRM_METHODS, null)), - deployContract(contractBytes), - ]; - const newFunctionCallActionBatch = actions.concat(functionCall('new', newArgs, MULTISIG_GAS, MULTISIG_DEPOSIT)); - Logger.log('deploying multisig contract for', accountId); - - const { stateStatus: multisigStateStatus } = await this.checkMultisigCodeAndStateStatus(contractBytes); - switch (multisigStateStatus) { - case MultisigStateStatus.STATE_NOT_INITIALIZED: - return await super.signAndSendTransactionWithAccount(accountId, newFunctionCallActionBatch); - case MultisigStateStatus.VALID_STATE: - return await super.signAndSendTransactionWithAccount(accountId, actions); - case MultisigStateStatus.INVALID_STATE: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account has existing state.`, 'ContractHasExistingState'); - default: - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - } - - /** - * Disable 2FA with the option to clean up contract state. - * @param options Options for disabling 2FA. - * @param options.contractBytes The bytecode of the contract to deploy. - * @param options.cleanupContractBytes The bytecode of the cleanup contract (optional). - * @returns {Promise} A promise that resolves to the final execution outcome of the operation. - */ - async disableWithFAK({ contractBytes, cleanupContractBytes }: { contractBytes: Uint8Array; cleanupContractBytes?: Uint8Array }) { - let cleanupActions = []; - if(cleanupContractBytes) { - await this.deleteAllRequests().catch(e => e); - cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes); - } - const keyConversionActions = await this.get2faDisableKeyConversionActions(); - - const actions = [ - ...cleanupActions, - ...keyConversionActions, - deployContract(contractBytes) - ]; - - const accessKeyInfo = await this.findAccessKey(this.accountId, actions); - - if(accessKeyInfo && accessKeyInfo.accessKey && accessKeyInfo.accessKey.permission !== 'FullAccess') { - throw new TypedError('No full access key found in keystore. Unable to bypass multisig', 'NoFAKFound'); - } - - return this.signAndSendTransactionWithAccount(this.accountId, actions); - } - - /** - * Retrieves cleanup actions for disabling 2FA. - * @param cleanupContractBytes - The bytecode of the cleanup contract. - * @returns {Promise} - A promise that resolves to an array of cleanup actions. - */ - async get2faDisableCleanupActions(cleanupContractBytes: Uint8Array) { - const currentAccountState: { key: Buffer; value: Buffer }[] = await this.viewState('').catch(error => { - const cause = error.cause && error.cause.name; - if (cause == 'NO_CONTRACT_CODE') { - return []; - } - throw cause == 'TOO_LARGE_CONTRACT_STATE' - ? new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account has existing state.`, 'ContractHasExistingState') - : error; - }); - - const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64')); - return currentAccountState.length ? [ - deployContract(cleanupContractBytes), - functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, 0n) - ] : []; - } - - /** - * Retrieves key conversion actions for disabling 2FA. - * @returns {Promise} - A promise that resolves to an array of key conversion actions. - */ - async get2faDisableKeyConversionActions() { - const { accountId } = this; - const accessKeys = await this.getAccessKeys(); - const lak2fak = accessKeys - .filter(({ access_key }) => access_key.permission !== 'FullAccess') - .filter(({ access_key }) => { - const perm = (access_key.permission as FunctionCallPermissionView).FunctionCall; - return perm.receiver_id === accountId && - perm.method_names.length === 4 && - perm.method_names.includes('add_request_and_confirm'); - }); - // @ts-ignore - const confirmOnlyKey = PublicKey.from((await this.postSignedJson('/2fa/getAccessKey', { accountId })).publicKey); - return [ - deleteKey(confirmOnlyKey), - ...lak2fak.map(({ public_key }) => deleteKey(PublicKey.from(public_key))), - ...lak2fak.map(({ public_key }) => addKey(PublicKey.from(public_key), fullAccessKey())) - ]; - } - - /** - * This method converts LAKs back to FAKs, clears state and deploys an 'empty' contract (contractBytes param) - * @param [contractBytes]{@link https://github.com/near/near-wallet/blob/master/packages/frontend/src/wasm/main.wasm?raw=true} - * @param [cleanupContractBytes]{@link https://github.com/near/core-contracts/blob/master/state-manipulation/res/state_cleanup.wasm?raw=true} - * @returns {Promise} A promise that resolves to the final execution outcome of the operation. - */ - async disable(contractBytes: Uint8Array, cleanupContractBytes: Uint8Array) { - const { stateStatus } = await this.checkMultisigCodeAndStateStatus(); - if(stateStatus !== MultisigStateStatus.VALID_STATE && stateStatus !== MultisigStateStatus.STATE_NOT_INITIALIZED) { - throw new TypedError(`Can not deploy a contract to account ${this.accountId} on network ${this.getConnection().networkId}, the account state could not be verified.`, 'ContractStateUnknown'); - } - - let deleteAllRequestsError; - await this.deleteAllRequests().catch(e => deleteAllRequestsError = e); - - const cleanupActions = await this.get2faDisableCleanupActions(cleanupContractBytes).catch(e => { - if(e.type === 'ContractHasExistingState') { - throw deleteAllRequestsError || e; - } - throw e; - }); - - const actions = [ - ...cleanupActions, - ...(await this.get2faDisableKeyConversionActions()), - deployContract(contractBytes), - ]; - Logger.log('disabling 2fa for', this.accountId); - return await this.signAndSendTransaction({ - receiverId: this.accountId, - actions - }); - } - - /** - * Default implementation for sending the 2FA code. - * @returns {Promise} - A promise that resolves to the request ID. - */ - async sendCodeDefault() { - const { accountId } = this; - const { requestId } = this.getRequest(); - const method = await this.get2faMethod(); - await this.postSignedJson('/2fa/send', { - accountId, - method, - requestId, - }); - return requestId; - } - - async getCodeDefault(): Promise { - throw new Error('There is no getCode callback provided. Please provide your own in AccountMultisig constructor options. It has a parameter method where method.kind is "email" or "phone".'); - } - - /** - * Prompts the user to enter and verify the 2FA code. - * @returns {Promise} - A promise that resolves to the verification result. - */ - async promptAndVerify() { - const method = await this.get2faMethod(); - const securityCode = await this.getCode(method); - try { - const result = await this.verifyCode(securityCode); - - // TODO: Parse error from result for real (like in normal account.signAndSendTransaction) - return result; - } catch (e) { - Logger.warn('Error validating security code:', e); - if (e.toString().includes('invalid 2fa code provided') || e.toString().includes('2fa code not valid')) { - return await this.promptAndVerify(); - } - - throw e; - } - } - - /** - * Verify the 2FA code using the default method. - * @param securityCode - The security code to verify. - * @returns {Promise} A promise that resolves to the verification result. - */ - async verifyCodeDefault(securityCode: string) { - const { accountId } = this; - const request = this.getRequest(); - if (!request) { - throw new Error('no request pending'); - } - const { requestId } = request; - return await this.postSignedJson('/2fa/verify', { - accountId, - securityCode, - requestId - }); - } - - /** - * Retrieves recovery methods for the account. - * @returns {Promise<{ accountId: string, data: any }>} - A promise that resolves to recovery methods data. - */ - async getRecoveryMethods() { - const { accountId } = this; - return { - accountId, - data: await this.postSignedJson('/account/recoveryMethods', { accountId }) - }; - } - - /** - * Gets the 2FA method (kind and detail). - * @returns {Promise<{ kind: string, detail: string }>} A promise that resolves to the 2FA method. - */ - async get2faMethod() { - let { data } = await this.getRecoveryMethods(); - // @ts-ignore - if (data && data.length) { - // @ts-ignore - data = data.find((m) => m.kind.indexOf('2fa-') === 0); - } - if (!data) return null; - // @ts-ignore - const { kind, detail } = data; - return { kind, detail }; - } - - /** - * Generates a signature for the latest finalized block. - * @returns {Promise<{ blockNumber: string, blockNumberSignature: string }>} - A promise that resolves to the signature information. - */ - async signatureFor() { - const block = await this.getConnection().provider.block({ finality: 'final' }); - const blockNumber = block.header.height.toString(); - // @ts-expect-error keyPair isn't public - const keyPair = (this.getConnection().signer as KeyPairSigner).keyPair as KeyPair; - const signed = keyPair.sign(Buffer.from(blockNumber)); - const blockNumberSignature = Buffer.from(signed.signature).toString('base64'); - return { blockNumber, blockNumberSignature }; - } - - /** - * Sends a signed JSON request to a specified path. - * @param path - The path for the request. - * @param body - The request body. - * @returns {Promise} - A promise that resolves to the response from the helper. - */ - async postSignedJson(path, body) { - return await fetch(this.helperUrl + path, { - body: JSON.stringify({ - ...body, - ...(await this.signatureFor()), - }), - method: 'POST', - }); - } -} - -// helpers -const toPK = (pk) => PublicKey.from(pk); diff --git a/packages/accounts/src/account_multisig.ts b/packages/accounts/src/account_multisig.ts deleted file mode 100644 index cb4314e4aa..0000000000 --- a/packages/accounts/src/account_multisig.ts +++ /dev/null @@ -1,261 +0,0 @@ -import { Action, actionCreators } from '@near-js/transactions'; -import { FinalExecutionOutcome } from '@near-js/types'; -import { Logger } from '@near-js/utils'; - -import { Account, SignAndSendTransactionOptions } from './account'; -import { Connection } from './connection'; -import { - MULTISIG_ALLOWANCE, - MULTISIG_CHANGE_METHODS, - MULTISIG_DEPOSIT, - MULTISIG_GAS, - MULTISIG_STORAGE_KEY, -} from './constants'; -import { MultisigDeleteRequestRejectionError, MultisigStateStatus } from './types'; - -const { deployContract, functionCall } = actionCreators; - -enum MultisigCodeStatus { - INVALID_CODE, - VALID_CODE, - UNKNOWN_CODE -} - -// in memory request cache for node w/o localStorage -const storageFallback = { - [MULTISIG_STORAGE_KEY]: null -}; - -export class AccountMultisig extends Account { - public storage: any; - public onAddRequestResult: (any) => any; - - /** - * Constructs an instance of the `AccountMultisig` class. - * @param connection The NEAR connection object. - * @param accountId The NEAR account ID. - * @param options Additional options for the multisig account. - * @param options.storage Storage to store data related to multisig operations. - * @param options.onAddRequestResult Callback function to handle the result of adding a request. - */ - constructor(connection: Connection, accountId: string, options: any) { - super(accountId, connection.provider, connection.signer); - this.storage = options.storage; - this.onAddRequestResult = options.onAddRequestResult; - } - - /** - * Sign and send a transaction with the multisig account as the sender. - * @param receiverId - The NEAR account ID of the transaction receiver. - * @param actions - The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - async signAndSendTransactionWithAccount(receiverId: string, actions: Action[]): Promise { - return super.signAndSendTransaction({ receiverId, actions }); - } - - /** - * Sign and send a multisig transaction to add a request and confirm it. - * @param options Options for the multisig transaction. - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions The list of actions to be included in the transaction. - * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. - */ - async signAndSendTransaction({ receiverId, actions }: SignAndSendTransactionOptions): Promise { - const { accountId } = this; - - const args = Buffer.from(JSON.stringify({ - request: { - receiver_id: receiverId, - actions: convertActions(actions, accountId, receiverId) - } - })); - - let result; - try { - result = await super.signAndSendTransaction({ - receiverId: accountId, - actions: [ - functionCall('add_request_and_confirm', args, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }); - } catch (e) { - if (e.toString().includes('Account has too many active requests. Confirm or delete some')) { - await this.deleteUnconfirmedRequests(); - return await this.signAndSendTransaction({ receiverId, actions }); - } - throw e; - } - - // TODO: Are following even needed? Seems like it throws on error already - if (!result.status) { - throw new Error('Request failed'); - } - const status: any = { ...result.status }; - if (!status.SuccessValue || typeof status.SuccessValue !== 'string') { - throw new Error('Request failed'); - } - - this.setRequest({ - accountId, - actions, - requestId: parseInt(Buffer.from(status.SuccessValue, 'base64').toString('ascii'), 10) - }); - - if (this.onAddRequestResult) { - await this.onAddRequestResult(result); - } - - // NOTE there is no await on purpose to avoid blocking for 2fa - this.deleteUnconfirmedRequests(); - - return result; - } - - /** - * This method submits a canary transaction that is expected to always fail in order to determine whether the contract currently has valid multisig state - * and whether it is initialized. The canary transaction attempts to delete a request at index u32_max and will go through if a request exists at that index. - * a u32_max + 1 and -1 value cannot be used for the canary due to expected u32 error thrown before deserialization attempt. - * @param contractBytes The bytecode of the multisig contract. - * @returns {Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }>} A promise that resolves to the status of the code and state. - */ - async checkMultisigCodeAndStateStatus(contractBytes?: Uint8Array): Promise<{ codeStatus: MultisigCodeStatus; stateStatus: MultisigStateStatus }> { - const u32_max = 4_294_967_295; - const validCodeStatusIfNoDeploy = contractBytes ? MultisigCodeStatus.UNKNOWN_CODE : MultisigCodeStatus.VALID_CODE; - - try { - if(contractBytes) { - await super.signAndSendTransaction({ - receiverId: this.accountId, actions: [ - deployContract(contractBytes), - functionCall('delete_request', { request_id: u32_max }, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }); - } else { - await this.deleteRequest(u32_max); - } - - return { codeStatus: MultisigCodeStatus.VALID_CODE, stateStatus: MultisigStateStatus.VALID_STATE }; - } catch (e) { - if (new RegExp(MultisigDeleteRequestRejectionError.CANNOT_DESERIALIZE_STATE).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.INVALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.MULTISIG_NOT_INITIALIZED).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.NO_SUCH_REQUEST).test(e && e.kind && e.kind.ExecutionError)) { - return { codeStatus: validCodeStatusIfNoDeploy, stateStatus: MultisigStateStatus.VALID_STATE }; - } else if (new RegExp(MultisigDeleteRequestRejectionError.METHOD_NOT_FOUND).test(e && e.message)) { - // not reachable if transaction included a deploy - return { codeStatus: MultisigCodeStatus.INVALID_CODE, stateStatus: MultisigStateStatus.UNKNOWN_STATE }; - } - throw e; - } - } - - /** - * Delete a multisig request by its ID. - * @param request_id The ID of the multisig request to be deleted. - * @returns {Promise} A promise that resolves to the final execution outcome of the deletion. - */ - deleteRequest(request_id) { - return super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }); - } - - /** - * Delete all multisig requests associated with the account. - * @returns {Promise} A promise that resolves when all requests are deleted. - */ - async deleteAllRequests() { - const request_ids = await this.getRequestIds(); - if(request_ids.length) { - await Promise.all(request_ids.map((id) => this.deleteRequest(id))); - } - } - - /** - * Delete unconfirmed multisig requests associated with the account. - * @returns {Promise} A promise that resolves when unconfirmed requests are deleted. - */ - async deleteUnconfirmedRequests () { - // TODO: Delete in batch, don't delete unexpired - // TODO: Delete in batch, don't delete unexpired (can reduce gas usage dramatically) - const request_ids = await this.getRequestIds(); - const { requestId } = this.getRequest(); - for (const requestIdToDelete of request_ids) { - if (requestIdToDelete == requestId) { - continue; - } - try { - await super.signAndSendTransaction({ - receiverId: this.accountId, - actions: [functionCall('delete_request', { request_id: requestIdToDelete }, MULTISIG_GAS, MULTISIG_DEPOSIT)] - }); - } catch (e) { - Logger.warn('Attempt to delete an earlier request before 15 minutes failed. Will try again.'); - } - } - } - - // helpers - - async getRequestIds(): Promise { - // TODO: Read requests from state to allow filtering by expiration time - // TODO: https://github.com/near/core-contracts/blob/305d1db4f4f2cf5ce4c1ef3479f7544957381f11/multisig/src/lib.rs#L84 - return this.viewFunction({ - contractId: this.accountId, - methodName: 'list_request_ids', - }); - } - - getRequest() { - if (this.storage) { - return JSON.parse(this.storage.getItem(MULTISIG_STORAGE_KEY) || '{}'); - } - return storageFallback[MULTISIG_STORAGE_KEY]; - } - - setRequest(data) { - if (this.storage) { - return this.storage.setItem(MULTISIG_STORAGE_KEY, JSON.stringify(data)); - } - storageFallback[MULTISIG_STORAGE_KEY] = data; - } -} - -const convertPKForContract = (pk) => pk.toString().replace('ed25519:', ''); - -const convertActions = (actions, accountId, receiverId) => actions.map((a) => { - const type = a.enum; - const { gas, publicKey, methodName, args, deposit, accessKey, code } = a[type]; - const action = { - type: type[0].toUpperCase() + type.substr(1), - gas: (gas && gas.toString()) || undefined, - public_key: (publicKey && convertPKForContract(publicKey)) || undefined, - method_name: methodName, - args: (args && Buffer.from(args).toString('base64')) || undefined, - code: (code && Buffer.from(code).toString('base64')) || undefined, - amount: (deposit && deposit.toString()) || undefined, - deposit: (deposit && deposit.toString()) || '0', - permission: undefined, - }; - if (accessKey) { - if (receiverId === accountId && accessKey.permission.enum !== 'fullAccess') { - action.permission = { - receiver_id: accountId, - allowance: MULTISIG_ALLOWANCE.toString(), - method_names: MULTISIG_CHANGE_METHODS, - }; - } - if (accessKey.permission.enum === 'functionCall') { - const { receiverId: receiver_id, methodNames: method_names, allowance } = accessKey.permission.functionCall; - action.permission = { - receiver_id, - allowance: (allowance && allowance.toString()) || undefined, - method_names - }; - } - } - return action; -}); diff --git a/packages/accounts/src/index.ts b/packages/accounts/src/index.ts index f8fe945e67..5b1b456e34 100644 --- a/packages/accounts/src/index.ts +++ b/packages/accounts/src/index.ts @@ -4,13 +4,11 @@ export { AccountAuthorizedApp, SignAndSendTransactionOptions } from './account'; -export { Account2FA } from './account_2fa'; export { AccountCreator, LocalAccountCreator, UrlAccountCreator, } from './account_creator'; -export { AccountMultisig } from './account_multisig'; export { Connection } from './connection'; export { MULTISIG_STORAGE_KEY, diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index 5476334213..3e35d99952 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -10,7 +10,7 @@ import { createAccount, generateUniqueString, HELLO_WASM_PATH, HELLO_WASM_BALANC import { InMemoryKeyStore } from '@near-js/keystores'; let nearjs; -let workingAccount; +let workingAccount: Account; jest.setTimeout(50000); @@ -24,59 +24,59 @@ afterAll(async () => { }); test('view pre-defined account works and returns correct name', async () => { - const status = await workingAccount.state(); + const status = await workingAccount.getState(); expect(status.code_hash).toEqual('11111111111111111111111111111111'); }); test('create account and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE'; - const { amount } = await workingAccount.state(); + const { amount } = await workingAccount.getState(); const newAmount = BigInt(amount) / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); - const state = await newAccount.state(); + const state = await newAccount.getState(); expect(state.amount).toEqual(newAmount.toString()); }); test('create account with a secp256k1 key and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = 'secp256k1:45KcWwYt6MYRnnWFSxyQVkuu9suAzxoSkUMEnFNBi9kDayTo5YPUaqMWUrf7YHUDNMMj3w75vKuvfAMgfiFXBy28'; - const { amount } = await workingAccount.state(); + const { amount } = await workingAccount.getState(); const newAmount = BigInt(amount) / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); - const state = await newAccount.state(); + const state = await newAccount.getState(); expect(state.amount).toEqual(newAmount.toString()); }); test('Secp256k1 send money', async() => { const sender = await createAccount(nearjs, KeyType.SECP256K1); const receiver = await createAccount(nearjs, KeyType.SECP256K1); - const { amount: receiverAmount } = await receiver.state(); + const { amount: receiverAmount } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); - const state = await receiver.state(); + const state = await receiver.getState(); expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); }); test('send money', async() => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.state(); + const { amount: receiverAmount } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); - const state = await receiver.state(); + const state = await receiver.getState(); expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); }); test('send money through signAndSendTransaction', async() => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.state(); + const { amount: receiverAmount } = await receiver.getState(); await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [actionCreators.transfer(10000n)], }); - const state = await receiver.state(); + const state = await receiver.getState(); expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); }); @@ -86,14 +86,14 @@ test('delete account', async() => { await sender.deleteAccount(receiver.accountId); // @ts-expect-error test input const reloaded = new Account(sender.connection, sender); - await expect(reloaded.state()).rejects.toThrow(); + await expect(reloaded.getState()).rejects.toThrow(); }); test('multiple parallel transactions', async () => { const PARALLEL_NUMBER = 5; // @ts-expect-error test input await Promise.all(new Array(PARALLEL_NUMBER).fill().map(async (_, i) => { - const account = new Account(workingAccount.accountId, workingAccount.provider, workingAccount.signer); + const account = new Account(workingAccount.accountId, workingAccount.provider, workingAccount.getSigner()); // NOTE: Need to have different transactions outside of nonce, or they all succeed by being identical // TODO: Check if randomization of exponential back off helps to do more transactions without exceeding retries await account.sendMoney(account.accountId, BigInt(i)); @@ -134,7 +134,7 @@ describe('errors', () => { }); test('create existing account', async() => { - await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100)) + await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100n)) .rejects.toThrow(/Can't create a new account .+, because it already exists/); }); }); diff --git a/packages/accounts/test/account_multisig.test.ts b/packages/accounts/test/account_multisig.test.ts deleted file mode 100644 index ce27aa1ca1..0000000000 --- a/packages/accounts/test/account_multisig.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { beforeAll, describe, expect, jest, test } from '@jest/globals'; -import { parseNearAmount } from '@near-js/utils'; -import { KeyPair } from '@near-js/crypto'; -import { KeyPairSigner } from '@near-js/signers'; -import { actionCreators } from '@near-js/transactions'; -import * as fs from 'fs'; -import semver from 'semver'; - -import { Account2FA, MULTISIG_DEPOSIT, MULTISIG_GAS, MultisigStateStatus } from '../src'; -import { createAccount, setUpTestConnection } from './test-utils'; - -const { functionCall, transfer } = actionCreators; - -let nearjs; -let startFromVersion; - -jest.setTimeout(50000); - -const getAccount2FA = async (account, keyMapping = ({ public_key: publicKey }) => ({ publicKey, kind: 'phone' })) => { - // modifiers to functions replaces contract helper (CH) - const { accountId } = account; - const keys = await account.getAccessKeys(); - const account2fa: any = new Account2FA(account.getConnection(), accountId, { - // skip this (not using CH) - getCode: () => {}, - sendCode: () => {}, - // auto accept "code" - verifyCode: () => ({ }), // TODO: Is there any content needed in result? - onAddRequestResult: async () => { - const { requestId } = account2fa.getRequest(); - // 2nd confirmation signing with confirmKey from Account instance - await account.signAndSendTransaction({ - receiverId: accountId, - actions: [ - functionCall('confirm', { request_id: requestId }, MULTISIG_GAS, MULTISIG_DEPOSIT) - ] - }, { signer: new KeyPairSigner(account2fa.confirmKey) }); - } - }); - account2fa.confirmKey = KeyPair.fromRandom('ed25519'); - account2fa.postSignedJson = () => ({ publicKey: account2fa.confirmKey.getPublicKey() }); - account2fa.getRecoveryMethods = () => ({ - data: keys.map(keyMapping) - }); - account2fa.checkMultisigCodeAndStateStatus = () => ({ codeStatus: 1, stateStatus: MultisigStateStatus.STATE_NOT_INITIALIZED }); - await account2fa.deployMultisig(fs.readFileSync('./test/wasm/multisig.wasm')); - return account2fa; -}; - -beforeAll(async () => { - nearjs = await setUpTestConnection(); - const nodeStatus = await nearjs.connection.provider.status(); - startFromVersion = (version) => semver.gte(nodeStatus.version.version, version); - console.log(startFromVersion); -}); - -describe('deployMultisig key rotations', () => { - - test('full access key if recovery method is "ledger" or "phrase", limited access key if "phone"', async () => { - const account = await createAccount(nearjs); - await account.addKey(KeyPair.fromRandom('ed25519').getPublicKey()); - await account.addKey(KeyPair.fromRandom('ed25519').getPublicKey()); - const keys = await account.getAccessKeys(); - const kinds = ['ledger', 'phrase', 'phone']; - const account2fa = await getAccount2FA( - account, - // @ts-expect-error test input - ({ public_key: publicKey }, i) => ({ publicKey, kind: kinds[i] }) - ); - const currentKeys = await account2fa.getAccessKeys(); - expect(currentKeys.find(({ public_key }) => keys[0].public_key === public_key).access_key.permission).toEqual('FullAccess'); - expect(currentKeys.find(({ public_key }) => keys[1].public_key === public_key).access_key.permission).toEqual('FullAccess'); - expect(currentKeys.find(({ public_key }) => keys[2].public_key === public_key).access_key.permission).not.toEqual('FullAccess'); - }); - -}); - -describe('account2fa transactions', () => { - - test('add app key before deployMultisig', async() => { - let account = await createAccount(nearjs); - const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey(); - const appAccountId = 'foobar'; - const appMethodNames = ['some_app_stuff','some_more_app_stuff']; - await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, BigInt(parseNearAmount('0.25'))); - account = await getAccount2FA(account); - const keys = await account.getAccessKeys(); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); - }); - - test('add app key', async() => { - let account = await createAccount(nearjs); - account = await getAccount2FA(account); - const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey(); - const appAccountId = 'foobar'; - const appMethodNames = ['some_app_stuff', 'some_more_app_stuff']; - await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, BigInt(parseNearAmount('0.25'))); - const keys = await account.getAccessKeys(); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.method_names).toEqual(appMethodNames); - expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key) - // @ts-expect-error test input - .access_key.permission.FunctionCall.receiver_id).toEqual(appAccountId); - }); - - test('send money', async() => { - let sender = await createAccount(nearjs); - let receiver = await createAccount(nearjs); - sender = await getAccount2FA(sender); - receiver = await getAccount2FA(receiver); - const { amount: receiverAmount } = await receiver.state(); - await sender.sendMoney(receiver.accountId, BigInt(parseNearAmount('1'))); - const state = await receiver.state(); - expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount)+ BigInt(parseNearAmount('0.9').toString())); - }); - - test('send money through signAndSendTransaction', async() => { - let sender = await createAccount(nearjs); - let receiver = await createAccount(nearjs); - sender = await getAccount2FA(sender); - receiver = await getAccount2FA(receiver); - const { amount: receiverAmount } = await receiver.state(); - await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [transfer(BigInt(parseNearAmount('1')))] }); - const state = await receiver.state(); - expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount) + BigInt(parseNearAmount('0.9').toString())); - }); - -}); diff --git a/packages/client/src/transactions/composers/signed_transaction_composer.ts b/packages/client/src/transactions/composers/signed_transaction_composer.ts index 79bcd42e27..173342092a 100644 --- a/packages/client/src/transactions/composers/signed_transaction_composer.ts +++ b/packages/client/src/transactions/composers/signed_transaction_composer.ts @@ -68,7 +68,7 @@ export class SignedTransactionComposer extends TransactionComposer { this.verifySigner(transaction.signerId); return signTransaction({ transaction, - deps: { signer: this.account.signer }, + deps: { signer: this.account.getSigner() }, }); } @@ -79,7 +79,7 @@ export class SignedTransactionComposer extends TransactionComposer { async signAndSend(blockReference: BlockReference = { finality: 'final' }) { this.verifySigner(this.sender); const { signedTransaction } = await this.toSignedTransaction({ - publicKey: this.publicKey || await this.account.signer.getPublicKey(), + publicKey: this.publicKey || await this.account.getSigner().getPublicKey(), blockHash: this.blockHash || (await this.account.provider.viewBlock(blockReference))?.header?.hash, }); diff --git a/packages/keystores/src/keystore.ts b/packages/keystores/src/keystore.ts index ad0f88d0e3..4ed7210e87 100644 --- a/packages/keystores/src/keystore.ts +++ b/packages/keystores/src/keystore.ts @@ -1,8 +1,7 @@ import { KeyPair } from '@near-js/crypto'; /** - * KeyStores are passed to {@link "@near-js/wallet-account".near.Near | Near} via {@link "@near-js/wallet-account".near.NearConfig | NearConfig} - * and are used by the {@link "@near-js/signers".in_memory_signer.InMemorySigner | InMemorySigner} to sign transactions. + * KeyStores are used by {@link "@near-js/signers".Signer} to sign transactions. * */ export abstract class KeyStore { diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index 855b86e48e..f0bd05a8b8 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -21,7 +21,6 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", - "@near-js/wallet-account": "workspace:*", "@noble/curves": "1.8.1", "borsh": "1.0.0", "depd": "2.0.0", @@ -51,8 +50,7 @@ "@near-js/signers": "^2.0.0", "@near-js/transactions": "^2.0.0", "@near-js/types": "^2.0.0", - "@near-js/utils": "^2.0.0", - "@near-js/wallet-account": "^2.0.0" + "@near-js/utils": "^2.0.0" }, "keywords": [], "license": "(MIT AND Apache-2.0)", diff --git a/packages/near-api-js/src/account_multisig.ts b/packages/near-api-js/src/account_multisig.ts deleted file mode 100644 index 8671cf5956..0000000000 --- a/packages/near-api-js/src/account_multisig.ts +++ /dev/null @@ -1,12 +0,0 @@ -export { - Account2FA, - AccountMultisig, - MULTISIG_STORAGE_KEY, - MULTISIG_ALLOWANCE, - MULTISIG_GAS, - MULTISIG_DEPOSIT, - MULTISIG_CHANGE_METHODS, - MULTISIG_CONFIRM_METHODS, - MultisigDeleteRequestRejectionError, - MultisigStateStatus, -} from '@near-js/accounts'; diff --git a/packages/near-api-js/src/common-index.ts b/packages/near-api-js/src/common-index.ts index fb4253f186..4413ac0176 100644 --- a/packages/near-api-js/src/common-index.ts +++ b/packages/near-api-js/src/common-index.ts @@ -5,7 +5,6 @@ import * as transactions from './transaction'; import * as validators from './validators'; import { Account } from './account'; -import * as multisig from './account_multisig'; import * as accountCreator from './account_creator'; import { Connection } from './connection'; import { Signer, KeyPairSigner } from './signer'; @@ -13,11 +12,6 @@ import { Contract } from './contract'; import { KeyPair } from './utils/key_pair'; import { Near } from './near'; -import { - ConnectedWalletAccount, - WalletConnection -} from './wallet-account'; - export { accountCreator, providers, @@ -25,7 +19,6 @@ export { transactions, validators, - multisig, Account, Connection, Contract, @@ -34,7 +27,4 @@ export { KeyPair, Near, - - ConnectedWalletAccount, - WalletConnection }; diff --git a/packages/near-api-js/src/near.ts b/packages/near-api-js/src/near.ts index c75aa3c7de..4b542169f3 100644 --- a/packages/near-api-js/src/near.ts +++ b/packages/near-api-js/src/near.ts @@ -1 +1,144 @@ -export { Near, NearConfig } from '@near-js/wallet-account'; +/** + * This module contains the main class developers will use to interact with NEAR. + * The {@link Near} class is used to interact with {@link "@near-js/accounts".account.Account | Account} through the {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider}. + * It is configured via the {@link NearConfig}. + * + * @see [https://docs.near.org/tools/near-api-js/quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) + * + * @module near + */ +import { + Account, + AccountCreator, + Connection, + LocalAccountCreator, + UrlAccountCreator, +} from '@near-js/accounts'; +import { PublicKey } from '@near-js/crypto'; +import { KeyStore } from '@near-js/keystores'; +import { Signer } from '@near-js/signers'; +import { LoggerService } from '@near-js/utils'; +import { Provider } from '@near-js/providers'; + +export interface NearConfig { + /** Holds {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} for signing transactions */ + keyStore?: KeyStore; + + /** @hidden */ + signer?: Signer; + + /** + * [NEAR Contract Helper](https://github.com/near/near-contract-helper) url used to create accounts if no master account is provided + * @see {@link UrlAccountCreator} + */ + helperUrl?: string; + + /** + * The balance transferred from the {@link NearConfig#masterAccount} to a created account + * @see {@link LocalAccountCreator} + */ + initialBalance?: string; + + /** + * The account to use when creating new accounts + * @see {@link LocalAccountCreator} + */ + masterAccount?: string; + + /** + * {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} are stored in a {@link KeyStore} under the `networkId` namespace. + */ + networkId: string; + + /** + * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} + */ + nodeUrl: string; + + /** + * NEAR RPC API headers. Can be used to pass API KEY and other parameters. + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} + */ + headers?: { [key: string]: string | number }; + + /** + * NEAR wallet url used to redirect users to their wallet in browser applications. + * @see [https://wallet.near.org/](https://wallet.near.org/) + */ + walletUrl?: string; + + /** + * Backward-compatibility for older versions + */ + deps?: { keyStore: KeyStore }; + /** + * Specifies the logger to use. Pass `false` to turn off logging. + */ + logger?: LoggerService | false; + /** + * Specifies NEAR RPC API connections, and is used to make JSON RPC calls to interact with NEAR + * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} + * @see {@link "@near-js/providers".json-rpc-provider.FailoverRpcProvider | FailoverRpcProvider} + * @see [List of available and public JSON RPC endpoints](https://docs.near.org/api/rpc/providers) + */ + provider?: Provider; +} + +/** + * This is the main class developers should use to interact with NEAR. + * @example + * ```js + * const near = new Near(config); + * ``` + */ +export class Near { + readonly config: any; + readonly connection: Connection; + readonly accountCreator: AccountCreator; + + constructor(config: NearConfig) { + this.config = config; + this.connection = Connection.fromConfig({ + networkId: config.networkId, + provider: config.provider || { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, + signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore || config.deps?.keyStore }, + }); + + if (config.masterAccount) { + // TODO: figure out better way of specifiying initial balance. + // Hardcoded number below must be enough to pay the gas cost to dev-deploy with near-shell for multiple times + const initialBalance = config.initialBalance ? BigInt(config.initialBalance) : 500000000000000000000000000n; + this.accountCreator = new LocalAccountCreator(new Account(config.masterAccount, this.connection.provider, this.connection.signer), initialBalance); + } else if (config.helperUrl) { + this.accountCreator = new UrlAccountCreator(this.connection, config.helperUrl); + } else { + this.accountCreator = null; + } + } + + /** + * @param accountId near accountId used to interact with the network. + */ + async account(accountId: string): Promise { + const account = new Account(accountId, this.connection.provider, this.connection.signer); + return account; + } + + /** + * Create an account using the {@link AccountCreator}. Either: + * * using a masterAccount with {@link LocalAccountCreator} + * * using the helperUrl with {@link UrlAccountCreator} + * @see {@link NearConfig#masterAccount} and {@link NearConfig#helperUrl} + * + * @param accountId + * @param publicKey + */ + async createAccount(accountId: string, publicKey: PublicKey): Promise { + if (!this.accountCreator) { + throw new Error('Must specify account creator, either via masterAccount or helperUrl configuration settings.'); + } + await this.accountCreator.createAccount(accountId, publicKey); + return new Account(accountId, this.connection.provider, this.connection.signer); + } +} diff --git a/packages/near-api-js/src/wallet-account.ts b/packages/near-api-js/src/wallet-account.ts deleted file mode 100644 index 172cdfa31f..0000000000 --- a/packages/near-api-js/src/wallet-account.ts +++ /dev/null @@ -1 +0,0 @@ -export { ConnectedWalletAccount, WalletConnection } from '@near-js/wallet-account'; diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index c807fee35e..0e30b2567b 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -139,8 +139,8 @@ export class FailoverRpcProvider implements Provider { return this.withBackoff((currentProvider) => currentProvider.viewContractState(accountId, prefix, blockQuery)); } - public async callContractViewFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { - return this.withBackoff((currentProvider) => currentProvider.callContractViewFunction(accountId, method, args, blockQuery)); + public async callFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.callFunction(accountId, method, args, blockQuery)); } public async viewBlock(blockQuery: BlockReference): Promise { diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index becd55ecde..d96bf2a7c7 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -198,7 +198,7 @@ export class JsonRpcProvider implements Provider { }); } - public async callContractViewFunction( + public async callFunction( contractId: string, method: string, args: Record, diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 9e8f57d5ab..371140c4b9 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -49,7 +49,7 @@ export interface Provider { viewAccount(accountId: string, blockQuery?: BlockReference): Promise; viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; - callContractViewFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; viewBlock(blockQuery: BlockReference): Promise; viewChunk(chunkId: ChunkId): Promise; diff --git a/packages/wallet-account/CHANGELOG.md b/packages/wallet-account/CHANGELOG.md deleted file mode 100644 index 013c5b3c37..0000000000 --- a/packages/wallet-account/CHANGELOG.md +++ /dev/null @@ -1,270 +0,0 @@ -# @near-js/wallet-account - -## 1.3.3 - -### Patch Changes - -- Updated dependencies [[`e408cfc`](https://github.com/near/near-api-js/commit/e408cfc4b4be4ea82e1e34314d9ee92885d03253), [`9cb5f56`](https://github.com/near/near-api-js/commit/9cb5f5621364c370fb2321f6a22dbee146f0f368)]: - - @near-js/providers@1.0.3 - - @near-js/transactions@1.3.3 - - @near-js/accounts@1.4.1 - -## 1.3.2 - -### Patch Changes - -- Updated dependencies [[`ef530cf6`](https://github.com/near/near-api-js/commit/ef530cf62b0ed0d7eb2a77482c4d908449a863c2), [`c85d12d3`](https://github.com/near/near-api-js/commit/c85d12d36b1d8cd9d02178506dcf9cf0598fe7a8)]: - - @near-js/accounts@1.4.0 - - @near-js/providers@1.0.2 - - @near-js/utils@1.1.0 - - @near-js/crypto@1.4.2 - - @near-js/transactions@1.3.2 - - @near-js/keystores@0.2.2 - - @near-js/signers@0.2.2 - -## 1.3.1 - -### Patch Changes - -- Updated dependencies [[`5b0bbbc1`](https://github.com/near/near-api-js/commit/5b0bbbc17ffe7d89d7767e405d2ca700dc2bba40)]: - - @near-js/crypto@1.4.1 - - @near-js/providers@1.0.1 - - @near-js/types@0.3.1 - - @near-js/utils@1.0.1 - - @near-js/accounts@1.3.1 - - @near-js/keystores@0.2.1 - - @near-js/signers@0.2.1 - - @near-js/transactions@1.3.1 - -## 1.3.0 - -### Minor Changes - -- [#1353](https://github.com/near/near-api-js/pull/1353) [`73690557`](https://github.com/near/near-api-js/commit/73690557c8e2a74386fca62f4ae123abe0651403) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Update to Node.js 20 LTS & pnpm 9.4, modularize packages, simplify dependencies, and update tests - - **Breaking Changes** - - - `near-api-js@5.0.0` - - - The following functions are no longer exported: - - `logWarning` - - `fetchJson` - - the unnamed wrapped `fetch` function exported from `setup-node-fetch.ts` - - The browser bundle is no longer being built in version 5; for browser support please use modules - - - `@near-js/providers@1.0.0` - - - The following functions are no longer exported: - - `fetchJson` - - - `@near-js/utils@1.0.0` - - The following functions are no longer exported: - - `logWarning` - -### Patch Changes - -- Updated dependencies [[`73690557`](https://github.com/near/near-api-js/commit/73690557c8e2a74386fca62f4ae123abe0651403)]: - - @near-js/accounts@1.3.0 - - @near-js/crypto@1.4.0 - - @near-js/keystores@0.2.0 - - @near-js/providers@1.0.0 - - @near-js/signers@0.2.0 - - @near-js/transactions@1.3.0 - - @near-js/types@0.3.0 - - @near-js/utils@1.0.0 - -## 1.2.3 - -### Patch Changes - -- [#1355](https://github.com/near/near-api-js/pull/1355) [`7d5a8244`](https://github.com/near/near-api-js/commit/7d5a8244a1683d7b5e82c4da1e40d834167a9a41) Thanks [@gtsonevv](https://github.com/gtsonevv)! - Add Secp256k1 support - -- Updated dependencies [[`9c7db11c`](https://github.com/near/near-api-js/commit/9c7db11c3c5c031a749dd72d5140f58056570e36), [`bad95007`](https://github.com/near/near-api-js/commit/bad95007edde4ed9d5989ded7f2032b9f15f5c23), [`7d5a8244`](https://github.com/near/near-api-js/commit/7d5a8244a1683d7b5e82c4da1e40d834167a9a41)]: - - @near-js/keystores@0.1.0 - - @near-js/utils@0.3.0 - - @near-js/crypto@1.3.0 - - @near-js/accounts@1.2.2 - - @near-js/signers@0.1.5 - - @near-js/transactions@1.2.3 - - @near-js/providers@0.2.3 - -## 1.2.2 - -### Patch Changes - -- Updated dependencies [[`ecdf1741`](https://github.com/near/near-api-js/commit/ecdf1741fb692e71202c541c5b3692790baa65f0), [`92a6f5be`](https://github.com/near/near-api-js/commit/92a6f5be3f4b7be6f3e9b55077025921c3aad2cb)]: - - @near-js/providers@0.2.2 - - @near-js/types@0.2.1 - - @near-js/accounts@1.2.1 - - @near-js/crypto@1.2.4 - - @near-js/keystores@0.0.12 - - @near-js/transactions@1.2.2 - - @near-js/utils@0.2.2 - - @near-js/signers@0.1.4 - -## 1.2.1 - -### Patch Changes - -- Updated dependencies [[`06baa81d`](https://github.com/near/near-api-js/commit/06baa81dc604cfe0463476de7a4dcdd39a6f716a)]: - - @near-js/accounts@1.2.0 - - @near-js/types@0.2.0 - - @near-js/crypto@1.2.3 - - @near-js/keystores@0.0.11 - - @near-js/providers@0.2.1 - - @near-js/transactions@1.2.1 - - @near-js/utils@0.2.1 - - @near-js/signers@0.1.3 - -## 1.2.0 - -### Minor Changes - -- [#1334](https://github.com/near/near-api-js/pull/1334) [`3f363113`](https://github.com/near/near-api-js/commit/3f363113e102d0acf29b7b2635acf6160a028cc3) Thanks [@denbite](https://github.com/denbite)! - Introduce FailoverRpcProvider that switches between providers in case of a failure of one of them - -### Patch Changes - -- [#1223](https://github.com/near/near-api-js/pull/1223) [`9060b781`](https://github.com/near/near-api-js/commit/9060b7811668d71bdf21170273a42842c3691f9b) Thanks [@gtsonevv](https://github.com/gtsonevv)! - Replace bn.js by BigInt. - -- Updated dependencies [[`9060b781`](https://github.com/near/near-api-js/commit/9060b7811668d71bdf21170273a42842c3691f9b), [`cc401a6c`](https://github.com/near/near-api-js/commit/cc401a6c893398e2185c35765ca316f68ac86074), [`3f363113`](https://github.com/near/near-api-js/commit/3f363113e102d0acf29b7b2635acf6160a028cc3), [`f739324b`](https://github.com/near/near-api-js/commit/f739324b2959712281d957eb26a09e5d68e32c80)]: - - @near-js/accounts@1.1.0 - - @near-js/transactions@1.2.0 - - @near-js/types@0.1.0 - - @near-js/utils@0.2.0 - - @near-js/crypto@1.2.2 - - @near-js/providers@0.2.0 - - @near-js/keystores@0.0.10 - - @near-js/signers@0.1.2 - -## 1.1.1 - -### Patch Changes - -- Updated dependencies [[`42dc7e2a`](https://github.com/near/near-api-js/commit/42dc7e2ac794e973987bed7b89da5ef2d3c6c8ac)]: - - @near-js/transactions@1.1.2 - - @near-js/accounts@1.0.4 - -## 1.1.0 - -### Minor Changes - -- [#1260](https://github.com/near/near-api-js/pull/1260) [`15885dd1`](https://github.com/near/near-api-js/commit/15885dd10ba9b562043a36dc80c449b7c3588313) Thanks [@denbite](https://github.com/denbite)! - Introduce methods to construct URLs to the wallet without instant redirect - -### Patch Changes - -- Updated dependencies [[`662cc13d`](https://github.com/near/near-api-js/commit/662cc13d7961c3bdabed3ad51b1c57958739a3e6), [`c4655576`](https://github.com/near/near-api-js/commit/c4655576bacb1d8b85030dca5b9443649621c8ee)]: - - @near-js/utils@0.1.0 - - @near-js/crypto@1.2.1 - - @near-js/accounts@1.0.3 - - @near-js/transactions@1.1.1 - - @near-js/keystores@0.0.9 - - @near-js/signers@0.1.1 - -## 1.0.2 - -### Patch Changes - -- Updated dependencies [[`1900c490`](https://github.com/near/near-api-js/commit/1900c49060c3ea8279448cead7347049a23f421f), [`c6cdc8c7`](https://github.com/near/near-api-js/commit/c6cdc8c724a6dd53114cc5f53fd58e57cea86b78)]: - - @near-js/crypto@1.2.0 - - @near-js/signers@0.1.0 - - @near-js/transactions@1.1.0 - - @near-js/accounts@1.0.2 - - @near-js/keystores@0.0.8 - -## 1.0.1 - -### Patch Changes - -- Updated dependencies [[`aeeb1502`](https://github.com/near/near-api-js/commit/aeeb15022a1c1deb99114eba0473739b0998fc50)]: - - @near-js/crypto@1.1.0 - - @near-js/accounts@1.0.1 - - @near-js/keystores@0.0.7 - - @near-js/signers@0.0.7 - - @near-js/transactions@1.0.1 - -## 1.0.0 - -### Major Changes - -- [#1168](https://github.com/near/near-api-js/pull/1168) [`61349aec`](https://github.com/near/near-api-js/commit/61349aeca3af830f702b24654e0f13cd428192d8) Thanks [@gagdiez](https://github.com/gagdiez)! - feat: updated borsh-js to v1.0.1 - -### Patch Changes - -- [#1215](https://github.com/near/near-api-js/pull/1215) [`ecf29e2d`](https://github.com/near/near-api-js/commit/ecf29e2d56611a7773c79d5bb5bd20c8b7e738ea) Thanks [@denbite](https://github.com/denbite)! - Internal logging library with capabilities for integration with modern logging libraries like Pino, Winston, etc - -- Updated dependencies [[`0f764ee0`](https://github.com/near/near-api-js/commit/0f764ee03b5747fbf8a971c7b04ef8326238a1d0), [`695220e7`](https://github.com/near/near-api-js/commit/695220e75bc43834a7700cfc5491a7eebd324947), [`ecf29e2d`](https://github.com/near/near-api-js/commit/ecf29e2d56611a7773c79d5bb5bd20c8b7e738ea), [`61349aec`](https://github.com/near/near-api-js/commit/61349aeca3af830f702b24654e0f13cd428192d8), [`cdd8d1c8`](https://github.com/near/near-api-js/commit/cdd8d1c8c37db641bd995b2c470ad0b4fdddb93f)]: - - @near-js/accounts@1.0.0 - - @near-js/utils@0.0.5 - - @near-js/crypto@1.0.0 - - @near-js/transactions@1.0.0 - - @near-js/keystores@0.0.6 - - @near-js/signers@0.0.6 - -## 0.0.7 - -### Patch Changes - -- Updated dependencies [[`40fa6465`](https://github.com/near/near-api-js/commit/40fa64654fdaf3b463122c35521a6f72282974f2)]: - - @near-js/crypto@0.0.5 - - @near-js/accounts@0.1.4 - - @near-js/keystores@0.0.5 - - @near-js/signers@0.0.5 - - @near-js/transactions@0.2.1 - -## 0.0.6 - -### Patch Changes - -- Updated dependencies [[`e21ff896`](https://github.com/near/near-api-js/commit/e21ff89601c858fb703169e3bb53c6d96cff5342), [`00b4d2ba`](https://github.com/near/near-api-js/commit/00b4d2ba3f9f3a1f90343e34cb9bde8cdb607ceb)]: - - @near-js/transactions@0.2.0 - - @near-js/accounts@0.1.3 - -## 0.0.5 - -### Patch Changes - -- [#1124](https://github.com/near/near-api-js/pull/1124) [`c1dcf3b8`](https://github.com/near/near-api-js/commit/c1dcf3b8207e7de358e1d711d55da938d5d9ff8d) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Allow use of legacy `deps.keyStore` path in NearConfig - -- Updated dependencies [[`bf81ddc1`](https://github.com/near/near-api-js/commit/bf81ddc11c958dece2244798bdfa6ab11e653940)]: - - @near-js/types@0.0.4 - - @near-js/accounts@0.1.2 - - @near-js/crypto@0.0.4 - - @near-js/keystores@0.0.4 - - @near-js/transactions@0.1.1 - - @near-js/utils@0.0.4 - - @near-js/signers@0.0.4 - -## 0.0.4 - -### Patch Changes - -- Updated dependencies []: - - @near-js/accounts@0.1.1 - -## 0.0.3 - -### Patch Changes - -- Updated dependencies [[`b713ae78`](https://github.com/near/near-api-js/commit/b713ae78969d530e7e69e21e315e3d3fdb5e68e9), [`bc53c32c`](https://github.com/near/near-api-js/commit/bc53c32c80694e6f22d9be97db44603591f0026b), [`b7b6c6a1`](https://github.com/near/near-api-js/commit/b7b6c6a1448050f60f6498f799654204f1998b91), [`d97d2a6e`](https://github.com/near/near-api-js/commit/d97d2a6e891350cdea418da2af2b2971bdf0467e), [`8c6bf391`](https://github.com/near/near-api-js/commit/8c6bf391a01af9adb81cb8731c45bdb17f5291c0)]: - - @near-js/types@0.0.3 - - @near-js/accounts@0.1.0 - - @near-js/transactions@0.1.0 - - @near-js/crypto@0.0.3 - - @near-js/keystores@0.0.3 - - @near-js/utils@0.0.3 - - @near-js/signers@0.0.3 - -## 0.0.2 - -### Patch Changes - -- [#1091](https://github.com/near/near-api-js/pull/1091) [`ca458cac`](https://github.com/near/near-api-js/commit/ca458cac683fab614b77eb5daa160e03b0640350) Thanks [@andy-haynes](https://github.com/andy-haynes)! - Only include contents of lib/ in NPM packages - -- Updated dependencies [[`ca458cac`](https://github.com/near/near-api-js/commit/ca458cac683fab614b77eb5daa160e03b0640350)]: - - @near-js/accounts@0.0.2 - - @near-js/crypto@0.0.2 - - @near-js/keystores@0.0.2 - - @near-js/signers@0.0.2 - - @near-js/transactions@0.0.2 - - @near-js/types@0.0.2 - - @near-js/utils@0.0.2 diff --git a/packages/wallet-account/README.md b/packages/wallet-account/README.md deleted file mode 100644 index ad4a9f6635..0000000000 --- a/packages/wallet-account/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# @near-js/wallet-account - -A collection of classes and types for working with accounts within browser-based wallets. - -## Modules - -- [Near](https://github.com/near/near-api-js/blob/master/packages/wallet-account/src/near.ts) a general purpose class for configuring and working with the NEAR blockchain -- [WalletAccount](https://github.com/near/near-api-js/blob/master/packages/wallet-account/src/wallet_account.ts) an [Account](https://github.com/near/near-api-js/blob/master/packages/accounts/src/account.ts) implementation for use in a browser-based wallet - -# License - -This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). -See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/wallet-account/jest.config.ts b/packages/wallet-account/jest.config.ts deleted file mode 100644 index 3b66313239..0000000000 --- a/packages/wallet-account/jest.config.ts +++ /dev/null @@ -1,13 +0,0 @@ -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/wallet-account/package.json b/packages/wallet-account/package.json deleted file mode 100644 index 038c5da39d..0000000000 --- a/packages/wallet-account/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@near-js/wallet-account", - "version": "1.3.3", - "description": "Dependencies for the NEAR API JavaScript client in the browser", - "main": "lib/esm/index.js", - "type": "module", - "scripts": { - "build": "pnpm compile:esm && pnpm compile:cjs", - "compile:esm": "tsc -p tsconfig.json", - "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", - "test": "jest" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@near-js/accounts": "workspace:*", - "@near-js/crypto": "workspace:*", - "@near-js/keystores": "workspace:*", - "@near-js/providers": "workspace:*", - "@near-js/signers": "workspace:*", - "@near-js/transactions": "workspace:*", - "@near-js/types": "workspace:*", - "@near-js/utils": "workspace:*", - "borsh": "1.0.0" - }, - "devDependencies": { - "@jest/globals": "^29.7.0", - "@types/node": "20.0.0", - "build": "workspace:*", - "jest": "29.7.0", - "localstorage-memory": "1.0.3", - "ts-jest": "29.2.6", - "tsconfig": "workspace:*", - "typescript": "5.4.5" - }, - "files": [ - "lib" - ], - "exports": { - "require": "./lib/commonjs/index.cjs", - "import": "./lib/esm/index.js" - } -} diff --git a/packages/wallet-account/src/index.ts b/packages/wallet-account/src/index.ts deleted file mode 100644 index ea0f840b63..0000000000 --- a/packages/wallet-account/src/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { Near, NearConfig } from './near'; -export { ConnectedWalletAccount, WalletConnection } from './wallet_account'; \ No newline at end of file diff --git a/packages/wallet-account/src/near.ts b/packages/wallet-account/src/near.ts deleted file mode 100644 index 4b542169f3..0000000000 --- a/packages/wallet-account/src/near.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * This module contains the main class developers will use to interact with NEAR. - * The {@link Near} class is used to interact with {@link "@near-js/accounts".account.Account | Account} through the {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider}. - * It is configured via the {@link NearConfig}. - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#account](https://docs.near.org/tools/near-api-js/quick-reference#account) - * - * @module near - */ -import { - Account, - AccountCreator, - Connection, - LocalAccountCreator, - UrlAccountCreator, -} from '@near-js/accounts'; -import { PublicKey } from '@near-js/crypto'; -import { KeyStore } from '@near-js/keystores'; -import { Signer } from '@near-js/signers'; -import { LoggerService } from '@near-js/utils'; -import { Provider } from '@near-js/providers'; - -export interface NearConfig { - /** Holds {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} for signing transactions */ - keyStore?: KeyStore; - - /** @hidden */ - signer?: Signer; - - /** - * [NEAR Contract Helper](https://github.com/near/near-contract-helper) url used to create accounts if no master account is provided - * @see {@link UrlAccountCreator} - */ - helperUrl?: string; - - /** - * The balance transferred from the {@link NearConfig#masterAccount} to a created account - * @see {@link LocalAccountCreator} - */ - initialBalance?: string; - - /** - * The account to use when creating new accounts - * @see {@link LocalAccountCreator} - */ - masterAccount?: string; - - /** - * {@link "@near-js/crypto".key_pair.KeyPair | KeyPair} are stored in a {@link KeyStore} under the `networkId` namespace. - */ - networkId: string; - - /** - * NEAR RPC API url. used to make JSON RPC calls to interact with NEAR. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} - */ - nodeUrl: string; - - /** - * NEAR RPC API headers. Can be used to pass API KEY and other parameters. - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} - */ - headers?: { [key: string]: string | number }; - - /** - * NEAR wallet url used to redirect users to their wallet in browser applications. - * @see [https://wallet.near.org/](https://wallet.near.org/) - */ - walletUrl?: string; - - /** - * Backward-compatibility for older versions - */ - deps?: { keyStore: KeyStore }; - /** - * Specifies the logger to use. Pass `false` to turn off logging. - */ - logger?: LoggerService | false; - /** - * Specifies NEAR RPC API connections, and is used to make JSON RPC calls to interact with NEAR - * @see {@link "@near-js/providers".json-rpc-provider.JsonRpcProvider | JsonRpcProvider} - * @see {@link "@near-js/providers".json-rpc-provider.FailoverRpcProvider | FailoverRpcProvider} - * @see [List of available and public JSON RPC endpoints](https://docs.near.org/api/rpc/providers) - */ - provider?: Provider; -} - -/** - * This is the main class developers should use to interact with NEAR. - * @example - * ```js - * const near = new Near(config); - * ``` - */ -export class Near { - readonly config: any; - readonly connection: Connection; - readonly accountCreator: AccountCreator; - - constructor(config: NearConfig) { - this.config = config; - this.connection = Connection.fromConfig({ - networkId: config.networkId, - provider: config.provider || { type: 'JsonRpcProvider', args: { url: config.nodeUrl, headers: config.headers } }, - signer: config.signer || { type: 'InMemorySigner', keyStore: config.keyStore || config.deps?.keyStore }, - }); - - if (config.masterAccount) { - // TODO: figure out better way of specifiying initial balance. - // Hardcoded number below must be enough to pay the gas cost to dev-deploy with near-shell for multiple times - const initialBalance = config.initialBalance ? BigInt(config.initialBalance) : 500000000000000000000000000n; - this.accountCreator = new LocalAccountCreator(new Account(config.masterAccount, this.connection.provider, this.connection.signer), initialBalance); - } else if (config.helperUrl) { - this.accountCreator = new UrlAccountCreator(this.connection, config.helperUrl); - } else { - this.accountCreator = null; - } - } - - /** - * @param accountId near accountId used to interact with the network. - */ - async account(accountId: string): Promise { - const account = new Account(accountId, this.connection.provider, this.connection.signer); - return account; - } - - /** - * Create an account using the {@link AccountCreator}. Either: - * * using a masterAccount with {@link LocalAccountCreator} - * * using the helperUrl with {@link UrlAccountCreator} - * @see {@link NearConfig#masterAccount} and {@link NearConfig#helperUrl} - * - * @param accountId - * @param publicKey - */ - async createAccount(accountId: string, publicKey: PublicKey): Promise { - if (!this.accountCreator) { - throw new Error('Must specify account creator, either via masterAccount or helperUrl configuration settings.'); - } - await this.accountCreator.createAccount(accountId, publicKey); - return new Account(accountId, this.connection.provider, this.connection.signer); - } -} diff --git a/packages/wallet-account/src/wallet_account.ts b/packages/wallet-account/src/wallet_account.ts deleted file mode 100644 index c1c5a97325..0000000000 --- a/packages/wallet-account/src/wallet_account.ts +++ /dev/null @@ -1,453 +0,0 @@ -/** - * This module exposes two classes: - * * {@link WalletConnection} which redirects users to [NEAR Wallet](https://wallet.near.org/) for key management. - * * {@link ConnectedWalletAccount} is an {@link "@near-js/accounts".account.Account | Account} implementation that uses {@link WalletConnection} to get keys - * - * @module walletAccount - */ -import { - Account, - Connection, - SignAndSendTransactionOptions, -} from '@near-js/accounts'; -import { KeyPair, PublicKey } from '@near-js/crypto'; -import { InMemoryKeyStore, KeyStore } from '@near-js/keystores'; -import { FinalExecutionOutcome } from '@near-js/types'; -import { baseDecode } from '@near-js/utils'; -import { Transaction, Action, SCHEMA, createTransaction } from '@near-js/transactions'; -import { serialize } from 'borsh'; - -import { Near, NearConfig } from './near'; - -const LOGIN_WALLET_URL_SUFFIX = '/login/'; -const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; -const LOCAL_STORAGE_KEY_SUFFIX = '_wallet_auth_key'; -const PENDING_ACCESS_KEY_PREFIX = 'pending_key'; // browser storage key for a pending access key (i.e. key has been generated but we are not sure it was added yet) - -interface SignInOptions { - contractId?: string; - methodNames?: string[]; - // TODO: Replace following with single callbackUrl - successUrl?: string; - failureUrl?: string; - keyType: 'ed25519' | 'secp256k1' -} - -/** - * Information to send NEAR wallet for signing transactions and redirecting the browser back to the calling application - */ -interface RequestSignTransactionsOptions { - /** list of transactions to sign */ - transactions: Transaction[]; - /** url NEAR Wallet will redirect to after transaction signing is complete */ - callbackUrl?: string; - /** meta information NEAR Wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param */ - meta?: string; -} - -/** - * This class is not intended for use outside the browser. Without `window` (i.e. in server contexts), it will instantiate but will throw a clear error when used. - * - * @see [https://docs.near.org/tools/near-api-js/quick-reference#wallet](https://docs.near.org/tools/near-api-js/quick-reference#wallet) - * @example - * ```js - * // create new WalletConnection instance - * const wallet = new WalletConnection(near, 'my-app'); - * - * // If not signed in redirect to the NEAR wallet to sign in - * // keys will be stored in the BrowserLocalStorageKeyStore - * if(!wallet.isSignedIn()) return wallet.requestSignIn() - * ``` - */ -export class WalletConnection { - /** @hidden */ - _walletBaseUrl: string; - - /** @hidden */ - _authDataKey: string; - - /** @hidden */ - _keyStore: KeyStore; - - /** @hidden */ - _authData: { accountId?: string; allKeys?: string[] }; - - /** @hidden */ - _networkId: string; - - /** @hidden */ - // _near: Near; - _near: Near; - - /** @hidden */ - _connectedAccount: ConnectedWalletAccount; - - /** @hidden */ - _completeSignInPromise: Promise; - - constructor(near: Near, appKeyPrefix: string) { - if (typeof(appKeyPrefix) !== 'string') { - throw new Error('Please define a clear appKeyPrefix for this WalletConnection instance as the second argument to the constructor'); - } - - if (typeof window === 'undefined') { - return new Proxy(this, { - get(target, property) { - if(property === 'isSignedIn') { - return () => false; - } - if(property === 'getAccountId') { - return () => ''; - } - if(target[property] && typeof target[property] === 'function') { - return () => { - throw new Error('No window found in context, please ensure you are using WalletConnection on the browser'); - }; - } - return target[property]; - } - }); - } - this._near = near; - const authDataKey = appKeyPrefix + LOCAL_STORAGE_KEY_SUFFIX; - const authData = JSON.parse(window.localStorage.getItem(authDataKey)); - this._networkId = near.config.networkId; - this._walletBaseUrl = near.config.walletUrl; - appKeyPrefix = appKeyPrefix || near.config.contractName || 'default'; - this._keyStore = (near.config as NearConfig).keyStore || new InMemoryKeyStore(); - this._authData = authData || { allKeys: [] }; - this._authDataKey = authDataKey; - if (!this.isSignedIn()) { - this._completeSignInPromise = this._completeSignInWithAccessKey(); - } - } - - /** - * Returns true, if this WalletConnection is authorized with the wallet. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); - * ``` - */ - isSignedIn() { - return !!this._authData.accountId; - } - - /** - * Returns promise of completing signing in after redirecting from wallet - * @example - * ```js - * // on login callback page - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.isSignedIn(); // false - * await wallet.isSignedInAsync(); // true - * ``` - */ - async isSignedInAsync() { - if (!this._completeSignInPromise) { - return this.isSignedIn(); - } - - await this._completeSignInPromise; - return this.isSignedIn(); - } - - /** - * Returns authorized Account ID. - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * wallet.getAccountId(); - * ``` - */ - getAccountId() { - return this._authData.accountId || ''; - } - - /** - * Constructs string URL to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url - * - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * // return string URL to the NEAR Wallet - * const url = await wallet.requestSignInUrl({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignInUrl({contractId, methodNames, successUrl, failureUrl, keyType = 'ed25519'}: SignInOptions): Promise { - const currentUrl = new URL(window.location.href); - const newUrl = new URL(this._walletBaseUrl + LOGIN_WALLET_URL_SUFFIX); - newUrl.searchParams.set('success_url', successUrl || currentUrl.href); - newUrl.searchParams.set('failure_url', failureUrl || currentUrl.href); - if (contractId) { - /* Throws exception if contract account does not exist */ - const contractAccount = await this._near.account(contractId); - await contractAccount.state(); - - newUrl.searchParams.set('contract_id', contractId); - const accessKey = KeyPair.fromRandom(keyType); - newUrl.searchParams.set('public_key', accessKey.getPublicKey().toString()); - await this._keyStore.setKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + accessKey.getPublicKey(), accessKey); - } - - if (methodNames) { - methodNames.forEach(methodName => { - newUrl.searchParams.append('methodNames', methodName); - }); - } - - return newUrl.toString(); - } - - /** - * Redirects current page to the wallet authentication page. - * @param options An optional options object - * @param options.contractId The NEAR account where the contract is deployed - * @param options.successUrl URL to redirect upon success. Default: current url - * @param options.failureUrl URL to redirect upon failure. Default: current url - * - * @example - * ```js - * const wallet = new WalletConnection(near, 'my-app'); - * // redirects to the NEAR Wallet - * wallet.requestSignIn({ contractId: 'account-with-deploy-contract.near' }); - * ``` - */ - async requestSignIn(options: SignInOptions) { - const url = await this.requestSignInUrl(options); - - window.location.assign(url); - } - - /** - * Constructs string URL to the wallet to sign a transaction or batch of transactions. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactionsUrl({ transactions, meta, callbackUrl }: RequestSignTransactionsOptions): string { - const currentUrl = new URL(window.location.href); - const newUrl = new URL('sign', this._walletBaseUrl); - - newUrl.searchParams.set('transactions', transactions - .map(transaction => serialize(SCHEMA.Transaction, transaction)) - .map(serialized => Buffer.from(serialized).toString('base64')) - .join(',')); - newUrl.searchParams.set('callbackUrl', callbackUrl || currentUrl.href); - if (meta) newUrl.searchParams.set('meta', meta); - - return newUrl.toString(); - } - - /** - * Requests the user to quickly sign for a transaction or batch of transactions by redirecting to the wallet. - * - * @param options A required options object - * @param options.transactions List of transactions to sign - * @param options.callbackUrl URL to redirect upon success. Default: current url - * @param options.meta Meta information the wallet will send back to the application. `meta` will be attached to the `callbackUrl` as a url search param - * - */ - requestSignTransactions(options: RequestSignTransactionsOptions): void { - const url = this.requestSignTransactionsUrl(options); - - window.location.assign(url); - } - - /** - * @hidden - * Complete sign in for a given account id and public key. To be invoked by the app when getting a callback from the wallet. - */ - async _completeSignInWithAccessKey() { - const currentUrl = new URL(window.location.href); - const publicKey = currentUrl.searchParams.get('public_key') || ''; - const allKeys = (currentUrl.searchParams.get('all_keys') || '').split(','); - const accountId = currentUrl.searchParams.get('account_id') || ''; - // TODO: Handle errors during login - if (accountId) { - const authData = { - accountId, - allKeys - }; - window.localStorage.setItem(this._authDataKey, JSON.stringify(authData)); - if (publicKey) { - await this._moveKeyFromTempToPermanent(accountId, publicKey); - } - this._authData = authData; - } - currentUrl.searchParams.delete('public_key'); - currentUrl.searchParams.delete('all_keys'); - currentUrl.searchParams.delete('account_id'); - currentUrl.searchParams.delete('meta'); - currentUrl.searchParams.delete('transactionHashes'); - - window.history.replaceState({}, document.title, currentUrl.toString()); - } - - /** - * @hidden - * @param accountId The NEAR account owning the given public key - * @param publicKey The public key being set to the key store - */ - async _moveKeyFromTempToPermanent(accountId: string, publicKey: string) { - const keyPair = await this._keyStore.getKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - await this._keyStore.setKey(this._networkId, accountId, keyPair); - await this._keyStore.removeKey(this._networkId, PENDING_ACCESS_KEY_PREFIX + publicKey); - } - - /** - * Sign out from the current account - * @example - * walletConnection.signOut(); - */ - signOut() { - this._authData = {}; - window.localStorage.removeItem(this._authDataKey); - } - - /** - * Returns the current connected wallet account - */ - account() { - if (!this._connectedAccount) { - this._connectedAccount = new ConnectedWalletAccount(this, this._near.connection, this._authData.accountId); - } - return this._connectedAccount; - } -} - -/** - * {@link Account} implementation which redirects to wallet using {@link WalletConnection} when no local key is available. - */ -export class ConnectedWalletAccount extends Account { - walletConnection: WalletConnection; - - constructor(walletConnection: WalletConnection, connection: Connection, accountId: string) { - super(accountId, connection.provider, connection.signer); - this.walletConnection = walletConnection; - } - - // Overriding Account methods - - /** - * Sign a transaction by redirecting to the NEAR Wallet - * @see {@link WalletConnection#requestSignTransactions} - * @param options An optional options object - * @param options.receiverId The NEAR account ID of the transaction receiver. - * @param options.actions An array of transaction actions to be performed. - * @param options.walletMeta Additional metadata to be included in the wallet signing request. - * @param options.walletCallbackUrl URL to redirect upon completion of the wallet signing process. Default: current URL. - */ - async signAndSendTransaction({ receiverId, actions, walletMeta, walletCallbackUrl = window.location.href }: SignAndSendTransactionOptions): Promise { - const localKey = await this.signer.getPublicKey(); - let accessKey = await this.accessKeyForTransaction(receiverId, actions, localKey); - if (!accessKey) { - throw new Error(`Cannot find matching key for transaction sent to ${receiverId}`); - } - - if (localKey && localKey.toString() === accessKey.public_key) { - try { - return await super.signAndSendTransaction({ receiverId, actions }); - } catch (e) { - if (e.type === 'NotEnoughAllowance') { - accessKey = await this.accessKeyForTransaction(receiverId, actions); - } else { - throw e; - } - } - } - - const block = await this.provider.block({ finality: 'final' }); - const blockHash = baseDecode(block.header.hash); - - const publicKey = PublicKey.from(accessKey.public_key); - // TODO: Cache & listen for nonce updates for given access key - const nonce = accessKey.access_key.nonce + 1n; - const transaction = createTransaction(this.accountId, publicKey, receiverId, nonce, actions, blockHash); - await this.walletConnection.requestSignTransactions({ - transactions: [transaction], - meta: walletMeta, - callbackUrl: walletCallbackUrl - }); - - return new Promise((resolve, reject) => { - setTimeout(() => { - reject(new Error('Failed to redirect to sign transaction')); - }, 1000); - }); - - // TODO: Aggregate multiple transaction request with "debounce". - // TODO: Introduce TransactionQueue which also can be used to watch for status? - } - - /** - * Check if given access key allows the function call or method attempted in transaction - * @param accessKey Array of \{access_key: AccessKey, public_key: PublicKey\} items - * @param receiverId The NEAR account attempting to have access - * @param actions The action(s) needed to be checked for access - */ - async accessKeyMatchesTransaction(accessKey, receiverId: string, actions: Action[]): Promise { - const { access_key: { permission } } = accessKey; - if (permission === 'FullAccess') { - return true; - } - - if (permission.FunctionCall) { - const { receiver_id: allowedReceiverId, method_names: allowedMethods } = permission.FunctionCall; - /******************************** - Accept multisig access keys and let wallets attempt to signAndSendTransaction - If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 - ********************************/ - if (allowedReceiverId === this.accountId && allowedMethods.includes(MULTISIG_HAS_METHOD)) { - return true; - } - if (allowedReceiverId === receiverId) { - if (actions.length !== 1) { - return false; - } - const [{ functionCall }] = actions; - return functionCall && - (!functionCall.deposit || functionCall.deposit.toString() === '0') && // TODO: Should support charging amount smaller than allowance? - (allowedMethods.length === 0 || allowedMethods.includes(functionCall.methodName)); - // TODO: Handle cases when allowance doesn't have enough to pay for gas - } - } - // TODO: Support other permissions than FunctionCall - - return false; - } - - /** - * Helper function returning the access key (if it exists) to the receiver that grants the designated permission - * @param receiverId The NEAR account seeking the access key for a transaction - * @param actions The action(s) sought to gain access to - * @param localKey A local public key provided to check for access - */ - async accessKeyForTransaction(receiverId: string, actions: Action[], localKey?: PublicKey): Promise { - const accessKeys = await this.getAccessKeys(); - - if (localKey) { - const accessKey = accessKeys.find(key => key.public_key.toString() === localKey.toString()); - if (accessKey && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - const walletKeys = this.walletConnection._authData.allKeys; - for (const accessKey of accessKeys) { - if (walletKeys.indexOf(accessKey.public_key) !== -1 && await this.accessKeyMatchesTransaction(accessKey, receiverId, actions)) { - return accessKey; - } - } - - return null; - } -} diff --git a/packages/wallet-account/test/wallet_account.test.ts b/packages/wallet-account/test/wallet_account.test.ts deleted file mode 100644 index 43a6a3667a..0000000000 --- a/packages/wallet-account/test/wallet_account.test.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createTransactions } from './wallet_account'; - -createTransactions(); diff --git a/packages/wallet-account/test/wallet_account.ts b/packages/wallet-account/test/wallet_account.ts deleted file mode 100644 index 6557e71670..0000000000 --- a/packages/wallet-account/test/wallet_account.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { beforeEach, describe, expect, it } from '@jest/globals'; -import { KeyPair } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { KeyPairSigner } from '@near-js/signers'; -import { actionCreators } from '@near-js/transactions'; -import localStorage from 'localstorage-memory'; -import { WalletConnection } from '../src'; - - -const { functionCall } = actionCreators; -// If an access key has itself as receiverId and method permission add_request_and_confirm, then it is being used in a wallet with multisig contract: https://github.com/near/core-contracts/blob/671c05f09abecabe7a7e58efe942550a35fc3292/multisig/src/lib.rs#L149-L153 -const MULTISIG_HAS_METHOD = 'add_request_and_confirm'; - -let lastTransaction; - -// @ts-ignore -global.window = { - localStorage -}; -// @ts-ignore -global.document = { - title: 'documentTitle' -}; - -let history; -let nearFake; -let walletConnection; -let keyStore = new InMemoryKeyStore(); -export const createTransactions = () => { - beforeEach(() => { - keyStore.clear(); - nearFake = { - config: { - networkId: 'networkId', - contractName: 'contractId', - walletUrl: 'http://example.com/wallet', - keyStore: keyStore - }, - connection: { - networkId: 'networkId', - signer: new KeyPairSigner(KeyPair.fromRandom('ed25519')) - }, - account() { - return { - state() {} - }; - } - }; - history = []; - Object.assign(global.window, { - location: { - href: 'http://example.com/location', - }, - history: { - replaceState: (state, title, url) => history.push([state, title, url]) - } - }); - walletConnection = new WalletConnection(nearFake, ''); - }); - - it('not signed in by default', () => { - expect(walletConnection.isSignedIn()).not.toBeTruthy(); - }); - - it('throws if non string appKeyPrefix', () => { - // @ts-ignore - expect(() => new WalletConnection(nearFake)).toThrow(/appKeyPrefix/); -// @ts-ignore - expect(() => new WalletConnection(nearFake, 1)).toThrow(/appKeyPrefix/); - expect(() => new WalletConnection(nearFake, null)).toThrow(/appKeyPrefix/); - expect(() => new WalletConnection(nearFake, undefined)).toThrow(/appKeyPrefix/); - }); - - const BLOCK_HASH = '244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM'; - - function setupWalletConnectionForSigning({ allKeys, accountAccessKeys, signer }) { - walletConnection._authData = { - allKeys: allKeys, - accountId: 'signer.near' - }; - nearFake.connection.provider = { - query(params) { - if (params.request_type === 'view_account' && params.account_id === 'signer.near') { - return { }; - } - if (params.request_type === 'view_access_key_list' && params.account_id === 'signer.near') { - return { keys: accountAccessKeys }; - } - if (params.request_type === 'view_access_key' && params.account_id === 'signer.near') { - for (let accessKey of accountAccessKeys) { - if (accessKey.public_key === params.public_key) { - return accessKey.access_key; - } - } - } - throw new Error(`Unexpected query: ${JSON.stringify(params)}`); - }, - sendTransaction(signedTransaction) { - lastTransaction = signedTransaction; - return { - transaction_outcome: { outcome: { logs: [] } }, - receipts_outcome: [] - }; - }, - block() { - return { - header: { - hash: BLOCK_HASH - } - }; - }, - viewAccessKey(accountId, pk) { - return this.query({ - request_type: "view_access_key", - account_id: accountId, - public_key: pk.toString(), - }); - }, - }; - - nearFake.connection.signer = signer; - } - - describe('requests transaction signing with 2fa access key', () => { - beforeEach(async () => { - let localKeyPair = KeyPair.fromRandom('ed25519'); - let walletKeyPair = KeyPair.fromRandom('ed25519'); - setupWalletConnectionForSigning({ - // @ts-ignore - allKeys: [ walletKeyPair.publicKey.toString() ], - accountAccessKeys: [{ - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: '1000000000', - receiver_id: 'signer.near', - method_names: [MULTISIG_HAS_METHOD] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }], - signer: new KeyPairSigner(localKeyPair) - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it('V2', async () => { - const res = await walletConnection.account().signAndSendTransaction({ - receiverId: 'receiver.near', - actions: [functionCall('someMethod', new Uint8Array(), 1n, 1n)] - }); - - // multisig access key is accepted res is object representing transaction, populated upon wallet redirect to app - expect(res).toHaveProperty('transaction_outcome'); - expect(res).toHaveProperty('receipts_outcome'); - }); - }); - - describe('fails requests transaction signing without 2fa access key', () => { - beforeEach(async () => { - const localKeyPair = KeyPair.fromRandom('ed25519'); - const walletKeyPair = KeyPair.fromRandom('ed25519'); - setupWalletConnectionForSigning({ - // @ts-ignore - allKeys: [ walletKeyPair.publicKey.toString() ], - accountAccessKeys: [{ - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: '1000000000', - receiver_id: 'signer.near', - method_names: ['not_a_valid_2fa_method'] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }], - signer: new KeyPairSigner(localKeyPair) - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it('V2', () => { - return expect( - walletConnection.account().signAndSendTransaction({ - receiverId: 'receiver.near', - actions: [functionCall('someMethod', new Uint8Array(), 1n, 1n)] - }) - ).rejects.toThrow('Cannot find matching key for transaction sent to receiver.near'); - }); - }); - - describe('can sign transaction locally when function call has no attached deposit', () => { - beforeEach(async () => { - const localKeyPair = KeyPair.fromRandom('ed25519'); - setupWalletConnectionForSigning({ - allKeys: [ /* no keys in wallet needed */ ], - accountAccessKeys: [{ - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: '1000000000', - receiver_id: 'receiver.near', - method_names: [] - } - } - }, - // @ts-ignore - public_key: localKeyPair.publicKey.toString() - }], - signer: new KeyPairSigner(localKeyPair) - }); - await keyStore.setKey('networkId', 'signer.near', localKeyPair); - }); - - it.each([ - functionCall('someMethod', new Uint8Array(), 1n, 0n), - functionCall('someMethod', new Uint8Array(), 1n), - functionCall('someMethod', new Uint8Array()) - ])('V2', async (functionCall) => { - await walletConnection.account().signAndSendTransaction({ - receiverId: 'receiver.near', - actions: [ functionCall ] - }); - // NOTE: Transaction gets signed without wallet in this test - expect(lastTransaction).toMatchObject({ - transaction: { - receiverId: 'receiver.near', - signerId: 'signer.near', - actions: [{ - functionCall: { - methodName: 'someMethod', - } - }] - } - }); - }); - }); -} \ No newline at end of file diff --git a/packages/wallet-account/test/wallet_accounts.test.ts b/packages/wallet-account/test/wallet_accounts.test.ts deleted file mode 100644 index 08d703bb35..0000000000 --- a/packages/wallet-account/test/wallet_accounts.test.ts +++ /dev/null @@ -1,533 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; -import { KeyPair, PublicKey } from '@near-js/crypto'; -import { InMemoryKeyStore } from '@near-js/keystores'; -import { baseDecode } from '@near-js/utils'; -import { KeyPairSigner } from '@near-js/signers'; -import { actionCreators, createTransaction, SCHEMA } from '@near-js/transactions'; -import { deserialize } from 'borsh'; -import localStorage from 'localstorage-memory'; -import * as url from 'url'; -import { WalletConnection } from '../src'; - - -const { functionCall, transfer } = actionCreators; - -let lastRedirectUrl; - -// @ts-expect-error test input -global.window = { - localStorage -}; -// @ts-expect-error test input -global.document = { - title: 'documentTitle' -}; - -let history; -let nearFake; -let walletConnection; -let keyStore = new InMemoryKeyStore(); -describe("Wallet account tests", () => { - beforeEach(() => { - keyStore.clear(); - nearFake = { - config: { - networkId: 'networkId', - contractName: 'contractId', - walletUrl: 'http://example.com/wallet', - keyStore: keyStore - }, - connection: { - networkId: 'networkId', - signer: new KeyPairSigner(KeyPair.fromRandom('ed25519')) - }, - account() { - return { - state() {} - }; - } - }; - lastRedirectUrl = null; - history = []; - Object.assign(global.window, { - location: { - href: 'http://example.com/location', - assign(url) { - lastRedirectUrl = url; - } - }, - history: { - replaceState: (state, title, url) => history.push([state, title, url]) - } - }); - walletConnection = new WalletConnection(nearFake, ''); - }); - - describe("fails gracefully on the server side (without window)", () => { - const windowValueBefore = global.window; - - beforeEach(() => { - global.window = undefined; - keyStore.clear(); - }); - - afterEach(() => { - global.window = windowValueBefore; - }); - - it("does not throw on instantiation", () => { - expect(() => new WalletConnection(nearFake, "")).not.toThrowError(); - }); - - it("throws if non string appKeyPrefix in server context", () => { - // @ts-expect-error test input - expect(() => new WalletConnection(nearFake)).toThrow( - /appKeyPrefix/ - ); - // @ts-expect-error invalid type check - expect(() => new WalletConnection(nearFake, 1)).toThrow( - /appKeyPrefix/ - ); - expect(() => new WalletConnection(nearFake, null)).toThrow( - /appKeyPrefix/ - ); - expect(() => new WalletConnection(nearFake, undefined)).toThrow( - /appKeyPrefix/ - ); - }); - - it("returns an empty string as accountId", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(serverWalletConnection.getAccountId()).toEqual(""); - }); - - it("returns false as isSignedIn", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(serverWalletConnection.isSignedIn()).toEqual(false); - }); - - it("throws explicit error when calling other methods on the instance", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(() => - serverWalletConnection.requestSignIn( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignInUrl( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignTransactions( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - expect(() => - serverWalletConnection.requestSignTransactionsUrl( - "signInContract", - // @ts-expect-error test input - "signInTitle", - "http://example.com/success", - "http://example.com/fail" - ) - ).toThrow( - /please ensure you are using WalletConnection on the browser/ - ); - }); - - it("can access other props on the instance", () => { - const serverWalletConnection = new WalletConnection(nearFake, ""); - expect(serverWalletConnection["randomValue"]).toEqual(undefined); - }); - }); - - describe("can request sign in", () => { - beforeEach(() => keyStore.clear()); - - it("V2", () => { - return walletConnection.requestSignIn({ - contractId: "signInContract", - successUrl: "http://example.com/success", - failureUrl: "http://example.com/fail", - }); - }); - - afterEach(async () => { - let accounts = await keyStore.getAccounts("networkId"); - expect(accounts).toHaveLength(1); - expect(accounts[0]).toMatch(/^pending_key.+/); - expect(url.parse(lastRedirectUrl, true)).toMatchObject({ - protocol: "http:", - host: "example.com", - query: { - contract_id: "signInContract", - success_url: "http://example.com/success", - failure_url: "http://example.com/fail", - public_key: ( - await keyStore.getKey("networkId", accounts[0]) - // @ts-expect-error test input - ).publicKey.toString(), - }, - }); - }); - }); - - it("can request sign in with methodNames", async () => { - await walletConnection.requestSignIn({ - contractId: "signInContract", - methodNames: ["hello", "goodbye"], - successUrl: "http://example.com/success", - failureUrl: "http://example.com/fail", - }); - - let accounts = await keyStore.getAccounts("networkId"); - expect(accounts).toHaveLength(1); - expect(accounts[0]).toMatch(/^pending_key.+/); - expect(url.parse(lastRedirectUrl, true)).toMatchObject({ - protocol: "http:", - host: "example.com", - query: { - contract_id: "signInContract", - methodNames: ["hello", "goodbye"], - success_url: "http://example.com/success", - failure_url: "http://example.com/fail", - public_key: ( - await keyStore.getKey("networkId", accounts[0]) - // @ts-expect-error test input - ).publicKey.toString(), - }, - }); - }); - - it("can complete sign in", async () => { - const keyPair = KeyPair.fromRandom("ed25519"); - // @ts-expect-error test input - global.window.location.href = `http://example.com/location?account_id=near.account&public_key=${keyPair.publicKey}`; - await keyStore.setKey( - "networkId", - // @ts-expect-error test input - "pending_key" + keyPair.publicKey, - keyPair - ); - - await walletConnection._completeSignInWithAccessKey(); - - expect(await keyStore.getKey("networkId", "near.account")).toEqual( - keyPair - ); - expect(localStorage.getItem("contractId_wallet_auth_key")); - expect(history.slice(1)).toEqual([ - [{}, "documentTitle", "http://example.com/location"], - ]); - }); - - it("Promise until complete sign in", async () => { - const keyPair = KeyPair.fromRandom("ed25519"); - // @ts-expect-error test input - global.window.location.href = `http://example.com/location?account_id=near2.account&public_key=${keyPair.publicKey}`; - await keyStore.setKey( - "networkId", - // @ts-expect-error test input - "pending_key" + keyPair.publicKey, - keyPair - ); - - const newWalletConn = new WalletConnection( - nearFake, - "promise_on_complete_signin" - ); - - expect(newWalletConn.isSignedIn()).toEqual(false); - expect(await newWalletConn.isSignedInAsync()).toEqual(true); - expect(await keyStore.getKey("networkId", "near2.account")).toEqual( - keyPair - ); - expect( - localStorage.getItem("promise_on_complete_signin_wallet_auth_key") - ); - expect(history).toEqual([ - [{}, "documentTitle", "http://example.com/location"], - ]); - }); - - const BLOCK_HASH = "244ZQ9cgj3CQ6bWBdytfrJMuMQ1jdXLFGnr4HhvtCTnM"; - const blockHash = baseDecode(BLOCK_HASH); - function createTransferTx() { - const actions = [transfer(1n)]; - return createTransaction( - "test.near", - PublicKey.fromString( - "Anu7LYDfpLtkP7E16LT9imXF694BdQaa9ufVkQiwTQxC" - ), - "whatever.near", - 1, - actions, - blockHash - ); - } - - describe("can request transaction signing", () => { - it("V1", async () => { - await walletConnection.requestSignTransactions({ - transactions: [createTransferTx()], - callbackUrl: "http://example.com/callback", - }); - - expect(url.parse(lastRedirectUrl, true)).toMatchObject({ - protocol: "http:", - host: "example.com", - query: { - callbackUrl: "http://example.com/callback", - transactions: - "CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAA==", - }, - }); - }); - - it("V2", async () => { - await walletConnection.requestSignTransactions({ - transactions: [createTransferTx()], - meta: "something", - callbackUrl: "http://example.com/after", - }); - - expect(url.parse(lastRedirectUrl, true)).toMatchObject({ - protocol: "http:", - host: "example.com", - query: { - meta: "something", - callbackUrl: "http://example.com/after", - transactions: - "CQAAAHRlc3QubmVhcgCRez0mjUtY9/7BsVC9aNab4+5dTMOYVeNBU4Rlu3eGDQEAAAAAAAAADQAAAHdoYXRldmVyLm5lYXIPpHP9JpAd8pa+atxMxN800EDvokNSJLaYaRDmMML+9gEAAAADAQAAAAAAAAAAAAAAAAAAAA==", - }, - }); - }); - }); - - function parseTransactionsFromUrl( - urlToParse, - callbackUrl = "http://example.com/location" - ) { - const parsedUrl = url.parse(urlToParse, true); - expect(parsedUrl).toMatchObject({ - protocol: "http:", - host: "example.com", - query: { - callbackUrl, - }, - }); - const transactions = parsedUrl.query.transactions - // @ts-expect-error test input - .split(",") - .map((txBase64) => - deserialize(SCHEMA.Transaction, Buffer.from(txBase64, "base64")) - ); - return transactions; - } - - function setupWalletConnectionForSigning({ allKeys, accountAccessKeys }) { - walletConnection._authData = { - allKeys: allKeys, - accountId: "signer.near", - }; - nearFake.connection.provider = { - query(params) { - if ( - params.request_type === "view_account" && - params.account_id === "signer.near" - ) { - return {}; - } - if ( - params.request_type === "view_access_key_list" && - params.account_id === "signer.near" - ) { - return { keys: accountAccessKeys }; - } - if ( - params.request_type === "view_access_key" && - params.account_id === "signer.near" - ) { - for (let accessKey of accountAccessKeys) { - if (accessKey.public_key === params.public_key) { - return accessKey; - } - } - } - throw new Error(`Unexpected query: ${JSON.stringify(params)}`); - }, - sendTransaction() { - return { - transaction_outcome: { outcome: { logs: [] } }, - receipts_outcome: [], - }; - }, - block() { - return { - header: { - hash: BLOCK_HASH, - }, - }; - }, - }; - } - - describe("requests transaction signing automatically when there is no local key", () => { - const keyPair = KeyPair.fromRandom("ed25519"); - let transactions; - beforeEach(() => { - setupWalletConnectionForSigning({ - // @ts-expect-error test input - allKeys: ["no_such_access_key", keyPair.publicKey.toString()], - accountAccessKeys: [ - { - access_key: { - nonce: 1, - permission: "FullAccess", - }, - // @ts-expect-error test input - public_key: keyPair.publicKey.toString(), - }, - ], - }); - }); - - it("V2", async () => { - let failed = true; - try { - await walletConnection.account().signAndSendTransaction({ - receiverId: "receiver.near", - actions: [transfer(1n)], - walletCallbackUrl: "http://callback.com/callback", - }); - failed = false; - } catch (e) { - expect(e.message).toEqual( - "Failed to redirect to sign transaction" - ); - } - if (!failed) { - throw new Error("expected to throw"); - } - transactions = parseTransactionsFromUrl( - lastRedirectUrl, - "http://callback.com/callback" - ); - }); - - afterEach(() => { - expect(transactions).toHaveLength(1); - expect(transactions[0]).toMatchObject({ - signerId: "signer.near", - // nonce: new BN(2) - receiverId: "receiver.near", - actions: [ - { - transfer: { - // deposit: new BN(1) - }, - }, - ], - }); - expect(transactions[0].nonce.toString()).toEqual("2"); - expect( - transactions[0].actions[0].transfer.deposit.toString() - ).toEqual("1"); - const txData = transactions[0].publicKey.ed25519Key.data ? transactions[0].publicKey.ed25519Key.data : transactions[0].publicKey.secp256k1Key.data; - const publicKey = keyPair.getPublicKey(); - const keyPairData = publicKey.ed25519Key.data ? publicKey.ed25519Key.data : publicKey.secp256k1Key.data; - expect(Buffer.from(txData)).toEqual( - Buffer.from(keyPairData) - ); - }); - }); - - describe("requests transaction signing automatically when function call has attached deposit", () => { - beforeEach(async () => { - const localKeyPair = KeyPair.fromRandom("ed25519"); - const walletKeyPair = KeyPair.fromRandom("ed25519"); - setupWalletConnectionForSigning({ - // @ts-expect-error test input - allKeys: [walletKeyPair.publicKey.toString()], - accountAccessKeys: [ - { - access_key: { - nonce: 1, - permission: { - FunctionCall: { - allowance: "1000000000", - receiver_id: "receiver.near", - method_names: [], - }, - }, - }, - // @ts-expect-error test input - public_key: localKeyPair.publicKey.toString(), - }, - { - access_key: { - nonce: 1, - permission: "FullAccess", - }, - // @ts-expect-error test input - public_key: walletKeyPair.publicKey.toString(), - }, - ], - }); - await keyStore.setKey("networkId", "signer.near", localKeyPair); - }); - - it("V2", async () => { - let failed = true; - try { - await walletConnection.account().signAndSendTransaction({ - receiverId: "receiver.near", - actions: [ - functionCall( - "someMethod", - new Uint8Array(), - 1n, - 1n - ), - ], - walletCallbackUrl: "http://example.com/after", - walletMeta: "someStuff", - }); - failed = false; - } catch (e) { - expect(e.message).toEqual( - "Failed to redirect to sign transaction" - ); - } - - if (!failed) { - throw new Error("expected to throw"); - } - - const transactions = parseTransactionsFromUrl( - lastRedirectUrl, - "http://example.com/after" - ); - expect(transactions).toHaveLength(1); - }); - }); -}); diff --git a/packages/wallet-account/tsconfig.cjs.json b/packages/wallet-account/tsconfig.cjs.json deleted file mode 100644 index 83abd57c4f..0000000000 --- a/packages/wallet-account/tsconfig.cjs.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "tsconfig/cjs.json", - "compilerOptions": { - "outDir": "./lib/commonjs", - "lib": ["es2022", "dom"] - }, - "files": [ - "src/index.ts" - ] -} diff --git a/packages/wallet-account/tsconfig.json b/packages/wallet-account/tsconfig.json deleted file mode 100644 index c228e5cdfe..0000000000 --- a/packages/wallet-account/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "tsconfig/browser.json", - "compilerOptions": { - "preserveSymlinks": false, - "outDir": "./lib/esm", - }, - "files": [ - "src/index.ts" - ] -} diff --git a/packages/wallet-account/typedoc.json b/packages/wallet-account/typedoc.json deleted file mode 100644 index 61b70ef7b6..0000000000 --- a/packages/wallet-account/typedoc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": [ - "../../typedoc.json" - ], - "entryPoints": [ - "src" - ], - "entryPointStrategy": "expand", -} \ No newline at end of file From 7fbf285709deaa02648665e3e70bfe2d7c6f14c3 Mon Sep 17 00:00:00 2001 From: denbite Date: Mon, 28 Apr 2025 23:52:12 +0200 Subject: [PATCH 33/57] feat: add new package `@near-js/tokens` with pre-defined Native and Fungible Tokens --- .changeset/rotten-cases-dance.md | 1 + packages/accounts/package.json | 1 + packages/accounts/src/account.ts | 52 ++++++++++ packages/providers/src/json-rpc-provider.ts | 2 +- packages/tokens/README.md | 8 ++ packages/tokens/jest.config.ts | 13 +++ packages/tokens/package.json | 52 ++++++++++ packages/tokens/src/format.ts | 81 +++++++++++++++ packages/tokens/src/fungible/index.ts | 11 ++ packages/tokens/src/index.ts | 6 ++ packages/tokens/src/native/index.ts | 11 ++ packages/tokens/src/token.ts | 24 +++++ packages/tokens/src/usdc/mainnet.ts | 13 +++ packages/tokens/src/usdc/testnet.ts | 10 ++ packages/tokens/src/usdt/mainnet.ts | 10 ++ packages/tokens/src/usdt/testnet.ts | 10 ++ packages/tokens/test/fungible_token.test.ts | 107 ++++++++++++++++++++ packages/tokens/test/native_token.test.ts | 107 ++++++++++++++++++++ packages/tokens/tsconfig.cjs.json | 9 ++ packages/tokens/tsconfig.json | 8 ++ packages/tokens/typedoc.json | 9 ++ packages/types/src/provider/response.ts | 2 +- pnpm-lock.yaml | 43 ++++++-- typedoc.json | 1 + 24 files changed, 581 insertions(+), 10 deletions(-) create mode 100644 packages/tokens/README.md create mode 100644 packages/tokens/jest.config.ts create mode 100644 packages/tokens/package.json create mode 100644 packages/tokens/src/format.ts create mode 100644 packages/tokens/src/fungible/index.ts create mode 100644 packages/tokens/src/index.ts create mode 100644 packages/tokens/src/native/index.ts create mode 100644 packages/tokens/src/token.ts create mode 100644 packages/tokens/src/usdc/mainnet.ts create mode 100644 packages/tokens/src/usdc/testnet.ts create mode 100644 packages/tokens/src/usdt/mainnet.ts create mode 100644 packages/tokens/src/usdt/testnet.ts create mode 100644 packages/tokens/test/fungible_token.test.ts create mode 100644 packages/tokens/test/native_token.test.ts create mode 100644 packages/tokens/tsconfig.cjs.json create mode 100644 packages/tokens/tsconfig.json create mode 100644 packages/tokens/typedoc.json diff --git a/.changeset/rotten-cases-dance.md b/.changeset/rotten-cases-dance.md index 97d65c25c4..4fb27eaf74 100644 --- a/.changeset/rotten-cases-dance.md +++ b/.changeset/rotten-cases-dance.md @@ -8,6 +8,7 @@ "@near-js/signers": major "@near-js/client": major "@near-js/types": major +"@near-js/tokens": major --- Major update for Signer and Account APIs to streamline development diff --git a/packages/accounts/package.json b/packages/accounts/package.json index ec42bfbe0d..01e5e36661 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -23,6 +23,7 @@ "@near-js/transactions": "workspace:*", "@near-js/types": "workspace:*", "@near-js/utils": "workspace:*", + "@near-js/tokens": "workspace:*", "@noble/hashes": "1.7.1", "borsh": "1.0.0", "depd": "2.0.0", diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 1832bf45f5..7369fbded1 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -44,6 +44,7 @@ import { } from "./interface"; import { randomBytes } from "crypto"; import { SignedMessage } from "@near-js/signers/lib/esm/signer"; +import { NearToken, FungibleToken } from "@near-js/tokens"; const { addKey, @@ -1136,6 +1137,57 @@ export class Account implements IntoConnection { }; } + public async getTokenBalance( + token: NearToken | FungibleToken + ): Promise { + if (token instanceof NearToken) { + const { amount } = await this.getInformation(); + return amount; + } else if (token instanceof FungibleToken) { + const { result } = await this.callReadFunction( + token.contractId, + "ft_balance_of", + { account_id: this.accountId } + ); + return BigInt(result); + } else { + throw new Error(`Invalid token`); + } + } + + public async getFormattedTokenBalance( + token: NearToken | FungibleToken + ): Promise { + const balance = await this.getTokenBalance(token); + + return token.toAmount(balance); + } + + public async transferToken( + token: NearToken | FungibleToken, + amount: bigint | string | number, + receiverId: string + ): Promise { + if (token instanceof NearToken) { + return this.signAndSendTransaction({ + receiverId, + actions: [transfer(BigInt(amount))], + }); + } else if (token instanceof FungibleToken) { + return this.callFunction( + token.contractId, + "ft_transfer", + { + amount: String(amount), + receiver_id: receiverId, + }, + "1" + ); + } else { + throw new Error(`Invalid token`); + } + } + /** * Execute a function call * diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index becd55ecde..57a9b160ef 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -225,7 +225,7 @@ export class JsonRpcProvider implements Provider { return { ...data, - result: Buffer.from(data.result).toString(), + result: JSON.parse(Buffer.from(data.result).toString()), }; } diff --git a/packages/tokens/README.md b/packages/tokens/README.md new file mode 100644 index 0000000000..80f8998c36 --- /dev/null +++ b/packages/tokens/README.md @@ -0,0 +1,8 @@ +# @near-js/tokens + +A collection of standard tokens + +# License + +This repository is distributed under the terms of both the MIT license and the Apache License (Version 2.0). +See [LICENSE](https://github.com/near/near-api-js/blob/master/LICENSE) and [LICENSE-APACHE](https://github.com/near/near-api-js/blob/master/LICENSE-APACHE) for details. diff --git a/packages/tokens/jest.config.ts b/packages/tokens/jest.config.ts new file mode 100644 index 0000000000..3b66313239 --- /dev/null +++ b/packages/tokens/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/tokens/package.json b/packages/tokens/package.json new file mode 100644 index 0000000000..56a049ebfa --- /dev/null +++ b/packages/tokens/package.json @@ -0,0 +1,52 @@ +{ + "name": "@near-js/tokens", + "version": "0.1.0", + "description": "Modules with tokens", + "main": "lib/commonjs/index.cjs", + "module": "lib/esm/index.js", + "types": "./lib/esm/index.d.ts", + "scripts": { + "build": "pnpm compile:esm && pnpm compile:cjs", + "compile:esm": "tsc -p tsconfig.json", + "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", + "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc", + "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc --fix", + "test": "jest" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": {}, + "devDependencies": { + "@jest/globals": "^29.7.0", + "@types/node": "20.0.0", + "build": "workspace:*", + "jest": "29.7.0", + "ts-jest": "29.2.6", + "tsconfig": "workspace:*", + "typescript": "5.4.5" + }, + "files": [ + "lib" + ], + "exports": { + ".": { + "require": "./lib/commonjs/index.cjs", + "import": "./lib/commonjs/index.cjs", + "types": "./lib/esm/index.d.ts", + "default": "./lib/esm/index.js" + }, + "./usdt/*": { + "require": "./lib/commonjs/usdt/*.cjs", + "import": "./lib/commonjs/usdt/*.cjs", + "types": "./lib/esm/usdt/*.d.ts", + "default": "./lib/esm/usdt/*.js" + }, + "./usdc/*": { + "require": "./lib/commonjs/usdc/*.cjs", + "import": "./lib/commonjs/usdc/*.cjs", + "types": "./lib/esm/usdc/*.d.ts", + "default": "./lib/esm/usdc/*.js" + } + } +} diff --git a/packages/tokens/src/format.ts b/packages/tokens/src/format.ts new file mode 100644 index 0000000000..36418898f4 --- /dev/null +++ b/packages/tokens/src/format.ts @@ -0,0 +1,81 @@ +export const MAX_NOMINATION_EXP = 24; + +/** + * Convert internal indivisible units to formatted human-readable amount + */ +export function formatAmount( + units: string | number | bigint, + fracDigits: number +): string { + units = cleanupUnits(units); + + const wholeStr = units.substring(0, units.length - fracDigits) || "0"; + const fractionStr = units + .substring(units.length - fracDigits) + .substring(0, fracDigits) + .padStart(fracDigits, "0"); + + return trimTrailingZeroes(`${wholeStr}.${fractionStr}`); +} + +/** + * Convert human readable amount to internal indivisible units. + */ +export function parseAmount(amount: string, fracDigits: number): string { + amount = cleanupAmount(amount); + const split = amount.split("."); + + if (split.length > 2) { + throw new Error( + `Cannot parse amount '${amount}' as it contains more than a single dot` + ); + } + + const wholePart = split[0]; + const fracPart = split[1] || ""; + + if (fracPart.length > MAX_NOMINATION_EXP) { + throw new Error( + `Cannot parse amount '${amount}' as it exceeds maximum decimal precision of ${MAX_NOMINATION_EXP}` + ); + } + + return trimLeadingZeroes( + wholePart + fracPart.substring(0, fracDigits).padEnd(fracDigits, "0") + ); +} + +/** + * Removes commas from the input + * @param amount A value or amount that may contain commas + * @returns string The cleaned value + */ +function cleanupAmount(amount: string): string { + return amount.replace(/,/g, ".").trim(); +} + +/** + * Removes .000… from an input + * @param value A value that may contain trailing zeroes in the decimals place + * @returns string The value without the trailing zeros + */ +function trimTrailingZeroes(value: string): string { + return value.replace(/\.?0*$/, ""); +} + +/** + * Removes leading zeroes from an input + * @param value A value that may contain leading zeroes + * @returns string The value without the leading zeroes + */ +function trimLeadingZeroes(value: string): string { + value = value.replace(/^0+/, ""); + if (value === "") { + return "0"; + } + return value; +} + +function cleanupUnits(amount: string | number | bigint): string { + return BigInt(amount).toString(); +} diff --git a/packages/tokens/src/fungible/index.ts b/packages/tokens/src/fungible/index.ts new file mode 100644 index 0000000000..7d014bacd0 --- /dev/null +++ b/packages/tokens/src/fungible/index.ts @@ -0,0 +1,11 @@ +import { Token, TokenMetadata } from '../token'; + +/** Fungible Token deployed as a contract */ +export class FungibleToken extends Token { + public readonly contractId: string; + + constructor(contractId: string, metadata: TokenMetadata) { + super(metadata); + this.contractId = contractId; + } +} diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts new file mode 100644 index 0000000000..a4543c0fbc --- /dev/null +++ b/packages/tokens/src/index.ts @@ -0,0 +1,6 @@ +export { Token, TokenMetadata } from "./token"; + +export { NearToken } from "./native"; +export { FungibleToken } from "./fungible"; + +export { formatAmount, parseAmount } from './format'; \ No newline at end of file diff --git a/packages/tokens/src/native/index.ts b/packages/tokens/src/native/index.ts new file mode 100644 index 0000000000..4b3e96479b --- /dev/null +++ b/packages/tokens/src/native/index.ts @@ -0,0 +1,11 @@ +import { Token } from '../token'; + +/** Native Near Token */ +export class NearToken extends Token { + constructor() { + super({ + decimals: 24, + symbol: 'NEAR', + }); + } +} diff --git a/packages/tokens/src/token.ts b/packages/tokens/src/token.ts new file mode 100644 index 0000000000..f7429acb06 --- /dev/null +++ b/packages/tokens/src/token.ts @@ -0,0 +1,24 @@ +import { formatAmount, parseAmount } from "./format"; + +export interface TokenMetadata { + decimals: number; + symbol: string; +} + +export abstract class Token { + public readonly metadata: TokenMetadata; + + constructor(metadata: TokenMetadata) { + this.metadata = metadata; + } + + public toUnits(amount: string | number): bigint { + const units = parseAmount(amount.toString(), this.metadata.decimals); + + return BigInt(units); + } + + public toAmount(units: bigint | string | number): string { + return formatAmount(units, this.metadata.decimals); + } +} diff --git a/packages/tokens/src/usdc/mainnet.ts b/packages/tokens/src/usdc/mainnet.ts new file mode 100644 index 0000000000..dbec734cac --- /dev/null +++ b/packages/tokens/src/usdc/mainnet.ts @@ -0,0 +1,13 @@ +import { FungibleToken } from '../fungible'; + +export class USDC extends FungibleToken { + constructor() { + super( + '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', + { + decimals: 6, + symbol: 'USDC', + } + ); + } +} diff --git a/packages/tokens/src/usdc/testnet.ts b/packages/tokens/src/usdc/testnet.ts new file mode 100644 index 0000000000..b916a88aa4 --- /dev/null +++ b/packages/tokens/src/usdc/testnet.ts @@ -0,0 +1,10 @@ +import { FungibleToken } from '../fungible'; + +export class USDC extends FungibleToken { + constructor() { + super('usdc.fakes.testnet', { + decimals: 6, + symbol: 'USDC', + }); + } +} diff --git a/packages/tokens/src/usdt/mainnet.ts b/packages/tokens/src/usdt/mainnet.ts new file mode 100644 index 0000000000..97ec97c216 --- /dev/null +++ b/packages/tokens/src/usdt/mainnet.ts @@ -0,0 +1,10 @@ +import { FungibleToken } from '../fungible'; + +export class USDT extends FungibleToken { + constructor() { + super('dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near', { + decimals: 6, + symbol: 'USDT', + }); + } +} diff --git a/packages/tokens/src/usdt/testnet.ts b/packages/tokens/src/usdt/testnet.ts new file mode 100644 index 0000000000..4086549f9f --- /dev/null +++ b/packages/tokens/src/usdt/testnet.ts @@ -0,0 +1,10 @@ +import { FungibleToken } from '../fungible'; + +export class USDT extends FungibleToken { + constructor() { + super('usdt.fakes.testnet', { + decimals: 6, + symbol: 'USDT', + }); + } +} diff --git a/packages/tokens/test/fungible_token.test.ts b/packages/tokens/test/fungible_token.test.ts new file mode 100644 index 0000000000..f95156812c --- /dev/null +++ b/packages/tokens/test/fungible_token.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from '@jest/globals'; +import { FungibleToken } from '../src'; + +const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST' }); + +test('test props are accessible', () => { + expect(FT.contractId).toBe('ft.testnet'); + + expect(FT.metadata.decimals).toBe(6); + expect(FT.metadata.symbol).toBe('TEST'); +}); + +test('test toUnits parses formatted amount', () => { + expect(FT.toUnits('1.234')).toBe(BigInt('1234000')); + expect(FT.toUnits(1.234)).toBe(BigInt('1234000')); + + expect(FT.toUnits('1.234567')).toBe(BigInt('1234567')); + expect(FT.toUnits(1.234567)).toBe(BigInt('1234567')); + + // exceeds the precision, rounding down + expect(FT.toUnits('1.2345678')).toBe(BigInt('1234567')); + expect(FT.toUnits(1.2345678)).toBe(BigInt('1234567')); + + expect(FT.toUnits('1')).toBe(BigInt('1000000')); + expect(FT.toUnits(1)).toBe(BigInt('1000000')); + + expect(FT.toUnits('00001.234')).toBe(BigInt('1234000')); + + expect(FT.toUnits('0000000000000.576919')).toBe(BigInt('576919')); + + // exceeds the precision, rounding down + expect(FT.toUnits('0000000000000.5769191111')).toBe(BigInt('576919')); + expect(FT.toUnits('.5769191111')).toBe(BigInt('576919')); + + expect(FT.toUnits('0')).toBe(BigInt('0')); +}); + +test('test toUnits parses formatted amount with comma', () => { + expect(FT.toUnits('1,234')).toBe(BigInt('1234000')); + expect(FT.toUnits('1,234567')).toBe(BigInt('1234567')); + // exceeds the precision, rounding down + expect(FT.toUnits('1,2345678')).toBe(BigInt('1234567')); + expect(FT.toUnits('00001,234')).toBe(BigInt('1234000')); + expect(FT.toUnits('0000000000000,576919')).toBe(BigInt('576919')); + // exceeds the precision, rounding down + expect(FT.toUnits('0000000000000,5769191111')).toBe(BigInt('576919')); +}); + +test('test toUnits fails on precision of more than 24 decimals', () => { + // there's 25 ones after the point + expect(() => FT.toUnits('0.1111111111111111111111111')).toThrow(); +}); + +test('test toUnits fails on invalid numeric string', () => { + expect(() => FT.toUnits('2.1.3')).toThrow(); +}); + +test('test toUnits fails on non-numeric symbols', () => { + expect(() => FT.toUnits('1.24n')).toThrow(); + + expect(() => FT.toUnits('abc192.31')).toThrow(); + + expect(() => FT.toUnits('abcdefg')).toThrow(); +}); + +test('test toAmount formats units', () => { + expect(FT.toAmount('1000000')).toBe('1'); + expect(FT.toAmount(1_000_000)).toBe('1'); + expect(FT.toAmount(BigInt(1_000_000))).toBe('1'); + + expect(FT.toAmount('1000001')).toBe('1.000001'); + expect(FT.toAmount(1_000_001)).toBe('1.000001'); + expect(FT.toAmount(BigInt(1_000_001))).toBe('1.000001'); + + expect(FT.toAmount('1234567')).toBe('1.234567'); + expect(FT.toAmount(1_234_567)).toBe('1.234567'); + expect(FT.toAmount(BigInt(1_234_567))).toBe('1.234567'); + + expect(FT.toAmount('12345678')).toBe('12.345678'); + expect(FT.toAmount(12_345_678)).toBe('12.345678'); + expect(FT.toAmount(BigInt(12_345_678))).toBe('12.345678'); + + expect(FT.toAmount('710')).toBe('0.00071'); + expect(FT.toAmount(710)).toBe('0.00071'); + expect(FT.toAmount(BigInt(710))).toBe('0.00071'); + + expect(FT.toAmount('1')).toBe('0.000001'); + expect(FT.toAmount(1)).toBe('0.000001'); + expect(FT.toAmount(BigInt(1))).toBe('0.000001'); + + expect(FT.toAmount('0')).toBe('0'); + expect(FT.toAmount(0)).toBe('0'); + expect(FT.toAmount(BigInt(0))).toBe('0'); +}); + +test('test toAmount fails on non-integer units', () => { + expect(() => FT.toAmount('0.1')).toThrow(); + expect(() => FT.toAmount(0.1)).toThrow(); +}); + +test('test toAmount fails on non-numeric symbols', () => { + expect(() => FT.toAmount('1.24n')).toThrow(); + + expect(() => FT.toAmount('abc192.31')).toThrow(); + + expect(() => FT.toAmount('abcdefg')).toThrow(); +}); diff --git a/packages/tokens/test/native_token.test.ts b/packages/tokens/test/native_token.test.ts new file mode 100644 index 0000000000..b45dbc2f3c --- /dev/null +++ b/packages/tokens/test/native_token.test.ts @@ -0,0 +1,107 @@ +import { expect, test } from '@jest/globals'; +import { NearToken } from '../src'; + +const NEAR = new NearToken(); + +test('test toUnits parses formatted amount', () => { + expect(NEAR.toUnits('1.234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits(1.234)).toBe(BigInt('1234000000000000000000000')); + + expect(NEAR.toUnits('1.234567')).toBe(BigInt('1234567000000000000000000')); + expect(NEAR.toUnits(1.234567)).toBe(BigInt('1234567000000000000000000')); + + expect(NEAR.toUnits('1')).toBe(BigInt('1000000000000000000000000')); + expect(NEAR.toUnits(1)).toBe(BigInt('1000000000000000000000000')); + + expect(NEAR.toUnits('00001.234')).toBe(BigInt('1234000000000000000000000')); + + expect(NEAR.toUnits('0000000000000.576919')).toBe( + BigInt('576919000000000000000000') + ); + + // exceeds the precision, rounding down + expect(NEAR.toUnits('0000000000000.5769191111')).toBe( + BigInt('576919111100000000000000') + ); + expect(NEAR.toUnits('.5769191111')).toBe( + BigInt('576919111100000000000000') + ); + + expect(NEAR.toUnits('0')).toBe(BigInt('0')); +}); + +test('test toUnits parses formatted amount with comma', () => { + expect(NEAR.toUnits('1,234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits('1,234567')).toBe(BigInt('1234567000000000000000000')); + + expect(NEAR.toUnits('00001,234')).toBe(BigInt('1234000000000000000000000')); + expect(NEAR.toUnits('0000000000000,576919')).toBe( + BigInt('576919000000000000000000') + ); + // exceeds the precision, rounding down + expect(NEAR.toUnits('0000000000000,5769191111')).toBe( + BigInt('576919111100000000000000') + ); +}); + +test('test toUnits fails on precision of more than 24 decimals', () => { + // there's 25 ones after the point + expect(() => NEAR.toUnits('0.1111111111111111111111111')).toThrow(); +}); + +test('test toUnits fails on invalid numeric string', () => { + expect(() => NEAR.toUnits('2.1.3')).toThrow(); +}); + +test('test toUnits fails on non-numeric symbols', () => { + expect(() => NEAR.toUnits('1.24n')).toThrow(); + + expect(() => NEAR.toUnits('abc192.31')).toThrow(); + + expect(() => NEAR.toUnits('abcdefg')).toThrow(); +}); + +test('test toAmount formats units', () => { + expect(NEAR.toAmount('1000000')).toBe('0.000000000000000001'); + expect(NEAR.toAmount(1_000_000)).toBe('0.000000000000000001'); + expect(NEAR.toAmount(BigInt(1_000_000))).toBe('0.000000000000000001'); + + expect(NEAR.toAmount('1000001')).toBe('0.000000000000000001000001'); + expect(NEAR.toAmount(1_000_001)).toBe('0.000000000000000001000001'); + expect(NEAR.toAmount(BigInt(1_000_001))).toBe('0.000000000000000001000001'); + + expect(NEAR.toAmount('1234567')).toBe('0.000000000000000001234567'); + expect(NEAR.toAmount(1_234_567)).toBe('0.000000000000000001234567'); + expect(NEAR.toAmount(BigInt(1_234_567))).toBe('0.000000000000000001234567'); + + expect(NEAR.toAmount('12345678')).toBe('0.000000000000000012345678'); + expect(NEAR.toAmount(12_345_678)).toBe('0.000000000000000012345678'); + expect(NEAR.toAmount(BigInt(12_345_678))).toBe( + '0.000000000000000012345678' + ); + + expect(NEAR.toAmount('710')).toBe('0.00000000000000000000071'); + expect(NEAR.toAmount(710)).toBe('0.00000000000000000000071'); + expect(NEAR.toAmount(BigInt(710))).toBe('0.00000000000000000000071'); + + expect(NEAR.toAmount('1')).toBe('0.000000000000000000000001'); + expect(NEAR.toAmount(1)).toBe('0.000000000000000000000001'); + expect(NEAR.toAmount(BigInt(1))).toBe('0.000000000000000000000001'); + + expect(NEAR.toAmount('0')).toBe('0'); + expect(NEAR.toAmount(0)).toBe('0'); + expect(NEAR.toAmount(BigInt(0))).toBe('0'); +}); + +test('test toAmount fails on non-integer units', () => { + expect(() => NEAR.toAmount('0.1')).toThrow(); + expect(() => NEAR.toAmount(0.1)).toThrow(); +}); + +test('test toAmount fails on non-numeric symbols', () => { + expect(() => NEAR.toAmount('1.24n')).toThrow(); + + expect(() => NEAR.toAmount('abc192.31')).toThrow(); + + expect(() => NEAR.toAmount('abcdefg')).toThrow(); +}); diff --git a/packages/tokens/tsconfig.cjs.json b/packages/tokens/tsconfig.cjs.json new file mode 100644 index 0000000000..667b87a94f --- /dev/null +++ b/packages/tokens/tsconfig.cjs.json @@ -0,0 +1,9 @@ +{ + "extends": "tsconfig/cjs.json", + "compilerOptions": { + "outDir": "./lib/commonjs", + "lib": ["es2022", "dom"] + }, + "files": ["src/index.ts"], + "include": ["src/usdt", "src/usdc"] +} diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json new file mode 100644 index 0000000000..e61b0d4fd9 --- /dev/null +++ b/packages/tokens/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "tsconfig/esm.json", + "compilerOptions": { + "outDir": "./lib/esm" + }, + "files": ["src/index.ts"], + "include": ["src/usdt", "src/usdc"] +} diff --git a/packages/tokens/typedoc.json b/packages/tokens/typedoc.json new file mode 100644 index 0000000000..61b70ef7b6 --- /dev/null +++ b/packages/tokens/typedoc.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "../../typedoc.json" + ], + "entryPoints": [ + "src" + ], + "entryPointStrategy": "expand", +} \ No newline at end of file diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index d4083158e7..22e5b1bc8d 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -128,7 +128,7 @@ export interface CallContractViewFunctionResultRaw extends QueryResponseKind { } export interface CallContractViewFunctionResult extends QueryResponseKind { - result?: string; + result?: string | number | any; logs: string[]; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b2f44d646d..3a7f5ef556 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,9 @@ importers: '@near-js/signers': specifier: workspace:* version: link:../signers + '@near-js/tokens': + specifier: workspace:* + version: link:../tokens '@near-js/transactions': specifier: workspace:* version: link:../transactions @@ -600,6 +603,30 @@ importers: specifier: 5.4.5 version: 5.4.5 + packages/tokens: + devDependencies: + '@jest/globals': + specifier: ^29.7.0 + version: 29.7.0 + '@types/node': + specifier: 20.0.0 + version: 20.0.0 + build: + specifier: workspace:* + version: link:../build + jest: + specifier: 29.7.0 + version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) + ts-jest: + specifier: 29.2.6 + version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) + tsconfig: + specifier: workspace:* + version: link:../tsconfig + typescript: + specifier: 5.4.5 + version: 5.4.5 + packages/transactions: dependencies: '@near-js/crypto': @@ -4542,7 +4569,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -4579,7 +4606,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -4616,7 +4643,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -6218,7 +6245,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6249,7 +6276,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6280,7 +6307,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6311,7 +6338,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -6342,7 +6369,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 diff --git a/typedoc.json b/typedoc.json index 47bca6adcf..b40dfc5bbb 100644 --- a/typedoc.json +++ b/typedoc.json @@ -11,6 +11,7 @@ "packages/keystores-node", "packages/providers", "packages/signers", + "packages/tokens", "packages/transactions", "packages/types", "packages/utils", From 3f4f02cfceb323f08a1c1294702731d719644d46 Mon Sep 17 00:00:00 2001 From: denbite Date: Tue, 29 Apr 2025 22:38:08 +0200 Subject: [PATCH 34/57] fix: fix minor bugs in Account --- packages/accounts/src/account.ts | 10 +++++++--- packages/accounts/test/account.test.ts | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 661db9ef12..d9601915df 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -56,7 +56,7 @@ const { } = actionCreators; // Default values to wait for -const DEFAULT_FINALITY = "near-final" +const DEFAULT_FINALITY = "optimistic"; export const DEFAULT_WAIT_STATUS: TxExecutionStatus = "EXECUTED_OPTIMISTIC" export interface AccountBalance { @@ -264,7 +264,9 @@ export class Account { await this.signer.getPublicKey() ) - return this.signer.signTransaction(tx)[1] + const [,signedTx] = await this.signer.signTransaction(tx); + + return signedTx; } /** @@ -1131,7 +1133,9 @@ export class Account { * @returns {Promise} */ async getActiveDelegatedStakeBalance(): Promise { - const block = await this.provider.viewBlock({ finality: DEFAULT_FINALITY }); + const block = await this.provider.block({ + finality: DEFAULT_FINALITY, + }); const blockHash = block.header.hash; const epochId = block.header.epoch_id; const { current_validators, next_validators, current_proposals } = diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index 3e35d99952..44ef661c6d 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -36,7 +36,7 @@ test('create account and then view account returns the created account', async ( await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.getState(); - expect(state.amount).toEqual(newAmount.toString()); + expect(state.amount).toEqual(newAmount); }); test('create account with a secp256k1 key and then view account returns the created account', async () => { @@ -47,7 +47,7 @@ test('create account with a secp256k1 key and then view account returns the crea await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.getState(); - expect(state.amount).toEqual(newAmount.toString()); + expect(state.amount).toEqual(newAmount); }); test('Secp256k1 send money', async() => { @@ -56,7 +56,7 @@ test('Secp256k1 send money', async() => { const { amount: receiverAmount } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); const state = await receiver.getState(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); }); test('send money', async() => { @@ -65,7 +65,7 @@ test('send money', async() => { const { amount: receiverAmount } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); const state = await receiver.getState(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); }); test('send money through signAndSendTransaction', async() => { @@ -77,7 +77,7 @@ test('send money through signAndSendTransaction', async() => { actions: [actionCreators.transfer(10000n)], }); const state = await receiver.getState(); - expect(state.amount).toEqual((BigInt(receiverAmount) + 10000n).toString()); + expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); }); test('delete account', async() => { From d66f2d59ef9180040274c57459991cff47aa0189 Mon Sep 17 00:00:00 2001 From: denbite Date: Wed, 30 Apr 2025 22:13:13 +0200 Subject: [PATCH 35/57] chore: update lockfile --- pnpm-lock.yaml | 58 -------------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a7f5ef556..5f243e841a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -460,9 +460,6 @@ importers: '@near-js/utils': specifier: workspace:* version: link:../utils - '@near-js/wallet-account': - specifier: workspace:* - version: link:../wallet-account '@noble/curves': specifier: 1.8.1 version: 1.8.1 @@ -730,61 +727,6 @@ importers: specifier: 5.4.5 version: 5.4.5 - packages/wallet-account: - dependencies: - '@near-js/accounts': - specifier: workspace:* - version: link:../accounts - '@near-js/crypto': - specifier: workspace:* - version: link:../crypto - '@near-js/keystores': - specifier: workspace:* - version: link:../keystores - '@near-js/providers': - specifier: workspace:* - version: link:../providers - '@near-js/signers': - specifier: workspace:* - version: link:../signers - '@near-js/transactions': - specifier: workspace:* - version: link:../transactions - '@near-js/types': - specifier: workspace:* - version: link:../types - '@near-js/utils': - specifier: workspace:* - version: link:../utils - borsh: - specifier: 1.0.0 - version: 1.0.0 - devDependencies: - '@jest/globals': - specifier: ^29.7.0 - version: 29.7.0 - '@types/node': - specifier: 20.0.0 - version: 20.0.0 - build: - specifier: workspace:* - version: link:../build - jest: - specifier: 29.7.0 - version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) - localstorage-memory: - specifier: 1.0.3 - version: 1.0.3 - ts-jest: - specifier: 29.2.6 - version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) - tsconfig: - specifier: workspace:* - version: link:../tsconfig - typescript: - specifier: 5.4.5 - version: 5.4.5 - packages: '@ampproject/remapping@2.3.0': From 96ea10f0f1301bf158ac3cbc43dbd684aad8d3fc Mon Sep 17 00:00:00 2001 From: denbite Date: Wed, 30 Apr 2025 22:14:10 +0200 Subject: [PATCH 36/57] feat: implement `Provider.viewAccountBalance` method --- packages/accounts/src/account.ts | 25 +++---------------- .../providers/src/failover-rpc-provider.ts | 5 ++++ packages/providers/src/json-rpc-provider.ts | 21 ++++++++++++++++ packages/providers/src/provider.ts | 2 ++ packages/types/src/provider/index.ts | 1 + packages/types/src/provider/response.ts | 7 ++++++ 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 6018984828..114b2bd935 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -25,6 +25,7 @@ import { ContractStateView, ErrorContext, TxExecutionStatus, + AccountBalanceInfo, } from "@near-js/types"; import { baseDecode, @@ -67,13 +68,6 @@ export interface AccountBalance { available: string; } -export interface AccountBalanceInfo { - total: bigint; - usedOnStorage: bigint; - locked: bigint; - available: bigint; -} - export interface AccountAuthorizedApp { contractId: string; amount: string; @@ -162,20 +156,9 @@ export class Account { * balance, the amount locked for storage and the amount available */ public async getBalance(): Promise { - const state = await this.getState(); - - const usedOnStorage = BigInt(state.storage_usage) * BigInt(1e19); - const totalBalance = BigInt(state.amount) + state.locked; - const availableBalance = - totalBalance - - (state.locked > usedOnStorage ? state.locked : usedOnStorage); - - return { - total: totalBalance, - usedOnStorage, - locked: state.locked, - available: availableBalance, - }; + return this.provider.viewAccountBalance(this.accountId, { + finality: DEFAULT_FINALITY, + }); } /** diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index 0e30b2567b..b84208f663 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -35,6 +35,7 @@ import { CallContractViewFunctionResult, ExecutionOutcomeReceiptDetail, FinalityReference, + AccountBalanceInfo, } from '@near-js/types'; import { SignedTransaction } from '@near-js/transactions'; import { Provider } from './provider'; @@ -131,6 +132,10 @@ export class FailoverRpcProvider implements Provider { return this.withBackoff((currentProvider) => currentProvider.viewAccount(accountId, blockQuery)); } + public async viewAccountBalance(accountId: string, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.viewAccountBalance(accountId, blockQuery)); + } + public async viewContractCode(accountId: string, blockQuery?: BlockReference): Promise { return this.withBackoff((currentProvider) => currentProvider.viewContractCode(accountId, blockQuery)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 48d09a49ae..e324058021 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -44,6 +44,7 @@ import { CallContractViewFunctionResultRaw, CallContractViewFunctionResult, ExecutionOutcomeReceiptDetail, + AccountBalanceInfo, } from '@near-js/types'; import { encodeTransaction, @@ -167,6 +168,26 @@ export class JsonRpcProvider implements Provider { }; } + public async viewAccountBalance( + accountId: string, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const state = await this.viewAccount(accountId, blockQuery); + + const usedOnStorage = BigInt(state.storage_usage) * BigInt(1e19); + const totalBalance = BigInt(state.amount) + state.locked; + const availableBalance = + totalBalance - + (state.locked > usedOnStorage ? state.locked : usedOnStorage); + + return { + total: totalBalance, + usedOnStorage, + locked: state.locked, + available: availableBalance, + }; + } + public async viewContractCode( contractId: string, blockQuery: BlockReference = { finality: 'final' } diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 371140c4b9..9ca837eb1f 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -33,6 +33,7 @@ import { ChunkId, ChunkResult, FinalityReference, + AccountBalanceInfo, } from '@near-js/types'; import { PublicKey } from '@near-js/crypto'; @@ -47,6 +48,7 @@ export interface Provider { viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise; viewAccount(accountId: string, blockQuery?: BlockReference): Promise; + viewAccountBalance(accountId: string, blockQuery?: BlockReference): Promise; viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index 42f0620610..e9a8bbd9a1 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -55,6 +55,7 @@ export { AccessKeyViewRaw, AccountView, AccountViewRaw, + AccountBalanceInfo, CodeResult, ContractCodeView, ContractCodeViewRaw, diff --git a/packages/types/src/provider/response.ts b/packages/types/src/provider/response.ts index 22e5b1bc8d..76fc1e2dca 100644 --- a/packages/types/src/provider/response.ts +++ b/packages/types/src/provider/response.ts @@ -106,6 +106,13 @@ export interface AccountView extends QueryResponseKind { storage_paid_at: BlockHeight; } +export interface AccountBalanceInfo { + total: bigint; + usedOnStorage: bigint; + locked: bigint; + available: bigint; +} + interface StateItem { key: string; value: string; From 089a0503a9f20bb6b2c951a882312cb586b3f76e Mon Sep 17 00:00:00 2001 From: denbite Date: Wed, 30 Apr 2025 22:14:45 +0200 Subject: [PATCH 37/57] fix: allow public key to be passed over as string or `PublicKey` --- packages/accounts/src/account.ts | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 114b2bd935..94c11a4df3 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -165,7 +165,9 @@ export class Account { * Calls {@link Provider.viewAccessKey} to retrieve information for a * specific key in the account */ - public async getAccessKey(publicKey: PublicKey): Promise { + public async getAccessKey( + publicKey: PublicKey | string + ): Promise { return this.provider.viewAccessKey(this.accountId, publicKey, { finality: DEFAULT_FINALITY, }); @@ -210,11 +212,13 @@ export class Account { public async createTransaction( receiverId: string, actions: Action[], - publicKey: PublicKey + publicKey: PublicKey | string ) { if (!publicKey) throw new Error("Please provide a public key"); - const accessKey = await this.getAccessKey(publicKey); + const pk = PublicKey.from(publicKey); + + const accessKey = await this.getAccessKey(pk); const block = await this.provider.viewBlock({ finality: DEFAULT_FINALITY, @@ -225,7 +229,7 @@ export class Account { return createTransaction( this.accountId, - publicKey, + pk, receiverId, nonce + 1n, actions, @@ -264,11 +268,13 @@ export class Account { receiverId: string, actions: Action[], blockHeightTtl: number = 200, - publicKey: PublicKey + publicKey: PublicKey | string ): Promise { if (!publicKey) throw new Error(`Please provide a public key`); - const accessKey = await this.getAccessKey(publicKey); + const pk = PublicKey.from(publicKey); + + const accessKey = await this.getAccessKey(pk); const nonce = BigInt(accessKey.nonce) + 1n; const { header } = await this.provider.viewBlock({ @@ -281,7 +287,7 @@ export class Account { receiverId, senderId: this.accountId, actions, - publicKey, + publicKey: pk, nonce, maxBlockHeight, }); @@ -449,15 +455,15 @@ export class Account { * @param opts * @returns */ - public async addFunctionCallKey( - publicKey: PublicKey, + public async addFunctionCallAccessKey( + publicKey: PublicKey | string, contractId: string, methodNames: string[], allowance?: bigint | string | number ): Promise { const actions = [ addKey( - publicKey, + PublicKey.from(publicKey), functionCallAccessKey( contractId, methodNames, @@ -477,11 +483,11 @@ export class Account { * @returns {Promise} */ public async deleteKey( - publicKey: PublicKey + publicKey: PublicKey | string ): Promise { return this.signAndSendTransaction({ receiverId: this.accountId, - actions: [deleteKey(publicKey)], + actions: [deleteKey(PublicKey.from(publicKey))], }); } From 0796f9d7be605014b33e7769453c0c0603a0117e Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 1 May 2025 23:01:10 +0200 Subject: [PATCH 38/57] fix: remove `transferNEAR` function because `transferToken` serves the same purpose --- packages/accounts/src/account.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 94c11a4df3..0194241430 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -491,22 +491,6 @@ export class Account { }); } - /** - * Transfer NEAR Tokens to another account - * - * @param receiverId The NEAR account that will receive the Ⓝ balance - * @param amount Amount to send in yoctoⓃ - */ - public async transferNEAR( - receiverId: string, - amount: bigint | string | number - ): Promise { - return this.signAndSendTransaction({ - receiverId, - actions: [transfer(BigInt(amount))], - }); - } - /** * Call a function on a smart contract * From 1d74764fca3af4989f5f39177f7db6d20d9ead28 Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 1 May 2025 23:01:35 +0200 Subject: [PATCH 39/57] fix: make deposit and gas parameters of `Account.callFunction` optional --- packages/accounts/src/account.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 0194241430..b1d092a011 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -512,8 +512,8 @@ export class Account { contractId: string; methodName: string; args: Uint8Array | Record; - deposit: bigint | string | number; - gas: bigint | string | number; + deposit?: bigint | string | number; + gas?: bigint | string | number; }): Promise { return this.signAndSendTransaction({ receiverId: contractId, From 602c8820b9e42bc789b15876dd93a3f9fc9149c1 Mon Sep 17 00:00:00 2001 From: denbite Date: Thu, 1 May 2025 23:03:11 +0200 Subject: [PATCH 40/57] feat: introduce functions for register/unregister an account in FT and transfer with callbacks --- packages/accounts/src/account.ts | 95 +++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 2 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index b1d092a011..e694592e9d 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -1233,6 +1233,15 @@ export class Account { return token.toAmount(balance); } + /** + * Transfers a token to the specified receiver. + * + * Supports sending either the native NEAR token or any supported Fungible Token (FT). + * + * @param token + * @param amount - The amount of tokens to transfer (in the smallest units) + * @param receiverId + */ public async transferToken( token: NearToken | FungibleToken, amount: bigint | string | number, @@ -1248,14 +1257,96 @@ export class Account { contractId: token.contractId, methodName: "ft_transfer", args: { - amount: String(amount), + amount: amount.toString(), receiver_id: receiverId, }, deposit: 1, - gas: 30_000_000_000_000, // 30 Tgas }); } else { throw new Error(`Invalid token`); } } + + /** + * Transfers a Fungible Token to a receiver with a callback message. + * + * Only works with Fungible Tokens that implement the NEP-141 standard. + * + * The {@link message} will be forwarded to the receiver's contract + * and typically triggers further logic on their side. + * + * @param token + * @param amount - The amount of tokens to transfer (in the smallest units) + * @param receiverId + * @param message + */ + public async transferTokenWithCallback( + token: FungibleToken, + amount: bigint | string | number, + receiverId: string, + message: string + ): Promise { + return this.callFunction({ + contractId: token.contractId, + methodName: "ft_transfer_call", + args: { + amount: amount.toString(), + receiver_id: receiverId, + msg: message, + }, + deposit: 1, + }); + } + + /** + * Registers an account in the token contract's state. + * + * Creates a record for the specified account with an initial balance of zero, allowing it + * to send and receive tokens. If the account is already registered, the function performs + * no state changes and returns the attached deposit back to the caller. + */ + public async registerTokenAccount( + token: FungibleToken, + accountId?: string + ): Promise { + accountId = accountId ?? this.accountId; + + const { result } = await this.provider.callFunction( + token.contractId, + "storage_balance_bounds", + {} + ); + + const requiredDeposit = result.min as string; + + return this.callFunction({ + contractId: token.contractId, + methodName: "storage_deposit", + args: { + account_id: accountId, + registration_only: true, + }, + deposit: requiredDeposit, + }); + } + + /** + * Unregisters an account from the token contract's state. + * + * Removes the account's record from the contract, effectively disabling it from sending + * or receiving tokens. This operation requires the account's token balance to be exactly zero; + * otherwise, the function will panic. + */ + public async unregisterTokenAccount( + token: FungibleToken + ): Promise { + return this.callFunction({ + contractId: token.contractId, + methodName: "storage_unregister", + args: { + force: false, + }, + deposit: "1", + }); + } } From d11bf8c159de0a4042bb892a885b527680c2d972 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Wed, 7 May 2025 13:27:26 +0200 Subject: [PATCH 41/57] chore: update workspaces version --- packages/accounts/package.json | 2 +- packages/near-api-js/package.json | 2 +- packages/providers/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 01e5e36661..de44fae1b4 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -40,7 +40,7 @@ "build": "workspace:*", "jest": "29.7.0", "near-hello": "0.5.1", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "semver": "7.7.1", "ts-jest": "29.2.6", "tsconfig": "workspace:*", diff --git a/packages/near-api-js/package.json b/packages/near-api-js/package.json index f0bd05a8b8..aaf2287d26 100644 --- a/packages/near-api-js/package.json +++ b/packages/near-api-js/package.json @@ -35,7 +35,7 @@ "in-publish": "2.0.0", "jest": "29.7.0", "near-hello": "0.5.1", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "rimraf": "^6.0.1", "semver": "7.7.1", "ts-jest": "29.2.6" diff --git a/packages/providers/package.json b/packages/providers/package.json index d6eda97893..85ac696594 100644 --- a/packages/providers/package.json +++ b/packages/providers/package.json @@ -29,7 +29,7 @@ "@types/node": "20.0.0", "build": "workspace:*", "jest": "29.7.0", - "near-workspaces": "4.0.0", + "near-workspaces": "5.0.0", "ts-jest": "29.2.6", "tsconfig": "workspace:*", "typescript": "5.4.5" From c5be238e4991c935f4167d33e2d460754549f952 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Wed, 7 May 2025 13:28:18 +0200 Subject: [PATCH 42/57] chore: removed unexisting rpc method & rename method on account --- packages/accounts/src/account.ts | 38 +++++++++++++------ .../providers/src/failover-rpc-provider.ts | 5 --- packages/providers/src/json-rpc-provider.ts | 21 ---------- packages/providers/src/provider.ts | 2 - 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index e694592e9d..4b3819fc6b 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -151,16 +151,6 @@ export class Account { }); } - /** - * Returns information on the account's balance including the total - * balance, the amount locked for storage and the amount available - */ - public async getBalance(): Promise { - return this.provider.viewAccountBalance(this.accountId, { - finality: DEFAULT_FINALITY, - }); - } - /** * Calls {@link Provider.viewAccessKey} to retrieve information for a * specific key in the account @@ -1091,13 +1081,37 @@ export class Account { publicKey: item.public_key, }; }); + return { authorizedApps }; } /** - * Returns calculated account balance + * Returns the total amount of NEAR in the account, how much is used for storage, + * and how much is available + */ + async getDetailedNearBalance(): Promise { + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: DEFAULT_FINALITY, + }); + const state = await this.provider.viewAccount(this.accountId) + + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); + const usedOnStorage = BigInt(state.storage_usage) * costPerByte; + const locked = BigInt(state.locked); + const total = BigInt(state.amount) + locked; + const available = + total - (locked > usedOnStorage ? locked : usedOnStorage); + + return { total, usedOnStorage, locked, available }; + } + + /** + * @deprecated please use {@link getDetailedNearBalance} instead + * + * Returns a * - * @deprecated */ async getAccountBalance(): Promise { const protocolConfig = await this.provider.experimental_protocolConfig({ diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index b84208f663..0e30b2567b 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -35,7 +35,6 @@ import { CallContractViewFunctionResult, ExecutionOutcomeReceiptDetail, FinalityReference, - AccountBalanceInfo, } from '@near-js/types'; import { SignedTransaction } from '@near-js/transactions'; import { Provider } from './provider'; @@ -132,10 +131,6 @@ export class FailoverRpcProvider implements Provider { return this.withBackoff((currentProvider) => currentProvider.viewAccount(accountId, blockQuery)); } - public async viewAccountBalance(accountId: string, blockQuery?: BlockReference): Promise { - return this.withBackoff((currentProvider) => currentProvider.viewAccountBalance(accountId, blockQuery)); - } - public async viewContractCode(accountId: string, blockQuery?: BlockReference): Promise { return this.withBackoff((currentProvider) => currentProvider.viewContractCode(accountId, blockQuery)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index e324058021..48d09a49ae 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -44,7 +44,6 @@ import { CallContractViewFunctionResultRaw, CallContractViewFunctionResult, ExecutionOutcomeReceiptDetail, - AccountBalanceInfo, } from '@near-js/types'; import { encodeTransaction, @@ -168,26 +167,6 @@ export class JsonRpcProvider implements Provider { }; } - public async viewAccountBalance( - accountId: string, - blockQuery: BlockReference = { finality: 'final' } - ): Promise { - const state = await this.viewAccount(accountId, blockQuery); - - const usedOnStorage = BigInt(state.storage_usage) * BigInt(1e19); - const totalBalance = BigInt(state.amount) + state.locked; - const availableBalance = - totalBalance - - (state.locked > usedOnStorage ? state.locked : usedOnStorage); - - return { - total: totalBalance, - usedOnStorage, - locked: state.locked, - available: availableBalance, - }; - } - public async viewContractCode( contractId: string, blockQuery: BlockReference = { finality: 'final' } diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 9ca837eb1f..371140c4b9 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -33,7 +33,6 @@ import { ChunkId, ChunkResult, FinalityReference, - AccountBalanceInfo, } from '@near-js/types'; import { PublicKey } from '@near-js/crypto'; @@ -48,7 +47,6 @@ export interface Provider { viewAccessKeyList(accountId: string, finalityQuery?: FinalityReference): Promise; viewAccount(accountId: string, blockQuery?: BlockReference): Promise; - viewAccountBalance(accountId: string, blockQuery?: BlockReference): Promise; viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; From 6d2485fc09d1dafa1a578e383303dc8c6a485f7e Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Wed, 7 May 2025 13:28:27 +0200 Subject: [PATCH 43/57] chore: update pnpm lock --- pnpm-lock.yaml | 73 ++++++++++++-------------------------------------- 1 file changed, 17 insertions(+), 56 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f243e841a..687635a920 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -122,8 +122,8 @@ importers: specifier: 0.5.1 version: 0.5.1 near-workspaces: - specifier: 4.0.0 - version: 4.0.0 + specifier: 5.0.0 + version: 5.0.0 semver: specifier: 7.7.1 version: 7.7.1 @@ -498,8 +498,8 @@ importers: specifier: 0.5.1 version: 0.5.1 near-workspaces: - specifier: 4.0.0 - version: 4.0.0 + specifier: 5.0.0 + version: 5.0.0 rimraf: specifier: ^6.0.1 version: 6.0.1 @@ -544,8 +544,8 @@ importers: specifier: 29.7.0 version: 29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)) near-workspaces: - specifier: 4.0.0 - version: 4.0.0 + specifier: 5.0.0 + version: 5.0.0 ts-jest: specifier: 29.2.6 version: 29.2.6(@babel/core@7.24.7)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(jest@29.7.0(@types/node@20.0.0)(node-notifier@8.0.2)(ts-node@10.9.2(@types/node@20.0.0)(typescript@5.4.5)))(typescript@5.4.5) @@ -1743,9 +1743,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1760,15 +1757,12 @@ packages: bn.js@4.12.1: resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} - bn.js@5.2.1: - resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - - borsh@0.5.0: - resolution: {integrity: sha512-p9w/qGBeeFdUf2GPBPHdX5JQyez8K5VtoFN7PqSfmR+cVUMSmcwAKhP9n2aXoDSKbtS7xZlZt3MVnrJL7GdYhg==} - borsh@1.0.0: resolution: {integrity: sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==} + borsh@2.0.0: + resolution: {integrity: sha512-kc9+BgR3zz9+cjbwM8ODoUB4fs3X3I5A/HtX7LZKxCLaMrEeDFoBpnhZY//DTS1VZBSs6S5v46RZRbZjRFspEg==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1791,9 +1785,6 @@ packages: resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} engines: {node: '>= 6'} - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} @@ -2982,13 +2973,9 @@ packages: resolution: {integrity: sha512-6/AjLOzGLQsSzTHavrRe7CVMU5+CUnTg/7g66a7+jkP22ff5CupnwXrUE14iJ2QyyhRZr7VuQQj/G+glUmaNQg==} hasBin: true - near-units@0.1.9: - resolution: {integrity: sha512-xiuBjpNsi+ywiu7P6iWRZdgFm7iCr/cfWlVO6+e5uaAqH4mE1rrurElyrL91llNDSnMwogd9XmlZOw5KbbHNsA==} - hasBin: true - - near-workspaces@4.0.0: - resolution: {integrity: sha512-ArFhJ9MqesRaNN1cDKSVKm3rmltQbe+fK2DzGn16ESebfR7bkhgc1XjLPtBxJp4WiYS0k2ZRsQyy5YLzCLV2Og==} - engines: {node: '>= 14.0.0', npm: '>= 6.0.0'} + near-workspaces@5.0.0: + resolution: {integrity: sha512-fktOa9A6gge8JPa+Gp6vMJSF3mE3H6dEMvVPTa0bXx9cNp142bAAderX8dZLtN1fYduPFkV3yb+OGM8V0d6JdA==} + engines: {node: '>=20.15.0', pnpm: '>= 9.15'} node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} @@ -3449,9 +3436,6 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - text-extensions@2.4.0: resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} engines: {node: '>=8'} @@ -5146,10 +5130,6 @@ snapshots: balanced-match@1.0.2: {} - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - base64-js@1.5.1: {} base64url@3.0.1: {} @@ -5160,16 +5140,10 @@ snapshots: bn.js@4.12.1: {} - bn.js@5.2.1: {} - - borsh@0.5.0: - dependencies: - bn.js: 5.2.1 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - borsh@1.0.0: {} + borsh@2.0.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -5196,10 +5170,6 @@ snapshots: dependencies: fast-json-stable-stringify: 2.1.0 - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - bser@2.1.1: dependencies: node-int64: 0.4.0 @@ -6795,27 +6765,20 @@ snapshots: got: 11.8.6 tar: 6.2.1 - near-units@0.1.9: - dependencies: - bn.js: 5.2.1 - - near-workspaces@4.0.0: + near-workspaces@5.0.0: dependencies: + '@scure/base': 1.2.4 base64url: 3.0.1 - bn.js: 5.2.1 - borsh: 0.5.0 - bs58: 4.0.1 + borsh: 2.0.0 callsites: 4.2.0 fs-extra: 10.1.0 js-sha256: 0.9.0 near-api-js: link:packages/near-api-js near-sandbox: 0.0.18 - near-units: 0.1.9 node-port-check: 2.0.1 promisify-child-process: 4.1.2 proper-lockfile: 4.1.2 pure-uuid: 1.8.1 - rimraf: 3.0.2 temp-dir: 2.0.0 node-addon-api@5.1.0: {} @@ -7211,8 +7174,6 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 - text-encoding-utf-8@1.0.2: {} - text-extensions@2.4.0: {} text-table@0.2.0: {} From ec6639709b59d03990060ecc109378ff20f60968 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 8 May 2025 01:16:12 +0200 Subject: [PATCH 44/57] fix: improved interface for getState --- packages/accounts/src/account.ts | 139 +++++++++++++++---------- packages/accounts/test/account.test.ts | 75 +++++++------ packages/providers/src/provider.ts | 2 +- 3 files changed, 121 insertions(+), 95 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 4b3819fc6b..7458f295fd 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -14,7 +14,6 @@ import { PositionalArgsError, FinalExecutionOutcome, TypedError, - AccountView, AccessKeyView, AccessKeyViewRaw, AccessKeyList, @@ -25,7 +24,7 @@ import { ContractStateView, ErrorContext, TxExecutionStatus, - AccountBalanceInfo, + AccountView, } from "@near-js/types"; import { baseDecode, @@ -61,6 +60,17 @@ const { const DEFAULT_FINALITY = "optimistic"; export const DEFAULT_WAIT_STATUS: TxExecutionStatus = "EXECUTED_OPTIMISTIC"; +export interface AccountState { + balance: { + total: bigint; + usedOnStorage: bigint; + locked: bigint; + available: bigint; + } + storageUsage: number; + codeHash: string; +} + export interface AccountBalance { total: string; stateStaked: string; @@ -142,13 +152,33 @@ export class Account { } /** - * Calls {@link Provider.viewAccount} to retrieve the account's balance, - * locked tokens, storage usage, and code hash + * Returns an overview of the account's state, including the account's + * balance, storage usage, and code hash */ - public async getState(): Promise { - return this.provider.viewAccount(this.accountId, { + public async getState(): Promise { + const protocolConfig = await this.provider.experimental_protocolConfig({ + finality: DEFAULT_FINALITY, + }); + const state = await this.provider.viewAccount(this.accountId, { finality: DEFAULT_FINALITY, }); + + const costPerByte = BigInt( + protocolConfig.runtime_config.storage_amount_per_byte + ); + const usedOnStorage = BigInt(state.storage_usage) * costPerByte; + const locked = BigInt(state.locked); + const total = BigInt(state.amount) + locked; + const available = + total - (locked > usedOnStorage ? locked : usedOnStorage); + + return { + balance: { + total, usedOnStorage, locked, available + }, + storageUsage: state.storage_usage, + codeHash: state.code_hash, + }; } /** @@ -451,23 +481,44 @@ export class Account { methodNames: string[], allowance?: bigint | string | number ): Promise { - const actions = [ - addKey( - PublicKey.from(publicKey), - functionCallAccessKey( - contractId, - methodNames, - BigInt(allowance) - ) - ), - ]; - return this.signAndSendTransaction({ receiverId: this.accountId, - actions, + actions: [ + addKey( + PublicKey.from(publicKey), + functionCallAccessKey( + contractId, + methodNames, + BigInt(allowance) + ) + ), + ] }); } + /** + * Add a full access key to the account + * + * @param publicKey The public key to be added + * @param opts + * @returns {Promise} + */ + public async addFullAccessKey( + publicKey: PublicKey | string + ): Promise { + return this.signAndSendTransaction( + { + receiverId: this.accountId, + actions: [ + addKey( + PublicKey.from(publicKey), + fullAccessKey() + ), + ], + } + ); + } + /** * @param publicKey The public key to be deleted * @returns {Promise} @@ -980,21 +1031,6 @@ export class Account { }); } - public async addFullAccessKey( - pk: PublicKey | string, - opts?: { signer: Signer } - ): Promise { - const actions = [addKey(PublicKey.from(pk), fullAccessKey())]; - - return this.signAndSendTransactionLegacy( - { - receiverId: this.accountId, - actions: actions, - }, - opts - ); - } - /** * Invoke a contract view function using the RPC API. * @see [https://docs.near.org/api/rpc/contracts#call-a-contract-function](https://docs.near.org/api/rpc/contracts#call-a-contract-function) @@ -1059,10 +1095,11 @@ export class Account { } /** + * @deprecated + * * Returns a list of authorized apps * @todo update the response value to return all the different keys, not just app keys. * - * @deprecated */ async getAccountDetails(): Promise<{ authorizedApps: AccountAuthorizedApp[]; @@ -1086,38 +1123,28 @@ export class Account { } /** - * Returns the total amount of NEAR in the account, how much is used for storage, - * and how much is available + * @deprecated please use {@link getState} instead + * + * Returns basic NEAR account information via the `view_account` RPC query method + * @see [https://docs.near.org/api/rpc/contracts#view-account](https://docs.near.org/api/rpc/contracts#view-account) */ - async getDetailedNearBalance(): Promise { - const protocolConfig = await this.provider.experimental_protocolConfig({ - finality: DEFAULT_FINALITY, + async state(): Promise { + return this.provider.query({ + request_type: 'view_account', + account_id: this.accountId, + finality: 'optimistic' }); - const state = await this.provider.viewAccount(this.accountId) - - const costPerByte = BigInt( - protocolConfig.runtime_config.storage_amount_per_byte - ); - const usedOnStorage = BigInt(state.storage_usage) * costPerByte; - const locked = BigInt(state.locked); - const total = BigInt(state.amount) + locked; - const available = - total - (locked > usedOnStorage ? locked : usedOnStorage); - - return { total, usedOnStorage, locked, available }; } /** - * @deprecated please use {@link getDetailedNearBalance} instead + * @deprecated please use {@link getState} instead * - * Returns a - * */ async getAccountBalance(): Promise { const protocolConfig = await this.provider.experimental_protocolConfig({ finality: DEFAULT_FINALITY, }); - const state = await this.getState(); + const state = await this.state(); const costPerByte = BigInt( protocolConfig.runtime_config.storage_amount_per_byte @@ -1225,7 +1252,7 @@ export class Account { token: NearToken | FungibleToken ): Promise { if (token instanceof NearToken) { - const { amount } = await this.getState(); + const { amount } = await this.state(); return amount; } else if (token instanceof FungibleToken) { const { result } = await this.provider.callFunction( diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index 44ef661c6d..605127f562 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -25,62 +25,62 @@ afterAll(async () => { test('view pre-defined account works and returns correct name', async () => { const status = await workingAccount.getState(); - expect(status.code_hash).toEqual('11111111111111111111111111111111'); + expect(status.codeHash).toEqual('11111111111111111111111111111111'); }); test('create account and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE'; - const { amount } = await workingAccount.getState(); - const newAmount = BigInt(amount) / 10n; + const { balance: { total } } = await workingAccount.getState(); + const newAmount = total / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.getState(); - expect(state.amount).toEqual(newAmount); + expect(state.balance.total.toString()).toEqual(newAmount.toString()); }); test('create account with a secp256k1 key and then view account returns the created account', async () => { const newAccountName = generateUniqueString('test'); const newAccountPublicKey = 'secp256k1:45KcWwYt6MYRnnWFSxyQVkuu9suAzxoSkUMEnFNBi9kDayTo5YPUaqMWUrf7YHUDNMMj3w75vKuvfAMgfiFXBy28'; - const { amount } = await workingAccount.getState(); - const newAmount = BigInt(amount) / 10n; + const { balance: { total } } = await workingAccount.getState(); + const newAmount = total / 10n; await nearjs.accountCreator.masterAccount.createAccount(newAccountName, newAccountPublicKey, newAmount); const newAccount = new Account(newAccountName, nearjs.connection.provider, nearjs.connection.signer); const state = await newAccount.getState(); - expect(state.amount).toEqual(newAmount); + expect(state.balance.total.toString()).toEqual(newAmount.toString()); }); -test('Secp256k1 send money', async() => { +test('Secp256k1 send money', async () => { const sender = await createAccount(nearjs, KeyType.SECP256K1); const receiver = await createAccount(nearjs, KeyType.SECP256K1); - const { amount: receiverAmount } = await receiver.getState(); + const { balance: { total } } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); const state = await receiver.getState(); - expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); + expect(state.balance.total).toEqual(total + 10000n); }); -test('send money', async() => { +test('send money', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.getState(); + const { balance: { total } } = await receiver.getState(); await sender.sendMoney(receiver.accountId, 10000n); const state = await receiver.getState(); - expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); + expect(state.balance.total).toEqual(total + 10000n); }); -test('send money through signAndSendTransaction', async() => { +test('send money through signAndSendTransaction', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); - const { amount: receiverAmount } = await receiver.getState(); + const { balance: { total } } = await receiver.getState(); await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [actionCreators.transfer(10000n)], }); const state = await receiver.getState(); - expect(state.amount).toEqual(BigInt(receiverAmount) + 10000n); + expect(state.balance.total).toEqual(total + 10000n); }); -test('delete account', async() => { +test('delete account', async () => { const sender = await createAccount(nearjs); const receiver = await createAccount(nearjs); await sender.deleteAccount(receiver.accountId); @@ -100,7 +100,7 @@ test('multiple parallel transactions', async () => { })); }); -test('findAccessKey returns the same access key when fetched simultaneously', async() => { +test('findAccessKey returns the same access key when fetched simultaneously', async () => { const account = await createAccount(nearjs); const [key1, key2] = await Promise.all([ @@ -121,8 +121,8 @@ describe('errors', () => { log: (...args) => { logs.push(args.join(' ')); }, - warn: () => {}, - error: () => {}, + warn: () => { }, + error: () => { }, }; Logger.overrideLogger(custom); @@ -130,10 +130,9 @@ describe('errors', () => { beforeEach(async () => { logs = []; - }); - test('create existing account', async() => { + test('create existing account', async () => { await expect(workingAccount.createAccount(workingAccount.accountId, '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE', 100n)) .rejects.toThrow(/Can't create a new account .+, because it already exists/); }); @@ -160,8 +159,8 @@ describe('with deploy contract', () => { log: (...args) => { logs.push(args.join(' ')); }, - warn: () => {}, - error: () => {}, + warn: () => { }, + error: () => { }, }; Logger.overrideLogger(custom); @@ -188,7 +187,7 @@ describe('with deploy contract', () => { expect(logs[6]).toMatch(new RegExp(`^\\s+Log \\[${contractId}\\]: ABORT: expected to fail, filename: \\"assembly/index.ts" line: \\d+ col: \\d+$`)); }); - test('make function calls via account', async() => { + test('make function calls via account', async () => { const result = await workingAccount.viewFunction({ contractId, methodName: 'hello', // this is the function defined in hello.wasm file that we are calling @@ -209,7 +208,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue); }); - test('view contract state', async() => { + test('view contract state', async () => { const setCallValue = generateUniqueString('setCallPrefix'); await workingAccount.functionCall({ contractId, @@ -222,17 +221,17 @@ describe('with deploy contract', () => { expect(state).toEqual([['name', setCallValue]]); }); - test('make function calls via account with custom parser', async() => { + test('make function calls via account with custom parser', async () => { const result = await workingAccount.viewFunction({ contractId, - methodName:'hello', // this is the function defined in hello.wasm file that we are calling + methodName: 'hello', // this is the function defined in hello.wasm file that we are calling args: { name: 'trex' }, parse: x => JSON.parse(x.toString()).replace('trex', 'friend') }); expect(result).toEqual('hello friend'); }); - test('make function calls via contract', async() => { + test('make function calls via contract', async () => { const result = await contract.hello({ name: 'trex' }); expect(result).toEqual('hello trex'); @@ -242,7 +241,7 @@ describe('with deploy contract', () => { expect(await contract.getValue()).toEqual(setCallValue); }); - test('view function calls by block Id and finality', async() => { + test('view function calls by block Id and finality', async () => { const setCallValue1 = generateUniqueString('setCallPrefix'); const result1 = await contract.setValue({ args: { value: setCallValue1 } }); expect(result1).toEqual(setCallValue1); @@ -321,7 +320,7 @@ describe('with deploy contract', () => { })).toEqual(setCallValue2); }); - test('make function calls via contract with gas', async() => { + test('make function calls via contract with gas', async () => { const setCallValue = generateUniqueString('setCallPrefix'); const result2 = await contract.setValue({ args: { value: setCallValue }, @@ -374,7 +373,7 @@ describe('with deploy contract', () => { })).toEqual('hello world'); }); - test('make viewFunction call with object format', async() => { + test('make viewFunction call with object format', async () => { const result = await workingAccount.viewFunction({ contractId, methodName: 'hello', // this is the function defined in hello.wasm file that we are calling @@ -383,7 +382,7 @@ describe('with deploy contract', () => { expect(result).toEqual('hello trex'); }); - test('get total stake balance and validator responses', async() => { + test('get total stake balance and validator responses', async () => { const CUSTOM_ERROR = new TypedError('Querying failed: wasm execution failed with error: FunctionCallError(CompilationError(CodeDoesNotExist { account_id: AccountId("invalid_account_id") })).', 'UntypedError'); const mockConnection = { ...nearjs.connection, @@ -399,7 +398,7 @@ describe('with deploy contract', () => { num_produced_blocks: 7, num_produced_chunks: 18, public_key: 'ed25519:5QzHuNZ4stznMwf3xbDfYGUbjVt8w48q8hinDRmVx41z', - shards: [ 1 ], + shards: [1], stake: '73527610191458905577047103204' }, { @@ -410,7 +409,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '74531922534760985104659653178' }, { @@ -421,7 +420,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '0' }, ], @@ -471,7 +470,7 @@ describe('with deploy contract', () => { num_produced_blocks: 4, num_produced_chunks: 20, public_key: 'ed25519:9SYKubUbsGVfxrMGaJ9tLMEfPdjD55FLqGoqy3cTnRm6', - shards: [ 2 ], + shards: [2], stake: '0' }, ], @@ -500,7 +499,7 @@ describe('with deploy contract', () => { try { await account.getActiveDelegatedStakeBalance(); - } catch(e) { + } catch (e) { expect(e).toEqual(new Error(ERROR_MESSAGE)); } }); diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 371140c4b9..5f078e4067 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -38,7 +38,7 @@ import { PublicKey } from '@near-js/crypto'; /** @hidden */ export interface Provider { -/** @deprecated use {@link viewNodeStatus} */ + /** @deprecated use {@link viewNodeStatus} */ status(): Promise; getNetworkId(): Promise; From 5cabd6178abba137918185b9b20ff6fc693ad883 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Thu, 8 May 2025 17:53:43 +0200 Subject: [PATCH 45/57] feat: add signAndSendTransactions to make interface compatible with WS --- packages/accounts/src/account.ts | 295 ++++++++++++++++--------------- 1 file changed, 152 insertions(+), 143 deletions(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 7458f295fd..7541ef8a08 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -362,6 +362,23 @@ export class Account { return await this.provider.sendTransactionUntil(signedTx, waitUntil); } + async signAndSendTransactions({ + transactions, waitUntil = DEFAULT_WAIT_STATUS + }: { + transactions: { receiverId: string; actions: Action[] }[], waitUntil?: TxExecutionStatus + }): Promise { + if (!this.signer) throw new Error("Please set a signer"); + + const results = await Promise.all( + transactions.map(async ({ receiverId, actions }) => { + const signedTx = await this.createSignedTransaction(receiverId, actions); + return await this.provider.sendTransactionUntil(signedTx, waitUntil); + }) + ); + + return results + } + /** * Creates an account of the form ., e.g. ana.testnet or ana.near * @@ -593,6 +610,141 @@ export class Account { ); } + public async getBalance( + token: NearToken | FungibleToken + ): Promise { + if (token instanceof NearToken) { + const { amount } = await this.state(); + return amount; + } else if (token instanceof FungibleToken) { + const { result } = await this.provider.callFunction( + token.contractId, + "ft_balance_of", + { account_id: this.accountId } + ); + return BigInt(result); + } else { + throw new Error(`Invalid token`); + } + } + + /** + * Transfers a token to the specified receiver. + * + * Supports sending either the native NEAR token or any supported Fungible Token (FT). + * + * @param token + * @param amount - The amount of tokens to transfer (in the smallest units) + * @param receiverId + */ + public async transferToken( + token: NearToken | FungibleToken, + amount: bigint | string | number, + receiverId: string + ): Promise { + if (token instanceof NearToken) { + return this.signAndSendTransaction({ + receiverId, + actions: [transfer(BigInt(amount))], + }); + } else if (token instanceof FungibleToken) { + return this.callFunction({ + contractId: token.contractId, + methodName: "ft_transfer", + args: { + amount: amount.toString(), + receiver_id: receiverId, + }, + deposit: 1, + }); + } else { + throw new Error(`Invalid token`); + } + } + + /** + * Transfers a Fungible Token to a receiver with a callback message. + * + * Only works with Fungible Tokens that implement the NEP-141 standard. + * + * The {@link message} will be forwarded to the receiver's contract + * and typically triggers further logic on their side. + * + * @param token + * @param amount - The amount of tokens to transfer (in the smallest units) + * @param receiverId + * @param message + */ + public async transferTokenWithCallback( + token: FungibleToken, + amount: bigint | string | number, + receiverId: string, + message: string + ): Promise { + return this.callFunction({ + contractId: token.contractId, + methodName: "ft_transfer_call", + args: { + amount: amount.toString(), + receiver_id: receiverId, + msg: message, + }, + deposit: 1, + }); + } + + /** + * Registers an account in the token contract's state. + * + * Creates a record for the specified account with an initial balance of zero, allowing it + * to send and receive tokens. If the account is already registered, the function performs + * no state changes and returns the attached deposit back to the caller. + */ + public async registerTokenAccount( + token: FungibleToken, + accountId?: string + ): Promise { + accountId = accountId ?? this.accountId; + + const { result } = await this.provider.callFunction( + token.contractId, + "storage_balance_bounds", + {} + ); + + const requiredDeposit = result.min as string; + + return this.callFunction({ + contractId: token.contractId, + methodName: "storage_deposit", + args: { + account_id: accountId, + registration_only: true, + }, + deposit: requiredDeposit, + }); + } + + /** + * Unregisters an account from the token contract's state. + * + * Removes the account's record from the contract, effectively disabling it from sending + * or receiving tokens. This operation requires the account's token balance to be exactly zero; + * otherwise, the function will panic. + */ + public async unregisterTokenAccount( + token: FungibleToken + ): Promise { + return this.callFunction({ + contractId: token.contractId, + methodName: "storage_unregister", + args: { + force: false, + }, + deposit: "1", + }); + } + // DEPRECATED FUNCTIONS BELLOW - Please remove in next release /** @@ -1247,147 +1399,4 @@ export class Account { total: summary.total.toString(), }; } - - public async getTokenBalance( - token: NearToken | FungibleToken - ): Promise { - if (token instanceof NearToken) { - const { amount } = await this.state(); - return amount; - } else if (token instanceof FungibleToken) { - const { result } = await this.provider.callFunction( - token.contractId, - "ft_balance_of", - { account_id: this.accountId } - ); - return BigInt(result); - } else { - throw new Error(`Invalid token`); - } - } - - public async getFormattedTokenBalance( - token: NearToken | FungibleToken - ): Promise { - const balance = await this.getTokenBalance(token); - - return token.toAmount(balance); - } - - /** - * Transfers a token to the specified receiver. - * - * Supports sending either the native NEAR token or any supported Fungible Token (FT). - * - * @param token - * @param amount - The amount of tokens to transfer (in the smallest units) - * @param receiverId - */ - public async transferToken( - token: NearToken | FungibleToken, - amount: bigint | string | number, - receiverId: string - ): Promise { - if (token instanceof NearToken) { - return this.signAndSendTransaction({ - receiverId, - actions: [transfer(BigInt(amount))], - }); - } else if (token instanceof FungibleToken) { - return this.callFunction({ - contractId: token.contractId, - methodName: "ft_transfer", - args: { - amount: amount.toString(), - receiver_id: receiverId, - }, - deposit: 1, - }); - } else { - throw new Error(`Invalid token`); - } - } - - /** - * Transfers a Fungible Token to a receiver with a callback message. - * - * Only works with Fungible Tokens that implement the NEP-141 standard. - * - * The {@link message} will be forwarded to the receiver's contract - * and typically triggers further logic on their side. - * - * @param token - * @param amount - The amount of tokens to transfer (in the smallest units) - * @param receiverId - * @param message - */ - public async transferTokenWithCallback( - token: FungibleToken, - amount: bigint | string | number, - receiverId: string, - message: string - ): Promise { - return this.callFunction({ - contractId: token.contractId, - methodName: "ft_transfer_call", - args: { - amount: amount.toString(), - receiver_id: receiverId, - msg: message, - }, - deposit: 1, - }); - } - - /** - * Registers an account in the token contract's state. - * - * Creates a record for the specified account with an initial balance of zero, allowing it - * to send and receive tokens. If the account is already registered, the function performs - * no state changes and returns the attached deposit back to the caller. - */ - public async registerTokenAccount( - token: FungibleToken, - accountId?: string - ): Promise { - accountId = accountId ?? this.accountId; - - const { result } = await this.provider.callFunction( - token.contractId, - "storage_balance_bounds", - {} - ); - - const requiredDeposit = result.min as string; - - return this.callFunction({ - contractId: token.contractId, - methodName: "storage_deposit", - args: { - account_id: accountId, - registration_only: true, - }, - deposit: requiredDeposit, - }); - } - - /** - * Unregisters an account from the token contract's state. - * - * Removes the account's record from the contract, effectively disabling it from sending - * or receiving tokens. This operation requires the account's token balance to be exactly zero; - * otherwise, the function will panic. - */ - public async unregisterTokenAccount( - token: FungibleToken - ): Promise { - return this.callFunction({ - contractId: token.contractId, - methodName: "storage_unregister", - args: { - force: false, - }, - deposit: "1", - }); - } } From c3b32df717e591b51791a633b29881c69495b39b Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Fri, 9 May 2025 00:34:17 +0200 Subject: [PATCH 46/57] feat: refactor tokens package --- packages/accounts/src/account.ts | 174 +++++--------------- packages/tokens/package.json | 21 +-- packages/tokens/src/{ => ft}/format.ts | 0 packages/tokens/src/ft/index.ts | 163 ++++++++++++++++++ packages/tokens/src/ft/mainnet/index.ts | 30 ++++ packages/tokens/src/ft/testnet/index.ts | 30 ++++ packages/tokens/src/fungible/index.ts | 11 -- packages/tokens/src/index.ts | 8 +- packages/tokens/src/native/index.ts | 11 -- packages/tokens/src/nft/index.ts | 77 +++++++++ packages/tokens/src/token.ts | 24 --- packages/tokens/src/usdc/mainnet.ts | 13 -- packages/tokens/src/usdc/testnet.ts | 10 -- packages/tokens/src/usdt/mainnet.ts | 10 -- packages/tokens/src/usdt/testnet.ts | 10 -- packages/tokens/test/fungible_token.test.ts | 4 +- packages/tokens/test/native_token.test.ts | 4 +- packages/tokens/tsconfig.json | 2 +- packages/types/src/accounts.ts | 16 ++ packages/types/src/index.ts | 1 + 20 files changed, 376 insertions(+), 243 deletions(-) rename packages/tokens/src/{ => ft}/format.ts (100%) create mode 100644 packages/tokens/src/ft/index.ts create mode 100644 packages/tokens/src/ft/mainnet/index.ts create mode 100644 packages/tokens/src/ft/testnet/index.ts delete mode 100644 packages/tokens/src/fungible/index.ts delete mode 100644 packages/tokens/src/native/index.ts create mode 100644 packages/tokens/src/nft/index.ts delete mode 100644 packages/tokens/src/token.ts delete mode 100644 packages/tokens/src/usdc/mainnet.ts delete mode 100644 packages/tokens/src/usdc/testnet.ts delete mode 100644 packages/tokens/src/usdt/mainnet.ts delete mode 100644 packages/tokens/src/usdt/testnet.ts create mode 100644 packages/types/src/accounts.ts diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 7541ef8a08..a0b3279569 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -33,6 +33,7 @@ import { baseEncode, parseResultError, printTxOutcomeLogsAndFailures, + getTransactionLastResult, } from "@near-js/utils"; import { SignedMessage, Signer } from "@near-js/signers"; @@ -42,7 +43,9 @@ import { ChangeFunctionCallOptions, ViewFunctionCallOptions, } from "./interface"; -import { NearToken, FungibleToken } from "@near-js/tokens"; + +import { NEAR } from "@near-js/tokens"; +import type { NativeToken, FungibleToken } from "@near-js/tokens"; const { addKey, @@ -400,15 +403,19 @@ export class Account { } const TLA = splitted[1]; - return await this.callFunction({ - contractId: TLA, - methodName: "create_account", - args: { - new_account_id: newAccountId, - new_public_key: publicKey.toString(), - }, - gas: BigInt("60000000000000"), - deposit: BigInt(amountToTransfer), + return this.signAndSendTransaction({ + receiverId: TLA, + actions: [ + functionCall( + "create_account", + { + new_account_id: newAccountId, + new_public_key: publicKey.toString(), + }, + BigInt("60000000000000"), + BigInt(amountToTransfer) + ), + ], }); } @@ -572,13 +579,15 @@ export class Account { args: Uint8Array | Record; deposit?: bigint | string | number; gas?: bigint | string | number; - }): Promise { - return this.signAndSendTransaction({ + }): Promise { + const result = await this.signAndSendTransaction({ receiverId: contractId, actions: [ functionCall(methodName, args, BigInt(gas), BigInt(deposit)), ], }); + + return getTransactionLastResult(result); } /** @@ -610,22 +619,15 @@ export class Account { ); } + /** + * + * @param token The token to check the balance of. Defaults to Native NEAR. + * @returns The available balance of the account in units (e.g. yoctoNEAR). + */ public async getBalance( - token: NearToken | FungibleToken + token: NativeToken | FungibleToken = NEAR ): Promise { - if (token instanceof NearToken) { - const { amount } = await this.state(); - return amount; - } else if (token instanceof FungibleToken) { - const { result } = await this.provider.callFunction( - token.contractId, - "ft_balance_of", - { account_id: this.accountId } - ); - return BigInt(result); - } else { - throw new Error(`Invalid token`); - } + return token.getBalance(this); } /** @@ -633,117 +635,25 @@ export class Account { * * Supports sending either the native NEAR token or any supported Fungible Token (FT). * - * @param token - * @param amount - The amount of tokens to transfer (in the smallest units) - * @param receiverId + * @param amount - The amount of tokens to transfer in units (e.g. yoctoNEAR). + * @param receiverId - The NEAR account ID of the receiver. + * @param token - The token to transfer. Defaults to Native NEAR. + * */ - public async transferToken( - token: NearToken | FungibleToken, - amount: bigint | string | number, - receiverId: string - ): Promise { - if (token instanceof NearToken) { - return this.signAndSendTransaction({ - receiverId, - actions: [transfer(BigInt(amount))], - }); - } else if (token instanceof FungibleToken) { - return this.callFunction({ - contractId: token.contractId, - methodName: "ft_transfer", - args: { - amount: amount.toString(), - receiver_id: receiverId, - }, - deposit: 1, - }); - } else { - throw new Error(`Invalid token`); + public async transfer( + { + receiverId, + amount, + token = NEAR + }: { + receiverId: string; + amount: bigint | string | number; + token?: NativeToken | FungibleToken; } - } - - /** - * Transfers a Fungible Token to a receiver with a callback message. - * - * Only works with Fungible Tokens that implement the NEP-141 standard. - * - * The {@link message} will be forwarded to the receiver's contract - * and typically triggers further logic on their side. - * - * @param token - * @param amount - The amount of tokens to transfer (in the smallest units) - * @param receiverId - * @param message - */ - public async transferTokenWithCallback( - token: FungibleToken, - amount: bigint | string | number, - receiverId: string, - message: string ): Promise { - return this.callFunction({ - contractId: token.contractId, - methodName: "ft_transfer_call", - args: { - amount: amount.toString(), - receiver_id: receiverId, - msg: message, - }, - deposit: 1, - }); + return token.transfer({ from: this, receiverId, amount }); } - /** - * Registers an account in the token contract's state. - * - * Creates a record for the specified account with an initial balance of zero, allowing it - * to send and receive tokens. If the account is already registered, the function performs - * no state changes and returns the attached deposit back to the caller. - */ - public async registerTokenAccount( - token: FungibleToken, - accountId?: string - ): Promise { - accountId = accountId ?? this.accountId; - - const { result } = await this.provider.callFunction( - token.contractId, - "storage_balance_bounds", - {} - ); - - const requiredDeposit = result.min as string; - - return this.callFunction({ - contractId: token.contractId, - methodName: "storage_deposit", - args: { - account_id: accountId, - registration_only: true, - }, - deposit: requiredDeposit, - }); - } - - /** - * Unregisters an account from the token contract's state. - * - * Removes the account's record from the contract, effectively disabling it from sending - * or receiving tokens. This operation requires the account's token balance to be exactly zero; - * otherwise, the function will panic. - */ - public async unregisterTokenAccount( - token: FungibleToken - ): Promise { - return this.callFunction({ - contractId: token.contractId, - methodName: "storage_unregister", - args: { - force: false, - }, - deposit: "1", - }); - } // DEPRECATED FUNCTIONS BELLOW - Please remove in next release diff --git a/packages/tokens/package.json b/packages/tokens/package.json index 56a049ebfa..d6db34879d 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -19,6 +19,7 @@ "dependencies": {}, "devDependencies": { "@jest/globals": "^29.7.0", + "@near-js/types": "workspace:*", "@types/node": "20.0.0", "build": "workspace:*", "jest": "29.7.0", @@ -36,17 +37,17 @@ "types": "./lib/esm/index.d.ts", "default": "./lib/esm/index.js" }, - "./usdt/*": { - "require": "./lib/commonjs/usdt/*.cjs", - "import": "./lib/commonjs/usdt/*.cjs", - "types": "./lib/esm/usdt/*.d.ts", - "default": "./lib/esm/usdt/*.js" + "./ft/mainnet": { + "require": "./lib/commonjs/mainnet/*.cjs", + "import": "./lib/commonjs/mainnet/*.cjs", + "types": "./lib/esm/mainnet/*.d.ts", + "default": "./lib/esm/mainnet/*.js" }, - "./usdc/*": { - "require": "./lib/commonjs/usdc/*.cjs", - "import": "./lib/commonjs/usdc/*.cjs", - "types": "./lib/esm/usdc/*.d.ts", - "default": "./lib/esm/usdc/*.js" + "./ft/testnet*": { + "require": "./lib/commonjs/testnet/*.cjs", + "import": "./lib/commonjs/testnet/*.cjs", + "types": "./lib/esm/testnet/*.d.ts", + "default": "./lib/esm/testnet/*.js" } } } diff --git a/packages/tokens/src/format.ts b/packages/tokens/src/ft/format.ts similarity index 100% rename from packages/tokens/src/format.ts rename to packages/tokens/src/ft/format.ts diff --git a/packages/tokens/src/ft/index.ts b/packages/tokens/src/ft/index.ts new file mode 100644 index 0000000000..981dd898fa --- /dev/null +++ b/packages/tokens/src/ft/index.ts @@ -0,0 +1,163 @@ +import { formatAmount, parseAmount } from "./format"; +import type { AccountLike } from "@near-js/types"; + +interface FTMetadata { + spec?: string; + name: string; + decimals: number; + symbol: string; + icon?: string; +} + +abstract class BaseFT { + public readonly metadata: FTMetadata; + public readonly native: boolean; + + constructor(metadata: FTMetadata) { + this.metadata = metadata; + } + + public toUnits(amount: string | number): bigint { + const units = parseAmount(amount.toString(), this.metadata.decimals); + return BigInt(units); + } + + public toAmount(units: bigint | string | number): string { + return formatAmount(units, this.metadata.decimals); + } + + abstract getBalance(account: AccountLike): Promise; + abstract transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise +} + +export class NativeToken extends BaseFT { + public readonly native: boolean = true; + + constructor(metadata: FTMetadata) { + super(metadata); + } + + public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { + return from.signAndSendTransaction({ + receiverId, + actions: [{ enum: "Transfer", transfer: { deposit: amount.toString() } }], + }); + } + + public async getBalance(account: AccountLike): Promise { + const { balance: { available } } = await account.getState(); + return available; + } +} + +export class FungibleToken extends BaseFT { + public readonly native: boolean = false; + public readonly accountId: string; + + constructor(accountId: string, metadata: FTMetadata) { + metadata.spec = metadata.spec || "ft-1.0.0"; + super(metadata); + this.accountId = accountId; + } + + /** + * + * @param param + * @param param.accountId The Account that will transfer the tokens + * @param param.receiverId The AccountID that will receive the tokens + * @param param.amount The amount of tokens to transfer in the smallest unit + * @returns + */ + public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: "ft_transfer", + args: { + amount: amount.toString(), + receiver_id: receiverId, + }, + gas: "30000000000000", + deposit: 1, + }); + } + + /** + * Get the balance of an account in units + * + * @param account The account to get the balance of + * @returns + */ + public async getBalance(account: AccountLike): Promise { + const { balance } = await account.provider.callFunction( + this.accountId, + "ft_balance_of", + { + account_id: account.accountId, + }, + ); + return BigInt(balance); + } + + /** + * Transfer tokens and call a function on the receiver contract, + * only works if the receiver implements the `ft_on_transfer` method + * + * @param param + * @param param.from The Account that will transfer the tokens + * @param param.receiverId The AccountID that will receive the tokens + * @param param.amount The amount of tokens to transfer in the smallest unit + * @param param.msg The message to send to the `ft_on_transfer` method + */ + public async transferCall({ from, receiverId, amount, msg }: { from: AccountLike, receiverId: string, amount: bigint, msg: string }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: "ft_transfer_call", + args: { + receiver_id: receiverId, + amount: amount.toString(), + msg, + }, + gas: "30000000000000", + deposit: 1, + }); + } + + public async registerAccount({ accountIdToRegister, fundingAccount }: { accountIdToRegister: AccountLike, fundingAccount: AccountLike }): Promise { + + const { result } = await fundingAccount.provider.callFunction( + this.accountId, + "storage_balance_bounds", + {} + ); + + const requiredDeposit = result.min as string; + + return fundingAccount.callFunction({ + contractId: this.accountId, + methodName: "storage_deposit", + args: { + account_id: accountIdToRegister, + registration_only: true, + }, + gas: "30000000000000", + deposit: requiredDeposit, + }); + } + + public async unregisterAccount({ account, force = false }: { account: AccountLike, force: boolean }): Promise { + return account.callFunction({ + contractId: this.accountId, + methodName: "storage_unregister", + args: { force }, + gas: "30000000000000", + deposit: 1, + }); + } +} + +export const NEAR = new NativeToken({ + name: 'NEAR', + decimals: 24, + symbol: 'NEAR', + icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" +}); \ No newline at end of file diff --git a/packages/tokens/src/ft/mainnet/index.ts b/packages/tokens/src/ft/mainnet/index.ts new file mode 100644 index 0000000000..0be8af90d8 --- /dev/null +++ b/packages/tokens/src/ft/mainnet/index.ts @@ -0,0 +1,30 @@ +import { FungibleToken } from "../.."; + +export const wNEAR = new FungibleToken( + 'wrap.near', + { + name: 'Wrapped NEAR fungible token', + decimals: 24, + symbol: 'wNEAR', + icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" +}); + +export const USDC = new FungibleToken( + '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', + { + name: 'USDC', + decimals: 6, + symbol: 'USDC', + icon: "data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A" + } +); + +export const USDT = new FungibleToken( + 'usdt.tether-token.near', + { + name: 'Tether USD', + decimals: 6, + symbol: 'USDt', + icon: "data:image/svg+xml,%3Csvg width='111' height='90' viewBox='0 0 111 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z' fill='%23009393'/%3E%3C/svg%3E" + } +) \ No newline at end of file diff --git a/packages/tokens/src/ft/testnet/index.ts b/packages/tokens/src/ft/testnet/index.ts new file mode 100644 index 0000000000..eceaf78774 --- /dev/null +++ b/packages/tokens/src/ft/testnet/index.ts @@ -0,0 +1,30 @@ +import { FungibleToken } from "../.."; + +export const wNEAR = new FungibleToken( + 'wrap.testnet', + { + name: 'Wrapped NEAR fungible token', + decimals: 24, + symbol: 'wNEAR', + icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" +}); + +export const USDC = new FungibleToken( + 'usdc.fakes.testnet', + { + name: 'USDC', + decimals: 6, + symbol: 'USDC', + icon: "data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A" + } +); + +export const USDT = new FungibleToken( + 'usdt.fakes.testnet', + { + name: 'Tether USD', + decimals: 6, + symbol: 'USDt', + icon: "data:image/svg+xml,%3Csvg width='111' height='90' viewBox='0 0 111 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z' fill='%23009393'/%3E%3C/svg%3E" + } +) \ No newline at end of file diff --git a/packages/tokens/src/fungible/index.ts b/packages/tokens/src/fungible/index.ts deleted file mode 100644 index 7d014bacd0..0000000000 --- a/packages/tokens/src/fungible/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Token, TokenMetadata } from '../token'; - -/** Fungible Token deployed as a contract */ -export class FungibleToken extends Token { - public readonly contractId: string; - - constructor(contractId: string, metadata: TokenMetadata) { - super(metadata); - this.contractId = contractId; - } -} diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts index a4543c0fbc..b8b00932f8 100644 --- a/packages/tokens/src/index.ts +++ b/packages/tokens/src/index.ts @@ -1,6 +1,2 @@ -export { Token, TokenMetadata } from "./token"; - -export { NearToken } from "./native"; -export { FungibleToken } from "./fungible"; - -export { formatAmount, parseAmount } from './format'; \ No newline at end of file +export { NEAR, NativeToken, FungibleToken } from "./ft" +export { NonFungibleToken, NFTContract } from "./nft" \ No newline at end of file diff --git a/packages/tokens/src/native/index.ts b/packages/tokens/src/native/index.ts deleted file mode 100644 index 4b3e96479b..0000000000 --- a/packages/tokens/src/native/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Token } from '../token'; - -/** Native Near Token */ -export class NearToken extends Token { - constructor() { - super({ - decimals: 24, - symbol: 'NEAR', - }); - } -} diff --git a/packages/tokens/src/nft/index.ts b/packages/tokens/src/nft/index.ts new file mode 100644 index 0000000000..722c4dd172 --- /dev/null +++ b/packages/tokens/src/nft/index.ts @@ -0,0 +1,77 @@ +import type { AccountLike } from "@near-js/types"; + +interface ContractMetadata { + spec?: string; + name: string; + symbol: string; + icon?: string; + baseUri?: string; + reference?: string; + referenceHash?: string; +} + +interface NFTMetadata { + title?: string; + description?: string; + media?: string; + mediaHash?: string; + copies?: number; + issuedAt?: string; + expiresAt?: string; + startsAt?: string; + updatedAt?: string; + extra?: string; + reference?: string; + referenceHash?: string; +} + +export class NFTContract { + public readonly metadata: ContractMetadata; + public readonly accountId: string; + + constructor(accountId: string, metadata: ContractMetadata) { + metadata.spec = metadata.spec || "nft-1.0.0"; + this.metadata = metadata; + this.accountId = accountId; + } + + transfer({ + from, + receiverId, + tokenId + }: { + from: AccountLike; + receiverId: string; + tokenId: string; + }): Promise { + return from.callFunction({ + contractId: this.accountId, + methodName: "nft_transfer", + args: { + receiver_id: receiverId, + token_id: tokenId + }, + deposit: 1, + gas: 30000000000000 + }); + } +} + +export class NonFungibleToken { + public readonly contractId: string; + public readonly tokenId: string; + public readonly ownerId: string; + public readonly metadata: NFTMetadata; + + constructor( + contractId: string, + tokenId: string, + ownerId: string, + metadata: NFTMetadata + ) { + this.contractId = contractId; + this.tokenId = tokenId; + this.ownerId = ownerId; + this.metadata = metadata; + } +} \ No newline at end of file diff --git a/packages/tokens/src/token.ts b/packages/tokens/src/token.ts deleted file mode 100644 index f7429acb06..0000000000 --- a/packages/tokens/src/token.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { formatAmount, parseAmount } from "./format"; - -export interface TokenMetadata { - decimals: number; - symbol: string; -} - -export abstract class Token { - public readonly metadata: TokenMetadata; - - constructor(metadata: TokenMetadata) { - this.metadata = metadata; - } - - public toUnits(amount: string | number): bigint { - const units = parseAmount(amount.toString(), this.metadata.decimals); - - return BigInt(units); - } - - public toAmount(units: bigint | string | number): string { - return formatAmount(units, this.metadata.decimals); - } -} diff --git a/packages/tokens/src/usdc/mainnet.ts b/packages/tokens/src/usdc/mainnet.ts deleted file mode 100644 index dbec734cac..0000000000 --- a/packages/tokens/src/usdc/mainnet.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { FungibleToken } from '../fungible'; - -export class USDC extends FungibleToken { - constructor() { - super( - '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', - { - decimals: 6, - symbol: 'USDC', - } - ); - } -} diff --git a/packages/tokens/src/usdc/testnet.ts b/packages/tokens/src/usdc/testnet.ts deleted file mode 100644 index b916a88aa4..0000000000 --- a/packages/tokens/src/usdc/testnet.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FungibleToken } from '../fungible'; - -export class USDC extends FungibleToken { - constructor() { - super('usdc.fakes.testnet', { - decimals: 6, - symbol: 'USDC', - }); - } -} diff --git a/packages/tokens/src/usdt/mainnet.ts b/packages/tokens/src/usdt/mainnet.ts deleted file mode 100644 index 97ec97c216..0000000000 --- a/packages/tokens/src/usdt/mainnet.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FungibleToken } from '../fungible'; - -export class USDT extends FungibleToken { - constructor() { - super('dac17f958d2ee523a2206206994597c13d831ec7.factory.bridge.near', { - decimals: 6, - symbol: 'USDT', - }); - } -} diff --git a/packages/tokens/src/usdt/testnet.ts b/packages/tokens/src/usdt/testnet.ts deleted file mode 100644 index 4086549f9f..0000000000 --- a/packages/tokens/src/usdt/testnet.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { FungibleToken } from '../fungible'; - -export class USDT extends FungibleToken { - constructor() { - super('usdt.fakes.testnet', { - decimals: 6, - symbol: 'USDT', - }); - } -} diff --git a/packages/tokens/test/fungible_token.test.ts b/packages/tokens/test/fungible_token.test.ts index f95156812c..155866e593 100644 --- a/packages/tokens/test/fungible_token.test.ts +++ b/packages/tokens/test/fungible_token.test.ts @@ -1,10 +1,10 @@ import { expect, test } from '@jest/globals'; import { FungibleToken } from '../src'; -const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST' }); +const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST', name: "Test Token" }); test('test props are accessible', () => { - expect(FT.contractId).toBe('ft.testnet'); + expect(FT.accountId).toBe('ft.testnet'); expect(FT.metadata.decimals).toBe(6); expect(FT.metadata.symbol).toBe('TEST'); diff --git a/packages/tokens/test/native_token.test.ts b/packages/tokens/test/native_token.test.ts index b45dbc2f3c..17829a97c3 100644 --- a/packages/tokens/test/native_token.test.ts +++ b/packages/tokens/test/native_token.test.ts @@ -1,7 +1,5 @@ import { expect, test } from '@jest/globals'; -import { NearToken } from '../src'; - -const NEAR = new NearToken(); +import { NEAR } from '../src'; test('test toUnits parses formatted amount', () => { expect(NEAR.toUnits('1.234')).toBe(BigInt('1234000000000000000000000')); diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json index e61b0d4fd9..7ddf377ab4 100644 --- a/packages/tokens/tsconfig.json +++ b/packages/tokens/tsconfig.json @@ -4,5 +4,5 @@ "outDir": "./lib/esm" }, "files": ["src/index.ts"], - "include": ["src/usdt", "src/usdc"] + "include": ["src/ft", "src/nft"] } diff --git a/packages/types/src/accounts.ts b/packages/types/src/accounts.ts new file mode 100644 index 0000000000..d4bf7391dd --- /dev/null +++ b/packages/types/src/accounts.ts @@ -0,0 +1,16 @@ +// TODO: Move Account and Provider to types +interface ProviderLike { + callFunction: (contractId, methodName, args, blockQuery?) => Promise; +} + +export interface AccountLike { + accountId: string; + provider: ProviderLike; + getState(): Promise; + signAndSendTransaction({ + receiverId, actions + }): Promise; + callFunction({ + contractId, methodName, args, gas, deposit + }): Promise; +} \ No newline at end of file diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index b8f27f715c..61040b35ce 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,3 +1,4 @@ +export * from './accounts'; export * from './assignable'; export * from './enum'; export * from './errors'; From af140411e32c360ad49ec7a01de6844969fbd96f Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Fri, 9 May 2025 18:02:06 +0200 Subject: [PATCH 47/57] fix: multiple small fixes --- package.json | 8 +++ packages/accounts/src/account.ts | 37 ++++++---- packages/accounts/tsconfig.json | 2 +- packages/tokens/package.json | 24 ++++--- packages/tokens/src/ft/index.ts | 70 +++++++++++++------ packages/tokens/src/index.ts | 4 +- packages/tokens/src/{ft => }/mainnet/index.ts | 2 +- packages/tokens/src/{ft => }/testnet/index.ts | 2 +- packages/tokens/test/fungible_token.test.ts | 70 +++++++++---------- packages/tokens/test/native_token.test.ts | 64 ++++++++--------- packages/tokens/tsconfig.cjs.json | 3 +- packages/tokens/tsconfig.json | 5 +- 12 files changed, 172 insertions(+), 119 deletions(-) rename packages/tokens/src/{ft => }/mainnet/index.ts (99%) rename packages/tokens/src/{ft => }/testnet/index.ts (99%) diff --git a/package.json b/package.json index 4a80d4c8f2..ef2b4e8249 100644 --- a/package.json +++ b/package.json @@ -37,5 +37,13 @@ "resolutions": { "near-sandbox": "0.0.18", "near-api-js": "workspace:*" + }, + "pnpm": { + "overrides": { + "packages": "link:packages" + } + }, + "dependencies": { + "packages": "link:packages" } } diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index a0b3279569..e020d5f338 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -345,7 +345,7 @@ export class Account { * * @param receiverId The NEAR account ID of the transaction receiver. * @param actions The list of actions to be performed in the transaction. - * @param returnError Whether to return an error if the transaction fails. + * @param throwOnFailure Whether to throw an error if the transaction fails. * @returns {Promise} A promise that resolves to the final execution outcome of the transaction. * */ @@ -353,29 +353,42 @@ export class Account { receiverId, actions, waitUntil = DEFAULT_WAIT_STATUS, + throwOnFailure = true, }: { receiverId: string; actions: Action[]; waitUntil?: TxExecutionStatus; + throwOnFailure?: boolean; }): Promise { const signedTx = await this.createSignedTransaction( receiverId, actions ); - return await this.provider.sendTransactionUntil(signedTx, waitUntil); + + const result = await this.provider.sendTransactionUntil(signedTx, waitUntil); + + if (throwOnFailure && typeof result.status === 'object' && typeof result.status.Failure === 'object' && result.status.Failure !== null) { + throw parseResultError(result); + } + + return result } async signAndSendTransactions({ - transactions, waitUntil = DEFAULT_WAIT_STATUS + transactions, waitUntil = DEFAULT_WAIT_STATUS, throwOnFailure = true }: { - transactions: { receiverId: string; actions: Action[] }[], waitUntil?: TxExecutionStatus + transactions: { receiverId: string; actions: Action[] }[], waitUntil?: TxExecutionStatus, throwOnFailure?: boolean }): Promise { if (!this.signer) throw new Error("Please set a signer"); const results = await Promise.all( transactions.map(async ({ receiverId, actions }) => { - const signedTx = await this.createSignedTransaction(receiverId, actions); - return await this.provider.sendTransactionUntil(signedTx, waitUntil); + return this.signAndSendTransaction({ + receiverId, + actions, + waitUntil, + throwOnFailure + }) }) ); @@ -387,13 +400,13 @@ export class Account { * * @param newAccountId the new account to create (e.g. ana.near) * @param publicKey the public part of the key that will control the account - * @param amountToTransfer how much NEAR to transfer to the account + * @param nearToTransfer how much NEAR to transfer to the account in yoctoNEAR * */ public async createTopLevelAccount( newAccountId: string, publicKey: PublicKey | string, - amountToTransfer: bigint | string | number + nearToTransfer: bigint | string | number ): Promise { const splitted = newAccountId.split("."); if (splitted.length != 2) { @@ -413,7 +426,7 @@ export class Account { new_public_key: publicKey.toString(), }, BigInt("60000000000000"), - BigInt(amountToTransfer) + BigInt(nearToTransfer) ), ], }); @@ -425,13 +438,13 @@ export class Account { * * @param accountOrPrefix a prefix (e.g. `sub`) or the full sub-account (`sub.ana.near`) * @param publicKey the public part of the key that will control the account - * @param amountToTransfer how much NEAR to transfer to the account + * @param nearToTransfer how much NEAR to transfer to the account * */ public async createSubAccount( accountOrPrefix: string, publicKey: PublicKey | string, - amountToTransfer: bigint | string | number + nearToTransfer: bigint | string | number ): Promise { if (!this.signer) throw new Error("Please set a signer"); @@ -449,7 +462,7 @@ export class Account { const actions = [ createAccount(), - transfer(BigInt(amountToTransfer)), + transfer(BigInt(nearToTransfer)), addKey(PublicKey.from(publicKey), fullAccessKey()), ]; diff --git a/packages/accounts/tsconfig.json b/packages/accounts/tsconfig.json index a353a73d9f..e641ec4821 100644 --- a/packages/accounts/tsconfig.json +++ b/packages/accounts/tsconfig.json @@ -6,5 +6,5 @@ }, "files": [ "src/index.ts" - ] + ], } diff --git a/packages/tokens/package.json b/packages/tokens/package.json index d6db34879d..37e9aeaec0 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -16,7 +16,9 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": {}, + "dependencies": { + "@near-js/utils": "workspace:*" + }, "devDependencies": { "@jest/globals": "^29.7.0", "@near-js/types": "workspace:*", @@ -37,17 +39,17 @@ "types": "./lib/esm/index.d.ts", "default": "./lib/esm/index.js" }, - "./ft/mainnet": { - "require": "./lib/commonjs/mainnet/*.cjs", - "import": "./lib/commonjs/mainnet/*.cjs", - "types": "./lib/esm/mainnet/*.d.ts", - "default": "./lib/esm/mainnet/*.js" + "./mainnet": { + "require": "./lib/commonjs/mainnet/index.cjs", + "import": "./lib/commonjs/mainnet/index.cjs", + "types": "./lib/esm/mainnet/index.d.ts", + "default": "./lib/esm/mainnet/index.js" }, - "./ft/testnet*": { - "require": "./lib/commonjs/testnet/*.cjs", - "import": "./lib/commonjs/testnet/*.cjs", - "types": "./lib/esm/testnet/*.d.ts", - "default": "./lib/esm/testnet/*.js" + "./testnet": { + "require": "./lib/commonjs/testnet/index.cjs", + "import": "./lib/commonjs/testnet/index.cjs", + "types": "./lib/esm/testnet/index.d.ts", + "default": "./lib/esm/testnet/index.js" } } } diff --git a/packages/tokens/src/ft/index.ts b/packages/tokens/src/ft/index.ts index 981dd898fa..ec0d4386d3 100644 --- a/packages/tokens/src/ft/index.ts +++ b/packages/tokens/src/ft/index.ts @@ -17,16 +17,43 @@ abstract class BaseFT { this.metadata = metadata; } + /** + * Converts a decimal number to indivisible units + * + * @param amount The amount in decimal format (e.g. "1.234") + * @returns The amount in indivisible units (e.g. "1234") + */ public toUnits(amount: string | number): bigint { const units = parseAmount(amount.toString(), this.metadata.decimals); return BigInt(units); } - public toAmount(units: bigint | string | number): string { - return formatAmount(units, this.metadata.decimals); + /** + * Converts indivisible units to a decimal number (represented as a string) + * + * @param units The amount in indivisible units (e.g. "1234") + * @returns The amount as a decimal string (e.g. "1.234") + */ + public toDecimal(amount: bigint | string | number): string { + return formatAmount(amount, this.metadata.decimals); } + /** + * Get the available balance of an account in indivisible units + * + * @param account The account to get the balance of + * @returns + */ abstract getBalance(account: AccountLike): Promise; + + /** + * Transfer tokens from one account to another + * + * @param param + * @param param.from The Account that will transfer the tokens + * @param param.receiverId The AccountID that will receive the tokens + * @param param.amount The amount of tokens to transfer in the smallest unit + */ abstract transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise } @@ -40,7 +67,7 @@ export class NativeToken extends BaseFT { public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { return from.signAndSendTransaction({ receiverId, - actions: [{ enum: "Transfer", transfer: { deposit: amount.toString() } }], + actions: [{ transfer: { deposit: amount.toString() } }], }); } @@ -60,15 +87,7 @@ export class FungibleToken extends BaseFT { this.accountId = accountId; } - /** - * - * @param param - * @param param.accountId The Account that will transfer the tokens - * @param param.receiverId The AccountID that will receive the tokens - * @param param.amount The amount of tokens to transfer in the smallest unit - * @returns - */ - public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { + public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { return from.callFunction({ contractId: this.accountId, methodName: "ft_transfer", @@ -81,14 +100,8 @@ export class FungibleToken extends BaseFT { }); } - /** - * Get the balance of an account in units - * - * @param account The account to get the balance of - * @returns - */ public async getBalance(account: AccountLike): Promise { - const { balance } = await account.provider.callFunction( + const { result: balance } = await account.provider.callFunction( this.accountId, "ft_balance_of", { @@ -105,7 +118,7 @@ export class FungibleToken extends BaseFT { * @param param * @param param.from The Account that will transfer the tokens * @param param.receiverId The AccountID that will receive the tokens - * @param param.amount The amount of tokens to transfer in the smallest unit + * @param param.units The amount of tokens to transfer in the smallest unit * @param param.msg The message to send to the `ft_on_transfer` method */ public async transferCall({ from, receiverId, amount, msg }: { from: AccountLike, receiverId: string, amount: bigint, msg: string }): Promise { @@ -122,6 +135,13 @@ export class FungibleToken extends BaseFT { }); } + /** + * Register an account to the fungible token contract by paying a storage deposit + * + * @param param + * @param param.accountIdToRegister The AccountID to register + * @param param.fundingAccount The Account that will fund the registration + */ public async registerAccount({ accountIdToRegister, fundingAccount }: { accountIdToRegister: AccountLike, fundingAccount: AccountLike }): Promise { const { result } = await fundingAccount.provider.callFunction( @@ -144,6 +164,13 @@ export class FungibleToken extends BaseFT { }); } + /** + * Unregister an account from the fungible token contract by paying a storage deposit + * + * @param param + * @param param.account The Account to unregister + * @param param.force Whether to remove the account without claiming the storage deposit + */ public async unregisterAccount({ account, force = false }: { account: AccountLike, force: boolean }): Promise { return account.callFunction({ contractId: this.accountId, @@ -155,6 +182,9 @@ export class FungibleToken extends BaseFT { } } +/** + * The NEAR token is the native token of the NEAR blockchain + */ export const NEAR = new NativeToken({ name: 'NEAR', decimals: 24, diff --git a/packages/tokens/src/index.ts b/packages/tokens/src/index.ts index b8b00932f8..51622afc8f 100644 --- a/packages/tokens/src/index.ts +++ b/packages/tokens/src/index.ts @@ -1,2 +1,4 @@ export { NEAR, NativeToken, FungibleToken } from "./ft" -export { NonFungibleToken, NFTContract } from "./nft" \ No newline at end of file +export { NonFungibleToken, NFTContract } from "./nft" +export * as mainnet from "./mainnet" +export * as testnet from "./testnet" \ No newline at end of file diff --git a/packages/tokens/src/ft/mainnet/index.ts b/packages/tokens/src/mainnet/index.ts similarity index 99% rename from packages/tokens/src/ft/mainnet/index.ts rename to packages/tokens/src/mainnet/index.ts index 0be8af90d8..ba43ce5a9b 100644 --- a/packages/tokens/src/ft/mainnet/index.ts +++ b/packages/tokens/src/mainnet/index.ts @@ -1,4 +1,4 @@ -import { FungibleToken } from "../.."; +import { FungibleToken } from "../ft"; export const wNEAR = new FungibleToken( 'wrap.near', diff --git a/packages/tokens/src/ft/testnet/index.ts b/packages/tokens/src/testnet/index.ts similarity index 99% rename from packages/tokens/src/ft/testnet/index.ts rename to packages/tokens/src/testnet/index.ts index eceaf78774..043e9f239e 100644 --- a/packages/tokens/src/ft/testnet/index.ts +++ b/packages/tokens/src/testnet/index.ts @@ -1,4 +1,4 @@ -import { FungibleToken } from "../.."; +import { FungibleToken } from "../ft"; export const wNEAR = new FungibleToken( 'wrap.testnet', diff --git a/packages/tokens/test/fungible_token.test.ts b/packages/tokens/test/fungible_token.test.ts index 155866e593..408fb456d7 100644 --- a/packages/tokens/test/fungible_token.test.ts +++ b/packages/tokens/test/fungible_token.test.ts @@ -63,45 +63,45 @@ test('test toUnits fails on non-numeric symbols', () => { expect(() => FT.toUnits('abcdefg')).toThrow(); }); -test('test toAmount formats units', () => { - expect(FT.toAmount('1000000')).toBe('1'); - expect(FT.toAmount(1_000_000)).toBe('1'); - expect(FT.toAmount(BigInt(1_000_000))).toBe('1'); - - expect(FT.toAmount('1000001')).toBe('1.000001'); - expect(FT.toAmount(1_000_001)).toBe('1.000001'); - expect(FT.toAmount(BigInt(1_000_001))).toBe('1.000001'); - - expect(FT.toAmount('1234567')).toBe('1.234567'); - expect(FT.toAmount(1_234_567)).toBe('1.234567'); - expect(FT.toAmount(BigInt(1_234_567))).toBe('1.234567'); - - expect(FT.toAmount('12345678')).toBe('12.345678'); - expect(FT.toAmount(12_345_678)).toBe('12.345678'); - expect(FT.toAmount(BigInt(12_345_678))).toBe('12.345678'); - - expect(FT.toAmount('710')).toBe('0.00071'); - expect(FT.toAmount(710)).toBe('0.00071'); - expect(FT.toAmount(BigInt(710))).toBe('0.00071'); - - expect(FT.toAmount('1')).toBe('0.000001'); - expect(FT.toAmount(1)).toBe('0.000001'); - expect(FT.toAmount(BigInt(1))).toBe('0.000001'); - - expect(FT.toAmount('0')).toBe('0'); - expect(FT.toAmount(0)).toBe('0'); - expect(FT.toAmount(BigInt(0))).toBe('0'); +test('test toDecimal formats units', () => { + expect(FT.toDecimal('1000000')).toBe('1'); + expect(FT.toDecimal(1_000_000)).toBe('1'); + expect(FT.toDecimal(BigInt(1_000_000))).toBe('1'); + + expect(FT.toDecimal('1000001')).toBe('1.000001'); + expect(FT.toDecimal(1_000_001)).toBe('1.000001'); + expect(FT.toDecimal(BigInt(1_000_001))).toBe('1.000001'); + + expect(FT.toDecimal('1234567')).toBe('1.234567'); + expect(FT.toDecimal(1_234_567)).toBe('1.234567'); + expect(FT.toDecimal(BigInt(1_234_567))).toBe('1.234567'); + + expect(FT.toDecimal('12345678')).toBe('12.345678'); + expect(FT.toDecimal(12_345_678)).toBe('12.345678'); + expect(FT.toDecimal(BigInt(12_345_678))).toBe('12.345678'); + + expect(FT.toDecimal('710')).toBe('0.00071'); + expect(FT.toDecimal(710)).toBe('0.00071'); + expect(FT.toDecimal(BigInt(710))).toBe('0.00071'); + + expect(FT.toDecimal('1')).toBe('0.000001'); + expect(FT.toDecimal(1)).toBe('0.000001'); + expect(FT.toDecimal(BigInt(1))).toBe('0.000001'); + + expect(FT.toDecimal('0')).toBe('0'); + expect(FT.toDecimal(0)).toBe('0'); + expect(FT.toDecimal(BigInt(0))).toBe('0'); }); -test('test toAmount fails on non-integer units', () => { - expect(() => FT.toAmount('0.1')).toThrow(); - expect(() => FT.toAmount(0.1)).toThrow(); +test('test toDecimal fails on non-integer units', () => { + expect(() => FT.toDecimal('0.1')).toThrow(); + expect(() => FT.toDecimal(0.1)).toThrow(); }); -test('test toAmount fails on non-numeric symbols', () => { - expect(() => FT.toAmount('1.24n')).toThrow(); +test('test toDecimal fails on non-numeric symbols', () => { + expect(() => FT.toDecimal('1.24n')).toThrow(); - expect(() => FT.toAmount('abc192.31')).toThrow(); + expect(() => FT.toDecimal('abc192.31')).toThrow(); - expect(() => FT.toAmount('abcdefg')).toThrow(); + expect(() => FT.toDecimal('abcdefg')).toThrow(); }); diff --git a/packages/tokens/test/native_token.test.ts b/packages/tokens/test/native_token.test.ts index 17829a97c3..8e37f1b0a3 100644 --- a/packages/tokens/test/native_token.test.ts +++ b/packages/tokens/test/native_token.test.ts @@ -59,47 +59,47 @@ test('test toUnits fails on non-numeric symbols', () => { expect(() => NEAR.toUnits('abcdefg')).toThrow(); }); -test('test toAmount formats units', () => { - expect(NEAR.toAmount('1000000')).toBe('0.000000000000000001'); - expect(NEAR.toAmount(1_000_000)).toBe('0.000000000000000001'); - expect(NEAR.toAmount(BigInt(1_000_000))).toBe('0.000000000000000001'); - - expect(NEAR.toAmount('1000001')).toBe('0.000000000000000001000001'); - expect(NEAR.toAmount(1_000_001)).toBe('0.000000000000000001000001'); - expect(NEAR.toAmount(BigInt(1_000_001))).toBe('0.000000000000000001000001'); - - expect(NEAR.toAmount('1234567')).toBe('0.000000000000000001234567'); - expect(NEAR.toAmount(1_234_567)).toBe('0.000000000000000001234567'); - expect(NEAR.toAmount(BigInt(1_234_567))).toBe('0.000000000000000001234567'); - - expect(NEAR.toAmount('12345678')).toBe('0.000000000000000012345678'); - expect(NEAR.toAmount(12_345_678)).toBe('0.000000000000000012345678'); - expect(NEAR.toAmount(BigInt(12_345_678))).toBe( +test('test toDecimal formats units', () => { + expect(NEAR.toDecimal('1000000')).toBe('0.000000000000000001'); + expect(NEAR.toDecimal(1_000_000)).toBe('0.000000000000000001'); + expect(NEAR.toDecimal(BigInt(1_000_000))).toBe('0.000000000000000001'); + + expect(NEAR.toDecimal('1000001')).toBe('0.000000000000000001000001'); + expect(NEAR.toDecimal(1_000_001)).toBe('0.000000000000000001000001'); + expect(NEAR.toDecimal(BigInt(1_000_001))).toBe('0.000000000000000001000001'); + + expect(NEAR.toDecimal('1234567')).toBe('0.000000000000000001234567'); + expect(NEAR.toDecimal(1_234_567)).toBe('0.000000000000000001234567'); + expect(NEAR.toDecimal(BigInt(1_234_567))).toBe('0.000000000000000001234567'); + + expect(NEAR.toDecimal('12345678')).toBe('0.000000000000000012345678'); + expect(NEAR.toDecimal(12_345_678)).toBe('0.000000000000000012345678'); + expect(NEAR.toDecimal(BigInt(12_345_678))).toBe( '0.000000000000000012345678' ); - expect(NEAR.toAmount('710')).toBe('0.00000000000000000000071'); - expect(NEAR.toAmount(710)).toBe('0.00000000000000000000071'); - expect(NEAR.toAmount(BigInt(710))).toBe('0.00000000000000000000071'); + expect(NEAR.toDecimal('710')).toBe('0.00000000000000000000071'); + expect(NEAR.toDecimal(710)).toBe('0.00000000000000000000071'); + expect(NEAR.toDecimal(BigInt(710))).toBe('0.00000000000000000000071'); - expect(NEAR.toAmount('1')).toBe('0.000000000000000000000001'); - expect(NEAR.toAmount(1)).toBe('0.000000000000000000000001'); - expect(NEAR.toAmount(BigInt(1))).toBe('0.000000000000000000000001'); + expect(NEAR.toDecimal('1')).toBe('0.000000000000000000000001'); + expect(NEAR.toDecimal(1)).toBe('0.000000000000000000000001'); + expect(NEAR.toDecimal(BigInt(1))).toBe('0.000000000000000000000001'); - expect(NEAR.toAmount('0')).toBe('0'); - expect(NEAR.toAmount(0)).toBe('0'); - expect(NEAR.toAmount(BigInt(0))).toBe('0'); + expect(NEAR.toDecimal('0')).toBe('0'); + expect(NEAR.toDecimal(0)).toBe('0'); + expect(NEAR.toDecimal(BigInt(0))).toBe('0'); }); -test('test toAmount fails on non-integer units', () => { - expect(() => NEAR.toAmount('0.1')).toThrow(); - expect(() => NEAR.toAmount(0.1)).toThrow(); +test('test toDecimal fails on non-integer units', () => { + expect(() => NEAR.toDecimal('0.1')).toThrow(); + expect(() => NEAR.toDecimal(0.1)).toThrow(); }); -test('test toAmount fails on non-numeric symbols', () => { - expect(() => NEAR.toAmount('1.24n')).toThrow(); +test('test toDecimal fails on non-numeric symbols', () => { + expect(() => NEAR.toDecimal('1.24n')).toThrow(); - expect(() => NEAR.toAmount('abc192.31')).toThrow(); + expect(() => NEAR.toDecimal('abc192.31')).toThrow(); - expect(() => NEAR.toAmount('abcdefg')).toThrow(); + expect(() => NEAR.toDecimal('abcdefg')).toThrow(); }); diff --git a/packages/tokens/tsconfig.cjs.json b/packages/tokens/tsconfig.cjs.json index 667b87a94f..ae3708b61c 100644 --- a/packages/tokens/tsconfig.cjs.json +++ b/packages/tokens/tsconfig.cjs.json @@ -4,6 +4,5 @@ "outDir": "./lib/commonjs", "lib": ["es2022", "dom"] }, - "files": ["src/index.ts"], - "include": ["src/usdt", "src/usdc"] + "include": ["src/**/*"] } diff --git a/packages/tokens/tsconfig.json b/packages/tokens/tsconfig.json index 7ddf377ab4..f3aef3e523 100644 --- a/packages/tokens/tsconfig.json +++ b/packages/tokens/tsconfig.json @@ -1,8 +1,7 @@ { "extends": "tsconfig/esm.json", "compilerOptions": { - "outDir": "./lib/esm" + "outDir": "./lib/esm", }, - "files": ["src/index.ts"], - "include": ["src/ft", "src/nft"] + "include": ["src/**/*"] } From 8bd734ac2f4a8e7b44590fd258c1b8968ed2959d Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Fri, 9 May 2025 18:04:42 +0200 Subject: [PATCH 48/57] fix: lint --- packages/tokens/src/ft/format.ts | 20 +++++++-------- packages/tokens/src/ft/index.ts | 28 ++++++++++----------- packages/tokens/src/mainnet/index.ts | 12 ++++----- packages/tokens/src/nft/index.ts | 6 ++--- packages/tokens/src/testnet/index.ts | 12 ++++----- packages/tokens/test/fungible_token.test.ts | 2 +- 6 files changed, 40 insertions(+), 40 deletions(-) diff --git a/packages/tokens/src/ft/format.ts b/packages/tokens/src/ft/format.ts index 36418898f4..8208e98d27 100644 --- a/packages/tokens/src/ft/format.ts +++ b/packages/tokens/src/ft/format.ts @@ -9,11 +9,11 @@ export function formatAmount( ): string { units = cleanupUnits(units); - const wholeStr = units.substring(0, units.length - fracDigits) || "0"; + const wholeStr = units.substring(0, units.length - fracDigits) || '0'; const fractionStr = units .substring(units.length - fracDigits) .substring(0, fracDigits) - .padStart(fracDigits, "0"); + .padStart(fracDigits, '0'); return trimTrailingZeroes(`${wholeStr}.${fractionStr}`); } @@ -23,7 +23,7 @@ export function formatAmount( */ export function parseAmount(amount: string, fracDigits: number): string { amount = cleanupAmount(amount); - const split = amount.split("."); + const split = amount.split('.'); if (split.length > 2) { throw new Error( @@ -32,7 +32,7 @@ export function parseAmount(amount: string, fracDigits: number): string { } const wholePart = split[0]; - const fracPart = split[1] || ""; + const fracPart = split[1] || ''; if (fracPart.length > MAX_NOMINATION_EXP) { throw new Error( @@ -41,7 +41,7 @@ export function parseAmount(amount: string, fracDigits: number): string { } return trimLeadingZeroes( - wholePart + fracPart.substring(0, fracDigits).padEnd(fracDigits, "0") + wholePart + fracPart.substring(0, fracDigits).padEnd(fracDigits, '0') ); } @@ -51,7 +51,7 @@ export function parseAmount(amount: string, fracDigits: number): string { * @returns string The cleaned value */ function cleanupAmount(amount: string): string { - return amount.replace(/,/g, ".").trim(); + return amount.replace(/,/g, '.').trim(); } /** @@ -60,7 +60,7 @@ function cleanupAmount(amount: string): string { * @returns string The value without the trailing zeros */ function trimTrailingZeroes(value: string): string { - return value.replace(/\.?0*$/, ""); + return value.replace(/\.?0*$/, ''); } /** @@ -69,9 +69,9 @@ function trimTrailingZeroes(value: string): string { * @returns string The value without the leading zeroes */ function trimLeadingZeroes(value: string): string { - value = value.replace(/^0+/, ""); - if (value === "") { - return "0"; + value = value.replace(/^0+/, ''); + if (value === '') { + return '0'; } return value; } diff --git a/packages/tokens/src/ft/index.ts b/packages/tokens/src/ft/index.ts index ec0d4386d3..c1c2f60dd4 100644 --- a/packages/tokens/src/ft/index.ts +++ b/packages/tokens/src/ft/index.ts @@ -1,5 +1,5 @@ -import { formatAmount, parseAmount } from "./format"; -import type { AccountLike } from "@near-js/types"; +import { formatAmount, parseAmount } from './format'; +import type { AccountLike } from '@near-js/types'; interface FTMetadata { spec?: string; @@ -82,7 +82,7 @@ export class FungibleToken extends BaseFT { public readonly accountId: string; constructor(accountId: string, metadata: FTMetadata) { - metadata.spec = metadata.spec || "ft-1.0.0"; + metadata.spec = metadata.spec || 'ft-1.0.0'; super(metadata); this.accountId = accountId; } @@ -90,12 +90,12 @@ export class FungibleToken extends BaseFT { public async transfer({ from, receiverId, amount }: { from: AccountLike, receiverId: string, amount: string | number | bigint }): Promise { return from.callFunction({ contractId: this.accountId, - methodName: "ft_transfer", + methodName: 'ft_transfer', args: { amount: amount.toString(), receiver_id: receiverId, }, - gas: "30000000000000", + gas: '30000000000000', deposit: 1, }); } @@ -103,7 +103,7 @@ export class FungibleToken extends BaseFT { public async getBalance(account: AccountLike): Promise { const { result: balance } = await account.provider.callFunction( this.accountId, - "ft_balance_of", + 'ft_balance_of', { account_id: account.accountId, }, @@ -124,13 +124,13 @@ export class FungibleToken extends BaseFT { public async transferCall({ from, receiverId, amount, msg }: { from: AccountLike, receiverId: string, amount: bigint, msg: string }): Promise { return from.callFunction({ contractId: this.accountId, - methodName: "ft_transfer_call", + methodName: 'ft_transfer_call', args: { receiver_id: receiverId, amount: amount.toString(), msg, }, - gas: "30000000000000", + gas: '30000000000000', deposit: 1, }); } @@ -146,7 +146,7 @@ export class FungibleToken extends BaseFT { const { result } = await fundingAccount.provider.callFunction( this.accountId, - "storage_balance_bounds", + 'storage_balance_bounds', {} ); @@ -154,12 +154,12 @@ export class FungibleToken extends BaseFT { return fundingAccount.callFunction({ contractId: this.accountId, - methodName: "storage_deposit", + methodName: 'storage_deposit', args: { account_id: accountIdToRegister, registration_only: true, }, - gas: "30000000000000", + gas: '30000000000000', deposit: requiredDeposit, }); } @@ -174,9 +174,9 @@ export class FungibleToken extends BaseFT { public async unregisterAccount({ account, force = false }: { account: AccountLike, force: boolean }): Promise { return account.callFunction({ contractId: this.accountId, - methodName: "storage_unregister", + methodName: 'storage_unregister', args: { force }, - gas: "30000000000000", + gas: '30000000000000', deposit: 1, }); } @@ -189,5 +189,5 @@ export const NEAR = new NativeToken({ name: 'NEAR', decimals: 24, symbol: 'NEAR', - icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' }); \ No newline at end of file diff --git a/packages/tokens/src/mainnet/index.ts b/packages/tokens/src/mainnet/index.ts index ba43ce5a9b..ed06b4c6fd 100644 --- a/packages/tokens/src/mainnet/index.ts +++ b/packages/tokens/src/mainnet/index.ts @@ -1,4 +1,4 @@ -import { FungibleToken } from "../ft"; +import { FungibleToken } from '../ft'; export const wNEAR = new FungibleToken( 'wrap.near', @@ -6,8 +6,8 @@ export const wNEAR = new FungibleToken( name: 'Wrapped NEAR fungible token', decimals: 24, symbol: 'wNEAR', - icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" -}); + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' + }); export const USDC = new FungibleToken( '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', @@ -15,7 +15,7 @@ export const USDC = new FungibleToken( name: 'USDC', decimals: 6, symbol: 'USDC', - icon: "data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A" + icon: 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A' } ); @@ -25,6 +25,6 @@ export const USDT = new FungibleToken( name: 'Tether USD', decimals: 6, symbol: 'USDt', - icon: "data:image/svg+xml,%3Csvg width='111' height='90' viewBox='0 0 111 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z' fill='%23009393'/%3E%3C/svg%3E" + icon: 'data:image/svg+xml,%3Csvg width=\'111\' height=\'90\' viewBox=\'0 0 111 90\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath fill-rule=\'evenodd\' clip-rule=\'evenodd\' d=\'M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z\' fill=\'%23009393\'/%3E%3C/svg%3E' } -) \ No newline at end of file +); \ No newline at end of file diff --git a/packages/tokens/src/nft/index.ts b/packages/tokens/src/nft/index.ts index 722c4dd172..3cd2ae03b1 100644 --- a/packages/tokens/src/nft/index.ts +++ b/packages/tokens/src/nft/index.ts @@ -1,4 +1,4 @@ -import type { AccountLike } from "@near-js/types"; +import type { AccountLike } from '@near-js/types'; interface ContractMetadata { spec?: string; @@ -30,7 +30,7 @@ export class NFTContract { public readonly accountId: string; constructor(accountId: string, metadata: ContractMetadata) { - metadata.spec = metadata.spec || "nft-1.0.0"; + metadata.spec = metadata.spec || 'nft-1.0.0'; this.metadata = metadata; this.accountId = accountId; } @@ -46,7 +46,7 @@ export class NFTContract { }): Promise { return from.callFunction({ contractId: this.accountId, - methodName: "nft_transfer", + methodName: 'nft_transfer', args: { receiver_id: receiverId, token_id: tokenId diff --git a/packages/tokens/src/testnet/index.ts b/packages/tokens/src/testnet/index.ts index 043e9f239e..4a79138274 100644 --- a/packages/tokens/src/testnet/index.ts +++ b/packages/tokens/src/testnet/index.ts @@ -1,4 +1,4 @@ -import { FungibleToken } from "../ft"; +import { FungibleToken } from '../ft'; export const wNEAR = new FungibleToken( 'wrap.testnet', @@ -6,8 +6,8 @@ export const wNEAR = new FungibleToken( name: 'Wrapped NEAR fungible token', decimals: 24, symbol: 'wNEAR', - icon: "data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E" -}); + icon: 'data:image/svg+xml,%3Csvg%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%20fill%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Ccircle%20cx%3D%2216%22%20cy%3D%2216%22%20r%3D%2216%22%20fill%3D%22white%22%3E%3C%2Fcircle%3E%3Cg%20clip-path%3D%22url(%23clip0000000003)%22%3E%3Cpath%20d%3D%22M20.8422%208.84471L17.4978%2013.776C17.4501%2013.847%2017.43%2013.9328%2017.4411%2014.0174C17.4522%2014.102%2017.4938%2014.1798%2017.5582%2014.2363C17.6225%2014.2928%2017.7053%2014.3243%2017.7913%2014.3249C17.8772%2014.3254%2017.9604%2014.2951%2018.0256%2014.2395L21.3178%2011.4036C21.3371%2011.3865%2021.361%2011.3753%2021.3866%2011.3714C21.4122%2011.3675%2021.4383%2011.3711%2021.4619%2011.3818C21.4855%2011.3924%2021.5054%2011.4096%2021.5193%2011.4314C21.5331%2011.4531%2021.5403%2011.4783%2021.54%2011.504V20.3824C21.54%2020.4095%2021.5316%2020.4361%2021.5158%2020.4583C21.5001%2020.4806%2021.4779%2020.4975%2021.4522%2020.5068C21.4265%2020.516%2021.3985%2020.5172%2021.3721%2020.5102C21.3456%2020.5031%2021.322%2020.4882%2021.3044%2020.4673L11.3533%208.63726C11.1933%208.44956%2010.994%208.29873%2010.7693%208.19525C10.5446%208.09178%2010.2999%208.03815%2010.0522%208.03809H9.70444C9.2524%208.03809%208.81887%208.21642%208.49922%208.53386C8.17957%208.8513%208%209.28185%208%209.73078V22.2351C8%2022.684%208.17957%2023.1145%208.49922%2023.432C8.81887%2023.7494%209.2524%2023.9277%209.70444%2023.9277V23.9277C9.99591%2023.9278%2010.2825%2023.8537%2010.537%2023.7125C10.7914%2023.5713%2011.0051%2023.3677%2011.1578%2023.1211L14.5022%2018.1898C14.5499%2018.1188%2014.57%2018.033%2014.5589%2017.9484C14.5478%2017.8638%2014.5062%2017.7861%2014.4418%2017.7295C14.3775%2017.673%2014.2947%2017.6415%2014.2087%2017.641C14.1228%2017.6404%2014.0396%2017.6707%2013.9744%2017.7264L10.6822%2020.5622C10.6629%2020.5794%2010.639%2020.5906%2010.6134%2020.5944C10.5878%2020.5983%2010.5617%2020.5947%2010.5381%2020.5841C10.5145%2020.5734%2010.4946%2020.5562%2010.4807%2020.5345C10.4669%2020.5128%2010.4597%2020.4875%2010.46%2020.4618V11.5813C10.46%2011.5541%2010.4684%2011.5276%2010.4842%2011.5053C10.4999%2011.483%2010.5221%2011.4661%2010.5478%2011.4568C10.5735%2011.4476%2010.6015%2011.4464%2010.6279%2011.4534C10.6544%2011.4605%2010.678%2011.4755%2010.6956%2011.4963L20.6456%2023.3286C20.8056%2023.5163%2021.0049%2023.6671%2021.2296%2023.7706C21.4543%2023.874%2021.699%2023.9277%2021.9467%2023.9277H22.2944C22.5184%2023.9279%2022.7401%2023.8842%2022.947%2023.7992C23.154%2023.7142%2023.342%2023.5895%2023.5004%2023.4324C23.6588%2023.2752%2023.7844%2023.0885%2023.8702%2022.8831C23.9559%2022.6776%2024%2022.4574%2024%2022.2351V9.73078C24%209.28185%2023.8204%208.8513%2023.5008%208.53386C23.1811%208.21642%2022.7476%208.03809%2022.2956%208.03809C22.0041%208.03801%2021.7175%208.11211%2021.4631%208.25332C21.2086%208.39453%2020.9949%208.59814%2020.8422%208.84471V8.84471Z%22%20fill%3D%22black%22%3E%3C%2Fpath%3E%3C%2Fg%3E%3Cdefs%3E%3CclipPath%20id%3D%22clip00033%22%3E%3Crect%20width%3D%2216%22%20height%3D%2216%22%20fill%3D%22black%22%20transform%3D%22translate(8%207.9834)%22%3E%3C%2Frect%3E%3C%2FclipPath%3E%3C%2Fdefs%3E%3C%2Fsvg%3E' + }); export const USDC = new FungibleToken( 'usdc.fakes.testnet', @@ -15,7 +15,7 @@ export const USDC = new FungibleToken( name: 'USDC', decimals: 6, symbol: 'USDC', - icon: "data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A" + icon: 'data:image/svg+xml,%3C%3Fxml version=%221.0%22 encoding=%22utf-8%22%3F%3E%3C!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --%3E%3Csvg version=%221.1%22 id=%22Layer_1%22 xmlns=%22http://www.w3.org/2000/svg%22 xmlns:xlink=%22http://www.w3.org/1999/xlink%22 x=%220px%22 y=%220px%22 viewBox=%220 0 256 256%22 style=%22enable-background:new 0 0 256 256;%22 xml:space=%22preserve%22%3E%3Cstyle type=%22text/css%22%3E .st0%7Bfill:%232775CA;%7D .st1%7Bfill:%23FFFFFF;%7D%0A%3C/style%3E%3Ccircle class=%22st0%22 cx=%22128%22 cy=%22128%22 r=%22128%22/%3E%3Cpath class=%22st1%22 d=%22M104,217c0,3-2.4,4.7-5.2,3.8C60,208.4,32,172.2,32,129.3c0-42.8,28-79.1,66.8-91.5c2.9-0.9,5.2,0.8,5.2,3.8 v7.5c0,2-1.5,4.3-3.4,5C69.9,65.4,48,94.9,48,129.3c0,34.5,21.9,63.9,52.6,75.1c1.9,0.7,3.4,3,3.4,5V217z%22/%3E%3Cpath class=%22st1%22 d=%22M136,189.3c0,2.2-1.8,4-4,4h-8c-2.2,0-4-1.8-4-4v-12.6c-17.5-2.4-26-12.1-28.3-25.5c-0.4-2.3,1.4-4.3,3.7-4.3 h9.1c1.9,0,3.5,1.4,3.9,3.2c1.7,7.9,6.3,14,20.3,14c10.3,0,17.7-5.8,17.7-14.4c0-8.6-4.3-11.9-19.5-14.4c-22.4-3-33-9.8-33-27.3 c0-13.5,10.3-24.1,26.1-26.3V69.3c0-2.2,1.8-4,4-4h8c2.2,0,4,1.8,4,4v12.7c12.9,2.3,21.1,9.6,23.8,21.8c0.5,2.3-1.3,4.4-3.7,4.4 h-8.4c-1.8,0-3.3-1.2-3.8-2.9c-2.3-7.7-7.8-11.1-17.4-11.1c-10.6,0-16.1,5.1-16.1,12.3c0,7.6,3.1,11.4,19.4,13.7 c22,3,33.4,9.3,33.4,28c0,14.2-10.6,25.7-27.1,28.3V189.3z%22/%3E%3Cpath class=%22st1%22 d=%22M157.2,220.8c-2.9,0.9-5.2-0.8-5.2-3.8v-7.5c0-2.2,1.3-4.3,3.4-5c30.6-11.2,52.6-40.7,52.6-75.1 c0-34.5-21.9-63.9-52.6-75.1c-1.9-0.7-3.4-3-3.4-5v-7.5c0-3,2.4-4.7,5.2-3.8C196,50.2,224,86.5,224,129.3 C224,172.2,196,208.4,157.2,220.8z%22/%3E%3C/svg%3E%0A' } ); @@ -25,6 +25,6 @@ export const USDT = new FungibleToken( name: 'Tether USD', decimals: 6, symbol: 'USDt', - icon: "data:image/svg+xml,%3Csvg width='111' height='90' viewBox='0 0 111 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z' fill='%23009393'/%3E%3C/svg%3E" + icon: 'data:image/svg+xml,%3Csvg width=\'111\' height=\'90\' viewBox=\'0 0 111 90\' fill=\'none\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cpath fill-rule=\'evenodd\' clip-rule=\'evenodd\' d=\'M24.4825 0.862305H88.0496C89.5663 0.862305 90.9675 1.64827 91.7239 2.92338L110.244 34.1419C111.204 35.7609 110.919 37.8043 109.549 39.1171L58.5729 87.9703C56.9216 89.5528 54.2652 89.5528 52.6139 87.9703L1.70699 39.1831C0.305262 37.8398 0.0427812 35.7367 1.07354 34.1077L20.8696 2.82322C21.6406 1.60483 23.0087 0.862305 24.4825 0.862305ZM79.8419 14.8003V23.5597H61.7343V29.6329C74.4518 30.2819 83.9934 32.9475 84.0642 36.1425L84.0638 42.803C83.993 45.998 74.4518 48.6635 61.7343 49.3125V64.2168H49.7105V49.3125C36.9929 48.6635 27.4513 45.998 27.3805 42.803L27.381 36.1425C27.4517 32.9475 36.9929 30.2819 49.7105 29.6329V23.5597H31.6028V14.8003H79.8419ZM55.7224 44.7367C69.2943 44.7367 80.6382 42.4827 83.4143 39.4727C81.0601 36.9202 72.5448 34.9114 61.7343 34.3597V40.7183C59.7966 40.8172 57.7852 40.8693 55.7224 40.8693C53.6595 40.8693 51.6481 40.8172 49.7105 40.7183V34.3597C38.8999 34.9114 30.3846 36.9202 28.0304 39.4727C30.8066 42.4827 42.1504 44.7367 55.7224 44.7367Z\' fill=\'%23009393\'/%3E%3C/svg%3E' } -) \ No newline at end of file +); \ No newline at end of file diff --git a/packages/tokens/test/fungible_token.test.ts b/packages/tokens/test/fungible_token.test.ts index 408fb456d7..bc4870c86f 100644 --- a/packages/tokens/test/fungible_token.test.ts +++ b/packages/tokens/test/fungible_token.test.ts @@ -1,7 +1,7 @@ import { expect, test } from '@jest/globals'; import { FungibleToken } from '../src'; -const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST', name: "Test Token" }); +const FT = new FungibleToken('ft.testnet', { decimals: 6, symbol: 'TEST', name: 'Test Token' }); test('test props are accessible', () => { expect(FT.accountId).toBe('ft.testnet'); From fd16a880126d3ae9ea12720fce400bd7851c36c0 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Fri, 9 May 2025 19:03:30 +0200 Subject: [PATCH 49/57] feat: changed provider.functioncall to return value --- .../providers/src/failover-rpc-provider.ts | 8 +++-- packages/providers/src/json-rpc-provider.ts | 32 +++++++++++++------ packages/providers/src/provider.ts | 5 +-- packages/types/src/provider/index.ts | 1 - 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/providers/src/failover-rpc-provider.ts b/packages/providers/src/failover-rpc-provider.ts index 0e30b2567b..f4d4075c3f 100644 --- a/packages/providers/src/failover-rpc-provider.ts +++ b/packages/providers/src/failover-rpc-provider.ts @@ -32,7 +32,7 @@ import { AccountView, ContractCodeView, ContractStateView, - CallContractViewFunctionResult, + CallContractViewFunctionResultRaw, ExecutionOutcomeReceiptDetail, FinalityReference, } from '@near-js/types'; @@ -139,10 +139,14 @@ export class FailoverRpcProvider implements Provider { return this.withBackoff((currentProvider) => currentProvider.viewContractState(accountId, prefix, blockQuery)); } - public async callFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + public async callFunction(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { return this.withBackoff((currentProvider) => currentProvider.callFunction(accountId, method, args, blockQuery)); } + public async callFunctionRaw(accountId: string, method: string, args: Record, blockQuery?: BlockReference): Promise { + return this.withBackoff((currentProvider) => currentProvider.callFunctionRaw(accountId, method, args, blockQuery)); + } + public async viewBlock(blockQuery: BlockReference): Promise { return this.withBackoff((currentProvider) => currentProvider.viewBlock(blockQuery)); } diff --git a/packages/providers/src/json-rpc-provider.ts b/packages/providers/src/json-rpc-provider.ts index 48d09a49ae..7da225faeb 100644 --- a/packages/providers/src/json-rpc-provider.ts +++ b/packages/providers/src/json-rpc-provider.ts @@ -42,7 +42,6 @@ import { ContractCodeView, ContractStateView, CallContractViewFunctionResultRaw, - CallContractViewFunctionResult, ExecutionOutcomeReceiptDetail, } from '@near-js/types'; import { @@ -203,7 +202,7 @@ export class JsonRpcProvider implements Provider { method: string, args: Record, blockQuery: BlockReference = { finality: 'final' } - ): Promise { + ): Promise { const argsBase64 = Buffer.from(JSON.stringify(args)).toString('base64'); const data = await ( @@ -217,16 +216,29 @@ export class JsonRpcProvider implements Provider { }); if (data.result.length === 0) { - return { - ...data, - result: undefined, - }; + return undefined; } - return { - ...data, - result: JSON.parse(Buffer.from(data.result).toString()), - }; + return JSON.parse(Buffer.from(data.result).toString()); + } + + public async callFunctionRaw( + contractId: string, + method: string, + args: Record, + blockQuery: BlockReference = { finality: 'final' } + ): Promise { + const argsBase64 = Buffer.from(JSON.stringify(args)).toString('base64'); + + return await ( + this as Provider + ).query({ + ...blockQuery, + request_type: 'call_function', + account_id: contractId, + method_name: method, + args_base64: argsBase64, + }); } public async viewBlock(blockQuery: BlockReference): Promise { diff --git a/packages/providers/src/provider.ts b/packages/providers/src/provider.ts index 5f078e4067..221fe9ee29 100644 --- a/packages/providers/src/provider.ts +++ b/packages/providers/src/provider.ts @@ -29,10 +29,10 @@ import { TxExecutionStatus, ContractCodeView, ContractStateView, - CallContractViewFunctionResult, ChunkId, ChunkResult, FinalityReference, + CallContractViewFunctionResultRaw, } from '@near-js/types'; import { PublicKey } from '@near-js/crypto'; @@ -49,7 +49,8 @@ export interface Provider { viewAccount(accountId: string, blockQuery?: BlockReference): Promise; viewContractCode(contractId: string, blockQuery?: BlockReference): Promise; viewContractState(contractId: string, prefix?: string, blockQuery?: BlockReference): Promise; - callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + callFunction(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; + callFunctionRaw(contractId: string, method: string, args: Record, blockQuery?: BlockReference): Promise; viewBlock(blockQuery: BlockReference): Promise; viewChunk(chunkId: ChunkId): Promise; diff --git a/packages/types/src/provider/index.ts b/packages/types/src/provider/index.ts index e9a8bbd9a1..772e9299bc 100644 --- a/packages/types/src/provider/index.ts +++ b/packages/types/src/provider/index.ts @@ -75,7 +75,6 @@ export { ExecutionOutcomeReceiptDetail, ContractStateView, CallContractViewFunctionResultRaw, - CallContractViewFunctionResult } from './response'; export { CurrentEpochValidatorInfo, From 0db0e3d4d59073c04c96dd9fc42be925fc5a47fd Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Fri, 9 May 2025 19:03:57 +0200 Subject: [PATCH 50/57] fix: minor change --- packages/accounts/src/account.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index e020d5f338..0ac3e93fdc 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -732,7 +732,7 @@ export class Account { } /** - * @deprecated please use callFunction instead + * @deprecated please use {@link callFunction} instead * * Execute a function call. * @param options The options for the function call. From 7d284c4fff081da9d51924c37875099a9631b116 Mon Sep 17 00:00:00 2001 From: Guillermo Alejandro Gallardo Diez Date: Tue, 13 May 2025 16:01:49 +0200 Subject: [PATCH 51/57] chore: remove unused native flag --- packages/tokens/src/ft/index.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/tokens/src/ft/index.ts b/packages/tokens/src/ft/index.ts index c1c2f60dd4..9d0964f636 100644 --- a/packages/tokens/src/ft/index.ts +++ b/packages/tokens/src/ft/index.ts @@ -11,7 +11,6 @@ interface FTMetadata { abstract class BaseFT { public readonly metadata: FTMetadata; - public readonly native: boolean; constructor(metadata: FTMetadata) { this.metadata = metadata; @@ -58,8 +57,6 @@ abstract class BaseFT { } export class NativeToken extends BaseFT { - public readonly native: boolean = true; - constructor(metadata: FTMetadata) { super(metadata); } @@ -78,7 +75,6 @@ export class NativeToken extends BaseFT { } export class FungibleToken extends BaseFT { - public readonly native: boolean = false; public readonly accountId: string; constructor(accountId: string, metadata: FTMetadata) { From 72fc357a2e2fe16f1f4141bfa798f2b02777406f Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 08:51:48 +0200 Subject: [PATCH 52/57] fix: specify `optimistic` finality as default for RPC calls in tests as `near-sandbox` is old --- packages/accounts/package.json | 2 +- packages/accounts/src/account.ts | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index de44fae1b4..7898a03ec2 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -11,7 +11,7 @@ "compile:cjs": "tsc -p tsconfig.cjs.json && cjsify ./lib/commonjs", "lint": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc", "lint:fix": "eslint -c ../../.eslintrc.ts.yml src/**/*.ts test/**/*.ts --no-eslintrc --fix", - "test": "jest" + "test": "DEFAULT_FINALITY=optimistic jest" }, "keywords": [], "author": "", diff --git a/packages/accounts/src/account.ts b/packages/accounts/src/account.ts index 0ac3e93fdc..f46d32aefe 100644 --- a/packages/accounts/src/account.ts +++ b/packages/accounts/src/account.ts @@ -25,6 +25,7 @@ import { ErrorContext, TxExecutionStatus, AccountView, + Finality, } from "@near-js/types"; import { baseDecode, @@ -59,8 +60,8 @@ const { transfer, } = actionCreators; -// Default values to wait for -const DEFAULT_FINALITY = "optimistic"; +// Environment value is used in tests since near-sandbox runs old version of nearcore that doesn't work with near-final finality +const DEFAULT_FINALITY = process.env.DEFAULT_FINALITY as Finality || "near-final"; export const DEFAULT_WAIT_STATUS: TxExecutionStatus = "EXECUTED_OPTIMISTIC"; export interface AccountState { From f8a611e32acc64943d07528e72f9acd9065b6fdd Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 09:03:15 +0200 Subject: [PATCH 53/57] chore: deprecate `Connection` --- packages/accounts/package.json | 1 + packages/accounts/src/connection.ts | 6 ++++++ pnpm-lock.yaml | 10 ++++++++++ 3 files changed, 17 insertions(+) diff --git a/packages/accounts/package.json b/packages/accounts/package.json index 7898a03ec2..b888311a3d 100644 --- a/packages/accounts/package.json +++ b/packages/accounts/package.json @@ -35,6 +35,7 @@ "@jest/globals": "^29.7.0", "@near-js/keystores": "workspace:*", "@scure/base": "^1.2.4", + "@types/depd": "^1.1.37", "@types/json-schema": "^7.0.15", "@types/node": "20.0.0", "build": "workspace:*", diff --git a/packages/accounts/src/connection.ts b/packages/accounts/src/connection.ts index 084a397c15..1f2d598c51 100644 --- a/packages/accounts/src/connection.ts +++ b/packages/accounts/src/connection.ts @@ -5,6 +5,7 @@ import { FailoverRpcProvider, } from "@near-js/providers"; import { IntoConnection } from "./interface"; +import depd from 'depd'; /** * @param config Contains connection info details @@ -44,6 +45,8 @@ function getSigner(config: any): Signer { } /** + * @deprecated + * * Connects an account to a given network via a given provider */ export class Connection implements IntoConnection { @@ -56,6 +59,9 @@ export class Connection implements IntoConnection { provider: Provider, signer: Signer, ) { + const deprecate = depd('new Connection(networkId, provider, signer)'); + deprecate('`Connection` is no longer used anywhere, please switch to constructing `Account` without it - use `new Account(accountId, provider, signer)`'); + this.networkId = networkId; this.provider = provider; this.signer = signer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 687635a920..14e12a785a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,6 +106,9 @@ importers: '@scure/base': specifier: ^1.2.4 version: 1.2.4 + '@types/depd': + specifier: ^1.1.37 + version: 1.1.37 '@types/json-schema': specifier: ^7.0.15 version: 7.0.15 @@ -1511,6 +1514,9 @@ packages: '@types/conventional-commits-parser@5.0.1': resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} + '@types/depd@1.1.37': + resolution: {integrity: sha512-PkEYFHnqDFgs+bJXJX0L8mq7sn3DWh+TP0m8BBJUJfZ2WcjRm7jd7Cq68jIJt+c31R1gX0cwSK1ZXOECvN97Rg==} + '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -4851,6 +4857,10 @@ snapshots: dependencies: '@types/node': 20.0.0 + '@types/depd@1.1.37': + dependencies: + '@types/node': 20.0.0 + '@types/graceful-fs@4.1.9': dependencies: '@types/node': 20.0.0 From b6333689e36ec3a7487657a6129c2891089addfa Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 09:10:58 +0200 Subject: [PATCH 54/57] chore: remove `@near-js/utils` from dependencies of `@near-js/tokens` --- packages/tokens/package.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/tokens/package.json b/packages/tokens/package.json index 37e9aeaec0..33da6e7177 100644 --- a/packages/tokens/package.json +++ b/packages/tokens/package.json @@ -16,9 +16,6 @@ "keywords": [], "author": "", "license": "ISC", - "dependencies": { - "@near-js/utils": "workspace:*" - }, "devDependencies": { "@jest/globals": "^29.7.0", "@near-js/types": "workspace:*", From b91bacf32675344db8905bc1ca4a327a6471eafe Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 09:12:13 +0200 Subject: [PATCH 55/57] chore: update lockfile --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14e12a785a..92570fd405 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -608,6 +608,9 @@ importers: '@jest/globals': specifier: ^29.7.0 version: 29.7.0 + '@near-js/types': + specifier: workspace:* + version: link:../types '@types/node': specifier: 20.0.0 version: 20.0.0 From e625bc52dc7e03672564fd9d9af26c7a72247557 Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 09:14:06 +0200 Subject: [PATCH 56/57] test: impl graceful shutdown for workspaces `Worker` --- .../accounts/test/account.access_key.test.ts | 23 +++++++++++++++---- packages/accounts/test/account.test.ts | 8 +++++++ packages/accounts/test/config.js | 6 ++--- packages/accounts/test/contract.test.ts | 20 +++++++++++++++- packages/accounts/test/promise.test.ts | 11 ++++++++- packages/accounts/test/providers.test.ts | 12 +++++++++- packages/accounts/test/test-utils.js | 4 +++- 7 files changed, 72 insertions(+), 12 deletions(-) diff --git a/packages/accounts/test/account.access_key.test.ts b/packages/accounts/test/account.access_key.test.ts index ad2c3c2c78..89cfec07d8 100644 --- a/packages/accounts/test/account.access_key.test.ts +++ b/packages/accounts/test/account.access_key.test.ts @@ -1,10 +1,12 @@ -import { beforeAll, beforeEach, expect, jest, test } from '@jest/globals'; +import { afterAll, beforeAll, beforeEach, expect, jest, test } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; import { createAccount, deployContract, generateUniqueString, setUpTestConnection } from './test-utils'; import { Account } from '../src'; import { KeyPairSigner } from '@near-js/signers'; +import { Worker } from 'near-workspaces'; + let nearjs; let workingAccount: Account; let contractId; @@ -16,6 +18,14 @@ beforeAll(async () => { nearjs = await setUpTestConnection(); }); +afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + beforeEach(async () => { try { contractId = generateUniqueString('test'); @@ -40,12 +50,14 @@ test('make function call using access key', async() => { }); test('remove access key no longer works', async() => { + const near = await setUpTestConnection(); + const keyPair = KeyPair.fromRandom('ed25519'); const publicKey = keyPair.getPublicKey(); - await nearjs.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000); - await nearjs.accountCreator.masterAccount.deleteKey(publicKey); + await near.accountCreator.masterAccount.addKey(publicKey, contractId, '', 400000n); + await near.accountCreator.masterAccount.deleteKey(publicKey); // Override account in the Contract to the masterAccount with the given access key. - contract.account = new Account(nearjs.accountCreator.masterAccount.accountId, nearjs.accountCreator.masterAccount.provider, new KeyPairSigner(keyPair)); + contract.account = new Account(near.accountCreator.masterAccount.accountId, near.accountCreator.masterAccount.provider, new KeyPairSigner(keyPair)); let failed = true; try { @@ -60,7 +72,8 @@ test('remove access key no longer works', async() => { throw new Error('should throw an error'); } - nearjs = await setUpTestConnection(); + const worker = near.worker as Worker; + await worker.tearDown(); }); test('view account details after adding access keys', async() => { diff --git a/packages/accounts/test/account.test.ts b/packages/accounts/test/account.test.ts index 605127f562..f40ef8ab8f 100644 --- a/packages/accounts/test/account.test.ts +++ b/packages/accounts/test/account.test.ts @@ -9,6 +9,8 @@ import { Account, Contract } from '../src'; import { createAccount, generateUniqueString, HELLO_WASM_PATH, HELLO_WASM_BALANCE, networkId, setUpTestConnection } from './test-utils'; import { InMemoryKeyStore } from '@near-js/keystores'; +import { Worker } from 'near-workspaces'; + let nearjs; let workingAccount: Account; @@ -21,6 +23,12 @@ beforeAll(async () => { afterAll(async () => { await workingAccount.deleteAccount(workingAccount.accountId); + + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); }); test('view pre-defined account works and returns correct name', async () => { diff --git a/packages/accounts/test/config.js b/packages/accounts/test/config.js index f1ecd95bdc..e77cbbae08 100644 --- a/packages/accounts/test/config.js +++ b/packages/accounts/test/config.js @@ -1,7 +1,6 @@ import { Worker } from 'near-workspaces'; import fs from 'fs'; -let worker; module.exports = async function getConfig(env) { switch (env) { case 'production': @@ -37,14 +36,15 @@ module.exports = async function getConfig(env) { }; case 'test': case 'ci': { - if (!worker) worker = await Worker.init(); + const worker = await Worker.init(); const keyFile = fs.readFileSync(`${worker.rootAccount.manager.config.homeDir}/validator_key.json`); const keyPair = JSON.parse(keyFile.toString()); return { networkId: worker.config.network, nodeUrl: worker.manager.config.rpcAddr, masterAccount: worker.rootAccount._accountId, - secretKey: keyPair.secret_key || keyPair.private_key + secretKey: keyPair.secret_key || keyPair.private_key, + worker: worker }; } default: diff --git a/packages/accounts/test/contract.test.ts b/packages/accounts/test/contract.test.ts index 07c0cff344..32054178b8 100644 --- a/packages/accounts/test/contract.test.ts +++ b/packages/accounts/test/contract.test.ts @@ -1,9 +1,11 @@ -import { afterEach, beforeAll, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, afterEach, beforeAll, describe, expect, jest, test } from '@jest/globals'; import { PositionalArgsError } from '@near-js/types'; import { Contract, Account } from '../src'; import { deployContractGuestBook, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Worker } from 'near-workspaces'; + const account = Object.setPrototypeOf({ getConnection() { return {}; @@ -127,6 +129,14 @@ describe('local view execution', () => { contract.connection.provider.query = jest.fn(contract.connection.provider.query); blockQuery = { blockId: block.header.height }; }); + + afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); + }); afterEach(() => { jest.clearAllMocks(); @@ -198,6 +208,14 @@ describe('contract without account', () => { }); }); + afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); + }); + test('view & change methods work', async () => { const totalMessagesBefore = await contract.total_messages({}); expect(totalMessagesBefore).toBe(0); diff --git a/packages/accounts/test/promise.test.ts b/packages/accounts/test/promise.test.ts index 8b90e305d0..e95992090d 100644 --- a/packages/accounts/test/promise.test.ts +++ b/packages/accounts/test/promise.test.ts @@ -1,5 +1,6 @@ -import { afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, jest, test } from '@jest/globals'; import { deployContract, generateUniqueString, setUpTestConnection } from './test-utils'; +import { Worker } from 'near-workspaces'; let nearjs; @@ -11,6 +12,14 @@ beforeAll(async () => { nearjs = await setUpTestConnection(); }); +afterAll(async () => { + const worker = nearjs.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + describe('with promises', () => { let contract, contract1, contract2; let oldLog; diff --git a/packages/accounts/test/providers.test.ts b/packages/accounts/test/providers.test.ts index eb966b30d5..9e2fba6f08 100644 --- a/packages/accounts/test/providers.test.ts +++ b/packages/accounts/test/providers.test.ts @@ -1,10 +1,12 @@ -import { beforeAll, describe, expect, jest, test } from '@jest/globals'; +import { afterAll, beforeAll, describe, expect, jest, test } from '@jest/globals'; import { KeyPair } from '@near-js/crypto'; import { ErrorMessages } from '@near-js/utils'; import { base58 } from '@scure/base'; import { createAccount, deployContract, generateUniqueString, setUpTestConnection, sleep, waitFor } from './test-utils'; +import { Worker } from 'near-workspaces'; + jest.setTimeout(60000); let provider; @@ -16,6 +18,14 @@ beforeAll(async () => { provider = near.connection.provider; }); +afterAll(async () => { + const worker = near.worker as Worker; + + if (!worker) return; + + await worker.tearDown(); +}); + describe('providers', () => { test('txStatus with string hash and buffer hash', async () => { const sender = await createAccount(near); diff --git a/packages/accounts/test/test-utils.js b/packages/accounts/test/test-utils.js index 0691943fe4..931fb922f0 100644 --- a/packages/accounts/test/test-utils.js +++ b/packages/accounts/test/test-utils.js @@ -75,7 +75,9 @@ export async function setUpTestConnection() { return { accountCreator: new LocalAccountCreator(new Account(config.masterAccount, connection.provider, connection.signer), BigInt('500000000000000000000000000')), connection, - keyStore + keyStore, + // return worker, so we can gracefully shut down tests + worker: config.worker || undefined }; } From c349775eaac12465d098229ba2e51da7691b5c4e Mon Sep 17 00:00:00 2001 From: denbite Date: Sat, 17 May 2025 09:20:14 +0200 Subject: [PATCH 57/57] chore: reomove override from package.json --- package.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/package.json b/package.json index ef2b4e8249..4a80d4c8f2 100644 --- a/package.json +++ b/package.json @@ -37,13 +37,5 @@ "resolutions": { "near-sandbox": "0.0.18", "near-api-js": "workspace:*" - }, - "pnpm": { - "overrides": { - "packages": "link:packages" - } - }, - "dependencies": { - "packages": "link:packages" } }