11import { CoinFeature , NetworkType , BaseCoin , EthereumNetwork } from '@bitgo/statics' ;
22import EthereumCommon from '@ethereumjs/common' ;
3+ import request from 'superagent' ;
34import { InvalidTransactionError } from '@bitgo/sdk-core' ;
45
56/**
@@ -23,3 +24,201 @@ export function getCommon(coin: Readonly<BaseCoin>): EthereumCommon {
2324 }
2425 ) ;
2526}
27+
28+ export async function recovery_HBAREVM_BlockchainExplorerQuery (
29+ query : Record < string , string > ,
30+ rpcUrl : string ,
31+ explorerUrl : string ,
32+ token ?: string
33+ ) : Promise < Record < string , unknown > > {
34+ // Hedera Mirror Node API does not use API keys, but we keep this for compatibility
35+ if ( token ) {
36+ query . apikey = token ;
37+ }
38+
39+ const { module, action } = query ;
40+
41+ // Remove trailing slash from explorerUrl if present
42+ const baseUrl = explorerUrl . replace ( / \/ $ / , '' ) ;
43+
44+ try {
45+ switch ( `${ module } .${ action } ` ) {
46+ case 'account.balance' :
47+ return await queryAddressBalanceHedera ( query , baseUrl ) ;
48+
49+ case 'account.txlist' :
50+ return await getAddressNonceHedera ( query , baseUrl ) ;
51+
52+ case 'account.tokenbalance' :
53+ return await queryTokenBalanceHedera ( query , baseUrl ) ;
54+
55+ case 'proxy.eth_gasPrice' :
56+ return await getGasPriceFromRPC ( query , rpcUrl ) ;
57+
58+ case 'proxy.eth_estimateGas' :
59+ return await getGasLimitFromRPC ( query , rpcUrl ) ;
60+
61+ case 'proxy.eth_call' :
62+ return await querySequenceIdFromRPC ( query , rpcUrl ) ;
63+
64+ default :
65+ throw new Error ( `Unsupported API call: ${ module } .${ action } ` ) ;
66+ }
67+ } catch ( error ) {
68+ throw error ;
69+ }
70+ }
71+
72+ /**
73+ * 1. Gets address balance using Hedera Mirror Node API
74+ */
75+ async function queryAddressBalanceHedera (
76+ query : Record < string , string > ,
77+ baseUrl : string
78+ ) : Promise < Record < string , unknown > > {
79+ const address = query . address ;
80+ const url = `${ baseUrl } /accounts/${ address } ` ;
81+ const response = await request . get ( url ) . send ( ) ;
82+
83+ if ( ! response . ok ) {
84+ throw new Error ( 'could not reach explorer' ) ;
85+ }
86+
87+ const balance = response . body . balance ?. balance || '0' ;
88+
89+ // Convert from tinybars to wei (1 HBAR = 10^8 tinybars, 1 HBAR = 10^18 wei)
90+ // So: wei = tinybars * 10^10
91+ const balanceInWei = ( BigInt ( balance ) * BigInt ( '10000000000' ) ) . toString ( ) ;
92+
93+ return { result : balanceInWei } ;
94+ }
95+
96+ /**
97+ * 2. Gets nonce using Hedera Mirror Node API
98+ */
99+ async function getAddressNonceHedera ( query : Record < string , string > , baseUrl : string ) : Promise < Record < string , unknown > > {
100+ const address = query . address ;
101+ const accountUrl = `${ baseUrl } /accounts/${ address } ` ;
102+ const response = await request . get ( accountUrl ) . send ( ) ;
103+
104+ if ( ! response . ok ) {
105+ throw new Error ( 'could not reach explorer' ) ;
106+ }
107+
108+ const nonce = response . body . ethereum_nonce || 0 ;
109+
110+ return { nonce : nonce } ;
111+ }
112+
113+ /**
114+ * 3. Gets token balance using Hedera Mirror Node API
115+ */
116+ async function queryTokenBalanceHedera (
117+ query : Record < string , string > ,
118+ baseUrl : string
119+ ) : Promise < Record < string , unknown > > {
120+ const contractAddress = query . contractaddress ;
121+ const address = query . address ;
122+
123+ // Get token balances for the account
124+ const url = `${ baseUrl } /accounts/${ address } /tokens` ;
125+ const response = await request . get ( url ) . send ( ) ;
126+
127+ if ( ! response . ok ) {
128+ throw new Error ( 'could not reach explorer' ) ;
129+ }
130+
131+ // Find the specific token balance
132+ const tokens = response . body . tokens || [ ] ;
133+ const tokenBalance = tokens . find (
134+ ( token : { token_id : string ; contract_address : string ; balance : number } ) =>
135+ token . token_id === contractAddress || token . contract_address === contractAddress
136+ ) ;
137+
138+ const balance = tokenBalance ? tokenBalance . balance . toString ( ) : '0' ;
139+ // Convert from tinybars to wei (1 HBAR = 10^8 tinybars, 1 HBAR = 10^18 wei)
140+ // So: wei = tinybars * 10^10
141+ const balanceInWei = ( BigInt ( balance ) * BigInt ( '10000000000' ) ) . toString ( ) ;
142+
143+ return { result : balanceInWei } ;
144+ }
145+
146+ /**
147+ * 4. Gets sequence ID using Hedera Mirror Node API or rpc call
148+ */
149+ async function querySequenceIdFromRPC ( query : Record < string , string > , rpcUrl : string ) : Promise < Record < string , unknown > > {
150+ const { to, data } = query ;
151+
152+ const url = rpcUrl ;
153+
154+ const requestBody = {
155+ jsonrpc : '2.0' ,
156+ method : 'eth_call' ,
157+ params : [
158+ {
159+ to : to ,
160+ data : data ,
161+ } ,
162+ ] ,
163+ id : 1 ,
164+ } ;
165+
166+ const response = await request . post ( url ) . send ( requestBody ) . set ( 'Content-Type' , 'application/json' ) ;
167+
168+ if ( ! response . ok ) {
169+ throw new Error ( 'could not fetch from rpc url' ) ;
170+ }
171+
172+ return response . body ;
173+ }
174+
175+ /**
176+ * 5. getGasPriceFromExternalAPI - Gets gas price using Hedera Mirror Node API
177+ */
178+ async function getGasPriceFromRPC ( query : Record < string , string > , rpcUrl : string ) : Promise < Record < string , unknown > > {
179+ const url = rpcUrl ;
180+
181+ const requestBody = {
182+ jsonrpc : '2.0' ,
183+ method : 'eth_gasPrice' ,
184+ params : [ ] ,
185+ id : 1 ,
186+ } ;
187+
188+ const response = await request . post ( url ) . send ( requestBody ) . set ( 'Content-Type' , 'application/json' ) ;
189+
190+ if ( ! response . ok ) {
191+ throw new Error ( 'could not fetch from rpc url' ) ;
192+ }
193+
194+ return response . body ;
195+ }
196+
197+ /**
198+ * 6. getGasLimitFromExternalAPI - Gets gas limit using Hedera Mirror Node API
199+ */
200+ async function getGasLimitFromRPC ( query : Record < string , string > , rpcUrl : string ) : Promise < Record < string , unknown > > {
201+ const url = rpcUrl ;
202+
203+ const { from, to, data } = query ;
204+
205+ const requestBody = {
206+ jsonrpc : '2.0' ,
207+ method : 'eth_estimateGas' ,
208+ params : [
209+ {
210+ from,
211+ to,
212+ data,
213+ } ,
214+ ] ,
215+ id : 1 ,
216+ } ;
217+ const response = await request . post ( url ) . send ( requestBody ) . set ( 'Content-Type' , 'application/json' ) ;
218+
219+ if ( ! response . ok ) {
220+ throw new Error ( 'could not fetch from rpc url' ) ;
221+ }
222+
223+ return response . body ;
224+ }
0 commit comments