@tunghm/relay-kit
Version:
327 lines • 16.6 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _GelatoRelayPack_gelatoRelay, _GelatoRelayPack_apiKey;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GelatoRelayPack = void 0;
const relay_sdk_1 = require("@gelatonetwork/relay-sdk");
const protocol_kit_1 = require("@safe-global/protocol-kit");
const RelayKitBasePack_1 = require("../../RelayKitBasePack");
const constants_1 = require("../../constants");
class GelatoRelayPack extends RelayKitBasePack_1.RelayKitBasePack {
constructor({ apiKey, protocolKit }) {
super(protocolKit);
_GelatoRelayPack_gelatoRelay.set(this, void 0);
_GelatoRelayPack_apiKey.set(this, void 0);
__classPrivateFieldSet(this, _GelatoRelayPack_gelatoRelay, new relay_sdk_1.GelatoRelay(), "f");
__classPrivateFieldSet(this, _GelatoRelayPack_apiKey, apiKey, "f");
}
_getFeeToken(gasToken) {
return !gasToken || gasToken === constants_1.ZERO_ADDRESS ? constants_1.GELATO_NATIVE_TOKEN_ADDRESS : gasToken;
}
getFeeCollector() {
return constants_1.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 __classPrivateFieldGet(this, _GelatoRelayPack_gelatoRelay, "f").getEstimatedFee(chainId, feeToken, BigInt(gasLimit), false);
return estimation.toString();
}
async getTaskStatus(taskId) {
return __classPrivateFieldGet(this, _GelatoRelayPack_gelatoRelay, "f").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 ?? constants_1.ZERO_ADDRESS;
const paymentToGelato = await this.getEstimateFee({ chainId, gasLimit: gas, gasToken });
// The Gelato payment transaction
const transferToGelato = (0, protocol_kit_1.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;
}
// If the ERC20 gas token does not follow the standard 18 decimals, we cannot use handlePayment to pay Gelato fees.
const gasToken = options.gasToken ?? constants_1.ZERO_ADDRESS;
const isGasTokenCompatible = await (0, protocol_kit_1.isGasTokenCompatibleWithHandlePayment)(gasToken, this.protocolKit);
if (!isGasTokenCompatible) {
// if the ERC20 gas token is not compatible (less than 18 decimals like USDC), a separate transfer is required to pay Gelato fees.
return this.createTransactionWithTransfer({ transactions, onlyCalls, options });
}
// If the gas token is compatible (Native token or standard ERC20), we use handlePayment function present in the Safe contract to pay Gelato fees
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();
// this transaction is only used for gas estimations
const transactionToEstimateGas = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
nonce
}
});
// as we set gasPrice to 1, safeTxGas is set to a non-zero value to prevent transaction failure due to out-of-gas errors. value see: https://github.com/safe-global/safe-contracts/blob/main/contracts/Safe.sol#L203
const gasPrice = '1';
const safeTxGas = await (0, protocol_kit_1.estimateSafeTxGas)(this.protocolKit, transactionToEstimateGas);
const gasToken = options.gasToken ?? constants_1.ZERO_ADDRESS;
const refundReceiver = this.getFeeCollector();
const chainId = await this.protocolKit.getChainId();
// if a custom gasLimit is provided, we do not need to estimate the gas cost
if (gasLimit) {
const paymentToGelato = await this.getEstimateFee({ chainId, gasLimit, gasToken });
const syncTransaction = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
baseGas: paymentToGelato,
gasPrice,
safeTxGas,
gasToken,
refundReceiver,
nonce
}
});
return syncTransaction;
}
// If gasLimit is not provided, we need to estimate the gas cost.
const baseGas = await (0, protocol_kit_1.estimateTxBaseGas)(this.protocolKit, transactionToEstimateGas);
const safeDeploymentGasCost = await (0, protocol_kit_1.estimateSafeDeploymentGas)(this.protocolKit);
const totalGas = Number(baseGas) + // baseGas
Number(safeTxGas) + // safeTxGas
Number(safeDeploymentGasCost) + // Safe deploymet gas cost if it is required
constants_1.GELATO_GAS_EXECUTION_OVERHEAD; // Gelato 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 ?? constants_1.ZERO_ADDRESS;
// if a custom gasLimit is provided, we do not need to estimate the gas cost
if (gasLimit) {
const transferToGelato = await this.createPaymentToGelato(gasLimit, options);
const syncTransaction = await this.protocolKit.createTransaction({
transactions: [...transactions, transferToGelato],
onlyCalls,
options: {
nonce,
gasToken
}
});
return syncTransaction;
}
// If gasLimit is not provided, we need to estimate the gas cost.
// this transaction is only used for gas estimations
const transactionToEstimateGas = await this.protocolKit.createTransaction({
transactions,
onlyCalls,
options: {
nonce
}
});
const safeTxGas = await (0, protocol_kit_1.estimateSafeTxGas)(this.protocolKit, transactionToEstimateGas);
const baseGas = await (0, protocol_kit_1.estimateTxBaseGas)(this.protocolKit, transactionToEstimateGas);
const safeDeploymentGasCost = await (0, protocol_kit_1.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
constants_1.GELATO_TRANSFER_GAS_COST + // Gelato payment transfer
constants_1.GELATO_GAS_EXECUTION_OVERHEAD; // Gelato 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 (!__classPrivateFieldGet(this, _GelatoRelayPack_apiKey, "f")) {
throw new Error('API key not defined');
}
const request = {
chainId,
target,
data: encodedTransaction
};
const response = await __classPrivateFieldGet(this, _GelatoRelayPack_gelatoRelay, "f").sponsoredCall(request, __classPrivateFieldGet(this, _GelatoRelayPack_apiKey, "f"));
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) : undefined
};
const response = await __classPrivateFieldGet(this, _GelatoRelayPack_gelatoRelay, "f").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 relayTransaction = {
target: safeAddress,
encodedTransaction: safeTransactionEncodedData,
chainId,
options: {
...options,
gasToken
}
};
return this.relayTransaction(relayTransaction);
}
// if the Safe is not deployed we create a batch with the Safe deployment transaction and the provided Safe transaction
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);
}
}
exports.GelatoRelayPack = GelatoRelayPack;
_GelatoRelayPack_gelatoRelay = new WeakMap(), _GelatoRelayPack_apiKey = new WeakMap();
//# sourceMappingURL=GelatoRelayPack.js.map