import { IABIMethod , IETHABI , LogDecoder } from "./ethjs-abi" ; import { EventEmitter } from "eventemitter3" ; import debug from "debug" ; const log = debug ( "qtumjs:contract" ) ; const { logDecoder } = require ( "qtumjs-ethjs-abi" ) as IETHABI ; import { decodeOutputs , encodeInputs , ContractLogDecoder } from "./abi" ; import { IDecodedSolidityEvent , ITransactionLog , IRPCSearchLogsRequest , } from "./index" ; import { IExecutionResult , IRPCCallContractResult , IRPCGetTransactionReceiptBase , IRPCGetTransactionReceiptResult , IRPCGetTransactionResult , IRPCSendToContractResult , IRPCWaitForLogsResult , QtumRPC , IRPCWaitForLogsRequest , ILogEntry , } from "./QtumRPC" ; import { TxReceiptConfirmationHandler , TxReceiptPromise , } from "./TxReceiptPromise" ; import { MethodMap } from "./MethodMap" ; export interface IContractSendTx { method : string ; txid : string ; } /**
 * The callback function invoked for each additional confirmation
 */ export type IContractSendConfirmationHandler = ( tx : IRPCGetTransactionResult , receipt : IContractSendReceipt ) => any ; param n param handler export type IContractSendConfirmFunction = ( n ? : number , handler ? : IContractSendConfirmationHandler ) => Promise < IContractSendReceipt > ; /**
 * Result of contract send.
 */ export interface IContractSendResult extends IRPCGetTransactionResult { /**
     * Name of contract method invoked.
     */ method : string ; /**
     * Wait for transaction confirmations.
     */ confirm : IContractSendConfirmFunction ; } /**
 * The minimal deployment information necessary to interact with a
 * deployed contract.
 */ export interface IContractInfo { /**
     * Contract's ABI definitions, produced by solc.
     */ abi : IABIMethod [ ] ; /**
     * Contract's address
     */ address : string ; /**
     * The owner address of the contract
     */ sender ? : string ; } /**
 * Deployment information stored by solar
 */ export interface IDeployedContractInfo extends IContractInfo { name : string ; deployName : string ; txid : string ; bin : string ; binhash : string ; createdAt : string ; confirmed : boolean ; } /**
 * The result of calling a contract method, with decoded outputs.
 */ export interface IContractCallResult extends IRPCCallContractResult { /**
     * ABI-decoded outputs
     */ outputs : any [ ] ; /**
     * ABI-decoded logs
     */ logs : ( IDecodedSolidityEvent | null ) [ ] ; } /**
 * Options for `send` to a contract method.
 */ export interface IContractSendRequestOptions { /**
     * The amount in QTUM to send. eg 0.1, default: 0
     */ amount ? : number | string ; /**
     * gasLimit, default: 200000, max: 40000000
     */ gasLimit ? : number ; /**
     * Qtum price per gas unit, default: 0.00000001, min:0.00000001
     */ gasPrice ? : number | string ; /**
     * The quantum address that will be used as sender.
     */ senderAddress ? : string ; } /**
 * Options for `call` to a contract method.
 */ export interface IContractCallRequestOptions { /**
     * The quantum address that will be used as sender.
     */ senderAddress ? : string ; gasLimit ? : number ; amount ? : number ; } /**
 * The transaction receipt for a `send` to a contract method, with the event
 * logs decoded.
 */ export interface IContractSendReceipt extends IRPCGetTransactionReceiptBase { /**
     * logs decoded using ABI
     */ logs : IDecodedSolidityEvent [ ] ; /**
     * undecoded logs
     */ rawlogs : ITransactionLog [ ] ; } export interface IContractLog < T > extends ILogEntry { event : T ; } /**
 * A decoded contract event log.
 */ export interface IContractEventLog extends ILogEntry { /**
     * Solidity event, ABI decoded. Null if no ABI definition is found.
     */ event ? : IDecodedSolidityEvent | null ; } /**
 * Query result of a contract's event logs.
 */ export interface IContractEventLogs { /**
     * Event logs, ABI decoded.
     */ entries : IContractEventLog [ ] ; /**
     * Number of event logs returned.
     */ count : number ; /**
     * The block number to start query for new event logs.
     */ nextblock : number ; } export interface IContractInitOptions { /**
     * event logs decoder. It may know how to decode logs not whose types are not
     * defined in this particular contract's `info`. Typically ContractsRepo would
     * pass in a logDecoder that knows about all the event definitions.
     */ logDecoder ? : ContractLogDecoder ; /**
     * If a contract's use case requires numbers more than 53 bits, use bn.js to
     * represent numbers instead of native JavaScript numbers. (default = false)
     */ useBigNumber ? : boolean ; } /**
 * Contract represents a Smart Contract deployed on the blockchain.
 */ export class Contract { /**
     * The contract's address as hex160
     */ public address : string ; private methodMap : MethodMap ; private _logDecoder : ContractLogDecoder ; private _useBigNumber : boolean ; param rpc param info param opts constructor ( private rpc , public info , opts = { } ) { this . methodMap = new MethodMap ( info . abi ) ; this . address = info . address ; this . _logDecoder = opts . logDecoder || new ContractLogDecoder ( this . info . abi ) ; this . _useBigNumber = false ; } public encodeParams ( method , args = [ ] ) { const methodABI = this . methodMap . findMethod ( method , args ) ; if ( ! methodABI ) { throw new Error ( `Unknown method to call: ${ method }` ) ; } return encodeInputs ( methodABI , args ) ; } param method param args public async rawCall ( method , args = [ ] , opts = { } ) { log ( "call" , method , args , opts ) ; const calldata = this . encodeParams ( method , args ) ; return this . rpc . callContract ( { address : this . address , datahex : calldata , senderAddress : opts . senderAddress || this . info . sender , ... opts , } ) ; } param method param args param opts public async call ( method , args = [ ] , opts = { } ) { const r = await this . rawCall ( method , args , opts ) ; const exception = r . executionResult . excepted ; if ( exception !== "None" ) { throw new Error ( `Call exception: ${ exception }` ) ; } const output = r . executionResult . output ; let decodedOutputs = [ ] ; if ( output !== "" ) { const methodABI = this . methodMap . findMethod ( method , args ) ! ; decodedOutputs = decodeOutputs ( methodABI , output ) ; } const decodedLogs = r . transactionReceipt . log . map ( ( rawLog ) => { return this . logDecoder . decode ( rawLog ) ; } ) ; return Object . assign ( r , { outputs : decodedOutputs , logs : decodedLogs , } ) ; } param method param args param opts public async return ( method , args = [ ] , opts = { } ) { const result = await this . call ( method , args , opts ) ; return result . outputs [ 0 ] ; } public async returnNumber ( method , args = [ ] , opts = { } ) { const result = await this . call ( method , args , opts ) ; const val = result . outputs [ 0 ] ; if ( typeof val . toNumber !== "function" ) { throw new Error ( "Cannot convert result to a number" ) ; } return val . toNumber ( ) ; } param method param args param opts public async returnDate ( method , args = [ ] , opts = { } ) { const result = await this . return ( method , args , opts ) ; if ( typeof result !== "number" ) { throw Error ( "Cannot convert return value to Date. Expect return value to be a number." ) ; } return new Date ( result * 1000 ) ; } param targetBase param method param args param opts public async returnCurrency ( targetBase , method , args = [ ] , opts = { } ) { const value = await this . return ( method , args , opts ) ; if ( typeof value !== "number" ) { throw Error ( "Cannot convert return value to currency unit. Expect return value to be a number." ) ; } let base = 0 ; if ( typeof targetBase === "number" ) { base = targetBase ; } else { switch ( targetBase ) { case "qtum" : case "btc" : base = 0 ; break ; case "sat" : case "satoshi" : base = - 8 ; default : throw Error ( `Unknown base currency unit: ${ targetBase }` ) ; } } const satoshi = 1e-8 ; return ( value * satoshi ) / 10 ** base ; } public async returnAs < T > ( converter , method , args = [ ] , opts = { } ) { const value = await this . return ( method , args , opts ) ; return await converter ( value ) ; } param method param args public async rawSend ( method , args , opts = { } ) { const methodABI = this . methodMap . findMethod ( method , args ) ; if ( methodABI == null ) { throw new Error ( `Unknown method to send: ${ method }` ) ; } if ( methodABI . constant ) { throw new Error ( `cannot send to a constant method: ${ method }` ) ; } const calldata = encodeInputs ( methodABI , args ) ; return this . rpc . sendToContract ( { address : this . address , datahex : calldata , senderAddress : opts . senderAddress || this . info . sender , ... opts , } ) ; } param txid param confirm param onConfirm public async confirm ( txid , confirm ? , onConfirm ? ) { const txrp = new TxReceiptPromise ( this . rpc , txid ) ; if ( onConfirm ) { txrp . onConfirm ( ( tx2 , receipt2 ) => { const sendTxReceipt = this . _makeSendTxReceipt ( receipt2 ) ; onConfirm ( tx2 , sendTxReceipt ) ; } ) ; } const receipt = await txrp . confirm ( confirm ) ; return this . _makeSendTxReceipt ( receipt ) ; } param txid returns public async receipt ( txid ) { const receipt = await this . rpc . getTransactionReceipt ( { txid } ) ; if ( ! receipt ) { return null ; } return this . _makeSendTxReceipt ( receipt ) ; } public async send ( method , args = [ ] , opts = { } ) { log ( "send" , method , args , opts ) ; const methodABI = this . methodMap . findMethod ( method , args ) ; if ( methodABI == null ) { throw new Error ( `Unknown method to send: ${ method }` ) ; } if ( methodABI . constant ) { throw new Error ( `cannot send to a constant method: ${ method }` ) ; } const calldata = encodeInputs ( methodABI , args ) ; const sent = await this . rpc . sendToContract ( { datahex : calldata , address : this . address , senderAddress : opts . senderAddress || this . info . sender , ... opts , } ) ; const txid = sent . txid ; const txinfo = await this . rpc . getTransaction ( { txid } ) ; const sendTx = { ... txinfo , method , confirm : ( n ? , handler ? ) => { return this . confirm ( txid , n , handler ) ; } , } ; return sendTx ; } param req public async logs ( req = { } ) { return this . waitLogs ( { fromBlock : 0 , toBlock : "latest" , ... req , } ) ; } param req public async waitLogs ( req = { } ) { const filter = req . filter || { } ; if ( ! filter . addresses ) { filter . addresses = [ this . address ] ; } const result = await this . rpc . waitforlogs ( { ... req , filter , } ) ; const entries = result . entries . map ( ( entry ) => { const parsedLog = this . logDecoder . decode ( entry ) ; return { ... entry , event : parsedLog , } ; } ) ; return { ... result , entries , } ; } /**
     * Subscribe to contract's events, using callback interface.
     */ public onLog ( fn , opts = { } ) { let nextblock = opts . fromBlock || "latest" ; const loop = async ( ) => { while ( true ) { const result = await this . waitLogs ( { ... opts , fromBlock : nextblock , } ) ; for ( const entry of result . entries ) { fn ( entry ) ; } nextblock = result . nextblock ; } } ; loop ( ) ; } /**
     * Subscribe to contract's events, use EventsEmitter interface.
     */ public logEmitter ( opts = { } ) { const emitter = new EventEmitter ( ) ; this . onLog ( ( entry ) => { const key = ( entry . event && entry . event . type ) || "?" ; emitter . emit ( key , entry ) ; } , opts ) ; return emitter ; } private get logDecoder ( ) : ContractLogDecoder { return this . _logDecoder ; } private _makeSendTxReceipt ( receipt ) { const { log : rawlogs , ... receiptNoLog } = receipt ; const logs = rawlogs . map ( ( rawLog ) => this . logDecoder . decode ( rawLog ) ! ) ; return { ... receiptNoLog , logs , rawlogs , } ; } }