import { RPCSendableMessage , toJsonRpcRequest , isValidResponse } from './utils/jsonrpc' ; import { InvalidProvider , InvalidResponse } from './utils/errors' ; import { BigNumber } from './utils/BigNumber' ; import { IFuture , future } from 'fp-future' ; import { eth } from './methods/eth' ; import { SHHFilterOptions , Data , Quantity , SHHFilterMessage , SHHPost , LogObject , TransactionReceipt , BlockObject , TransactionObject , TransactionCallOptions , TransactionOptions , Syncing , TxHash , Address , FilterOptions , QueuedTransaction , PendingTransaction , ReplacedTransaction , Transaction , RevertedTransaction , ConfirmedTransaction , TransactionType , BlockIdentifier , TransactionStatus , FinishedTransactionAndReceipt , TransactionAndReceipt } from './Schema' ; import { sleep } from './utils/sleep' ; import { inputTransactionId } from './utils/formatters' ; export let TRANSACTION_FETCH_DELAY = 2 * 1000 ; export function inject ( target , propertyKey ) { const method = eth [ propertyKey ] ; if ( ! method ) { throw new Error ( `Could not find the method/property named ${ propertyKey . toString ( ) }` ) ; } Object . defineProperty ( target , propertyKey , { value : function ( this ) { return method . execute ( this , ... arguments ) ; } } ) ; } public export class RequestManager { requests = new Map < number , IFuture < any > > ( ) ; /** Returns the current client version. */ @ inject web3_clientVersion ! : ( ) => Promise < string > ; /** Returns Keccak-256 (not the standardized SHA3-256) of the given data. */ @ inject web3_sha3 ! : ( data : Data ) => Promise < Data > ; /** Returns the current network id. */ @ inject net_version ! : ( ) => Promise < string > ; /** Returns number of peers currently connected to the client. */ @ inject net_peerCount ! : ( ) => Promise < Quantity > ; /** Returns true if client is actively listening for network connections. */ @ inject net_listening ! : ( ) => Promise < boolean > ; /** Returns the current ethereum protocol version. */ @ inject eth_protocolVersion ! : ( ) => Promise < number > ; /** Returns an object with data about the sync status or false. */ @ inject eth_syncing ! : ( ) => Promise < false | Syncing > ; /** Returns the client coinbase address. */ @ inject eth_coinbase ! : ( ) => Promise < Address > ; /** Returns true if client is actively mining new blocks. */ @ inject eth_mining ! : ( ) => Promise < boolean > ; /** Returns the number of hashes per second that the node is mining with. */ @ inject eth_hashrate ! : ( ) => Promise < Quantity > ; /** Returns the current price per gas in wei. */ @ inject eth_gasPrice ! : ( ) => Promise < BigNumber > ; /** Returns a list of addresses owned by client. */ @ inject eth_accounts ! : ( ) => Promise < Address [ ] > ; /** Returns the number of most recent block. */ @ inject eth_blockNumber ! : ( ) => Promise < Quantity > ; /** Returns the balance of the account of given address. */ @ inject eth_getBalance ! : ( address : Address , block : BlockIdentifier ) => Promise < BigNumber > ; /** Returns the value from a storage position at a given address. */ @ inject eth_getStorageAt ! : ( address : Address , position : Quantity , block : BlockIdentifier ) => Promise < Data > ; /** Returns the number of transactions sent from an address. */ @ inject eth_getTransactionCount ! : ( address : Address , block : BlockIdentifier ) => Promise < number > ; /** Returns the number of transactions in a block from a block matching the given block hash. */ @ inject eth_getBlockTransactionCountByHash ! : ( blockHash : TxHash ) => Promise < number > ; /** Returns the number of transactions in a block matching the given block number. */ @ inject eth_getBlockTransactionCountByNumber ! : ( block : BlockIdentifier ) => Promise < number > ; /** Returns the number of uncles in a block from a block matching the given block hash. */ @ inject eth_getUncleCountByBlockHash ! : ( blockHash : TxHash ) => Promise < number > ; /** Returns the number of uncles in a block from a block matching the given block number. */ @ inject eth_getUncleCountByBlockNumber ! : ( block : BlockIdentifier ) => Promise < number > ; /** Returns code at a given address. */ @ inject eth_getCode ! : ( address : Address , block : BlockIdentifier ) => Promise < Data > ; deprecated @ inject eth_sign ! : ( address : Address , message : Data ) => Promise < Data > ; /** Creates new message call transaction or a contract creation, if the data field contains code. */ @ inject eth_sendTransaction ! : ( options : TransactionOptions ) => Promise < TxHash > ; /** Creates new message call transaction or a contract creation for signed transactions. */ @ inject eth_sendRawTransaction ! : ( rawTransaction : Data ) => Promise < TxHash > ; /** Executes a new message call immediately without creating a transaction on the block chain. */ @ inject eth_call ! : ( options : TransactionCallOptions , block : BlockIdentifier ) => Promise < Data > ; /**
     * Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
     * The transaction will not be added to the blockchain. Note that the estimate may be significantly more
     * than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics
     * and node performance.
     */ @ inject eth_estimateGas ! : ( data : Partial < TransactionCallOptions > & Partial < TransactionOptions > ) => Promise < Quantity > ; /** Returns information about a block by hash. */ @ inject eth_getBlockByHash ! : ( blockHash : TxHash , fullTransactionObjects : boolean ) => Promise < BlockObject > ; /** Returns information about a block by block number. */ @ inject eth_getBlockByNumber ! : ( block : BlockIdentifier , fullTransactionObjects : boolean ) => Promise < BlockObject > ; /** Returns the information about a transaction requested by transaction hash. */ @ inject eth_getTransactionByHash ! : ( hash : TxHash ) => Promise < TransactionObject > ; /** Returns information about a transaction by block hash and transaction index position. */ @ inject eth_getTransactionByBlockHashAndIndex ! : ( blockHash : TxHash , txIndex : Quantity ) => Promise < TransactionObject > ; /** Returns information about a transaction by block number and transaction index position. */ @ inject eth_getTransactionByBlockNumberAndIndex ! : ( block : BlockIdentifier , txIndex : Quantity ) => Promise < TransactionObject > ; /**
     * Returns the receipt of a transaction by transaction hash.
     * Note That the receipt is not available for pending transactions.
     */ @ inject eth_getTransactionReceipt ! : ( hash : TxHash ) => Promise < TransactionReceipt > ; /** Returns information about a uncle of a block by hash and uncle index position. */ @ inject eth_getUncleByBlockHashAndIndex ! : ( blockHash : TxHash , index : Quantity ) => Promise < BlockObject > ; /** Returns information about a uncle of a block by number and uncle index position. */ @ inject eth_getUncleByBlockNumberAndIndex ! : ( block : BlockIdentifier , index : Quantity ) => Promise < BlockObject > ; /**
     * Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state
     * has changed, call eth_getFilterChanges.
     *
     * A note on specifying topic filters:
     * Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic
     * filters:
     *
     * [] "anything"
     * [A] "A in first position (and anything after)"
     * [null, B] "anything in first position AND B in second position (and anything after)"
     * [A, B] "A in first position AND B in second position (and anything after)"
     * [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)"
     */ @ inject eth_newFilter ! : ( options : FilterOptions ) => Promise < Data > ; /**
     * Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call
     * eth_getFilterChanges.
     */ @ inject eth_newBlockFilter ! : ( ) => Promise < Data > ; /**
     * Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed,
     * call eth_getFilterChanges.
     */ @ inject eth_newPendingTransactionFilter ! : ( ) => Promise < Data > ; /**
     * Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additonally Filters
     * timeout when they aren't requested with eth_getFilterChanges for a period of time.
     */ @ inject eth_uninstallFilter ! : ( filterId : Data ) => Promise < boolean > ; /**
     * Polling method for a filter, which returns an array of logs which occurred since last poll.
     */ @ inject eth_getFilterChanges ! : ( filterId : Data ) => Promise < Array < TxHash > | Array < LogObject > > ; /** Returns an array of all logs matching filter with given id. */ @ inject eth_getFilterLogs ! : ( filterId : Data ) => Promise < Array < TxHash > | Array < LogObject > > ; /** Returns an array of all logs matching a given filter object. */ @ inject eth_getLogs ! : ( options : FilterOptions ) => Promise < Array < TxHash > | Array < LogObject > > ; returns alpha @ inject eth_getWork ! : ( blockHeaderHash : Data ) => Promise < Array < TxHash > > ; /** Used for submitting a proof-of-work solution. */ @ inject eth_submitWork ! : ( data : Data , powHash : TxHash , digest : TxHash ) => Promise < boolean > ; /** Used for submitting mining hashrate. */ @ inject eth_submitHashrate ! : ( hashRate : Data , id : Data ) => Promise < boolean > ; /** Sends a whisper message. */ @ inject shh_post ! : ( data : SHHPost ) => Promise < boolean > ; /** Returns the current whisper protocol version. */ @ inject shh_version ! : ( ) => Promise < string > ; /** Creates new whisper identity in the client. */ @ inject shh_newIdentity ! : ( ) => Promise < Data > ; /** Checks if the client hold the private keys for a given identity. */ @ inject shh_hasIdentity ! : ( identity : Data ) => Promise < boolean > ; @ inject shh_newGroup ! : ( ) => Promise < Data > ; @ inject shh_addToGroup ! : ( group : Data ) => Promise < boolean > ; /** Creates filter to notify, when client receives whisper message matching the filter options. */ @ inject shh_newFilter ! : ( options : SHHFilterOptions ) => Promise < Data > ; /**
     * Uninstalls a filter with given id. Should always be called when watch is no longer needed.
     * Additonally Filters timeout when they aren't requested with shh_getFilterChanges for a period of time.
     */ @ inject shh_uninstallFilter ! : ( filterId : Data ) => Promise < boolean > ; /**
     * Polling method for whisper filters. Returns new messages since the last call of this method.
     *
     * Note calling the shh_getMessages method, will reset the buffer for this method, so that you won't receive duplicate
     * messages.
     */ @ inject shh_getFilterChanges ! : ( filterId : Data ) => Promise < Array < SHHFilterMessage > > ; /** Get all messages matching a filter. Unlike shh_getFilterChanges this returns all messages. */ @ inject shh_getMessages ! : ( filterId : Data ) => Promise < Array < SHHFilterMessage > > ; /**
     * Decrypts the key with the given address from the key store.
     *
     * Both passphrase and unlock duration are optional when using the JavaScript console. If the passphrase is not
     * supplied as an argument, the console will prompt for the passphrase interactively.
     *
     * The unencrypted key will be held in memory until the unlock duration expires. If the unlock duration defaults to
     * 300 seconds. An explicit duration of zero seconds unlocks the key until geth exits.
     *
     * The account can be used with eth_sign and eth_sendTransaction while it is unlocked.
     */ @ inject personal_unlockAccount ! : ( account : Address , passPhrase ? : Data , seconds ? : Quantity ) => Promise < boolean > ; /**
     * Generates a new private key and stores it in the key store directory. The key file is encrypted with the given
     * passphrase. Returns the address of the new account.
     *
     * At the geth console, newAccount will prompt for a passphrase when it is not supplied as the argument.
     */ @ inject personal_newAccount ! : ( passPhrase : Data ) => Promise < Address > ; /** Returns all the Ethereum account addresses of all keys in the key store. */ @ inject personal_listAccounts ! : ( ) => Promise < Array < Address > > ; /** Removes the private key with given address from memory. The account can no longer be used to send transactions. */ @ inject personal_lockAccount ! : ( address : Address ) => Promise < boolean > ; /**
     * Imports the given unencrypted private key (hex string) into the key store, encrypting it with the passphrase.
     * Returns the address of the new account.
     */ @ inject personal_importRawKey ! : ( keydata : Data , passPhrase : Data ) => Promise < Address > ; /**
     * Imports the given unencrypted private key (hex string) into the key store, encrypting it with the passphrase.
     * Returns the address of the new account.
     */ @ inject personal_sendTransaction ! : ( txObject : TransactionOptions , passPhrase : Data ) => Promise < TxHash > ; /**
     * The sign method calculates an Ethereum specific signature with:
     *   sign(keccack256("Ethereum Signed Message:" + len(message) + message))).
     *
     * By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature.
     * This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to
     * impersonate the victim.
     *
     * See ecRecover to verify the signature.
     */ @ inject personal_sign ! : ( data : Data , signerAddress : Address , passPhrase : Data ) => Promise < Data > ; /**
     * ecRecover returns the address associated with the private key that was used to calculate the signature in
     * personal_sign.
     */ @ inject personal_ecRecover ! : ( message : Data , signature : Data ) => Promise < Address > ; constructor ( public provider ) { } param data async sendAsync ( data ) { const provider = await this . provider ; if ( ! provider ) { throw InvalidProvider ( ) ; } let payload = toJsonRpcRequest ( data . method , data . params ) ; const defer = future ( ) ; this . requests . set ( payload . id , defer ) ; provider . sendAsync ( payload , ( err , result ) => { this . requests . delete ( payload . id ) ; if ( err ) { defer . reject ( err ) ; return ; } if ( ! isValidResponse ( result ) ) { defer . reject ( InvalidResponse ( result ) ) ; return ; } defer . resolve ( result . result ) ; } ) ; return defer ; } param p setProvider ( p ) { this . provider = p ; } param txId async getConfirmedTransaction ( txId ) { const tx = await this . waitForCompletion ( txId ) ; if ( this . isFailure ( tx ) ) { throw new Error ( `Transaction "${ txId }" failed` ) ; } return tx ; } param txId param retriesOnEmpty async waitForCompletion ( txId , retriesOnEmpty ? ) { const txIdString = inputTransactionId ( txId ) ; const isDropped = await this . isTxDropped ( txIdString , retriesOnEmpty ) ; if ( isDropped ) { const tx = await this . getTransactionAndReceipt ( txIdString ) ; return { ... tx , status : TransactionStatus . failed } ; } while ( true ) { const tx = await this . getTransactionAndReceipt ( txIdString ) ; if ( ! this . isPending ( tx ) && tx . receipt ) { return { ... tx , status : this . isFailure ( tx ) ? TransactionStatus . failed : TransactionStatus . confirmed } ; } await sleep ( TRANSACTION_FETCH_DELAY ) ; } } param txId async getTransaction ( txId ) { let currentNonce = null ; let status ; const hash = inputTransactionId ( txId ) ; try { const accounts = await this . eth_accounts ( ) ; const account = accounts [ 0 ] ; if ( account ) { currentNonce = await this . eth_getTransactionCount ( account , 'latest' ) ; } } catch ( error ) { currentNonce = null ; } try { status = await this . eth_getTransactionByHash ( hash ) ; if ( status == null ) { return null ; } } catch ( e ) { return null ; } if ( status . blockNumber == null ) { if ( currentNonce != null ) { if ( status . nonce < currentNonce ) { const tx = { hash , type : TransactionType . replaced , nonce : status . nonce } ; return tx ; } if ( status . nonce > currentNonce ) { const tx = { hash , type : TransactionType . queued , nonce : status . nonce } ; return tx ; } } const tx = { type : TransactionType . pending , ... status } ; return tx ; } let receipt ; try { receipt = await this . eth_getTransactionReceipt ( hash ) ; if ( receipt == null || receipt . status === 0x0 ) { const tx = { type : TransactionType . reverted , ... status } ; return tx ; } } catch ( e ) { return null ; } const tx = { type : TransactionType . confirmed , ... status , receipt } ; return tx ; } param txId param retryAttemps async isTxDropped ( txId , _retryAttemps = 15 ) { let retryAttemps = _retryAttemps ; while ( retryAttemps > 0 ) { const tx = await this . getTransactionAndReceipt ( txId ) ; if ( tx !== null ) { return false ; } retryAttemps -= 1 ; await sleep ( TRANSACTION_FETCH_DELAY ) ; } return true ; } param txId async getTransactionAndReceipt ( txId ) { txId = inputTransactionId ( txId ) ; const [ tx , receipt ] = await Promise . all ( [ this . eth_getTransactionByHash ( txId ) , this . eth_getTransactionReceipt ( txId ) ] ) ; return { ... tx , receipt } ; } param tx isPending ( tx ) { return tx && tx . blockNumber === null ; } param tx isFailure ( tx ) { return tx && ( ! tx . receipt || tx . receipt . status === 0 ) ; } }