@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
252 lines • 10.8 kB
JavaScript
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