UNPKG

@tunghm/relay-kit

Version:
512 lines 23.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const protocol_kit_1 = __importStar(require("@safe-global/protocol-kit")); const safe_core_sdk_types_1 = require("@safe-global/safe-core-sdk-types"); const constants_1 = require("../../constants"); const GelatoRelayPack_1 = require("./GelatoRelayPack"); var TaskState; (function (TaskState) { TaskState["CheckPending"] = "CheckPending"; })(TaskState || (TaskState = {})); const CHAIN_ID = 1n; const ADDRESS = '0x...address'; const GAS_TOKEN = '0x...gasToken'; const SAFE_ADDRESS = '0x...safe-address'; const API_KEY = 'api-key'; const FEE_ESTIMATION = BigInt(100000); const BASEGAS_ESTIMATION = '20000'; const SAFETXGAS_ESTIMATION = '10000'; const SAFE_DEPLOYMENT_GAS_ESTIMATION = '30000'; const TASK_ID = 'task-id'; const TASK_STATUS = { chainId: Number(CHAIN_ID), taskState: TaskState.CheckPending, taskId: TASK_ID, creationDate: Date.now().toString() }; const RELAY_RESPONSE = { taskId: TASK_ID }; const SAFE_TRANSACTION = { data: { operation: safe_core_sdk_types_1.OperationType.Call, safeTxGas: '0', baseGas: '0', gasPrice: '0', nonce: 0, gasToken: '0x', refundReceiver: '0x', to: ADDRESS, value: '0', data: '0x' } }; const mockGetEstimateFee = jest.fn().mockResolvedValue(FEE_ESTIMATION); const mockGetTaskStatus = jest.fn().mockResolvedValue(TASK_STATUS); const mockSponsoredCall = jest.fn().mockResolvedValue(RELAY_RESPONSE); const mockCallWithSyncFee = jest.fn().mockResolvedValue(RELAY_RESPONSE); jest.mock('@gelatonetwork/relay-sdk', () => { return { GelatoRelay: jest.fn().mockImplementation(() => { return { getEstimatedFee: mockGetEstimateFee, getTaskStatus: mockGetTaskStatus, sponsoredCall: mockSponsoredCall, callWithSyncFee: mockCallWithSyncFee }; }) }; }); jest.mock('@safe-global/protocol-kit'); // Cast the import to jest.Mocked type const mockEstimateTxBaseGas = protocol_kit_1.estimateTxBaseGas; const mockEstimateSafeTxGas = protocol_kit_1.estimateSafeTxGas; const mockEstimateSafeDeploymentGas = protocol_kit_1.estimateSafeDeploymentGas; const mockCreateERC20TokenTransferTransaction = protocol_kit_1.createERC20TokenTransferTransaction; const mockedIsGasTokenCompatibleWithHandlePayment = protocol_kit_1.isGasTokenCompatibleWithHandlePayment; jest.doMock('@safe-global/protocol-kit', () => ({ ...jest.requireActual('@safe-global/protocol-kit'), estimateTxBaseGas: mockEstimateTxBaseGas, estimateSafeTxGas: mockEstimateSafeTxGas, estimateSafeDeploymentGas: mockEstimateSafeDeploymentGas, createERC20TokenTransferTransaction: mockCreateERC20TokenTransferTransaction, isGasTokenCompatibleWithHandlePayment: mockedIsGasTokenCompatibleWithHandlePayment })); const safe = new protocol_kit_1.default(); const gelatoRelayPack = new GelatoRelayPack_1.GelatoRelayPack({ apiKey: API_KEY, protocolKit: safe }); describe('GelatoRelayPack', () => { beforeEach(() => { jest.clearAllMocks(); mockEstimateTxBaseGas.mockResolvedValue(Promise.resolve(BASEGAS_ESTIMATION)); mockEstimateSafeTxGas.mockResolvedValue(Promise.resolve(SAFETXGAS_ESTIMATION)); mockEstimateSafeDeploymentGas.mockResolvedValue(Promise.resolve(SAFE_DEPLOYMENT_GAS_ESTIMATION)); }); it('should allow to get a fee estimation', async () => { const chainId = 1n; const gasLimit = '100000'; const gasToken = '0x0000000000000000000000000000000000000000'; const estimation = await gelatoRelayPack.getEstimateFee({ chainId, gasLimit, gasToken }); expect(estimation).toBe(FEE_ESTIMATION.toString()); expect(mockGetEstimateFee).toHaveBeenCalledWith(chainId, constants_1.GELATO_NATIVE_TOKEN_ADDRESS, BigInt(gasLimit), false); expect(BigInt(estimation) > 0).toBe(true); }); it('should allow to check the task status', async () => { const taskId = 'task-id'; const status = await gelatoRelayPack.getTaskStatus(taskId); expect(status).toBe(TASK_STATUS); expect(mockGetTaskStatus).toHaveBeenCalledWith('task-id'); }); it('should allow to make a sponsored transaction', async () => { const response = await gelatoRelayPack.sendSponsorTransaction(SAFE_ADDRESS, '0x', CHAIN_ID); expect(response).toBe(RELAY_RESPONSE); expect(mockSponsoredCall).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: '0x' }, API_KEY); }); it('should throw an error when trying to do a sponsored transaction without an api key', async () => { const relayPack = new GelatoRelayPack_1.GelatoRelayPack({ protocolKit: safe }); await expect(relayPack.sendSponsorTransaction(SAFE_ADDRESS, '0x', CHAIN_ID)).rejects.toThrowError('API key not defined'); }); describe('when creating a relayed transaction', () => { describe('When gas limit is manually defined', () => { let relayPack; const transactions = [ { to: ADDRESS, data: '0x', value: '0' } ]; const options = { gasLimit: '100', isSponsored: true }; beforeEach(() => { jest.clearAllMocks(); relayPack = new GelatoRelayPack_1.GelatoRelayPack({ protocolKit: safe }); safe.getNonce = jest.fn().mockResolvedValue(0); safe.getChainId = jest.fn().mockResolvedValue(0); safe.getContractManager = jest.fn().mockReturnValue({ safeContract: {} }); safe.createTransaction = jest.fn().mockResolvedValue(SAFE_TRANSACTION); mockedIsGasTokenCompatibleWithHandlePayment.mockResolvedValue(Promise.resolve(true)); }); it('should allow you to create a sponsored one', async () => { await relayPack.createTransaction({ transactions, options }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { nonce: 0 } }); }); it('should allow to create a sync fee one', async () => { await relayPack.createTransaction({ transactions, options: { ...options, isSponsored: false } }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { baseGas: FEE_ESTIMATION.toString(), safeTxGas: SAFETXGAS_ESTIMATION, gasPrice: '1', gasToken: constants_1.ZERO_ADDRESS, // native token refundReceiver: constants_1.GELATO_FEE_COLLECTOR, nonce: 0 } }); }); it('should return the correct gasToken when being sent through the options', async () => { await relayPack.createTransaction({ transactions, options: { ...options, isSponsored: false, gasToken: GAS_TOKEN } }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { baseGas: FEE_ESTIMATION.toString(), safeTxGas: SAFETXGAS_ESTIMATION, gasPrice: '1', gasToken: GAS_TOKEN, refundReceiver: constants_1.GELATO_FEE_COLLECTOR, nonce: 0 } }); }); it('should allow you to create relay transaction using a non standard ERC20 gas token to pay Gelato fees', async () => { // non standard ERC20 like USDC mockedIsGasTokenCompatibleWithHandlePayment.mockResolvedValue(Promise.resolve(false)); const options = { gasToken: GAS_TOKEN, isSponsored: false, gasLimit: '5000' // manual gas limit }; const transferToGelato = { to: constants_1.GELATO_FEE_COLLECTOR, value: FEE_ESTIMATION.toString(), data: '0x' }; mockCreateERC20TokenTransferTransaction.mockReturnValue(transferToGelato); await relayPack.createTransaction({ transactions, options }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions: [...transactions, transferToGelato], // the transfer to Gelato is prensent onlyCalls: false, options: { gasToken: GAS_TOKEN, // non standard ERC20 gas token nonce: 0 } }); }); }); describe('When gas limit is automatically estimate', () => { let relayPack; const mockTransferTransacton = { to: ADDRESS, data: '0x', value: '0' }; const transactions = [mockTransferTransacton]; beforeEach(() => { jest.clearAllMocks(); relayPack = new GelatoRelayPack_1.GelatoRelayPack({ protocolKit: safe }); safe.getNonce = jest.fn().mockResolvedValue(0); safe.getChainId = jest.fn().mockResolvedValue(0); safe.getContractManager = jest.fn().mockReturnValue({ safeContract: {} }); safe.createTransaction = jest.fn().mockResolvedValue(SAFE_TRANSACTION); mockedIsGasTokenCompatibleWithHandlePayment.mockResolvedValue(Promise.resolve(true)); }); it('should allow you to create a sponsored one', async () => { const options = { isSponsored: true }; await relayPack.createTransaction({ transactions, options }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { nonce: 0 } }); }); describe('When a compatible gas token is used', () => { beforeEach(() => { jest.clearAllMocks(); mockedIsGasTokenCompatibleWithHandlePayment.mockResolvedValue(Promise.resolve(true)); }); it('should allow you to create relay transaction using the native token to pay Gelato fees', async () => { await relayPack.createTransaction({ transactions }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { baseGas: FEE_ESTIMATION.toString(), gasPrice: '1', safeTxGas: SAFETXGAS_ESTIMATION, gasToken: constants_1.ZERO_ADDRESS, // native token refundReceiver: constants_1.GELATO_FEE_COLLECTOR, nonce: 0 } }); }); it('should allow you to create relay transaction using a compatible ERC20 token to pay Gelato fees', async () => { const options = { gasToken: GAS_TOKEN }; await relayPack.createTransaction({ transactions, options }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions, onlyCalls: false, options: { baseGas: FEE_ESTIMATION.toString(), gasPrice: '1', safeTxGas: SAFETXGAS_ESTIMATION, gasToken: GAS_TOKEN, // ERC20 gas token refundReceiver: constants_1.GELATO_FEE_COLLECTOR, nonce: 0 } }); }); }); describe('When a non compatible gas token is used', () => { beforeEach(() => { jest.clearAllMocks(); mockedIsGasTokenCompatibleWithHandlePayment.mockResolvedValue(Promise.resolve(false)); }); it('should allow you to create relay transaction using a non standard ERC20 gas token to pay Gelato fees', async () => { const options = { gasToken: GAS_TOKEN }; const transferToGelato = { to: constants_1.GELATO_FEE_COLLECTOR, value: FEE_ESTIMATION.toString(), data: '0x' }; mockCreateERC20TokenTransferTransaction.mockReturnValue(transferToGelato); await relayPack.createTransaction({ transactions, options }); expect(safe.createTransaction).toHaveBeenCalledWith({ transactions: [...transactions, transferToGelato], // the transfer to Gelato is prensent onlyCalls: false, options: { gasToken: GAS_TOKEN, // non standard ERC20 gas token nonce: 0 } }); }); }); }); }); it('should allow to make a sync fee transaction', async () => { const response = await gelatoRelayPack.sendSyncTransaction(SAFE_ADDRESS, '0x', CHAIN_ID, { gasLimit: '100000' }); expect(response).toBe(RELAY_RESPONSE); expect(mockCallWithSyncFee).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: '0x', feeToken: constants_1.GELATO_NATIVE_TOKEN_ADDRESS, isRelayContext: false }, { gasLimit: BigInt(100000) }); }); it('should expose a relayTransaction doing a sponsored or sync fee transaction depending on an optional parameter', async () => { const sponsoredResponse = await gelatoRelayPack.relayTransaction({ target: SAFE_ADDRESS, encodedTransaction: '0x', chainId: CHAIN_ID, options: { gasLimit: '100000', isSponsored: true } }); expect(sponsoredResponse).toBe(RELAY_RESPONSE); expect(mockSponsoredCall).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: '0x' }, API_KEY); const paidResponse = await gelatoRelayPack.relayTransaction({ target: SAFE_ADDRESS, encodedTransaction: '0x', chainId: CHAIN_ID, options: { gasLimit: '100000', isSponsored: false } }); expect(paidResponse).toBe(RELAY_RESPONSE); expect(mockCallWithSyncFee).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: '0x', feeToken: constants_1.GELATO_NATIVE_TOKEN_ADDRESS, isRelayContext: false }, { gasLimit: BigInt(100000) }); }); it('should allow to retrieve the fee collector address', () => { expect(gelatoRelayPack.getFeeCollector()).toBe(constants_1.GELATO_FEE_COLLECTOR); }); describe('executeTransaction', () => { const ENCODED_TRANSACTION_DATA = '0x...txData'; const MULTISEND_ADDRESS = '0x...multiSendAddress'; const SAFE_DEPLOYMENT_BATCH = { to: MULTISEND_ADDRESS, value: '0', data: '0x...deplymentBachData' }; beforeEach(() => { jest.clearAllMocks(); safe.isSafeDeployed = jest.fn().mockResolvedValue(true); safe.getChainId = jest.fn().mockResolvedValue(CHAIN_ID); safe.getAddress = jest.fn().mockResolvedValue(SAFE_ADDRESS); safe.getEncodedTransaction = jest.fn().mockResolvedValue(ENCODED_TRANSACTION_DATA); safe.wrapSafeTransactionIntoDeploymentBatch = jest .fn() .mockResolvedValue(SAFE_DEPLOYMENT_BATCH); }); describe('when the Safe is already deployed', () => { it('should execute a sponsored relay transaction', async () => { const relayTransaction = { data: { nonce: 0, to: ADDRESS, value: '0', data: '0x' } }; const gelatoResponse = await gelatoRelayPack.executeTransaction({ executable: relayTransaction, options: { isSponsored: true } }); expect(gelatoResponse).toBe(RELAY_RESPONSE); expect(mockSponsoredCall).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: ENCODED_TRANSACTION_DATA }, API_KEY); // no counterfactual deployment present expect(safe.wrapSafeTransactionIntoDeploymentBatch).not.toHaveBeenCalled(); }); it('should execute a sync relay transaction', async () => { const relayTransaction = { data: { operation: safe_core_sdk_types_1.OperationType.Call, safeTxGas: SAFETXGAS_ESTIMATION, baseGas: FEE_ESTIMATION.toString(), gasPrice: '1', nonce: 0, gasToken: GAS_TOKEN, refundReceiver: constants_1.GELATO_FEE_COLLECTOR, to: ADDRESS, value: '0', data: '0x' } }; const gelatoResponse = await gelatoRelayPack.executeTransaction({ executable: relayTransaction }); expect(gelatoResponse).toBe(RELAY_RESPONSE); expect(mockCallWithSyncFee).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: SAFE_ADDRESS, data: ENCODED_TRANSACTION_DATA, feeToken: GAS_TOKEN, isRelayContext: false }, { gasLimit: undefined }); // no counterfactual deployment present expect(safe.wrapSafeTransactionIntoDeploymentBatch).not.toHaveBeenCalled(); }); }); describe('when the Safe is not deployed (counterfactual deployment)', () => { it('should execute a sponsored relay transaction & counterfactual deployment', async () => { // Safe is not deployed safe.isSafeDeployed = jest.fn().mockResolvedValue(false); const relayTransaction = { data: { nonce: 0, to: ADDRESS, value: '0', data: '0x' } }; const gelatoResponse = await gelatoRelayPack.executeTransaction({ executable: relayTransaction, options: { isSponsored: true } }); expect(gelatoResponse).toBe(RELAY_RESPONSE); expect(mockSponsoredCall).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: MULTISEND_ADDRESS, // multiSend contract as a target address because a counterfactual deployment is present data: SAFE_DEPLOYMENT_BATCH.data }, API_KEY); // counterfactual deployment in present expect(safe.wrapSafeTransactionIntoDeploymentBatch).toHaveBeenCalled(); }); it('should execute a sync relay transaction & counterfactual deployment', async () => { // Safe is not deployed safe.isSafeDeployed = jest.fn().mockResolvedValue(false); const relayTransaction = { data: { operation: safe_core_sdk_types_1.OperationType.Call, safeTxGas: SAFETXGAS_ESTIMATION, baseGas: FEE_ESTIMATION.toString(), gasPrice: '1', nonce: 0, gasToken: GAS_TOKEN, refundReceiver: constants_1.GELATO_FEE_COLLECTOR, to: ADDRESS, value: '0', data: '0x' } }; const gelatoResponse = await gelatoRelayPack.executeTransaction({ executable: relayTransaction }); expect(gelatoResponse).toBe(RELAY_RESPONSE); expect(mockCallWithSyncFee).toHaveBeenCalledWith({ chainId: CHAIN_ID, target: MULTISEND_ADDRESS, // multiSend contract as a target address because a counterfactual deployment is present data: SAFE_DEPLOYMENT_BATCH.data, feeToken: GAS_TOKEN, isRelayContext: false }, { gasLimit: undefined }); // counterfactual deployment in present expect(safe.wrapSafeTransactionIntoDeploymentBatch).toHaveBeenCalled(); }); }); }); }); //# sourceMappingURL=GelatoRelayPack.test.js.map