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 .