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