chaingate
Version:
Multi-chain cryptocurrency SDK for TypeScript — unified API for Bitcoin, Ethereum, Litecoin, Dogecoin, Bitcoin Cash, Polygon, Arbitrum, and any EVM-compatible chain. Create wallets, query balances, send transactions, and manage tokens and NFTs across UTXO
187 lines (186 loc) • 7.62 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EvmTransaction = void 0;
const decimal_js_1 = __importDefault(require("decimal.js"));
const evmTx_1 = require("../../utils/evmTx");
const evmGasFallback_1 = require("../../utils/evmGasFallback");
const networks_1 = require("../../ChainGate/networks");
const errors_1 = require("../../errors");
const BaseEvmTransaction_1 = require("./BaseEvmTransaction");
const BroadcastedEvmTransaction_1 = require("./BroadcastedEvmTransaction");
/** Converts Gwei string to wei bigint. */
function gweiToWei(gwei) {
const d = new decimal_js_1.default(gwei).mul(new decimal_js_1.default(10).pow(9));
return BigInt(d.toFixed(0));
}
/** Converts an API fee grade to our EvmRecommendedFee type, computing enoughFunds. */
function gradeToFee(grade, gasLimit, valueWei, balanceWei, gasEstimationFailed) {
const maxFeeGwei = grade.maxFeePerGasGwei ?? grade.gasPriceGwei ?? '0';
const tipGwei = grade.maxPriorityFeePerGasGwei ?? '0';
const maxFeePerGas = gweiToWei(maxFeeGwei);
const maxPriorityFeePerGas = gweiToWei(tipGwei);
const totalCost = valueWei + maxFeePerGas * gasLimit;
const enoughFunds = balanceWei >= totalCost;
const fee = {
maxFeePerGas,
maxPriorityFeePerGas,
estimatedConfirmationSecs: grade.confirmationTimeSecs,
enoughFunds,
};
if (!enoughFunds) {
fee.missingFunds = totalCost - balanceWei;
}
if (gasEstimationFailed) {
fee.gasEstimationFailed = true;
}
return fee;
}
/**
* An unsigned EVM transaction prepared by {@link EvmConnector.transfer}.
*
* @example
* ```ts
* const amount = cg.networks.ethereum.amount('0.1');
* const tx = await eth.transfer(amount, '0xRecipient...');
*
* // Inspect recommended fees
* const fees = tx.recommendedFees();
* console.log(fees.normal.maxFeePerGas);
*
* // Override with a specific tier
* tx.setFee(fees.high);
*
* // Or set a manual fee with optional gas limit override
* tx.setFee({ maxFeePerGas: 30_000_000_000n, maxPriorityFeePerGas: 2_000_000_000n, gasLimit: 50_000n });
*
* // Sign and broadcast
* const broadcasted = await tx.signAndBroadcast();
* console.log(broadcasted.transactionId);
*
* // Wait for confirmation
* const cancel = broadcasted.onConfirmed((details) => {
* console.log('Confirmed in block', details.blockHeight);
* });
*
* // Stop waiting at any time:
* cancel();
* ```
*/
class EvmTransaction extends BaseEvmTransaction_1.BaseEvmTransaction {
/** @internal */
constructor(params) {
super({
fromAddress: params.fromAddress,
toAddress: params.toAddress,
valueWei: params.valueWei,
data: params.data,
nonce: params.nonce,
gasLimit: params.gasLimit,
chainId: params.chainId,
balanceWei: params.balanceWei,
getPrivateKey: params.getPrivateKey,
initialFee: {
maxFeePerGas: params.feeRates.normal.maxFeePerGas,
maxPriorityFeePerGas: params.feeRates.normal.maxPriorityFeePerGas,
},
});
this.explorer = params.explorer;
this.feeRates = params.feeRates;
}
/**
* Returns all recommended fee tiers (low, normal, high, maximum).
*
* Each tier includes `maxFeePerGas`, `maxPriorityFeePerGas` (both in wei),
* and `estimatedConfirmationSecs`.
*/
recommendedFees() {
return this.feeRates;
}
/**
* Sets the fee for this transaction.
*
* Pass a tier object from {@link recommendedFees} or an object with manual
* `maxFeePerGas`, `maxPriorityFeePerGas` (in wei), and an optional
* `gasLimit` override.
*
* @throws {@link TransactionAlreadySentError} if the transaction has already been sent.
*/
setFee(fee) {
this.applyFee(fee);
}
signTransaction(privateKey, params) {
return (0, evmTx_1.signEip1559Transaction)(params, privateKey);
}
async broadcast(signedRaw) {
const { transactionId } = await this.explorer.broadcastTransaction(signedRaw);
return transactionId;
}
recordNonceUsed() {
this.explorer.global.evmNonceCache.recordUsed(Number(this.chainId), this.fromAddress, this.nonce);
}
buildBroadcasted(transactionId) {
return new BroadcastedEvmTransaction_1.BroadcastedEvmTransaction(transactionId, this.explorer);
}
/** @internal — used by EvmConnector.transfer to build the transaction. */
static async create(params) {
const { explorer, fromAddress, toAddress, valueWei, data = '0x', getPrivateKey } = params;
// Fetch the nonce first so we can pass it to estimateGas (the nonce can
// affect the estimate for contracts whose execution depends on account
// state). Gas estimation may fail when the sender lacks sufficient funds;
// in that case we fall back to the intrinsic gas for a simple transfer
// and flag every fee tier as insufficient.
const networkNonce = BigInt((await explorer.getNonce(fromAddress)).nonce);
const networkInfo = networks_1.NETWORKS_INFO[explorer.network];
if (!networkInfo.chainId) {
throw new errors_1.UnsupportedOperationError(`Network '${explorer.network}' does not have a configured chain ID.`);
}
const chainId = BigInt(networkInfo.chainId);
const cachedNonce = explorer.global.evmNonceCache.get(networkInfo.chainId, fromAddress);
const nonce = cachedNonce !== undefined && cachedNonce > networkNonce ? cachedNonce : networkNonce;
const [gasEstimateResult, feeRateResult, balanceResult] = await Promise.all([
explorer
.estimateGas({
addressFrom: fromAddress,
addressTo: toAddress,
nonce: nonce.toString(),
amount: valueWei.toString(),
data: data !== '0x' ? data : undefined,
})
.catch(() => null),
explorer.getFeeRate(),
explorer.getAddressBalance(fromAddress),
]);
const gasEstimationFailed = gasEstimateResult === null;
const gasLimit = gasEstimationFailed
? (0, evmGasFallback_1.fallbackGasFromCalldata)(data)
: BigInt(gasEstimateResult.estimatedGas);
const balanceWei = balanceResult.confirmed.min();
const feeRates = parseApiFeeTiers(feeRateResult, gasLimit, valueWei, balanceWei, gasEstimationFailed);
return new EvmTransaction({
explorer,
fromAddress,
toAddress,
valueWei,
data,
nonce,
gasLimit,
chainId,
balanceWei,
feeRates,
getPrivateKey,
});
}
}
exports.EvmTransaction = EvmTransaction;
/** Converts the fee rate response to our EvmRecommendedFees type. */
function parseApiFeeTiers(apiResponse, gasLimit, valueWei, balanceWei, gasEstimationFailed) {
return {
low: gradeToFee(apiResponse.low, gasLimit, valueWei, balanceWei, gasEstimationFailed),
normal: gradeToFee(apiResponse.normal, gasLimit, valueWei, balanceWei, gasEstimationFailed),
high: gradeToFee(apiResponse.high, gasLimit, valueWei, balanceWei, gasEstimationFailed),
maximum: gradeToFee(apiResponse.maximum, gasLimit, valueWei, balanceWei, gasEstimationFailed),
};
}