diff --git a/.gitignore b/.gitignore index 3acd809f..c30b9de4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,4 @@ node_modules package-lock.json .DS_Store **/tsconfig.tsbuildinfo -**/*.js.map \ No newline at end of file +**/*.js.map diff --git a/docs/source/conf.py b/docs/source/conf.py index 282135f8..6282ae09 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -15,9 +15,9 @@ # built documents. # # The short X.Y version. -version = u'v0.3.0-rc2' +version = u'v0.3.0-rc3' # The full version, including alpha/beta/rc tags. -release = u'v0.3.0-rc2' +release = u'v0.3.0-rc3' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/source/constants.rst b/docs/source/constants.rst index 4084f6f7..d1d54406 100644 --- a/docs/source/constants.rst +++ b/docs/source/constants.rst @@ -18,7 +18,7 @@ js-moi-sdk package. console.log(moi.VERSION) - >> 0.3.0-rc2 + >> 0.3.0-rc3 MOI Derivation Path ------------------- diff --git a/docs/source/hierarchical-deterministic-wallet.rst b/docs/source/hierarchical-deterministic-wallet.rst index bf0bb55c..f10b571b 100644 --- a/docs/source/hierarchical-deterministic-wallet.rst +++ b/docs/source/hierarchical-deterministic-wallet.rst @@ -44,53 +44,41 @@ The ``Keystore`` interface represents a keystore object. It has the following pr Wallet ------ + A class representing a Hierarchical Deterministic Wallet that can sign interactions and manage accounts. .. code-block:: javascript // Example - const provider = new JsonRpcProvider("http://localhost:1600"); - const wallet = new Wallet(provider); + const mnemonic = "hollow appear story text start mask salt social child ..."; -Methods -~~~~~~~ + const wallet = await Wallet.fromMnemonic(mnemonic); -.. autofunction:: Wallet#load -.. code-block:: javascript +Creating Instances +====================== - // Example - const privateKey = Buffer.from("...") - wallet.load(privateKey, "secp256k1") +- Create a wallet instance from private key -.. autofunction:: Wallet#isInitialized + .. code-block:: javascript -.. code-block:: javascript + const privateKey = "0x..."; - // Example - const isInitialized = wallet.isInitialized(); - console.log(isInitialized) - - >> true - -.. autofunction:: Wallet#createRandom + const wallet = new Wallet(privateKey, CURVE.SECP256K1); + +- Create a wallet instance from a mnemonic -.. code-block:: javascript + .. code-block:: javascript - // Example - await wallet.createRandom(); + const mnemonic = "hollow appear story text start mask salt social child ..."; -.. autofunction:: Wallet#generateKeystore + const wallet = await Wallet.fromMnemonic(mnemonic); -.. code-block:: javascript +- Create a wallet instance from JSON keystore - // Example - const keystore = await wallet.generateKeystore("CZ%90$DI"); - console.log(keystore); + .. code-block:: javascript - // Output - /* - { + const keystore = `{ "cipher": "aes-128-ctr", "ciphertext": "...", "cipherparams": { @@ -98,115 +86,83 @@ Methods }, "kdf": "scrypt", "kdfparams": { - ... + "n": 4096, + "r": 8, + "p": 1, + "dklen": 32, + "salt": "..." }, "mac": "..." - } - */ + }`; + const password = "YOUR_PASSWORD_HERE"; -.. autofunction:: Wallet#fromMnemonic + const wallet = await Wallet.fromKeystore(keystore, password); -.. code-block:: javascript - - // Example - const mnemonic = "hollow appear story text start mask salt social child ..."; - const path = "m/44'/7567'/0'/0/1"; - await wallet.fromMnemonic(mnemonic, path); +- Create a wallet instance from a random mnemonic -.. autofunction:: Wallet#fromKeystore + .. code-block:: javascript -.. code-block:: javascript + const wallet = await Wallet.createRandom(); - // Example - const keystore = { - "cipher": "aes-128-ctr", - "ciphertext": "...", - "cipherparams": { - "IV": "..." - }, - "kdf": "scrypt", - "kdfparams": { - ... - }, - "mac": "..." - } - wallet.fromKeystore(keystore, "CZ%90$DI"); +Properties +====================== -.. autofunction:: Wallet#mnemonic +- ``address`` - ``readonly`` ``string`` : The address of the wallet. .. code-block:: javascript - // Example - const mnemonic = wallet.mnemonic(); - console.log(mnemonic); - - >> hollow appear story text start mask salt social child ... + console.log(wallet.address); + >> "0x87925..." -.. autofunction:: Wallet#privateKey +- ``publicKey`` - ``readonly`` ``string``: The public key of the wallet. .. code-block:: javascript - // Example - const privateKey = wallet.privateKey(); - console.log(privateKey); + console.log(wallet.publicKey); + >> "038792..." - >> 084384... - -.. autofunction:: Wallet#publicKey +- ``privateKey`` - ``readonly`` ``string``: The private key of the wallet. .. code-block:: javascript - // Example - const publicKey = wallet.publicKey(); - console.log(publicKey); - - >> 038792... + console.log(wallet.privateKey); + >> "0x87925..." -.. autofunction:: Wallet#curve +- ``mnemonic`` - ``readonly`` ``string``: The mnemonic of the wallet. .. code-block:: javascript - // Example - const curve = wallet.curve(); - console.log(curve); - - >> secp256k1 + console.log(wallet.mnemonic); + >> "hollow appear story text start mask salt social child ..." -.. autofunction:: Wallet#getAddress +- ``curve`` - ``readonly`` ``string``: The curve of the wallet. .. code-block:: javascript - // Example - const address = wallet.getAddress(); - console.log(address); + console.log(wallet.curve); + >> "secp256k1" - >> 0x87925... -.. autofunction:: Wallet#connect +Methods +====================== -.. code-block:: javascript +.. autofunction:: Wallet#sign - // Example - const provider = new VoyageProvider("babylon"); - wallet.connect(provider); -.. autofunction:: Wallet#sign +**Example** .. code-block:: javascript - // Example - const message = "Hello, MOI"; - const sigAlgo = wallet.signingAlgorithms["ecdsa_secp256k1"]; - const signature = wallet.sign(Buffer.from(message), sigAlgo); - console.log(signature); + const message = "Hello, MOI"; + const algo = wallet.signingAlgorithms["ecdsa_secp256k1"]; - >> 0146304402201546497d46ed2ad7b1b77d1cdf383a28d988197bcad268be7163ebdf2f70645002207768e4225951c02a488713caf32d76ed8ea0bf3d7706128c59ee01788aac726402 + const signature = wallet.sign(Buffer.from(message), algo); + >> "0146304402201546497d46ed2ad7b1b77d1cdf383a28d988197bcad268be7163ebdf2f70645002207768e4225951c02a488713caf32d76ed8ea0bf3d7706128c59ee..." .. autofunction:: Wallet#signInteraction .. code-block:: javascript - // Example const address = "0x870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b"; const interaction = { type: IxType.ASSET_CREATE, @@ -224,10 +180,22 @@ Methods const signedIxn = wallet.signInteraction(interaction, sigAlgo); console.log(signedIxn) - // Ouptut + // Output /* { ix_args:'0e9f0203131696049608900c900c930ca30cb60c03870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c80e7f063363636161604d4f49130d41', signature: '01463044022059e8e9839a02d2a0b2585e2267400826f91e575eb27cb89485d2deab697c5a34022020d71b2d3caa8c0b003849a2cb4effdbfd32028357db335549a75c82dd329f8902' } */ + +.. autofunction:: Wallet#generateKeystore + +.. autofunction:: Wallet.fromMnemonic + +.. autofunction:: Wallet.fromMnemonicSync + +.. autofunction:: Wallet.fromKeystore + +.. autofunction:: Wallet.createRandom + +.. autofunction:: Wallet.createRandomSync \ No newline at end of file diff --git a/docs/source/logic.rst b/docs/source/logic.rst index 1f7a97a9..73da5896 100644 --- a/docs/source/logic.rst +++ b/docs/source/logic.rst @@ -128,9 +128,10 @@ applications on the MOI network. // Example const initWallet = async () => { const mnemonic = "mother clarify push liquid ordinary social track ..."; + const wallet = await Wallet.fromMnemonic(mnemonic); const provider = new JsonRpcProvider("http://localhost:1600/"); - const wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic); + wallet.connect(provider); + return wallet; } @@ -220,9 +221,11 @@ Functions // Example const initWallet = async () => { const mnemonic = "mother clarify push liquid ordinary social track ..."; + const wallet = await Wallet.fromMnemonic(mnemonic); const provider = new JsonRpcProvider("http://localhost:1600/"); - const wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic); + + wallet.connect(provider); + return wallet; } @@ -230,6 +233,15 @@ Functions const wallet = await initWallet(); const logicDriver = await getLogicDriver(logicId, wallet); +.. warning:: + When the logic driver is initialized with a provider, + any attempt to execute a mutating routine will trigger the SDK to + raise an exception. The error message associated with this exception + will state: **"Mutating routine calls require a signer to be initialized"**. + Developers should ensure they should pass signer instance while + doing mutating routine calls to avoid encountering this exception. + + Usage ~~~~~ diff --git a/package.json b/package.json index b5900cad..d8e8f19a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moi", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "JavaScript library to interact with MOI Protocol via RPC API", "private": true, "main": "packages/js-moi/dist/index.js", diff --git a/packages/js-moi-bip39/package.json b/packages/js-moi-bip39/package.json index 34bee874..411ae8cd 100644 --- a/packages/js-moi-bip39/package.json +++ b/packages/js-moi-bip39/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-bip39", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Bitcoin BIP39 package for deterministic key generation using mnemonic code", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -24,10 +24,10 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-hdnode": "^0.3.0-rc2", - "js-moi-signer": "^0.3.0-rc2", - "js-moi-constants": "^0.3.0-rc2", - "js-moi-utils": "^0.3.0-rc2", + "js-moi-hdnode": "^0.3.0-rc3", + "js-moi-signer": "^0.3.0-rc3", + "js-moi-constants": "^0.3.0-rc3", + "js-moi-utils": "^0.3.0-rc3", "buffer": "^6.0.3", "@noble/hashes": "^1.1.5" } diff --git a/packages/js-moi-constants/dist/version.d.ts b/packages/js-moi-constants/dist/version.d.ts index 34647d64..fea99eee 100644 --- a/packages/js-moi-constants/dist/version.d.ts +++ b/packages/js-moi-constants/dist/version.d.ts @@ -1 +1 @@ -export declare const VERSION = "0.3.0-rc2"; +export declare const VERSION = "0.3.0-rc3"; diff --git a/packages/js-moi-constants/dist/version.js b/packages/js-moi-constants/dist/version.js index d3cb9040..d50df038 100644 --- a/packages/js-moi-constants/dist/version.js +++ b/packages/js-moi-constants/dist/version.js @@ -1,4 +1,4 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VERSION = void 0; -exports.VERSION = "0.3.0-rc2"; +exports.VERSION = "0.3.0-rc3"; diff --git a/packages/js-moi-constants/package.json b/packages/js-moi-constants/package.json index 53dbee31..5c92e49b 100644 --- a/packages/js-moi-constants/package.json +++ b/packages/js-moi-constants/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-constants", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Collection of constant variables used in js-moi-sdk", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/js-moi-constants/src/version.ts b/packages/js-moi-constants/src/version.ts index 02931389..e2c4e3be 100644 --- a/packages/js-moi-constants/src/version.ts +++ b/packages/js-moi-constants/src/version.ts @@ -1 +1 @@ -export const VERSION = "0.3.0-rc2"; +export const VERSION = "0.3.0-rc3"; diff --git a/packages/js-moi-hdnode/package.json b/packages/js-moi-hdnode/package.json index 8fe93c1a..b9ba8651 100644 --- a/packages/js-moi-hdnode/package.json +++ b/packages/js-moi-hdnode/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-hdnode", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "BIP32 HD Node for cryptocurrency key management.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,7 +23,7 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-utils": "^0.3.0-rc2", + "js-moi-utils": "^0.3.0-rc3", "buffer": "^6.0.3", "hdkey": "^2.1.0", "assert": "^2.0.0", diff --git a/packages/js-moi-logic/README.md b/packages/js-moi-logic/README.md index 6f20d8dd..62ba8979 100644 --- a/packages/js-moi-logic/README.md +++ b/packages/js-moi-logic/README.md @@ -14,7 +14,6 @@ [![pulls count](https://img.shields.io/github/issues-pr/sarvalabs/js-moi-sdk?style=for-the-badge&color=brightgreen)][pullslink] ![test status](https://img.shields.io/github/actions/workflow/status/sarvalabs/js-moi-sdk/test.yml?label=test&style=for-the-badge) - # js-moi-wallet This is a sub-package of [js-moi-sdk](https://github.com/sarvalabs/js-moi-sdk). @@ -22,6 +21,7 @@ This is a sub-package of [js-moi-sdk](https://github.com/sarvalabs/js-moi-sdk). The **js-moi-wallet** package represents a Hierarchical Deterministic Wallet capable of signing interactions and managing accounts. It provides a convenient interface for managing multiple accounts, generating keys, and securely signing interactions. ## Installation + Install the latest [release](https://github.com/sarvalabs/js-moi-sdk/releases) using the following command. ```sh @@ -34,11 +34,13 @@ npm install js-moi-wallet import { Wallet } from "js-moi-wallet"; import { getLogicDriver } from "js-moi-logic"; - const initWallet = async () => { + const initWallet = async () => { const mnemonic = "mother clarify push liquid ordinary social track ..."; + const wallet = await Wallet.fromMnemonic(mnemonic); const provider = new JsonRpcProvider("http://localhost:1600/"); - const wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic); + + wallet.connect(provider); + return wallet; } @@ -57,14 +59,17 @@ npm install js-moi-wallet ``` ## Contributing + Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions. ## License + © 2023 Sarva Labs Inc. & MOI Protocol Developers. This project is licensed under either of + - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) ([`LICENSE-APACHE`](LICENSE-APACHE)) - [MIT license](https://opensource.org/licenses/MIT) ([`LICENSE-MIT`](LICENSE-MIT)) diff --git a/packages/js-moi-logic/__tests__/logic.test.ts b/packages/js-moi-logic/__tests__/logic.test.ts index deea3efb..ea79fe03 100644 --- a/packages/js-moi-logic/__tests__/logic.test.ts +++ b/packages/js-moi-logic/__tests__/logic.test.ts @@ -7,18 +7,18 @@ import { LogicFactory } from "../src/logic-factory"; import manifest from "../manifests/erc20.json"; const HOST = "http://localhost:1600/"; -const MNEMONIC = "main story burst wonder sausage spice okay pioneer person unaware force bubble"; +const MNEMONIC = "visa security tobacco hood forget rate exhibit habit deny good sister slender"; const INITIAL_SUPPLY = 100000000; const SYMBOL = "MOI"; const RECEIVER = "0x4cdc9a1430ca00cbaaab5dcd858236ba75e64b863d69fa799d31854e103ddf72"; -const DEVIATION_PATH = "m/44'/6174'/0'/0/1"; +const PATH = "m/44'/6174'/0'/0/1"; const PROVIDER = new JsonRpcProvider(HOST); let wallet: Wallet; beforeAll(async () => { - wallet = new Wallet(PROVIDER); - await wallet.fromMnemonic(MNEMONIC, DEVIATION_PATH); + wallet = await Wallet.fromMnemonic(MNEMONIC, PATH); + wallet.connect(PROVIDER); }); it("should initialize the wallet", async () => { @@ -158,4 +158,56 @@ describe("Logic", () => { }).rejects.toThrow(`The provided slot "${invalidKey}" does not exist.`); }); }); + + describe("logic driver initialized using provider", () => { + let logic: LogicDriver; + + beforeAll(async () => { + if(logicId == null) { + expect(logicId).toBeDefined(); + return; + }; + + logic = await getLogicDriver(logicId, PROVIDER); + }); + + it("should able to retrieve balance of the account", async () => { + const { balance } = await logic.routines.BalanceOf(wallet.getAddress()); + + expect(balance).toBeGreaterThan(0); + }); + + it("should return object when multiple values are returned", async () => { + const values = await logic.routines.DoubleReturnValue(wallet.getAddress()); + + expect(values).toBeDefined(); + + const { symbol, supply } = values; + + expect(typeof symbol).toBe('string'); + expect(typeof supply).toBe('number'); + }); + + it("should throw an exception in mutating routine call", async () => { + const amount = Math.floor(Math.random() * 1000); + + expect(async () => { + await logic.routines.Transfer(RECEIVER, amount); + }).rejects.toThrow("Mutating routine calls require a signer to be initialized."); + }); + + it("should be able to read from persistent storage", async () => { + const symbol = await logic.persistentState.get("symbol"); + + expect(symbol).toBe(SYMBOL); + }); + + it("should throw an exception when reading from persistent storage with invalid key", async () => { + const invalidKey = "invalid-key"; + + expect(async () => { + await logic.persistentState.get(invalidKey); + }).rejects.toThrow(`The provided slot "${invalidKey}" does not exist.`); + }); + }); }) diff --git a/packages/js-moi-logic/__tests__/utils/utils.ts b/packages/js-moi-logic/__tests__/utils/utils.ts index 5aae18cf..8d3bfbd9 100644 --- a/packages/js-moi-logic/__tests__/utils/utils.ts +++ b/packages/js-moi-logic/__tests__/utils/utils.ts @@ -4,8 +4,9 @@ import { Wallet } from "js-moi-wallet"; export const initializeWallet = async (provider: JsonRpcProvider, mnemonic: string): Promise => { const derivationPath = "m/44'/6174'/7020'/0/0"; - const wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic, derivationPath); + const wallet = await Wallet.fromMnemonic(mnemonic, derivationPath); + + wallet.connect(provider); return wallet; } \ No newline at end of file diff --git a/packages/js-moi-logic/dist/logic-base.d.ts b/packages/js-moi-logic/dist/logic-base.d.ts index 8a1c8bce..10473ab7 100644 --- a/packages/js-moi-logic/dist/logic-base.d.ts +++ b/packages/js-moi-logic/dist/logic-base.d.ts @@ -1,4 +1,5 @@ import { LogicManifest, ManifestCoder } from "js-moi-manifest"; +import type { AbstractProvider } from "js-moi-providers"; import { InteractionCallResponse, InteractionResponse, LogicPayload } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { IxType } from "js-moi-utils"; @@ -7,13 +8,14 @@ import { LogicIxRequest, RoutineOption } from "../types/logic"; import ElementDescriptor from "./element-descriptor"; /** * This abstract class extends the ElementDescriptor class and serves as a base - class for logic-related operations. + * class for logic-related operations. * It defines common properties and abstract methods that subclasses should implement. */ export declare abstract class LogicBase extends ElementDescriptor { - protected signer: Signer; + protected signer?: Signer; + protected provider: AbstractProvider; protected manifestCoder: ManifestCoder; - constructor(manifest: LogicManifest.Manifest, signer: Signer); + constructor(manifest: LogicManifest.Manifest, arg: Signer | AbstractProvider); protected abstract createPayload(ixObject: LogicIxObject): LogicPayload; protected abstract getIxType(): IxType; protected abstract processResult(response: LogicIxResponse, timeout?: number): Promise; @@ -24,11 +26,11 @@ export declare abstract class LogicBase extends ElementDescriptor { */ protected getLogicId(): string; /** - * Updates the signer or establishes a connection with a new signer. + * Updates the signer and provider instances for the LogicBase instance. * - * @param {Signer} signer - The updated signer object or the new signer object to connect. + * @param {Signer | AbstractProvider} arg - The signer or provider instance. */ - connect(signer: Signer): void; + connect(arg: AbstractProvider | Signer): void; /** * Executes a routine with the given arguments and returns the interaction response. * diff --git a/packages/js-moi-logic/dist/logic-base.js b/packages/js-moi-logic/dist/logic-base.js index 8fe6d190..a71b40fc 100644 --- a/packages/js-moi-logic/dist/logic-base.js +++ b/packages/js-moi-logic/dist/logic-base.js @@ -5,22 +5,23 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); exports.LogicBase = void 0; const js_moi_manifest_1 = require("js-moi-manifest"); +const js_moi_signer_1 = require("js-moi-signer"); const js_moi_utils_1 = require("js-moi-utils"); const element_descriptor_1 = __importDefault(require("./element-descriptor")); const DEFAULT_FUEL_PRICE = 1; -const DEFAULT_FUEL_LIMIT = 5000; /** * This abstract class extends the ElementDescriptor class and serves as a base - class for logic-related operations. + * class for logic-related operations. * It defines common properties and abstract methods that subclasses should implement. */ class LogicBase extends element_descriptor_1.default { signer; + provider; manifestCoder; - constructor(manifest, signer) { + constructor(manifest, arg) { super(manifest.elements); this.manifestCoder = new js_moi_manifest_1.ManifestCoder(this.elements, this.classDefs); - this.signer = signer; + this.connect(arg); } /** * Returns the logic ID associated with the LogicBase instance. @@ -31,12 +32,17 @@ class LogicBase extends element_descriptor_1.default { return ""; } /** - * Updates the signer or establishes a connection with a new signer. + * Updates the signer and provider instances for the LogicBase instance. * - * @param {Signer} signer - The updated signer object or the new signer object to connect. + * @param {Signer | AbstractProvider} arg - The signer or provider instance. */ - connect(signer) { - this.signer = signer; + connect(arg) { + if (arg instanceof js_moi_signer_1.Signer) { + this.signer = arg; + this.provider = arg.getProvider(); + return; + } + this.provider = arg; } /** * Executes a routine with the given arguments and returns the interaction response. @@ -56,7 +62,7 @@ class LogicBase extends element_descriptor_1.default { const { type, params } = this.processArguments(ixObject, method, option); switch (type) { case "call": { - const response = await this.signer.call(params); + const response = await this.provider.call(params); return { ...response, result: this.processResult.bind(this, { @@ -66,9 +72,15 @@ class LogicBase extends element_descriptor_1.default { }; } case "estimate": { - return this.signer.estimateFuel(params); + if (!this.signer?.isInitialized()) { + js_moi_utils_1.ErrorUtils.throwError("Mutating routine calls require a signer to be initialized.", js_moi_utils_1.ErrorCode.NOT_INITIALIZED); + } + return this.provider.estimateFuel(params); } case "send": { + if (!this.signer?.isInitialized()) { + js_moi_utils_1.ErrorUtils.throwError("Mutating routine calls require a signer to be initialized.", js_moi_utils_1.ErrorCode.NOT_INITIALIZED); + } const response = await this.signer.sendInteraction(params); return { ...response, @@ -144,8 +156,6 @@ class LogicBase extends element_descriptor_1.default { arguments: args }; ixObject.call = async () => { - option.fuelLimit = option.fuelLimit != null ? option.fuelLimit : await ixObject.estimateFuel(); - option.fuelPrice = option.fuelPrice != null ? option.fuelPrice : DEFAULT_FUEL_PRICE; return this.executeRoutine(ixObject, "call", option); }; ixObject.send = async () => { @@ -154,8 +164,6 @@ class LogicBase extends element_descriptor_1.default { return this.executeRoutine(ixObject, "send", option); }; ixObject.estimateFuel = () => { - option.fuelLimit = option.fuelLimit != null ? option.fuelLimit : DEFAULT_FUEL_LIMIT; - option.fuelPrice = option.fuelPrice != null ? option.fuelPrice : DEFAULT_FUEL_PRICE; return this.executeRoutine(ixObject, "estimate", option); }; ixObject.createPayload = () => { diff --git a/packages/js-moi-logic/dist/logic-descriptor.d.ts b/packages/js-moi-logic/dist/logic-descriptor.d.ts index e9a261e4..483c3829 100644 --- a/packages/js-moi-logic/dist/logic-descriptor.d.ts +++ b/packages/js-moi-logic/dist/logic-descriptor.d.ts @@ -1,4 +1,5 @@ import { LogicManifest } from "js-moi-manifest"; +import type { AbstractProvider } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { LogicBase } from "./logic-base"; import { LogicId } from "./logic-id"; @@ -17,7 +18,7 @@ export declare abstract class LogicDescriptor extends LogicBase { protected engine: EngineKind; protected sealed: boolean; protected assetLogic: boolean; - constructor(logicId: string, manifest: LogicManifest.Manifest, signer: Signer); + constructor(logicId: string, manifest: LogicManifest.Manifest, arg: Signer | AbstractProvider); /** * Returns the logic id of the logic. * diff --git a/packages/js-moi-logic/dist/logic-descriptor.js b/packages/js-moi-logic/dist/logic-descriptor.js index f714fade..92f880cd 100644 --- a/packages/js-moi-logic/dist/logic-descriptor.js +++ b/packages/js-moi-logic/dist/logic-descriptor.js @@ -21,8 +21,8 @@ class LogicDescriptor extends logic_base_1.LogicBase { engine; sealed; assetLogic; - constructor(logicId, manifest, signer) { - super(manifest, signer); + constructor(logicId, manifest, arg) { + super(manifest, arg); this.logicId = new logic_id_1.LogicId(logicId); this.manifest = manifest; this.encodedManifest = js_moi_manifest_1.ManifestCoder.encodeManifest(this.manifest); diff --git a/packages/js-moi-logic/dist/logic-driver.d.ts b/packages/js-moi-logic/dist/logic-driver.d.ts index 028f9fa0..523340df 100644 --- a/packages/js-moi-logic/dist/logic-driver.d.ts +++ b/packages/js-moi-logic/dist/logic-driver.d.ts @@ -1,5 +1,5 @@ import { LogicManifest } from "js-moi-manifest"; -import { LogicPayload, Options } from "js-moi-providers"; +import { LogicPayload, Options, type AbstractProvider } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { IxType } from "js-moi-utils"; import { LogicIxObject, LogicIxResponse } from "../types/interaction"; @@ -14,6 +14,7 @@ export declare class LogicDriver any> readonly persistentState: PersistentState; readonly ephemeralState: EphemeralState; constructor(logicId: string, manifest: LogicManifest.Manifest, signer: Signer); + constructor(logicId: string, manifest: LogicManifest.Manifest, provider: AbstractProvider); /** * Creates the persistent and ephemeral states for the logic driver, if available in logic manifest. @@ -61,13 +62,37 @@ export declare class LogicDriver any> */ protected processResult(response: LogicIxResponse, timeout?: number): Promise; } +type LogicRoutines = Record any>; +interface GetLogicDriver { + /** + * Returns a logic driver instance based on the given logic id. + * + * @param {string} logicId - The logic id of the logic. + * @param {Signer} signer - The signer instance. + * @param {Options} options - The custom tesseract options for retrieving + * logic manifest. (optional) + * @returns {Promise} A promise that resolves to a LogicDriver instance. + */ + (logicId: string, signer: Signer, options?: Options): Promise>; + /** + * Returns a logic driver instance based on the given logic id. + * + * @param {string} logicId - The logic id of the logic. + * @param {AbstractProvider} provider - The provider instance. + * @param {Options} options - The custom tesseract options for retrieving + * logic manifest. (optional) + * @returns {Promise} A promise that resolves to a LogicDriver instance. + */ + (logicId: string, provider: AbstractProvider, options?: Options): Promise>; +} /** * Returns a logic driver instance based on the given logic id. * * @param {string} logicId - The logic id of the logic. - * @param {Signer} signer - The signer or provider instance. + * @param {Signer | AbstractProvider} signerOrProvider - The instance of the `Signer` or `AbstractProvider`. * @param {Options} options - The custom tesseract options for retrieving - * logic manifest. (optional) + * * @returns {Promise} A promise that resolves to a LogicDriver instance. */ -export declare const getLogicDriver: any>>(logicId: string, signer: Signer, options?: Options) => Promise>; +export declare const getLogicDriver: GetLogicDriver; +export {}; diff --git a/packages/js-moi-logic/dist/logic-driver.js b/packages/js-moi-logic/dist/logic-driver.js index 6c8b568f..ac2848d1 100644 --- a/packages/js-moi-logic/dist/logic-driver.js +++ b/packages/js-moi-logic/dist/logic-driver.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getLogicDriver = exports.LogicDriver = void 0; +const js_moi_signer_1 = require("js-moi-signer"); const js_moi_utils_1 = require("js-moi-utils"); const logic_descriptor_1 = require("./logic-descriptor"); const state_1 = require("./state"); @@ -11,8 +12,8 @@ class LogicDriver extends logic_descriptor_1.LogicDescriptor { routines = {}; persistentState; ephemeralState; - constructor(logicId, manifest, signer) { - super(logicId, manifest, signer); + constructor(logicId, manifest, arg) { + super(logicId, manifest, arg); this.createState(); this.createRoutines(); } @@ -23,7 +24,7 @@ class LogicDriver extends logic_descriptor_1.LogicDescriptor { createState() { const [persistentStatePtr, persistentStateExists] = this.hasPersistentState(); if (persistentStateExists) { - const persistentState = new state_1.PersistentState(this.logicId.hex(), this.elements.get(persistentStatePtr), this.manifestCoder, this.signer.getProvider()); + const persistentState = new state_1.PersistentState(this.logicId.hex(), this.elements.get(persistentStatePtr), this.manifestCoder, this.provider); (0, js_moi_utils_1.defineReadOnly)(this, "persistentState", persistentState); } } @@ -138,22 +139,20 @@ exports.LogicDriver = LogicDriver; * Returns a logic driver instance based on the given logic id. * * @param {string} logicId - The logic id of the logic. - * @param {Signer} signer - The signer or provider instance. + * @param {Signer | AbstractProvider} signerOrProvider - The instance of the `Signer` or `AbstractProvider`. * @param {Options} options - The custom tesseract options for retrieving - * logic manifest. (optional) + * * @returns {Promise} A promise that resolves to a LogicDriver instance. */ -const getLogicDriver = async (logicId, signer, options) => { - try { - const provider = signer.getProvider(); - const manifest = await provider.getLogicManifest(logicId, "JSON", options); - if (typeof manifest === "object") { - return new LogicDriver(logicId, manifest, signer); - } +const getLogicDriver = async (logicId, signerOrProvider, options) => { + const provider = signerOrProvider instanceof js_moi_signer_1.Signer ? signerOrProvider.getProvider() : signerOrProvider; + const manifest = await provider.getLogicManifest(logicId, "JSON", options); + if (typeof manifest !== "object") { js_moi_utils_1.ErrorUtils.throwError("Invalid logic manifest", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); } - catch (err) { - throw err; - } + // below check added for type safety + return signerOrProvider instanceof js_moi_signer_1.Signer + ? new LogicDriver(logicId, manifest, signerOrProvider) + : new LogicDriver(logicId, manifest, signerOrProvider); }; exports.getLogicDriver = getLogicDriver; diff --git a/packages/js-moi-logic/package.json b/packages/js-moi-logic/package.json index 5fc1e070..dca2d2d0 100644 --- a/packages/js-moi-logic/package.json +++ b/packages/js-moi-logic/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-logic", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Module to interact with MOI Logic Objects.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,10 +23,10 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-manifest": "^0.3.0-rc2", - "js-moi-utils": "^0.3.0-rc2", - "js-moi-providers": "^0.3.0-rc2", - "js-moi-signer": "^0.3.0-rc2", + "js-moi-manifest": "^0.3.0-rc3", + "js-moi-utils": "^0.3.0-rc3", + "js-moi-providers": "^0.3.0-rc3", + "js-moi-signer": "^0.3.0-rc3", "blakejs": "^1.2.1", "buffer": "^6.0.3" } diff --git a/packages/js-moi-logic/src/logic-base.ts b/packages/js-moi-logic/src/logic-base.ts index a2b8b2e3..09c9e8dd 100644 --- a/packages/js-moi-logic/src/logic-base.ts +++ b/packages/js-moi-logic/src/logic-base.ts @@ -1,4 +1,5 @@ import { LogicManifest, ManifestCoder } from "js-moi-manifest"; +import type { AbstractProvider } from "js-moi-providers"; import { CallorEstimateIxObject, InteractionCallResponse, InteractionObject, InteractionResponse, LogicPayload } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { ErrorCode, ErrorUtils, IxType } from "js-moi-utils"; @@ -7,22 +8,22 @@ import { LogicIxRequest, RoutineOption } from "../types/logic"; import ElementDescriptor from "./element-descriptor"; const DEFAULT_FUEL_PRICE = 1; -const DEFAULT_FUEL_LIMIT = 5000; /** * This abstract class extends the ElementDescriptor class and serves as a base - class for logic-related operations. + * class for logic-related operations. * It defines common properties and abstract methods that subclasses should implement. */ export abstract class LogicBase extends ElementDescriptor { - protected signer: Signer; + protected signer?: Signer; + protected provider: AbstractProvider; protected manifestCoder: ManifestCoder; - constructor(manifest: LogicManifest.Manifest, signer: Signer) { + constructor(manifest: LogicManifest.Manifest, arg: Signer | AbstractProvider) { super(manifest.elements) this.manifestCoder = new ManifestCoder(this.elements, this.classDefs) - this.signer = signer; + this.connect(arg) } // abstract methods to be implemented by subclasses @@ -43,12 +44,17 @@ export abstract class LogicBase extends ElementDescriptor { } /** - * Updates the signer or establishes a connection with a new signer. + * Updates the signer and provider instances for the LogicBase instance. * - * @param {Signer} signer - The updated signer object or the new signer object to connect. + * @param {Signer | AbstractProvider} arg - The signer or provider instance. */ - public connect(signer: Signer): void { - this.signer = signer; + public connect(arg: AbstractProvider | Signer): void { + if (arg instanceof Signer) { + this.signer = arg; + this.provider = arg.getProvider(); + return; + } + this.provider = arg; } /** @@ -71,10 +77,10 @@ export abstract class LogicBase extends ElementDescriptor { } const { type, params } = this.processArguments(ixObject, method, option); - + switch (type) { case "call": { - const response = await this.signer.call(params as CallorEstimateIxObject); + const response = await this.provider.call(params as CallorEstimateIxObject); return { ...response, @@ -85,9 +91,23 @@ export abstract class LogicBase extends ElementDescriptor { }; } case "estimate": { - return this.signer.estimateFuel(params as CallorEstimateIxObject); + if (!this.signer?.isInitialized()) { + ErrorUtils.throwError( + "Mutating routine calls require a signer to be initialized.", + ErrorCode.NOT_INITIALIZED + ); + } + + return this.provider.estimateFuel(params as CallorEstimateIxObject); } case "send": { + if (!this.signer?.isInitialized()) { + ErrorUtils.throwError( + "Mutating routine calls require a signer to be initialized.", + ErrorCode.NOT_INITIALIZED + ); + } + const response = await this.signer.sendInteraction(params); return { @@ -175,9 +195,6 @@ export abstract class LogicBase extends ElementDescriptor { } as LogicIxObject ixObject.call = async (): Promise => { - option.fuelLimit = option.fuelLimit != null ? option.fuelLimit : await ixObject.estimateFuel(); - option.fuelPrice = option.fuelPrice != null ? option.fuelPrice : DEFAULT_FUEL_PRICE; - return this.executeRoutine(ixObject, "call", option) as Promise } @@ -189,9 +206,6 @@ export abstract class LogicBase extends ElementDescriptor { } ixObject.estimateFuel = (): Promise => { - option.fuelLimit = option.fuelLimit != null ? option.fuelLimit : DEFAULT_FUEL_LIMIT; - option.fuelPrice = option.fuelPrice != null ? option.fuelPrice : DEFAULT_FUEL_PRICE; - return this.executeRoutine(ixObject, "estimate", option) as Promise } diff --git a/packages/js-moi-logic/src/logic-descriptor.ts b/packages/js-moi-logic/src/logic-descriptor.ts index 052a576b..55a4c199 100644 --- a/packages/js-moi-logic/src/logic-descriptor.ts +++ b/packages/js-moi-logic/src/logic-descriptor.ts @@ -1,4 +1,5 @@ import { LogicManifest, ManifestCoder } from "js-moi-manifest"; +import type { AbstractProvider } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { LogicBase } from "./logic-base"; import { LogicId } from "./logic-id"; @@ -21,8 +22,8 @@ export abstract class LogicDescriptor extends LogicBase { protected sealed: boolean; protected assetLogic: boolean; - constructor(logicId: string, manifest: LogicManifest.Manifest, signer: Signer) { - super(manifest, signer) + constructor(logicId: string, manifest: LogicManifest.Manifest, arg: Signer | AbstractProvider) { + super(manifest, arg) this.logicId = new LogicId(logicId); this.manifest = manifest; this.encodedManifest = ManifestCoder.encodeManifest(this.manifest); diff --git a/packages/js-moi-logic/src/logic-driver.ts b/packages/js-moi-logic/src/logic-driver.ts index 4094672c..de92470d 100644 --- a/packages/js-moi-logic/src/logic-driver.ts +++ b/packages/js-moi-logic/src/logic-driver.ts @@ -1,5 +1,5 @@ import { LogicManifest } from "js-moi-manifest"; -import { LogicPayload, Options } from "js-moi-providers"; +import { LogicPayload, Options, type AbstractProvider } from "js-moi-providers"; import { Signer } from "js-moi-signer"; import { ErrorCode, ErrorUtils, IxType, defineReadOnly, hexToBytes } from "js-moi-utils"; import { LogicIxObject, LogicIxResponse } from "../types/interaction"; @@ -15,8 +15,10 @@ export class LogicDriver any> = any> public readonly persistentState: PersistentState; public readonly ephemeralState: EphemeralState; - constructor(logicId: string, manifest: LogicManifest.Manifest, signer: Signer) { - super(logicId, manifest, signer) + constructor(logicId: string, manifest: LogicManifest.Manifest, signer: Signer); + constructor(logicId: string, manifest: LogicManifest.Manifest, provider: AbstractProvider); + constructor(logicId: string, manifest: LogicManifest.Manifest, arg: Signer | AbstractProvider) { + super(logicId, manifest, arg) this.createState(); this.createRoutines(); } @@ -33,7 +35,7 @@ export class LogicDriver any> = any> this.logicId.hex(), this.elements.get(persistentStatePtr), this.manifestCoder, - this.signer.getProvider() + this.provider ) defineReadOnly(this, "persistentState", persistentState) @@ -175,29 +177,54 @@ export class LogicDriver any> = any> } } +type LogicRoutines = Record any>; + +interface GetLogicDriver { + /** + * Returns a logic driver instance based on the given logic id. + * + * @param {string} logicId - The logic id of the logic. + * @param {Signer} signer - The signer instance. + * @param {Options} options - The custom tesseract options for retrieving + * logic manifest. (optional) + * @returns {Promise} A promise that resolves to a LogicDriver instance. + */ + (logicId: string, signer: Signer, options?: Options): Promise>; + + /** + * Returns a logic driver instance based on the given logic id. + * + * @param {string} logicId - The logic id of the logic. + * @param {AbstractProvider} provider - The provider instance. + * @param {Options} options - The custom tesseract options for retrieving + * logic manifest. (optional) + * @returns {Promise} A promise that resolves to a LogicDriver instance. + */ + (logicId: string, provider: AbstractProvider, options?: Options): Promise>; +} + /** * Returns a logic driver instance based on the given logic id. * * @param {string} logicId - The logic id of the logic. - * @param {Signer} signer - The signer or provider instance. - * @param {Options} options - The custom tesseract options for retrieving - * logic manifest. (optional) + * @param {Signer | AbstractProvider} signerOrProvider - The instance of the `Signer` or `AbstractProvider`. + * @param {Options} options - The custom tesseract options for retrieving + * * @returns {Promise} A promise that resolves to a LogicDriver instance. */ -export const getLogicDriver = async any>>(logicId: string, signer: Signer, options?: Options): Promise> => { - try { - const provider = signer.getProvider(); - const manifest = await provider.getLogicManifest(logicId, "JSON", options); - - if (typeof manifest === "object") { - return new LogicDriver(logicId, manifest, signer); - } +export const getLogicDriver: GetLogicDriver = async any>>(logicId: string, signerOrProvider: Signer | AbstractProvider, options?: Options): Promise> => { + const provider = signerOrProvider instanceof Signer ? signerOrProvider.getProvider() : signerOrProvider; + const manifest = await provider.getLogicManifest(logicId, "JSON", options); + if (typeof manifest !== "object") { ErrorUtils.throwError( "Invalid logic manifest", ErrorCode.INVALID_ARGUMENT ); - } catch(err) { - throw err; } -} + + // below check added for type safety + return signerOrProvider instanceof Signer + ? new LogicDriver(logicId, manifest, signerOrProvider) + : new LogicDriver(logicId, manifest, signerOrProvider); +} \ No newline at end of file diff --git a/packages/js-moi-manifest/package.json b/packages/js-moi-manifest/package.json index 70766343..74c6b8a9 100644 --- a/packages/js-moi-manifest/package.json +++ b/packages/js-moi-manifest/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-manifest", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Module to encode and decode MOI Logic Engine input and output.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,7 +23,7 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-utils": "^0.3.0-rc2", + "js-moi-utils": "^0.3.0-rc3", "js-polo": "^0.1.3" } } diff --git a/packages/js-moi-providers/__tests__/utils/utils.ts b/packages/js-moi-providers/__tests__/utils/utils.ts index fb6b15f7..9b3df11e 100644 --- a/packages/js-moi-providers/__tests__/utils/utils.ts +++ b/packages/js-moi-providers/__tests__/utils/utils.ts @@ -4,8 +4,9 @@ import { JsonRpcProvider } from "../../src/jsonrpc-provider"; export const initializeWallet = async (provider: JsonRpcProvider, mnemonic: string): Promise => { const derivationPath = "m/44'/6174'/0'/0/1"; - const wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic, derivationPath); + + const wallet = await Wallet.fromMnemonic(mnemonic, derivationPath); + wallet.connect(provider); return wallet; } diff --git a/packages/js-moi-providers/package.json b/packages/js-moi-providers/package.json index b10b8a5b..23729dfe 100644 --- a/packages/js-moi-providers/package.json +++ b/packages/js-moi-providers/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-providers", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Module to connect and interact with MOI network", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,8 +23,8 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-utils": "^0.3.0-rc2", - "js-moi-manifest": "^0.3.0-rc2", + "js-moi-utils": "^0.3.0-rc3", + "js-moi-manifest": "^0.3.0-rc3", "cross-fetch": "^4.0.0", "websocket": "^1.0.34" } diff --git a/packages/js-moi-providers/src/jsonrpc-provider.ts b/packages/js-moi-providers/src/jsonrpc-provider.ts index 1aaa13f2..45554231 100644 --- a/packages/js-moi-providers/src/jsonrpc-provider.ts +++ b/packages/js-moi-providers/src/jsonrpc-provider.ts @@ -53,7 +53,7 @@ export class JsonRpcProvider extends BaseProvider { jsonrpc: "2.0", id: 1 }; - + const response = await fetch(this.host, { method: 'POST', body: JSON.stringify(payload), diff --git a/packages/js-moi-signer/dist/signer.d.ts b/packages/js-moi-signer/dist/signer.d.ts index 8f3c1e75..06efc5d9 100644 --- a/packages/js-moi-signer/dist/signer.d.ts +++ b/packages/js-moi-signer/dist/signer.d.ts @@ -34,8 +34,8 @@ export declare abstract class Signer { /** * Checks the validity of an interaction object by performing various checks. * + * @param {InteractionMethod} method - The method to be checked. * @param {InteractionObject} ixObject - The interaction object to be checked. - * @param {number | bigint} nonce - The nonce (interaction count) for comparison. * @throws {Error} if any of the checks fail, indicating an invalid interaction. */ private checkInteraction; @@ -43,6 +43,7 @@ export declare abstract class Signer { * Prepares the interaction object by populating necessary fields and * performing validity checks. * + * @param {InteractionMethod} method - The method for which the interaction is being prepared. * @param {InteractionObject} ixObject - The interaction object to prepare. * @returns {Promise} A Promise that resolves once the preparation is complete. * @throws {Error} if the interaction object is not valid or if there is diff --git a/packages/js-moi-signer/dist/signer.js b/packages/js-moi-signer/dist/signer.js index c426f3e7..1c13305d 100644 --- a/packages/js-moi-signer/dist/signer.js +++ b/packages/js-moi-signer/dist/signer.js @@ -58,20 +58,20 @@ class Signer { /** * Checks the validity of an interaction object by performing various checks. * + * @param {InteractionMethod} method - The method to be checked. * @param {InteractionObject} ixObject - The interaction object to be checked. - * @param {number | bigint} nonce - The nonce (interaction count) for comparison. * @throws {Error} if any of the checks fail, indicating an invalid interaction. */ - async checkInteraction(ixObject) { - if (ixObject.type === undefined || ixObject.type === null) { + async checkInteraction(method, ixObject) { + if (ixObject.type == null) { js_moi_utils_1.ErrorUtils.throwError("Interaction type is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); } - if (!(0, js_moi_utils_1.isValidAddress)(ixObject.sender)) { - js_moi_utils_1.ErrorUtils.throwError("Invalid sender address", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); - } if (ixObject.sender == null) { js_moi_utils_1.ErrorUtils.throwError("Sender address is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); } + if (!(0, js_moi_utils_1.isValidAddress)(ixObject.sender)) { + js_moi_utils_1.ErrorUtils.throwError("Invalid sender address", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); + } if (this.isInitialized() && ixObject.sender !== this.getAddress()) { js_moi_utils_1.ErrorUtils.throwError("Sender address mismatches with the signer", js_moi_utils_1.ErrorCode.UNEXPECTED_ARGUMENT); } @@ -83,19 +83,21 @@ class Signer { js_moi_utils_1.ErrorUtils.throwError("Invalid receiver address", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); } } - if (ixObject.fuel_price === undefined || ixObject.fuel_price === null) { - js_moi_utils_1.ErrorUtils.throwError("Fuel price is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); - } - if (ixObject.fuel_limit === undefined || ixObject.fuel_limit === null) { - js_moi_utils_1.ErrorUtils.throwError("Fuel limit is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); - } - if (ixObject.fuel_limit === 0) { - js_moi_utils_1.ErrorUtils.throwError("Invalid fuel limit", js_moi_utils_1.ErrorCode.INTERACTION_UNDERPRICED); - } - if (ixObject.nonce !== undefined || ixObject.nonce !== null) { - const nonce = await this.getNonce({ tesseract_number: -1 }); - if (ixObject.nonce < nonce) { - js_moi_utils_1.ErrorUtils.throwError("Invalid nonce", js_moi_utils_1.ErrorCode.NONCE_EXPIRED); + if (method === "send") { + if (ixObject.fuel_price == null) { + js_moi_utils_1.ErrorUtils.throwError("Fuel price is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); + } + if (ixObject.fuel_limit == null) { + js_moi_utils_1.ErrorUtils.throwError("Fuel limit is missing", js_moi_utils_1.ErrorCode.MISSING_ARGUMENT); + } + if (ixObject.fuel_price === 0) { + js_moi_utils_1.ErrorUtils.throwError("Invalid fuel price", js_moi_utils_1.ErrorCode.INTERACTION_UNDERPRICED); + } + if (ixObject.nonce != null) { + const nonce = await this.getNonce({ tesseract_number: -1 }); + if (ixObject.nonce < nonce) { + js_moi_utils_1.ErrorUtils.throwError("Invalid nonce", js_moi_utils_1.ErrorCode.NONCE_EXPIRED); + } } } } @@ -103,17 +105,18 @@ class Signer { * Prepares the interaction object by populating necessary fields and * performing validity checks. * + * @param {InteractionMethod} method - The method for which the interaction is being prepared. * @param {InteractionObject} ixObject - The interaction object to prepare. * @returns {Promise} A Promise that resolves once the preparation is complete. * @throws {Error} if the interaction object is not valid or if there is * an error during preparation. */ - async prepareInteraction(ixObject) { + async prepareInteraction(method, ixObject) { if (!ixObject.sender) { ixObject.sender = this.getAddress(); } - await this.checkInteraction(ixObject); - if (ixObject.nonce == null) { + await this.checkInteraction(method, ixObject); + if (method === "send" && ixObject.nonce == null) { ixObject.nonce = await this.getNonce(); } } @@ -130,7 +133,7 @@ class Signer { async call(ixObject) { // Get the provider const provider = this.getProvider(); - await this.prepareInteraction(ixObject); + await this.prepareInteraction('call', ixObject); return await provider.call(ixObject); } /** @@ -148,7 +151,7 @@ class Signer { async estimateFuel(ixObject) { // Get the provider const provider = this.getProvider(); - await this.prepareInteraction(ixObject); + await this.prepareInteraction('estimateFuel', ixObject); return await provider.estimateFuel(ixObject); } /** @@ -167,7 +170,7 @@ class Signer { const provider = this.getProvider(); // Get the signature algorithm const sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; - await this.prepareInteraction(ixObject); + await this.prepareInteraction('send', ixObject); // Sign the interaction object const ixRequest = this.signInteraction(ixObject, sigAlgo); // Send the interaction request and return the response diff --git a/packages/js-moi-signer/package.json b/packages/js-moi-signer/package.json index d4815355..92804829 100644 --- a/packages/js-moi-signer/package.json +++ b/packages/js-moi-signer/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-signer", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "This package enables users to sign and verify messages in their applications.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,8 +23,8 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-providers": "^0.3.0-rc2", - "js-moi-utils": "^0.3.0-rc2", + "js-moi-providers": "^0.3.0-rc3", + "js-moi-utils": "^0.3.0-rc3", "blake2b": "^2.1.4", "buffer": "^6.0.3", "@noble/hashes": "^1.1.5", diff --git a/packages/js-moi-signer/src/signer.ts b/packages/js-moi-signer/src/signer.ts index 8e2ea35b..b0304226 100644 --- a/packages/js-moi-signer/src/signer.ts +++ b/packages/js-moi-signer/src/signer.ts @@ -4,6 +4,8 @@ import { SigType, SigningAlgorithms } from "../types"; import ECDSA_S256 from "./ecdsa"; import Signature from "./signature"; +type InteractionMethod = "call" | "send" | "estimateFuel"; + /** * An abstract class representing a signer responsible for cryptographic * activities like signing and verification. @@ -71,23 +73,23 @@ export abstract class Signer { /** * Checks the validity of an interaction object by performing various checks. * + * @param {InteractionMethod} method - The method to be checked. * @param {InteractionObject} ixObject - The interaction object to be checked. - * @param {number | bigint} nonce - The nonce (interaction count) for comparison. * @throws {Error} if any of the checks fail, indicating an invalid interaction. */ - private async checkInteraction(ixObject: InteractionObject): Promise { - if(ixObject.type === undefined || ixObject.type === null) { + private async checkInteraction(method: InteractionMethod, ixObject: InteractionObject): Promise { + if(ixObject.type == null) { ErrorUtils.throwError("Interaction type is missing", ErrorCode.MISSING_ARGUMENT) } - if(!isValidAddress(ixObject.sender)) { - ErrorUtils.throwError("Invalid sender address", ErrorCode.INVALID_ARGUMENT); - } - if(ixObject.sender == null) { ErrorUtils.throwError("Sender address is missing", ErrorCode.MISSING_ARGUMENT); } + if(!isValidAddress(ixObject.sender)) { + ErrorUtils.throwError("Invalid sender address", ErrorCode.INVALID_ARGUMENT); + } + if(this.isInitialized() && ixObject.sender !== this.getAddress()) { ErrorUtils.throwError("Sender address mismatches with the signer", ErrorCode.UNEXPECTED_ARGUMENT); } @@ -102,22 +104,24 @@ export abstract class Signer { } } - if(ixObject.fuel_price === undefined || ixObject.fuel_price === null) { - ErrorUtils.throwError("Fuel price is missing", ErrorCode.MISSING_ARGUMENT); - } + if (method === "send") { + if(ixObject.fuel_price == null) { + ErrorUtils.throwError("Fuel price is missing", ErrorCode.MISSING_ARGUMENT); + } - if(ixObject.fuel_limit === undefined || ixObject.fuel_limit === null) { - ErrorUtils.throwError("Fuel limit is missing", ErrorCode.MISSING_ARGUMENT); - } + if(ixObject.fuel_limit == null) { + ErrorUtils.throwError("Fuel limit is missing", ErrorCode.MISSING_ARGUMENT); + } - if(ixObject.fuel_limit === 0) { - ErrorUtils.throwError("Invalid fuel limit", ErrorCode.INTERACTION_UNDERPRICED); - } + if(ixObject.fuel_price === 0) { + ErrorUtils.throwError("Invalid fuel price", ErrorCode.INTERACTION_UNDERPRICED); + } - if(ixObject.nonce !== undefined || ixObject.nonce !== null) { - const nonce = await this.getNonce({ tesseract_number: -1 }); - if(ixObject.nonce < nonce) { - ErrorUtils.throwError("Invalid nonce", ErrorCode.NONCE_EXPIRED); + if(ixObject.nonce != null) { + const nonce = await this.getNonce({ tesseract_number: -1 }); + if(ixObject.nonce < nonce) { + ErrorUtils.throwError("Invalid nonce", ErrorCode.NONCE_EXPIRED); + } } } } @@ -126,19 +130,20 @@ export abstract class Signer { * Prepares the interaction object by populating necessary fields and * performing validity checks. * + * @param {InteractionMethod} method - The method for which the interaction is being prepared. * @param {InteractionObject} ixObject - The interaction object to prepare. * @returns {Promise} A Promise that resolves once the preparation is complete. * @throws {Error} if the interaction object is not valid or if there is * an error during preparation. */ - private async prepareInteraction(ixObject: InteractionObject): Promise { + private async prepareInteraction(method: InteractionMethod, ixObject: InteractionObject): Promise { if (!ixObject.sender) { ixObject.sender = this.getAddress(); } - await this.checkInteraction(ixObject); + await this.checkInteraction(method, ixObject); - if (ixObject.nonce == null) { + if (method === "send" && ixObject.nonce == null) { ixObject.nonce = await this.getNonce(); } } @@ -157,7 +162,7 @@ export abstract class Signer { // Get the provider const provider = this.getProvider(); - await this.prepareInteraction(ixObject); + await this.prepareInteraction('call', ixObject); return await provider.call(ixObject as CallorEstimateIxObject) } @@ -178,7 +183,7 @@ export abstract class Signer { // Get the provider const provider = this.getProvider(); - await this.prepareInteraction(ixObject); + await this.prepareInteraction('estimateFuel', ixObject); return await provider.estimateFuel(ixObject as CallorEstimateIxObject) } @@ -201,7 +206,7 @@ export abstract class Signer { // Get the signature algorithm const sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; - await this.prepareInteraction(ixObject); + await this.prepareInteraction('send', ixObject); // Sign the interaction object const ixRequest = this.signInteraction(ixObject, sigAlgo) diff --git a/packages/js-moi-utils/package.json b/packages/js-moi-utils/package.json index d7cbe936..cad4bcd5 100644 --- a/packages/js-moi-utils/package.json +++ b/packages/js-moi-utils/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-utils", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Collection of utility functions used in js-moi-sdk.", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/js-moi-wallet/README.md b/packages/js-moi-wallet/README.md index 25256de3..dc11b2f3 100644 --- a/packages/js-moi-wallet/README.md +++ b/packages/js-moi-wallet/README.md @@ -14,7 +14,6 @@ [![pulls count](https://img.shields.io/github/issues-pr/sarvalabs/js-moi-sdk?style=for-the-badge&color=brightgreen)][pullslink] ![test status](https://img.shields.io/github/actions/workflow/status/sarvalabs/js-moi-sdk/test.yml?label=test&style=for-the-badge) - # js-moi-wallet This is a sub-package of [js-moi-sdk](https://github.com/sarvalabs/js-moi-sdk). @@ -22,6 +21,7 @@ This is a sub-package of [js-moi-sdk](https://github.com/sarvalabs/js-moi-sdk). The **js-moi-wallet** package represents a Hierarchical Deterministic Wallet capable of signing interactions and managing accounts. It provides a convenient interface for managing multiple accounts, generating keys, and securely signing interactions. ## Installation + Install the latest [release](https://github.com/sarvalabs/js-moi-sdk/releases) using the following command. ```sh @@ -33,23 +33,31 @@ npm install js-moi-wallet ```javascript import { Wallet } from "js-moi-wallet"; - (async() => { - const wallet = new Wallet(); - const mnemonic = "hollow appear story text start mask salt social child ..."; - const path = "m/44'/7567'/0'/0/1"; - await wallet.fromMnemonic(mnemonic, path); - })() + const initWallet = async () => { + const mnemonic = "mother clarify push liquid ordinary social track ..."; + const wallet = await Wallet.fromMnemonic(mnemonic); + const provider = new JsonRpcProvider("http://localhost:1600/"); + + wallet.connect(provider); + + return wallet; + }; + + const wallet = await initWallet(); ``` ## Contributing + Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as below, without any additional terms or conditions. ## License + © 2023 Sarva Labs Inc. & MOI Protocol Developers. This project is licensed under either of + - [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) ([`LICENSE-APACHE`](LICENSE-APACHE)) - [MIT license](https://opensource.org/licenses/MIT) ([`LICENSE-MIT`](LICENSE-MIT)) diff --git a/packages/js-moi-wallet/__tests__/index.test.ts b/packages/js-moi-wallet/__tests__/index.test.ts index d46df254..9fa5c181 100644 --- a/packages/js-moi-wallet/__tests__/index.test.ts +++ b/packages/js-moi-wallet/__tests__/index.test.ts @@ -1,43 +1,178 @@ -import { JsonRpcProvider } from "js-moi-providers"; -import { AssetStandard } from "js-moi-utils"; -import { Wallet } from "../src/index"; - -describe("Test Wallet", () => { - let wallet: Wallet; - - beforeAll(async () => { - const mnemonic = "profit behave tribe dash diet stool crawl general country student smooth oxygen"; - const derivationPath = "m/44'/6174'/0'/0/1"; - const provider = new JsonRpcProvider("http://localhost:1600"); - wallet = new Wallet(provider); - await wallet.fromMnemonic(mnemonic, derivationPath); +import { VoyageProvider, type InteractionRequest } from "js-moi-providers"; +import { AssetStandard, isValidAddress } from "js-moi-utils"; +import { CURVE, Wallet } from "../src/index"; + +const MNEMONIC = "profit behave tribe dash diet stool crawl general country student smooth oxygen"; +const ADDRESS = "0x870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b"; +const DEVIATION_PATH = "m/44'/6174'/0'/0/1"; +const PRIVATE_KEY = "879b415fc8ef34da94aa62a26345b20ea76f7cc9d5485fda428dfe2d6b6d158c"; + +describe("Wallet", () => { + describe("constructor", () => { + test("instance is created with a private key (Buffer type) and a specified curve", async () => { + const wallet = new Wallet(Buffer.from(PRIVATE_KEY, "hex"), CURVE.SECP256K1); + + expect(wallet).toBeDefined(); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.address).toBe(ADDRESS); + expect(wallet.privateKey).toBe(PRIVATE_KEY); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); + + test("Instance is created with a private key (String type) and a specified curve", async () => { + const wallet = new Wallet(PRIVATE_KEY, CURVE.SECP256K1); + + expect(wallet).toBeDefined(); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.address).toBe(ADDRESS); + expect(wallet.privateKey).toBe(PRIVATE_KEY); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); }); - test("Sign Message", async () => { - const message = "Hello, MOI"; - const sigAlgo = wallet.signingAlgorithms["ecdsa_secp256k1"]; - const signature = wallet.sign(Buffer.from(message), sigAlgo); - expect(signature).toBe("0146304402201546497d46ed2ad7b1b77d1cdf383a28d988197bcad268be7163ebdf2f70645002207768e4225951c02a488713caf32d76ed8ea0bf3d7706128c59ee01788aac726402"); + describe("Static methods", () => { + test(Wallet.fromMnemonic.name, async () => { + const wallet = await Wallet.fromMnemonic(MNEMONIC, DEVIATION_PATH); + + expect(wallet.address).toBe(ADDRESS); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.mnemonic).toBe(MNEMONIC); + expect(wallet.curve).toBe(CURVE.SECP256K1); + expect(wallet.privateKey).toBe(PRIVATE_KEY); + }); + + test(Wallet.fromMnemonicSync.name, () => { + const wallet = Wallet.fromMnemonicSync(MNEMONIC, DEVIATION_PATH); + + expect(wallet.address).toBe(ADDRESS); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.mnemonic).toBe(MNEMONIC); + expect(wallet.privateKey).toBe(PRIVATE_KEY); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); + + test(Wallet.createRandom.name, async () => { + const wallet = await Wallet.createRandom(); + + expect(wallet).toBeDefined(); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.mnemonic.split(" ").length).toBe(12); + expect(wallet.privateKey).toBeDefined(); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); + + test(Wallet.createRandomSync.name, () => { + const wallet = Wallet.createRandomSync(); + + expect(wallet.isInitialized()).toBe(true); + expect(wallet).toBeDefined(); + expect(isValidAddress(wallet.address)).toBeTruthy(); + expect(wallet.privateKey).toBeDefined(); + expect(wallet.mnemonic.split(" ").length).toBe(12); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); + + test(Wallet.fromKeystore.name, async () => { + const keystore = `{ + "cipher": "aes-128-ctr", + "ciphertext": "b241d6a71004b0f73397e4e0e1a324ca7e06b314d7694a092e76c898b48d4d6c", + "cipherparams": { + "IV": "c7a6284851f604b70a483bdd18c16c5a" + }, + "kdf": "scrypt", + "kdfparams": { + "n": 4096, + "r": 8, + "p": 1, + "dklen": 32, + "salt": "17584125264a5817c6c46fbbb86c1009815261975168b7edc2dd57b175470880" + }, + "mac": "f7b658893365e6f787db31888859c0531f1434a12ba4a10e548ef869c3428780" + }`; + + const wallet = Wallet.fromKeystore(keystore, "password"); + + expect(wallet).toBeDefined(); + expect(wallet.isInitialized()).toBe(true); + expect(wallet.address).toBe(ADDRESS); + expect(wallet.privateKey).toBe(PRIVATE_KEY); + expect(wallet.curve).toBe(CURVE.SECP256K1); + }); }); - test("Sign Interaction", async () => { - const ixObject = { - type: 3, - nonce: 0, - sender: "0x870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b", - fuel_price: 1, - fuel_limit: 200, - payload: { - standard: AssetStandard.MAS0, - symbol: "SIG", - supply: 1248577 - } - } - const sigAlgo = wallet.signingAlgorithms["ecdsa_secp256k1"] - const ixArgs = wallet.signInteraction(ixObject, sigAlgo) - expect(ixArgs).toEqual({ - ix_args: "0e9f0203131696049608900c900c930ca30cb60c03870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c80e7f06336363616160534947130d41", - signature: "0147304502210089d5c9125fbc605eaa7c51ba1157ed774896cc02483992bb9b3180555ba20c1a02202aad27b8c6ce498a9ce6167924b6286fb13865ec9d9e3767d32b8c6a250b3e9e02" - }) + describe("Instance methods", () => { + let wallet: Wallet; + + beforeEach(() => { + wallet = Wallet.fromMnemonicSync(MNEMONIC, DEVIATION_PATH); + }); + + test("sign", () => { + const message = "Hello, MOI"; + const algo = wallet.signingAlgorithms["ecdsa_secp256k1"]; + const signature = wallet.sign(Buffer.from(message), algo); + + expect(signature).toBe( + "0146304402201546497d46ed2ad7b1b77d1cdf383a28d988197bcad268be7163ebdf2f70645002207768e4225951c02a488713caf32d76ed8ea0bf3d7706128c59ee01788aac726402" + ); + }); + + test("signInteraction", () => { + const ixObject = { + type: 3, + nonce: 0, + sender: wallet.address, + fuel_price: 1, + fuel_limit: 200, + payload: { + standard: AssetStandard.MAS0, + symbol: "SIG", + supply: 1248577, + }, + }; + + const algo = wallet.signingAlgorithms["ecdsa_secp256k1"]; + const ixArgs = wallet.signInteraction(ixObject, algo); + + expect(ixArgs).toBeDefined(); + expect(ixArgs).toMatchObject({ + ix_args: expect.any(String), + signature: expect.any(String), + }); + expect(ixArgs.ix_args).toEqual( + "0e9f0203131696049608900c900c930ca30cb60c03870ad6c5150ea8c0355316974873313004c6b9425a855a06fff16f408b0e0a8b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c80e7f06336363616160534947130d41" + ); + }); + + test("address", () => { + expect(wallet.address).toBe(ADDRESS); + }); + + test("isInitialized", () => { + expect(wallet.isInitialized()).toBe(true); + }); + + test("mnemonic", () => { + expect(wallet.mnemonic).toBe(MNEMONIC); + }); + + test("privateKey", () => { + expect(wallet.privateKey).toBe(PRIVATE_KEY); + }); + + test("connect", () => { + const provider = new VoyageProvider("babylon"); + + wallet.connect(provider); + + expect(wallet.provider).toBe(provider); + }); + + test("generateKeystore", () => { + const keystore = wallet.generateKeystore("password"); + + expect(keystore).toBeDefined(); + expect(keystore).not.toBeNull(); + }); }); -}) +}); diff --git a/packages/js-moi-wallet/dist/wallet.d.ts b/packages/js-moi-wallet/dist/wallet.d.ts index 6f148e00..1843879a 100644 --- a/packages/js-moi-wallet/dist/wallet.d.ts +++ b/packages/js-moi-wallet/dist/wallet.d.ts @@ -1,40 +1,43 @@ /// import { Buffer } from "buffer"; -import { Signer, SigType } from "js-moi-signer"; -import { AbstractProvider, InteractionRequest, InteractionObject } from "js-moi-providers"; +import { AbstractProvider, InteractionObject, InteractionRequest } from "js-moi-providers"; +import { SigType, Signer } from "js-moi-signer"; import { Keystore } from "../types/keystore"; export declare enum CURVE { SECP256K1 = "secp256k1" } /** - * Wallet + * A class representing a wallet that can sign interactions. * - * A class representing a wallet that can sign interactions and manage keys. + * The Wallet implements the Signer API and can be used anywhere a [Signer](https://js-moi-sdk.docs.moi.technology/signer) + * is expected and has all the required properties. + * + * @example + * // creating a wallet from mnemonic + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); + * + * @example + * // creating a wallet from keystore + * const keystore = { ... } + * const wallet = Wallet.fromKeystore(keystore, "password"); + * + * @example + * // Connecting a wallet to a provider + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); + * const provider = new VoyagerProvider("babylon"); + * + * wallet.connect(provider); + * + * @docs https://js-moi-sdk.docs.moi.technology/hierarchical-deterministic-wallet */ export declare class Wallet extends Signer { - constructor(provider?: AbstractProvider); - /** - * Initializes the wallet with a private key, mnemonic, and curve. - * - * @param {Buffer} key - The private key as a Buffer. - * @param {string} curve - The elliptic curve algorithm used for key generation. - * @param {string} mnemonic - The mnemonic associated with the wallet. (optional) - * @throws {Error} if the key is undefined or if an error occurs during the - * initialization process. - */ - load(key: Buffer, curve: string, mnemonic?: string): void; + constructor(key: Buffer | string, curve: string); /** * Checks if the wallet is initialized. * * @returns {boolean} true if the wallet is initialized, false otherwise. */ isInitialized(): boolean; - /** - * Generates a random mnemonic and initializes the wallet from it. - * - * @throws {Error} if there is an error generating the random mnemonic. - */ - createRandom(): Promise; /** * Generates a keystore file from the wallet's private key, encrypted with a password. * @@ -45,58 +48,44 @@ export declare class Wallet extends Signer { */ generateKeystore(password: string): Keystore; /** - * Intializes the wallet from a provided mnemonic. + * Private key associated with the wallet. * - * @param {string} mnemonic - The mnemonic associated with the wallet. - * @param {string} path - The derivation path for the HDNode. (optional) - * @param {string[]} wordlist - The wordlist for the mnemonic. (optional) - * @throws {Error} if there is an error loading the wallet from the mnemonic. - */ - fromMnemonic(mnemonic: string, path?: string, wordlist?: string[]): Promise; - /** - * Initializes the wallet by decrypting and loading the private key from - * a keystore file. - * - * @param {string} keystore The keystore object as a JSON string. - * @param {string} password The password used for decrypting the keystore. - * @throws {Error} if there is an error parsing the keystore, decrypting - * the keystore data, or loading the private key. - */ - fromKeystore(keystore: string, password: string): void; - /** - * Retrieves the private key associated with the wallet. - * - * @returns {string} The private key as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - privateKey(): string; + get privateKey(): string; /** * Retrieves the mnemonic associated with the wallet. * - * @returns {string} The mnemonic as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - mnemonic(): string; + get mnemonic(): string; /** - * Retrieves the public key associated with the wallet. + * Public key associated with the wallet. * - * @returns {string} The public key as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - publicKey(): string; + get publicKey(): string; /** - * Retrieves the curve used by the wallet. + * Curve associated with the wallet. * - * @returns {string} The curve as a string. - * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - curve(): string; + get curve(): string; /** * Retrieves the address associated with the wallet. * * @returns {string} The address as a string. */ getAddress(): string; + /** + * Address associated with the wallet. + * + * @readonly + */ + get address(): string; /** * Connects the wallet to the given provider. * @@ -126,4 +115,74 @@ export declare class Wallet extends Signer { * @throws {Error} if there is an error during signing or serialization. */ signInteraction(ixObject: InteractionObject, sigAlgo: SigType): InteractionRequest; + /** + * Initializes the wallet from a provided mnemonic. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = await Wallet.fromMnemonic(mnemonic); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = await Wallet.fromMnemonic(mnemonic, path); + */ + static fromMnemonic(mnemonic: string, path?: string, wordlist?: string[]): Promise; + /** + * Initializes the wallet from a provided mnemonic synchronously. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = Wallet.fromMnemonicSync(); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = Wallet.fromMnemonicSync(mnemonic, path); + */ + static fromMnemonicSync(mnemonic: string, path?: string, wordlist?: string[]): Wallet; + /** + * Initializes the wallet from a provided keystore. + * + * @param {string} keystore - The keystore to initialize the wallet with. + * @param {string} password - The password used to decrypt the keystore. + * + * @returns {Wallet} a instance of `Wallet`. + * @throws {Error} if there is an error during initialization. + */ + static fromKeystore(keystore: string, password: string): Wallet; + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + static createRandom(): Promise; + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Wallet} a instance of `Wallet`. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + static createRandomSync(): Wallet; } diff --git a/packages/js-moi-wallet/dist/wallet.js b/packages/js-moi-wallet/dist/wallet.js index c5a7b54b..996f7c03 100644 --- a/packages/js-moi-wallet/dist/wallet.js +++ b/packages/js-moi-wallet/dist/wallet.js @@ -27,17 +27,17 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Wallet = exports.CURVE = void 0; -const bip39 = __importStar(require("js-moi-bip39")); -const elliptic_1 = __importDefault(require("elliptic")); -const js_moi_hdnode_1 = require("js-moi-hdnode"); -const crypto_1 = require("crypto"); const buffer_1 = require("buffer"); +const crypto_1 = require("crypto"); +const elliptic_1 = __importDefault(require("elliptic")); +const bip39 = __importStar(require("js-moi-bip39")); const js_moi_constants_1 = require("js-moi-constants"); +const js_moi_hdnode_1 = require("js-moi-hdnode"); const js_moi_signer_1 = require("js-moi-signer"); const js_moi_utils_1 = require("js-moi-utils"); const SigningKeyErrors = __importStar(require("./errors")); -const serializer_1 = require("./serializer"); const keystore_1 = require("./keystore"); +const serializer_1 = require("./serializer"); var CURVE; (function (CURVE) { CURVE["SECP256K1"] = "secp256k1"; @@ -86,28 +86,36 @@ const privateMapSet = (receiver, privateMap, value) => { }; const __vault = new WeakMap(); /** - * Wallet + * A class representing a wallet that can sign interactions. + * + * The Wallet implements the Signer API and can be used anywhere a [Signer](https://js-moi-sdk.docs.moi.technology/signer) + * is expected and has all the required properties. + * + * @example + * // creating a wallet from mnemonic + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); + * + * @example + * // creating a wallet from keystore + * const keystore = { ... } + * const wallet = Wallet.fromKeystore(keystore, "password"); * - * A class representing a wallet that can sign interactions and manage keys. + * @example + * // Connecting a wallet to a provider + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); + * const provider = new VoyagerProvider("babylon"); + * + * wallet.connect(provider); + * + * @docs https://js-moi-sdk.docs.moi.technology/hierarchical-deterministic-wallet */ class Wallet extends js_moi_signer_1.Signer { - constructor(provider) { - super(provider); - __vault.set(this, { - value: void 0 - }); - } - /** - * Initializes the wallet with a private key, mnemonic, and curve. - * - * @param {Buffer} key - The private key as a Buffer. - * @param {string} curve - The elliptic curve algorithm used for key generation. - * @param {string} mnemonic - The mnemonic associated with the wallet. (optional) - * @throws {Error} if the key is undefined or if an error occurs during the - * initialization process. - */ - load(key, curve, mnemonic) { + constructor(key, curve) { try { + super(); + __vault.set(this, { + value: void 0, + }); let privKey, pubKey; if (!key) { js_moi_utils_1.ErrorUtils.throwError("Key is required, cannot be undefined", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); @@ -116,19 +124,19 @@ class Wallet extends js_moi_signer_1.Signer { js_moi_utils_1.ErrorUtils.throwError(`Unsupported curve: ${curve}`, js_moi_utils_1.ErrorCode.UNSUPPORTED_OPERATION); } const ecPrivKey = new elliptic_1.default.ec(curve); - const keyInBytes = (0, js_moi_utils_1.bufferToUint8)(key); + const keyBuffer = key instanceof buffer_1.Buffer ? key : buffer_1.Buffer.from(key, "hex"); + const keyInBytes = (0, js_moi_utils_1.bufferToUint8)(keyBuffer); const keyPair = ecPrivKey.keyFromPrivate(keyInBytes); privKey = keyPair.getPrivate("hex"); pubKey = keyPair.getPublic(true, "hex"); privateMapSet(this, __vault, { _key: privKey, - _mnemonic: mnemonic, _public: pubKey, - _curve: curve + _curve: curve, }); } - catch (err) { - js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); + catch (error) { + js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: error }); } } /** @@ -142,21 +150,6 @@ class Wallet extends js_moi_signer_1.Signer { } return false; } - /** - * Generates a random mnemonic and initializes the wallet from it. - * - * @throws {Error} if there is an error generating the random mnemonic. - */ - async createRandom() { - try { - const _random16Bytes = (0, crypto_1.randomBytes)(16); - var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); - await this.fromMnemonic(mnemonic); - } - catch (err) { - js_moi_utils_1.ErrorUtils.throwError("Failed to create random mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); - } - } /** * Generates a keystore file from the wallet's private key, encrypted with a password. * @@ -170,7 +163,7 @@ class Wallet extends js_moi_signer_1.Signer { js_moi_utils_1.ErrorUtils.throwError("Keystore not found. The wallet has not been loaded or initialized.", js_moi_utils_1.ErrorCode.NOT_INITIALIZED); } try { - const data = buffer_1.Buffer.from(this.privateKey(), "hex"); + const data = buffer_1.Buffer.from(this.privateKey, "hex"); return (0, keystore_1.encryptKeystoreData)(data, password); } catch (err) { @@ -178,51 +171,12 @@ class Wallet extends js_moi_signer_1.Signer { } } /** - * Intializes the wallet from a provided mnemonic. - * - * @param {string} mnemonic - The mnemonic associated with the wallet. - * @param {string} path - The derivation path for the HDNode. (optional) - * @param {string[]} wordlist - The wordlist for the mnemonic. (optional) - * @throws {Error} if there is an error loading the wallet from the mnemonic. - */ - async fromMnemonic(mnemonic, path, wordlist) { - mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); - try { - const seed = await bip39.mnemonicToSeed(mnemonic, undefined); - const masterNode = js_moi_hdnode_1.HDNode.fromSeed(seed); - const childNode = masterNode.derivePath(path ? path : js_moi_constants_1.MOI_DERIVATION_PATH); - this.load(childNode.privateKey(), CURVE.SECP256K1, mnemonic); - } - catch (err) { - js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet from mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); - } - } - /** - * Initializes the wallet by decrypting and loading the private key from - * a keystore file. - * - * @param {string} keystore The keystore object as a JSON string. - * @param {string} password The password used for decrypting the keystore. - * @throws {Error} if there is an error parsing the keystore, decrypting - * the keystore data, or loading the private key. - */ - fromKeystore(keystore, password) { - try { - const keystoreJson = JSON.parse(keystore); - const privateKey = (0, keystore_1.decryptKeystoreData)(keystoreJson, password); - this.load(privateKey, CURVE.SECP256K1); - } - catch (err) { - js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet from keystore", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); - } - } - /** - * Retrieves the private key associated with the wallet. + * Private key associated with the wallet. * - * @returns {string} The private key as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - privateKey() { + get privateKey() { if (this.isInitialized()) { return privateMapGet(this, __vault)._key; } @@ -231,34 +185,33 @@ class Wallet extends js_moi_signer_1.Signer { /** * Retrieves the mnemonic associated with the wallet. * - * @returns {string} The mnemonic as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - mnemonic() { + get mnemonic() { if (this.isInitialized()) { return privateMapGet(this, __vault)._mnemonic; } js_moi_utils_1.ErrorUtils.throwError("Mnemonic not found. The wallet has not been loaded or initialized.", js_moi_utils_1.ErrorCode.NOT_INITIALIZED); } /** - * Retrieves the public key associated with the wallet. + * Public key associated with the wallet. * - * @returns {string} The public key as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - publicKey() { + get publicKey() { if (this.isInitialized()) { return privateMapGet(this, __vault)._public; } js_moi_utils_1.ErrorUtils.throwError("Public key not found. The wallet has not been loaded or initialized.", js_moi_utils_1.ErrorCode.NOT_INITIALIZED); } /** - * Retrieves the curve used by the wallet. + * Curve associated with the wallet. * - * @returns {string} The curve as a string. - * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - curve() { + get curve() { if (this.isInitialized()) { return privateMapGet(this, __vault)._curve; } @@ -270,8 +223,15 @@ class Wallet extends js_moi_signer_1.Signer { * @returns {string} The address as a string. */ getAddress() { - const publicKey = this.publicKey(); - return "0x" + publicKey.slice(2); + return "0x" + this.publicKey.slice(2); + } + /** + * Address associated with the wallet. + * + * @readonly + */ + get address() { + return this.getAddress(); } /** * Connects the wallet to the given provider. @@ -292,21 +252,20 @@ class Wallet extends js_moi_signer_1.Signer { * there is an error during signing. */ sign(message, sigAlgo) { - if (sigAlgo) { - switch (sigAlgo.sigName) { - case "ECDSA_S256": { - const privateKey = this.privateKey(); - const _sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; - const sig = _sigAlgo.sign(buffer_1.Buffer.from(message), privateKey); - const sigBytes = sig.serialize(); - return (0, js_moi_utils_1.bytesToHex)(sigBytes); - } - default: { - js_moi_utils_1.ErrorUtils.throwError("Unsupported signature type", js_moi_utils_1.ErrorCode.UNSUPPORTED_OPERATION); - } + if (sigAlgo == null) { + js_moi_utils_1.ErrorUtils.throwError("Signature type cannot be undefined", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); + } + switch (sigAlgo.sigName) { + case "ECDSA_S256": { + const _sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; + const sig = _sigAlgo.sign(buffer_1.Buffer.from(message), this.privateKey); + const sigBytes = sig.serialize(); + return (0, js_moi_utils_1.bytesToHex)(sigBytes); + } + default: { + js_moi_utils_1.ErrorUtils.throwError("Unsupported signature type", js_moi_utils_1.ErrorCode.UNSUPPORTED_OPERATION); } } - js_moi_utils_1.ErrorUtils.throwError("Signature type cannot be undefiend", js_moi_utils_1.ErrorCode.INVALID_ARGUMENT); } /** * Signs an interaction object using the wallet's private key and the @@ -325,12 +284,146 @@ class Wallet extends js_moi_signer_1.Signer { const signature = this.sign(ixData, sigAlgo); return { ix_args: (0, js_moi_utils_1.bytesToHex)(ixData), - signature: signature + signature: signature, }; } catch (err) { js_moi_utils_1.ErrorUtils.throwError("Failed to sign interaction", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); } } + /** + * Initializes the wallet from a provided mnemonic. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = await Wallet.fromMnemonic(mnemonic); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = await Wallet.fromMnemonic(mnemonic, path); + */ + static async fromMnemonic(mnemonic, path, wordlist) { + try { + mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); + const seed = await bip39.mnemonicToSeed(mnemonic, undefined); + const masterNode = js_moi_hdnode_1.HDNode.fromSeed(seed); + const childNode = masterNode.derivePath(path ? path : js_moi_constants_1.MOI_DERIVATION_PATH); + const wallet = new Wallet(childNode.privateKey(), CURVE.SECP256K1); + privateMapSet(wallet, __vault, { + ...privateMapGet(wallet, __vault), + _mnemonic: mnemonic, + }); + return wallet; + } + catch (error) { + js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet from mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { + originalError: error, + }); + } + } + /** + * Initializes the wallet from a provided mnemonic synchronously. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = Wallet.fromMnemonicSync(); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = Wallet.fromMnemonicSync(mnemonic, path); + */ + static fromMnemonicSync(mnemonic, path, wordlist) { + try { + mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); + const seed = bip39.mnemonicToSeedSync(mnemonic, undefined); + const masterNode = js_moi_hdnode_1.HDNode.fromSeed(seed); + const childNode = masterNode.derivePath(path ? path : js_moi_constants_1.MOI_DERIVATION_PATH); + const wallet = new Wallet(childNode.privateKey(), CURVE.SECP256K1); + privateMapSet(wallet, __vault, { + ...privateMapGet(wallet, __vault), + _mnemonic: mnemonic, + }); + return wallet; + } + catch (error) { + js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet from mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { + originalError: error, + }); + } + } + /** + * Initializes the wallet from a provided keystore. + * + * @param {string} keystore - The keystore to initialize the wallet with. + * @param {string} password - The password used to decrypt the keystore. + * + * @returns {Wallet} a instance of `Wallet`. + * @throws {Error} if there is an error during initialization. + */ + static fromKeystore(keystore, password) { + try { + const privateKey = (0, keystore_1.decryptKeystoreData)(JSON.parse(keystore), password); + return new Wallet(privateKey, CURVE.SECP256K1); + } + catch (err) { + js_moi_utils_1.ErrorUtils.throwError("Failed to load wallet from keystore", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { + originalError: err, + }); + } + } + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + static async createRandom() { + try { + const _random16Bytes = (0, crypto_1.randomBytes)(16); + var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); + return await Wallet.fromMnemonic(mnemonic); + } + catch (err) { + js_moi_utils_1.ErrorUtils.throwError("Failed to create random mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); + } + } + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Wallet} a instance of `Wallet`. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + static createRandomSync() { + try { + const _random16Bytes = (0, crypto_1.randomBytes)(16); + var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); + return Wallet.fromMnemonicSync(mnemonic); + } + catch (err) { + js_moi_utils_1.ErrorUtils.throwError("Failed to create random mnemonic", js_moi_utils_1.ErrorCode.UNKNOWN_ERROR, { originalError: err }); + } + } } exports.Wallet = Wallet; diff --git a/packages/js-moi-wallet/package.json b/packages/js-moi-wallet/package.json index da5359fa..af1e4ad5 100644 --- a/packages/js-moi-wallet/package.json +++ b/packages/js-moi-wallet/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-wallet", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "Module to interact with the MOI accounts.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,12 +23,12 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-hdnode": "^0.3.0-rc2", - "js-moi-signer": "^0.3.0-rc2", - "js-moi-providers": "^0.3.0-rc2", - "js-moi-constants": "^0.3.0-rc2", - "js-moi-utils": "^0.3.0-rc2", - "js-moi-bip39": "^0.3.0-rc2", + "js-moi-hdnode": "^0.3.0-rc3", + "js-moi-signer": "^0.3.0-rc3", + "js-moi-providers": "^0.3.0-rc3", + "js-moi-constants": "^0.3.0-rc3", + "js-moi-utils": "^0.3.0-rc3", + "js-moi-bip39": "^0.3.0-rc3", "buffer": "^6.0.3", "crypto": "npm:crypto-browserify", "elliptic": "^6.5.4", diff --git a/packages/js-moi-wallet/src/wallet.ts b/packages/js-moi-wallet/src/wallet.ts index 4ed3994e..0e351061 100644 --- a/packages/js-moi-wallet/src/wallet.ts +++ b/packages/js-moi-wallet/src/wallet.ts @@ -1,20 +1,20 @@ -import * as bip39 from "js-moi-bip39"; -import elliptic from "elliptic"; -import { HDNode } from "js-moi-hdnode"; -import { randomBytes } from "crypto"; import { Buffer } from "buffer"; +import { randomBytes } from "crypto"; +import elliptic from "elliptic"; +import * as bip39 from "js-moi-bip39"; import { MOI_DERIVATION_PATH } from "js-moi-constants"; -import { Signer, SigType } from "js-moi-signer"; -import { AbstractProvider, InteractionRequest, InteractionObject } from "js-moi-providers"; -import { ErrorCode, ErrorUtils, bytesToHex, bufferToUint8 } from "js-moi-utils"; +import { HDNode } from "js-moi-hdnode"; +import { AbstractProvider, InteractionObject, InteractionRequest } from "js-moi-providers"; +import { SigType, Signer } from "js-moi-signer"; +import { ErrorCode, ErrorUtils, bufferToUint8, bytesToHex } from "js-moi-utils"; import { Keystore } from "../types/keystore"; import * as SigningKeyErrors from "./errors"; -import { serializeIxObject } from "./serializer"; import { decryptKeystoreData, encryptKeystoreData } from "./keystore"; +import { serializeIxObject } from "./serializer"; export enum CURVE { - SECP256K1 = "secp256k1" + SECP256K1 = "secp256k1", } /** @@ -28,14 +28,14 @@ export enum CURVE { */ const privateMapGet = (receiver: any, privateMap: any) => { if (!privateMap.has(receiver)) { - SigningKeyErrors.ErrPrivateGet() + SigningKeyErrors.ErrPrivateGet(); } const descriptor = privateMap.get(receiver); if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; -} +}; /** * Sets the value associated with the receiver in a private map. @@ -49,7 +49,7 @@ const privateMapGet = (receiver: any, privateMap: any) => { */ const privateMapSet = (receiver: any, privateMap: any, value: any) => { if (!privateMap.has(receiver)) { - SigningKeyErrors.ErrPrivateSet() + SigningKeyErrors.ErrPrivateSet(); } const descriptor = privateMap.get(receiver); if (descriptor.set) { @@ -58,67 +58,67 @@ const privateMapSet = (receiver: any, privateMap: any, value: any) => { descriptor.value = value; } return value; -} +}; const __vault = new WeakMap(); /** - * Wallet + * A class representing a wallet that can sign interactions. + * + * The Wallet implements the Signer API and can be used anywhere a [Signer](https://js-moi-sdk.docs.moi.technology/signer) + * is expected and has all the required properties. + * + * @example + * // creating a wallet from mnemonic + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); * - * A class representing a wallet that can sign interactions and manage keys. + * @example + * // creating a wallet from keystore + * const keystore = { ... } + * const wallet = Wallet.fromKeystore(keystore, "password"); + * + * @example + * // Connecting a wallet to a provider + * const wallet = await Wallet.fromMnemonic("hollow appear story text start mask salt social child ..."); + * const provider = new VoyagerProvider("babylon"); + * + * wallet.connect(provider); + * + * @docs https://js-moi-sdk.docs.moi.technology/hierarchical-deterministic-wallet */ export class Wallet extends Signer { - constructor(provider?: AbstractProvider) { - super(provider) - __vault.set(this, { - value: void 0 - }) - } - - /** - * Initializes the wallet with a private key, mnemonic, and curve. - * - * @param {Buffer} key - The private key as a Buffer. - * @param {string} curve - The elliptic curve algorithm used for key generation. - * @param {string} mnemonic - The mnemonic associated with the wallet. (optional) - * @throws {Error} if the key is undefined or if an error occurs during the - * initialization process. - */ - public load(key: Buffer, curve: string, mnemonic?: string) { + constructor(key: Buffer | string, curve: string) { try { + super(); + + __vault.set(this, { + value: void 0, + }); + let privKey: string, pubKey: string; - if(!key) { - ErrorUtils.throwError( - "Key is required, cannot be undefined", - ErrorCode.INVALID_ARGUMENT - ); + + if (!key) { + ErrorUtils.throwError("Key is required, cannot be undefined", ErrorCode.INVALID_ARGUMENT); } - if(curve !== CURVE.SECP256K1) { - ErrorUtils.throwError( - `Unsupported curve: ${curve}`, - ErrorCode.UNSUPPORTED_OPERATION - ); + if (curve !== CURVE.SECP256K1) { + ErrorUtils.throwError(`Unsupported curve: ${curve}`, ErrorCode.UNSUPPORTED_OPERATION); } const ecPrivKey = new elliptic.ec(curve); - const keyInBytes = bufferToUint8(key) - const keyPair = ecPrivKey.keyFromPrivate(keyInBytes) - privKey = keyPair.getPrivate("hex") - pubKey = keyPair.getPublic(true, "hex") - + const keyBuffer = key instanceof Buffer ? key : Buffer.from(key, "hex"); + const keyInBytes = bufferToUint8(keyBuffer); + const keyPair = ecPrivKey.keyFromPrivate(keyInBytes); + privKey = keyPair.getPrivate("hex"); + pubKey = keyPair.getPublic(true, "hex"); + privateMapSet(this, __vault, { _key: privKey, - _mnemonic: mnemonic, _public: pubKey, - _curve: curve + _curve: curve, }); - } catch(err) { - ErrorUtils.throwError( - "Failed to load wallet", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ); + } catch (error) { + ErrorUtils.throwError("Failed to load wallet", ErrorCode.UNKNOWN_ERROR, { originalError: error }); } } @@ -128,42 +128,23 @@ export class Wallet extends Signer { * @returns {boolean} true if the wallet is initialized, false otherwise. */ public isInitialized(): boolean { - if(privateMapGet(this, __vault)) { - return true + if (privateMapGet(this, __vault)) { + return true; } return false; } - /** - * Generates a random mnemonic and initializes the wallet from it. - * - * @throws {Error} if there is an error generating the random mnemonic. - */ - public async createRandom() { - try { - const _random16Bytes = randomBytes(16) - var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); - await this.fromMnemonic(mnemonic); - } catch(err) { - ErrorUtils.throwError( - "Failed to create random mnemonic", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ) - } - } - /** * Generates a keystore file from the wallet's private key, encrypted with a password. * * @param {string} password Used for encrypting the keystore data. * @returns {Keystore} The generated keystore object. - * @throws {Error} if the wallet is not initialized or loaded, or if there + * @throws {Error} if the wallet is not initialized or loaded, or if there * is an error generating the keystore. */ public generateKeystore(password: string): Keystore { - if(!this.isInitialized()) { + if (!this.isInitialized()) { ErrorUtils.throwError( "Keystore not found. The wallet has not been loaded or initialized.", ErrorCode.NOT_INITIALIZED @@ -171,130 +152,78 @@ export class Wallet extends Signer { } try { - const data = Buffer.from(this.privateKey(), "hex"); - return encryptKeystoreData(data, password) - } catch(err) { - ErrorUtils.throwError( - "Failed to generate keystore", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ); - } - } - - /** - * Intializes the wallet from a provided mnemonic. - * - * @param {string} mnemonic - The mnemonic associated with the wallet. - * @param {string} path - The derivation path for the HDNode. (optional) - * @param {string[]} wordlist - The wordlist for the mnemonic. (optional) - * @throws {Error} if there is an error loading the wallet from the mnemonic. - */ - public async fromMnemonic(mnemonic: string, path?: string, wordlist?: string[]) { - mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); - try { - const seed = await bip39.mnemonicToSeed(mnemonic, undefined); - const masterNode = HDNode.fromSeed(seed); - const childNode = masterNode.derivePath(path ? path : MOI_DERIVATION_PATH) - this.load(childNode.privateKey(), CURVE.SECP256K1, mnemonic) - } catch(err) { - ErrorUtils.throwError( - "Failed to load wallet from mnemonic", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ) - } - } - - /** - * Initializes the wallet by decrypting and loading the private key from - * a keystore file. - * - * @param {string} keystore The keystore object as a JSON string. - * @param {string} password The password used for decrypting the keystore. - * @throws {Error} if there is an error parsing the keystore, decrypting - * the keystore data, or loading the private key. - */ - public fromKeystore(keystore: string, password: string) { - try { - const keystoreJson = JSON.parse(keystore); - const privateKey = decryptKeystoreData(keystoreJson, password); - this.load(privateKey, CURVE.SECP256K1) - } catch(err) { - ErrorUtils.throwError( - "Failed to load wallet from keystore", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ) + const data = Buffer.from(this.privateKey, "hex"); + return encryptKeystoreData(data, password); + } catch (err) { + ErrorUtils.throwError("Failed to generate keystore", ErrorCode.UNKNOWN_ERROR, { originalError: err }); } } /** - * Retrieves the private key associated with the wallet. + * Private key associated with the wallet. * - * @returns {string} The private key as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - public privateKey(): string { - if(this.isInitialized()) { - return privateMapGet(this, __vault)._key + public get privateKey(): string { + if (this.isInitialized()) { + return privateMapGet(this, __vault)._key; } - + ErrorUtils.throwError( "Private key not found. The wallet has not been loaded or initialized.", ErrorCode.NOT_INITIALIZED - ) + ); } /** * Retrieves the mnemonic associated with the wallet. * - * @returns {string} The mnemonic as a string. * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - public mnemonic(): string { - if(this.isInitialized()) { - return privateMapGet(this, __vault)._mnemonic + public get mnemonic(): string { + if (this.isInitialized()) { + return privateMapGet(this, __vault)._mnemonic; } ErrorUtils.throwError( "Mnemonic not found. The wallet has not been loaded or initialized.", ErrorCode.NOT_INITIALIZED - ) + ); } /** - * Retrieves the public key associated with the wallet. - * - * @returns {string} The public key as a string. + * Public key associated with the wallet. + * * @throws {Error} if the wallet is not loaded or initialized. + * @readonly */ - public publicKey(): string { - if(this.isInitialized()) { - return privateMapGet(this, __vault)._public + public get publicKey(): string { + if (this.isInitialized()) { + return privateMapGet(this, __vault)._public; } ErrorUtils.throwError( "Public key not found. The wallet has not been loaded or initialized.", ErrorCode.NOT_INITIALIZED - ) + ); } /** - * Retrieves the curve used by the wallet. - * - * @returns {string} The curve as a string. - * @throws {Error} if the wallet is not loaded or initialized. + * Curve associated with the wallet. + * + * @readonly */ - public curve(): string { - if(this.isInitialized()) { - return privateMapGet(this, __vault)._curve + public get curve(): string { + if (this.isInitialized()) { + return privateMapGet(this, __vault)._curve; } ErrorUtils.throwError( "Curve not found. The wallet has not been loaded or initialized.", ErrorCode.NOT_INITIALIZED - ) + ); } /** @@ -303,9 +232,16 @@ export class Wallet extends Signer { * @returns {string} The address as a string. */ public getAddress(): string { - const publicKey = this.publicKey(); + return "0x" + this.publicKey.slice(2); + } - return "0x" + publicKey.slice(2,); + /** + * Address associated with the wallet. + * + * @readonly + */ + public get address(): string { + return this.getAddress(); } /** @@ -314,52 +250,46 @@ export class Wallet extends Signer { * @param {AbstractProvider} provider - The provider to connect. */ public connect(provider: AbstractProvider): void { - this.provider = provider + this.provider = provider; } /** - * Signs a message using the wallet's private key and the specified + * Signs a message using the wallet's private key and the specified * signature algorithm. * * @param {Uint8Array} message - The message to sign as a Uint8Array. * @param {SigType} sigAlgo - The signature algorithm to use. * @returns {string} The signature as a string. - * @throws {Error} if the signature type is unsupported or undefined, or if + * @throws {Error} if the signature type is unsupported or undefined, or if * there is an error during signing. */ public sign(message: Uint8Array, sigAlgo: SigType): string { - if(sigAlgo) { - switch(sigAlgo.sigName) { - case "ECDSA_S256": { - const privateKey = this.privateKey(); - const _sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; - const sig = _sigAlgo.sign(Buffer.from(message), privateKey); - const sigBytes = sig.serialize(); - return bytesToHex(sigBytes); - } - default: { - ErrorUtils.throwError( - "Unsupported signature type", - ErrorCode.UNSUPPORTED_OPERATION - ) - } + if (sigAlgo == null) { + ErrorUtils.throwError("Signature type cannot be undefined", ErrorCode.INVALID_ARGUMENT); + } + + switch (sigAlgo.sigName) { + case "ECDSA_S256": { + const _sigAlgo = this.signingAlgorithms["ecdsa_secp256k1"]; + const sig = _sigAlgo.sign(Buffer.from(message), this.privateKey); + const sigBytes = sig.serialize(); + return bytesToHex(sigBytes); + } + default: { + ErrorUtils.throwError("Unsupported signature type", ErrorCode.UNSUPPORTED_OPERATION); } } - ErrorUtils.throwError( - "Signature type cannot be undefiend", - ErrorCode.INVALID_ARGUMENT - ) } /** - * Signs an interaction object using the wallet's private key and the - * specified signature algorithm. The interaction object is serialized + * Signs an interaction object using the wallet's private key and the + * specified signature algorithm. The interaction object is serialized * into POLO bytes before signing. * * @param {InteractionObject} ixObject - The interaction object to sign. * @param {SigType} sigAlgo - The signature algorithm to use. - * @returns {InteractionRequest} The signed interaction request containing + * @returns {InteractionRequest} The signed interaction request containing * the serialized interaction object and the signature. * @throws {Error} if there is an error during signing or serialization. */ @@ -369,14 +299,150 @@ export class Wallet extends Signer { const signature = this.sign(ixData, sigAlgo); return { ix_args: bytesToHex(ixData), - signature: signature - } - } catch(err) { - ErrorUtils.throwError( - "Failed to sign interaction", - ErrorCode.UNKNOWN_ERROR, - { originalError: err } - ) + signature: signature, + }; + } catch (err) { + ErrorUtils.throwError("Failed to sign interaction", ErrorCode.UNKNOWN_ERROR, { originalError: err }); + } + } + + /** + * Initializes the wallet from a provided mnemonic. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = await Wallet.fromMnemonic(mnemonic); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = await Wallet.fromMnemonic(mnemonic, path); + */ + static async fromMnemonic(mnemonic: string, path?: string, wordlist?: string[]): Promise { + try { + mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); + const seed = await bip39.mnemonicToSeed(mnemonic, undefined); + const masterNode = HDNode.fromSeed(seed); + const childNode = masterNode.derivePath(path ? path : MOI_DERIVATION_PATH); + + const wallet = new Wallet(childNode.privateKey(), CURVE.SECP256K1); + + privateMapSet(wallet, __vault, { + ...privateMapGet(wallet, __vault), + _mnemonic: mnemonic, + }) + + return wallet + } catch (error) { + ErrorUtils.throwError("Failed to load wallet from mnemonic", ErrorCode.UNKNOWN_ERROR, { + originalError: error, + }); + } + } + + /** + * Initializes the wallet from a provided mnemonic synchronously. + * + * @param {string} mnemonic - The mnemonic to initialize the wallet with. + * @param {string | undefined} path - The derivation path to use for key generation. (optional) + * @param {string[] | undefined} wordlist - The wordlist to use for mnemonic generation. (optional) + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * @throws {Error} if there is an error during initialization. + * + * @example + * // Initializing a wallet from mnemonic + * const mnemonic = "hollow appear story text start mask salt social child ..." + * const wallet = Wallet.fromMnemonicSync(); + * + * @example + * // Initializing a wallet from mnemonic with custom path + * const mnemonic = "hollow appear story text start mask salt social child ..."; + * const path = "m/44'/60'/0'/0/0"; + * const wallet = Wallet.fromMnemonicSync(mnemonic, path); + */ + public static fromMnemonicSync(mnemonic: string, path?: string, wordlist?: string[]): Wallet { + try { + mnemonic = bip39.entropyToMnemonic(bip39.mnemonicToEntropy(mnemonic, wordlist), wordlist); + const seed = bip39.mnemonicToSeedSync(mnemonic, undefined); + const masterNode = HDNode.fromSeed(seed); + const childNode = masterNode.derivePath(path ? path : MOI_DERIVATION_PATH); + + const wallet = new Wallet(childNode.privateKey(), CURVE.SECP256K1); + + privateMapSet(wallet, __vault, { + ...privateMapGet(wallet, __vault), + _mnemonic: mnemonic, + }); + + return wallet + } catch (error) { + ErrorUtils.throwError("Failed to load wallet from mnemonic", ErrorCode.UNKNOWN_ERROR, { + originalError: error, + }); + } + } + + /** + * Initializes the wallet from a provided keystore. + * + * @param {string} keystore - The keystore to initialize the wallet with. + * @param {string} password - The password used to decrypt the keystore. + * + * @returns {Wallet} a instance of `Wallet`. + * @throws {Error} if there is an error during initialization. + */ + public static fromKeystore(keystore: string, password: string): Wallet { + try { + const privateKey = decryptKeystoreData(JSON.parse(keystore), password); + return new Wallet(privateKey, CURVE.SECP256K1); + } catch (err) { + ErrorUtils.throwError("Failed to load wallet from keystore", ErrorCode.UNKNOWN_ERROR, { + originalError: err, + }); + } + } + + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Promise} a promise that resolves to a `Wallet` instance. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + public static async createRandom(): Promise { + try { + const _random16Bytes = randomBytes(16); + var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); + return await Wallet.fromMnemonic(mnemonic); + } catch (err) { + ErrorUtils.throwError("Failed to create random mnemonic", ErrorCode.UNKNOWN_ERROR, { originalError: err }); + } + } + + /** + * Generates a random mnemonic and initializes the wallet from it. + * + * @returns {Wallet} a instance of `Wallet`. + * + * @throws {Error} if there is an error generating the random mnemonic. + */ + public static createRandomSync(): Wallet { + try { + const _random16Bytes = randomBytes(16); + var mnemonic = bip39.entropyToMnemonic(_random16Bytes, undefined); + return Wallet.fromMnemonicSync(mnemonic); + } catch (err) { + ErrorUtils.throwError("Failed to create random mnemonic", ErrorCode.UNKNOWN_ERROR, { originalError: err }); } } } diff --git a/packages/js-moi/package.json b/packages/js-moi/package.json index a2e23fed..d88b2b20 100644 --- a/packages/js-moi/package.json +++ b/packages/js-moi/package.json @@ -1,6 +1,6 @@ { "name": "js-moi-sdk", - "version": "0.3.0-rc2", + "version": "0.3.0-rc3", "description": "A feature-rich library designed to seamlessly interact with the MOI Protocol.", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -23,14 +23,14 @@ "author": "Sarva Labs Inc. & MOI Protocol Developers", "license": "Apache-2.0 OR MIT", "dependencies": { - "js-moi-providers": "^0.3.0-rc2", - "js-moi-manifest": "^0.3.0-rc2", - "js-moi-logic": "^0.3.0-rc2", - "js-moi-hdnode": "^0.3.0-rc2", - "js-moi-wallet": "^0.3.0-rc2", - "js-moi-signer": "^0.3.0-rc2", - "js-moi-bip39": "^0.3.0-rc2", - "js-moi-constants": "^0.3.0-rc2", - "js-moi-utils": "^0.3.0-rc2" + "js-moi-providers": "^0.3.0-rc3", + "js-moi-manifest": "^0.3.0-rc3", + "js-moi-logic": "^0.3.0-rc3", + "js-moi-hdnode": "^0.3.0-rc3", + "js-moi-wallet": "^0.3.0-rc3", + "js-moi-signer": "^0.3.0-rc3", + "js-moi-bip39": "^0.3.0-rc3", + "js-moi-constants": "^0.3.0-rc3", + "js-moi-utils": "^0.3.0-rc3" } }