UNPKG

@wormhole-foundation/sdk-evm

Version:

SDK for EVM chains, used in conjunction with @wormhole-foundation/sdk

136 lines 6.14 kB
import { PlatformContext, Wormhole, chainToPlatform, decimals, encoding, isNative, nativeChainIds, networkPlatformConfigs, getWalletBalances, } from '@wormhole-foundation/sdk-connect'; import { FetchRequest, JsonRpcProvider } from 'ethers'; import * as ethers_contracts from './ethers-contracts/index.js'; import { EvmAddress, EvmZeroAddress } from './address.js'; import { EvmChain } from './chain.js'; import { _platform } from './types.js'; /** * @category EVM */ export class EvmPlatform extends PlatformContext { static _platform = _platform; _providers = {}; constructor(network, _config) { super(network, _config ?? networkPlatformConfigs(network, EvmPlatform._platform)); } getRpc(chain) { const cachedProvider = this._providers[chain]; if (cachedProvider) { return cachedProvider; } if (chain in this.config && this.config[chain].rpc) { const chainConfig = this.config[chain]; let connection = chainConfig.rpc; if (chainConfig.httpHeaders) { connection = new FetchRequest(chainConfig.rpc); for (const [k, v] of Object.entries(chainConfig.httpHeaders)) connection.setHeader(k, v); } const provider = new JsonRpcProvider(connection, nativeChainIds.networkChainToNativeChainId.get(this.network, chain), { staticNetwork: true, }); this._providers[chain] = provider; return provider; } else { throw new Error('No configuration available for chain: ' + chain); } } getChain(chain, rpc) { if (chain in this.config) return new EvmChain(chain, this, rpc); throw new Error('No configuration available for chain: ' + chain); } static nativeTokenId(network, chain) { if (!EvmPlatform.isSupportedChain(chain)) throw new Error(`invalid chain for EVM: ${chain}`); return Wormhole.tokenId(chain, EvmZeroAddress); } static isNativeTokenId(network, chain, tokenId) { if (!EvmPlatform.isSupportedChain(chain)) return false; if (tokenId.chain !== chain) return false; return tokenId.address.toString() === EvmZeroAddress; } static isSupportedChain(chain) { const platform = chainToPlatform(chain); return platform === EvmPlatform._platform; } static async getDecimals(_network, _chain, rpc, token) { if (isNative(token)) return decimals.nativeDecimals(EvmPlatform._platform); const tokenContract = EvmPlatform.getTokenImplementation(rpc, new EvmAddress(token).toString()); return Number(await tokenContract.decimals()); } static async getBalance(_network, _chain, rpc, walletAddr, token) { if (isNative(token)) return rpc.getBalance(walletAddr); const tokenImpl = EvmPlatform.getTokenImplementation(rpc, new EvmAddress(token).toString()); return tokenImpl.balanceOf(walletAddr); } static async getBalances(network, chain, rpc, walletAddr, indexers) { const balances = await getWalletBalances(walletAddr, network, chain, indexers); balances['native'] ??= await rpc.getBalance(walletAddr); return balances; } // Chains with custom transaction types that ethers.js can't parse when // fetching full blocks (used by txRes.wait() for replacement detection). // For these chains, we use waitForTransaction() which polls // getTransactionReceipt() directly, bypassing block fetching. static CHAINS_WITH_CUSTOM_TX_TYPES = new Set([ 'Celo', 'Tempo', ]); static async sendWait(chain, rpc, stxns) { const txhashes = []; for (const stxn of stxns) { const txRes = await rpc.broadcastTransaction(stxn); txhashes.push(txRes.hash); if (EvmPlatform.CHAINS_WITH_CUSTOM_TX_TYPES.has(chain)) { // txRes.wait() fetches full blocks for replacement detection, which // ethers.js can't parse for chains with custom tx types (Celo, Tempo). // waitForTransaction() polls getTransactionReceipt() directly. const timeoutMs = 120_000; const receipt = await rpc.waitForTransaction(txRes.hash, 1, timeoutMs); if (receipt === null) throw new Error(`Transaction was not mined within ${timeoutMs / 1_000}s: ${txRes.hash}`); if (receipt.status !== 1) throw new Error(`Transaction reverted: ${txRes.hash}`); continue; } const txReceipt = await txRes.wait(); if (txReceipt === null) throw new Error('Received null TxReceipt'); } return txhashes; } static async getLatestBlock(rpc) { return await rpc.getBlockNumber(); } static async getLatestFinalizedBlock(rpc) { const block = await rpc.getBlock('finalized'); if (!block) throw new Error('Could not get finalized block'); return block?.number; } // Look up the Wormhole Canonical Network and Chain from the EVM chainId static chainFromChainId(eip155ChainId) { const networkChainPair = nativeChainIds.platformNativeChainIdToNetworkChain(EvmPlatform._platform, BigInt(eip155ChainId)); if (networkChainPair === undefined) throw new Error(`Unknown EVM chainId ${eip155ChainId}`); const [network, chain] = networkChainPair; return [network, chain]; } static async chainFromRpc(rpc) { const { chainId } = await rpc.getNetwork(); return EvmPlatform.chainFromChainId(encoding.bignum.encode(chainId, true)); } static getTokenImplementation(connection, address) { const ti = ethers_contracts.TokenImplementation__factory.connect(address, connection); if (!ti) throw new Error(`No token implementation available for: ${address}`); return ti; } } //# sourceMappingURL=platform.js.map