Overview · Screenshots · Architecture · Stack · Setup · Deploy
AirAuction is an end-to-end English-auction marketplace for ERC-721 NFTs. Sellers escrow their NFT into a Solidity contract; bidders register with a refundable deposit and place bids on-chain; an AI Auctioneer narrates the lot, answers bidder questions, and writes a signed log of its reasoning to IPFS and an on-chain agent registry so every recommendation is auditable.
| Capability | How it works |
|---|---|
| Trust-minimised escrow | NFT custody and ETH deposits live inside AuctionAirEscrow.sol — no custodial backend. |
| Reserve + deposit model | Sellers set a reserve price; bidders post a depositBps fraction of the starting bid to register, preventing wash bids. |
| Atomic settlement | A single settle() call transfers the NFT, pays the seller, deducts platform fees, and refunds losing deposits. |
| AI-narrated lots | The auctioneer agent generates lot copy and Q&A; a hash of its input and output is stored on-chain via the agent benchmark contract. |
| IPFS audit trail | Each auction's metadata and agent transcript is pinned to IPFS through Pinata, returning an ipfs:// CID referenced by both the contract and the UI. |
| SSR-rendered frontend | TanStack Start + Nitro deploys to Vercel as a hybrid SSR/edge app. |
| Landing | Dashboard | Live Auctions |
|---|---|---|
![]() |
![]() |
![]() |
| Hero, live auction strip, volume + bidder stats | Personalised home — your auctions, watchlist, bid activity | Countdown, highest bid, one-click bidding |
| Scheduled | My NFTs | Raise Auction |
|---|---|---|
![]() |
![]() |
![]() |
| Upcoming lots, register-ahead deposit flow | NFTs in your wallet via Reservoir — pick one to auction | Multi-step lot creation, approve + escrow in two tx |
AI Auctioneer — chat with on-chain receipt of the model call (input hash, output hash, tx hash on the benchmark contract). |
flowchart LR
subgraph Client["Browser"]
UI["TanStack Router UI<br/>React 19 + Tailwind 4"]
Wallet["MetaMask /<br/>EIP-1193 Provider"]
end
subgraph Edge["Vercel Edge / Nitro SSR"]
SSR["TanStack Start<br/>server entry"]
MW["errorMiddleware<br/>(src/start.ts)"]
end
subgraph Services["External Services"]
Reservoir["Reservoir API<br/>(wallet NFT inventory)"]
Pinata["Pinata<br/>(IPFS pinning)"]
Agent["AI Auctioneer Backend<br/>(/api/auctioneer)"]
end
subgraph Chain["Mantle Sepolia"]
Escrow["AuctionAirEscrow.sol<br/>(NFT + ETH escrow)"]
NFT["ERC-721 collections"]
Registry["Agent Identity +<br/>Reputation + Benchmark"]
end
UI -- "JSON-RPC / eth_*" --> Wallet
UI -- "fetch /api/auctioneer" --> Agent
UI -- "fetch /v7/users/.../tokens" --> Reservoir
UI -- "pinJSONToIPFS" --> Pinata
Wallet -- "createAuction / placeBid / settle" --> Escrow
Escrow -- "safeTransferFrom" --> NFT
Escrow -- "AuctionCreated / BidPlaced events" --> UI
Agent -- "writes benchmark tx" --> Registry
Agent -- "fetches model output" --> Pinata
SSR --> UI
MW --> SSR
Bid lifecycle sequence — click to expand
sequenceDiagram
actor Seller
actor Bidder
participant UI as Frontend (TanStack)
participant NFT as ERC-721
participant Escrow as AuctionAirEscrow
participant Agent as AI Auctioneer
participant IPFS as Pinata / IPFS
Seller->>NFT: approve(escrow, tokenId)
Seller->>UI: fill Raise Auction form
UI->>IPFS: pinJSONToIPFS(metadata)
IPFS-->>UI: ipfs://CID
UI->>Escrow: createAuction(..., metadataURI)
Escrow->>NFT: safeTransferFrom(seller, escrow, tokenId)
Escrow-->>UI: emit AuctionCreated(auctionId)
Bidder->>Escrow: registerForAuction(id) {deposit}
Escrow-->>UI: emit BidderRegistered
Bidder->>UI: ask auctioneer "is the reserve fair?"
UI->>Agent: POST /api/auctioneer {context}
Agent->>IPFS: pin reasoning log
Agent-->>UI: reply + onchain receipt (txHash)
Bidder->>Escrow: placeBid(id, amount)
Escrow-->>UI: emit BidPlaced
Note over Escrow: endTime reached
Bidder->>Escrow: settle(id)
Escrow->>NFT: transfer to winner
Escrow->>Seller: pay (bid - fee)
Escrow->>Bidder: refund losing deposits
| Layer | Folder / File | Responsibility |
|---|---|---|
| UI shell | src/components/ |
Reusable shadcn-style primitives: Navbar.tsx, AuctionCard.tsx, WalletButton.tsx, ChatBubble.tsx. |
| Routing | src/routes/ |
File-based routes for landing, dashboard, single-auction view, and AI agent page. Tree generated into src/routeTree.gen.ts. |
| State / hooks | src/hooks/ |
useAuctions.ts loads escrow state, useCountdown.ts ticks lot timers. |
| Chain access | src/services/auctionContract.ts |
Ethers v6 wrappers around the escrow contract: read auctions, place bids, settle, approve. |
| NFT inventory | src/services/nftApi.ts |
Reservoir API client — fetches the connected wallet's NFTs for the Raise Auction picker. |
| IPFS | src/services/ipfs.ts |
Pins auction + agent JSON via Pinata, returns ipfs://CID. |
| AI | src/services/aiAuctioneer.ts + src/services/agentRegistry.ts |
Talks to the auctioneer backend, fetches on-chain agent identity / reputation. |
| Config | src/config/ |
Env var schema (env.ts) and supported chains. |
| SSR entry | src/start.ts |
TanStack Start instance with an errorMiddleware that catches non-HTTP throws and renders a branded error page. |
| Router entry | src/router.tsx |
Builds the Router with a shared QueryClient. |
| Smart contract | blockchain/contracts/AuctionAirEscrow.sol |
The escrow, bid book, settlement, and refund logic. |
| Deploy scripts | blockchain/ignition/, blockchain/scripts/ |
Hardhat Ignition module + helper scripts for Mantle Sepolia. |
AirAuction/
├── assets/ # README screenshots (img1.png … img7.png)
├── blockchain/
│ ├── contracts/
│ │ └── AuctionAirEscrow.sol # Core auction escrow
│ ├── ignition/modules/
│ │ └── AuctionAirEscrow.ts # Hardhat Ignition deploy module
│ └── scripts/ # Deploy / mint / agent registration helpers
├── Agent/ # AI auctioneer backend (separate service)
├── src/
│ ├── components/ # UI primitives + feature components
│ ├── config/
│ │ ├── env.ts # VITE_* env loader
│ │ └── chains.ts # Supported chains + RPC config
│ ├── hooks/ # useAuctions, useCountdown, use-mobile
│ ├── lib/ # utils, format, error-page, error-capture
│ ├── routes/
│ │ ├── index.tsx # Landing
│ │ ├── dashboard.tsx # Dashboard shell + sidebar
│ │ ├── dashboard/
│ │ │ ├── index.tsx # Dashboard home
│ │ │ ├── live.tsx # Live auctions
│ │ │ ├── scheduled.tsx # Scheduled auctions
│ │ │ ├── my-auctions.tsx # Auctions I created
│ │ │ ├── my-nfts.tsx # NFTs in my wallet
│ │ │ ├── bids.tsx # My bid history
│ │ │ └── raise.tsx # Create new auction
│ │ ├── agents.tsx # AI Auctioneer chat
│ │ └── auction/$id.tsx # Single auction detail
│ ├── services/ # Contract / IPFS / NFT / AI clients
│ ├── types/ # Shared TypeScript types
│ ├── router.tsx # TanStack Router setup
│ └── start.ts # TanStack Start instance + middleware
├── package.json
├── tsconfig.json
└── vite.config.ts # tanstackStart + nitro + tailwind + viteReact
AuctionAirEscrow.sol is a single-file, non-upgradeable escrow contract.
| Function | Caller | Effect |
|---|---|---|
createAuction(...) | Seller | Transfers NFT into escrow, records reserve / starting / deposit / window, emits AuctionCreated. |
registerForAuction(id) | Bidder | Posts a refundable ETH deposit (startingBid * depositBps / 10_000). Required before bidding. |
placeBid(id, amount) | Registered bidder | Updates highestBid / highestBidder. Previous high bid is credited back to that bidder's withdrawable balance. |
settle(id) | Anyone, after endTime | If reserve met: transfers NFT to winner, pays seller (minus platformFeeBps), refunds losing deposits. Otherwise: returns NFT to seller. |
withdrawRefund(id) | Outbid / unregistered bidder | Pulls back any owed ETH (pull-payments). |
cancelUnstartedAuction(id) | Seller, before startTime | Returns NFT, refunds any early deposits. |
setFee(recipient, bps) | Owner | Updates platform fee (capped at 10%). |
Safety properties — click to expand
| Property | Implementation |
|---|---|
| Re-entrancy | Custom nonReentrant modifier on all state-changing entry points. |
| NFT custody | Implements IERC721Receiver.onERC721Received so it can receive via safeTransferFrom. |
| Pull payments | Losing bidders withdraw via withdrawRefund instead of being pushed ETH on every bid update — prevents griefing. |
| Fee cap | MAX_PLATFORM_FEE_BPS = 1_000 (10%) and MAX_DEPOSIT_BPS = 5_000 (50%) hard-coded. |
| Self-bid block | placeBid rejects the seller's address. |
The auctioneer is a separate Node service (under Agent/) consumed by the frontend through VITE_AGENT_API_URL. Each call is on-chain verifiable:
askAuctioneer(context, userMessage)
→ POST {agentApiUrl}/api/auctioneer
→ returns { reply, model, latencyMs, agentId, onchain: { txHash, inputHash, outputHash, ... } }| Concept | Where | Why |
|---|---|---|
| Agent identity | VITE_AGENT_IDENTITY_ADDRESS |
NFT-style identity contract — each agent has an agentId minted on-chain. |
| Agent reputation | VITE_AGENT_REPUTATION_ADDRESS |
Scoring contract that aggregates user feedback on past calls. |
| Agent benchmark | VITE_AGENT_BENCHMARK_ADDRESS |
Each model call writes keccak256(input) and keccak256(output) — anyone can replay the prompt and verify the hash. |
The frontend renders the onchain receipt in src/routes/agents.tsx as a small View on explorer link next to the reply.
- Node.js 20+
- Package manager — npm / bun / pnpm (this repo includes both
package-lock.jsonandbun.lock) - MetaMask wallet funded with Mantle Sepolia ETH (faucet)
- API keys — Pinata (IPFS), Reservoir (NFT inventory)
npm installCopy the template and fill in your values:
cp .env.example .env.local # if .env.example exists; otherwise create .env.localSee Environment Variables below.
cd blockchain
npm install
npm run compile
npm run deploy:mantle-sepolia:ignition
npm run deploy:agents:mantle-sepolia # identity + reputation + benchmarkCopy the deployed addresses back into .env.local as VITE_AUCTION_ESCROW_ADDRESS and the three VITE_AGENT_*_ADDRESS vars.
npm run dev # http://localhost:3000
npm run build # production build → .vercel/output/
npm run preview # preview the production build locallyAll public env vars are prefixed VITE_ so they're inlined by Vite at build time. The schema lives in src/config/env.ts.
Required & optional variables
| Variable | Required | Purpose |
|---|---|---|
VITE_AUCTION_ESCROW_ADDRESS |
yes | Deployed AuctionAirEscrow address on Mantle Sepolia. |
VITE_PUBLIC_RPC_URL |
optional | Fallback RPC when wallet isn't connected. Defaults to the chain config. |
VITE_PINATA_JWT |
yes (raising) | Pinata API JWT for IPFS pinning. |
VITE_RESERVOIR_API_KEY |
yes (My NFTs) | Reservoir API key. |
VITE_RESERVOIR_BASE_URL |
optional | Override default Reservoir endpoint. |
VITE_NFT_CONTRACTS |
optional | Comma-separated allow-list of NFT contract addresses. |
VITE_AGENT_API_URL |
yes (AI tab) | URL of the Auctioneer backend. Defaults to http://localhost:5050. |
VITE_AGENT_IDENTITY_ADDRESS |
optional | Agent identity contract address. |
VITE_AGENT_REPUTATION_ADDRESS |
optional | Agent reputation contract address. |
VITE_AGENT_BENCHMARK_ADDRESS |
optional | Agent benchmark contract address. |
VITE_AGENT_ID |
optional | Numeric ID of the auctioneer agent to display. |
Frontend ( |
| Script | What it does |
|---|---|
npm run dev |
Vite dev server with HMR. |
npm run build |
Production build through Nitro. |
npm run build:dev |
Build with source maps, no minification. |
npm run preview |
Serve the production build locally. |
npm run lint |
ESLint over the repo. |
npm run format |
Prettier write-mode. |
Blockchain (blockchain/package.json)
| Script | What it does |
|---|---|
npm run compile |
hardhat compile — builds all contracts. |
npm run deploy:mantle-sepolia |
Runs the legacy deploy script. |
npm run deploy:mantle-sepolia:ignition |
Deploys via Hardhat Ignition (recommended). |
npm run mint:mantle-sepolia |
Mints a test NFT for trying the auction flow. |
npm run deploy:agents:mantle-sepolia |
Deploys identity + reputation + benchmark. |
This repo is configured for Vercel via the Nitro Vite plugin.
flowchart LR
Push["git push"] --> Vercel["Vercel build"]
Vercel --> Vite["vite build"]
Vite --> Nitro["nitro emits<br/>.vercel/output/"]
Nitro --> Functions["Vercel Functions<br/>(SSR + server fns)"]
Nitro --> Static["Static assets<br/>(/assets)"]
Functions --> Edge["Fluid Compute"]
Static --> CDN["Vercel CDN"]
Vercel project settings — leave Framework Preset on auto-detect, leave Output Directory blank. Nitro writes the
.vercel/output/build manifest Vercel reads natively.Do not add a
vercel.jsonwithrewritesto/index.html— there is noindex.htmlin an SSR build, and that rewrite returns 404 for every URL.
Required env vars in the Vercel dashboard: copy the same VITE_* variables from your .env.local into Project Settings → Environment Variables.
|
Prieyan MN
Engineering |
Sanjay E
Engineering |
MadhanRaj M
Engineering |
Lakshanika RSM
Engineering |
Released under the MIT License. See LICENSE for details.
Crafted with care for the Mantle ecosystem · Back to top








