import { PinoLogger } from 'nestjs-pino' ; import axios from 'axios' ; import { convertAddressFromHex , CreateTronTrc10 , CreateTronTrc20 , Currency , FreezeTron , generateAddressFromXPub , generatePrivateKeyFromMnemonic , generateWallet , prepareTronCreateTrc10SignedKMSTransaction , prepareTronCreateTrc10SignedTransaction , prepareTronCreateTrc20SignedKMSTransaction , prepareTronCreateTrc20SignedTransaction , prepareTronFreezeKMSTransaction , prepareTronFreezeTransaction , prepareTronSignedKMSTransaction , prepareTronSignedTransaction , prepareTronTrc10SignedKMSTransaction , prepareTronTrc10SignedTransaction , prepareTronTrc20SignedKMSTransaction , prepareTronTrc20SignedTransaction , TransferTron , TransferTronTrc10 , TransferTronTrc20 , TronAccount , TronBlock , TronTransaction , TronTrc10 } from '@tatumio/tatum' ; import { Trc20Tx } from './dto/Trc20Tx' ; import { TronError } from './TronError' ; export abstract class TronService { private static mapTransaction ( t ) { if ( t . internal_tx_id ) { return { ... t , fromAddressBase58 : convertAddressFromHex ( t . from_address ) , toAddressBase58 : convertAddressFromHex ( t . to_address ) } ; } const rawData = { ... t . raw_data } ; rawData . contract = rawData . contract ?. map ( c => ( { ... c , parameter : { ... c . parameter , value : { ... c . parameter . value , ownerAddressBase58 : convertAddressFromHex ( c . parameter . value . owner_address ) , toAddressBase58 : convertAddressFromHex ( c . parameter . value . to_address ) , contractAddressBase58 : convertAddressFromHex ( c . parameter . value . contract_address ) , } } } ) ) || [ ] ; return { ret : t . ret , signature : t . signature , blockNumber : t . blockNumber , txID : t . txID , netFee : t . net_fee , netUsage : t . net_usage , energyFee : t . energy_fee , energyUsage : t . energy_usage , energyUsageTotal : t . energy_usage_total , internalTransactions : t . internal_transactions , rawData : rawData , log : t . log , } ; } private static mapTransaction20 ( t ) { return { txID : t . transaction_id , tokenInfo : t . token_info , from : t . from , to : t . to , type : t . type , value : t . value , } ; } private async getNodeAddress ( ) { return ( await this . isTestnet ( ) ) ? 'https://api.shasta.trongrid.io' : 'https://api.trongrid.io' ; } protected constructor ( protected readonly logger ) { } protected abstract isTestnet ( ) ; protected abstract getNodesUrl ( testnet ) ; protected abstract getApiKey ( ) ; protected abstract getScanningApiKey ( ) ; protected abstract storeKMSTransaction ( txData , currency , signatureId , index ? ) ; protected abstract completeKMSTransaction ( txId , signatureId ) ; public async broadcast ( txData , signatureId ? ) { const transaction = JSON . parse ( txData ) ; if ( Date . now ( ) > transaction ?. raw_data ?. expiration ) { throw new TronError ( 'Transaction expired' , 'tron.error' ) ; } const url = ( await this . getNodesUrl ( await this . isTestnet ( ) ) ) [ 0 ] ; const broadcast = ( await axios . post ( `${ url }/wallet/broadcasttransaction` , transaction , { headers : { 'TRON-PRO-API-KEY' : this . getApiKey ( ) } } ) ) . data ; if ( broadcast . result ) { if ( signatureId ) { try { await this . completeKMSTransaction ( broadcast . txid , signatureId ) ; } catch ( e ) { this . logger . error ( e ) ; return { txId : broadcast . txid , failed : true } ; } } return { txId : broadcast . txid } ; } let msg = broadcast . message ; try { msg = Buffer . from ( msg , 'hex' ) . toString ( ) ; } catch ( e ) { } throw new TronError ( `Broadcast failed due to ${ msg }` , 'tron.error' ) ; } public async getBlockChainInfo ( testnet ? ) { let t ; let apiKey ; if ( testnet !== undefined ) { t = testnet ; apiKey = this . getScanningApiKey ( ) ; } else { t = await this . isTestnet ( ) ; apiKey = this . getApiKey ( ) ; } const urls = await this . getNodesUrl ( t ) ; const block = ( await axios . post ( urls [ 0 ] + '/wallet/getnowblock' , undefined , { headers : { 'TRON-PRO-API-KEY' : apiKey } } ) ) . data ; return { testnet : t , hash : block . blockID , blockNumber : block . block_header . raw_data . number } ; } public async getBlock ( hashOrHeight , testnet ? ) { let t ; let apiKey ; if ( testnet !== undefined ) { t = testnet ; apiKey = this . getScanningApiKey ( ) ; } else { t = await this . isTestnet ( ) ; apiKey = this . getApiKey ( ) ; } const url = ( await this . getNodesUrl ( t ) ) [ 0 ] ; let block ; if ( hashOrHeight . length > 32 ) { block = ( await axios . post ( `${ url }/wallet/getblockbyid` , { value : hashOrHeight } , { headers : { 'TRON-PRO-API-KEY' : apiKey } } ) ) . data ; } else { block = ( await axios . post ( `${ url }/wallet/getblockbynum` , { num : parseInt ( hashOrHeight ) } , { headers : { 'TRON-PRO-API-KEY' : apiKey } } ) ) . data ; } return { blockNumber : block . block_header . raw_data . number , hash : block . blockID , parentHash : block . block_header . raw_data . parentHash , timestamp : block . block_header . raw_data . timestamp , witnessAddress : block . block_header . raw_data . witness_address , witnessSignature : block . block_header . witness_signature , transactions : block . transactions ?. map ( TronService . mapTransaction ) || [ ] , } ; } public async getTransaction ( txId , testnet ? ) { const url = ( await this . getNodesUrl ( testnet !== undefined ? testnet : await this . isTestnet ( ) ) ) [ 0 ] ; let apiKey1 , apiKey2 ; if ( testnet !== undefined ) { apiKey1 = this . getScanningApiKey ( ) ; apiKey2 = this . getScanningApiKey ( ) ; } else { apiKey1 = this . getApiKey ( ) ; apiKey2 = this . getApiKey ( ) ; } const [ { data : tx } , { data : info } ] = await Promise . all ( [ axios . post ( `${ url }/wallet/gettransactionbyid` , { value : txId } , { headers : { 'TRON-PRO-API-KEY' : apiKey1 } } ) , axios . post ( `${ url }/wallet/gettransactioninfobyid` , { value : txId } , { headers : { 'TRON-PRO-API-KEY' : apiKey2 } } ) ] ) ; return TronService . mapTransaction ( { ... tx , ... info . receipt , log : info . log , blockNumber : info . blockNumber } ) ; } public async getAccount ( address ) { const url = await this . getNodeAddress ( ) ; const { data } = ( await axios . get ( `${ url }/v1/accounts/${ address }` , { headers : { 'TRON-PRO-API-KEY' : this . getApiKey ( ) } } ) ) . data ; if ( ! data ?. length ) { throw new TronError ( 'no such account.' , 'tron.error' ) ; } const account = data [ 0 ] ; return { address : account . add , assetIssuedId : account . asset_issued_ID , assetIssuedName : account . asset_issued_name , balance : account . balance , createTime : account . create_time , freeNetUsage : account . free_net_usage , trc10 : account . assetV2 , trc20 : account . trc20 , } ; } public async getTransactionsByAccount ( address , next ? ) { const url = await this . getNodeAddress ( ) ; let u = `${ url }/v1/accounts/${ address }/transactions?limit=200` ; if ( next ) { u += '&fingerprint=' + next ; } const result = ( await axios . get ( u , { headers : { 'TRON-PRO-API-KEY' : this . getApiKey ( ) } } ) ) . data ; return { transactions : result . data . map ( TronService . mapTransaction ) . filter ( t => ! t . internal_tx_id ) , internalTransactions : result . data . map ( TronService . mapTransaction ) . filter ( t => t . internal_tx_id ) , next : result . meta ?. fingerprint } ; } public async getTrc20TransactionsByAccount ( address , next ? ) { const url = await this . getNodeAddress ( ) ; let u = `${ url }/v1/accounts/${ address }/transactions/trc20?limit=200` ; if ( next ) { u += '&fingerprint=' + next ; } const result = ( await axios . get ( u , { headers : { 'TRON-PRO-API-KEY' : this . getApiKey ( ) } } ) ) . data ; return { transactions : result . data . map ( TronService . mapTransaction20 ) , next : result . meta ?. fingerprint } ; } public async generateWallet ( mnem ? ) { return generateWallet ( Currency . TRON , await this . isTestnet ( ) , mnem ) ; } public async generateAddress ( xpub , i ) { return { address : await generateAddressFromXPub ( Currency . TRON , await this . isTestnet ( ) , xpub , i ) } ; } public async generatePrivateKey ( mnemonic , i ) { return { key : await generatePrivateKeyFromMnemonic ( Currency . TRON , await this . isTestnet ( ) , mnemonic , i ) } ; } public async sendTransaction ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronSignedKMSTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronSignedTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async sendTrc10Transaction ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronTrc10SignedKMSTransaction ( t , body , undefined , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronTrc10SignedTransaction ( t , body , undefined , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async sendTrc20Transaction ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronTrc20SignedKMSTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronTrc20SignedTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async createTrc10Transaction ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronCreateTrc10SignedKMSTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronCreateTrc10SignedTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async createTrc20Transaction ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronCreateTrc20SignedKMSTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronCreateTrc20SignedTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async freezeBalance ( body ) { const t = await this . isTestnet ( ) ; if ( body . signatureId ) { const txData = await prepareTronFreezeKMSTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ; return { signatureId : await this . storeKMSTransaction ( txData , Currency . TRON , [ body . signatureId ] , body . index ) } ; } else { return this . broadcast ( await prepareTronFreezeTransaction ( t , body , ( await this . getNodesUrl ( t ) ) [ 0 ] ) ) ; } } public async getTrc10Detail ( id ) { const url = `${ ( await this . getNodeAddress ( ) ) }/v1/assets/${ id }` ; const { data } = ( await axios . get ( url , { headers : { 'TRON-PRO-API-KEY' : this . getApiKey ( ) } } ) ) . data ; if ( ! data ?. length ) { throw new TronError ( 'No such asset.' , 'tron.error' ) ; } return { abbr : data [ 0 ] . abbr , description : data [ 0 ] . description , id : data [ 0 ] . id , name : data [ 0 ] . name , ownerAddress : data [ 0 ] . owner_address , precision : data [ 0 ] . precision , totalSupply : data [ 0 ] . total_supply , url : data [ 0 ] . url , } ; } }