import { NodeProvider } from '@liquality/node-provider' ; import { near , BigNumber , ChainProvider , FeeProvider , Address , Block , Transaction , FeeDetails } from '@liquality/types' ; import { NearNetwork } from '@liquality/near-networks' ; import { addressToString } from '@liquality/utils' ; import { normalizeTransactionObject , providers , Account , fromNearTimestamp } from '@liquality/near-utils' ; import { NodeError } from '@liquality/errors' ; import { get , isArray } from 'lodash' ; interface RpcProvider extends providers . JsonRpcProvider { [ key ] : any ; } export default class NearRpcProvider extends NodeProvider implements Partial < ChainProvider > , FeeProvider { _network : NearNetwork ; _jsonRpc : RpcProvider ; constructor ( network ) { super ( { baseURL : network . helperUrl , responseType : 'text' , transformResponse : undefined } ) ; this . _jsonRpc = new providers . JsonRpcProvider ( network . nodeUrl ) ; this . _network = network ; } async sendRawTransaction ( hash ) { const result = await this . _sendRawTransaction ( hash ) ; return get ( result , 'transaction.hash' ) ; } async getBlockByHash ( blockHash , includeTx ) { return this . _getBlockById ( blockHash , includeTx ) ; } async getBlockByNumber ( blockNumber , includeTx ? ) { return this . _getBlockById ( blockNumber , includeTx ) ; } async getBlockHeight ( txHash ? ) { const result = await this . _rpc ( 'block' , txHash ? { blockId : txHash } : { finality : 'final' } ) ; return get ( result , 'header.height' ) ; } async getTransactionByHash ( txHash ) { const currentHeight = await this . getBlockHeight ( ) ; const args = txHash . split ( '_' ) ; const tx = await this . _rpcQuery ( 'tx' , args ) ; const blockHash = tx . transaction_outcome . block_hash ; const blockNumber = await this . getBlockHeight ( blockHash ) ; return normalizeTransactionObject ( { ... tx , blockNumber , blockHash } , currentHeight ) ; } async getTransactionReceipt ( txHash ) { const args = txHash . split ( '_' ) ; const tx = await this . _rpcQuery ( 'EXPERIMENTAL_tx_status' , args ) ; const blockNumber = await this . getBlockHeight ( tx . transaction_outcome . block_hash ) ; return { ... tx , blockNumber } ; } async getGasPrice ( ) { const result = await this . _rpcQuery ( 'gas_price' , [ null ] ) ; const gasPrice = get ( result , 'gas_price' ) ; return parseInt ( gasPrice ) ; } async getBalance ( _addresses ) { const addresses = _addresses . map ( addressToString ) ; const promiseBalances = await Promise . all ( addresses . map ( async ( address ) => { try { const balance = await this . getAccount ( address ) . getAccountBalance ( ) ; return new BigNumber ( balance . available ) ; } catch ( err ) { if ( err . message && err . message . includes ( 'does not exist while viewing' ) ) { return new BigNumber ( 0 ) ; } throw err ; } } ) ) ; return promiseBalances . map ( ( balance ) => new BigNumber ( balance ) ) . reduce ( ( acc , balance ) => acc . plus ( balance ) , new BigNumber ( 0 ) ) ; } async generateBlock ( numberOfBlocks ) { await new Promise ( ( resolve ) => setTimeout ( resolve , numberOfBlocks * 20000 ) ) ; } async getImplicitAccount ( publicKey , index ) { const accounts = await this . nodeGet ( `/publicKey/${ publicKey . toString ( ) }/accounts` ) ; return accounts [ index ] ; } getAccount ( accountId , signer ? ) { return new Account ( { networkId : this . _network . networkId , provider : this . _jsonRpc , signer } , accountId ) ; } async getFees ( ) { const fee = await this . getGasPrice ( ) ; return { slow : { fee } , average : { fee } , fast : { fee } } ; } async _sendRawTransaction ( hash ) { return this . _rpcQuery ( 'broadcast_tx_commit' , [ hash ] ) ; } normalizeBlock ( block ) { const normalizedBlock = { number : block . height , hash : block . hash , timestamp : fromNearTimestamp ( block . timestamp ) , size : block . chunks_included , transactions : [ ] , parentHash : block . prev_hash } ; return normalizedBlock ; } async _getBlockById ( blockId , includeTx ) { const block = await this . _rpc ( 'block' , { blockId } ) ; const currentHeight = await this . getBlockHeight ( ) ; const header = block . header ; const normalizedBlock = this . normalizeBlock ( header ) ; if ( includeTx && ! block . transactions && isArray ( block . chunks ) ) { const chunks = await Promise . all ( block . chunks . map ( ( c ) => this . _rpc ( 'chunk' , c . chunk_hash ) ) ) ; const transactions = chunks . reduce < Transaction < near . InputTransaction > [ ] > ( ( p , c ) => { const tx = c . transactions . map ( ( t ) => { return normalizeTransactionObject ( { transaction : t , blockNumber : header . height , blockHash : header . hash , status : t . status } , currentHeight ) ; } ) ; p . push ( ... tx ) ; return p ; } , [ ] ) ; normalizedBlock . transactions = transactions ; } return normalizedBlock ; } async _rpc ( method , args ) { try { const data = await this . _jsonRpc [ method ] ( args ) ; return data ; } catch ( error ) { throw new NodeError ( `${ error . type } ${ error . message }` , { data : { method , args } } ) ; } } async _rpcQuery ( method , args ) { try { const data = await this . _jsonRpc . sendJsonRpc ( method , args ) ; return data ; } catch ( error ) { throw new NodeError ( `${ error . type } ${ error . message }` , { data : { method , args } } ) ; } } }