Gelato is deprecating the SyncFee payment pattern (callWithSyncFee and GelatoRelayContext). The old pattern will no longer be available. To continue accepting ERC-20 token payments for relayed transactions, you must update your contracts and frontend to use direct token transfers.
What’s Changing?
The old approach required:
- Contract inheritance from
GelatoRelayContext
- Fee data encoded in calldata - Gelato appended
fee, feeToken, and feeCollector to the calldata
- On-chain fee extraction - Contract called
_transferRelayFee() to decode and transfer fees
onlyGelatoRelay modifier - To restrict who can call the function
// OLD WAY - Being deprecated
import {GelatoRelayContext} from "@gelatonetwork/relay-context/contracts/GelatoRelayContext.sol";
contract MyContract is GelatoRelayContext {
function myFunction() external onlyGelatoRelay {
// Your logic here
// Extract fee from calldata and transfer to Gelato
_transferRelayFee();
}
}
The new approach is simpler:
- Call Gelato API to get fee collector address and fee quote
- Transfer tokens directly from the contract to fee collector
- No inheritance needed - your contract stays clean
The contract must hold the fee tokens. Ensure your contract has sufficient token balance before executing transactions.
// NEW WAY - Direct transfer
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MyContract {
function myFunction(
address feeToken,
address feeCollector,
uint256 fee
) external {
// Transfer fee from contract to Gelato's fee collector
// Contract must hold the fee tokens
IERC20(feeToken).transfer(feeCollector, fee);
// Your logic here
}
}
For gasless approvals, use EIP-2612 permit to pull tokens from the user:
- Call Gelato API to get fee collector address and fee quote
- User signs permit for gasless token approval
- Transfer tokens from user via
transferFrom
This approach pulls tokens from the user’s wallet. The permit signature validates the user address, so no additional verification is needed.
// NEW WAY - With permit
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
contract MyContract {
function myFunction(
address user,
address feeToken,
address feeCollector,
uint256 fee,
uint256 deadline,
uint8 v, bytes32 r, bytes32 s
) external {
// Execute permit (signature validates the user)
IERC20Permit(feeToken).permit(user, address(this), fee, deadline, v, r, s);
IERC20(feeToken).transferFrom(user, feeCollector, fee);
// Your logic here
}
}
Migration Steps
Update Your Smart Contract
Remove the old Gelato inheritance and add direct token transfer. Before (SyncFee)
After (Direct Transfer)
After (With Permit)
import {GelatoRelayContext} from "@gelatonetwork/relay-context/contracts/GelatoRelayContext.sol";
contract MyContract is GelatoRelayContext {
uint256 public counter;
function increment() external onlyGelatoRelay {
counter++;
// Fee extracted from calldata
_transferRelayFee();
}
}
The contract must hold the fee tokens. Ensure your contract has sufficient token balance before executing transactions.
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract MyContract {
uint256 public counter;
function increment(
address feeToken,
address feeCollector,
uint256 fee
) external {
// Transfer fee from contract to Gelato's fee collector
// Contract must hold the fee tokens
IERC20(feeToken).transfer(feeCollector, fee);
counter++;
}
}
For gasless approvals using EIP-2612 permit, transfer tokens from the user:This approach pulls tokens from the user’s wallet. The permit signature validates the user address, so no additional verification is needed.
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
contract MyContract {
uint256 public counter;
function incrementWithPermit(
address user,
address feeToken,
address feeCollector,
uint256 fee,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
// Execute permit (signature validates the user)
IERC20Permit(feeToken).permit(user, address(this), fee, deadline, v, r, s);
// Transfer fee from user to Gelato's fee collector
IERC20(feeToken).transferFrom(user, feeCollector, fee);
counter++;
}
}
Changes Required:
- Remove
GelatoRelayContext inheritance
- Remove
_transferRelayFee() calls
- Remove
onlyGelatoRelay modifier
- Add
feeToken, feeCollector, and fee parameters to your function
- Add direct
IERC20.transfer() call from the contract (or transferFrom with permit)
Deploy Your Updated Contract
Deploy your updated contract to the network.If your contract is not upgradeable and contains important state data, a migration strategy will be required to transfer the state to the new contract.
Update Frontend to Get Fee Data
Your frontend now needs to fetch the fee collector and fee quote. Gelato Gasless SDK
Relay API Endpoints
import { createGelatoEvmRelayerClient } from '@gelatocloud/gasless';
import { createPublicClient, http, formatUnits } from 'viem';
import { baseSepolia } from 'viem/chains';
const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const relayer = createGelatoEvmRelayerClient({
apiKey: process.env.GELATO_API_KEY,
testnet: true
});
const publicClient = createPublicClient({
chain: baseSepolia,
transport: http()
});
// Get fee collector address
const capabilities = await relayer.getCapabilities();
const feeCollector = capabilities[baseSepolia.id].feeCollector;
// Estimate gas for your transaction
const gasEstimate = await publicClient.estimateGas({
to: contractAddress,
data: functionData
});
// Get fee quote in token
const quote = await relayer.getFeeQuote({
chainId: baseSepolia.id,
gas: gasEstimate,
token: USDC_ADDRESS
});
console.log('Fee:', formatUnits(quote.fee, 6), 'USDC');
console.log('Fee Collector:', feeCollector);
Use https://api.gelato.cloud for mainnets, or https://api.t.gelato.cloud for testnets.Get Fee Collectorconst response = await fetch('https://api.t.gelato.cloud/rpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.GELATO_API_KEY
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'relayer_getCapabilities',
params: {}
})
});
const data = await response.json();
const feeCollector = data.result['84532'].feeCollector; // Base Sepolia
Get Fee Quoteconst USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const response = await fetch('https://api.t.gelato.cloud/rpc', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.GELATO_API_KEY
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'relayer_getFeeQuote',
params: {
chainId: '84532',
gas: gasEstimate.toString(),
token: USDC_ADDRESS
}
})
});
const data = await response.json();
const fee = data.result.fee; // Fee amount in token units
Update Frontend Transaction Encoding
// OLD WAY - Gelato appended fee data to calldata
const functionData = contract.interface.encodeFunctionData("increment", []);
await gelatoRelay.callWithSyncFee({
chainId: chainId,
target: contractAddress,
data: functionData,
feeToken: FEE_TOKEN_ADDRESS,
});
import { createGelatoEvmRelayerClient, token } from '@gelatocloud/gasless';
import { encodeFunctionData } from 'viem';
import { baseSepolia } from 'viem/chains';
const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const relayer = createGelatoEvmRelayerClient({
apiKey: process.env.GELATO_API_KEY,
testnet: true
});
// Get fee collector and quote
const capabilities = await relayer.getCapabilities();
const feeCollector = capabilities[baseSepolia.id].feeCollector;
const quote = await relayer.getFeeQuote({
chainId: baseSepolia.id,
gas: gasEstimate,
token: USDC_ADDRESS
});
// Encode transaction with fee parameters
const data = encodeFunctionData({
abi: contractAbi,
functionName: 'increment',
args: [USDC_ADDRESS, feeCollector, quote.fee]
});
// Submit to Gelato Relay
const id = await relayer.sendTransaction({
chainId: baseSepolia.id,
to: contractAddress,
data,
payment: token(USDC_ADDRESS)
});
console.log(`Gelato transaction id: ${id}`);
The contract must hold the fee tokens before the transaction is executed.
For gasless token approvals using EIP-2612 permit:import { createGelatoEvmRelayerClient, token } from '@gelatocloud/gasless';
import { encodeFunctionData } from 'viem';
import { baseSepolia } from 'viem/chains';
const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e';
const relayer = createGelatoEvmRelayerClient({
apiKey: process.env.GELATO_API_KEY,
testnet: true
});
// Get fee collector and quote
const capabilities = await relayer.getCapabilities();
const feeCollector = capabilities[baseSepolia.id].feeCollector;
const quote = await relayer.getFeeQuote({
chainId: baseSepolia.id,
gas: gasEstimate,
token: USDC_ADDRESS
});
// Sign EIP-2612 permit (gasless approval)
const deadline = Math.floor(Date.now() / 1000) + 3600;
const nonce = await feeToken.nonces(userAddress);
const permitSignature = await signer.signTypedData(
{
name: await feeToken.name(),
version: "1",
chainId: baseSepolia.id,
verifyingContract: USDC_ADDRESS,
},
{
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
},
{
owner: userAddress,
spender: contractAddress,
value: quote.fee,
nonce: nonce,
deadline: deadline,
}
);
const { v, r, s } = ethers.Signature.from(permitSignature);
// Encode transaction with permit parameters
const data = encodeFunctionData({
abi: contractAbi,
functionName: 'incrementWithPermit',
args: [userAddress, USDC_ADDRESS, feeCollector, quote.fee, deadline, v, r, s]
});
// Submit to Gelato Relay
const id = await relayer.sendTransaction({
chainId: baseSepolia.id,
to: contractAddress,
data,
payment: token(USDC_ADDRESS)
});
This approach pulls tokens from the user’s wallet using a permit signature.
Key Changes Summary
| What | Before (SyncFee) | After (Direct Transfer) |
|---|
| Contract inheritance | GelatoRelayContext | None required |
| Fee extraction | _transferRelayFee() | Direct IERC20.transfer() |
| Modifier | onlyGelatoRelay | Not required |
| Fee data source | Encoded in calldata by Gelato | From Gelato API |
| Fee collector | Extracted from calldata | From relayer_getCapabilities |
| Fee amount | Extracted from calldata | Calculated from relayer_getFeeData |
Migration Checklist
API Reference
| Environment | URL |
|---|
| Mainnet | https://api.gelato.cloud/rpc |
| Testnet | https://api.t.gelato.cloud/rpc |
| Method | Description |
|---|
relayer_getCapabilities | Get supported tokens and fee collector per chain |
relayer_getFeeData | Get fee quote with exchange rate and gas price |
relayer_sendTransaction | Submit transaction for relay |
relayer_getStatus | Check transaction status |
Example Implementations