Introduction Summary:
Gasless transactions abstract gas payment away from wallets (e.g. MetaMask), enabling users to send signed transactions without holding the native token.
Gelato Relay enables meta transactions so users can interact with smart contracts without paying gas.
This guide explains how to enable gasless transactions for your contracts and build dApps that offer a gasless user experience.
Showcase App: Gasless Proposal Voting for DAOs
Example app: DAO members create proposals, then have 30 minutes to vote. Afterward, an automated Gelato task closes the voting period.
Not Gasless at first
Initially, proposals and votes are signed transactions. Repo:
github.com/donoso-eth/gasless-voting .
git clone https:
cd gasless -voting
git checkout main
yarn
Run a local Hardhat node:
# Terminal 1
npm run fork
# Terminal 2
npm run compile
npm run deploy
Launch the Angular frontend:
App runs at http://localhost:4200/. Test proposals locally before deploying to testnet for relayed transactions.
Why Gasless?
DAO participation is often hindered by gas costs. To remove friction, we convert our contract to be relay-aware.
Here’s how a relayer works:
The app sends an HTTP POST request to Gelato via the Relay SDK.
Gelato forwards the request to the Relay contract.
The target contract executes the transaction.
How to Gasless Step by Step We’ll convert createProposal() and vote() into gasless transactions, following the table:
Gelato Auth
Payment
Inheriting Contract
SDK/API method
No
User
GelatoRelayContext
relayWithSyncFee
Yes
User
GelatoRelayContextERC2771
relayWithSyncFeeERC2771
No
1Balance
n.a.
relayWithSponsoredCall
Yes*
1Balance
ERC2771Context
relayWithSponsoredCallERC2771
*Requires SponsorKey from Gelato 1Balance .
Transaction without authentication and 1Balance (relayWithSyncFee) We allow all users to create proposals, no authentication needed. We use Gelato’s SyncFee payment method.
Inherit contract: GelatoRelayContext
SDK method: relayWithSyncFee
Smart Contract Update
pragma solidity ^0.8 .0 ;
import { GelatoRelayContext } from "@gelatonetwork/relay-context/contracts/GelatoRelayContext.sol" ;
contract GaslessProposing is GelatoRelayContext {
function createProposal ( bytes calldata payload) external onlyGelatoRelay {
require ( proposal .proposalStatus == ProposalStatus .Ready , "OLD_PROPOSAL_STILL_ACTIVE" ) ;
_transferRelayFee ( ) ;
proposalId ++;
proposal .proposalStatus = ProposalStatus .Voting ;
proposal .proposalId = proposalId ;
proposalTimestamp = block .timestamp ;
proposalBytes = payload ;
IGaslessVoting ( gaslessVoting ) ._createProposal ( proposalId , payload ) ;
finishingVotingTask = createFinishVotingTask ( ) ;
proposal .taskId = finishingVotingTask ;
emit ProposalCreated ( finishingVotingTask ) ;
}
}
Ensures only the Gelato Relay contract can call createProposal().
Transfers fees to Gelato Relay’s feeCollector .
Frontend Update (SDK) import { CallWithSyncFeeRequest , GelatoRelay } from '@gelatonetwork/relay-sdk' ;
const relay = new GelatoRelay ( ) ;
async createProposal ( ) {
let name = this .proposalForm .controls .nameCtrl .value ;
let description = this .proposalForm .controls .descriptionCtrl .value ;
let payload = this .abiCoder .encode ( [ 'string' , 'string' ] , [ name , description ] ) ;
const { data } =
await this .readGaslessProposing .populateTransaction .createProposal ( payload ) ;
const request : CallWithSyncFeeRequest = {
chainId : 5 ,
target : this .readGaslessProposing .address ,
data : data !,
isRelayContext : true ,
feeToken : '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
} ;
const relayResponse = await relay .callWithSyncFee ( request ) ;
console .log ( relayResponse .taskId ) ;
}
✅ Gasless transaction complete! Demo:
gelato-gasless-dao.web.app/landing
Transaction with authentication + 1Balance (relayWithSponsoredCallERC2771) Inherit contract: ERC2771Context
SDK method: relayWithSponsoredCallERC2771
Configure 1Balance in the dashboard , deposit GETH, create app, and copy your sponsor API key.
Smart Contract Update import { ERC2771Context } from "@gelatonetwork/relay-context/contracts/vendor/ERC2771Context.sol" ;
contract GaslessVoting is ERC2771Context {
constructor ( ) ERC2771Context ( 0xBf175FCC7086b4f9bd59d5EAE8eA67b8f940DE0d ) { }
function votingProposal ( bool positive) external onlyTrustedForwarder {
address voter = _msgSender ( ) ;
_votingProposal ( positive , voter ) ;
emit ProposalVoted ( ) ;
}
}
Frontend Update (SDK) import { GelatoRelay } from '@gelatonetwork/relay-sdk' ;
const relay = new GelatoRelay ( ) ;
async vote ( value : boolean) {
try {
const { data } =
await this .gaslessVoting .populateTransaction .votingProposal ( value ) ;
const request = {
chainId : 5 ,
target : this .readGaslessVoting .address ,
data : data !,
user : this .dapp .signerAddress !
} ;
const sponsorApiKey = 'YOUR_1BALANCE_API_KEY' ;
const relayResponse = await relay .sponsoredCallERC2771 (
request ,
new ethers .providers .Web3Provider ( ethereum ) ,
sponsorApiKey
) ;
console .log ( relayResponse .taskId ) ;
} catch {
alert ( 'only one vote per user' ) ;
}
}
Bonus: Implementing Gelato Automate In the demo app, Gelato Automate is used to close proposals after ~30 minutes. See Automate contract addresses and the repo .
function createFinishVotingTask ( ) internal returns ( bytes32 taskId) {
bytes memory timeArgs = abi .encode ( uint128 ( block .timestamp + proposalValidity ) , proposalValidity ) ;
bytes memory execData = abi .encodeWithSelector ( this .finishVoting .selector ) ;
LibDataTypes .Module ;
modules [ 0 ] = LibDataTypes .Module .TIME ;
modules [ 1 ] = LibDataTypes .Module .SINGLE_EXEC ;
bytes ;
args [ 0 ] = timeArgs ;
LibDataTypes .ModuleData memory moduleData = LibDataTypes .ModuleData ( modules , args ) ;
taskId = IOps ( ops ) .createTask ( address ( this ) , execData , moduleData , ETH ) ;
}
function finishVoting ( ) public onlyOps {
( uint256 fee, address feeToken) = IOps ( ops ) .getFeeDetails ( ) ;
transfer ( fee , feeToken ) ;
}
function transfer ( uint256 _amount, address _paymentToken) internal {
( bool success, ) = gelato .call { value: _amount } ( "" ) ;
require ( success , "ETH transfer failed" ) ;
}
modifier onlyOps ( ) {
require ( msg .sender == address ( ops ) , "OpsReady: onlyOps" ) ;
_ ;
}
About Gelato
Gelato is a Web3 Cloud Platform enabling developers to build automated, gasless, and off-chain-aware Layer 2 chains and smart contracts.
400+ web3 projects use Gelato to power millions of DeFi, NFT, and gaming transactions.
Gelato RaaS: Deploy ZK or OP L2 chains with account abstraction and middleware baked in.
Web3 Functions: Connect contracts to off-chain data via decentralized cloud functions.
Automate: Automate smart contracts with decentralized execution.
Relay: Enable robust, gasless transactions with a simple API.
Account Abstraction SDK: Built with Safe, combining gasless transactions with the security of Safe wallets.
Subscribe to our newsletter and turn on
Twitter notifications for the latest Gelato updates!
Interested in building the future of web3? Explore open roles and apply
here .