UNPKG

chaingate

Version:

A complete TypeScript library for connecting to and making transactions on different blockchains

151 lines 6.84 kB
import { ethers, SigningKey } from 'ethers'; import { Transaction } from '../../Transaction'; import { EvmFee } from './EvmFee'; import { EvmBroadcastedTransaction } from './EvmBroadcastedTransaction'; import { NotEnoughFundsError } from '../../errors'; export class EvmTransaction extends Transaction { utils; privateKeyProvider; data; constructor(utils, fromAddress, toAddress, amount, data, privateKeyProvider) { super(fromAddress, toAddress, amount); this.utils = utils; this.data = data; this.privateKeyProvider = privateKeyProvider; } async broadcast(fee) { // Convert fee-level shortcut into concrete fee description when required if (typeof fee === 'string') { const suggested = this._suggestedFees ?? (await this.buildSuggestedFees()); fee = suggested[fee]; } if (!fee.enoughFunds || !fee.feeAmount) { throw new NotEnoughFundsError(); } const signer = new ethers.Wallet(new SigningKey((await this.privateKeyProvider()).raw)); const nonce = await this.utils.addressTransactionCount(this.fromAddress); const gasLimit = await this.estimateGasLimit(nonce); const txBase = this.createBaseTransaction(nonce, gasLimit); const tx = this.createTransactionWithFees(txBase, fee); const txSigned = await signer.signTransaction(tx); const broadcastedTx = await this.utils.broadcastTransaction(txSigned); return new EvmBroadcastedTransaction(broadcastedTx.transactionId); } createBaseTransaction(nonce, gasLimit) { return { from: this.fromAddress, to: this.toAddress, value: ethers.toBigInt(this.amount.minimalUnitAmount.toString()), data: this.data ?? '0x', nonce, gasLimit, chainId: this.utils.currencyInfo.chainId, }; } async estimateGasLimit(nonce) { try { const limit = await this.utils.estimateGas(this.fromAddress, this.toAddress, this.amount, nonce, this.data ?? '0x'); return BigInt(limit); } catch (ex) { if (this.isInsufficientAmountError(ex)) { throw new NotEnoughFundsError(); } throw ex; } } isInsufficientAmountError(ex) { return ex?.response?.data === 'Insufficient amount'; } createTransactionWithFees(txBase, fee) { if (fee.gasPrice) { return { ...txBase, gasPrice: ethers.toBigInt(fee.gasPrice.minimalUnitAmount.toString()), }; } if (fee.maxFeePerGas && fee.maxPriorityFeePerGas) { return { ...txBase, maxFeePerGas: ethers.toBigInt(fee.maxFeePerGas.minimalUnitAmount.toString()), maxPriorityFeePerGas: ethers.toBigInt(fee.maxPriorityFeePerGas.minimalUnitAmount.toString()), }; } throw new Error('Invalid fee structure provided'); } async buildSuggestedFees() { const addressBalance = (await this.utils.addressBalance(this.fromAddress)).confirmed; const feeRate = await this.utils.getFeeRate(); const estimatedGas = await this.getEstimatedGas(addressBalance); const fees = {}; this.processFeeLevels(feeRate, estimatedGas, addressBalance, fees); return fees; } async getEstimatedGas(addressBalance) { if (!addressBalance.baseAmount.gte(this.amount.baseAmount)) { return null; } try { const nonce = await this.utils.addressTransactionCount(this.fromAddress); return await this.utils.estimateGas(this.fromAddress, this.toAddress, this.amount, nonce, this.data ?? '0x'); } catch { return null; } } processFeeLevels(feeRate, estimatedGas, addressBalance, fees) { const levels = ['low', 'normal', 'high', 'maximum']; for (const level of levels) { const entry = feeRate[level]; const isLegacy = 'gasPrice' in entry; const { feeAmount, enoughFunds } = estimatedGas !== null ? this.calculateFeeAmount(entry, estimatedGas, addressBalance, isLegacy) : { feeAmount: null, enoughFunds: false }; fees[level] = this.createFeeInstance(entry, isLegacy, enoughFunds, feeAmount); } } calculateFeeAmount(entry, estimatedGas, addressBalance, isLegacy) { const gasPrice = isLegacy ? entry.gasPrice : entry.maxFeePerGas; const feeBase = this.utils.amount(gasPrice, this.utils.currencyInfo.symbol).baseAmount; const totalFeeBase = feeBase.mul(estimatedGas); const feeAmount = this.utils.amount(totalFeeBase, this.utils.currencyInfo.symbol); const enoughFunds = addressBalance.baseAmount.gte(this.amount.baseAmount.plus(feeAmount.baseAmount)); return { feeAmount, enoughFunds }; } createFeeInstance(entry, isLegacy, enoughFunds, feeAmount) { const feeConfig = isLegacy ? { gasPrice: this.utils.amount(entry.gasPrice, this.utils.currencyInfo.symbol), } : { maxFeePerGas: this.utils.amount(entry.maxFeePerGas, this.utils.currencyInfo.symbol), maxPriorityFeePerGas: this.utils.amount(entry.maxPriorityFeePerGas, this.utils.currencyInfo.symbol), }; return new EvmFee(feeConfig, true, enoughFunds, entry.confirmationTimeSecs, feeAmount); } async fee(fee) { const addressBalance = (await this.utils.addressBalance(this.fromAddress)).confirmed; const nonce = await this.utils.addressTransactionCount(this.fromAddress); try { const estimatedGas = await this.utils.estimateGas(this.fromAddress, this.toAddress, this.amount, nonce, this.data ?? '0x'); const { feeAmount, enoughFunds } = this.calculateFeeFromType(fee, estimatedGas, addressBalance); return new EvmFee(fee, true, enoughFunds, null, feeAmount); } catch (ex) { if (this.isInsufficientAmountError(ex)) { return new EvmFee(fee, true, false, null, null); } throw ex; } } calculateFeeFromType(fee, estimatedGas, addressBalance) { const isLegacy = 'gasPrice' in fee; const feeBase = isLegacy ? fee.gasPrice.baseAmount : fee.maxFeePerGas.baseAmount; const feeAmount = this.utils.amount(feeBase.mul(estimatedGas), this.utils.currencyInfo.symbol); const enoughFunds = addressBalance.baseAmount.gte(this.amount.baseAmount.plus(feeAmount.baseAmount)); return { feeAmount, enoughFunds }; } } //# sourceMappingURL=EvmTransaction.js.map