@tunghm/relay-kit
Version:
SDK for Safe Smart Accounts with support for ERC-4337 and Relay
1,391 lines (1,369 loc) • 75.3 kB
JavaScript
// src/packs/gelato/GelatoRelayPack.ts
import {
GelatoRelay as GelatoNetworkRelay
} from "@gelatonetwork/relay-sdk";
import {
estimateTxBaseGas,
estimateSafeTxGas,
estimateSafeDeploymentGas,
createERC20TokenTransferTransaction,
isGasTokenCompatibleWithHandlePayment
} from "@safe-global/protocol-kit";
// src/RelayKitBasePack.ts
var RelayKitBasePack = class {
/**
* Creates a new RelayKitBasePack instance.
* The packs implemented using our SDK should extend this class and therefore provide a Safe SDK instance
* @param {Safe} protocolKit - The Safe SDK instance
*/
constructor(protocolKit) {
this.protocolKit = protocolKit;
}
};
// src/constants.ts
var GELATO_NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
var GELATO_FEE_COLLECTOR = "0x3AC05161b76a35c1c28dC99Aa01BEd7B24cEA3bf";
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
var GELATO_GAS_EXECUTION_OVERHEAD = 15e4;
var GELATO_TRANSFER_GAS_COST = 15e3;
// src/packs/gelato/GelatoRelayPack.ts
var GelatoRelayPack = class extends RelayKitBasePack {
#gelatoRelay;
#apiKey;
constructor({ apiKey, protocolKit }) {
super(protocolKit);
this.#gelatoRelay = new GelatoNetworkRelay();
this.#apiKey = apiKey;
}
_getFeeToken(gasToken) {
return !gasToken || gasToken === ZERO_ADDRESS ? GELATO_NATIVE_TOKEN_ADDRESS : gasToken;
}
getFeeCollector() {
return GELATO_FEE_COLLECTOR;
}
async getEstimateFee(propsOrChainId, inputGasLimit, inputGasToken) {
let chainId;
let gasLimit;
let gasToken;
if (typeof propsOrChainId === "object") {
;
({ chainId, gasLimit, gasToken } = propsOrChainId);
} else {
chainId = propsOrChainId;
gasLimit = inputGasLimit;
gasToken = inputGasToken;
}
const feeToken = this._getFeeToken(gasToken);
const estimation = await this.#gelatoRelay.getEstimatedFee(
chainId,
feeToken,
BigInt(gasLimit),
false
);
return estimation.toString();
}
async getTaskStatus(taskId) {
return this.#gelatoRelay.getTaskStatus(taskId);
}
/**
* Creates a payment transaction to Gelato
*
* @private
* @async
* @function
* @param {string} gas - The gas amount for the payment.
* @param {MetaTransactionOptions} options - Options for the meta transaction.
* @returns {Promise<Transaction>} Promise object representing the created payment transaction.
*
*/
async createPaymentToGelato(gas, options) {
const chainId = await this.protocolKit.getChainId();
const gelatoAddress = this.getFeeCollector();
const gasToken = options.gasToken ?? ZERO_ADDRESS;
const paymentToGelato = await this.getEstimateFee({ chainId, gasLimit: gas, gasToken });
const transferToGelato = createERC20TokenTransferTransaction(
gasToken,
gelatoAddress,
paymentToGelato
);
return transferToGelato;
}
/**
* @deprecated Use createTransaction instead
*/
async createRelayedTransaction({
transactions,
onlyCalls = false,
options = {}
}) {
return this.createTransaction({ transactions, onlyCalls, options });
}
/**
* Creates a Safe transaction designed to be executed using the Gelato Relayer.
*
* @param {GelatoCreateTransactionProps} options - Options for Gelato.
* @param {MetaTransactionData[]} [options.transactions] - The transactions batch.
* @param {boolean} [options.onlyCalls=false] - If true, MultiSendCallOnly contract should be used. Remember to not use delegate calls in the batch.
* @param {MetaTransactionOptions} [options.options={}] - Gas Options for the transaction batch.
* @returns {Promise<SafeTransaction>} Returns a Promise that resolves with a SafeTransaction object.
*/
async createTransaction({
transactions,
onlyCalls = false,
options = {}
}) {
const { isSponsored = false } = options;
if (isSponsored) {
const nonce = await this.protocolKit.getNonce();
const sponsoredTransaction = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
nonce
}
});
return sponsoredTransaction;
}
const gasToken = options.gasToken ?? ZERO_ADDRESS;
const isGasTokenCompatible = await isGasTokenCompatibleWithHandlePayment(
gasToken,
this.protocolKit
);
if (!isGasTokenCompatible) {
return this.createTransactionWithTransfer({ transactions, onlyCalls, options });
}
return this.createTransactionWithHandlePayment({ transactions, onlyCalls, options });
}
/**
* Creates a Safe transaction designed to be executed using the Gelato Relayer and
* uses the handlePayment function defined in the Safe contract to pay the fees
* to the Gelato relayer.
*
* @async
* @function createTransactionWithHandlePayment
* @param {GelatoCreateTransactionProps} options - Options for Gelato.
* @param {MetaTransactionData[]} [options.transactions] - The transactions batch.
* @param {boolean} [options.onlyCalls=false] - If true, MultiSendCallOnly contract should be used. Remember to not use delegate calls in the batch.
* @param {MetaTransactionOptions} [options.options={}] - Gas Options for the transaction batch.
* @returns {Promise<SafeTransaction>} Returns a promise that resolves to the created SafeTransaction.
* @private
*/
async createTransactionWithHandlePayment({
transactions,
onlyCalls = false,
options = {}
}) {
const { gasLimit } = options;
const nonce = await this.protocolKit.getNonce();
const transactionToEstimateGas = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
nonce
}
});
const gasPrice = "1";
const safeTxGas = await estimateSafeTxGas(this.protocolKit, transactionToEstimateGas);
const gasToken = options.gasToken ?? ZERO_ADDRESS;
const refundReceiver = this.getFeeCollector();
const chainId = await this.protocolKit.getChainId();
if (gasLimit) {
const paymentToGelato2 = await this.getEstimateFee({ chainId, gasLimit, gasToken });
const syncTransaction2 = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
baseGas: paymentToGelato2,
gasPrice,
safeTxGas,
gasToken,
refundReceiver,
nonce
}
});
return syncTransaction2;
}
const baseGas = await estimateTxBaseGas(this.protocolKit, transactionToEstimateGas);
const safeDeploymentGasCost = await estimateSafeDeploymentGas(this.protocolKit);
const totalGas = Number(baseGas) + // baseGas
Number(safeTxGas) + // safeTxGas
Number(safeDeploymentGasCost) + // Safe deploymet gas cost if it is required
GELATO_GAS_EXECUTION_OVERHEAD;
const paymentToGelato = await this.getEstimateFee({
chainId,
gasLimit: String(totalGas),
gasToken
});
const syncTransaction = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
baseGas: paymentToGelato,
// payment to Gelato
gasPrice,
safeTxGas,
gasToken,
refundReceiver,
nonce
}
});
return syncTransaction;
}
/**
* Creates a Safe transaction designed to be executed using the Gelato Relayer and
* uses a separate ERC20 transfer to pay the fees to the Gelato relayer.
*
* @async
* @function createTransactionWithTransfer
* @param {GelatoCreateTransactionProps} options - Options for Gelato.
* @param {MetaTransactionData[]} [options.transactions] - The transactions batch.
* @param {boolean} [options.onlyCalls=false] - If true, MultiSendCallOnly contract should be used. Remember to not use delegate calls in the batch.
* @param {MetaTransactionOptions} [options.options={}] - Gas Options for the transaction batch.
* @returns {Promise<SafeTransaction>} Returns a promise that resolves to the created SafeTransaction.
* @private
*/
async createTransactionWithTransfer({
transactions,
onlyCalls = false,
options = {}
}) {
const { gasLimit } = options;
const nonce = await this.protocolKit.getNonce();
const gasToken = options.gasToken ?? ZERO_ADDRESS;
if (gasLimit) {
const transferToGelato2 = await this.createPaymentToGelato(gasLimit, options);
const syncTransaction2 = await this.protocolKit.createTransaction({
transactions: [...transactions, transferToGelato2],
onlyCalls,
options: {
nonce,
gasToken
}
});
return syncTransaction2;
}
const transactionToEstimateGas = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
nonce
}
});
const safeTxGas = await estimateSafeTxGas(this.protocolKit, transactionToEstimateGas);
const baseGas = await estimateTxBaseGas(this.protocolKit, transactionToEstimateGas);
const safeDeploymentGasCost = await estimateSafeDeploymentGas(this.protocolKit);
const totalGas = Number(baseGas) + // baseGas
Number(safeTxGas) + // safeTxGas without Gelato payment transfer
Number(safeDeploymentGasCost) + // Safe deploymet gas cost if it is required
GELATO_TRANSFER_GAS_COST + // Gelato payment transfer
GELATO_GAS_EXECUTION_OVERHEAD;
const transferToGelato = await this.createPaymentToGelato(String(totalGas), options);
const syncTransaction = await this.protocolKit.createTransaction({
transactions: [...transactions, transferToGelato],
onlyCalls,
options: {
nonce,
gasToken
}
});
return syncTransaction;
}
async sendSponsorTransaction(target, encodedTransaction, chainId) {
if (!this.#apiKey) {
throw new Error("API key not defined");
}
const request = {
chainId,
target,
data: encodedTransaction
};
const response = await this.#gelatoRelay.sponsoredCall(request, this.#apiKey);
return response;
}
async sendSyncTransaction(target, encodedTransaction, chainId, options) {
const { gasLimit, gasToken } = options;
const feeToken = this._getFeeToken(gasToken);
const request = {
chainId,
target,
data: encodedTransaction,
feeToken,
isRelayContext: false
};
const relayRequestOptions = {
gasLimit: gasLimit ? BigInt(gasLimit) : void 0
};
const response = await this.#gelatoRelay.callWithSyncFee(request, relayRequestOptions);
return response;
}
async relayTransaction({
target,
encodedTransaction,
chainId,
options = {}
}) {
const response = options.isSponsored ? this.sendSponsorTransaction(target, encodedTransaction, chainId) : this.sendSyncTransaction(target, encodedTransaction, chainId, options);
return response;
}
/**
* @deprecated Use executeTransaction instead
*/
async executeRelayTransaction(safeTransaction, options) {
return this.executeTransaction({ executable: safeTransaction, options });
}
/**
* Sends the Safe transaction to the Gelato Relayer for execution.
* If the Safe is not deployed, it creates a batch of transactions including the Safe deployment transaction.
*
* @param {GelatoExecuteTransactionProps} props - Execution props
* @param {SafeTransaction} props.executable - The Safe transaction to be executed.
* @param {MetaTransactionOptions} props.options - Options for the transaction.
* @returns {Promise<RelayResponse>} Returns a Promise that resolves with a RelayResponse object.
*/
async executeTransaction({
executable: safeTransaction,
options
}) {
const isSafeDeployed = await this.protocolKit.isSafeDeployed();
const chainId = await this.protocolKit.getChainId();
const safeAddress = await this.protocolKit.getAddress();
const safeTransactionEncodedData = await this.protocolKit.getEncodedTransaction(safeTransaction);
const gasToken = options?.gasToken || safeTransaction.data.gasToken;
if (isSafeDeployed) {
const relayTransaction2 = {
target: safeAddress,
encodedTransaction: safeTransactionEncodedData,
chainId,
options: {
...options,
gasToken
}
};
return this.relayTransaction(relayTransaction2);
}
const safeDeploymentBatch = await this.protocolKit.wrapSafeTransactionIntoDeploymentBatch(safeTransaction);
const relayTransaction = {
target: safeDeploymentBatch.to,
// multiSend Contract address
encodedTransaction: safeDeploymentBatch.data,
chainId,
options: {
...options,
gasToken
}
};
return this.relayTransaction(relayTransaction);
}
};
// src/packs/safe-4337/Safe4337Pack.ts
import { getAddress as getAddress2, isAddress as isAddress2, toHex as toHex9 } from "viem";
import semverSatisfies from "semver/functions/satisfies.js";
import Safe, {
EthSafeSignature as EthSafeSignature2,
encodeMultiSendData as encodeMultiSendData2,
getMultiSendContract,
SafeProvider,
generateOnChainIdentifier
} from "@safe-global/protocol-kit";
import {
OperationType as OperationType3,
SigningMethod
} from "@safe-global/types-kit";
import {
getSafeModuleSetupDeployment,
getSafe4337ModuleDeployment,
getSafeWebAuthnShareSignerDeployment
} from "@safe-global/safe-modules-deployments";
import { encodeFunctionData as encodeFunctionData3, zeroAddress, concat as concat2 } from "viem";
// src/packs/safe-4337/BaseSafeOperation.ts
import { encodePacked, hashTypedData } from "viem";
import { buildSignatureBytes } from "@safe-global/protocol-kit";
var BaseSafeOperation = class {
constructor(userOperation, options) {
this.signatures = /* @__PURE__ */ new Map();
this.userOperation = userOperation;
this.options = options;
}
/**
* Helper to apply percentage increase to a bigint value.
* @param value - The original value
* @param percentage - The percentage to add (e.g., 50 means +50%)
* @returns The adjusted value
*/
applyPercentageIncrease(value, percentage) {
if (percentage <= 0) return value;
return value + value * BigInt(Math.floor(percentage)) / 100n;
}
getSignature(signer) {
return this.signatures.get(signer.toLowerCase());
}
addSignature(signature) {
this.signatures.set(signature.signer.toLowerCase(), signature);
}
encodedSignatures() {
return buildSignatureBytes(Array.from(this.signatures.values()));
}
getUserOperation() {
return {
...this.userOperation,
signature: encodePacked(
["uint48", "uint48", "bytes"],
[
this.options.validAfter || 0,
this.options.validUntil || 0,
this.encodedSignatures()
]
)
};
}
getHash() {
return hashTypedData({
domain: {
chainId: Number(this.options.chainId),
verifyingContract: this.options.moduleAddress
},
types: this.getEIP712Type(),
primaryType: "SafeOp",
message: this.getSafeOperation()
});
}
};
var BaseSafeOperation_default = BaseSafeOperation;
// src/packs/safe-4337/constants.ts
import { parseAbi } from "viem";
var DEFAULT_SAFE_VERSION = "1.4.1";
var DEFAULT_SAFE_MODULES_VERSION = "0.2.0";
var EIP712_SAFE_OPERATION_TYPE_V06 = {
SafeOp: [
{ type: "address", name: "safe" },
{ type: "uint256", name: "nonce" },
{ type: "bytes", name: "initCode" },
{ type: "bytes", name: "callData" },
{ type: "uint256", name: "callGasLimit" },
{ type: "uint256", name: "verificationGasLimit" },
{ type: "uint256", name: "preVerificationGas" },
{ type: "uint256", name: "maxFeePerGas" },
{ type: "uint256", name: "maxPriorityFeePerGas" },
{ type: "bytes", name: "paymasterAndData" },
{ type: "uint48", name: "validAfter" },
{ type: "uint48", name: "validUntil" },
{ type: "address", name: "entryPoint" }
]
};
var EIP712_SAFE_OPERATION_TYPE_V07 = {
SafeOp: [
{ type: "address", name: "safe" },
{ type: "uint256", name: "nonce" },
{ type: "bytes", name: "initCode" },
{ type: "bytes", name: "callData" },
{ type: "uint128", name: "verificationGasLimit" },
{ type: "uint128", name: "callGasLimit" },
{ type: "uint256", name: "preVerificationGas" },
{ type: "uint128", name: "maxPriorityFeePerGas" },
{ type: "uint128", name: "maxFeePerGas" },
{ type: "bytes", name: "paymasterAndData" },
{ type: "uint48", name: "validAfter" },
{ type: "uint48", name: "validUntil" },
{ type: "address", name: "entryPoint" }
]
};
var ABI = parseAbi([
"function enableModules(address[])",
"function multiSend(bytes memory transactions) public payable",
"function executeUserOp(address to, uint256 value, bytes data, uint8 operation)",
"function approve(address _spender, uint256 _value)",
"function configure((uint256 x, uint256 y, uint176 verifiers) signer)"
]);
var ENTRYPOINT_ABI = [
{
inputs: [
{ name: "sender", type: "address" },
{ name: "key", type: "uint192" }
],
name: "getNonce",
outputs: [{ name: "nonce", type: "uint256" }],
stateMutability: "view",
type: "function"
}
];
var ENTRYPOINT_ADDRESS_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
var ENTRYPOINT_ADDRESS_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
// src/packs/safe-4337/SafeOperationV06.ts
var SafeOperationV06 = class extends BaseSafeOperation_default {
constructor(userOperation, options) {
super(userOperation, options);
}
addEstimations(estimations) {
this.userOperation.maxFeePerGas = BigInt(
estimations.maxFeePerGas || this.userOperation.maxFeePerGas
);
this.userOperation.maxPriorityFeePerGas = BigInt(
estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas
);
this.userOperation.verificationGasLimit = BigInt(
estimations.verificationGasLimit || this.userOperation.verificationGasLimit
);
this.userOperation.preVerificationGas = BigInt(
estimations.preVerificationGas || this.userOperation.preVerificationGas
);
this.userOperation.callGasLimit = BigInt(
estimations.callGasLimit || this.userOperation.callGasLimit
);
this.userOperation.paymasterAndData = estimations.paymasterAndData || this.userOperation.paymasterAndData;
}
adjustEstimations(adjustments) {
if (adjustments.maxFeePerGas) {
this.userOperation.maxFeePerGas = this.applyPercentageIncrease(
this.userOperation.maxFeePerGas,
adjustments.maxFeePerGas
);
}
if (adjustments.maxPriorityFeePerGas) {
this.userOperation.maxPriorityFeePerGas = this.applyPercentageIncrease(
this.userOperation.maxPriorityFeePerGas,
adjustments.maxPriorityFeePerGas
);
}
if (adjustments.verificationGasLimit) {
this.userOperation.verificationGasLimit = this.applyPercentageIncrease(
this.userOperation.verificationGasLimit,
adjustments.verificationGasLimit
);
}
if (adjustments.preVerificationGas) {
this.userOperation.preVerificationGas = this.applyPercentageIncrease(
this.userOperation.preVerificationGas,
adjustments.preVerificationGas
);
}
if (adjustments.callGasLimit) {
this.userOperation.callGasLimit = this.applyPercentageIncrease(
this.userOperation.callGasLimit,
adjustments.callGasLimit
);
}
}
getSafeOperation() {
return {
safe: this.userOperation.sender,
nonce: this.userOperation.nonce,
initCode: this.userOperation.initCode,
callData: this.userOperation.callData,
callGasLimit: this.userOperation.callGasLimit,
verificationGasLimit: this.userOperation.verificationGasLimit,
preVerificationGas: this.userOperation.preVerificationGas,
maxFeePerGas: this.userOperation.maxFeePerGas,
maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas,
paymasterAndData: this.userOperation.paymasterAndData,
validAfter: this.options.validAfter || 0,
validUntil: this.options.validUntil || 0,
entryPoint: this.options.entryPoint
};
}
getEIP712Type() {
return EIP712_SAFE_OPERATION_TYPE_V06;
}
};
var SafeOperationV06_default = SafeOperationV06;
// src/packs/safe-4337/SafeOperationV07.ts
import { concat, isAddress, pad, toHex } from "viem";
var SafeOperationV07 = class extends BaseSafeOperation_default {
constructor(userOperation, options) {
super(userOperation, options);
}
addEstimations(estimations) {
this.userOperation.maxFeePerGas = BigInt(
estimations.maxFeePerGas || this.userOperation.maxFeePerGas
);
this.userOperation.maxPriorityFeePerGas = BigInt(
estimations.maxPriorityFeePerGas || this.userOperation.maxPriorityFeePerGas
);
this.userOperation.verificationGasLimit = BigInt(
estimations.verificationGasLimit || this.userOperation.verificationGasLimit
);
this.userOperation.preVerificationGas = BigInt(
estimations.preVerificationGas || this.userOperation.preVerificationGas
);
this.userOperation.callGasLimit = BigInt(
estimations.callGasLimit || this.userOperation.callGasLimit
);
this.userOperation.paymasterPostOpGasLimit = estimations.paymasterPostOpGasLimit ? BigInt(estimations.paymasterPostOpGasLimit) : this.userOperation.paymasterPostOpGasLimit;
this.userOperation.paymasterVerificationGasLimit = estimations.paymasterVerificationGasLimit ? BigInt(estimations.paymasterVerificationGasLimit) : this.userOperation.paymasterVerificationGasLimit;
this.userOperation.paymaster = estimations.paymaster || this.userOperation.paymaster;
this.userOperation.paymasterData = estimations.paymasterData || this.userOperation.paymasterData;
}
adjustEstimations(adjustments) {
if (adjustments.maxFeePerGas) {
this.userOperation.maxFeePerGas = this.applyPercentageIncrease(
this.userOperation.maxFeePerGas,
adjustments.maxFeePerGas
);
}
if (adjustments.maxPriorityFeePerGas) {
this.userOperation.maxPriorityFeePerGas = this.applyPercentageIncrease(
this.userOperation.maxPriorityFeePerGas,
adjustments.maxPriorityFeePerGas
);
}
if (adjustments.verificationGasLimit) {
this.userOperation.verificationGasLimit = this.applyPercentageIncrease(
this.userOperation.verificationGasLimit,
adjustments.verificationGasLimit
);
}
if (adjustments.preVerificationGas) {
this.userOperation.preVerificationGas = this.applyPercentageIncrease(
this.userOperation.preVerificationGas,
adjustments.preVerificationGas
);
}
if (adjustments.callGasLimit) {
this.userOperation.callGasLimit = this.applyPercentageIncrease(
this.userOperation.callGasLimit,
adjustments.callGasLimit
);
}
if (adjustments.paymasterVerificationGasLimit && this.userOperation.paymasterVerificationGasLimit) {
this.userOperation.paymasterVerificationGasLimit = this.applyPercentageIncrease(
this.userOperation.paymasterVerificationGasLimit,
adjustments.paymasterVerificationGasLimit
);
}
if (adjustments.paymasterPostOpGasLimit && this.userOperation.paymasterPostOpGasLimit) {
this.userOperation.paymasterPostOpGasLimit = this.applyPercentageIncrease(
this.userOperation.paymasterPostOpGasLimit,
adjustments.paymasterPostOpGasLimit
);
}
}
getSafeOperation() {
const initCode = this.userOperation.factory ? concat([
this.userOperation.factory,
this.userOperation.factoryData || "0x"
]) : "0x";
const paymasterAndData = isAddress(this.userOperation.paymaster || "") ? concat([
this.userOperation.paymaster,
pad(toHex(this.userOperation.paymasterVerificationGasLimit || 0n), {
size: 16
}),
pad(toHex(this.userOperation.paymasterPostOpGasLimit || 0n), {
size: 16
}),
this.userOperation.paymasterData || "0x"
]) : "0x";
return {
safe: this.userOperation.sender,
nonce: this.userOperation.nonce,
initCode,
callData: this.userOperation.callData,
callGasLimit: this.userOperation.callGasLimit,
verificationGasLimit: this.userOperation.verificationGasLimit,
preVerificationGas: this.userOperation.preVerificationGas,
maxFeePerGas: this.userOperation.maxFeePerGas,
maxPriorityFeePerGas: this.userOperation.maxPriorityFeePerGas,
paymasterAndData,
validAfter: this.options.validAfter || 0,
validUntil: this.options.validUntil || 0,
entryPoint: this.options.entryPoint
};
}
getEIP712Type() {
return EIP712_SAFE_OPERATION_TYPE_V07;
}
};
var SafeOperationV07_default = SafeOperationV07;
// src/packs/safe-4337/utils/index.ts
import { createPublicClient, encodeFunctionData as encodeFunctionData2, http, rpcSchema } from "viem";
import { OperationType as OperationType2 } from "@safe-global/types-kit";
import { encodeMultiSendData } from "@safe-global/protocol-kit";
// src/packs/safe-4337/utils/entrypoint.ts
var EQ_0_2_0 = "0.2.0";
var EQ_OR_GT_0_3_0 = ">=0.3.0";
function sameString(str1, str2) {
return str1.toLowerCase() === str2.toLowerCase();
}
function entryPointToSafeModules(entryPoint) {
const moduleVersionToEntryPoint = {
[ENTRYPOINT_ADDRESS_V06]: EQ_0_2_0,
[ENTRYPOINT_ADDRESS_V07]: EQ_OR_GT_0_3_0
};
return moduleVersionToEntryPoint[entryPoint];
}
function isEntryPointV6(address) {
return sameString(address, ENTRYPOINT_ADDRESS_V06);
}
function isEntryPointV7(address) {
return sameString(address, ENTRYPOINT_ADDRESS_V07);
}
async function getSafeNonceFromEntrypoint(protocolKit, safeAddress, entryPointAddress) {
const safeProvider = protocolKit.getSafeProvider();
const newNonce = await safeProvider.readContract({
address: entryPointAddress || "0x",
abi: ENTRYPOINT_ABI,
functionName: "getNonce",
args: [safeAddress, 0n]
});
return newNonce;
}
// src/packs/safe-4337/utils/signing.ts
import { encodePacked as encodePacked2, toHex as toHex2 } from "viem";
import { EthSafeSignature, buildSignatureBytes as buildSignatureBytes2 } from "@safe-global/protocol-kit";
var DUMMY_CLIENT_DATA_FIELDS = [
`"origin":"https://safe.global"`,
`"padding":"This pads the clientDataJSON so that we can leave room for additional implementation specific fields for a more accurate 'preVerificationGas' estimate."`
].join(",");
var DUMMY_AUTHENTICATOR_DATA = new Uint8Array(37);
DUMMY_AUTHENTICATOR_DATA.fill(254);
DUMMY_AUTHENTICATOR_DATA[32] = 4;
function getDummySignature(signer, threshold) {
const signatures = [];
for (let i = 0; i < threshold; i++) {
const isContractSignature = true;
const passkeySignature = getSignatureBytes({
authenticatorData: DUMMY_AUTHENTICATOR_DATA,
clientDataFields: DUMMY_CLIENT_DATA_FIELDS,
r: BigInt(`0x${"ec".repeat(32)}`),
s: BigInt(`0x${"d5a".repeat(21)}f`)
});
signatures.push(new EthSafeSignature(signer, passkeySignature, isContractSignature));
}
return encodePacked2(["uint48", "uint48", "bytes"], [0, 0, buildSignatureBytes2(signatures)]);
}
function getSignatureBytes({
authenticatorData,
clientDataFields,
r,
s
}) {
const encodeUint256 = (x) => x.toString(16).padStart(64, "0");
const byteSize = (data) => 32 * (Math.ceil(data.length / 32) + 1);
const encodeBytes = (data) => `${encodeUint256(data.length)}${toHex2(data).slice(2)}`.padEnd(byteSize(data) * 2, "0");
const authenticatorDataOffset = 32 * 4;
const clientDataFieldsOffset = authenticatorDataOffset + byteSize(authenticatorData);
return "0x" + encodeUint256(authenticatorDataOffset) + encodeUint256(clientDataFieldsOffset) + encodeUint256(r) + encodeUint256(s) + encodeBytes(authenticatorData) + encodeBytes(new TextEncoder().encode(clientDataFields));
}
// src/packs/safe-4337/utils/userOperations.ts
import { encodeFunctionData, getAddress, hexToBytes, sliceHex, toHex as toHex3 } from "viem";
import {
OperationType
} from "@safe-global/types-kit";
function encodeExecuteUserOpCallData(transaction) {
return encodeFunctionData({
abi: ABI,
functionName: "executeUserOp",
args: [
transaction.to,
BigInt(transaction.value),
transaction.data,
transaction.operation || OperationType.Call
]
});
}
async function getCallData(protocolKit, transactions, paymasterOptions, amountToApprove) {
if (amountToApprove) {
const approveToPaymasterTransaction = {
to: paymasterOptions.paymasterTokenAddress,
data: encodeFunctionData({
abi: ABI,
functionName: "approve",
args: [paymasterOptions.paymasterAddress, amountToApprove]
}),
value: "0",
operation: OperationType.Call
// Call for approve
};
transactions.push(approveToPaymasterTransaction);
}
const isBatch = transactions.length > 1;
const multiSendAddress = protocolKit.getMultiSendAddress();
const callData = isBatch ? encodeExecuteUserOpCallData({
to: multiSendAddress,
value: "0",
data: encodeMultiSendCallData(transactions),
operation: OperationType.DelegateCall
}) : encodeExecuteUserOpCallData(transactions[0]);
return callData;
}
function unpackInitCode(initCode) {
const initCodeBytes = hexToBytes(initCode);
return initCodeBytes.length > 0 ? {
factory: getAddress(sliceHex(initCode, 0, 20)),
factoryData: sliceHex(initCode, 20)
} : {};
}
async function createUserOperation(protocolKit, transactions, {
amountToApprove,
entryPoint,
paymasterOptions,
customNonce
}) {
const safeAddress = await protocolKit.getAddress();
const nonce = customNonce || await getSafeNonceFromEntrypoint(protocolKit, safeAddress, entryPoint);
const isSafeDeployed = await protocolKit.isSafeDeployed();
const paymasterAndData = paymasterOptions && "paymasterAddress" in paymasterOptions ? paymasterOptions.paymasterAddress : "0x";
const callData = await getCallData(
protocolKit,
transactions,
paymasterOptions,
amountToApprove
);
const initCode = isSafeDeployed ? "0x" : await protocolKit.getInitCode();
if (isEntryPointV6(entryPoint)) {
return {
sender: safeAddress,
nonce: nonce.toString(),
initCode,
callData,
callGasLimit: 0n,
verificationGasLimit: 0n,
preVerificationGas: 0n,
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
paymasterAndData,
signature: "0x"
};
}
return {
sender: safeAddress,
nonce: nonce.toString(),
...unpackInitCode(initCode),
callData,
callGasLimit: 0n,
verificationGasLimit: 0n,
preVerificationGas: 0n,
maxFeePerGas: 1n,
maxPriorityFeePerGas: 1n,
paymaster: paymasterAndData,
paymasterData: "0x",
paymasterVerificationGasLimit: void 0,
paymasterPostOpGasLimit: void 0,
signature: "0x"
};
}
function userOperationToHexValues(userOperation, entryPointAddress) {
const userOpV07 = userOperation;
const userOperationWithHexValues = {
...userOperation,
nonce: toHex3(BigInt(userOperation.nonce)),
callGasLimit: toHex3(userOperation.callGasLimit),
verificationGasLimit: toHex3(userOperation.verificationGasLimit),
preVerificationGas: toHex3(userOperation.preVerificationGas),
maxFeePerGas: toHex3(userOperation.maxFeePerGas),
maxPriorityFeePerGas: toHex3(userOperation.maxPriorityFeePerGas),
...isEntryPointV7(entryPointAddress) ? {
// Only include paymaster fields if paymaster is actually used (not empty "0x")
...userOpV07.paymaster && userOpV07.paymaster !== "0x" ? {
paymaster: userOpV07.paymaster,
paymasterData: userOpV07.paymasterData || "0x",
paymasterVerificationGasLimit: userOpV07.paymasterVerificationGasLimit ? toHex3(userOpV07.paymasterVerificationGasLimit) : void 0,
paymasterPostOpGasLimit: userOpV07.paymasterPostOpGasLimit ? toHex3(userOpV07.paymasterPostOpGasLimit) : void 0
} : {}
} : {}
};
if (isEntryPointV7(entryPointAddress) && (!userOpV07.paymaster || userOpV07.paymaster === "0x")) {
delete userOperationWithHexValues.paymaster;
delete userOperationWithHexValues.paymasterData;
delete userOperationWithHexValues.paymasterVerificationGasLimit;
delete userOperationWithHexValues.paymasterPostOpGasLimit;
}
return userOperationWithHexValues;
}
// src/packs/safe-4337/utils/getRelayKitVersion.ts
var getRelayKitVersion = () => "4.4.1";
// src/packs/safe-4337/utils/encodeNonce.ts
import { toHex as toHex4 } from "viem";
function encodeNonce(args) {
const key = BigInt(toHex4(args.key, { size: 24 }));
const sequence = BigInt(toHex4(args.sequence, { size: 8 }));
return (key << BigInt(64)) + sequence;
}
// src/packs/safe-4337/utils/index.ts
function createBundlerClient(bundlerUrl) {
const provider = createPublicClient({
transport: http(bundlerUrl),
rpcSchema: rpcSchema()
});
return provider;
}
function encodeMultiSendCallData(transactions) {
return encodeFunctionData2({
abi: ABI,
functionName: "multiSend",
args: [
encodeMultiSendData(
transactions.map((tx) => ({ ...tx, operation: tx.operation ?? OperationType2.Call }))
)
]
});
}
// src/packs/safe-4337/SafeOperationFactory.ts
var SafeOperationFactory = class {
/**
* Creates a new SafeOperation with proper validation
* @param userOperation - The base user operation
* @param options - Configuration options
* @returns Validated SafeOperation instance
*/
static createSafeOperation(userOperation, options) {
if (isEntryPointV6(options.entryPoint)) {
return new SafeOperationV06_default(userOperation, options);
}
return new SafeOperationV07_default(userOperation, options);
}
};
var SafeOperationFactory_default = SafeOperationFactory;
// src/packs/safe-4337/estimators/pimlico/PimlicoFeeEstimator.ts
import { toHex as toHex5 } from "viem";
var PimlicoFeeEstimator = class {
async preEstimateUserOperationGas({
bundlerUrl,
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
const bundlerClient = createBundlerClient(bundlerUrl);
const feeData = await this.#getUserOperationGasPrices(bundlerClient);
const chainId = await protocolKit.getChainId();
let paymasterStubData = {};
if (paymasterOptions) {
const paymasterClient = createBundlerClient(
paymasterOptions.paymasterUrl
);
const context = "paymasterTokenAddress" in paymasterOptions ? {
token: paymasterOptions.paymasterTokenAddress
} : void 0;
paymasterStubData = await paymasterClient.request({
method: "pm_getPaymasterStubData" /* GET_PAYMASTER_STUB_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex5(chainId),
context
]
});
}
return {
...feeData,
...paymasterStubData
};
}
async postEstimateUserOperationGas({
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
if (!paymasterOptions) return {};
const paymasterClient = createBundlerClient(
paymasterOptions.paymasterUrl
);
if (paymasterOptions.isSponsored) {
const params = [
userOperationToHexValues(userOperation, entryPoint),
entryPoint
];
if (paymasterOptions.sponsorshipPolicyId) {
params.push({
sponsorshipPolicyId: paymasterOptions.sponsorshipPolicyId
});
}
const sponsoredData = await paymasterClient.request({
method: "pm_sponsorUserOperation" /* SPONSOR_USER_OPERATION */,
params
});
return sponsoredData;
}
const chainId = await protocolKit.getChainId();
const erc20PaymasterData = await paymasterClient.request({
method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex5(chainId),
{ token: paymasterOptions.paymasterTokenAddress }
]
});
return erc20PaymasterData;
}
async #getUserOperationGasPrices(client) {
const feeData = await client.request({
method: "pimlico_getUserOperationGasPrice" /* GET_USER_OPERATION_GAS_PRICE */
});
const {
fast: { maxFeePerGas, maxPriorityFeePerGas }
} = feeData;
return {
maxFeePerGas,
maxPriorityFeePerGas
};
}
};
// src/packs/safe-4337/estimators/generic/GenericFeeEstimator.ts
import { createPublicClient as createPublicClient2, http as http2, toHex as toHex6 } from "viem";
var GenericFeeEstimator = class {
constructor(rpcUrl, overrides = {}) {
this.defaultVerificationGasLimitOverhead = overrides.defaultVerificationGasLimitOverhead ?? 35000n;
this.overrides = overrides;
this.rpcUrl = rpcUrl;
}
async preEstimateUserOperationGas({
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
let feeDataRes = {};
let paymasterStubDataRes = {};
if (paymasterOptions) {
const chainId = await protocolKit.getChainId();
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const context = "paymasterTokenAddress" in paymasterOptions ? {
token: paymasterOptions.paymasterTokenAddress
} : paymasterOptions.paymasterContext ?? {};
const [feeData, paymasterStubData] = await Promise.all([
this.#getUserOperationGasPrices(this.rpcUrl),
paymasterClient.request({
method: "pm_getPaymasterStubData" /* GET_PAYMASTER_STUB_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex6(chainId),
context
]
})
]);
feeDataRes = feeData;
paymasterStubDataRes = paymasterStubData;
} else {
const feeData = await this.#getUserOperationGasPrices(this.rpcUrl);
feeDataRes = feeData;
}
feeDataRes.callGasLimit = this.overrides.callGasLimit ?? feeDataRes.callGasLimit;
feeDataRes.verificationGasLimit = this.overrides.verificationGasLimit ?? feeDataRes.verificationGasLimit;
feeDataRes.preVerificationGas = this.overrides.preVerificationGas ?? feeDataRes.preVerificationGas;
feeDataRes.maxFeePerGas = this.overrides.maxFeePerGas ?? feeDataRes.maxFeePerGas;
feeDataRes.maxPriorityFeePerGas = this.overrides.maxPriorityFeePerGas ?? feeDataRes.maxPriorityFeePerGas;
const result = {
...feeDataRes,
...paymasterStubDataRes
};
if (result.verificationGasLimit != null) {
const threshold = await protocolKit.getThreshold();
result.verificationGasLimit = (BigInt(result.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
}
return result;
}
async postEstimateUserOperationGas({
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
if (protocolKit == null) {
throw new Error("Can't use GenericFeeEstimator if protocolKit is null.");
}
if (!paymasterOptions) return {};
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const chainId = await protocolKit.getChainId();
if (paymasterOptions.isSponsored) {
const params = [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex6(chainId)
];
if (paymasterOptions.paymasterContext) {
params.push(paymasterOptions.paymasterContext);
}
const sponsoredData = await paymasterClient.request({
method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
params
});
return sponsoredData;
}
const erc20PaymasterData = await paymasterClient.request({
method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex6(chainId),
{ token: paymasterOptions.paymasterTokenAddress }
]
});
if ("verificationGasLimit" in erc20PaymasterData && erc20PaymasterData.verificationGasLimit != null) {
const threshold = await protocolKit.getThreshold();
erc20PaymasterData.verificationGasLimit = (BigInt(erc20PaymasterData.verificationGasLimit) + BigInt(threshold) * this.defaultVerificationGasLimitOverhead).toString();
}
return erc20PaymasterData;
}
async #getUserOperationGasPrices(rpcUrl) {
const client = createPublicClient2({
transport: http2(rpcUrl)
});
const [block, maxPriorityFeePerGas] = await Promise.all([
client.getBlock({ blockTag: "latest" }),
client.estimateMaxPriorityFeePerGas()
]);
const baseFeePerGas = block.baseFeePerGas;
if (!baseFeePerGas) {
throw new Error("Base fee not available - probably not an EIP-1559 block.");
}
const maxFeePerGas = baseFeePerGas + maxPriorityFeePerGas;
return {
maxFeePerGas: BigInt(
Math.ceil(Number(maxFeePerGas) * (this.overrides.maxFeePerGasMultiplier ?? 1.5))
),
maxPriorityFeePerGas: BigInt(
Math.ceil(
Number(maxPriorityFeePerGas) * (this.overrides.maxPriorityFeePerGasMultiplier ?? 1.5)
)
)
};
}
};
// src/packs/safe-4337/estimators/biconomy/BiconomyFeeEstimator.ts
import { toHex as toHex7 } from "viem";
var BiconomyFeeEstimator = class {
/**
* Setup the userOperation before calling eth_estimateUserOperationGas
*
* This method:
* 1. Fetches gas prices from biconomy_getGasFeeValues
* 2. If paymaster enabled, fetches stub data from pm_getPaymasterStubData
* 3. Returns combined estimation data
*
* @param props - Estimation parameters
* @returns Gas estimation data including fees and paymaster stub data
*/
async preEstimateUserOperationGas({
bundlerUrl,
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
const bundlerClient = createBundlerClient(bundlerUrl);
const feeData = await this.#getGasFeeValues(bundlerClient);
const chainId = await protocolKit.getChainId();
let paymasterStubData = {};
if (paymasterOptions) {
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const context = "paymasterTokenAddress" in paymasterOptions ? { token: paymasterOptions.paymasterTokenAddress } : void 0;
paymasterStubData = await paymasterClient.request({
method: "pm_getPaymasterStubData" /* GET_PAYMASTER_STUB_DATA */,
// Standard method
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex7(chainId),
context
]
});
}
return {
...feeData,
...paymasterStubData
};
}
/**
* Adjust the userOperation values after calling eth_estimateUserOperationGas
*
* This method:
* 1. If sponsored mode: calls pm_getPaymasterData with sponsorship context
* 2. If ERC-20 mode: calls pm_getPaymasterData with token context
* 3. Returns paymaster data to be merged into userOperation
*
* Note: Unlike Pimlico which uses custom pm_sponsorUserOperation for sponsored mode,
* Biconomy uses the standard pm_getPaymasterData for both modes.
*
* @param props - Estimation parameters
* @returns Paymaster data including paymasterAndData/paymaster fields
*/
async postEstimateUserOperationGas({
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
if (!paymasterOptions) return {};
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const chainId = await protocolKit.getChainId();
const context = paymasterOptions.isSponsored ? paymasterOptions.sponsorshipPolicyId ? { sponsorshipPolicyId: paymasterOptions.sponsorshipPolicyId } : {} : { token: paymasterOptions.paymasterTokenAddress };
const paymasterData = await paymasterClient.request({
method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
// Standard method
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex7(chainId),
context
]
});
return paymasterData;
}
/**
* Fetch gas fee values from Biconomy bundler
*
* Biconomy returns 3 tiers (slow/standard/fast) like Pimlico.
* We use the 'fast' tier by default for optimal transaction speed.
*
* @param client - Bundler client with Biconomy custom RPC schema
* @returns Gas fee data (maxFeePerGas, maxPriorityFeePerGas) from 'fast' tier
* @private
*/
async #getGasFeeValues(client) {
const feeData = await client.request({
method: "biconomy_getGasFeeValues" /* GET_GAS_FEE_VALUES */
});
const {
fast: { maxFeePerGas, maxPriorityFeePerGas }
} = feeData;
return {
maxFeePerGas,
maxPriorityFeePerGas
};
}
};
// src/packs/safe-4337/estimators/gelato/GelatoFeeEstimator.ts
import { createPublicClient as createPublicClient3, http as http3, toHex as toHex8 } from "viem";
// src/packs/safe-4337/estimators/gelato/types.ts
var GELATO_URL_PATTERNS = ["api.gelato.digital"];
function isUsing1Balance(bundlerUrl) {
return bundlerUrl.includes("sponsorApiKey=");
}
// src/packs/safe-4337/estimators/gelato/GelatoFeeEstimator.ts
var DEFAULT_GAS_MULTIPLIERS = {
maxFeePerGasMultiplier: 1.5,
maxPriorityFeePerGasMultiplier: 1.5
};
var DEFAULT_INITIAL_GAS_LIMITS = {
verificationGasLimit: 500000n,
callGasLimit: 100000n,
preVerificationGas: 50000n
};
var GelatoFeeEstimator = class {
#gasMultipliers;
constructor(gasMultipliers) {
this.#gasMultipliers = {
...DEFAULT_GAS_MULTIPLIERS,
...gasMultipliers
};
}
/**
* Setup the userOperation before calling eth_estimateUserOperationGas
*
* This method:
* 1. Checks if using 1Balance sponsorship (gas fields = 0)
* 2. If not sponsored, calculates gas from standard RPC
* 3. If external paymaster provided, fetches stub data
*
* @param props - Estimation parameters
* @returns Gas estimation data including fees and paymaster stub data
*/
async preEstimateUserOperationGas({
bundlerUrl,
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
if (isUsing1Balance(bundlerUrl)) {
return {
maxFeePerGas: "0x0",
maxPriorityFeePerGas: "0x0"
};
}
const providerUrl = protocolKit.getSafeProvider().getExternalProvider().transport?.url;
if (!providerUrl) {
throw new Error("Cannot get provider URL from protocolKit for gas estimation");
}
const feeData = await this.#getGasFees(providerUrl);
const chainId = await protocolKit.getChainId();
let paymasterStubData = {};
if (paymasterOptions) {
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const context = "paymasterTokenAddress" in paymasterOptions ? { token: paymasterOptions.paymasterTokenAddress } : void 0;
paymasterStubData = await paymasterClient.request({
method: "pm_getPaymasterStubData" /* GET_PAYMASTER_STUB_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex8(chainId),
context
]
});
}
return {
...feeData,
...DEFAULT_INITIAL_GAS_LIMITS,
...paymasterStubData
};
}
/**
* Adjust the userOperation values after calling eth_estimateUserOperationGas
*
* This method:
* 1. If using 1Balance: no post-estimation needed (return empty)
* 2. If external paymaster: calls pm_getPaymasterData for final data
*
* @param props - Estimation parameters
* @returns Paymaster data to merge into userOperation
*/
async postEstimateUserOperationGas({
bundlerUrl,
userOperation,
entryPoint,
paymasterOptions,
protocolKit
}) {
if (isUsing1Balance(bundlerUrl)) {
return {};
}
if (!paymasterOptions) {
return {};
}
const paymasterClient = createBundlerClient(paymasterOptions.paymasterUrl);
const chainId = await protocolKit.getChainId();
const context = paymasterOptions.isSponsored ? paymasterOptions.sponsorshipPolicyId ? { sponsorshipPolicyId: paymasterOptions.sponsorshipPolicyId } : paymasterOptions.paymasterContext || {} : { token: paymasterOptions.paymasterTokenAddress };
const paymasterData = await paymasterClient.request({
method: "pm_getPaymasterData" /* GET_PAYMASTER_DATA */,
params: [
userOperationToHexValues(userOperation, entryPoint),
entryPoint,
toHex8(chainId),
context
]
});
return paymasterData;
}
/**
* Get gas fees using chain RPC provider (not bundler)
*
* Gelato doesn't have a custom gas price RPC like Pimlico/Biconomy.
* Instead, we use the chain provider (not bundler) to:
* - Get latest block baseFeePerGas
* - Estimate maxPriorityFeePerGas
* - Calculate: maxFeePerGas = baseFee + priorityFee
* - Apply multipliers for safety margin
*
* Note: Bundler URL only supports 4337-specific RPC methods (eth_sendUserOperation,
* eth_estimateUserOperationGas, etc.), NOT standard Ethereum RPC methods like
* eth_getBlockByNumber or eth_maxPriorityFeePerGas.
*
* @param rpcUrl - Chain RPC URL (from protocolKit provider)
* @returns Gas fee data (maxFeePerGas, maxPriorityFeePerGas)
* @private
*/
async #getGasFees(rpcUrl) {
const client = createPublicClient3({
transport: http3(rpcUrl)
});
const [block, maxPriorityFeePerGas] = await Promise.all([
client.getBlock({ blockTag: "latest" }),
client.estimateMaxPriorityFeePerGas()
]);
const baseFeePerGas = block.baseFeePerGas;
if (!baseFeePerGas) {
throw new Error("Base fee not available - probably not an EIP-1559 block.");
}
const rawMaxFee = baseFeePerGas + maxPriorityFee