/** * @packageDocumentation * @module API-XVM */ import BN from "bn.js" import { Buffer } from "buffer/" import LuxCore from "../../lux" import BinTools from "../../utils/bintools" import { UTXO, UTXOSet } from "./utxos" import { XVMConstants } from "./constants" import { KeyChain } from "./keychain" import { Tx, UnsignedTx } from "./tx" import { PayloadBase } from "../../utils/payload" import { SECPMintOutput } from "./outputs" import { InitialStates } from "./initialstates" import { UnixNow } from "../../utils/helperfunctions" import { JRPCAPI } from "../../common/jrpcapi" import { RequestResponseData } from "../../common/apibase" import { Defaults, PrimaryAssetAlias, ONELUX } from "../../utils/constants" import { MinterSet } from "./minterset" import { PersistanceOptions } from "../../utils/persistenceoptions" import { OutputOwners } from "../../common/output" import { SECPTransferOutput } from "./outputs" import { AddressError, GooseEggCheckError, ChainIdError, NoAtomicUTXOsError, SymbolError, NameError, TransactionError } from "../../utils/errors" import { Serialization, SerializedType } from "../../utils" import { BuildGenesisParams, CreateAddressParams, CreateFixedCapAssetParams, CreateVariableCapAssetParams, ExportParams, ExportKeyParams, GetAllBalancesParams, GetAssetDescriptionParams, GetLUXAssetIDParams, GetBalanceParams, GetTxParams, GetTxStatusParams, GetUTXOsParams, ImportParams, ImportKeyParams, ListAddressesParams, MintParams, SendMultipleParams, SOutputsParams, GetUTXOsResponse, GetAssetDescriptionResponse, GetBalanceResponse, SendParams, SendResponse, SendMultipleResponse, GetAddressTxsParams, GetAddressTxsResponse, CreateNFTAssetParams, SendNFTParams, MintNFTParams, IMinterSet } from "./interfaces" import { IssueTxParams } from "../../common" /** * @ignore */ const bintools: BinTools = BinTools.getInstance() const serialization: Serialization = Serialization.getInstance() /** * Class for interacting with a node endpoint that is using the XVM. * * @category RPCAPIs * * @remarks This extends the [[JRPCAPI]] class. This class should not be directly called. Instead, use the [[Lux.addAPI]] function to register this interface with Lux. */ export class XVMAPI extends JRPCAPI { /** * @ignore */ protected keychain: KeyChain = new KeyChain("", "") protected blockchainID: string = "" protected blockchainAlias: string = undefined protected LUXAssetID: Buffer = undefined protected txFee: BN = undefined protected creationTxFee: BN = undefined protected mintTxFee: BN = undefined /** * Gets the alias for the blockchainID if it exists, otherwise returns `undefined`. * * @returns The alias for the blockchainID */ getBlockchainAlias = (): string => { if (typeof this.blockchainAlias === "undefined") { const netid: number = this.core.getNetworkID() if ( netid in Defaults.network && this.blockchainID in Defaults.network[`${netid}`] ) { this.blockchainAlias = Defaults.network[`${netid}`][this.blockchainID]["alias"] return this.blockchainAlias } else { /* istanbul ignore next */ return undefined } } return this.blockchainAlias } /** * Sets the alias for the blockchainID. * * @param alias The alias for the blockchainID. * */ setBlockchainAlias = (alias: string): undefined => { this.blockchainAlias = alias /* istanbul ignore next */ return undefined } /** * Gets the blockchainID and returns it. * * @returns The blockchainID */ getBlockchainID = (): string => this.blockchainID /** * Refresh blockchainID, and if a blockchainID is passed in, use that. * * @param Optional. BlockchainID to assign, if none, uses the default based on networkID. * * @returns The blockchainID */ refreshBlockchainID = (blockchainID: string = undefined): boolean => { const netid: number = this.core.getNetworkID() if ( typeof blockchainID === "undefined" && typeof Defaults.network[`${netid}`] !== "undefined" ) { this.blockchainID = Defaults.network[`${netid}`].X.blockchainID //default to X-Chain return true } if (typeof blockchainID === "string") { this.blockchainID = blockchainID return true } return false } /** * Takes an address string and returns its {@link https://github.com/feross/buffer|Buffer} representation if valid. * * @returns A {@link https://github.com/feross/buffer|Buffer} for the address if valid, undefined if not valid. */ parseAddress = (addr: string): Buffer => { const alias: string = this.getBlockchainAlias() const blockchainID: string = this.getBlockchainID() return bintools.parseAddress( addr, blockchainID, alias, XVMConstants.ADDRESSLENGTH ) } addressFromBuffer = (address: Buffer): string => { const chainID: string = this.getBlockchainAlias() ? this.getBlockchainAlias() : this.getBlockchainID() const type: SerializedType = "bech32" const hrp: string = this.core.getHRP() return serialization.bufferToType(address, type, hrp, chainID) } /** * Fetches the LUX AssetID and returns it in a Promise. * * @param refresh This function caches the response. Refresh = true will bust the cache. * * @returns The the provided string representing the LUX AssetID */ getLUXAssetID = async (refresh: boolean = false): Promise => { if (typeof this.LUXAssetID === "undefined" || refresh) { const asset: GetLUXAssetIDParams = await this.getAssetDescription( PrimaryAssetAlias ) this.LUXAssetID = asset.assetID } return this.LUXAssetID } /** * Overrides the defaults and sets the cache to a specific LUX AssetID * * @param luxAssetID A cb58 string or Buffer representing the LUX AssetID * * @returns The the provided string representing the LUX AssetID */ setLUXAssetID = (luxAssetID: string | Buffer) => { if (typeof luxAssetID === "string") { luxAssetID = bintools.cb58Decode(luxAssetID) } this.LUXAssetID = luxAssetID } /** * Gets the default tx fee for this chain. * * @returns The default tx fee as a {@link https://github.com/indutny/bn.js/|BN} */ getDefaultTxFee = (): BN => { return this.core.getNetworkID() in Defaults.network ? new BN(Defaults.network[this.core.getNetworkID()]["X"]["txFee"]) : new BN(0) } /** * Gets the tx fee for this chain. * * @returns The tx fee as a {@link https://github.com/indutny/bn.js/|BN} */ getTxFee = (): BN => { if (typeof this.txFee === "undefined") { this.txFee = this.getDefaultTxFee() } return this.txFee } /** * Sets the tx fee for this chain. * * @param fee The tx fee amount to set as {@link https://github.com/indutny/bn.js/|BN} */ setTxFee = (fee: BN): void => { this.txFee = fee } /** * Gets the default creation fee for this chain. * * @returns The default creation fee as a {@link https://github.com/indutny/bn.js/|BN} */ getDefaultCreationTxFee = (): BN => { return this.core.getNetworkID() in Defaults.network ? new BN(Defaults.network[this.core.getNetworkID()]["X"]["creationTxFee"]) : new BN(0) } /** * Gets the default mint fee for this chain. * * @returns The default mint fee as a {@link https://github.com/indutny/bn.js/|BN} */ getDefaultMintTxFee = (): BN => { return this.core.getNetworkID() in Defaults.network ? new BN(Defaults.network[this.core.getNetworkID()]["X"]["mintTxFee"]) : new BN(0) } /** * Gets the mint fee for this chain. * * @returns The mint fee as a {@link https://github.com/indutny/bn.js/|BN} */ getMintTxFee = (): BN => { if (typeof this.mintTxFee === "undefined") { this.mintTxFee = this.getDefaultMintTxFee() } return this.mintTxFee } /** * Gets the creation fee for this chain. * * @returns The creation fee as a {@link https://github.com/indutny/bn.js/|BN} */ getCreationTxFee = (): BN => { if (typeof this.creationTxFee === "undefined") { this.creationTxFee = this.getDefaultCreationTxFee() } return this.creationTxFee } /** * Sets the mint fee for this chain. * * @param fee The mint fee amount to set as {@link https://github.com/indutny/bn.js/|BN} */ setMintTxFee = (fee: BN): void => { this.mintTxFee = fee } /** * Sets the creation fee for this chain. * * @param fee The creation fee amount to set as {@link https://github.com/indutny/bn.js/|BN} */ setCreationTxFee = (fee: BN): void => { this.creationTxFee = fee } /** * Gets a reference to the keychain for this class. * * @returns The instance of [[KeyChain]] for this class */ keyChain = (): KeyChain => this.keychain /** * @ignore */ newKeyChain = (): KeyChain => { // warning, overwrites the old keychain const alias: string = this.getBlockchainAlias() if (alias) { this.keychain = new KeyChain(this.core.getHRP(), alias) } else { this.keychain = new KeyChain(this.core.getHRP(), this.blockchainID) } return this.keychain } /** * Helper function which determines if a tx is a goose egg transaction. * * @param utx An UnsignedTx * * @returns boolean true if passes goose egg test and false if fails. * * @remarks * A "Goose Egg Transaction" is when the fee far exceeds a reasonable amount */ checkGooseEgg = async ( utx: UnsignedTx, outTotal: BN = new BN(0) ): Promise => { const luxAssetID: Buffer = await this.getLUXAssetID() const outputTotal: BN = outTotal.gt(new BN(0)) ? outTotal : utx.getOutputTotal(luxAssetID) const fee: BN = utx.getBurn(luxAssetID) if (fee.lte(ONELUX.mul(new BN(10))) || fee.lte(outputTotal)) { return true } else { return false } } /** * Gets the balance of a particular asset on a blockchain. * * @param address The address to pull the asset balance from * @param assetID The assetID to pull the balance from * @param includePartial If includePartial=false, returns only the balance held solely * * @returns Promise with the balance of the assetID as a {@link https://github.com/indutny/bn.js/|BN} on the provided address for the blockchain. */ getBalance = async ( address: string, assetID: string, includePartial: boolean = false ): Promise => { if (typeof this.parseAddress(address) === "undefined") { /* istanbul ignore next */ throw new AddressError( "Error - XVMAPI.getBalance: Invalid address format" ) } const params: GetBalanceParams = { address, assetID, includePartial } const response: RequestResponseData = await this.callMethod( "xvm.getBalance", params ) return response.data.result } /** * Creates an address (and associated private keys) on a user on a blockchain. * * @param username Name of the user to create the address under * @param password Password to unlock the user and encrypt the private key * * @returns Promise for a string representing the address created by the vm. */ createAddress = async ( username: string, password: string ): Promise => { const params: CreateAddressParams = { username, password } const response: RequestResponseData = await this.callMethod( "xvm.createAddress", params ) return response.data.result.address } /** * Create a new fixed-cap, fungible asset. A quantity of it is created at initialization and there no more is ever created. * * @param username The user paying the transaction fee (in $LUX) for asset creation * @param password The password for the user paying the transaction fee (in $LUX) for asset creation * @param name The human-readable name for the asset * @param symbol Optional. The shorthand symbol for the asset. Between 0 and 4 characters * @param denomination Optional. Determines how balances of this asset are displayed by user interfaces. Default is 0 * @param initialHolders An array of objects containing the field "address" and "amount" to establish the genesis values for the new asset * * ```js * Example initialHolders: * [ * { * "address": "X-lux1kj06lhgx84h39snsljcey3tpc046ze68mek3g5", * "amount": 10000 * }, * { * "address": "X-lux1am4w6hfrvmh3akduzkjthrtgtqafalce6an8cr", * "amount": 50000 * } * ] * ``` * * @returns Returns a Promise string containing the base 58 string representation of the ID of the newly created asset. */ createFixedCapAsset = async ( username: string, password: string, name: string, symbol: string, denomination: number, initialHolders: object[] ): Promise => { const params: CreateFixedCapAssetParams = { name, symbol, denomination, username, password, initialHolders } const response: RequestResponseData = await this.callMethod( "xvm.createFixedCapAsset", params ) return response.data.result.assetID } /** * Create a new variable-cap, fungible asset. No units of the asset exist at initialization. Minters can mint units of this asset using createMintTx, signMintTx and sendMintTx. * * @param username The user paying the transaction fee (in $LUX) for asset creation * @param password The password for the user paying the transaction fee (in $LUX) for asset creation * @param name The human-readable name for the asset * @param symbol Optional. The shorthand symbol for the asset -- between 0 and 4 characters * @param denomination Optional. Determines how balances of this asset are displayed by user interfaces. Default is 0 * @param minterSets is a list where each element specifies that threshold of the addresses in minters may together mint more of the asset by signing a minting transaction * * ```js * Example minterSets: * [ * { * "minters":[ * "X-lux1am4w6hfrvmh3akduzkjthrtgtqafalce6an8cr" * ], * "threshold": 1 * }, * { * "minters": [ * "X-lux1am4w6hfrvmh3akduzkjthrtgtqafalce6an8cr", * "X-lux1kj06lhgx84h39snsljcey3tpc046ze68mek3g5", * "X-lux1yell3e4nln0m39cfpdhgqprsd87jkh4qnakklx" * ], * "threshold": 2 * } * ] * ``` * * @returns Returns a Promise string containing the base 58 string representation of the ID of the newly created asset. */ createVariableCapAsset = async ( username: string, password: string, name: string, symbol: string, denomination: number, minterSets: object[] ): Promise => { const params: CreateVariableCapAssetParams = { name, symbol, denomination, username, password, minterSets } const response: RequestResponseData = await this.callMethod( "xvm.createVariableCapAsset", params ) return response.data.result.assetID } /** * Creates a family of NFT Asset. No units of the asset exist at initialization. Minters can mint units of this asset using createMintTx, signMintTx and sendMintTx. * * @param username The user paying the transaction fee (in $LUX) for asset creation * @param password The password for the user paying the transaction fee (in $LUX) for asset creation * @param from Optional. An array of addresses managed by the node's keystore for this blockchain which will fund this transaction * @param changeAddr Optional. An address to send the change * @param name The human-readable name for the asset * @param symbol Optional. The shorthand symbol for the asset -- between 0 and 4 characters * @param minterSets is a list where each element specifies that threshold of the addresses in minters may together mint more of the asset by signing a minting transaction * * @returns Returns a Promise string containing the base 58 string representation of the ID of the newly created asset. */ createNFTAsset = async ( username: string, password: string, from: string[] | Buffer[] = undefined, changeAddr: string, name: string, symbol: string, minterSet: IMinterSet ): Promise => { const params: CreateNFTAssetParams = { username, password, name, symbol, minterSet } const caller: string = "createNFTAsset" from = this._cleanAddressArray(from, caller) if (typeof from !== "undefined") { params["from"] = from } if (typeof changeAddr !== "undefined") { if (typeof this.parseAddress(changeAddr) === "undefined") { /* istanbul ignore next */ throw new AddressError( "Error - XVMAPI.createNFTAsset: Invalid address format" ) } params["changeAddr"] = changeAddr } const response: RequestResponseData = await this.callMethod( "xvm.createNFTAsset", params ) return response.data.result.assetID } /** * Create an unsigned transaction to mint more of an asset. * * @param amount The units of the asset to mint * @param assetID The ID of the asset to mint * @param to The address to assign the units of the minted asset * @param minters Addresses of the minters responsible for signing the transaction * * @returns Returns a Promise string containing the base 58 string representation of the unsigned transaction. */ mint = async ( username: string, password: string, amount: number | BN, assetID: Buffer | string, to: string, minters: string[] ): Promise => { let asset: string let amnt: BN if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } if (typeof amount === "number") { amnt = new BN(amount) } else { amnt = amount } const params: MintParams = { username: username, password: password, amount: amnt, assetID: asset, to, minters } const response: RequestResponseData = await this.callMethod( "xvm.mint", params ) return response.data.result.txID } /** * Mint non-fungible tokens which were created with XVMAPI.createNFTAsset * * @param username The user paying the transaction fee (in $LUX) for asset creation * @param password The password for the user paying the transaction fee (in $LUX) for asset creation * @param from Optional. An array of addresses managed by the node's keystore for this blockchain which will fund this transaction * @param changeAddr Optional. An address to send the change * @param assetID The asset id which is being sent * @param to Address on X-Chain of the account to which this NFT is being sent * @param encoding Optional. is the encoding format to use for the payload argument. Can be either "cb58" or "hex". Defaults to "hex". * * @returns ID of the transaction */ mintNFT = async ( username: string, password: string, from: string[] | Buffer[] = undefined, changeAddr: string = undefined, payload: string, assetID: string | Buffer, to: string, encoding: string = "hex" ): Promise => { let asset: string if (typeof this.parseAddress(to) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.mintNFT: Invalid address format") } if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } const params: MintNFTParams = { username, password, assetID: asset, payload, to, encoding } const caller: string = "mintNFT" from = this._cleanAddressArray(from, caller) if (typeof from !== "undefined") { params["from"] = from } if (typeof changeAddr !== "undefined") { if (typeof this.parseAddress(changeAddr) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.mintNFT: Invalid address format") } params["changeAddr"] = changeAddr } const response: RequestResponseData = await this.callMethod( "xvm.mintNFT", params ) return response.data.result.txID } /** * Send NFT from one account to another on X-Chain * * @param username The user paying the transaction fee (in $LUX) for asset creation * @param password The password for the user paying the transaction fee (in $LUX) for asset creation * @param from Optional. An array of addresses managed by the node's keystore for this blockchain which will fund this transaction * @param changeAddr Optional. An address to send the change * @param assetID The asset id which is being sent * @param groupID The group this NFT is issued to. * @param to Address on X-Chain of the account to which this NFT is being sent * * @returns ID of the transaction */ sendNFT = async ( username: string, password: string, from: string[] | Buffer[] = undefined, changeAddr: string = undefined, assetID: string | Buffer, groupID: number, to: string ): Promise => { let asset: string if (typeof this.parseAddress(to) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.sendNFT: Invalid address format") } if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } const params: SendNFTParams = { username, password, assetID: asset, groupID, to } const caller: string = "sendNFT" from = this._cleanAddressArray(from, caller) if (typeof from !== "undefined") { params["from"] = from } if (typeof changeAddr !== "undefined") { if (typeof this.parseAddress(changeAddr) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.sendNFT: Invalid address format") } params["changeAddr"] = changeAddr } const response: RequestResponseData = await this.callMethod( "xvm.sendNFT", params ) return response.data.result.txID } /** * Exports the private key for an address. * * @param username The name of the user with the private key * @param password The password used to decrypt the private key * @param address The address whose private key should be exported * * @returns Promise with the decrypted private key as store in the database */ exportKey = async ( username: string, password: string, address: string ): Promise => { if (typeof this.parseAddress(address) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.exportKey: Invalid address format") } const params: ExportKeyParams = { username, password, address } const response: RequestResponseData = await this.callMethod( "xvm.exportKey", params ) return response.data.result.privateKey } /** * Imports a private key into the node's keystore under an user and for a blockchain. * * @param username The name of the user to store the private key * @param password The password that unlocks the user * @param privateKey A string representing the private key in the vm's format * * @returns The address for the imported private key. */ importKey = async ( username: string, password: string, privateKey: string ): Promise => { const params: ImportKeyParams = { username, password, privateKey } const response: RequestResponseData = await this.callMethod( "xvm.importKey", params ) return response.data.result.address } /** * Send ANT (Lux Native Token) assets including LUX from the X-Chain to an account on the P-Chain or C-Chain. * * After calling this method, you must call the P-Chain's `import` or the C-Chain’s `import` method to complete the transfer. * * @param username The Keystore user that controls the P-Chain or C-Chain account specified in `to` * @param password The password of the Keystore user * @param to The account on the P-Chain or C-Chain to send the asset to. * @param amount Amount of asset to export as a {@link https://github.com/indutny/bn.js/|BN} * @param assetID The asset id which is being sent * * @returns String representing the transaction id */ export = async ( username: string, password: string, to: string, amount: BN, assetID: string ): Promise => { const params: ExportParams = { username, password, to, amount: amount, assetID } const response: RequestResponseData = await this.callMethod( "xvm.export", params ) return response.data.result.txID } /** * Send ANT (Lux Native Token) assets including LUX from an account on the P-Chain or C-Chain to an address on the X-Chain. This transaction * must be signed with the key of the account that the asset is sent from and which pays * the transaction fee. * * @param username The Keystore user that controls the account specified in `to` * @param password The password of the Keystore user * @param to The address of the account the asset is sent to. * @param sourceChain The chainID where the funds are coming from. Ex: "C" * * @returns Promise for a string for the transaction, which should be sent to the network * by calling issueTx. */ import = async ( username: string, password: string, to: string, sourceChain: string ): Promise => { const params: ImportParams = { username, password, to, sourceChain } const response: RequestResponseData = await this.callMethod( "xvm.import", params ) return response.data.result.txID } /** * Lists all the addresses under a user. * * @param username The user to list addresses * @param password The password of the user to list the addresses * * @returns Promise of an array of address strings in the format specified by the blockchain. */ listAddresses = async ( username: string, password: string ): Promise => { const params: ListAddressesParams = { username, password } const response: RequestResponseData = await this.callMethod( "xvm.listAddresses", params ) return response.data.result.addresses } /** * Retrieves all assets for an address on a server and their associated balances. * * @param address The address to get a list of assets * * @returns Promise of an object mapping assetID strings with {@link https://github.com/indutny/bn.js/|BN} balance for the address on the blockchain. */ getAllBalances = async (address: string): Promise => { if (typeof this.parseAddress(address) === "undefined") { /* istanbul ignore next */ throw new AddressError( "Error - XVMAPI.getAllBalances: Invalid address format" ) } const params: GetAllBalancesParams = { address } const response: RequestResponseData = await this.callMethod( "xvm.getAllBalances", params ) return response.data.result.balances } /** * Retrieves an assets name and symbol. * * @param assetID Either a {@link https://github.com/feross/buffer|Buffer} or an b58 serialized string for the AssetID or its alias. * * @returns Returns a Promise object with keys "name" and "symbol". */ getAssetDescription = async ( assetID: Buffer | string ): Promise => { let asset: string if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } const params: GetAssetDescriptionParams = { assetID: asset } const response: RequestResponseData = await this.callMethod( "xvm.getAssetDescription", params ) return { name: response.data.result.name, symbol: response.data.result.symbol, assetID: bintools.cb58Decode(response.data.result.assetID), denomination: parseInt(response.data.result.denomination, 10) } } /** * Returns the transaction data of a provided transaction ID by calling the node's `getTx` method. * * @param txID The string representation of the transaction ID * @param encoding sets the format of the returned transaction. Can be, "cb58", "hex" or "json". Defaults to "cb58". * * @returns Returns a Promise string or object containing the bytes retrieved from the node */ getTx = async ( txID: string, encoding: string = "hex" ): Promise => { const params: GetTxParams = { txID, encoding } const response: RequestResponseData = await this.callMethod( "xvm.getTx", params ) return response.data.result.tx } /** * Returns the status of a provided transaction ID by calling the node's `getTxStatus` method. * * @param txID The string representation of the transaction ID * * @returns Returns a Promise string containing the status retrieved from the node */ getTxStatus = async (txID: string): Promise => { const params: GetTxStatusParams = { txID } const response: RequestResponseData = await this.callMethod( "xvm.getTxStatus", params ) return response.data.result.status } /** * Retrieves the UTXOs related to the addresses provided from the node's `getUTXOs` method. * * @param addresses An array of addresses as cb58 strings or addresses as {@link https://github.com/feross/buffer|Buffer}s * @param sourceChain A string for the chain to look for the UTXO's. Default is to use this chain, but if exported UTXOs exist from other chains, this can used to pull them instead. * @param limit Optional. Returns at most [limit] addresses. If [limit] == 0 or > [maxUTXOsToFetch], fetches up to [maxUTXOsToFetch]. * @param startIndex Optional. [StartIndex] defines where to start fetching UTXOs (for pagination.) * UTXOs fetched are from addresses equal to or greater than [StartIndex.Address] * For address [StartIndex.Address], only UTXOs with IDs greater than [StartIndex.Utxo] will be returned. * @param persistOpts Options available to persist these UTXOs in local storage * * @remarks * persistOpts is optional and must be of type [[PersistanceOptions]] * */ getUTXOs = async ( addresses: string[] | string, sourceChain: string = undefined, limit: number = 0, startIndex: { address: string; utxo: string } = undefined, persistOpts: PersistanceOptions = undefined, encoding: string = "hex" ): Promise => { if (typeof addresses === "string") { addresses = [addresses] } const params: GetUTXOsParams = { addresses: addresses, limit, encoding } if (typeof startIndex !== "undefined" && startIndex) { params.startIndex = startIndex } if (typeof sourceChain !== "undefined") { params.sourceChain = sourceChain } const response: RequestResponseData = await this.callMethod( "xvm.getUTXOs", params ) const utxos: UTXOSet = new UTXOSet() let data = response.data.result.utxos if (persistOpts && typeof persistOpts === "object") { if (this.db.has(persistOpts.getName())) { const selfArray: string[] = this.db.get(persistOpts.getName()) if (Array.isArray(selfArray)) { utxos.addArray(data) const utxoSet: UTXOSet = new UTXOSet() utxoSet.addArray(selfArray) utxoSet.mergeByRule(utxos, persistOpts.getMergeRule()) data = utxoSet.getAllUTXOStrings() } } this.db.set(persistOpts.getName(), data, persistOpts.getOverwrite()) } if (data.length > 0 && data[0].substring(0, 2) === "0x") { const cb58Strs: string[] = [] data.forEach((str: string): void => { cb58Strs.push(bintools.cb58Encode(new Buffer(str.slice(2), "hex"))) }) utxos.addArray(cb58Strs, false) } else { utxos.addArray(data, false) } response.data.result.utxos = utxos return response.data.result } /** * Helper function which creates an unsigned transaction. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param amount The amount of AssetID to be spent in its smallest denomination, represented as {@link https://github.com/indutny/bn.js/|BN}. * @param assetID The assetID of the value being sent * @param toAddresses The addresses to send the funds * @param fromAddresses The addresses being used to send the funds from the UTXOs provided * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[BaseTx]]. * * @remarks * This helper exists because the endpoint API should be the primary point of entry for most functionality. */ buildBaseTx = async ( utxoset: UTXOSet, amount: BN, assetID: Buffer | string = undefined, toAddresses: string[], fromAddresses: string[], changeAddresses: string[], memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), threshold: number = 1 ): Promise => { const caller: string = "buildBaseTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (typeof assetID === "string") { assetID = bintools.cb58Decode(assetID) } if (memo instanceof PayloadBase) { memo = memo.getPayload() } const networkID: number = this.core.getNetworkID() const blockchainIDBuf: Buffer = bintools.cb58Decode(this.blockchainID) const fee: BN = this.getTxFee() const feeAssetID: Buffer = await this.getLUXAssetID() const builtUnsignedTx: UnsignedTx = utxoset.buildBaseTx( networkID, blockchainIDBuf, amount, assetID, to, from, change, fee, feeAssetID, memo, asOf, locktime, threshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildBaseTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Helper function which creates an unsigned NFT Transfer. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param toAddresses The addresses to send the NFT * @param fromAddresses The addresses being used to send the NFT from the utxoID provided * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param utxoid A base58 utxoID or an array of base58 utxoIDs for the nfts this transaction is sending * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[NFTTransferTx]]. * * @remarks * This helper exists because the endpoint API should be the primary point of entry for most functionality. */ buildNFTTransferTx = async ( utxoset: UTXOSet, toAddresses: string[], fromAddresses: string[], changeAddresses: string[], utxoid: string | string[], memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), threshold: number = 1 ): Promise => { const caller: string = "buildNFTTransferTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } const luxAssetID: Buffer = await this.getLUXAssetID() let utxoidArray: string[] = [] if (typeof utxoid === "string") { utxoidArray = [utxoid] } else if (Array.isArray(utxoid)) { utxoidArray = utxoid } const builtUnsignedTx: UnsignedTx = utxoset.buildNFTTransferTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), to, from, change, utxoidArray, this.getTxFee(), luxAssetID, memo, asOf, locktime, threshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildNFTTransferTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Helper function which creates an unsigned Import Tx. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param ownerAddresses The addresses being used to import * @param sourceChain The chainid for where the import is coming from * @param toAddresses The addresses to send the funds * @param fromAddresses The addresses being used to send the funds from the UTXOs provided * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[ImportTx]]. * * @remarks * This helper exists because the endpoint API should be the primary point of entry for most functionality. */ buildImportTx = async ( utxoset: UTXOSet, ownerAddresses: string[], sourceChain: Buffer | string, toAddresses: string[], fromAddresses: string[], changeAddresses: string[] = undefined, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), threshold: number = 1 ): Promise => { const caller: string = "buildImportTx" const to: Buffer[] = this._cleanAddressArray(toAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) let srcChain: string = undefined if (typeof sourceChain === "undefined") { throw new ChainIdError( "Error - XVMAPI.buildImportTx: Source ChainID is undefined." ) } else if (typeof sourceChain === "string") { srcChain = sourceChain sourceChain = bintools.cb58Decode(sourceChain) } else if (!(sourceChain instanceof Buffer)) { throw new ChainIdError( "Error - XVMAPI.buildImportTx: Invalid destinationChain type: " + typeof sourceChain ) } const atomicUTXOs: UTXOSet = ( await this.getUTXOs(ownerAddresses, srcChain, 0, undefined) ).utxos const luxAssetID: Buffer = await this.getLUXAssetID() const atomics: UTXO[] = atomicUTXOs.getAllUTXOs() if (atomics.length === 0) { throw new NoAtomicUTXOsError( "Error - XVMAPI.buildImportTx: No atomic UTXOs to import from " + srcChain + " using addresses: " + ownerAddresses.join(", ") ) } if (memo instanceof PayloadBase) { memo = memo.getPayload() } const builtUnsignedTx: UnsignedTx = utxoset.buildImportTx( this.core.getNetworkID(), bintools.cb58Decode(this.blockchainID), to, from, change, atomics, sourceChain, this.getTxFee(), luxAssetID, memo, asOf, locktime, threshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildImportTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Helper function which creates an unsigned Export Tx. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param amount The amount being exported as a {@link https://github.com/indutny/bn.js/|BN} * @param destinationChain The chainid for where the assets will be sent. * @param toAddresses The addresses to send the funds * @param fromAddresses The addresses being used to send the funds from the UTXOs provided * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting outputs * @param threshold Optional. The number of signatures required to spend the funds in the resultant UTXO * @param assetID Optional. The assetID of the asset to send. Defaults to LUX assetID. * Regardless of the asset which you"re exporting, all fees are paid in LUX. * * @returns An unsigned transaction ([[UnsignedTx]]) which contains an [[ExportTx]]. */ buildExportTx = async ( utxoset: UTXOSet, amount: BN, destinationChain: Buffer | string, toAddresses: string[], fromAddresses: string[], changeAddresses: string[] = undefined, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0), threshold: number = 1, assetID: string = undefined ): Promise => { const prefixes: object = {} toAddresses.map((a: string): void => { prefixes[a.split("-")[0]] = true }) if (Object.keys(prefixes).length !== 1) { throw new AddressError( "Error - XVMAPI.buildExportTx: To addresses must have the same chainID prefix." ) } if (typeof destinationChain === "undefined") { throw new ChainIdError( "Error - XVMAPI.buildExportTx: Destination ChainID is undefined." ) } else if (typeof destinationChain === "string") { destinationChain = bintools.cb58Decode(destinationChain) // } else if (!(destinationChain instanceof Buffer)) { throw new ChainIdError( "Error - XVMAPI.buildExportTx: Invalid destinationChain type: " + typeof destinationChain ) } if (destinationChain.length !== 32) { throw new ChainIdError( "Error - XVMAPI.buildExportTx: Destination ChainID must be 32 bytes in length." ) } const to: Buffer[] = [] toAddresses.map((a: string): void => { to.push(bintools.stringToAddress(a)) }) const caller: string = "buildExportTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } const luxAssetID: Buffer = await this.getLUXAssetID() if (typeof assetID === "undefined") { assetID = bintools.cb58Encode(luxAssetID) } const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const assetIDBuf: Buffer = bintools.cb58Decode(assetID) const fee: BN = this.getTxFee() const builtUnsignedTx: UnsignedTx = utxoset.buildExportTx( networkID, blockchainID, amount, assetIDBuf, to, from, change, destinationChain, fee, luxAssetID, memo, asOf, locktime, threshold ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildExportTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Creates an unsigned transaction. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param initialState The [[InitialStates]] that represent the intial state of a created asset * @param name String for the descriptive name of the asset * @param symbol String for the ticker symbol of the asset * @param denomination Number for the denomination which is 10^D. D must be >= 0 and <= 32. Ex: $1 LUX = 10^9 $nLUX * @param mintOutputs Optional. Array of [[SECPMintOutput]]s to be included in the transaction. These outputs can be spent to mint more tokens. * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[CreateAssetTx]]. * */ buildCreateAssetTx = async ( utxoset: UTXOSet, fromAddresses: string[], changeAddresses: string[], initialStates: InitialStates, name: string, symbol: string, denomination: number, mintOutputs: SECPMintOutput[] = undefined, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow() ): Promise => { const caller: string = "buildCreateAssetTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } if (symbol.length > XVMConstants.SYMBOLMAXLEN) { throw new SymbolError( "Error - XVMAPI.buildCreateAssetTx: Symbols may not exceed length of " + XVMConstants.SYMBOLMAXLEN ) } if (name.length > XVMConstants.ASSETNAMELEN) { throw new NameError( "Error - XVMAPI.buildCreateAssetTx: Names may not exceed length of " + XVMConstants.ASSETNAMELEN ) } const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const luxAssetID: Buffer = await this.getLUXAssetID() const fee: BN = this.getDefaultCreationTxFee() const builtUnsignedTx: UnsignedTx = utxoset.buildCreateAssetTx( networkID, blockchainID, from, change, initialStates, name, symbol, denomination, mintOutputs, fee, luxAssetID, memo, asOf ) if (!(await this.checkGooseEgg(builtUnsignedTx, fee))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildCreateAssetTx:Failed Goose Egg Check" ) } return builtUnsignedTx } buildSECPMintTx = async ( utxoset: UTXOSet, mintOwner: SECPMintOutput, transferOwner: SECPTransferOutput, fromAddresses: string[], changeAddresses: string[], mintUTXOID: string, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow() ): Promise => { const caller: string = "buildSECPMintTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const luxAssetID: Buffer = await this.getLUXAssetID() const fee: BN = this.getMintTxFee() const builtUnsignedTx: UnsignedTx = utxoset.buildSECPMintTx( networkID, blockchainID, mintOwner, transferOwner, from, change, mintUTXOID, fee, luxAssetID, memo, asOf ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildSECPMintTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Creates an unsigned transaction. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param fromAddresses The addresses being used to send the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param minterSets is a list where each element specifies that threshold of the addresses in minters may together mint more of the asset by signing a minting transaction * @param name String for the descriptive name of the asset * @param symbol String for the ticker symbol of the asset * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * @param locktime Optional. The locktime field created in the resulting mint output * * ```js * Example minterSets: * [ * { * "minters":[ * "X-lux1ghstjukrtw8935lryqtnh643xe9a94u3tc75c7" * ], * "threshold": 1 * }, * { * "minters": [ * "X-lux1yell3e4nln0m39cfpdhgqprsd87jkh4qnakklx", * "X-lux1k4nr26c80jaquzm9369j5a4shmwcjn0vmemcjz", * "X-lux1ztkzsrjnkn0cek5ryvhqswdtcg23nhge3nnr5e" * ], * "threshold": 2 * } * ] * ``` * * @returns An unsigned transaction ([[UnsignedTx]]) which contains a [[CreateAssetTx]]. * */ buildCreateNFTAssetTx = async ( utxoset: UTXOSet, fromAddresses: string[], changeAddresses: string[], minterSets: MinterSet[], name: string, symbol: string, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow(), locktime: BN = new BN(0) ): Promise => { const caller: string = "buildCreateNFTAssetTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } if (name.length > XVMConstants.ASSETNAMELEN) { /* istanbul ignore next */ throw new NameError( "Error - XVMAPI.buildCreateNFTAssetTx: Names may not exceed length of " + XVMConstants.ASSETNAMELEN ) } if (symbol.length > XVMConstants.SYMBOLMAXLEN) { /* istanbul ignore next */ throw new SymbolError( "Error - XVMAPI.buildCreateNFTAssetTx: Symbols may not exceed length of " + XVMConstants.SYMBOLMAXLEN ) } const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const creationTxFee: BN = this.getCreationTxFee() const luxAssetID: Buffer = await this.getLUXAssetID() const builtUnsignedTx: UnsignedTx = utxoset.buildCreateNFTAssetTx( networkID, blockchainID, from, change, minterSets, name, symbol, creationTxFee, luxAssetID, memo, asOf, locktime ) if (!(await this.checkGooseEgg(builtUnsignedTx, creationTxFee))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildCreateNFTAssetTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Creates an unsigned transaction. For more granular control, you may create your own * [[UnsignedTx]] manually (with their corresponding [[TransferableInput]]s, [[TransferableOutput]]s, and [[TransferOperation]]s). * * @param utxoset A set of UTXOs that the transaction is built on * @param owners Either a single or an array of [[OutputOwners]] to send the nft output * @param fromAddresses The addresses being used to send the NFT from the utxoID provided * @param changeAddresses The addresses that can spend the change remaining from the spent UTXOs * @param utxoid A base58 utxoID or an array of base58 utxoIDs for the nft mint output this transaction is sending * @param groupID Optional. The group this NFT is issued to. * @param payload Optional. Data for NFT Payload as either a [[PayloadBase]] or a {@link https://github.com/feross/buffer|Buffer} * @param memo Optional CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} * * @returns An unsigned transaction ([[UnsignedTx]]) which contains an [[OperationTx]]. * */ buildCreateNFTMintTx = async ( utxoset: UTXOSet, owners: OutputOwners[] | OutputOwners, fromAddresses: string[], changeAddresses: string[], utxoid: string | string[], groupID: number = 0, payload: PayloadBase | Buffer = undefined, memo: PayloadBase | Buffer = undefined, asOf: BN = UnixNow() ): Promise => { const caller: string = "buildCreateNFTMintTx" const from: Buffer[] = this._cleanAddressArray(fromAddresses, caller).map( (a: string): Buffer => bintools.stringToAddress(a) ) const change: Buffer[] = this._cleanAddressArray( changeAddresses, caller ).map((a: string): Buffer => bintools.stringToAddress(a)) if (memo instanceof PayloadBase) { memo = memo.getPayload() } if (payload instanceof PayloadBase) { payload = payload.getPayload() } if (typeof utxoid === "string") { utxoid = [utxoid] } const luxAssetID: Buffer = await this.getLUXAssetID() if (owners instanceof OutputOwners) { owners = [owners] } const networkID: number = this.core.getNetworkID() const blockchainID: Buffer = bintools.cb58Decode(this.blockchainID) const txFee: BN = this.getTxFee() const builtUnsignedTx: UnsignedTx = utxoset.buildCreateNFTMintTx( networkID, blockchainID, owners, from, change, utxoid, groupID, payload, txFee, luxAssetID, memo, asOf ) if (!(await this.checkGooseEgg(builtUnsignedTx))) { /* istanbul ignore next */ throw new GooseEggCheckError( "Error - XVMAPI.buildCreateNFTMintTx:Failed Goose Egg Check" ) } return builtUnsignedTx } /** * Helper function which takes an unsigned transaction and signs it, returning the resulting [[Tx]]. * * @param utx The unsigned transaction of type [[UnsignedTx]] * * @returns A signed transaction of type [[Tx]] */ signTx = (utx: UnsignedTx): Tx => utx.sign(this.keychain) /** * Calls the node's issueTx method from the API and returns the resulting transaction ID as a string. * * @param tx A string, {@link https://github.com/feross/buffer|Buffer}, or [[Tx]] representing a transaction * * @returns A Promise string representing the transaction ID of the posted transaction. */ issueTx = async (tx: string | Buffer | Tx): Promise => { let Transaction = "" if (typeof tx === "string") { Transaction = tx } else if (tx instanceof Buffer) { const txobj: Tx = new Tx() txobj.fromBuffer(tx) Transaction = txobj.toStringHex() } else if (tx instanceof Tx) { Transaction = tx.toStringHex() } else { /* istanbul ignore next */ throw new TransactionError( "Error - XVMAPI.issueTx: provided tx is not expected type of string, Buffer, or Tx" ) } const params: IssueTxParams = { tx: Transaction.toString(), encoding: "hex" } const response: RequestResponseData = await this.callMethod( "xvm.issueTx", params ) return response.data.result.txID } /** * Calls the node's getAddressTxs method from the API and returns transactions corresponding to the provided address and assetID * * @param address The address for which we're fetching related transactions. * @param cursor Page number or offset. * @param pageSize Number of items to return per page. Optional. Defaults to 1024. If [pageSize] == 0 or [pageSize] > [maxPageSize], then it fetches at max [maxPageSize] transactions * @param assetID Only return transactions that changed the balance of this asset. Must be an ID or an alias for an asset. * * @returns A promise object representing the array of transaction IDs and page offset */ getAddressTxs = async ( address: string, cursor: number, pageSize: number | undefined, assetID: string | Buffer ): Promise => { let asset: string let pageSizeNum: number if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } if (typeof pageSize !== "number") { pageSizeNum = 0 } else { pageSizeNum = pageSize } const params: GetAddressTxsParams = { address, cursor, pageSize: pageSizeNum, assetID: asset } const response: RequestResponseData = await this.callMethod( "xvm.getAddressTxs", params ) return response.data.result } /** * Sends an amount of assetID to the specified address from a list of owned of addresses. * * @param username The user that owns the private keys associated with the `from` addresses * @param password The password unlocking the user * @param assetID The assetID of the asset to send * @param amount The amount of the asset to be sent * @param to The address of the recipient * @param from Optional. An array of addresses managed by the node's keystore for this blockchain which will fund this transaction * @param changeAddr Optional. An address to send the change * @param memo Optional. CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * * @returns Promise for the string representing the transaction's ID. */ send = async ( username: string, password: string, assetID: string | Buffer, amount: number | BN, to: string, from: string[] | Buffer[] = undefined, changeAddr: string = undefined, memo: string | Buffer = undefined ): Promise => { let asset: string let amnt: BN if (typeof this.parseAddress(to) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.send: Invalid address format") } if (typeof assetID !== "string") { asset = bintools.cb58Encode(assetID) } else { asset = assetID } if (typeof amount === "number") { amnt = new BN(amount) } else { amnt = amount } const params: SendParams = { username: username, password: password, assetID: asset, amount: amnt.toString(10), to: to } const caller: string = "send" from = this._cleanAddressArray(from, caller) if (typeof from !== "undefined") { params["from"] = from } if (typeof changeAddr !== "undefined") { if (typeof this.parseAddress(changeAddr) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.send: Invalid address format") } params["changeAddr"] = changeAddr } if (typeof memo !== "undefined") { if (typeof memo !== "string") { params["memo"] = bintools.cb58Encode(memo) } else { params["memo"] = memo } } const response: RequestResponseData = await this.callMethod( "xvm.send", params ) return response.data.result } /** * Sends an amount of assetID to an array of specified addresses from a list of owned of addresses. * * @param username The user that owns the private keys associated with the `from` addresses * @param password The password unlocking the user * @param sendOutputs The array of SendOutputs. A SendOutput is an object literal which contains an assetID, amount, and to. * @param from Optional. An array of addresses managed by the node's keystore for this blockchain which will fund this transaction * @param changeAddr Optional. An address to send the change * @param memo Optional. CB58 Buffer or String which contains arbitrary bytes, up to 256 bytes * * @returns Promise for the string representing the transaction"s ID. */ sendMultiple = async ( username: string, password: string, sendOutputs: { assetID: string | Buffer amount: number | BN to: string }[], from: string[] | Buffer[] = undefined, changeAddr: string = undefined, memo: string | Buffer = undefined ): Promise => { let asset: string let amnt: BN const sOutputs: SOutputsParams[] = [] sendOutputs.forEach( (output: { assetID: string | Buffer amount: number | BN to: string }) => { if (typeof this.parseAddress(output.to) === "undefined") { /* istanbul ignore next */ throw new AddressError( "Error - XVMAPI.sendMultiple: Invalid address format" ) } if (typeof output.assetID !== "string") { asset = bintools.cb58Encode(output.assetID) } else { asset = output.assetID } if (typeof output.amount === "number") { amnt = new BN(output.amount) } else { amnt = output.amount } sOutputs.push({ to: output.to, assetID: asset, amount: amnt.toString(10) }) } ) const params: SendMultipleParams = { username: username, password: password, outputs: sOutputs } const caller: string = "send" from = this._cleanAddressArray(from, caller) if (typeof from !== "undefined") { params.from = from } if (typeof changeAddr !== "undefined") { if (typeof this.parseAddress(changeAddr) === "undefined") { /* istanbul ignore next */ throw new AddressError("Error - XVMAPI.send: Invalid address format") } params.changeAddr = changeAddr } if (typeof memo !== "undefined") { if (typeof memo !== "string") { params.memo = bintools.cb58Encode(memo) } else { params.memo = memo } } const response: RequestResponseData = await this.callMethod( "xvm.sendMultiple", params ) return response.data.result } /** * Given a JSON representation of this Virtual Machine’s genesis state, create the byte representation of that state. * * @param genesisData The blockchain's genesis data object * * @returns Promise of a string of bytes */ buildGenesis = async (genesisData: object): Promise => { const params: BuildGenesisParams = { genesisData } const response: RequestResponseData = await this.callMethod( "xvm.buildGenesis", params ) return response.data.result.bytes } /** * @ignore */ protected _cleanAddressArray( addresses: string[] | Buffer[], caller: string ): string[] { const addrs: string[] = [] const chainID: string = this.getBlockchainAlias() ? this.getBlockchainAlias() : this.getBlockchainID() if (addresses && addresses.length > 0) { for (let i: number = 0; i < addresses.length; i++) { if (typeof addresses[`${i}`] === "string") { if ( typeof this.parseAddress(addresses[`${i}`] as string) === "undefined" ) { /* istanbul ignore next */ throw new AddressError( "Error - XVMAPI.${caller}: Invalid address format" ) } addrs.push(addresses[`${i}`] as string) } else { const type: SerializedType = "bech32" addrs.push( serialization.bufferToType( addresses[`${i}`] as Buffer, type, this.core.getHRP(), chainID ) ) } } } return addrs } /** * This class should not be instantiated directly. Instead use the [[Lux.addAP`${I}`]] method. * * @param core A reference to the Lux class * @param baseURL Defaults to the string "/ext/bc/X" as the path to blockchain's baseURL * @param blockchainID The Blockchain"s ID. Defaults to an empty string: "" */ constructor( core: LuxCore, baseURL: string = "/ext/bc/X", blockchainID: string = "" ) { super(core, baseURL) this.blockchainID = blockchainID const netID: number = core.getNetworkID() if ( netID in Defaults.network && blockchainID in Defaults.network[`${netID}`] ) { const alias: string = Defaults.network[`${netID}`][`${blockchainID}`]["alias"] this.keychain = new KeyChain(this.core.getHRP(), alias) } else { this.keychain = new KeyChain(this.core.getHRP(), blockchainID) } } }