import { to } from 'await-to-js' import { DefaultAsset } from './Constant' import { Setting } from './Setting' import { ContractConfig, ABIMethod, Contract, CallParams } from './contract/Contract' import { ChainRpcProvider } from './providers/ChainRpcProvider' import { AsiLinkProvider } from './providers/AsiLinkProvider' /** * Parameters to execute a method in contract. */ export interface ExecuteParams { /** * Contract address. */ address: string /** * Name of method to execute. */ method: string /** * Arguments of method. */ args ? : any[] /** * Address of caller to execute method. The address must be setup in AsiLink. */ caller ? : string /** * Amount of asset transferred to contract when executing a payable method. */ assetValue ? : number /** * Type of asset transferred to contract when executing a payable method. */ assetType ? : string /** * Amount of transaction fee, calculated in Satoshi (eg:2000000). If not set, it defaults to minimal transaction fee (21000 satoshis). */ feeValue ? : number /** * Asset type of transaction fee. If not set, it defaults to ASIM. */ feeType ? : string } /** * Parameters to vote on a method in contract. */ export interface VoteParams { /** * Contract address. */ address: string /** * Name of method to vote. */ method: string /** * Address of caller to vote. The address must be setup in AsiLink. */ caller ? : string /** * Arguments of vote method. */ args ? : any[] /** * Vote value. 0 means vote all values relate to a given asset type. If a specific value is set, SDK will compose UTXO inputs to get a vote value no less than the set value. */ voteValue ? : number /** * Type of asset used to vote. If not set, it defaults to ASIM. */ assetType ? : string /** * Amount of transaction fee, calculated in Satoshi (eg:2000000). If not set, it defaults to minimal transaction fee (21000 satoshis). */ feeValue ? : number /** * Asset type of transaction fee. If not set, it defaults to ASIM. */ feeType ? : string } /** * Parameters to call a readonly function in contract. */ export interface ReadParams { /** * Contract address. */ address: string /** * Name of the readonly method to call. */ method: string /** * Arguments of readonly method. */ args: any[] /** * Address of caller to call the readonly method. The address must be setup in AsiLink. */ caller ? : string } /** * Configurations of Contracts object */ export interface ContractsConfig { /** * ChainRPC provider */ chainRpcProvider ? : ChainRpcProvider /** * AsiLink provider */ asiLinkProvider ? : AsiLinkProvider } /** * Contracts object provides high level API for developers to interact with a contract on Asimov blockchain * * - execute a public/external method * - call a readonly method (view/pure method) * - vote on a public/external method */ export class Contracts { public setting: Setting = Setting.getInstance() private _chainRpcProvider: ChainRpcProvider private _asiLinkProvider: AsiLinkProvider private _privateKey: string = this.setting.privateKey /** * Constructor of Contracts * @param config configurations including ChainRPC provider and AsiLink provider */ constructor(config: ContractsConfig = {}) { this.chainRpcProvider = this.setting.chainRpcProvider this.asiLinkProvider = this.setting.asiLinkProvider if (config.chainRpcProvider) { this.chainRpcProvider = config.chainRpcProvider } if (config.asiLinkProvider) { this.asiLinkProvider = config.asiLinkProvider } } /** * getter of ChainRPC provider */ public get chainRpcProvider(): ChainRpcProvider { return this._chainRpcProvider } /** * setter of ChainRPC provider */ public set chainRpcProvider(rpc: ChainRpcProvider) { this._chainRpcProvider = rpc } /** * getter of AsiLink provider */ public get asiLinkProvider(): AsiLinkProvider { return this._asiLinkProvider } /** * setter of AsiLink provider. Normally AsiLink provider is set when developing Web DApps. */ public set asiLinkProvider(asilink: AsiLinkProvider) { this._asiLinkProvider = asilink } /** * getter of private key. */ public get privateKey(): string { return this._privateKey } /** * setter of private key. Normally private key is set when developing automation scripts. */ public set privateKey(pk: string) { this._privateKey = pk } private async getContract(address: string) { let [err1, templateInfo] = await to(this.chainRpcProvider.getContractTemplate(address)) if (err1!) { throw err1 } let { template_type, template_name } = templateInfo let [err2, template] = await to(this.chainRpcProvider.getContractTemplateInfoByName({ category: template_type, templateName: template_name })) if (err2!) { throw err2 } let abi: ABIMethod[] = JSON.parse(template.abi) try { abi = JSON.parse(template.abi) } catch (e) { throw e } let contractConfig: ContractConfig = { contractAddress: address, abi: abi, asiLinkProvider: this.asiLinkProvider, chainRpcProvider: this.chainRpcProvider } let contract = new Contract(contractConfig) return contract } /** * Execute a contract method in contract * @param params Parameters to execute contract method * @return Transaction id */ public async execute(params: ExecuteParams): Promise < any > { let { address, method, args = [], assetValue = 0, assetType = DefaultAsset, feeValue, feeType, caller } = params if (!address) { throw new Error('Contract address is not provided') } if (!method) { throw new Error('Contract mmethod is not provided') } let fee = this.setting.fee if (feeValue && feeType) { fee = { amount: feeValue, asset: feeType } } let [err, contract] = await to(this.getContract(address)) if (err) { throw err } let callParams: CallParams = { methodName: method, args: args, amount: assetValue, asset: assetType, privateKey: this.privateKey, caller: caller, fee: fee, contractType: "call" } let [err1, res] = await to(contract.call(callParams)) if (err1) { throw err1 } return res } /** * Vote on a method in contract. * @param params Parameters to vote contract method. * @return Transaction Id. */ public async vote(params: VoteParams): Promise < string > { let { address, method, args = [], voteValue = 0, assetType = DefaultAsset, feeValue, feeType, caller } = params if (!address) { throw new Error('Contract address is not provided') } if (!method) { throw new Error('Contract mmethod is not provided') } let fee = this.setting.fee if (feeValue && feeType) { fee = { amount: feeValue, asset: feeType } } let [err, contract] = await to(this.getContract(address)) if (err) { throw err } let callParams: CallParams = { methodName: method, args: args, amount: 0, voteValue: voteValue, voteId: args[0], asset: assetType, privateKey: this.privateKey, fee: fee, caller: caller, contractType: "vote" } let [err1, res] = await to(contract.call(callParams)) if (err1) { throw err1 } return res } /** * Call a readonly method in contract * @param params Parameters to call a readonly method. * @return Return value of the called method. */ public async read(params: ReadParams): Promise < any > { let { address, method, args = [], caller } = params if (!address) { throw new Error('Contract address is not provided') } if (!method) { throw new Error('Contract mmethod is not provided') } let [err, contract] = await to(this.getContract(address)) if (err) { throw err } if (!contract.isReadOnlyMethod(method, args)) { throw new Error(method + " is not a view or pure method") } let callParams: CallParams = { methodName: method, args: args, privateKey: this.privateKey, caller: caller } let [err1, res] = await to(contract.call(callParams)) if (err1) { throw err1 } return res } }