diff --git a/README.md b/README.md index c86b45f..20ab923 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ain-js -AI Network Client Library for Node.js +AI Network Blockchain SDK for javascript (or typescript). ## Installation @@ -52,264 +52,9 @@ yarn run test_snapshot # update test snapshot files ``` -## API - -### Ain -`constructor(provideUrl: string, chainId?: number)` -- Constructs Ain. -- Usage : `const ain = new Ain('http://node.ainetwork.ai:8080/');` - -`setProvider(providerUrl: string, chainId?: number)` -- Sets a new provider. - -`getBlock(blockHashOrBlockNumber: string | number, returnTransactionObjects?: boolean): Promise` -- A promise returns a block with the given hash or block number. - -`getProposer(blockHashOrBlockNumber: string | number): Promise` -- A promise returns the address of the forger of given block. - -`getValidators(blockHashOrBlockNumber: string | number): Promise` -- A promise returns the list of validators for a given block. - -`getTransaction(transactionHash: string): Promise` -- Returns the transaction with the given transaction hash. - -`getStateUsage(appName: string): Promise` -- Returns the state usage information with the given app name. - -`async validateAppName(appName: string): Promise` -- Validates an app name. - -`async sendTransaction(transactionObject: TransactionInput): Promise` -- Signs and sends a transaction to the network. - -`async sendSignedTransaction(signature: string, txBody: TransactionBody): Promise` -- Sends a signed transaction to the network. - -`async sendTransactionBatch(transactionObjects: TransactionInput[]): Promise` -- Sends signed transactions to the network. - -`depositConsensusStake(transactionObject: ValueOnlyTransactionInput): Promise` -- Sends a transaction that deposits AIN for consensus staking. - -`withdrawConsensusStake(transactionObject: ValueOnlyTransactionInput): Promise` -- Sends a transaction that withdraws AIN for consensus staking. - -`getConsensusStakeAmount(account?: string): Promise` -- Gets the amount of AIN currently staked for participating in consensus protocol. - -`getNonce(args: {address?: string, from?: string}): Promise` -- Returns the current transaction count of account, which is the nonce of the account. - -`async buildTransactionBody(transactionInput: TransactionInput): Promise` -- Builds a transaction body from transaction input. - -`static get utils()` -- Getter for ain-util library. - -`static instanceofTransactionBody(object: any): object is TransactionBody` -- Checks whether a given object is an instance of TransactionBody interface. - -### Ain.Provider - -`sync send(rpcMethod: string, params?: any): Promise` -- Creates the JSON-RPC payload and sends it to the node. - -`setDefaultTimeoutMs(time: number)` -- Sets the httpClient's default timeout time. - -### Ain.Database - -`ref(path?: string): Reference` -- Returns a reference instance of the given path. - -#### Ain.Database.Reference - -`setIsGlobal(isGlobal: boolean)` -- Sets the global path flag. - -`get numberOfListeners(): number` -- A getter for number of listeners. - -`push(value?: any): Promise | Reference` -- If value is given, it sets the value at a new child of this.path. Otherwise, it creates a key for a new child but doesn't set any values. - -`getValue(path?: string): Promise` -- Returns the value at the path. - -`getRule(path?: string): Promise` -- Returns the rule at the path. - -`getOwner(path?: string): Promise` -- Returns the owner config at the path. - -`getFunction(path?: string): Promise` -- Returns the function config at the path. - -`get(gets: GetOperation[]): Promise` -- Returns the value / write rule / owner rule / function hash at multiple paths. - -`deleteValue(transactionInput?: ValueOnlyTransactionInput): Promise` -- Deletes a value. - -`setFunction(transactionInput: ValueOnlyTransactionInput): Promise` -- Sets a function config. - -`setOwner(transactionInput: ValueOnlyTransactionInput): Promise` -- Sets the owner rule. - -`setRule(transactionInput: ValueOnlyTransactionInput): Promise` -- Sets the write rule. - -`setValue(transactionInput: ValueOnlyTransactionInput): Promise` -- Sets a value. - -`incrementValue(transactionInput: ValueOnlyTransactionInput): Promise` -- Increments the value. - -`decrementValue(transactionInput: ValueOnlyTransactionInput): Promise` -- Decrements the value. - -`set(transactionInput: SetMultiTransactionInput): Promise` -- Processes multiple set operations. - -`evalRule(params: EvalRuleInput): Promise` -- Returns the rule evaluation result. True if the params satisfy the write rule false if not. - -`evalOwner(params: EvalOwnerInput): Promise` -- Returns the owner evaluation result. - -`matchFunction(params?: MatchInput): Promise` -- Returns the function configs that are related to the input ref. - -`matchRule(params?: MatchInput): Promise` -- Returns the rule configs that are related to the input ref. - -`matchOwner(params?: MatchInput): Promise` -- Returns the owner configs that are related to the input ref. - -`static buildGetRequest(type: GetOperationType, ref: string)` -- Builds a get request. - -`static extendPath(basePath?: string, extension?: string): string` -- Returns a path that is the basePath extended with extension. - -`static extendSetTransactionInput(input: ValueOnlyTransactionInput, ref: string, type: SetOperationType, isGlobal: boolean): TransactionInput` -- Decorates a transaction input with an appropriate type, ref and value. - -`static extendSetMultiTransactionInput(input: SetMultiTransactionInput, ref: string): TransactionInput` -- Decorates a transaction input with an appropriate type and op_list. - -`static sanitizeRef(ref?: string): string` -- Returns a sanitized ref. If should have a slash at the beginning and no slash at the end. - -### Ain.net - -`isListening(): Promise` -- Returns whether the node is listening for network connections. - -`getNetworkId(): Promise` -- Returns the id of the network the node is connected to. - -`async checkProtocolVersion(): Promise` -- Checks the protocol version - -`getProtocolVersion(): Promise` -- Returns the protocol version of the node. - -`getPeerCount(): Promise` -- Returns the number of peers the provider node is connected to. - -`isSyncing(): Promise` -- Returns whether the node is syncing with the network or not. - -### Ain.wallet -`get length()` -- Getter for the number of accounts in the wallet. - -`setChainId(chainId: number)` -- Sets the chain ID. - -`getPublicKey(address: string): string` -- Returns the full public key of the given address. - -`create(numberOfAccounts: number): Array` -- Creates {numberOfAccounts} new accounts and add them to the wallet. - -`isAdded(address: string): boolean` -- Returns whether the address has already been added to the wallet. - -`add(privateKey: string): string` -- Adds a new account from the given private key. - -`addAndSetDefaultAccount(privateKey: string): string` -- Adds a new account from the given private key and sets the new account as the default account. - -`addFromHDWallet(seedPhrase: string, index: number = 0): string` -- Adds an account from a seed phrase. Only the account at the given index (default = 0) will be added. - -`addFromV3Keystore(v3Keystore: V3Keystore | string, password: string): string` -- Adds an account from a V3 Keystore. - -`remove(address: string)` -- Removes an account - -`setDefaultAccount(address: string)` -- Sets the default account as {address}. The account should be already added in the wallet. - -`removeDefaultAccount()` -- Removes a default account (sets it to null). - -`clear()` -- Clears the wallet (remove all account information). - -`getImpliedAddress(inputAddress?: string)` -- Returns the "implied" address. If address is not given, it returns the defaultAccount. It throws an error if an address is not given and defaultAccount is not set, or the specified address is not added to the wallet. - -`getBalance(address?: string): Promise` -- Returns the AIN balance of the address. - -`transfer(input: {to: string, value: number, from?: string, nonce?: number}): Promise` -- Sends a transfer transaction to the network. - -`sign(data: string, address?: string): string` -- Signs a string data with the private key of the given address. It will use the defaultAccount if an address is not provided. - -`signTransaction(tx: TransactionBody, address?: string): string` -- Signs a transaction data with the private key of the given address. It will use the defaultAccount if an address is not provided. - -`getHashStrFromSig(signature: string): string` -- Gets the hash from the signature. - -`recover(signature: string): string` -- Recovers an address of the account that was used to create the signature. - -`verifySignature(data: any, signature: string, address: string): boolean` -- Verifies if the signature is valid and was signed by the address. - -`toV3Keystore(password: string, options: V3KeystoreOptions = {}): V3Keystore[]` -- Save the accounts in the wallet as V3 Keystores, locking them with the password. - -`accountToV3Keystore(address: string, password: string, options: V3KeystoreOptions = {}): V3Keystore` -- Converts an account into a V3 Keystore and encrypts it with a password. - -`static fromPrivateKey(privateKey: Buffer): Account` -- Imports an account from a private key. - -### Ain.eh -`connect(connectionOption?: EventChannelConnectionOption, disconnectCallback?: DisconnectCallback)` -- Connect to event handler node. Must be called before subscribing. - -`disconnect()` -- Disconnect from the event handler node and unsubscribe all subscriptions. - -`subscribe(eventTypeStr: string, config: EventConfigType, -eventCallback?: BlockchainEventCallback, errorCallback?: (error: any) => void): string` -- Subscribe to specific blockchain events in the blockchain. - -`unsubscribe(filterId: string, callback: ErrorFirstCallback)` -- Unsubscribe from a previously subscribed event. +## API Documentation +API documentation is available at https://ainblockchain.github.io/ain-js/. ## LICENSE diff --git a/package.json b/package.json index 747fdee..fbc305e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ainblockchain/ain-js", - "version": "1.6.2", + "version": "1.6.3", "description": "", "main": "lib/ain.js", "scripts": { @@ -13,7 +13,7 @@ "test_ain_raw": "jest ain_raw.test.ts", "test_he": "jest he.test.ts", "test_em": "jest event_manager.test.ts", - "docs": "yarn build && typedoc --out docs" + "docs": "yarn build && typedoc --plugin @mxssfd/typedoc-theme --theme my-theme --out docs" }, "engines": { "node": ">=16" @@ -38,6 +38,7 @@ "lib/**/*" ], "devDependencies": { + "@mxssfd/typedoc-theme": "^1.1.3", "@types/jest": "^27.0.2", "@types/ws": "^8.5.3", "jest": "^27.3.1", diff --git a/src/ain-db/db.ts b/src/ain-db/db.ts index d2f3d60..d41c6c8 100755 --- a/src/ain-db/db.ts +++ b/src/ain-db/db.ts @@ -2,15 +2,19 @@ import Reference from './ref'; import Ain from '../ain'; import Provider from '../provider'; +/** + * A class for managing the states of the blockchain database. + */ export default class Database { + /** The network provider object. */ public provider: Provider; + /** The Ain object. */ private _ain: Ain; /** - * @param {Ain} ain - * @param {Provider} provider - * - * @constructor + * Creates a new Database object. + * @param {Ain} ain The Ain object. + * @param {Provider} provider The network provider object. */ constructor(ain: Ain, provider: Provider) { this.provider = provider; @@ -19,8 +23,8 @@ export default class Database { /** * Returns a reference instance of the given path. - * @param {String} path - * @return {Reference} A reference instance of the given path. + * @param {String} path The path to refer to. + * @returns {Reference} A reference instance of the given path. */ ref(path?: string): Reference { return new Reference(this._ain, path); diff --git a/src/ain-db/ref.ts b/src/ain-db/ref.ts index 68f4de1..e769764 100755 --- a/src/ain-db/ref.ts +++ b/src/ain-db/ref.ts @@ -4,7 +4,6 @@ import { ListenerMap, SetOperationType, GetOperationType, - SetMultiOperationType, SetMultiOperation, TransactionInput, ValueOnlyTransactionInput, @@ -17,6 +16,9 @@ import { import Ain from '../ain'; import { PushId } from './push-id'; +/** + * A class for referencing the states of the blockchain database. + */ export default class Reference { public readonly path: string; public readonly key: string | null; @@ -27,9 +29,9 @@ export default class Reference { private _isGlobal: boolean; /** - * @param {Ain} ain An ain instance. - * @param {String} path The path in the global state tree. - * @constructor + * Creates a new Reference object. + * @param {Ain} ain The Ain object. + * @param {string} path The path to refer to in the global state tree. */ constructor(ain: Ain, path?: string) { this.path = Reference.sanitizeRef(path); @@ -51,18 +53,21 @@ export default class Reference { } /** - * A getter for number of listeners. - * @return {number} The number of listeners. + * Getter for the number of listeners. + * @returns {number} The number of listeners. */ get numberOfListeners(): number { return this._numberOfListeners; } /** - * If value is given, it sets the value at a new child of this.path; - * otherwise, it creates a key for a new child but doesn't set any values. - * @param {any} value - A value to set at the path. - * @return {Promise | Reference} A reference instance of the given path. + * Pushes a new child state to the current path of the blockchain states and + * returns the reference of the child state. + * If a value is given, it's set as the value of the newly added child + * by sending a transaction to the network. Otherwise, it creates a key locally + * for a new child but doesn't change any blockchain states. + * @param {any} value The value of the newly added child state. + * @returns {Promise | Reference} The reference of the newly added child state. */ push(value?: any): Promise | Reference { const newKey = "/" + PushId.generate(); @@ -74,8 +79,10 @@ export default class Reference { } /** - * Returns the value at the path. - * @param path + * Fetches the value of a blockchain state path. + * @param {string} path The path of the blockchain state. + * @param {GetOperation} options The get options. + * @returns {Promise} The value of the blockchain state. */ getValue(path?: string, options?: GetOptions): Promise { const req = Reference.buildGetRequest('GET_VALUE', Reference.extendPath(this.path, path), options); @@ -83,8 +90,10 @@ export default class Reference { } /** - * Returns the rule at the path. - * @param path + * Fetches the rule configuration of a blockchain state path. + * @param {string} path The path of the blockchain state. + * @param {GetOperation} options The get options. + * @returns {Promise} The rule configuration the blockchain state. */ getRule(path?: string, options?: GetOptions): Promise { const req = Reference.buildGetRequest('GET_RULE', Reference.extendPath(this.path, path), options); @@ -92,8 +101,10 @@ export default class Reference { } /** - * Returns the owner config at the path. - * @param path + * Fetches the owner configuration of a blockchain state path. + * @param {string} path The path of the blockchain state. + * @param {GetOperation} options The get options. + * @returns {Promise} The owner configuration of the blockchain state. */ getOwner(path?: string, options?: GetOptions): Promise { const req = Reference.buildGetRequest('GET_OWNER', Reference.extendPath(this.path, path), options); @@ -101,8 +112,10 @@ export default class Reference { } /** - * Returns the function config at the path. - * @param path + * Fetches the function configuration of a blockchain state path. + * @param {string} path The path of the blockchain state. + * @param {GetOperation} options The get options. + * @returns {Promise} The function configuration of the blockchain state. */ getFunction(path?: string, options?: GetOptions): Promise { const req = Reference.buildGetRequest('GET_FUNCTION', Reference.extendPath(this.path, path), options); @@ -110,10 +123,9 @@ export default class Reference { } /** - * Returns the value / write rule / owner rule / function hash at multiple paths. - * @param {Array} requests - Array of get requests - * Could be any one from "VALUE", "RULE", "OWNER", "FUNC" or a combination of them as an array. - * @return {Promise} + * Performs multiple get operations for values, rules, owners, or functions. + * @param {Array} gets The get operations. + * @returns {Promise} The results of the get operations. */ get(gets: GetOperation[]): Promise { const request = { type: 'GET', op_list: gets }; @@ -124,11 +136,11 @@ export default class Reference { } /** - * Deletes a value. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. + * Deletes a value from the blockchain states. + * @param {ValueOnlyTransactionInput} transactionInput The transaction input object. * Any value given will be overwritten with null. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ deleteValue(transactionInput?: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { let txInput: ValueOnlyTransactionInput = transactionInput || {}; @@ -145,9 +157,10 @@ export default class Reference { } /** - * Sets a function config. - * @param transactionInput - * @param {boolean} isDryrun - dryrun option. + * Sets a function configuration in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ setFunction(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -162,10 +175,10 @@ export default class Reference { } /** - * Sets the owner rule. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Sets a owner configuration in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ setOwner(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -180,10 +193,10 @@ export default class Reference { } /** - * Sets the write rule. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Sets a rule configuration in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ setRule(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -198,10 +211,10 @@ export default class Reference { } /** - * Sets a value. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Sets a value in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ setValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -216,10 +229,10 @@ export default class Reference { } /** - * Increments the value. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Increments a value in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ incrementValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -234,10 +247,10 @@ export default class Reference { } /** - * Decrements the value. - * @param {ValueOnlyTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Decrements a value in the blockchain states. + * @param transactionInput The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ decrementValue(transactionInput: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -252,10 +265,10 @@ export default class Reference { } /** - * Processes multiple set operations. - * @param {SetMultiTransactionInput} transactionInput - A transaction input object. - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Sends a transaction of multi-set (SET) operation to the network. + * @param transactionInput The multi-set (SET) transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ set(transactionInput: SetMultiTransactionInput, isDryrun: boolean = false): Promise { return this._ain.sendTransaction( @@ -263,9 +276,12 @@ export default class Reference { } /** - * Returns the rule evaluation result. True if the params satisfy the write rule, - * false if not. - * @param params + * Requests an eval-rule (EVAL_RULE) operation to the network. + * If it returns true, it means that the input operation satisfies the write rule + * in the blockchain states. + * @param transactionInput The multi-set (SET) transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ evalRule(params: EvalRuleInput): Promise { const address = this._ain.signer.getAddress(params.address); @@ -279,8 +295,12 @@ export default class Reference { } /** - * Returns the owner evaluation result. - * @param params + * Requests an eval-owner (EVAL_OWNER) operation to the network. + * If it returns true, it means that the input operation satisfies the owner permissions + * in the blockchain states. + * @param transactionInput The multi-set (SET) transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ evalOwner(params: EvalOwnerInput): Promise { const request = { @@ -292,8 +312,9 @@ export default class Reference { } /** - * Returns the function configs that are related to the input ref. - * @param params + * Fetches the function configurations matched to the input reference (blockchain state path). + * @param {MatchInput} params The match input object. + * @returns {Promise} The return value of the blockchain API. */ matchFunction(params?: MatchInput): Promise { const request = { @@ -303,8 +324,9 @@ export default class Reference { } /** - * Returns the rule configs that are related to the input ref. - * @param params + * Fetches the rule configurations matched to the input reference (blockchain state path). + * @param {MatchInput} params The match input object. + * @returns {Promise} The return value of the blockchain API. */ matchRule(params?: MatchInput): Promise { const request = { @@ -314,8 +336,9 @@ export default class Reference { } /** - * Returns the owner configs that are related to the input ref. - * @param params + * Fetches the owner configurations matched to the input reference (blockchain state path). + * @param {MatchInput} params The match input object. + * @returns {Promise} The return value of the blockchain API. */ matchOwner(params?: MatchInput): Promise { const request = { @@ -324,12 +347,12 @@ export default class Reference { return this._ain.provider.send('ain_matchOwner', request); } - /** - * TODO(liayoo): Add this function. - * Attaches an listener for database events. - * @param {EventType} event - A type of event. - * @param {Function} callback function to be executed when an event occurs. - */ + // TODO(liayoo): Add this function. + ///** + // * Attaches an listener for database events. + // * @param {EventType} event - A type of event. + // * @param {Function} callback function to be executed when an event occurs. + // */ // on(event: EventType, callback: Function) { // if (this._isRootReference) { // throw new Error('[ain-js.Reference.on] Cannot attach an on() listener to a root node'); @@ -349,12 +372,12 @@ export default class Reference { // }, 1000); // } - /** - * TODO (liayoo): Add this function - * Removes a database event listener. - * @param {EventType} event - A type of event. - * @param {Function} callback - A callback function to dettach from the event. - */ + // TODO(liayoo): Add this function. + ///** + // * Removes a database event listener. + // * @param {EventType} event - A type of event. + // * @param {Function} callback - A callback function to dettach from the event. + // */ // off(event?: EventType, callback?: Function) { // if (!event && !callback) { // this._listeners = {}; @@ -376,10 +399,12 @@ export default class Reference { /** * Builds a get request. - * @param type - * @param ref + * @param {GetOperationType} type The get operations type. + * @param {string} ref The blockchain state reference (path). + * @param {GetOptions} options The get options. + * @returns {any} The request built. */ - static buildGetRequest(type: GetOperationType, ref: string, options?: GetOptions) { + static buildGetRequest(type: GetOperationType, ref: string, options?: GetOptions): any { const request = { type, ref: Reference.sanitizeRef(ref) }; if (options) { Object.assign(request, options); @@ -388,9 +413,10 @@ export default class Reference { } /** - * Returns a path that is the basePath extended with extension. - * @param basePath - * @param extension + * Extends a base path with an extension. + * @param {string} basePath The base path. + * @param {string} extension The extension. + * @returns {string} The extended path. */ static extendPath(basePath?: string, extension?: string): string { const sanitizedBase = Reference.sanitizeRef(basePath); @@ -405,11 +431,12 @@ export default class Reference { } /** - * Decorates a transaction input with an appropriate type, ref and value. - * @param {ValueOnlyTransactionInput} input - A transaction input object - * @param {string} ref - The path at which set operations will take place - * @param {SetOperationType} type - A type of set operations - * @return {TransactionInput} + * Builds a transaction input object from a value-only transaction input object + * and additional parameters. + * @param {ValueOnlyTransactionInput} input The value-only transaction input object. + * @param {string} ref The blockchain state reference (path). + * @param {SetOperationType} type The set operation type. + * @returns {TransactionInput} The transaction input built. */ static extendSetTransactionInput( input: ValueOnlyTransactionInput, @@ -429,11 +456,12 @@ export default class Reference { } /** - * Decorates a transaction input with an appropriate type and op_list. - * @param {SetMultiTransactionInput} input - A transaction input object - * @param {string} ref - The path at which set operations will take place - * @param {SetMultiOperationType} type - A type of set operations - * @return {TransactionInput} + * Builds a transaction input object from a multi-set (SET) transaction input object + * and additional parameters. + * @param {ValueOnlyTransactionInput} input The multi-set (SET) transaction input object. + * @param {string} ref The blockchain state reference (path). + * @param {SetOperationType} type The set operation type. + * @returns {TransactionInput} The transaction input built. */ static extendSetMultiTransactionInput( input: SetMultiTransactionInput, @@ -451,9 +479,10 @@ export default class Reference { } /** - * Returns a sanitized ref. If should have a slash at the + * Returns a sanitized blockchain state reference (path). It should have a slash at the * beginning and no slash at the end. - * @param ref + * @param {string} ref The blockchain state reference (path). + * @returns {string} The blockchain state reference sanitized. */ static sanitizeRef(ref?: string): string { if (!ref) return '/'; diff --git a/src/ain.ts b/src/ain.ts index 5f24cdb..05b69c2 100755 --- a/src/ain.ts +++ b/src/ain.ts @@ -1,8 +1,6 @@ -import * as EventEmitter from 'eventemitter3' import * as AinUtil from "@ainblockchain/ain-util"; import { AxiosRequestConfig } from 'axios'; -import request from './request'; import { AinOptions, Block, TransactionInfo, TransactionBody, TransactionResult, SetOperationType, SetOperation, TransactionInput, ValueOnlyTransactionInput, StateUsageInfo, AppNameValidationInfo, @@ -14,23 +12,39 @@ import Wallet from './wallet'; import Network from './net'; import EventManager from './event-manager'; import HomomorphicEncryption from './he'; -import { DefaultSigner, Signer } from "./signer/signer"; +import { Signer } from "./signer/signer"; +import { DefaultSigner } from './signer/default-signer'; +/** + * The main class of the ain-js SDK library. + */ export default class Ain { + /** The axios request config object. */ public axiosConfig: AxiosRequestConfig | undefined; + /** The chain ID of the blockchain network. */ public chainId: number; + /** The network provider object. */ public provider: Provider; + /** The raw result mode option. */ public rawResultMode: boolean; + /** The database object. */ public db: Database; + /** The network object. */ public net: Network; + /** The wallet object. */ public wallet: Wallet; + /** The homorphic encryption object. */ public he: HomomorphicEncryption; + /** The event manager object. */ public em: EventManager; + /** The signer object. */ public signer: Signer; /** - * @param {string} providerUrl - * @constructor + * Creates a new Ain object. + * @param {string} providerUrl The endpoint URL of the network provider. + * @param {number} chainId The chain ID of the blockchain network. + * @param {AinOptions} ainOptions The options of the class. */ constructor(providerUrl: string, chainId?: number, ainOptions?: AinOptions) { this.axiosConfig = ainOptions?.axiosConfig; @@ -42,12 +56,14 @@ export default class Ain { this.db = new Database(this, this.provider); this.he = new HomomorphicEncryption(); this.em = new EventManager(this); - this.signer = new DefaultSigner(this.wallet); + this.signer = new DefaultSigner(this.wallet, this.provider); } /** - * Sets a new provider - * @param {string} providerUrl + * Sets a new provider. + * @param {string} providerUrl The endpoint URL of the network provider. + * @param {number} chainId The chain ID of the blockchain network. + * @param {AxiosRequestConfig} axiosConfig The axios request config. */ setProvider(providerUrl: string, chainId?: number, axiosConfig?: AxiosRequestConfig | undefined) { if (axiosConfig) { @@ -61,19 +77,19 @@ export default class Ain { } /** - * Sets a new signer - * @param {Signer} signer + * Sets a new signer. + * @param {Signer} signer The signer to set. */ setSigner(signer: Signer) { this.signer = signer; } /** - * A promise returns a block with the given hash or block number. - * @param {string | number} blockHashOrBlockNumber - * @param {boolean} returnTransactionObjects - If true, returns the full transaction objects; - * otherwise, returns only the transaction hashes - * @return {Promise} + * Fetches a block with a block hash or block number. + * @param {string | number} blockHashOrBlockNumber The block hash or block number. + * @param {boolean} returnTransactionObjects If it's true, returns a block with full transaction objects. + * Otherwise, returns a block with only transaction hashes. + * @returns {Promise} */ getBlock(blockHashOrBlockNumber: string | number, returnTransactionObjects?: boolean): Promise { const byHash = typeof blockHashOrBlockNumber === 'string' @@ -85,9 +101,9 @@ export default class Ain { } /** - * A promise returns the address of the forger of given block - * @param {string | number} blockHashOrBlockNumber - * @return {Promise} + * Fetches the forger's address of a block with a block hash or block number. + * @param {string | number} blockHashOrBlockNumber The block hash or block number. + * @returns {Promise} */ getProposer(blockHashOrBlockNumber: string | number): Promise { const byHash = typeof blockHashOrBlockNumber === 'string' @@ -97,9 +113,9 @@ export default class Ain { } /** - * A promise returns the list of validators for a given block - * @param {string | number} blockHashOrBlockNumber - * @return {Promise} + * Fetches the validator list of a block with a block hash or block number. + * @param {string | number} blockHashOrBlockNumber The block hash or block number. + * @returns {Promise} */ getValidators(blockHashOrBlockNumber: string | number): Promise { const byHash = typeof blockHashOrBlockNumber === 'string' @@ -109,108 +125,66 @@ export default class Ain { } /** - * Returns the transaction with the given transaction hash. - * @param {string} transactionHash - * @return {Promise} + * Fetches a transaction's information with a transaction hash. + * @param {string} transactionHash The transaction hash. + * @returns {Promise} */ getTransaction(transactionHash: string): Promise { return this.provider.send('ain_getTransactionByHash', { hash: transactionHash }); } /** - * Returns the state usage information with the given app name. - * @param {string} appName - * @return {Promise} + * Fetches a blockchain app's state usage information with an app name. + * @param {string} appName The blockchain app name. + * @returns {Promise} */ getStateUsage(appName: string): Promise { return this.provider.send('ain_getStateUsage', { app_name: appName }); } /** - * Returns the result of the transaction with the given transaaction hash. - * @param {string} transactionHash - * @return {Promise} - */ - // TODO(liayoo): implement this function. - // getTransactionResult(transactionHash: string): Promise {} - - /** - * Validate the given app name. - * @param {string} appName - * @return {Promise} + * Validates a blockchain app's name. + * @param {string} appName The blockchain app name. + * @returns {Promise} */ validateAppName(appName: string): Promise { return this.provider.send('ain_validateAppName', { app_name: appName }); } /** - * Signs and sends a transaction to the network - * @param {TransactionInput} transactionObject - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Signs and sends a transaction to the network. + * @param {TransactionInput} transactionObject The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} */ async sendTransaction(transactionObject: TransactionInput, isDryrun: boolean = false): Promise { - const txBody = await this.buildTransactionBody(transactionObject); - const signature = await this.signer.signMessage(txBody, transactionObject.address); - return await this.sendSignedTransaction(signature, txBody, isDryrun); + return this.signer.sendTransaction(transactionObject, isDryrun); } /** - * Sends a signed transaction to the network - * @param {string} signature - * @param {TransactionBody} txBody - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * Sends a signed transaction to the network. + * @param {string} signature The signature of the transaction. + * @param {TransactionBody} txBody The transaction body. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} */ async sendSignedTransaction(signature: string, txBody: TransactionBody, isDryrun: boolean = false): Promise { - const method = isDryrun ? 'ain_sendSignedTransactionDryrun' : 'ain_sendSignedTransaction'; - let result = await this.provider.send(method, { signature, tx_body: txBody }); - if (!result || typeof result !== 'object') { - result = { result }; - } - return result; + return this.signer.sendSignedTransaction(signature, txBody, isDryrun); } /** - * Sends signed transactions to the network. - * @param {TransactionInput[]} transactionObjects + * Signs and sends multiple transactions in a batch to the network. + * @param {TransactionInput[]} transactionObjects The list of the transaction input objects. + * @returns {Promise} */ async sendTransactionBatch(transactionObjects: TransactionInput[]): Promise { - let promises: Promise[] = []; - for (let tx of transactionObjects) { - promises.push(this.buildTransactionBody(tx).then(async (txBody) => { - if (tx.nonce === undefined) { - // Batch transactions' nonces should be specified. - // If they're not, they default to un-nonced (nonce = -1). - txBody.nonce = -1; - } - const signature = await this.signer.signMessage(txBody, tx.address); - return { signature, tx_body: txBody }; - })); - } - return Promise.all(promises).then(async (tx_list) => { - const resultList = await this.provider.send('ain_sendSignedTransactionBatch', { tx_list }); - if (!Array.isArray(resultList)) { - return resultList; - } - const len = resultList.length; - if (len !== tx_list.length) { - return resultList; - } else { - for (let i = 0; i < len; i++) { - if (!resultList[i] || typeof resultList[i] !== 'object') { - resultList[i] = { result: resultList[i] }; - } - } - return resultList; - } - }) + return this.signer.sendTransactionBatch(transactionObjects); } /** * Sends a transaction that deposits AIN for consensus staking. - * @param {ValueOnlyTransactionInput} transactionObject - * @return {Promise} + * @param {ValueOnlyTransactionInput} transactionObject The transaction input object. + * @returns {Promise} */ depositConsensusStake(transactionObject: ValueOnlyTransactionInput): Promise { return this.stakeFunction('/deposit/consensus', transactionObject); @@ -218,17 +192,18 @@ export default class Ain { /** * Sends a transaction that withdraws AIN for consensus staking. - * @param {ValueOnlyTransactionInput} transactionObject - * @return {Promise} + * @param {ValueOnlyTransactionInput} transactionObject The transaction input object. + * @returns {Promise} */ withdrawConsensusStake(transactionObject: ValueOnlyTransactionInput): Promise { return this.stakeFunction('/withdraw/consensus', transactionObject); } /** - * Gets the amount of AIN currently staked for participating in consensus protocol. - * @param {string} account - If not specified, will try to use the defaultAccount value. - * @return {Promise} + * Fetches the amount of AIN currently staked for participating in consensus protocol. + * @param {string} account The account to fetch the value with. If not specified, + * the default account of the signer is used. + * @returns {Promise} */ getConsensusStakeAmount(account?: string): Promise { const address = account ? Ain.utils.toChecksumAddress(account) @@ -237,56 +212,16 @@ export default class Ain { } /** - * Returns the current transaction count of account, which is the nonce of the account. - * @param {object} args - May contain a string 'address' and a string 'from' values. - * The 'address' indicates the address of the account to get the - * nonce of, and the 'from' indicates where to get the nonce from. - * It could be either the pending transaction pool ("pending") or - * the committed blocks ("committed"). The default value is "committed". - * @return {Promise} - */ - getNonce(args: {address?: string, from?: string}): Promise { - if (!args) { args = {}; } - const address = args.address ? Ain.utils.toChecksumAddress(args.address) - : this.signer.getAddress(args.address); - if (args.from !== undefined && args.from !== 'pending' && args.from !== 'committed') { - throw Error("'from' should be either 'pending' or 'committed'"); - } - return this.provider.send('ain_getNonce', { address, from: args.from }) - } - - /** - * Builds a transaction body from transaction input. - * @param {TransactionInput} transactionInput - * @return {Promise} - */ - async buildTransactionBody(transactionInput: TransactionInput): Promise { - const address = this.signer.getAddress(transactionInput.address); - let tx = { - operation: transactionInput.operation, - parent_tx_hash: transactionInput.parent_tx_hash - } - let nonce = transactionInput.nonce; - if (nonce === undefined) { - nonce = await this.getNonce({address, from: "pending"}); - } - const timestamp = transactionInput.timestamp ? transactionInput.timestamp : Date.now(); - const gasPrice = transactionInput.gas_price || 0; - const billing = transactionInput.billing; - return Object.assign(tx, { nonce, timestamp, gas_price: gasPrice, billing }); - } - - /** - * Getter for ain-util library + * Getter for ain-util library. */ static get utils() { return AinUtil; } /** - * Checks whether a given object is an instance of TransactionBody interface. - * @param {string} account - * @return {boolean} + * Checks whether an object is an instance of TransactionBody interface. + * @param {any} object The object to check. + * @returns {boolean} */ static instanceofTransactionBody(object: any): object is TransactionBody { return object.nonce !== undefined && object.timestamp !== undefined && @@ -296,10 +231,10 @@ export default class Ain { /** * A base function for all staking related database changes. It builds a * deposit/withdraw transaction and sends the transaction by calling sendTransaction(). - * @param {string} path - * @param {ValueOnlyTransactionInput} transactionObject - * @param {boolean} isDryrun - dryrun option. - * @return {Promise} + * @param {string} path The path to set a value with. + * @param {ValueOnlyTransactionInput} transactionObject The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} */ private stakeFunction(path: string, transactionObject: ValueOnlyTransactionInput, isDryrun: boolean = false): Promise { const type: SetOperationType = "SET_VALUE"; diff --git a/src/errors.ts b/src/errors.ts index dfeab7e..ba0c98e 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,6 +1,15 @@ +/** + * A class for blockchain errors. + */ export class BlockchainError extends Error { + /** The error code. */ public code: number; + /** + * Creates a new BlockchainError object. + * @param {number} code The error code. + * @param {string} message The error message. + */ constructor(code: number, message: string) { super(message); // NOTE(platfowner): https://stackoverflow.com/questions/68899615/how-to-expect-a-custom-error-to-be-thrown-with-jest diff --git a/src/event-manager/event-callback-manager.ts b/src/event-manager/event-callback-manager.ts index 8a32c2c..77cf67f 100644 --- a/src/event-manager/event-callback-manager.ts +++ b/src/event-manager/event-callback-manager.ts @@ -1,26 +1,48 @@ import EventFilter from './event-filter'; import Subscription from './subscription'; -import { BlockchainEventTypes, EventConfigType, BlockchainEventCallback, FilterDeletedEventCallback, FilterDeletedEvent } from '../types'; +import { BlockchainEventTypes, BlockchainEventConfig, BlockchainEventCallback, BlockchainErrorCallback, FilterDeletedEventCallback, FilterDeletedEvent } from '../types'; import { PushId } from '../ain-db/push-id'; import { FAILED_TO_REGISTER_ERROR_CODE } from '../constants'; +/** + * A class for managing event callbacks. + */ export default class EventCallbackManager { + /** The event filter map from filter ID to event filter. */ private readonly _filters: Map; + /** The subscription map from filter ID to subscription. */ private readonly _filterIdToSubscription: Map; + /** + * Creates a new EventCallbackManager object. + */ constructor() { this._filters = new Map(); this._filterIdToSubscription = new Map(); } + /** + * Builds a filter ID. + * @returns {string} The filter ID built. + */ buildFilterId() { return PushId.generate(); } + /** + * Builds a subscription ID. + * @returns {string} The subscription ID built. + */ buildSubscriptionId() { return PushId.generate(); } + /** + * Emits a blockchain event to trigger callback functions. + * @param {string} filterId The filter ID. + * @param {BlockchainEventTypes} eventType The blockchain event type. + * @param {any} payload The payload of the event. + */ emitEvent(filterId: string, eventType: BlockchainEventTypes, payload: any) { const subscription = this._filterIdToSubscription.get(filterId); if (!subscription) { @@ -33,6 +55,12 @@ export default class EventCallbackManager { subscription.emit('event', payload); } + /** + * Emits an error to trigger callback functions. + * @param {string} filterId The filter ID. + * @param {number} code The error code. + * @param {string} errorMessage The error message. + */ emitError(filterId: string, code: number, errorMessage: string) { const subscription = this._filterIdToSubscription.get(filterId); if (!subscription) { @@ -47,7 +75,13 @@ export default class EventCallbackManager { }); } - createFilter(eventTypeStr: string, config: EventConfigType): EventFilter { + /** + * Creates a new EventFilter object and adds it to the event filter map. + * @param {string} eventTypeStr The event type string. + * @param {BlockchainEventConfig} config The blockchain event configuration. + * @returns {EventFilter} The event filter object created. + */ + createFilter(eventTypeStr: string, config: BlockchainEventConfig): EventFilter { const eventType = eventTypeStr as BlockchainEventTypes; if (!Object.values(BlockchainEventTypes).includes(eventType) || eventType === BlockchainEventTypes.FILTER_DELETED) { @@ -62,7 +96,12 @@ export default class EventCallbackManager { return filter; } - getFilter(filterId): EventFilter { + /** + * Looks up an event filter with a filter ID. + * @param {string} filterId The filter ID. + * @returns {EventFilter} The event filter looked up. + */ + getFilter(filterId: string): EventFilter { const filter = this._filters.get(filterId); if (!filter) { throw Error(`Non-existent filter ID (${filterId})`); @@ -70,13 +109,21 @@ export default class EventCallbackManager { return filter; } + /** + * Creates a new Subscription object. + * @param {EventFilter} filter The event filter. + * @param {BlockchainEventCallback} eventCallback The blockchain event callback function. + * @param {BlockchainErrorCallback} errorCallback The blockchain error callback function. + * @param {FilterDeletedEventCallback} filterDeletedEventCallback The filter deletion event callback function. + * @returns {Subscription} The subscription object created. + */ createSubscription( filter: EventFilter, eventCallback?: BlockchainEventCallback, - errorCallback?: (error: any) => void, + errorCallback?: BlockchainErrorCallback, filterDeletedEventCallback: FilterDeletedEventCallback = (payload) => console.log( `Event filter (id: ${payload.filter_id}) is deleted because of ${payload.reason}`) - ) { + ): Subscription { const subscription = new Subscription(filter); subscription.on( 'filterDeleted', (payload: FilterDeletedEvent) => { @@ -94,6 +141,10 @@ export default class EventCallbackManager { return subscription; } + /** + * Deletes an event filter. + * @param {string} filterId The event filter ID to delete. + */ deleteFilter(filterId: string) { if (!this._filterIdToSubscription.delete(filterId)) { console.log(`Can't remove the subscription because it can't be found. (${filterId})`); diff --git a/src/event-manager/event-channel-client.ts b/src/event-manager/event-channel-client.ts index b239481..9c64f61 100644 --- a/src/event-manager/event-channel-client.ts +++ b/src/event-manager/event-channel-client.ts @@ -4,8 +4,8 @@ import { EventChannelMessageTypes, EventChannelMessage, BlockchainEventTypes, - EventChannelConnectionOption, - DisconnectCallback, + EventChannelConnectionOptions, + DisconnectionCallback, } from '../types'; import EventFilter from './event-filter'; import EventCallbackManager from './event-callback-manager'; @@ -13,14 +13,28 @@ import EventCallbackManager from './event-callback-manager'; const DEFAULT_HEARTBEAT_INTERVAL_MS = 15000 + 1000; // NOTE: This time must be longer than blockchain event handler heartbeat interval. const DEFAULT_HANDSHAKE_TIMEOUT_MS = 30000; +/** + * A class for managing event channels and event handling callback functions. + */ export default class EventChannelClient { + /** The Ain object. */ private readonly _ain: Ain; + /** The event callback manager object. */ private readonly _eventCallbackManager: EventCallbackManager; + /** The web socket client. */ private _wsClient?: WebSocket; + /** The blockchain endpoint URL. */ private _endpointUrl?: string; + /** Whether it's connected or not. */ private _isConnected: boolean; + /** The heartbeat timeout object. */ private _heartbeatTimeout?: ReturnType | null; + /** + * Creates a new EventChannelClient object. + * @param {Ain} ain The Ain object. + * @param {EventCallbackManager} eventCallbackManager The event callback manager object. + */ constructor(ain: Ain, eventCallbackManager: EventCallbackManager) { this._ain = ain; this._eventCallbackManager = eventCallbackManager; @@ -34,7 +48,13 @@ export default class EventChannelClient { return this._isConnected; } - connect(connectionOption: EventChannelConnectionOption, disconnectCallback?: DisconnectCallback) { + /** + * Opens a new event channel. + * @param {EventChannelConnectionOptions} connectionOption The event channel connection options. + * @param {DisconnectionCallback} disconnectionCallback The disconnection callback function. + * @returns {Promise} A promise for the connection success. + */ + connect(connectionOption: EventChannelConnectionOptions, disconnectionCallback?: DisconnectionCallback): Promise { return new Promise(async (resolve, reject) => { if (this.isConnected) { reject(new Error(`Can't connect multiple channels`)); @@ -86,13 +106,16 @@ export default class EventChannelClient { }); this._wsClient.on('close', () => { this.disconnect(); - if (disconnectCallback) { - disconnectCallback(this._wsClient); + if (disconnectionCallback) { + disconnectionCallback(this._wsClient); } }); }) } + /** + * Closes the current event channel. + */ disconnect() { this._isConnected = false; this._wsClient!.terminate(); @@ -102,6 +125,10 @@ export default class EventChannelClient { } } + /** + * Starts the heartbeat timer for the event channel. + * @param {number} timeoutMs The timeout value in miliseconds. + */ startHeartbeatTimer(timeoutMs: number) { this._heartbeatTimeout = setTimeout(() => { console.log(`Connection timeout! Terminate the connection. All event subscriptions are stopped.`); @@ -109,7 +136,11 @@ export default class EventChannelClient { }, timeoutMs); } - handleEmitEventMessage(messageData) { + /** + * Handles an emit-event message from the event channel. + * @param {any} messageData The payload data of the message. + */ + handleEmitEventMessage(messageData: any) { const filterId = messageData.filter_id; if (!filterId) { throw Error(`Can't find filter ID from message data (${JSON.stringify(messageData, null, 2)})`); @@ -126,7 +157,11 @@ export default class EventChannelClient { this._eventCallbackManager.emitEvent(filterId, eventType, payload); } - handleEmitErrorMessage(messageData) { + /** + * Handles an emit-error message from the event channel. + * @param {any} messageData The payload data of the message. + */ + handleEmitErrorMessage(messageData: any) { const code = messageData.code; if (!code) { console.log(`Can't find code from message data (${JSON.stringify(messageData, null, 2)})`); @@ -145,6 +180,10 @@ export default class EventChannelClient { this._eventCallbackManager.emitError(filterId, code, errorMessage); } + /** + * Handles a message from the event channel. + * @param {string} message The message. + */ handleMessage(message: string) { try { const parsedMessage = JSON.parse(message); @@ -171,6 +210,12 @@ export default class EventChannelClient { } } + /** + * Builds a message to be sent to the event channel. + * @param {EventChannelMessageTypes} messageType The message type. + * @param {any} data The payload data of the msssage. + * @returns + */ buildMessage(messageType: EventChannelMessageTypes, data: any): EventChannelMessage { return { type: messageType, @@ -178,6 +223,10 @@ export default class EventChannelClient { }; } + /** + * Sends a message to the event channel. + * @param {EventChannelMessage} message The message to be sent. + */ sendMessage(message: EventChannelMessage) { if (!this._isConnected) { throw Error(`Failed to send message. Event channel is not connected!`); @@ -185,12 +234,20 @@ export default class EventChannelClient { this._wsClient!.send(JSON.stringify(message)); } + /** + * Sends a register-event-filter messsage to the event channel. + * @param {EventFilter} filter The event filter to register. + */ registerFilter(filter: EventFilter) { const filterObj = filter.toObject(); const registerMessage = this.buildMessage(EventChannelMessageTypes.REGISTER_FILTER, filterObj); this.sendMessage(registerMessage); } + /** + * Sends a deregister-event-filter messsage to the event channel. + * @param {EventFilter} filter The event filter to deregister. + */ deregisterFilter(filter: EventFilter) { const filterObj = filter.toObject(); const deregisterMessage = this.buildMessage(EventChannelMessageTypes.DEREGISTER_FILTER, filterObj); diff --git a/src/event-manager/event-filter.ts b/src/event-manager/event-filter.ts index 3280176..a77cb32 100644 --- a/src/event-manager/event-filter.ts +++ b/src/event-manager/event-filter.ts @@ -1,16 +1,32 @@ -import { BlockchainEventTypes, EventConfigType } from '../types'; +import { BlockchainEventTypes, BlockchainEventConfig } from '../types'; +/** + * A class for filtering blockchain events. + */ export default class EventFilter { + /** The event filter ID. */ public readonly id: string; + /** The blockchain event type. */ public readonly type: BlockchainEventTypes; - public readonly config: EventConfigType; + /** The blockchain event configuration. */ + public readonly config: BlockchainEventConfig; - constructor(id: string, type: BlockchainEventTypes, config: EventConfigType) { + /** + * Creates a new EventFilter object. + * @param {string} id The event filter ID object. + * @param {BlockchainEventTypes} type The blockchain event type value. + * @param {BlockchainEventConfig} config The blockchain event configuration object. + */ + constructor(id: string, type: BlockchainEventTypes, config: BlockchainEventConfig) { this.id = id; this.type = type; this.config = config; } + /** + * Converts to a javascript object. + * @returns {Object} The javascript object. + */ toObject() { return { id: this.id, diff --git a/src/event-manager/index.ts b/src/event-manager/index.ts index 5724b67..0e05fd2 100644 --- a/src/event-manager/index.ts +++ b/src/event-manager/index.ts @@ -2,30 +2,45 @@ import Ain from '../ain'; import { BlockFinalizedEventConfig, BlockFinalizedEvent, ErrorFirstCallback, - EventChannelConnectionOption, - EventConfigType, BlockchainEventCallback, + EventChannelConnectionOptions, + BlockchainEventConfig, BlockchainEventCallback, TxStateChangedEventConfig, TxStateChangedEvent, - ValueChangedEventConfig, ValueChangedEvent, DisconnectCallback, FilterDeletedEventCallback, + ValueChangedEventConfig, ValueChangedEvent, DisconnectionCallback, FilterDeletedEventCallback, BlockchainErrorCallback, } from '../types'; import EventChannelClient from './event-channel-client'; import EventCallbackManager from './event-callback-manager'; import EventFilter from './event-filter'; +/** + * A class for managing blockchain events. + */ export default class EventManager { - private _ain: Ain; + /** The event callback manager. */ private readonly _eventCallbackManager: EventCallbackManager; + /** The event channel client. */ private readonly _eventChannelClient: EventChannelClient; + /** + * Creates a new EventManager object. + * @param {Ain} ain The Ain object. + */ constructor(ain: Ain) { - this._ain = ain; this._eventCallbackManager = new EventCallbackManager(); this._eventChannelClient = new EventChannelClient(ain, this._eventCallbackManager); } - async connect(connectionOption?: EventChannelConnectionOption, disconnectCallback?: DisconnectCallback) { - await this._eventChannelClient.connect(connectionOption || {}, disconnectCallback); + /** + * Opens a new event channel. + * @param {EventChannelConnectionOptions} connectionOption The event channel connection options. + * @param {DisconnectionCallback} disconnectionCallback The disconnection callback function. + */ + async connect(connectionOption?: EventChannelConnectionOptions, disconnectionCallback?: DisconnectionCallback) { + await this._eventChannelClient.connect(connectionOption || {}, disconnectionCallback); } + /** + * Closes the current event channel. + */ disconnect() { this._eventChannelClient.disconnect(); } @@ -34,26 +49,35 @@ export default class EventManager { eventType: 'BLOCK_FINALIZED', config: BlockFinalizedEventConfig, eventCallback?: (event: BlockFinalizedEvent) => void, - errorCallback?: (error: any) => void + errorCallback?: BlockchainErrorCallback ): string; subscribe( eventType: 'VALUE_CHANGED', config: ValueChangedEventConfig, eventCallback?: (event: ValueChangedEvent) => void, - errorCallback?: (error: any) => void + errorCallback?: BlockchainErrorCallback ): string; subscribe( eventType: 'TX_STATE_CHANGED', config: TxStateChangedEventConfig, eventCallback?: (event: TxStateChangedEvent) => void, - errorCallback?: (error: any) => void, + errorCallback?: BlockchainErrorCallback, filterDeletedEventCallback?: FilterDeletedEventCallback ): string; + /** + * Subscribes to blockchain events. + * @param {string} eventTypeStr The event type. + * @param {BlockchainEventConfig} config The blockchain event configuraiton. + * @param {BlockchainEventCallback} eventCallback The blockchain event callback function. + * @param {BlockchainErrorCallback} errorCallback The blockchain error callback function. + * @param {FilterDeletedEventCallback} filterDeletedEventCallback The filter-deleted event callback function. + * @returns {string} The created filter ID. + */ subscribe( eventTypeStr: string, - config: EventConfigType, + config: BlockchainEventConfig, eventCallback?: BlockchainEventCallback, - errorCallback?: (error: any) => void, + errorCallback?: BlockchainErrorCallback, filterDeletedEventCallback?: FilterDeletedEventCallback ): string { if (!this._eventChannelClient.isConnected) { @@ -65,6 +89,11 @@ export default class EventManager { return filter.id; } + /** + * Cancel a subscription. + * @param {string} filterId The filter ID of the subscription to cancel. + * @param {ErrorFirstCallback} callback The error handling callback function. + */ unsubscribe(filterId: string, callback: ErrorFirstCallback) { try { if (!this._eventChannelClient.isConnected) { diff --git a/src/event-manager/subscription.ts b/src/event-manager/subscription.ts index 442ed1c..5475a32 100644 --- a/src/event-manager/subscription.ts +++ b/src/event-manager/subscription.ts @@ -1,14 +1,25 @@ import { EventEmitter } from 'events'; import EventFilter from './event-filter'; +/** + * A class for subscribing callback functions to blockchain events. + */ export default class Subscription extends EventEmitter { + /** The event filter object. */ private readonly _filter: EventFilter; + /** + * Creates a new Subscription object. + * @param {EventFilter} filter The event filter object. + */ constructor(filter: EventFilter) { super(); this._filter = filter; } + /** + * Getter for the event filter. + */ get filter(): EventFilter { return this._filter; } diff --git a/src/he/desilo.js b/src/he/desilo.js index 7688207..f477b77 100644 --- a/src/he/desilo.js +++ b/src/he/desilo.js @@ -5,9 +5,13 @@ const SEAL = require('node-seal'); /** - * wrapper Error class for DesiloSeal + * A wrapper Error class for DesiloSeal. */ class DesiloSealError extends Error { + /** + * Creates a new DesiloSealError object with an error message. + * @param {String} message The error message. + */ constructor(message) { super(message); this.name = this.constructor.name; @@ -16,12 +20,12 @@ class DesiloSealError extends Error { } /** - * node-seal wrapper class + * A wrapper class for the Desilo's HE solution. */ class DesiloSeal { /** - * Constructor + * Creates a new DesiloSeal object. * @param {Number} polyModulusDegree * @param {Int32Array} coeffModulusArray * @param {Number} scaleBits @@ -33,7 +37,7 @@ class DesiloSeal { } /** - * Initializes new SEALContext + * Initializes new SEALContext. */ async initContext() { this.seal = await SEAL(); @@ -58,9 +62,7 @@ class DesiloSeal { } /** - * Makes a new keyset - * @param {string} secretKeyStr - secret key to load - * @param {string} publicKeyStr - public key to load + * Generates a new key set. */ initKeySet() { const keyGenerator = this.seal.KeyGenerator(this.context); @@ -78,9 +80,9 @@ class DesiloSeal { } /** - * Loads secret key - * @param {string} secretKeyStr - secret key to load - * @param {string} publicKeyStr - public key to load + * Loads a key set. + * @param {string} secretKeyStr The secret key to load. + * @param {string} publicKeyStr The public key to load. */ loadKeySet(secretKeyStr, publicKeyStr = undefined) { const secretKey = this.seal.SecretKey(); @@ -104,7 +106,7 @@ class DesiloSeal { } /** - * Initialize encryptor, decryptor, evaluator + * Initializes encryptor, decryptor, and evaluator objects. */ initClasses() { this.encryptor = this.seal.Encryptor(this.context, this.keys.publicKey); @@ -113,8 +115,9 @@ class DesiloSeal { } /** - * gets entire keyset - * @returns {object} keys + * Returns the entire key set. + * It temporarily returns the secret key only due to memory issues. + * @returns {Object} The key set. */ getKeys() { // hidden for now, due to memory issue @@ -132,8 +135,8 @@ class DesiloSeal { } /** - * gets secretKey - * @returns {seal.SecretKey} secretKey + * Returns the secret key. + * @returns {any} The secret key. */ getSecretKey() { const secretKey = this.keys.secretKey ? this.keys.secretKey.save() : ''; @@ -141,9 +144,9 @@ class DesiloSeal { } /** - * encrypts a length-fixed array into a ciphertext - * @param {Float64Array} - array of length poly_mod_degree / 2 - * @returns {CipherText} + * Encrypts a length-fixed array into a ciphertext. + * @param {Float64Array} array The array of length poly_mod_degree / 2. + * @returns {CipherText} The cipertext encrypted. */ encrypt(array) { const plaintext = this.encoder.encode(array, this.scale); @@ -155,9 +158,9 @@ class DesiloSeal { } /** - * Decrypt ciphertext - * @param {string::Ciphertext} cipherStr - ciphertext string - * @returns {Float64Array} + * Decrypts a ciphertext to an float64 array. + * @param {Ciphertext} cipherStr The ciphertext. + * @returns {Float64Array} The float64 array decrypted. */ decrypt(cipherStr) { const uploadedCipherText = this.seal.CipherText(); @@ -169,9 +172,9 @@ class DesiloSeal { } /** - * Factory method for DesiloSeal class - * @param {Object} keys - object containing sk, pk, galois-key, relin-key strings - * @returns {DesiloSeal} seal - Desilo SEAL wrapper class + * A factory method for DesiloSeal class. + * @param {Object} keys The object containing sk, pk, galois-key, and relin-key strings. + * @returns {DesiloSeal} The newly created DesiloSeal object. */ async function DesiloSealFactory(keys, params) { const { polyModulusDegree, coeffModulusArray, scaleBit } = params; diff --git a/src/he/index.ts b/src/he/index.ts index 705d90f..2a28f80 100644 --- a/src/he/index.ts +++ b/src/he/index.ts @@ -7,15 +7,29 @@ const DEFAULT_PARAMS: HomomorphicEncryptionParams = { coeffModulusArray: Int32Array.from([60, 40, 40, 60]), scaleBit: 40 }; + +/** + * A class for homorphic encryption based on the Desilo's HE solution. + */ export default class HomomorphicEncryption { + /** The DesiloSeal object. */ public seal: any; + /** Whether the class is initialized or not. */ private _initialized: boolean; + /** + * Creates a new HomorphicEncryption obect. + */ constructor() { this.seal = null; this._initialized = false; } + /** + * Initializes the class with keys and parameters. + * @param {HomomorphicEncryptionSecretKey | null} keys The secret key. + * @param {HomomorphicEncryptionParams | null} params The homorphic encryption parameters. + */ async init(keys?: HomomorphicEncryptionSecretKey | null, params?: HomomorphicEncryptionParams | null) { this.seal = await DesiloSealFactory(keys, params ? params : DEFAULT_PARAMS); if (!this.seal) { @@ -31,32 +45,56 @@ export default class HomomorphicEncryption { this._initialized = true; } + /** + * Getter for _initialized. + */ get initialized() { return this._initialized; } - TEST_getKeys() { + /** + * Returns the key set currently in use. + * It temporarily returns the secret key only due to memory issues. + * This is a method for test purposes only. + * @returns {Object} The key set. + */ + TEST_getKeys(): Object { if (!this.initialized) { throw new Error('Cannot encode before initializing.'); } return this.seal.getKeys(); } - encrypt(array: Float64Array) { + /** + * Encrypts a length-fixed array into a ciphertext. + * @param {Float64Array} array The array of length poly_mod_degree / 2. + * @returns {CipherText} The cipertext encrypted. + */ + encrypt(array: Float64Array): CipherText { if (!this.initialized) { throw new Error('Cannot encrypt before initializing.'); } return this.seal.encrypt(array); } - decrypt(cipherText: CipherText) { + /** + * Decrypts a ciphertext to an float64 array. + * @param {CipherText} cipherText The ciphertext. + * @returns {Float64Array} The float64 array decrypted. + */ + decrypt(cipherText: CipherText): Float64Array { if (!this.initialized) { throw new Error('Cannot decrypt before initializing.'); } return this.seal.decrypt(cipherText); } - TEST_evaluate_double(cipherText: CipherText) { + /** + * Doubles an input cipher text and performs a homorphic calculation on it. + * This is a method for test purposes only. + * @returns {Object} The result of the homorphic calculation. + */ + TEST_evaluate_double(cipherText: CipherText): Object { if (!this.initialized) { throw new Error('Cannot evaluate before initializing.'); } diff --git a/src/net.ts b/src/net.ts index d24e9a1..c42a9f0 100755 --- a/src/net.ts +++ b/src/net.ts @@ -2,13 +2,18 @@ import * as semver from 'semver'; import Provider from './provider'; import { BLOCKCHAIN_PROTOCOL_VERSION } from './constants'; +/** + * A class for checking the blockchain node status. + */ export default class Network { + /** The network provider. */ public provider: Provider; + /** The protocol version. */ public protoVer: string; /** - * @param {Provider} provider - * @constructor + * Creates a new Network object. + * @param {Provider} provider The network provider. */ constructor (provider: Provider) { this.provider = provider; @@ -16,52 +21,52 @@ export default class Network { } /** - * Returns whether the node is listening for network connections. - * @return {Promise} + * Checks whether the blockchain node is listening for network connections. + * @returns {Promise} */ isListening(): Promise { return this.provider.send('net_listening'); } /** - * Returns the id of the network the node is connected to. + * Fetches the ID of the network the blokchain node is connected to. */ getNetworkId(): Promise { return this.provider.send('net_getNetworkId'); } /** - * Checks the protocol version. + * Checks the protocol version compatibility with the blockchain node. */ async checkProtocolVersion(): Promise { return this.provider.send('ain_checkProtocolVersion'); } /** - * Returns the protocol version of the node. + * Fetches the protocol version of the blockchain node. */ getProtocolVersion(): Promise { return this.provider.send('ain_getProtocolVersion'); } /** - * Returns the number of peers the provider node is connected to. - * @return {Promise} + * Fetches the number of the peers the blockchain node is connected to. + * @returns {Promise} */ getPeerCount(): Promise { return this.provider.send('net_peerCount'); } /** - * Returns whether the node is syncing with the network or not. - * @return {Promise} + * Checks whether the blockchain node is syncing with the network or not. + * @returns {Promise} */ isSyncing(): Promise { return this.provider.send('net_syncing'); } /** - * Returns the event handler network information. + * Fetches the event handler network information. */ getEventHandlerNetworkInfo(): Promise { return this.provider.send('net_getEventHandlerNetworkInfo'); diff --git a/src/promi-event.ts b/src/promi-event.ts index 9cda7b8..53973f5 100644 --- a/src/promi-event.ts +++ b/src/promi-event.ts @@ -1,16 +1,24 @@ import * as EventEmitter from 'eventemitter3'; /** - * A combination of a promise and an event emitter. + * A class that combines the Promise interface and the EventEmitter class. * @implements {Promise} */ export class PromiEvent implements Promise { + /** The event emitter. */ public eventEmitter: EventEmitter; + /** The promise. */ public promise: Promise + /** The value for toString(). */ public [Symbol.toStringTag]; + /** The resolve function. */ private _resolve; + /** The reject function. */ private _reject; + /** + * Creates a new PromiEvent object. + */ constructor() { this.promise = new Promise((resolve, reject) => { this._resolve = resolve; @@ -21,6 +29,7 @@ export class PromiEvent implements Promise { this[Symbol.toStringTag] = 'Promise'; } + /** The then method. */ then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, @@ -30,6 +39,7 @@ export class PromiEvent implements Promise { return this.promise.then(onfulfilled, onrejected) } + /** The catch method. */ catch( onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null @@ -37,18 +47,23 @@ export class PromiEvent implements Promise { return this.promise.then(onrejected) } + /** The finally method. */ finally(onfinally?: (() => void) | undefined | null): Promise { return this.promise; } + /** The resolve method. */ resolve(val:T) { this._resolve(val) } + /** The reject method. */ reject(reason:any) { this._reject(reason) } + /** The once method. */ once(type: string, handler: (res: any) => void): PromiEvent { this.eventEmitter.once(type, handler); return this; }; + /** The on method. */ on(type: string, handler: (res: any) => void): PromiEvent { this.eventEmitter.on(type, handler); return this; diff --git a/src/provider.ts b/src/provider.ts index ff2c306..e1ec746 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -6,15 +6,24 @@ import { BlockchainError } from './errors'; const JSON_RPC_ENDPOINT = 'json-rpc'; +/** + * A class for providing JSON-RPC channels with blockchain node endpoints. + */ export default class Provider { + /** The blockchain node endpoint. */ public endpoint: string; + /** The blockchain node JSON-RPC endpoint. */ public apiEndpoint: string; + /** The Ain object. */ private ain: Ain; + /** The axios http client object. */ private httpClient: AxiosInstance; /** - * @param {String} endpoint - * @constructor + * Creates a new Provider object. + * @param {Ain} ain The Ain object. + * @param {string} endpoint The blockchain node endpoint. + * @param {AxiosRequestConfig} axiosConfig The axios request config object. */ constructor(ain: Ain, endpoint: string, axiosConfig: AxiosRequestConfig | undefined) { this.ain = ain; @@ -29,10 +38,10 @@ export default class Provider { } /** - * Creates the JSON-RPC payload and sends it to the node. - * @param {string} rpcMethod - * @param {any} params - * @return {Promise} + * Creates a JSON-RPC payload and sends it to the network. + * @param {string} rpcMethod The JSON-RPC method. + * @param {any} params The JSON-RPC parameters. + * @returns {Promise} */ async send(rpcMethod: string, params?: any): Promise { const data = { @@ -53,8 +62,8 @@ export default class Provider { } /** - * Sets the httpClient's default timeout time - * @param {number} time (in milliseconds) + * Sets the http client's default timeout value. + * @param {number} time The timeout value (in milliseconds). */ setDefaultTimeoutMs(time: number) { this.httpClient.defaults.timeout = time; diff --git a/src/request.ts b/src/request.ts deleted file mode 100644 index 6619fc2..0000000 --- a/src/request.ts +++ /dev/null @@ -1,9 +0,0 @@ -// This is a mock module for sending requests and having delays. -export default function request(url: string) { - return new Promise(resolve => { - process.nextTick(() => { - if (url.includes("Count") || url.includes("Amount")) resolve(31); - resolve('test_string'); - }); - }); -} diff --git a/src/signer/ain-wallet-signer.ts b/src/signer/ain-wallet-signer.ts index 4b9401d..ddae075 100644 --- a/src/signer/ain-wallet-signer.ts +++ b/src/signer/ain-wallet-signer.ts @@ -1,14 +1,17 @@ +import { TransactionBody, TransactionInput } from "../types"; import { Signer } from "./signer"; /** - * A signer class for AIN Wallet chrome extension + * A class of Signer interface for AIN Wallet chrome extension * (https://chrome.google.com/webstore/detail/ain-wallet/hbdheoebpgogdkagfojahleegjfkhkpl). */ export class AinWalletSigner implements Signer { + /** The Ain Wallet object. */ private ainetwork: Signer; /** - * Initializes the class based on the AIN Wallet's global variable. + * Creates a new AinWalletSigner object. + * It initializes the Ain Wallet object using the global variable 'window'. */ constructor() { if (window.ainetwork) { @@ -19,21 +22,47 @@ export class AinWalletSigner implements Signer { } /** - * Returns the checksum address to sign messages with. - * If the address is not given, the default address of the wallet is used. - * @param {string} address - The address of the account to sign the message with. + * Gets an account's checksum address. + * If the address is not given, the default account of the Ain Wallet is used. + * @param {string} address The address of the account. + * @returns {string} The checksum address. */ getAddress(address?: string): string { return this.ainetwork.getAddress(address); } /** - * Signs a message with the private key of the given address. - * If an address is not given, the default address of the wallet is used. - * @param {any} message - The message to sign. - * @param {string} address - The address of the account to sign the message with. + * Signs a message using an account. + * If an address is not given, the default account of the Ain Wallet is used. + * @param {string} message The message to sign. + * @param {string} address The address of the account. + * @returns {Promise | string} The signature. */ signMessage(message: any, address?: string): Promise | string { return this.ainetwork.signMessage(message, address); } + + /** + * Signs and sends a transaction to the network. + * @param {TransactionInput} transactionObject The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. + */ + sendTransaction(transactionObject: TransactionInput) { + return this.ainetwork.sendTransaction(transactionObject); + } + + /** + * This method is not implemented yet. + */ + sendTransactionBatch(transactionObjects: TransactionInput[]): Promise { + throw new Error("Method not implemented."); + } + + /** + * This method is not implemented yet. + */ + sendSignedTransaction(signature: string, txBody: TransactionBody, isDryrun?: boolean | undefined): Promise { + throw new Error("Method not implemented."); + } } \ No newline at end of file diff --git a/src/signer/default-signer.ts b/src/signer/default-signer.ts new file mode 100644 index 0000000..cfa5a89 --- /dev/null +++ b/src/signer/default-signer.ts @@ -0,0 +1,154 @@ +import { TransactionBody } from "@ainblockchain/ain-util"; +import Provider from "../provider"; +import Wallet from "../wallet"; +import { Signer } from "./signer"; +import { TransactionInput } from "../types"; +import Ain from "../ain"; + +/** + * The default class of Signer interface implemented using Wallet class. + * When Ain class is initialized, DefaultSigner is set as its signer. + */ +export class DefaultSigner implements Signer { + /** The wallet object. */ + readonly wallet: Wallet; + /** The network provider object. */ + readonly provider: Provider; + + /** + * Creates a new DefaultClass object. + * @param {Wallet} wallet The wallet object. + * @param {Provider} provider The network provider object. + */ + constructor(wallet: Wallet, provider: Provider) { + this.wallet = wallet; + this.provider = provider; + } + + /** + * Gets an account's checksum address. + * If the address is not given, the default account of the wallet is used. + * @param {string} address The address of the account. + * @returns {string} The checksum address. + */ + getAddress(address?: string): string { + return this.wallet.getImpliedAddress(address); + } + + /** + * Signs a message using an account. + * If an address is not given, the default account of the wallet is used. + * @param {string} message The message to sign. + * @param {string} address The address of the account. + * @returns {Promise | string} The signature. + */ + signMessage(message: string, address?: string): Promise | string { + return this.wallet.sign(message, address); + } + + /** + * Signs and sends a transaction to the network. + * @param {TransactionInput} transactionObject The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. + */ + async sendTransaction(transactionObject: TransactionInput, isDryrun: boolean = false) { + const txBody = await this.buildTransactionBody(transactionObject); + const signature = await this.wallet.signTransaction(txBody, transactionObject.address); + return await this.sendSignedTransaction(signature, txBody, isDryrun); + } + + /** + * Signs and sends multiple transactions in a batch to the network. + * @param {TransactionInput[]} transactionObjects The list of the transaction input objects. + * @returns {Promise} The return value of the blockchain API. + */ + async sendTransactionBatch(transactionObjects: TransactionInput[]): Promise { + let promises: Promise[] = []; + for (let tx of transactionObjects) { + promises.push(this.buildTransactionBody(tx).then(async (txBody) => { + if (tx.nonce === undefined) { + // Batch transactions' nonces should be specified. + // If they're not, they default to un-nonced (nonce = -1). + txBody.nonce = -1; + } + const signature = await this.wallet.signTransaction(txBody, tx.address); + return { signature, tx_body: txBody }; + })); + } + return Promise.all(promises).then(async (tx_list) => { + const resultList = await this.provider.send('ain_sendSignedTransactionBatch', { tx_list }); + if (!Array.isArray(resultList)) { + return resultList; + } + const len = resultList.length; + if (len !== tx_list.length) { + return resultList; + } else { + for (let i = 0; i < len; i++) { + if (!resultList[i] || typeof resultList[i] !== 'object') { + resultList[i] = { result: resultList[i] }; + } + } + return resultList; + } + }) + } + + /** + * Sends a signed transaction to the network. + * @param {string} signature The signature. + * @param {TransactionBody} txBody The transaction body. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. + */ + async sendSignedTransaction(signature: string, txBody: TransactionBody, isDryrun: boolean = false): Promise { + const method = isDryrun ? 'ain_sendSignedTransactionDryrun' : 'ain_sendSignedTransaction'; + let result = await this.provider.send(method, { signature, tx_body: txBody }); + if (!result || typeof result !== 'object') { + result = { result }; + } + return result; + } + + /** + * Builds a transaction body object from a transaction input object. + * @param {TransactionInput} transactionInput The transaction input object. + * @returns {Promise} The transaction body object. + */ + async buildTransactionBody(transactionInput: TransactionInput): Promise { + const address = this.getAddress(transactionInput.address); + let tx = { + operation: transactionInput.operation, + parent_tx_hash: transactionInput.parent_tx_hash + } + let nonce = transactionInput.nonce; + if (nonce === undefined) { + nonce = await this.getNonce({ address, from: "pending" }); + } + const timestamp = transactionInput.timestamp ? transactionInput.timestamp : Date.now(); + const gasPrice = transactionInput.gas_price || 0; + const billing = transactionInput.billing; + return Object.assign(tx, { nonce, timestamp, gas_price: gasPrice, billing }); + } + + /** + * Fetches an account's nonce value, which is the current transaction count of the account. + * @param {object} args The ferch options. + * It may contain a string 'address' value and a string 'from' value. + * The 'address' is the address of the account to get the nonce of, + * and the 'from' is the source of the data. + * It could be either the pending transaction pool ("pending") or + * the committed blocks ("committed"). The default value is "committed". + * @returns {Promise} The nonce value. + */ + getNonce(args: { address?: string, from?: string }): Promise { + if (!args) { args = {}; } + const address = args.address ? Ain.utils.toChecksumAddress(args.address) + : this.getAddress(args.address); + if (args.from !== undefined && args.from !== 'pending' && args.from !== 'committed') { + throw Error("'from' should be either 'pending' or 'committed'"); + } + return this.provider.send('ain_getNonce', { address, from: args.from }) + } +} \ No newline at end of file diff --git a/src/signer/signer.ts b/src/signer/signer.ts index 57b5b19..085e741 100644 --- a/src/signer/signer.ts +++ b/src/signer/signer.ts @@ -1,60 +1,47 @@ -import Wallet from "../wallet"; +import { TransactionBody, TransactionInput } from "../types"; /** * An interface for signing messages and transactions. */ export interface Signer { - /** - * Returns the checksum address to sign messages with. - * If the address is not given, the default address of the signer is used. - * @param {string} address - The address of the account to sign the message with. - */ - getAddress(address?: string): string; + /** + * Gets an account's checksum address. + * If the address is not given, the default account of the signer is used. + * @param {string} address The address of the account. + * @returns {string} The checksum address. + */ + getAddress(address?: string): string; - /** - * Signs a message with the private key of the given address. - * If an address is not given, the default address of the signer is used. - * @param {any} message - The message to sign. - * @param {string} address - The address of the account to sign the message with. - */ - signMessage(message: any, address?: string): Promise | string; -} + /** + * Signs a message using an account. + * If an address is not given, the default account of the signer is used. + * @param {string} message The message to sign. + * @param {string} address The address of the account. + * @returns {Promise | string} The signature. + */ + signMessage(message: string, address?: string): Promise | string; -/** - * The default class of Signer interface implemented using Wallet class. - * When Ain class is initialized, DefaultSigner is set as its signer. - */ -export class DefaultSigner implements Signer { - readonly wallet: Wallet; + /** + * Signs and sends a transaction to the network. + * @param {TransactionInput} transactionObject The transaction input object. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. + */ + sendTransaction(transactionObject: TransactionInput, isDryrun?: boolean); - /** - * Initializes the class. - * @param {Wallet} wallet - The wallet to initialize with. - */ - constructor(wallet: Wallet) { - this.wallet = wallet; - } + /** + * Signs and sends multiple transactions in a batch to the network. + * @param {TransactionInput[]} transactionObjects The list of the transaction input objects. + * @returns {Promise} The return value of the blockchain API. + */ + sendTransactionBatch(transactionObjects: TransactionInput[]): Promise - /** - * Returns the checksum address to sign messages with. - * If the address is not given, the default address of the wallet is used. - * @param {string} address - The address of the account to sign the message with. - */ - getAddress(address?: string): string { - return this.wallet.getImpliedAddress(address); - } - - /** - * Signs a message with the private key of the given address. - * If an address is not given, the default address of the wallet is used. - * @param {any} message - The message to sign. - * @param {string} address - The address of the account to sign the message with. - */ - signMessage(message: any, address?: string): Promise | string { - if (typeof message === 'string') { - return this.wallet.sign(message, address); - } else { - return this.wallet.signTransaction(message, address); - } - } -} \ No newline at end of file + /** + * Sends a signed transaction to the network. + * @param {string} signature The signature. + * @param {TransactionBody} txBody The transaction body. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. + */ + sendSignedTransaction(signature: string, txBody: TransactionBody, isDryrun?: boolean): Promise +} diff --git a/src/types.ts b/src/types.ts index fc665b5..d8c8452 100755 --- a/src/types.ts +++ b/src/types.ts @@ -7,14 +7,26 @@ declare global { } } +/** + * An interface for account. + */ export interface Account { + /** The address. */ address: string; + /** The private key. */ private_key: string; + /** The public key. */ public_key: string; } +/** + * An interface for account list. + */ export interface Accounts {[address: string]: Account} +/** + * An interface for key derivation function parameters. + */ export interface KdfParams { dklen: number; salt: string; @@ -25,6 +37,9 @@ export interface KdfParams { p?: number; } +/** + * An interface for v3 keystore options. + */ export interface V3KeystoreOptions { salt?: string; iv?: Buffer; @@ -39,6 +54,9 @@ export interface V3KeystoreOptions { uuid?: Buffer; } +/** + * An interface for keystore files in Ethereum wallet format version 3. + */ export interface V3Keystore { version: 3; id: string; @@ -55,23 +73,41 @@ export interface V3Keystore { }; } +/** + * A type for Ain options. + */ export type AinOptions = { + /** The raw result mode option. */ rawResultMode?: boolean; + /** The axios request config object. */ axiosConfig?: AxiosRequestConfig | undefined; } // export type EventType = "value" | "child_added" | "child_changed" | "child_removed"; +/** + * A type for multi-set (SET) operation type value. + */ export type SetMultiOperationType = "SET"; -export type GetMultiOperationType = "GET"; - +/** + * A type for set operation type values. + */ export type SetOperationType = "SET_VALUE" | "INC_VALUE" | "DEC_VALUE" | "SET_RULE" | "SET_OWNER" | "SET_FUNCTION"; +/** + * A type for get operation type values. + */ export type GetOperationType = "GET_VALUE" | "GET_RULE" | "GET_OWNER" | "GET_FUNCTION"; +/** + * A type for owner permission values. + */ export type OwnerPermission = "branch_owner" | "write_function" | "write_owner" | "write_rule"; +/** + * A type for blockchain get API options. + */ export type GetOptions = { is_global?: boolean; is_final?: boolean; @@ -81,6 +117,9 @@ export type GetOptions = { include_proof?: boolean; } +/** + * An interface for blockchain set operation. + */ export interface SetOperation { type: SetOperationType; ref: string; @@ -88,26 +127,33 @@ export interface SetOperation { is_global?: boolean; } +/** + * An interface for blockchain multi-set (SET) operation. + */ export interface SetMultiOperation { type: SetMultiOperationType; op_list: SetOperation[]; } +/** + * An interface for blockchain get operation. + */ export interface GetOperation extends GetOptions { type: GetOperationType; ref?: string; } -export interface GetMultiOperation { - type: GetMultiOperationType; - op_list: GetOperation[]; -} - +/** + * An interface for transaction body base. + */ export interface TransactionBodyBase { parent_tx_hash?: string; operation: SetOperation | SetMultiOperation; } +/** + * An interface for value-only transaction body base. + */ export interface ValueOnlyTransactionBodyBase { parent_tx_hash?: string; value?: any; @@ -115,6 +161,9 @@ export interface ValueOnlyTransactionBodyBase { is_global?: boolean; } +/** + * An interface for transaction input base. + */ export interface TransactionInputBase { nonce?: number; address?: string; @@ -123,6 +172,9 @@ export interface TransactionInputBase { billing?: string; } +/** + * An interface for transaction body. + */ export interface TransactionBody extends TransactionBodyBase { nonce: number; timestamp: number; @@ -130,15 +182,27 @@ export interface TransactionBody extends TransactionBodyBase { billing?: string; } +/** + * An interface for transaction input. + */ export interface TransactionInput extends TransactionBodyBase, TransactionInputBase {} +/** + * An interface for value-only transaction input. + */ export interface ValueOnlyTransactionInput extends ValueOnlyTransactionBodyBase, TransactionInputBase {} +/** + * An interface for multi-set (SET) transaction input. + */ export interface SetMultiTransactionInput extends TransactionInputBase { parent_tx_hash?: string; op_list?: SetOperation[]; } +/** + * An interface for transaction. + */ export interface Transaction { tx_body: TransactionBody; signature: string; @@ -146,6 +210,9 @@ export interface Transaction { address: string; } +/** + * An interface for transaction information. + */ export interface TransactionInfo { transaction: Transaction; status: string; @@ -156,6 +223,9 @@ export interface TransactionInfo { finalized_at: number; } +/** + * An interface for transaction result. + */ export interface TransactionResult { status: boolean; block_hash?: string; @@ -166,6 +236,9 @@ export interface TransactionResult { parent_tx_hash?: string; } +/** + * An interface for blockchain block. + */ export interface Block { number: number; epoch: number; @@ -182,10 +255,16 @@ export interface Block { transactions_hash: string; } +/** + * An interface for listener map. + */ export interface ListenerMap { [key: string]: Function[]; } +/** + * An interface for eval rule (EVAL_RULE) input. + */ export interface EvalRuleInput { value: any; ref?: string; @@ -194,6 +273,9 @@ export interface EvalRuleInput { is_global?: boolean; } +/** + * An interface for eval owner (EVAL_OWNER) input. + */ export interface EvalOwnerInput { ref?: string; address?: string; @@ -201,33 +283,51 @@ export interface EvalOwnerInput { is_global?: boolean; } +/** + * An interface for match input. + */ export interface MatchInput { ref?: string; is_global?: boolean; } +/** + * An interface for state usage information. + */ export interface StateUsageInfo { tree_height?: number; tree_size?: number; tree_bytes?: number; } +/** + * An interface for app name validation information. + */ export interface AppNameValidationInfo { is_valid: boolean; code: number; message?: string; } +/** + * A type for homomorphic encryption (HE) parameters. + */ export type HomomorphicEncryptionParams = { polyModulusDegree: number; coeffModulusArray: Int32Array; scaleBit: number; } +/** + * A type for homomorphic encryption (HE) secret key. + */ export type HomomorphicEncryptionSecretKey = { secretKey: string; } +/** + * Blockchain event types for blockchain event handler. + */ export enum BlockchainEventTypes { BLOCK_FINALIZED = 'BLOCK_FINALIZED', VALUE_CHANGED = 'VALUE_CHANGED', @@ -235,6 +335,9 @@ export enum BlockchainEventTypes { FILTER_DELETED = 'FILTER_DELETED', } +/** + * Event channel message types for blockchain event handler. + */ export enum EventChannelMessageTypes { REGISTER_FILTER = 'REGISTER_FILTER', DEREGISTER_FILTER = 'DEREGISTER_FILTER', @@ -242,48 +345,81 @@ export enum EventChannelMessageTypes { EMIT_ERROR = 'EMIT_ERROR', } +/** + * An interface for event channel message (blockchain event handler). + */ export interface EventChannelMessage { type: EventChannelMessageTypes; data: any; } +/** + * An interface for block-finalized event configuration (blockchain event handler). + */ export interface BlockFinalizedEventConfig { block_number: number | null; } +/** + * A type for value-changed event source (blockchain event handler). + */ export type ValueChangedEventSource = 'BLOCK' | 'USER'; +/** + * An interface for value-changed event configuraiton (blockchain event handler). + */ export interface ValueChangedEventConfig { path: string; event_source: ValueChangedEventSource | null; } +/** + * An interface for transaction-state-changed event configuration (blockchain event handler). + */ export interface TxStateChangedEventConfig { tx_hash: string; } -export type EventConfigType = BlockFinalizedEventConfig | ValueChangedEventConfig | TxStateChangedEventConfig; +/** + * A type for blockchain event configuration (blockchain event handler). + */ +export type BlockchainEventConfig = BlockFinalizedEventConfig | ValueChangedEventConfig | TxStateChangedEventConfig; -export interface EventChannelConnectionOption { +/** + * An interface for event-channel-connection options (blockchain event handler). + */ +export interface EventChannelConnectionOptions { handshakeTimeout?: number; heartbeatIntervalMs?: number; } +/** + * An interface for error handling callbacks (blockchain event handler). + */ export interface ErrorFirstCallback { (err: any, result?: undefined | null): void; (err: undefined | null, result: T): void; } +/** + * An interface for block-finalized event (blockchain event handler). + */ export interface BlockFinalizedEvent { block_number: number; block_hash: string; } +/** + * An interface for value-changed event authentication (blockchain event handler). + */ export interface ValueChangedEventAuth { addr?: string; fid?: string; } +/** + * An interface for value-changed event (blockchain event handler). + */ export interface ValueChangedEvent { filter_path: string; matched_path: string; @@ -297,6 +433,9 @@ export interface ValueChangedEvent { }; } +/** + * Transaction states for transaction-state-changed event (blockchain event handler). + */ export enum TransactionStates { FINALIZED = 'FINALIZED', REVERTED = 'REVERTED', // Failed but included in a block @@ -307,6 +446,9 @@ export enum TransactionStates { TIMED_OUT = 'TIMED_OUT', }; +/** + * An interface for transaction-state-changed event (blockchain event handler). + */ export interface TxStateChangedEvent { transaction: Transaction; tx_state: { @@ -315,22 +457,42 @@ export interface TxStateChangedEvent { }; } +/** + * Filter deletion reasons (blockchain event handler). + */ export enum FilterDeletionReasons { FILTER_TIMEOUT = 'FILTER_TIMEOUT', END_STATE_REACHED = 'END_STATE_REACHED', } +/** + * An interface for filter-deleted event (blockchain event handler). + */ export interface FilterDeletedEvent { filter_id: string; reason: FilterDeletionReasons; } +/** + * An interface for blockchain event callback functions (blockchain event handler). + */ export interface BlockchainEventCallback { (event: BlockFinalizedEvent): void; (event: ValueChangedEvent): void; (event: TxStateChangedEvent): void; } +/** + * A type for blockchain error callback functions (blockchain event handler). + */ +export type BlockchainErrorCallback = (error: any) => void; + +/** + * A type for filter-deleted event callback functions (blockchain event handler). + */ export type FilterDeletedEventCallback = (event: FilterDeletedEvent) => void; -export type DisconnectCallback = (webSocket) => void; +/** + * A type for disconnection callback functions (blockchain event handler). + */ +export type DisconnectionCallback = (webSocket) => void; diff --git a/src/wallet.ts b/src/wallet.ts index 7178f78..fef037b 100755 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -1,18 +1,28 @@ -import { Accounts, Account, TransactionBody, V3Keystore, V3KeystoreOptions, KdfParams } from './types'; +import { Accounts, Account, TransactionBody, V3Keystore, V3KeystoreOptions } from './types'; import Ain from './ain'; import { validateMnemonic, mnemonicToSeedSync } from 'bip39'; import Reference from './ain-db/ref'; const AIN_HD_DERIVATION_PATH = "m/44'/412'/0'/0/"; /* default wallet address for AIN */ +/** + * A class for AI Network wallets. + */ export default class Wallet { + /** The default account. */ public defaultAccount: Account | null; + /** The list of accounts. */ public accounts: Accounts; + /** The number of accounts. */ public _length: number; + /** The Ain object. */ public ain: Ain; + /** The chain ID of the blockchain. */ public chainId: number; /** - * @constructor + * Creates a new Wallet object. + * @param {Ain} ain The Ain object. + * @param {number} chainId The chain ID. */ constructor(ain: Ain, chainId: number) { this.defaultAccount = null; @@ -40,7 +50,7 @@ export default class Wallet { /** * Returns the full public key of the given address. * @param {string} address - * @return {string} + * @returns {string} */ getPublicKey(address: string): string { const checksummed = Ain.utils.toChecksumAddress(address); @@ -49,8 +59,9 @@ export default class Wallet { } /** - * Creates {numberOfAccounts} new accounts and add them to the wallet. - * @param {number} numberOfAccounts + * Creates new accounts and adds them to the wallet. + * @param {number} numberOfAccounts The number of accounts to create. + * @returns {Array} The newly created accounts. */ create(numberOfAccounts: number): Array { if (numberOfAccounts <= 0) throw Error("numberOfAccounts should be greater than 0."); @@ -67,8 +78,8 @@ export default class Wallet { /** * Returns whether the address has already been added to the wallet. - * @param {string} address - * @return {boolean} + * @param {string} address The address to check. + * @returns {boolean} */ isAdded(address: string): boolean { return !!(this.accounts[Ain.utils.toChecksumAddress(address)]) @@ -76,7 +87,8 @@ export default class Wallet { /** * Adds a new account from the given private key. - * @param {string} privateKey + * @param {string} privateKey The private key. + * @returns {string} The address of the newly added account. */ add(privateKey: string): string { let newAccount = Wallet.fromPrivateKey(Buffer.from(privateKey, 'hex')); @@ -87,8 +99,8 @@ export default class Wallet { /** * Adds a new account from the given private key and sets the new account as the default account. - * @param {string} privateKey - * @return {string} address + * @param {string} privateKey The private key. + * @returns {string} The address of the newly added account. */ addAndSetDefaultAccount(privateKey: string): string { const address = this.add(privateKey); @@ -99,9 +111,9 @@ export default class Wallet { /** * Adds an account from a seed phrase. Only the account at the given * index (default = 0) will be added. - * @param {string} seedPhrase - * @param {number} index - * @return {string} - The address of the newly added account. + * @param {string} seedPhrase The seed phrase. + * @param {number} index The index of the account. + * @returns {string} The address of the newly added account. */ addFromHDWallet(seedPhrase: string, index: number = 0): string { if (index < 0) { @@ -127,10 +139,10 @@ export default class Wallet { } /** - * Adds an account from a V3 Keystore. - * @param {V3Keystore | string} v3Keystore - * @param {string} [password] - * @return {string} - The address of the newly added account. + * Adds an account from a v3 keystore. + * @param {V3Keystore | string} v3Keystore The v3 keystore. + * @param {string} [password] The password of the v3 keystore. + * @returns {string} The address of the newly added account. */ addFromV3Keystore(v3Keystore: V3Keystore | string, password: string): string { const privateKey = Ain.utils.v3KeystoreToPrivate(v3Keystore, password); @@ -139,8 +151,8 @@ export default class Wallet { } /** - * Removes an account - * @param {string} address + * Removes an account from the wallet. + * @param {string} address The address of the account to be removed. */ remove(address: string) { let addressToRemove = Ain.utils.toChecksumAddress(address); @@ -158,7 +170,7 @@ export default class Wallet { /** * Sets the default account as {address}. The account should be already added * in the wallet. - * @param {string} address + * @param {string} address The address of the account. */ setDefaultAccount(address: string) { const checksummed = Ain.utils.toChecksumAddress(address); @@ -190,7 +202,7 @@ export default class Wallet { * it returns the defaultAccount. It throws an error if * an address is not given and defaultAccount is not set, or * the specified address is not added to the wallet. - * @param {string} address + * @param {string} address The address of the account. */ getImpliedAddress(inputAddress?: string) { const address = inputAddress || (this.defaultAccount ? this.defaultAccount.address : null); @@ -205,8 +217,9 @@ export default class Wallet { } /** - * Returns the AIN balance of the address. - * @param {string} address - Defaults to the defaultAccount. + * Fetches the AIN balance of the address. + * @param {string} address The address of the account. It defaults to the default account of the wallet. + * @returns {Promise} The AIN balance of the account. */ getBalance(address?: string): Promise { const addr = address ? Ain.utils.toChecksumAddress(address) @@ -216,8 +229,9 @@ export default class Wallet { /** * Sends a transfer transaction to the network. - * @param input - * @param {boolean} isDryrun - dryrun option. + * @param {{to: string, value: number, from?: string, nonce?: number, gas_price?: number}} input The input parameters of the transaction. + * @param {boolean} isDryrun The dryrun option. + * @returns {Promise} The return value of the blockchain API. */ transfer(input: {to: string, value: number, from?: string, nonce?: number, gas_price?: number}, isDryrun: boolean = false): Promise { const address = this.getImpliedAddress(input.from); @@ -229,10 +243,10 @@ export default class Wallet { /** * Signs a string data with the private key of the given address. It will use - * the defaultAccount if an address is not provided. - * @param {string} data - * @param {string} address - * @return {string} - signature + * the default account if an address is not provided. + * @param {string} data The data to sign. + * @param {string} address The address of the account. It defaults to the default account of the wallet. + * @returns {string} The signature. */ sign(data: string, address?: string): string { const addr = this.getImpliedAddress(address); @@ -240,20 +254,21 @@ export default class Wallet { } /** - * Signs a transaction data with the private key of the given address. It will use - * the defaultAccount if an address is not provided. - * @param {TransactionBody} data - * @param {string} address - * @return {string} - signature + * Signs a transaction body with the private key of the given address. It will use + * the default account if an address is not provided. + * @param {TransactionBody} txBody The transaction body. + * @param {string} address The address of the account. It defaults to the adefault account of the wallet.. + * @returns {string} The signature. */ - signTransaction(tx: TransactionBody, address?: string): string { + signTransaction(txBody: TransactionBody, address?: string): string { const addr = this.getImpliedAddress(address); - return Ain.utils.ecSignTransaction(tx, Buffer.from(this.accounts[addr].private_key, 'hex'), this.chainId); + return Ain.utils.ecSignTransaction(txBody, Buffer.from(this.accounts[addr].private_key, 'hex'), this.chainId); } /** * Gets the hash from the signature. - * @param {string} signature + * @param {string} signature The signature. + * @returns {string} The hash of the signature. */ getHashStrFromSig(signature: string): string { const sigBuffer = Ain.utils.toBuffer(signature); @@ -265,8 +280,8 @@ export default class Wallet { /** * Recovers an address of the account that was used to create the signature. - * @param {string} signature - * @return {string} - address + * @param {string} signature The signature. + * @returns {string} The address recovered. */ recover(signature: string): string { const sigBuffer = Ain.utils.toBuffer(signature); @@ -281,20 +296,20 @@ export default class Wallet { /** * Verifies if the signature is valid and was signed by the address. - * @param {any} data - * @param {string} signature - * @param {string} address - * @return {boolean} + * @param {any} data The data used in the signing. + * @param {string} signature The signature to verify. + * @param {string} address The address to verify. + * @returns {boolean} */ verifySignature(data: any, signature: string, address: string): boolean { return Ain.utils.ecVerifySig(data, signature, address, this.chainId); } /** - * Save the accounts in the wallet as V3 Keystores, locking them with the password. - * @param {string} password - * @param {V3KeystoreOptions} options - * @return {V3Keystore[]} + * Saves the accounts in the wallet as v3 keystores, locking them with the password. + * @param {string} password The password. + * @param {V3KeystoreOptions} options The v3 keystore options. + * @returns {V3Keystore[]} The v3 keystores. */ toV3Keystore(password: string, options: V3KeystoreOptions = {}): V3Keystore[] { let V3KeystoreArr: V3Keystore[] = []; @@ -306,12 +321,11 @@ export default class Wallet { } /** - * Converts an account into a V3 Keystore and encrypts it with a password. - * @param {TransactionBody} data - * @param {string} address - * @param {string} password - * @param {V3KeystoreOptions} options - * @return {V3Keystore} + * Converts an account into a v3 keystore and encrypts it with a password. + * @param {string} address The address of the account. + * @param {string} password The password. + * @param {V3KeystoreOptions} options The v3 keystore options. + * @returns {V3Keystore} The v3 keystore. */ accountToV3Keystore( address: string, @@ -327,8 +341,8 @@ export default class Wallet { /** * Imports an account from a private key. - * @param {Buffer} privateKey - * @return {Account} + * @param {Buffer} privateKey The private key. + * @returns {Account} The account. */ static fromPrivateKey(privateKey: Buffer): Account { let publicKey = Ain.utils.privateToPublic(privateKey); diff --git a/yarn.lock b/yarn.lock index 9b4f8d3..8d7d0ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -727,6 +727,11 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@mxssfd/typedoc-theme@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@mxssfd/typedoc-theme/-/typedoc-theme-1.1.3.tgz#ca1f20aeee08fad19cc6b71111ac09fc330c219c" + integrity sha512-/yP5rqhvibMpzXpmw0YLLRCpoj3uVWWlwyJseZXzGxTfiA6/fd1uubUqNoQAi2U19atMDonq8mQc+hlVctrX4g== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"