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