UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

252 lines 10.8 kB
import { encodeSecp256k1Pubkey } from '@cosmjs/amino'; import { wasmTypes } from '@cosmjs/cosmwasm-stargate'; import { toUtf8 } from '@cosmjs/encoding'; import { Uint53 } from '@cosmjs/math'; import { Registry } from '@cosmjs/proto-signing'; import { defaultRegistryTypes } from '@cosmjs/stargate'; import { MsgExecuteContract } from 'cosmjs-types/cosmwasm/wasm/v1/tx.js'; import { StargateClientCache, disconnectStargateClient, shouldCacheStargateClient, } from '@hyperlane-xyz/cosmos-sdk'; import { ProtocolType, assert, convertToProtocolAddress, } from '@hyperlane-xyz/utils'; import { ProviderType, } from './ProviderType.js'; const stargateClientCache = new StargateClientCache(32); export function clearCachedStargateClients() { stargateClientCache.clear(); } function getStargateClient(url) { return stargateClientCache.get(url); } export async function estimateTransactionFeeEthersV5({ transaction, provider, sender, }) { const gasUnits = await provider.estimateGas({ ...transaction, from: sender, }); return estimateTransactionFeeEthersV5ForGasUnits({ provider, gasUnits: BigInt(gasUnits.toString()), }); } // Separating out inner function to allow WarpCore to reuse logic export async function estimateTransactionFeeEthersV5ForGasUnits({ provider, gasUnits, }) { const feeData = await provider.getFeeData(); return computeEvmTxFee(gasUnits, feeData.gasPrice ? BigInt(feeData.gasPrice.toString()) : undefined, feeData.maxFeePerGas ? BigInt(feeData.maxFeePerGas.toString()) : undefined, feeData.maxPriorityFeePerGas ? BigInt(feeData.maxPriorityFeePerGas.toString()) : undefined); } export async function estimateTransactionFeeViem({ transaction, provider, sender, }) { const gasUnits = await provider.provider.estimateGas({ ...transaction.transaction, blockNumber: undefined, account: sender, }); // Cast to silence overly-protective type enforcement from viem here const feeData = await provider.provider.estimateFeesPerGas(); return computeEvmTxFee(gasUnits, feeData.gasPrice, feeData.maxFeePerGas, feeData.maxPriorityFeePerGas); } function computeEvmTxFee(gasUnits, gasPrice, maxFeePerGas, maxPriorityFeePerGas) { let estGasPrice; if (maxFeePerGas && maxPriorityFeePerGas) { estGasPrice = maxFeePerGas + maxPriorityFeePerGas; } else if (gasPrice) { estGasPrice = gasPrice; } else { throw new Error('Invalid fee data, neither 1559 nor legacy'); } return { gasUnits, gasPrice: estGasPrice, fee: gasUnits * estGasPrice, }; } export async function estimateTransactionFeeSolanaWeb3({ provider, transaction, }) { const connection = provider.provider; const { value } = await connection.simulateTransaction(transaction.transaction); assert(!value.err, `Solana gas estimation failed: ${JSON.stringify(value)}`); const gasUnits = BigInt(value.unitsConsumed || 0); const recentFees = await connection.getRecentPrioritizationFees(); // prioritizationFee is in micro-lamports per compute unit; divide by 1e6 to get lamports const microLamportsPerCu = BigInt(recentFees[0]?.prioritizationFee ?? 0); const fee = (gasUnits * microLamportsPerCu) / 1000000n; return { gasUnits, gasPrice: microLamportsPerCu, fee, }; } // This is based on a reverse-engineered version of the // SigningStargateClient's simulate function. It cannot be // used here because it requires access to the private key. // https://github.com/cosmos/cosmjs/issues/1568 export async function estimateTransactionFeeCosmJs({ transaction, provider, estimatedGasPrice, sender, senderPubKey, memo, }) { const stargateClient = await provider.provider; const message = transaction.transaction; const registry = new Registry([...defaultRegistryTypes, ...wasmTypes]); const encodedMsg = registry.encodeAsAny(message); const encodedPubkey = encodeSecp256k1Pubkey(Buffer.from(senderPubKey, 'hex')); const { sequence } = await stargateClient.getSequence(sender); const { gasInfo } = await stargateClient // @ts-ignore force access to protected method .forceGetQueryClient() .tx.simulate([encodedMsg], memo, encodedPubkey, sequence); assert(gasInfo, 'Gas estimation failed'); const gasUnits = Uint53.fromString(gasInfo.gasUsed.toString()).toNumber(); const gasPrice = parseFloat(estimatedGasPrice.toString()); return { gasUnits, gasPrice, fee: Math.floor(gasUnits * gasPrice), }; } export async function estimateTransactionFeeCosmJsWasm({ transaction, provider, estimatedGasPrice, sender, senderPubKey, memo, }) { const message = { typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: MsgExecuteContract.fromPartial({ sender, contract: transaction.transaction.contractAddress, msg: toUtf8(JSON.stringify(transaction.transaction.msg)), funds: [...(transaction.transaction.funds || [])], }), }; const wasmClient = await provider.provider; // @ts-ignore access a private field here to extract client URL const url = wasmClient.cometClient.client.url; const stargateClient = getStargateClient(url); try { return await estimateTransactionFeeCosmJs({ transaction: { type: ProviderType.CosmJs, transaction: message }, provider: { type: ProviderType.CosmJs, provider: stargateClient }, estimatedGasPrice, sender, senderPubKey, memo, }); } catch (error) { stargateClientCache.evict(url, stargateClient); throw error; } finally { if (!shouldCacheStargateClient(url)) { disconnectStargateClient(stargateClient); } else { stargateClientCache.release(stargateClient); } } } export async function estimateTransactionFeeCosmJsNative({ transaction, provider, estimatedGasPrice, senderAddress, senderPubKey, }) { const client = await provider.provider; return client.estimateTransactionFee({ transaction: transaction.transaction, estimatedGasPrice: estimatedGasPrice.toString(), senderAddress, senderPubKey, }); } // Starknet does not support gas estimation without starknet account // TODO: Figure out a way to inject starknet account export async function estimateTransactionFeeStarknet({ transaction: _transaction, provider: _provider, sender: _sender, }) { return { gasUnits: 0, gasPrice: 0, fee: 0 }; } export async function estimateTransactionFeeRadix({ transaction, provider, }) { return provider.provider.estimateTransactionFee({ transaction: transaction.transaction, }); } export async function estimateTransactionFeeAleo({ transaction, provider, }) { return provider.provider.estimateTransactionFee({ transaction: transaction.transaction, }); } export function estimateTransactionFee({ transaction, provider, chainMetadata, sender, senderPubKey, }) { if (transaction.type === ProviderType.EthersV5 && provider.type === ProviderType.EthersV5) { return estimateTransactionFeeEthersV5({ transaction: transaction.transaction, provider: provider.provider, sender, }); } else if (transaction.type === ProviderType.Viem && provider.type === ProviderType.Viem) { return estimateTransactionFeeViem({ transaction, provider, sender }); } else if (transaction.type === ProviderType.SolanaWeb3 && provider.type === ProviderType.SolanaWeb3) { return estimateTransactionFeeSolanaWeb3({ transaction, provider }); } else if (transaction.type === ProviderType.CosmJs && provider.type === ProviderType.CosmJs) { const { transactionOverrides } = chainMetadata; const estimatedGasPrice = transactionOverrides?.gasPrice; assert(estimatedGasPrice, 'gasPrice required for CosmJS gas estimation'); assert(senderPubKey, 'senderPubKey required for CosmJS gas estimation'); return estimateTransactionFeeCosmJs({ transaction, provider, estimatedGasPrice, sender, senderPubKey, }); } else if (transaction.type === ProviderType.CosmJsWasm && provider.type === ProviderType.CosmJsWasm) { const { transactionOverrides } = chainMetadata; const estimatedGasPrice = transactionOverrides?.gasPrice; assert(estimatedGasPrice, 'gasPrice required for CosmJS gas estimation'); assert(senderPubKey, 'senderPubKey required for CosmJS gas estimation'); return estimateTransactionFeeCosmJsWasm({ transaction, provider, estimatedGasPrice, sender, senderPubKey, }); } else if (transaction.type === ProviderType.CosmJsNative && provider.type === ProviderType.CosmJsNative) { const { transactionOverrides } = chainMetadata; const estimatedGasPrice = transactionOverrides?.gasPrice; assert(estimatedGasPrice, 'gasPrice required for CosmJS gas estimation'); assert(senderPubKey, 'senderPubKey required for CosmJS gas estimation'); return estimateTransactionFeeCosmJsNative({ transaction, provider, estimatedGasPrice, senderAddress: sender, senderPubKey, }); } else if (transaction.type === ProviderType.Starknet && provider.type === ProviderType.Starknet) { return estimateTransactionFeeStarknet({ transaction, provider, sender }); } else if (transaction.type === ProviderType.Radix && provider.type === ProviderType.Radix) { return estimateTransactionFeeRadix({ transaction, provider, }); } else if (transaction.type === ProviderType.Aleo && provider.type === ProviderType.Aleo) { return estimateTransactionFeeAleo({ transaction, provider, }); } else if (transaction.type === ProviderType.Tron && provider.type === ProviderType.Tron) { // Tron is EVM-compatible; its typed transaction/provider use EthersV5 underlying types sender = convertToProtocolAddress(sender, ProtocolType.Ethereum); return estimateTransactionFeeEthersV5({ transaction: transaction.transaction, provider: provider.provider, sender, }); } else { throw new Error(`Unsupported transaction type ${transaction.type} or provider type ${provider.type} for gas estimation`); } } //# sourceMappingURL=transactionFeeEstimators.js.map