From b832596290b006a7403a4e5767b88c9929f74505 Mon Sep 17 00:00:00 2001 From: Rohit Saw Date: Fri, 14 Nov 2025 15:46:19 +0530 Subject: [PATCH] feat: recovery support for Hedera EVM ticket: win-7852 --- .../src/abstractEthLikeNewCoins.ts | 5 + modules/sdk-coin-evm/package.json | 3 +- modules/sdk-coin-evm/src/evmCoin.ts | 20 +- modules/sdk-coin-evm/src/lib/utils.ts | 200 +++++++++ modules/sdk-coin-evm/test/unit/utils.ts | 422 ++++++++++++++++++ modules/sdk-core/src/bitgo/environments.ts | 11 +- modules/statics/src/map.ts | 2 + modules/statics/src/networks.ts | 2 +- package.json | 4 + yarn.lock | 89 +--- 10 files changed, 681 insertions(+), 77 deletions(-) create mode 100644 modules/sdk-coin-evm/test/unit/utils.ts diff --git a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts index 05f0c38f43..be9a301d33 100644 --- a/modules/abstract-eth/src/abstractEthLikeNewCoins.ts +++ b/modules/abstract-eth/src/abstractEthLikeNewCoins.ts @@ -902,6 +902,11 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin { }, apiKey ); + + if (result && typeof result?.nonce === 'number') { + return Number(result.nonce); + } + if (!result || !Array.isArray(result.result)) { throw new Error('Unable to find next nonce from Etherscan, got: ' + JSON.stringify(result)); } diff --git a/modules/sdk-coin-evm/package.json b/modules/sdk-coin-evm/package.json index 1fac844dcc..fc43d8fbac 100644 --- a/modules/sdk-coin-evm/package.json +++ b/modules/sdk-coin-evm/package.json @@ -19,7 +19,8 @@ "@bitgo/abstract-eth": "^24.16.0", "@bitgo/sdk-core": "^36.20.1", "@bitgo/statics": "^58.13.0", - "@ethereumjs/common": "^2.6.5" + "@ethereumjs/common": "^2.6.5", + "superagent": "^9.0.1" }, "author": "BitGo SDK Team ", "license": "MIT", diff --git a/modules/sdk-coin-evm/src/evmCoin.ts b/modules/sdk-coin-evm/src/evmCoin.ts index acc2cd6466..93c1d2755e 100644 --- a/modules/sdk-coin-evm/src/evmCoin.ts +++ b/modules/sdk-coin-evm/src/evmCoin.ts @@ -2,7 +2,7 @@ * @prettier */ import { BaseCoin, BitGoBase, common, MPCAlgorithm, MultisigType, multisigTypes } from '@bitgo/sdk-core'; -import { BaseCoin as StaticsBaseCoin, CoinFeature, coins } from '@bitgo/statics'; +import { BaseCoin as StaticsBaseCoin, CoinFeature, coins, CoinFamily } from '@bitgo/statics'; import { AbstractEthLikeNewCoins, OfflineVaultTxInfo, @@ -13,6 +13,7 @@ import { VerifyEthTransactionOptions, } from '@bitgo/abstract-eth'; import { TransactionBuilder } from './lib'; +import { recovery_HBAREVM_BlockchainExplorerQuery } from './lib/utils'; import assert from 'assert'; export class EvmCoin extends AbstractEthLikeNewCoins { @@ -78,7 +79,22 @@ export class EvmCoin extends AbstractEthLikeNewCoins { const apiToken = apiKey || evmConfig[this.getFamily()].apiToken; const explorerUrl = evmConfig[this.getFamily()].baseUrl; - return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken as string); + switch (this.getFamily()) { + case CoinFamily.HBAREVM: + assert( + evmConfig[this.getFamily()].rpcUrl, + `rpc url config is missing for ${this.getFamily()} in ${this.bitgo.getEnv()}` + ); + const rpcUrl = evmConfig[this.getFamily()].rpcUrl; + return await recovery_HBAREVM_BlockchainExplorerQuery( + query, + rpcUrl as string, + explorerUrl as string, + apiToken as string + ); + default: + return await recoveryBlockchainExplorerQuery(query, explorerUrl as string, apiToken as string); + } } /** @inheritDoc */ diff --git a/modules/sdk-coin-evm/src/lib/utils.ts b/modules/sdk-coin-evm/src/lib/utils.ts index 98182ba012..849db8ca3a 100644 --- a/modules/sdk-coin-evm/src/lib/utils.ts +++ b/modules/sdk-coin-evm/src/lib/utils.ts @@ -1,5 +1,6 @@ import { CoinFeature, NetworkType, BaseCoin, EthereumNetwork } from '@bitgo/statics'; import EthereumCommon from '@ethereumjs/common'; +import request from 'superagent'; import { InvalidTransactionError } from '@bitgo/sdk-core'; /** @@ -23,3 +24,202 @@ export function getCommon(coin: Readonly): EthereumCommon { } ); } + +function tinybarsToWei(tinybars: string): string { + // Convert from tinybars to wei (1 HBAR = 10^8 tinybars, 1 HBAR = 10^18 wei) + // So: wei = tinybars * 10^10 + return (BigInt(tinybars) * BigInt('10000000000')).toString(); +} + +/** + * + * @param query - etherscan query parameters for the API call + * @param rpcUrl - RPC URL of the Hedera network + * @param explorerUrl - base URL of the Hedera Mirror Node API + * @param token - optional API key to use for the query + * @returns + */ +export async function recovery_HBAREVM_BlockchainExplorerQuery( + query: Record, + rpcUrl: string, + explorerUrl: string, + token?: string +): Promise> { + // Hedera Mirror Node API does not use API keys, but we keep this for compatibility + if (token) { + query.apikey = token; + } + + const { module, action } = query; + + // Remove trailing slash from explorerUrl if present + const baseUrl = explorerUrl.replace(/\/$/, ''); + + switch (`${module}.${action}`) { + case 'account.balance': + return await queryAddressBalanceHedera(query, baseUrl); + + case 'account.txlist': + return await getAddressNonceHedera(query, baseUrl); + + case 'account.tokenbalance': + return await queryTokenBalanceHedera(query, baseUrl); + + case 'proxy.eth_gasPrice': + return await getGasPriceFromRPC(query, rpcUrl); + + case 'proxy.eth_estimateGas': + return await getGasLimitFromRPC(query, rpcUrl); + + case 'proxy.eth_call': + return await querySequenceIdFromRPC(query, rpcUrl); + + default: + throw new Error(`Unsupported API call: ${module}.${action}`); + } +} + +/** + * 1. Gets address balance using Hedera Mirror Node API + */ +async function queryAddressBalanceHedera( + query: Record, + baseUrl: string +): Promise> { + const address = query.address; + const url = `${baseUrl}/accounts/${address}`; + const response = await request.get(url).send(); + + if (!response.ok) { + throw new Error('could not reach explorer'); + } + + const balance = response.body.balance?.balance || '0'; + + const balanceInWei = tinybarsToWei(balance); + + return { result: balanceInWei }; +} + +/** + * 2. Gets nonce using Hedera Mirror Node API + */ +async function getAddressNonceHedera(query: Record, baseUrl: string): Promise> { + const address = query.address; + const accountUrl = `${baseUrl}/accounts/${address}`; + const response = await request.get(accountUrl).send(); + + if (!response.ok) { + throw new Error('could not reach explorer'); + } + + const nonce = response.body.ethereum_nonce || 0; + + return { nonce: nonce }; +} + +/** + * 3. Gets token balance using Hedera Mirror Node API + */ +async function queryTokenBalanceHedera( + query: Record, + baseUrl: string +): Promise> { + const contractAddress = query.contractaddress; + const address = query.address; + + // Get token balances for the account + const url = `${baseUrl}/accounts/${address}/tokens`; + const response = await request.get(url).send(); + + if (!response.ok) { + throw new Error('could not reach explorer'); + } + + // Find the specific token balance + const tokens = response.body.tokens || []; + const tokenBalance = tokens.find( + (token: { token_id: string; contract_address: string; balance: number }) => + token.token_id === contractAddress || token.contract_address === contractAddress + ); + + const balance = tokenBalance && tokenBalance.balance !== null ? tokenBalance.balance.toString() : '0'; + + const balanceInWei = tinybarsToWei(balance); + + return { result: balanceInWei }; +} + +/** + * 4. Gets sequence ID using RPC call + */ +async function querySequenceIdFromRPC(query: Record, rpcUrl: string): Promise> { + const { to, data } = query; + + const requestBody = { + jsonrpc: '2.0', + method: 'eth_call', + params: [ + { + to: to, + data: data, + }, + ], + id: 1, + }; + + const response = await request.post(rpcUrl).send(requestBody).set('Content-Type', 'application/json'); + + if (!response.ok) { + throw new Error('could not fetch sequence ID from RPC'); + } + + return response.body; +} + +/** + * 5. getGasPriceFromRPC - Gets gas price using Hedera Mirror Node API + */ +async function getGasPriceFromRPC(query: Record, rpcUrl: string): Promise> { + const requestBody = { + jsonrpc: '2.0', + method: 'eth_gasPrice', + params: [], + id: 1, + }; + + const response = await request.post(rpcUrl).send(requestBody).set('Content-Type', 'application/json'); + + if (!response.ok) { + throw new Error('could not fetch gas price from RPC'); + } + + return response.body; +} + +/** + * 6. getGasLimitFromRPC - Gets gas limit estimate using RPC call. + */ +async function getGasLimitFromRPC(query: Record, rpcUrl: string): Promise> { + const { from, to, data } = query; + + const requestBody = { + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [ + { + from, + to, + data, + }, + ], + id: 1, + }; + const response = await request.post(rpcUrl).send(requestBody).set('Content-Type', 'application/json'); + + if (!response.ok) { + throw new Error('could not estimate gas limit from RPC'); + } + + return response.body; +} diff --git a/modules/sdk-coin-evm/test/unit/utils.ts b/modules/sdk-coin-evm/test/unit/utils.ts new file mode 100644 index 0000000000..7b2032bf0b --- /dev/null +++ b/modules/sdk-coin-evm/test/unit/utils.ts @@ -0,0 +1,422 @@ +import nock from 'nock'; +import 'should'; + +import { recovery_HBAREVM_BlockchainExplorerQuery } from '../../src/lib/utils'; + +describe('EVM Coin Utils', function () { + describe('recovery_HBAREVM_BlockchainExplorerQuery', function () { + const mockRpcUrl = 'https://testnet.hashio.io/api'; + const mockExplorerUrl = 'https://testnet.mirrornode.hedera.com/api/v1'; + const mockAddress = '0xe9591fe1bd82ebc6b293fb355c79f22e204d6d84'; + const mockToken = 'test-api-token'; + + beforeEach(function () { + nock.cleanAll(); + }); + + afterEach(function () { + nock.cleanAll(); + }); + + describe('account.balance', function () { + it('should return balance in wei from Hedera Mirror Node API', async function () { + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + const mockResponse = { + balance: { + balance: 10000000000, // 100 HBAR in tinybars + }, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('100000000000000000000'); // 100 HBAR in wei + }); + + it('should return 0 balance when balance is not provided', async function () { + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + const mockResponse = {}; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('0'); + }); + + it('should throw error when API request fails', async function () { + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(500, 'Internal Server Error'); + + try { + await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + throw new Error('Expected function to throw'); + } catch (error) { + // The superagent library throws this error for 500 responses + (error as Error).message.should.match(/Internal Server Error/); + } + }); + }); + + describe('account.txlist', function () { + it('should return nonce from Hedera Mirror Node API', async function () { + const query = { + module: 'account', + action: 'txlist', + address: mockAddress, + }; + + const mockResponse = { + ethereum_nonce: 42, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('nonce'); + (result.nonce as number).should.equal(42); + }); + + it('should return 0 nonce when ethereum_nonce is not provided', async function () { + const query = { + module: 'account', + action: 'txlist', + address: mockAddress, + }; + + const mockResponse = {}; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('nonce'); + (result.nonce as number).should.equal(0); + }); + }); + + describe('account.tokenbalance', function () { + it('should return token balance in wei from Hedera Mirror Node API', async function () { + const contractAddress = '0x123456789'; + const query = { + module: 'account', + action: 'tokenbalance', + contractaddress: contractAddress, + address: mockAddress, + }; + + const mockResponse = { + tokens: [ + { + token_id: contractAddress, + contract_address: contractAddress, + balance: 5000000000, // 50 tokens in tinybars + }, + ], + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}/tokens`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('50000000000000000000'); // 50 tokens in wei + }); + + it('should return 0 balance when token is not found', async function () { + const contractAddress = '0x123456789'; + const query = { + module: 'account', + action: 'tokenbalance', + contractaddress: contractAddress, + address: mockAddress, + }; + + const mockResponse = { + tokens: [], + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}/tokens`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('0'); + }); + + it('should find token by contract_address when token_id does not match', async function () { + const contractAddress = '0x123456789'; + const query = { + module: 'account', + action: 'tokenbalance', + contractaddress: contractAddress, + address: mockAddress, + }; + + const mockResponse = { + tokens: [ + { + token_id: 'different_id', + contract_address: contractAddress, + balance: 2500000000, // 25 tokens in tinybars + }, + ], + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}/tokens`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('25000000000000000000'); // 25 tokens in wei + }); + }); + + describe('proxy.eth_gasPrice', function () { + it('should return gas price from RPC endpoint', async function () { + const query = { + module: 'proxy', + action: 'eth_gasPrice', + }; + + const mockResponse = { + jsonrpc: '2.0', + id: 1, + result: '0x84b6a5c400', // 570 Gwei in hex + }; + + nock(mockRpcUrl) + .post('', '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}') + .reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('jsonrpc'); + result.should.have.property('result'); + (result.jsonrpc as string).should.equal('2.0'); + (result.result as string).should.equal('0x84b6a5c400'); + }); + + it('should throw error when RPC request fails', async function () { + const query = { + module: 'proxy', + action: 'eth_gasPrice', + }; + + nock(mockRpcUrl) + .post('', '{"jsonrpc":"2.0","method":"eth_gasPrice","params":[],"id":1}') + .reply(500, 'Internal Server Error'); + + try { + await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + throw new Error('Expected function to throw'); + } catch (error) { + // The superagent library throws this error for 500 responses + (error as Error).message.should.match(/Internal Server Error/); + } + }); + }); + + describe('proxy.eth_estimateGas', function () { + it('should return gas estimate from RPC endpoint', async function () { + const query = { + module: 'proxy', + action: 'eth_estimateGas', + from: '0xfrom', + to: '0xto', + data: '0xdata', + }; + + const mockResponse = { + jsonrpc: '2.0', + id: 1, + result: '0x5208', // 21000 gas in hex + }; + + nock(mockRpcUrl) + .post( + '', + '{"jsonrpc":"2.0","method":"eth_estimateGas","params":[{"from":"0xfrom","to":"0xto","data":"0xdata"}],"id":1}' + ) + .reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('jsonrpc'); + result.should.have.property('result'); + (result.jsonrpc as string).should.equal('2.0'); + (result.result as string).should.equal('0x5208'); + }); + }); + + describe('proxy.eth_call', function () { + it('should return sequence ID from RPC endpoint', async function () { + const query = { + module: 'proxy', + action: 'eth_call', + to: '0xcontract', + data: '0xfunctiondata', + }; + + const mockResponse = { + jsonrpc: '2.0', + id: 1, + result: '0x0000000000000000000000000000000000000000000000000000000000000001', // sequence ID 1 + }; + + nock(mockRpcUrl) + .post( + '', + '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"0xcontract","data":"0xfunctiondata"}],"id":1}' + ) + .reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('jsonrpc'); + result.should.have.property('result'); + (result.jsonrpc as string).should.equal('2.0'); + (result.result as string).should.equal('0x0000000000000000000000000000000000000000000000000000000000000001'); + }); + }); + + describe('unsupported API calls', function () { + it('should throw error for unsupported module.action combination', async function () { + const query = { + module: 'unsupported', + action: 'unknown', + }; + + await recovery_HBAREVM_BlockchainExplorerQuery( + query, + mockRpcUrl, + mockExplorerUrl, + mockToken + ).should.be.rejectedWith('Unsupported API call: unsupported.unknown'); + }); + }); + + describe('URL handling', function () { + it('should handle explorer URL with trailing slash', async function () { + const explorerUrlWithSlash = mockExplorerUrl + '/'; + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + const mockResponse = { + balance: { + balance: 10000000000, + }, + }; + + nock(mockExplorerUrl) // Note: nock should match without trailing slash + .get(`/accounts/${mockAddress}`) + .reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery( + query, + mockRpcUrl, + explorerUrlWithSlash, + mockToken + ); + + result.should.have.property('result'); + (result.result as string).should.equal('100000000000000000000'); + }); + }); + + describe('API token handling', function () { + it('should add apikey to query when token is provided', async function () { + const query: Record = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + // Since the token is added to the query object but not used in the actual request for Hedera, + // we just verify the function doesn't break when token is provided + const mockResponse = { + balance: { + balance: 10000000000, + }, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('100000000000000000000'); + + // Verify that apikey was added to the query object + query.should.have.property('apikey'); + query.apikey.should.equal(mockToken); + }); + }); + + describe('BigInt conversion', function () { + it('should handle large balance numbers correctly', async function () { + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + const mockResponse = { + balance: { + balance: 50000000000000, // Very large balance in tinybars + }, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('500000000000000000000000'); // Large number in wei + }); + + it('should handle zero balance correctly', async function () { + const query = { + module: 'account', + action: 'balance', + address: mockAddress, + }; + + const mockResponse = { + balance: { + balance: 0, + }, + }; + + nock(mockExplorerUrl).get(`/accounts/${mockAddress}`).reply(200, mockResponse); + + const result = await recovery_HBAREVM_BlockchainExplorerQuery(query, mockRpcUrl, mockExplorerUrl, mockToken); + + result.should.have.property('result'); + (result.result as string).should.equal('0'); + }); + }); + }); +}); diff --git a/modules/sdk-core/src/bitgo/environments.ts b/modules/sdk-core/src/bitgo/environments.ts index 6220a1deef..ca24d40f86 100644 --- a/modules/sdk-core/src/bitgo/environments.ts +++ b/modules/sdk-core/src/bitgo/environments.ts @@ -93,6 +93,7 @@ interface EnvironmentTemplate { [key: string]: { baseUrl: string; apiToken?: string; + rpcUrl?: string; }; }; // The key here is coinFamily and it will be same for both mainnet and testnet (eg: 'cronos') @@ -269,8 +270,9 @@ const mainnetBase: EnvironmentTemplate = { megaeth: { baseUrl: 'https://carrot.megaeth.com/rpc', //TODO: add mainnet url when available }, - hedera: { - baseUrl: 'https://server-verify.hashscan.io/verify', + hbarevm: { + baseUrl: 'https://mainnet.mirrornode.hedera.com/api/v1', + rpcUrl: 'https://mainnet.hashio.io/api', }, fluenteth: { baseUrl: 'https://testnet.fluentscan.xyz/api/', //TODO: COIN-6478: add mainnet url when available @@ -415,8 +417,9 @@ const testnetBase: EnvironmentTemplate = { plume: { baseUrl: 'https://testnet-explorer.plume.org', }, - hedera: { - baseUrl: 'https://server-verify.hashscan.io/verify', + hbarevm: { + baseUrl: 'https://testnet.mirrornode.hedera.com/api/v1', + rpcUrl: 'https://testnet.hashio.io/api', }, fluenteth: { baseUrl: 'https://testnet.fluentscan.xyz/api/', diff --git a/modules/statics/src/map.ts b/modules/statics/src/map.ts index a2dd5cef7e..49fa3c6819 100644 --- a/modules/statics/src/map.ts +++ b/modules/statics/src/map.ts @@ -149,6 +149,8 @@ export class CoinMap { 98867: 'tplume', 98866: 'plume', 6342: 'tmegaeth', + 295: 'hbarevm', + 296: 'thbarevm', }; return ethLikeCoinFromChainId[chainId]; } diff --git a/modules/statics/src/networks.ts b/modules/statics/src/networks.ts index 038fa6930b..8e01154477 100644 --- a/modules/statics/src/networks.ts +++ b/modules/statics/src/networks.ts @@ -2035,7 +2035,7 @@ class Plume extends Mainnet implements EthereumNetwork { class HederaEVMTestnet extends Testnet implements EthereumNetwork { name = 'Testnet Hedera EVM'; family = CoinFamily.HBAREVM; - explorerUrl = 'https://hashscan.io/mainnet/transactions/'; + explorerUrl = 'https://hashscan.io/mainnet/transaction/'; accountExplorerUrl = 'https://hashscan.io/mainnet/account/'; chainId = 296; nativeCoinOperationHashPrefix = '296'; diff --git a/package.json b/package.json index 3345696574..4763b3aae3 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,10 @@ "yeoman-generator": "^5.6.1" }, "resolutions": { + "**/lerna/**/glob": "11.1.0", + "**/yeoman-generator/**/glob": "11.1.0", + "**/cacache/glob": "11.1.0", + "**/pacote/glob": "11.1.0", "**/sha.js": ">=2.4.12", "@ethereumjs/util": "8.0.3", "@types/keyv": "3.1.4", diff --git a/yarn.lock b/yarn.lock index 1e5a81fe55..2b3ece56f1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4316,11 +4316,6 @@ tslib "^2.6.2" webcrypto-core "^1.8.0" -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@polka/url@^1.0.0-next.24": version "1.0.0-next.29" resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz#5a40109a1ab5f84d6fd8fc928b19f367cbe7e7b1" @@ -11729,7 +11724,7 @@ foreground-child@^2.0.0: cross-spawn "^7.0.0" signal-exit "^3.0.2" -foreground-child@^3.1.0, foreground-child@^3.3.1: +foreground-child@^3.3.1: version "3.3.1" resolved "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz" integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== @@ -12140,33 +12135,21 @@ glob-to-regexp@^0.4.1: resolved "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^10.2.2: - version "10.4.5" - resolved "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - -glob@^11.0.3: - version "11.0.3" - resolved "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" - integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== +glob@11.1.0, glob@^10.2.2, glob@^11.0.3, glob@^7.0.0, glob@^8.0.1, glob@^9.2.0: + version "11.1.0" + resolved "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz#4f826576e4eb99c7dad383793d2f9f08f67e50a6" + integrity sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw== dependencies: foreground-child "^3.3.1" jackspeak "^4.1.1" - minimatch "^10.0.3" + minimatch "^10.1.1" minipass "^7.1.2" package-json-from-dist "^1.0.0" path-scurry "^2.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@~7.2.3: +glob@^7.0.3, glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7, glob@~7.2.3: version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== dependencies: fs.realpath "^1.0.0" @@ -12176,9 +12159,9 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, gl once "^1.3.0" path-is-absolute "^1.0.0" -glob@^8.0.0, glob@^8.0.1, glob@^8.1.0: +glob@^8.0.0, glob@^8.1.0: version "8.1.0" - resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz" + resolved "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== dependencies: fs.realpath "^1.0.0" @@ -12187,16 +12170,6 @@ glob@^8.0.0, glob@^8.0.1, glob@^8.1.0: minimatch "^5.0.1" once "^1.3.0" -glob@^9.2.0: - version "9.3.5" - resolved "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" - integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - global-directory@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz" @@ -13669,15 +13642,6 @@ istanbul-reports@^3.0.0, istanbul-reports@^3.0.2: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jackspeak@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" @@ -14679,7 +14643,7 @@ lowercase-keys@^2.0.0: resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== -lru-cache@^10.0.1, lru-cache@^10.2.0, lru-cache@^10.2.2: +lru-cache@^10.0.1, lru-cache@^10.2.2: version "10.4.3" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== @@ -15127,6 +15091,13 @@ minimatch@^10.0.3: dependencies: "@isaacs/brace-expansion" "^5.0.0" +minimatch@^10.1.1: + version "10.1.1" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz#e6e61b9b0c1dcab116b5a7d1458e8b6ae9e73a55" + integrity sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ== + dependencies: + "@isaacs/brace-expansion" "^5.0.0" + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" @@ -15148,13 +15119,6 @@ minimatch@^7.2.0, minimatch@^7.4.6: dependencies: brace-expansion "^2.0.1" -minimatch@^8.0.2: - version "8.0.4" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" - integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== - dependencies: - brace-expansion "^2.0.1" - minimatch@^9.0.0, minimatch@^9.0.4, minimatch@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz" @@ -15259,17 +15223,12 @@ minipass@^3.0.0, minipass@^3.1.1, minipass@^3.1.6: dependencies: yallist "^4.0.0" -minipass@^4.2.4: - version "4.2.8" - resolved "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - minipass@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: +minipass@^7.0.2, minipass@^7.0.3, minipass@^7.0.4, minipass@^7.1.2: version "7.1.2" resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz" integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== @@ -16038,7 +15997,7 @@ number-to-bn@1.7.0: nyc@^15.0.0, nyc@^15.1.0: version "15.1.0" - resolved "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz" + resolved "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz#1335dae12ddc87b6e249d5a1994ca4bdaea75f02" integrity sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A== dependencies: "@istanbuljs/load-nyc-config" "^1.0.0" @@ -16740,14 +16699,6 @@ path-platform@~0.11.15: resolved "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz" integrity sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg== -path-scurry@^1.11.1, path-scurry@^1.6.1: - version "1.11.1" - resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"