Click the image above to watch the project introduction and demo
Research Project: Exploring the feasibility of gasless GameFi on EVM-compatible chains through EIP-2612/ERC-7604 Permit patterns and Backend Relayer architecture.
- Overview
- Key Features
- Quick Start
- Architecture
- Core Flows
- Smart Contracts
- Security Audit
- Gas Costs
- Development
The MoeGirls Project is a research-focused implementation investigating how to build truly gasless GameFi experiences on EVM-compatible blockchains. By leveraging EIP-2612 (ERC-20 Permit) and ERC-7604 (ERC-1155 Permit) standards combined with a Backend Relayer pattern, we enable users to interact with blockchain without ever paying gas fees.
Traditional blockchain games face a significant UX barrier: users must hold native tokens (ETH, MATIC, etc.) to pay transaction fees. This project eliminates that friction by:
- Zero Gas for Users: All blockchain interactions are free for players
- Simple Wallets: Users only need a basic EOA wallet (MetaMask, etc.)
- Standard Patterns: Using audited EIP standards (2612, 7604, 712)
- Backend Pays Gas: Game backend covers all transaction costs
We migrated away from the complex Safe Smart Account + ERC-4337 stack to a simpler EOA + Permit architecture:
| Aspect | Old (Safe + AA) | New (EOA + Permit) | Improvement |
|---|---|---|---|
| User Setup | Deploy Safe contract (~500k gas) | Just connect wallet | ✅ Instant |
| Transaction Flow | UserOperation → Bundler → EntryPoint → Safe | Permit signature → Backend → Contract | ✅ Simpler |
| Gas Cost | Higher (multi-step validation) | Lower (direct execution) | ✅ ~30% cheaper |
| Attack Surface | Large (AA infrastructure) | Small (standard EIP patterns) | ✅ More secure |
| User Control | Shared (Safe owners) | Full (EOA private key) | ✅ True ownership |
- 🎮 Gasless Gameplay: Users interact without holding native tokens
- 🔐 EIP Standards: Built on EIP-2612, ERC-7604, and EIP-712
- 💰 Flexible Economics: Deposit, withdraw, trade NFTs - all gasless
- 🛡️ Security Audited: Analyzed with Slither and Mythril
- ✅ 100% Test Coverage: 161/161 tests passing
- 📦 OpenZeppelin: Using audited OZ contracts v5.0.0
- Node.js >= 18.x (LTS recommended)
- npm or yarn
# Clone the repository
git clone https://github.com/your-org/MoeGirlsProject-Contract
cd MoeGirlsProject-Contract
# Install dependencies
npm installnpm run compilenpm test# Slither static analysis
npm run slither
# Mythril symbolic execution (requires Python + Mythril)
myth analyze artifacts/contracts/YourContract.sol/YourContract.jsongraph TB
User[👤 User EOA Wallet]
Frontend[🟩 Frontend dApp]
Backend[🟨 Backend Relayer]
Contracts[🟥 Smart Contracts]
User -->|1. Sign Permit Gasless| Frontend
Frontend -->|2. Submit Signature| Backend
Backend -->|3. Execute Transaction<br/>Backend Pays Gas| Contracts
Contracts -->|4. Transfer Assets| User
style User fill:#e1f5fe
style Frontend fill:#c8e6c9
style Backend fill:#fff9c4
style Contracts fill:#ffcdd2
-
User Signs Permit (Off-chain, Free)
- User signs EIP-712 structured data in MetaMask
- No blockchain transaction, zero gas cost
- Signature contains: spender, amount, deadline, nonce
-
Frontend Submits to Backend
- Frontend sends signature to backend API
- Backend validates signature format and deadline
-
Backend Executes Transaction
- Backend calls contract with user's signature
- Backend's wallet pays ETH gas fees
- Contract verifies signature using
ecrecover
-
Assets Transferred to User
- Tokens/NFTs sent to user's EOA address
- User sees balance update immediately
The backend acts as a trusted relayer that:
- ✅ Accepts signed Permits from users
- ✅ Pre-validates with
eth_callbefore submitting transactions - ✅ Pays gas for all transactions
- ✅ Cannot steal user funds (signatures are scoped)
- ✅ Simplifies UX (users never need ETH)
Pre-Transaction Validation:
Before submitting any transaction, the backend MUST use eth_call to simulate execution:
// Simulate transaction (no gas cost, no state change)
const result = await provider.call({
to: contractAddress,
data: encodedFunctionCall
});
// Only proceed if simulation succeeds
if (result) {
await wallet.sendTransaction(...);
}Security: Even if backend is compromised, attackers can only execute transactions that users explicitly signed (with amount/deadline limits).
Purpose: Enable users to withdraw in-game MOE tokens to their wallet through a time-locked vesting schedule.
Key Contracts: VestingWalletFactory, StageBasedVestingWallet
sequenceDiagram
participant User as 👤 User
participant Frontend as 🟩 Frontend
participant Backend as 🟨 Backend
participant Factory as 🟥 VestingFactory
participant Vesting as 🟥 VestingWallet
participant MOE as 🟥 MOEToken
User->>Frontend: Request withdrawal (400 MOE)
Frontend->>Backend: POST /api/withdrawals
Note over Backend: Validate request<br/>Check balance ≥ 400 MOE<br/>Must be divisible by 4
Backend->>Backend: eth_call simulation:<br/>createVesting(userEOA, 400 ether)<br/>✅ Verify will succeed
Backend->>Factory: createVesting(userEOA, 400 ether)
Note over Factory: Backend pays gas (~375k)
Factory->>Vesting: Deploy new VestingWallet
Factory->>MOE: mint(vestingWallet, 400 ether)
Factory-->>Backend: ✅ vestingWallet address
Backend->>Backend: Deduct 400 MOE from DB balance
Backend-->>Frontend: ✅ Withdrawal initiated
Note over Vesting: Wait 30 seconds
User->>Frontend: Click "Claim Stage 1"
Frontend->>Backend: POST /api/vestings/release
Backend->>Vesting: release(MOEToken)
Note over Vesting: Calculate releasable<br/>25% = 100 MOE
Vesting->>MOE: transfer(userEOA, 100 ether)
MOE-->>User: 💰 100 MOE received
Note over User: Repeat at 60s, 90s, 120s<br/>to claim 50%, 75%, 100%
Vesting Schedule:
- 30s: 25% released (100 MOE)
- 60s: 50% released (200 MOE total)
- 90s: 75% released (300 MOE total)
- 120s: 100% released (400 MOE total)
Why Vesting?
- Prevents instant dumps
- Encourages long-term engagement
- Creates predictable token velocity
Purpose: Allow users to deposit MOE tokens from their wallet into the game (gasless).
Key Contracts: DepositContract, MOEToken (EIP-2612)
sequenceDiagram
participant User as 👤 User
participant MetaMask as 🦊 MetaMask
participant Frontend as 🟩 Frontend
participant Backend as 🟨 Backend
participant Deposit as 🟥 DepositContract
participant MOE as 🟥 MOEToken
User->>Frontend: Enter deposit amount (400 MOE)
Frontend->>Backend: GET /api/deposits/prepare
Backend-->>Frontend: {owner, spender, value, nonce, deadline}
Frontend->>MetaMask: Request EIP-2612 Permit signature
Note over MetaMask: User signs:<br/>Approve 400 MOE to DepositContract<br/>Deadline: 5 minutes
MetaMask-->>Frontend: ✅ signature {v, r, s}
Frontend->>Backend: POST /api/deposits<br/>{amount, deadline, v, r, s}
Backend->>Backend: eth_call simulation:<br/>depositWithPermit(...)<br/>✅ Verify signature & balance
Backend->>Deposit: depositWithPermit(user, 400, deadline, v, r, s)
Note over Backend: Backend pays gas (~214k)
Deposit->>MOE: permit(user, depositContract, 400, deadline, v, r, s)
MOE->>MOE: Verify signature (ecrecover)<br/>Set allowance[user][deposit] = 400
MOE-->>Deposit: ✅ Permit granted
Deposit->>MOE: transferFrom(user, owner, 400 ether)
MOE->>MOE: Transfer 400 MOE<br/>from User → Platform Owner
MOE-->>Deposit: ✅ Transfer complete
Deposit->>Deposit: Record deposit<br/>emit DepositMade(user, amount)
Deposit-->>Backend: ✅ txHash
Backend->>Backend: Update DB<br/>Add 400 MOE to user balance
Backend-->>Frontend: ✅ Deposit successful
Frontend-->>User: ✅ 400 MOE deposited!
EIP-2612 Permit:
- User signs approval message (off-chain, free)
- Backend submits signature with transaction
- Single transaction: approve + transfer (atomic)
Gas Savings:
- Traditional:
approve()(46k) +transferFrom()(58k) = 104k gas - With Permit:
depositWithPermit()= 214k gas (but backend pays, not user) - User savings: 104k gas = $0.00 (user pays nothing)
Purpose: Mint game NFT cards using in-game MOE tokens (gasless).
Key Contracts: MoeGirlsNFT, MOEToken
Architecture: tokenId = cards.id (ERC-1155 Fungible Mode)
- Each card type has a unique
cards.idin database - The NFT tokenId directly uses this
cards.id - Multiple users can mint the same card type (same tokenId)
- Balance tracking via ERC-1155
balanceOf(user, tokenId)
sequenceDiagram
participant User as 👤 User
participant MetaMask as 🦊 MetaMask
participant Frontend as 🟩 Frontend
participant Backend as 🟨 Backend
participant NFT as 🟥 MoeGirlsNFT
participant MOE as 🟥 MOEToken
User->>Frontend: Select card to mint<br/>Cost: 1000 MOE
Frontend->>Backend: POST /api/nft/prepare<br/>{cardId: "card_12345"}
Backend-->>Frontend: {owner, spender, value: 1000, nonce, deadline}
Frontend->>MetaMask: Request EIP-2612 Permit signature
Note over MetaMask: User signs:<br/>Approve 1000 MOE to NFT Contract<br/>Deadline: 5 minutes
MetaMask-->>Frontend: ✅ signature {v, r, s}
Frontend->>Backend: POST /api/nft/mint<br/>{cardId, deadline, v, r, s}
Backend->>Backend: Upload metadata to IPFS<br/>Get metadataURI
Backend->>Backend: eth_call simulation:<br/>mintWithPermit(...)<br/>✅ Verify signature & balance
Backend->>NFT: mintWithPermit(payer: user, to: user,<br/>amount: 1, cardId, metadataURI,<br/>price: 1000, deadline, v, r, s)
Note over Backend: Backend pays gas (~159k)
rect rgb(240, 255, 240)
Note over NFT,MOE: Atomic Operation
NFT->>MOE: permit(user, NFT, 1000, deadline, v, r, s)
MOE->>MOE: Verify signature<br/>Set allowance = 1000
NFT->>MOE: transferFrom(user, owner, 1000 ether)
MOE->>MOE: Payment: 1000 MOE<br/>User → Platform Owner
NFT->>NFT: _mint(user, tokenId, amount: 1)
NFT->>NFT: Store: _cardIds[tokenId] = "card_12345"<br/>_tokenURIs[tokenId] = "ipfs://..."
end
NFT->>NFT: emit NFTMinted(user, tokenId, cardId)
NFT-->>Backend: ✅ tokenId = 123
Backend->>Backend: Optional: Remove card from DB
Backend-->>Frontend: ✅ Mint successful, tokenId: 123
Frontend-->>User: ✅ NFT minted! TokenId: 123
Features:
- Permit-based Payment: Single transaction for approval + payment + mint
- Gasless for User: Backend covers
159k gas ($0.05 at 20 gwei) - Metadata on IPFS: Decentralized storage for card images/data
- ERC-1155: Support for multiple copies (e.g., 10x of same card)
Purpose: Enable peer-to-peer NFT trading with gasless approvals and order matching.
Key Contracts: MoeGirlsMarketplace, MoeGirlsNFT (ERC-7604), MOEToken
sequenceDiagram
participant Seller as 👤 Seller
participant Buyer as 👤 Buyer
participant Frontend as 🟩 Frontend
participant Backend as 🟨 Backend
participant Market as 🟥 Marketplace
participant NFT as 🟥 MoeGirlsNFT
participant MOE as 🟥 MOEToken
rect rgb(255, 240, 240)
Note over Seller,Backend: Phase 1: Seller Lists NFT
Seller->>Frontend: List NFT #123 for 100 MOE
Frontend->>Seller: Sign ERC-7604 Permit<br/>(Approve NFT to Marketplace)
Seller->>Frontend: ✅ NFT Permit signature
Frontend->>Seller: Sign EIP-712 SellOrder<br/>(tokenId: 123, minPrice: 100)
Seller->>Frontend: ✅ SellOrder signature
Frontend->>Backend: POST /api/orders/sell<br/>{order, permitSig, orderSig}
Backend->>Backend: eth_call simulation:<br/>permit(...)<br/>✅ Verify NFT ownership & signature
Backend->>NFT: permit(seller, marketplace, true, deadline, v, r, s)
NFT->>NFT: Set approval: seller → marketplace
Backend->>Backend: Store order in DB (status: active)
end
rect rgb(240, 255, 240)
Note over Buyer,Backend: Phase 2: Buyer Bids
Buyer->>Frontend: Buy NFT #123 for 120 MOE
Frontend->>Buyer: Sign EIP-2612 Permit<br/>(Approve 120 MOE to Marketplace)
Buyer->>Frontend: ✅ MOE Permit signature
Frontend->>Buyer: Sign EIP-712 BuyOrder<br/>(tokenId: 123, maxPrice: 120)
Buyer->>Frontend: ✅ BuyOrder signature
Frontend->>Backend: POST /api/orders/buy<br/>{order, permitSig, orderSig}
Backend->>Backend: eth_call simulation:<br/>permit(...)<br/>✅ Verify MOE balance & signature
Backend->>MOE: permit(buyer, marketplace, 120, deadline, v, r, s)
MOE->>MOE: Set allowance: buyer → marketplace
Backend->>Backend: Store order in DB (status: active)
end
rect rgb(240, 240, 255)
Note over Backend,Market: Phase 3: Backend Matches Orders (onlyOwner)
Backend->>Backend: Matching Engine:<br/>Find: buyOrder.maxPrice ≥ sellOrder.minPrice<br/>Match found: 120 ≥ 100 ✅
Backend->>Backend: eth_call simulation:<br/>matchOrders(...)<br/>✅ Verify approvals & nonces
Backend->>Market: matchOrders(sellOrder, sellSig, buyOrder, buySig)
Note over Backend: Backend pays gas (~192k)<br/>Only owner can call (onlyOwner)
Market->>Market: Verify signatures (ecrecover)<br/>Check prices: 120 ≥ 100 ✅<br/>Check nonces (prevent replay)
Market->>MOE: transferFrom(buyer, seller, 100 ether)
MOE->>Seller: 💰 100 MOE payment
Market->>NFT: safeTransferFrom(seller, buyer, 123, 1, "")
NFT->>Buyer: 🎨 NFT #123 received
Market->>Market: Mark nonces as used<br/>emit OrderMatched(...)
Market-->>Backend: ✅ Trade executed
Backend->>Backend: Update DB: orders → completed<br/>Record trade history
end
Order Types:
| Order Type | Maker | Price Logic | Signature |
|---|---|---|---|
| Sell Order | Seller | minPrice (e.g., 100 MOE) |
EIP-712 SellOrder |
| Buy Order | Buyer | maxPrice (e.g., 120 MOE) |
EIP-712 BuyOrder |
Matching Logic:
IF buyOrder.maxPrice >= sellOrder.minPrice THEN
executionPrice = sellOrder.minPrice // Seller's ask price
buyer saves (120 - 100) = 20 MOE
END IF
Permit Standards Used:
- ERC-7604: NFT approval (setApprovalForAll with signature)
- EIP-2612: MOE token approval (ERC-20 Permit)
- EIP-712: Order signature (structured data)
Access Control:
- ✅ onlyOwner: Only the Backend (contract owner) can call
matchOrders() - ✅ Prevents MEV: Orders are matched off-chain, no front-running risk
- ✅ Order Cancellation: Users cancel orders via backend API (gasless), not on-chain
Gas Costs:
- User: 0 gas (only signs messages)
- Backend:
192k gas per matched order ($0.06 at 20 gwei)
| Contract | Purpose | Standards | Architecture Notes |
|---|---|---|---|
| MOEToken | ERC-20 game token with Permit | ERC-20, EIP-2612 | Standard implementation |
| DepositContract | Handle user deposits | EIP-2612 | Gasless deposits |
| MoeGirlsNFT | Game NFT cards | ERC-1155, ERC-7604 | tokenId = cards.id (ERC-1155 fungible) |
| MoeGirlsMarketplace | P2P NFT trading | EIP-712 | Off-chain orders + atomic swap |
| VestingWalletFactory | Create time-locked wallets | EIP-1167 (Clones) | 4-stage vesting |
| StageBasedVestingWallet | 4-stage vesting schedule | OpenZeppelin VestingWallet | 25%, 50%, 75%, 100% |
| ERC1155Permit | Permit for ERC-1155 | ERC-7604 (draft) | Gasless NFT approvals |
- OpenZeppelin Contracts v5.0.0: Audited, battle-tested implementations
ERC20,ERC20PermitERC1155Ownable,ReentrancyGuard,SafeERC20EIP712,ECDSA,NoncesVestingWallet,Clones
| Feature | Implementation | Benefit |
|---|---|---|
| Reentrancy Protection | ReentrancyGuard modifier |
Prevents reentrancy attacks |
| Access Control | Ownable (onlyOwner) |
All relayer functions restricted to backend owner |
| Permit Signatures | EIP-712 + ECDSA | Gasless approvals with cryptographic security |
| Nonce Management | OpenZeppelin Nonces |
Prevents signature replay attacks |
| SafeERC20 | OpenZeppelin library | Safe token transfers |
| EIP-1167 Proxies | Clones library | Gas-efficient vesting wallet creation |
All contracts have been analyzed using industry-standard security tools:
| Tool | Version | Status | Issues Found |
|---|---|---|---|
| Mythril | v0.24.8 | ✅ PASS | 0 critical, 0 high, 0 medium |
| Slither | v0.11.3 | ✅ PASS | 0 critical, 0 high (3 false positives), 0 medium (2 OZ internals) |
| Tests | Hardhat | ✅ PASS | 161/161 tests passing (100%) |
Mythril symbolic execution found zero vulnerabilities:
{
"error": null,
"issues": [],
"success": true
}Contracts Analyzed:
- ✅ DepositContract
- ✅ MoeGirlsNFT
- ✅ VestingWalletFactory
Vulnerabilities Checked:
- ✅ No integer overflow/underflow
- ✅ No exploitable reentrancy
- ✅ No unchecked external calls
- ✅ No delegatecall to untrusted callee
- ✅ No unprotected selfdestruct
Slither static analysis findings:
| Severity | Count | Status | Notes |
|---|---|---|---|
| 🔴 Critical | 0 | ✅ None | - |
| 🟠 High | 0 | ✅ None | 3 false positives (intentional Relayer pattern) |
| 🟡 Medium | 0 | ✅ None | 2 OpenZeppelin internals |
| 🔵 Low | 7 | ✅ Mitigated | Benign reentrancy, timestamp usage (standard practice) |
| ⚪ Informational | 2 | ✅ Accepted | Naming conventions (EIP standard names) |
Key False Positives Explained:
-
"Arbitrary from in transferFrom" - ✅ INTENTIONAL
- This is the core Backend Relayer pattern
- Users sign Permit → Backend calls on their behalf
- Protected by
onlyOwner+ signature verification
-
Benign Reentrancy - ✅ MITIGATED
nonReentrantmodifiers applied where needed- Calls only to trusted contracts (MOEToken, OpenZeppelin)
- No funds at risk
-
Timestamp Usage - ✅ ACCEPTABLE
- Standard practice for deadlines/vesting
- 15-second miner manipulation negligible for multi-minute/hour windows
✅ APPROVED FOR TESTNET DEPLOYMENT (Arbitrum Sepolia)
Mainnet Recommendation:
- ✅ Current security level: Acceptable
⚠️ Optional: Third-party audit of ERC1155Permit (custom EIP-7604 implementation)- ✅ All other contracts: Production ready
For full audit details, see SECURITY-AUDIT-REPORT.md.
All costs shown are paid by the backend, not users.
| Operation | Gas Cost | USD Cost* | User Pays |
|---|---|---|---|
| depositWithPermit() | 214,438 | $0.06 | $0.00 ✅ |
| mintWithPermit() | 158,202 | $0.05 | $0.00 ✅ |
| matchOrders() | 192,262 | $0.06 | $0.00 ✅ |
| release() [Vesting] | 67,805 | $0.02 | $0.00 ✅ |
| createVesting() | 370,823 | $0.11 | $0.00 ✅ |
* Assuming 20 gwei gas price, $3000 ETH
| Operation | Description | Gas Cost | User Pays |
|---|---|---|---|
| EIP-2612 Permit | MOE token approval | 0 (off-chain) | $0.00 ✅ |
| ERC-7604 Permit | NFT approval | 0 (off-chain) | $0.00 ✅ |
| EIP-712 Order | Marketplace order signature | 0 (off-chain) | $0.00 ✅ |
| Contract | Gas Cost | % of Block Limit |
|---|---|---|
| MOEToken | 1,151,632 | 3.8% |
| DepositContract | 894,546 | 3.0% |
| MoeGirlsNFT | 1,983,200 | 6.6% |
| MoeGirlsMarketplace | 1,220,688 | 4.1% |
| VestingWalletFactory | 1,481,942 | 4.9% |
Traditional Flow (User Pays):
approve() → 46,462 gas → $0.014
transferFrom() → 57,680 gas → $0.017
---------------------------------------------
TOTAL 104,142 gas $0.031 paid by user ❌
Permit Flow (Backend Pays):
User: Sign permit → 0 gas → $0.000 ✅
Backend: Execute with permit → 214,435 gas → $0.064 paid by backend
---------------------------------------------
TOTAL 214,435 gas $0.000 paid by user ✅
Result: Users save 100% of gas costs, backend pays ~2x but enables gasless UX.
npx hardhat nodeThis starts a local Ethereum node at http://localhost:8545.
npx hardhat run scripts/deploy.js --network localhost# Run all tests
npm test
# Run specific test file
npx hardhat test test/Flows.test.js
# Run tests with gas reporting
REPORT_GAS=true npm testOur test suite covers:
- Unit Tests: Individual contract functions
- Integration Tests: Multi-contract interactions
- Flow Tests: Complete user journeys (Flows 3-6)
- Security Tests: Signature verification, replay protection, access control
✅ 161/161 tests passing (100%)
✅ DepositContract 26/26 tests
✅ MoeGirlsNFT 29/29 tests ⭐ (updated for tokenId=cardId)
✅ MoeGirlsMarketplace 18/18 tests
✅ MOEToken 20/20 tests
✅ StageBasedVestingWallet 30/30 tests
✅ VestingWalletFactory 22/22 tests
✅ Flows (Integration) 16/16 tests
├─ Flow 3 (Withdraw) 2/2 tests
├─ Flow 4 (Deposit) 2/2 tests
├─ Flow 5 (NFT Mint) 2/2 tests
├─ Flow 6 (Marketplace) 7/7 tests
└─ ERC-7604 Permit 3/3 tests{
chainId: 31337,
url: "http://127.0.0.1:8545"
}npx hardhat run scripts/deploy.js --network arbitrumSepoliaConfiguration (.env):
PRIVATE_KEY=your_private_key_here
ARBITRUM_SEPOLIA_RPC=https://sepolia-rollup.arbitrum.io/rpc
ARBISCAN_API_KEY=your_arbiscan_api_key_here| Command | Description |
|---|---|
npm test |
Run all tests |
npm run compile |
Compile contracts |
npm run clean |
Clean artifacts |
npm run slither |
Run Slither security analysis |
npm run coverage |
Generate test coverage report |
