Skip to content

Repository files navigation

GasVote

Gasless meta-transaction infrastructure with DAG-based batch execution, on-chain gas analytics, and configurable paymaster sponsorship. Built for the KRITI Hackathon at IIT Guwahati.

Solidity 0.8.24 | Foundry | OpenZeppelin v5.x | React 18 | TypeScript | MIT License

What problem does this solve?

Every Ethereum transaction costs gas. For apps with frequent multi-step interactions, this means:

  • Fees ranging from $1 to $50+ depending on congestion
  • Multi-step flows like approve, deposit, stake require 2-3 separate transactions
  • New users have to buy ETH before they can do anything

GasVote is a reusable gas optimization layer that any dApp can plug into. Users sign once, pay nothing, and the relayer handles submission and gas payment.

How it works:

Technique What it does
Transaction batching with DAG dependencies Saves (N-1) x 21,000 base gas per batch
Meta-transactions (EIP-2771 / EIP-712) Users sign once, pay zero gas
Configurable paymaster 3 sponsorship modes with on-chain policy enforcement
On-chain analytics Every batch's gas savings recorded and queryable
Dual-mode execution Mode 1 (EIP-2771) + Mode 2 (Smart Account / EntryPoint)

Architecture

graph TB
    subgraph User["User (Browser)"]
        W[Wallet / MetaMask]
        FE[React Frontend]
    end

    subgraph OffChain["Off-Chain"]
        R[Relayer - Fastify + viem]
        GO[Gas Oracle]
        NM[Nonce Manager]
        SSE[SSE Manager]
    end

    subgraph OnChain["On-Chain (EVM)"]
        TF[TrustedForwarder]
        BE[BatchExecutor]
        PM[GasPaymaster]
        MF[MetaTxBatchForwarder]
        GA[GasAnalytics]
        EP[MiniEntryPoint]
        SA[SmartAccount]
        DT[DemoToken]
        DV[DemoVault]
        DN[DemoNFT]
    end

    W -->|"EIP-712 sign"| FE
    FE -->|"POST /relay"| R
    FE -->|"SSE /relay/events"| SSE
    R --> GO
    R --> NM
    R --> SSE
    R -->|"Mode 1"| TF
    R -->|"Mode 2"| EP
    TF --> BE
    BE --> DT & DV & DN
    TF --> MF
    MF --> PM
    MF --> GA
    EP --> SA
    SA --> BE
    PM -.->|reimburse| R
Loading

There are two execution paths. Mode 1 goes through the TrustedForwarder (EIP-2771) and requires target contracts to inherit ERC2771Context. Mode 2 goes through a MiniEntryPoint and SmartAccount, which works with any contract since the smart account acts as the caller.

Mode 1 (EIP-2771) Mode 2 (Smart Account)
Path User signs, relayer calls MetaTxBatchForwarder, then TrustedForwarder, then BatchExecutor User signs, relayer calls MiniEntryPoint, then SmartAccount, then BatchExecutor
Identity _msgSender() via ERC2771Context Owner-based verification
Target requirement Must inherit ERC2771Context Works with anything
Trade-off Strong on-chain user identity No contract modification needed

The demo flow: user picks actions (approve GVDT, deposit into vault, stake), frontend bundles them into one EIP-712 message, user signs once in MetaMask (free), relayer validates and submits, BatchExecutor runs the calls with dependency checks, paymaster reimburses relayer, GasAnalytics records savings. The user pays $0 gas — all costs are sponsored by the paymaster.

Project structure

gasvote/
  contracts/              Foundry project (Solidity 0.8.24)
    src/
      core/               7 core contracts (BatchExecutor, TrustedForwarder, etc.)
      demo/               DemoToken (ERC-20 + Permit), DemoVault, DemoNFT (ERC-721)
      interfaces/         7 interfaces
      libraries/          Types, Events, Errors, TransientReentrancyGuard
    test/                 148 tests (unit, integration, fuzz, gas, halmos, invariant)
    script/               DeployAll.s.sol, DemoMode2.s.sol

  relayer/                Fastify + viem relay service
    src/
      index.ts            Server entry
      config.ts           Zod-validated env config
      client.ts           viem public + wallet clients
      types.ts            Request/response types
      services/           gasOracle, nonceManager, relay, signature, sseManager
      routes/             11 REST + SSE endpoints
      abi/                Contract ABIs

  frontend/               React + Vite + wagmi
    src/
      App.tsx             Router (4 routes + Infra sub-tabs)
      pages/              7 pages (Dashboard, BatchBuilder, BundlerDemo, Infra, Analytics, PaymasterAdmin, GasCalculator)
      components/         10 components
      hooks/              useGasAnalytics, useRelayer, usePaymaster, useGasPrice
      lib/                wagmi config, relayer API, contracts, utils
      abi/                9 ABIs

  setup-local.ps1         One-click local setup (Windows)
  setup-local.sh          One-click local setup (Linux/macOS)
  Makefile                Build/test/deploy shortcuts
  pnpm-workspace.yaml     Workspaces: relayer, frontend

Quick start

You need Node.js >= 20, pnpm >= 9, and Foundry.

Install Foundry:

# Linux / macOS
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Windows - download from https://github.com/foundry-rs/foundry/releases
# Add to PATH in every new PowerShell session:
$env:Path = "$env:USERPROFILE\.foundry\bin;$env:Path"

One-click setup (recommended)

The setup script installs deps, compiles contracts, starts Anvil, deploys, writes .env files, and transfers test tokens.

# Windows
git clone https://github.com/<your-username>/gasvote.git
cd gasvote
.\setup-local.ps1
# Linux / macOS
git clone https://github.com/<your-username>/gasvote.git
cd gasvote
chmod +x setup-local.sh
./setup-local.sh

When done, open two more terminals:

cd relayer && pnpm dev        # http://localhost:3001
cd frontend && pnpm dev       # http://localhost:5173

Open http://localhost:5173 and skip to the "Connect wallet" section below.

Manual setup

  1. Clone and build:
git clone https://github.com/<your-username>/gasvote.git
cd gasvote
pnpm install
cd contracts && forge build && cd ..
  1. Start Anvil (keep running):
pnpm anvil
  1. Deploy and configure:
pnpm deploy:anvil

Copy the printed addresses into both .env files (or use the pre-filled Anvil templates):

cp relayer/.env.anvil relayer/.env
cp frontend/.env.anvil frontend/.env

Transfer test tokens to account #1 so the frontend shows a balance:

cast send 0x0165878A594ca255338adfa4d48449f69242Eb8F "transfer(address,uint256)(bool)" 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 100000000000000000000000 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 --rpc-url http://127.0.0.1:8545
  1. Start relayer + frontend:
pnpm dev:relayer    # http://localhost:3001
pnpm dev:frontend   # http://localhost:5173

Connect wallet and test

Add the Anvil network to MetaMask: RPC URL http://127.0.0.1:8545, Chain ID 31337.

Import test account #1 (not #0, that's the relayer):

Private key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d

This account has 10,000 ETH and 100,000 GVDT tokens.

Go to Batch Builder, enter an amount like 100, click Execute Batch, sign the EIP-712 message in MetaMask (free), and watch the steps complete. Check Dashboard and Analytics for results.

Environment variables

Frontend (frontend/.env)

Variable Description Example
VITE_RELAYER_URL Relayer HTTP endpoint http://localhost:3001
VITE_CHAIN_ID Target chain ID 31337 (Anvil) or 11155111 (Sepolia)
VITE_RPC_URL JSON-RPC URL http://127.0.0.1:8545
VITE_WALLETCONNECT_PROJECT_ID WalletConnect project ID (optional) demo
VITE_BATCH_EXECUTOR BatchExecutor contract address 0x...
VITE_TRUSTED_FORWARDER TrustedForwarder contract address 0x...
VITE_GAS_PAYMASTER GasPaymaster contract address 0x...
VITE_GAS_ANALYTICS GasAnalytics contract address 0x...
VITE_METATX_BATCH_FORWARDER MetaTxBatchForwarder address 0x...
VITE_MINI_ENTRY_POINT MiniEntryPoint contract address 0x...
VITE_SMART_ACCOUNT SmartAccount contract address 0x...
VITE_DEMO_TOKEN DemoToken (GVDT) contract address 0x...
VITE_DEMO_VAULT DemoVault contract address 0x...

Relayer (relayer/.env)

Variable Description Example / Default
RPC_URL JSON-RPC URL http://127.0.0.1:8545
CHAIN_ID Target chain ID 31337
RELAYER_PRIVATE_KEY Relayer wallet private key 0xac09... (Anvil account #0)
PORT HTTP listen port 3001 (local) / 8080 (Railway)
META_TX_BATCH_FORWARDER MetaTxBatchForwarder address 0x...
TRUSTED_FORWARDER TrustedForwarder address 0x...
GAS_PAYMASTER GasPaymaster address 0x...
MINI_ENTRY_POINT MiniEntryPoint address 0x...
GAS_ANALYTICS GasAnalytics address 0x...
MAX_GAS_PRICE_GWEI Max gas price before rejecting relays 100
ENABLE_GAS_QUEUEING Queue relays when gas is high false
CORS_ORIGINS Comma-separated allowed origins http://localhost:5173

Deploy scripts (contracts/.env or root .env)

Variable Description Required for
SEPOLIA_RPC_URL Sepolia RPC endpoint Sepolia deploy
DEPLOYER_PRIVATE_KEY Deployer wallet private key Sepolia deploy
ETHERSCAN_API_KEY Etherscan API key for verify Sepolia deploy

Template files: .env.example (root), frontend/.env.example, frontend/.env.anvil, frontend/.env.sepolia, relayer/.env.example, relayer/.env.anvil, relayer/.env.sepolia.

Testing

Run all 148 tests:

pnpm test:contracts

Run specific tests:

cd contracts
forge test --match-path test/unit/BatchExecutor.t.sol -vvv
forge test --match-test testBatchExecuteWithDependencies -vvv
forge test --match-path test/integration/*.t.sol -vvv

Gas reports:

pnpm test:gas
pnpm snapshot

Test breakdown (148 tests across 13 suites):

Suite Tests What it covers
BatchExecutor 18 DAG execution, dependency resolution, failure isolation, gas measurement
TrustedForwarder 11 EIP-712 sigs, nonces, deadlines, replay protection
GasPaymaster 25 3 sponsorship modes, policy, deposit/withdraw, auth
MetaTxBatchForwarder 19 Atomic flow: verify, paymaster, execute, analytics
GasAnalytics 8 Recording, aggregates, authorization
MiniEntryPoint 17 UserOp validation, smart account exec, paymaster
SmartAccount 11 Owner auth, batch exec, entry point, unauthorized rejection
DemoTokenVault 14 ERC-20, permit, vault ops, ERC-2771
Integration 9 E2E Mode 1 + Mode 2
BatchExecutor (fuzz) 5 Random DAGs, cascading failures (256 runs)
GasPaymaster (invariant) 3 Pool accounting invariants (128 runs x 64 depth)
GasComparison 1 Gas: baseline vs Mode 1 vs Mode 2
BatchExecutor (symbolic) 7 Halmos: DAG validity, skip semantics

Security properties verified: replay attacks revert, tampered calldata reverts, expired deadlines revert, wrong signer reverts, unauthorized reimburse/analytics/execute reverts, dependency failures cascade correctly, exhausted paymaster rejects.

Deployment

Local Anvil:

pnpm anvil          # Terminal 1
pnpm deploy:anvil   # Terminal 2

Sepolia testnet -- get Sepolia ETH from a faucet, an RPC URL from Alchemy/Infura, and an Etherscan API key:

export SEPOLIA_RPC_URL="https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY"
export DEPLOYER_PRIVATE_KEY="0xYOUR_PRIVATE_KEY"
export ETHERSCAN_API_KEY="YOUR_ETHERSCAN_API_KEY"
pnpm deploy:sepolia

DeployAll.s.sol deploys all 10 contracts in one transaction and wires permissions automatically: authorizes callers on the paymaster, whitelists demo contracts, deposits 1 ETH, sets up analytics recorders, mints 1M GVDT to the deployer.

Smart contracts

7 core contracts:

Contract What it does
BatchExecutor Multicall with DAG dependencies, failure isolation, per-call gas measurement
TrustedForwarder EIP-2771 + EIP-712 forwarder, batch support, nonce management, deadlines
GasPaymaster 3 modes (full sponsor, partial, ERC-20 payment), on-chain policy
MetaTxBatchForwarder Coordinates: verify sig, check paymaster, execute batch, record analytics
GasAnalytics On-chain gas tracking, global aggregates
MiniEntryPoint ERC-4337-inspired entry point for Mode 2
SmartAccount Minimal contract wallet, owner auth + batch execution

3 demo contracts: DemoToken (ERC-20 + EIP-2612 Permit + ERC-2771, symbol GVDT), DemoVault (deposit/stake/withdraw), DemoNFT (ERC-721 + ERC-2771, max 10k supply).

EIP stack: EIP-712 (structured signing), EIP-2771 (trusted forwarder), EIP-2612 (permit), ERC-4337 (paymaster + entry point), EIP-1559 (gas oracle), EIP-1153 (transient storage).

Relayer

Fastify 5, viem 2, TypeScript, Zod validation, pino logging.

Method Path What it does
POST /relay/mode1 Relay via TrustedForwarder (EIP-2771)
POST /relay/mode2 Relay via MiniEntryPoint (ERC-4337)
POST /relay/bundle Bundle multiple Mode 1 requests
POST /relay/session Create SSE session (returns UUID)
GET /relay/events/:id SSE event stream
GET /status/:txHash Transaction status + receipt
GET /gas-price EIP-1559 gas prices
GET /nonce/:address User nonce
GET /analytics On-chain stats
GET /recent-batches Persisted batch log (max 200)
GET /health Health check + relayer balance

The relayer supports SSE for real-time progress. Create a session, open the event stream, then relay with the X-SSE-Session header. The frontend's RelayerTerminal component renders these as a color-coded terminal.

cd relayer
pnpm dev       # http://localhost:3001
pnpm build     # production build

Frontend

React 18, Vite 6, wagmi v2, TailwindCSS 3, Recharts, TanStack Query v5.

4 main routes (Infra contains 3 sub-tabs):

  • User pages: Dashboard (stats, gas breakdown, recent activity), Batch Builder (DAG builder, faucet, phantom comparison, terminal), Bundler Demo (multi-user bundling with real EIP-712 signing)
  • Infra pages (tabbed): Analytics (break-even simulator, gas benchmarks), Paymaster Admin (deposit/withdraw, policies, contracts), Gas Calculator (slider-based savings projector)

10 components: Layout, DAGVisualizer, RelayerTerminal, PhantomComparison, GasSavingsChart, SponsorRunway, BatchStep, StatCard, ConnectWallet, RecentActivity.

cd frontend
pnpm dev       # http://localhost:5173
pnpm build     # production build

Gas benchmarks

From forge test --match-contract GasComparisonTest -vv on Cancun EVM. Scenario: permit, deposit, stake (3 DeFi ops).

Method Exec gas Intrinsic Total vs baseline
Baseline (3 individual txs) 160,964 63,000 223,964 --
Mode 1 (MetaTxBatchForwarder) 401,733 21,000 422,733 +89%
Mode 2 (MiniEntryPoint) 314,689 21,000 335,689 +50%

For a small 3-operation batch, the meta-tx overhead (signature verification, nonce management, paymaster checks, analytics recording, ERC-2771 forwarding) exceeds the 42k gas saved from combining 3 txs into 1. This is expected.

The real value is: users pay $0, one signature instead of three, atomic execution with dependency tracking, no ETH needed to get started, and savings improve as batch size grows.

See GAS_REPORT.md for the full report.

Commands

Command What it does
pnpm build:contracts Compile (dev profile)
pnpm test:contracts Run all 148 tests
pnpm test:gas Tests with gas report
pnpm deploy:anvil Deploy to local Anvil
pnpm deploy:sepolia Deploy to Sepolia + verify
pnpm anvil Start Anvil node
pnpm dev:relayer Start relayer (dev)
pnpm dev:frontend Start frontend (dev)
pnpm fmt:sol Format Solidity
pnpm snapshot Generate gas snapshot

On Linux/macOS you can also use make build, make test, make deploy-anvil, etc. Windows users should stick with pnpm commands.

Assumptions and limitations

Design assumptions:

  • Single relayer: the system assumes one trusted relayer per deployment. Relay decentralization (multi-relayer selection, reputation, staking) is out of scope.
  • Anvil determinism: gas measurements in GAS_REPORT.md are deterministic on Anvil / Cancun EVM. Mainnet numbers will differ due to base fee dynamics, warm storage from previous transactions, and validator MEV behavior.
  • Batch size: the BatchExecutor supports up to 32 calls per batch. DAG depth is uncapped but gas limits impose a practical ceiling of ~15-20 non-trivial calls per block.
  • ERC-2771 requirement: Mode 1 requires target contracts to inherit ERC2771Context. Unmodified third-party contracts must use Mode 2 (SmartAccount) instead.
  • No ERC-20 gas payment oracle: the GasPaymaster's ERC-20 payment mode uses a fixed tokenGasPrice set by the owner. There is no on-chain price oracle integration.

Known limitations:

  • 3-op batches cost more, not less: for the demo's 3-operation batch (approve + deposit + stake), the meta-tx overhead (signature verification, nonce management, paymaster checks, analytics recording) exceeds the 42k gas saved from combining 3 txs into 1. Break-even is ~8-10+ operations per batch.
  • Smart account deployment: Mode 2 SmartAccount addresses are deterministic but deployment is not counterfactual. The account must be deployed before use (DeployAll handles this).
  • No multi-chain: contracts are deployed per-chain. Cross-chain batching and bridging are not supported.
  • Relayer censorship: the relayer can refuse to submit a valid request (censorship), but cannot forge, modify, or replay user-signed operations.
  • Demo contracts only: DemoToken, DemoVault, and DemoNFT are minimal implementations for demonstration. They are not intended for production use.

Security considerations

The project implements defense-in-depth across the contract stack:

  • Replay protection: 2D nonce scheme (Mode 1) and sequential nonces (Mode 2), both chain-bound via EIP-712 domain separators. Every request includes a deadline timestamp.
  • Reentrancy: all state-changing functions use TransientReentrancyGuard (EIP-1153), saving ~5,000 gas per guard entry compared to OpenZeppelin's storage-based approach.
  • Access control: Ownable2Step for ownership. Paymaster reimbursement restricted to authorized callers. Analytics recording restricted to authorized recorders.
  • Signature validation: ECDSA recovery with zero-address checks. Invalid, expired, or replayed signatures revert atomically.
  • Trust model: users are untrusted (all actions require valid EIP-712 signatures), the relayer is semi-trusted (can censor but not forge), the deployer/owner is fully trusted.

This is an educational / hackathon project and has not been professionally audited. See SECURITY.md for the full threat model, access control matrix, and formal verification details.

Troubleshooting

forge/anvil/cast not found -- add Foundry to PATH. On Windows: $env:Path = "$env:USERPROFILE\.foundry\bin;$env:Path". On Linux/macOS: add export PATH="$HOME/.foundry/bin:$PATH" to your shell config.

Anvil connection refused -- make sure Anvil is running in a separate terminal (pnpm anvil).

"Could not load EIP-712 domain" -- contract addresses in frontend/.env are wrong or missing. Re-paste from deploy output.

Relay fails or simulation errors -- check relayer/.env has correct addresses, make sure the test user has GVDT tokens, restart the relayer after changing .env.

Dashboard shows all zeros -- you need to execute a batch first via Batch Builder.

Wallet not connecting -- MetaMask needs the Anvil network (chain ID 31337, RPC http://127.0.0.1:8545), and you need to import account #1.

Windows make not found -- use pnpm commands instead (pnpm test:contracts, pnpm deploy:anvil).

Need to start over -- stop all terminals, re-run setup-local.ps1 (Windows) or setup-local.sh (Linux/macOS), then start relayer and frontend again.

About

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages