Skip to content

iarturo/ElaraPay

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

55 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

ElaraPay

A non-custodial USDC payment gateway built natively on Base.
Accept stablecoin payments in your storefront β€” no intermediaries, no custody, no friction.

Solidity Foundry Next.js OnchainKit USDC Supabase Base Sepolia


Why This Exists

Traditional payment processors charge 2.9% + $0.30 per transaction, enforce 3-7 day settlement windows, and require merchants to pass KYB checks that take weeks. For small and emerging brands, these costs and delays are prohibitive.

BasePaymentGateway eliminates all of that. It routes USDC payments directly from a customer's wallet to the merchant's wallet in a single atomic transaction β€” no middlemen, no custody, instant settlement. It's the payment infrastructure that Base was built for.

This project is a working proof of concept: a premium e-commerce storefront (ELARA) that accepts USDC payments on Base through a smart contract, using Coinbase OnchainKit for wallet connectivity, Circle's USDC as the settlement currency, and a Node.js + Supabase worker to track and index orders automatically.


Live Demo

🌐 Live App: https://elarapay.xyz Network: Base Sepolia Testnet
Contract: 0x43EE62E72CDf8CD941AD8e7c20e8B384f6b3D684
USDC (Sepolia): 0x036CbD53842c5426634e7929541eC2318f3dCF7e


Key Features

Feature Details
Escrow Model Funds are held in the contract until the admin marks the order as shipped, then released to the merchant.
USDC Native Built for Circle's USDC on Base. Stable, predictable, and denominated in dollars.
Backend-Created Orders Orders are created on-chain by the backend (admin) with a fixed price β€” the frontend never sets the price.
Replay Protection Each orderId can only be paid once. Prevents double-charge attacks at the contract level.
ReentrancyGuard + CEI Uses OpenZeppelin's ReentrancyGuard and follows the Checks-Effects-Interactions pattern.
Order Limits (M-02) Configurable minOrder / maxOrder bounds enforced on-chain.
EIP-2612 Permit Supports gasless approvals via payWithPermit for a better UX.
Rate Limiting IP and wallet-based rate limiting via Upstash Redis to prevent abuse.
Distributed Locking (M-01) Redis-based distributed lock prevents nonce racing on concurrent order creation.
Gas Optimized Custom errors instead of require strings. Immutable state variables. Minimal storage footprint.
OnchainKit Integration Coinbase Smart Wallet support via @coinbase/onchainkit β€” one-click wallet connection.
Event-Driven Fulfillment Emits PaymentReceived events that a dedicated Node.js backend listens to and indexes into Supabase.
Admin Controls Mark orders as shipped (releases funds), process refunds, rescue stuck tokens, pause/unpause.
ETH Rejection (M-05) Contract rejects unexpected ETH transfers to prevent accidental loss.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     ELARA Storefront                     β”‚
β”‚               (Next.js + OnchainKit + wagmi)             β”‚
β”‚                                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Connect  β”‚    β”‚ Product Card β”‚    β”‚  Transaction  β”‚  β”‚
β”‚  β”‚ Wallet   β”‚    β”‚ + Size/Color β”‚    β”‚    Status     β”‚  β”‚
β”‚  β”‚ (Smart)  β”‚    β”‚  Selection   β”‚    β”‚   (On-chain)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚       β”‚                 β”‚                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                 β”‚
        β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚     β”‚   Two Transactions    β”‚
        β”‚     β”‚   (Batched by OCK)    β”‚
        β”‚     β”‚                       β”‚
        β”‚     β”‚  1. USDC.approve()    β”‚
        β”‚     β”‚  2. gateway.payFor()  β”‚
        β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        β”‚                 β”‚
────────┼─────────────────┼──────────── Base Network ──────
        β”‚                 β”‚                 β”‚ (Emits PaymentReceived)
        β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
        β”‚     β”‚  BasePaymentGateway   β”œβ”€β”€β”€β”€β”€β”€
        β”‚     β”‚                       β”‚     β”‚
        β”‚     β”‚  β€’ Validates order    β”‚     β–Ό
        β”‚     β”‚  β€’ Marks fulfilled    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚     β”‚  β€’ Emits event        β”‚  β”‚      Node.js Worker     β”‚
        β”‚     β”‚  β€’ transferFrom β†’     β”‚  β”‚ (ethers.js + Supabase)  β”‚
        β”‚     β”‚    merchant wallet    β”‚  β”‚                         β”‚
        β”‚     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β€’ Listens via WebSocketsβ”‚
        β”‚                                β”‚ β€’ Decodes event data    β”‚
        β–Ό                                β”‚ β€’ Saves order to DB     β”‚
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β”‚  Coinbase β”‚         β”‚  USDC    β”‚
  β”‚  Smart    β”‚         β”‚ (Circle) β”‚
  β”‚  Wallet   β”‚         β”‚  ERC-20  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Payment Flow

  1. Customer connects their Coinbase Smart Wallet via OnchainKit
  2. Customer selects a product, size, and color
  3. Frontend calls the backend API (/api/create-order) which creates the order on-chain with a fixed price (backend is source of truth)
  4. OnchainKit batches two calls into one user approval:
    • USDC.approve(gateway, amount) β€” authorize the gateway to spend
    • gateway.payForOrder(amount, orderId) β€” execute the payment
  5. The contract validates the order, marks it as Paid, emits PaymentReceived, and holds USDC in escrow
  6. Admin calls markShipped() to release funds to the merchant, or refund() to return USDC to the buyer
  7. A Node.js backend worker listens for the event via Alchemy WebSockets and indexes into Supabase

Tech Stack

Smart Contracts (/contracts)

Technology Purpose
Solidity ^0.8.20 Contract language
Foundry (Forge) Testing, compilation, and deployment
USDC (Circle) Payment settlement currency

Frontend (/web)

Technology Purpose
Next.js 14 React framework with App Router
OnchainKit @coinbase/onchainkit Wallet connection, identity, and transaction components
wagmi v2 React hooks for Ethereum
viem TypeScript-first EVM interactions
TailwindCSS Utility-first styling

Backend API (/web/src/app/api)

Technology Purpose
Next.js API Routes Server-side order creation and management
Upstash Redis Rate limiting and distributed locking (nonce racing prevention)
nanoid Cryptographically secure order ID generation
Supabase PostgreSQL database for order persistence and idempotency

Backend Worker (/worker)

Technology Purpose
Node.js Runtime environment
ethers.js v6 WebSocket provider and contract interactions
Supabase PostgreSQL database for storing order and payment records

Project Structure

Base/
β”œβ”€β”€ contracts/                    # Foundry project
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   └── BasePaymentGateway.sol    # Core payment gateway contract (escrow model)
β”‚   β”œβ”€β”€ test/
β”‚   β”‚   └── Gateway.t.sol             # Comprehensive Forge test suite
β”‚   β”œβ”€β”€ script/
β”‚   β”‚   └── DeployGateway.s.sol       # Deployment script
β”‚   β”œβ”€β”€ foundry.toml                  # Forge config (Etherscan V2 verification)
β”‚   └── .env.example                  # Environment variable template
β”‚
β”œβ”€β”€ web/                          # Next.js storefront
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”‚   β”œβ”€β”€ page.tsx              # Main storefront (ELARA)
β”‚   β”‚   β”‚   β”œβ”€β”€ layout.tsx            # Root layout with Providers
β”‚   β”‚   β”‚   β”œβ”€β”€ globals.css           # Design system + animations
β”‚   β”‚   β”‚   └── api/
β”‚   β”‚   β”‚       └── create-order/
β”‚   β”‚   β”‚           └── route.ts      # Backend order creation API
β”‚   β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”‚   └── Providers.tsx         # wagmi + OnchainKit + React Query
β”‚   β”‚   └── lib/
β”‚   β”‚       β”œβ”€β”€ contracts.ts          # ABI + contract addresses
β”‚   β”‚       β”œβ”€β”€ products.ts           # Product catalog (source of truth)
β”‚   β”‚       └── redis.ts              # Upstash Redis client
β”‚   └── package.json
β”‚
β”œβ”€β”€ worker/                       # Node.js Event Indexer
β”‚   β”œβ”€β”€ index.js                  # Listens to on-chain events and pushes to Supabase
β”‚   └── package.json
β”‚
β”œβ”€β”€ .gitignore
└── README.md

Smart Contract

BasePaymentGateway.sol

A single, focused contract that manages the full order lifecycle: Create β†’ Pay β†’ Ship/Refund, with USDC held in escrow.

Core Functions:

Function Access Description
createOrder() Owner Creates an order with fixed price, bound to a specific buyer
payForOrder() Buyer Pays for an existing order (requires prior USDC approval)
payWithPermit() Buyer Pays using EIP-2612 permit (gasless approval)
markShipped() Owner Releases escrowed USDC to the merchant
refund() Owner Returns escrowed USDC to the buyer
setLimits() Owner Sets min/max order amount bounds
rescueERC20() Owner Rescues stuck tokens from the contract
pause() / unpause() Owner Emergency pause mechanism
getOrder() Public View helper to query order details

Security Audit Fixes Applied:

ID Severity Fix
M-01 Medium Distributed lock (Redis) prevents nonce racing in backend
M-02 Medium On-chain minOrder / maxOrder bounds
M-03 Medium rescueERC20() to recover stuck tokens
M-04 Medium Explicit allowance check after permit try/catch
M-05 Medium receive() reverts to reject unexpected ETH
B-01 Backend Validate order amount against contract bounds before createOrder
B-02 Backend Per-wallet rate limiting via Upstash Redis
B-03 Backend Cryptographically secure order IDs via nanoid
I-01 Info getOrder() view helper for easier integrations
I-03 Info Rescued event emitted on token recovery

Additional Security:

  • OpenZeppelin Ownable2Step β€” Two-step ownership transfer prevents accidental loss
  • OpenZeppelin ReentrancyGuard β€” Protects all state-changing external functions
  • OpenZeppelin Pausable β€” Emergency circuit breaker
  • CEI Pattern β€” State changes before external calls
  • Custom errors β€” Gas-efficient error handling (no string storage)
  • Immutable USDC β€” usdc address set once at deploy time

Test Suite

The contract is tested with 10 test cases covering:

Category Tests
Positive flows Successful payment, multiple orders, allowance checks
Input validation Zero amount, empty order ID
Allowance checks Insufficient approval, insufficient balance
Replay protection Same order ID from same buyer, same order ID from different buyers
State verification Balance assertions, event emission, fulfillment mapping

Run Tests

cd contracts
forge test -vvv

Getting Started

Prerequisites

1. Clone the Repository

git clone https://github.com/iarturo/ElaraPay.git
cd ElaraPay

2. Smart Contract Setup

cd contracts
cp .env.example .env
# Edit .env with your deployer key and USDC address

Compile:

forge build

Test:

forge test -vvv

Deploy to Base Sepolia (with verification):

source .env && forge script script/DeployGateway.s.sol:DeployGateway \
  --rpc-url $BASE_SEPOLIA_RPC_URL \
  --broadcast \
  --verify \
  --verifier etherscan \
  --verifier-url "https://api.etherscan.io/v2/api?chainid=84532" \
  --etherscan-api-key $BASESCAN_API_KEY

3. Frontend Setup

cd web
npm install

Create a .env.local file:

NEXT_PUBLIC_ONCHAINKIT_API_KEY=your_coinbase_api_key
NEXT_PUBLIC_GATEWAY_ADDRESS=0x43EE62E72CDf8CD941AD8e7c20e8B384f6b3D684
NEXT_PUBLIC_USDC_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e
NEXT_PUBLIC_CHAIN=sepolia
NEXT_PUBLIC_ALCHEMY_ID=your_alchemy_api_key

Create a .env.server.local file (server-side secrets):

ADMIN_PRIVATE_KEY=0xYourAdminPrivateKey
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_KEY=your_supabase_service_key
UPSTASH_REDIS_REST_URL=your_upstash_redis_url
UPSTASH_REDIS_REST_TOKEN=your_upstash_redis_token

Run the development server:

npm run dev

Open http://localhost:3000 to see the storefront.

4. Backend Worker Setup

The worker listens for on-chain events and logs payments to Supabase.

cd worker
npm install

Create a .env file in the worker directory:

ALCHEMY_WSS=wss://base-sepolia.g.alchemy.com/v2/your_alchemy_api_key
GATEWAY_ADDRESS=0x43EE62E72CDf8CD941AD8e7c20e8B384f6b3D684
SUPABASE_URL=https://your-project-id.supabase.co
SUPABASE_SERVICE_KEY=your_supabase_service_role_key
MERCHANT_WEBHOOK_URL=https://merchant.example/webhooks/elarapay

Run the worker:

node index.js

When MERCHANT_WEBHOOK_URL is set, each indexed payment sends:

{
  "orderId": "ord_...",
  "amount": 42,
  "buyer": "0x...",
  "txHash": "0x...",
  "timestamp": "2026-05-30T00:00:00.000Z"
}

Failed webhook deliveries retry 3 times with exponential backoff.


Deployment

Network Contract USDC Address Chain ID
Base Sepolia 0x43EE62E...D684 0x036CbD53842c5426634e7929541eC2318f3dCF7e 84532
Base Mainnet Not yet deployed 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 8453
Platform URL
Frontend https://elarapay.xyz
Contract (BaseScan) View on BaseScan

To switch to mainnet, update the USDC address in your .env and change the chain configuration in Providers.tsx from baseSepolia to base.


Roadmap

  • Refund mechanism β€” Admin-initiated refunds with on-chain audit trail
  • Webhook notifications / indexing β€” Node.js worker listens to events and pushes to Supabase
  • Multi-token support β€” Accept ETH and other ERC-20s alongside USDC
  • Merchant dashboard β€” Real-time order tracking via event indexing
  • Multi-merchant support β€” Route payments to different merchants per product
  • Mainnet deployment β€” Production launch on Base Mainnet

Built With

Base USDC OnchainKit Foundry Next.js


License

This project is licensed under the MIT License. 2026


Built with πŸ’™ on Base β€” bringing the world onchain, one payment at a time.

About

A non-custodial B2C payment gateway built on Base using OnchainKit and Smart Wallets. Enables zero-gas USDC checkouts for e-commerce.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors