SpaceSeven NFT Marketplace
EVM + Concordium Indexer Drivers & a Shared TypeScript Marketplace Layer
Role: Built the chain-indexer "network drivers" for Ethereum and Concordium and worked on the shared TypeScript marketplace layer. The Go marketplace backend (Fiber + sqlx) was built by another team.
What it is. A multi-chain NFT marketplace — ERC-721 and ERC-1155, fixed-price sales and auctions — on Ethereum and Concordium, with white-label copies for partner brands.
Indexer drivers. Chain watching lives outside the marketplace backend in one network driver per chain. Each driver follows the marketplace contracts from a per-contract block cursor seeded at the deployment block, so a cold start backfills and a restart resumes. It decodes contract events — mint, transfer, listing, sale, bid, pause, close — into one chain-neutral protobuf event (23 types) and pushes it to the backend over an HMAC-SHA256-signed endpoint. The backend applies each event idempotently, fingerprinting it in the same database transaction as the state change, so driver retries never double-count a sale or a bid.
Shared TypeScript layer. A versioned private npm package used by 4 separate Next.js marketplace apps: API clients, MetaMask/WalletConnect and Concordium wallet adapters behind one interface, and a transaction layer on web3.js with 10 operations across 4 lot types, resolving contract addresses at runtime from the drivers' contract set.
Closing the loop. The UI marks a transaction pending when the wallet returns a hash and flips it only when the indexed event comes back over STOMP, or times it out after 5 minutes — the indexed chain state is the source of truth, not the client receipt.
Key Features
- Indexer network drivers for Ethereum and Concordium, outside the marketplace backend — a new chain is a new driver plus one enum value
- Per-contract block cursors seeded at each deployment block: cold starts backfill, restarts resume, contract redeploys are a seed migration
- ERC-721/1155 mint, transfer, listing, sale, bid, pause and close events decoded into one chain-neutral protobuf event (23 types)
- HMAC-SHA256-signed delivery, applied idempotently by the backend in the same DB transaction as the state change
- Shared TypeScript marketplace layer (private npm package) used by 4 Next.js apps: API clients, wallet adapters, web3.js transaction layer
- Transactions confirmed by the indexer over STOMP, with a 5-minute timeout for dropped or replaced transactions
Tech Stack
Indexer Drivers
Shared Layer
Frontend
Platform
Challenges & Solutions
Two Chains, One Marketplace Backend
The marketplace had to reflect state from two chains with very different models — EVM logs on Ethereum and contract indices on Concordium — without the backend talking to either chain.
One network driver per chain decodes contract events into a chain-neutral protobuf event and pushes it, HMAC-signed, to a single ingest endpoint. The backend never touches an RPC node, and adding a chain means a new driver plus one enum value.
Retries Without Double-Counting
Drivers retry on failure, so the same event can arrive twice — which would double-count a sale or a bid.
Each event is fingerprinted and inserted with ON CONFLICT DO NOTHING in the same database transaction as the state change, and processed only if the fingerprint was new: an exactly-once effect over at-least-once delivery.
Backfill Across Contract Redeploys
Marketplace contracts were redeployed several times across environments, and a restarted driver must neither miss nor re-scan months of blocks.
Each indexed contract has a cursor row seeded at its deployment block. A cold start backfills from deploy to head, a restart resumes from the cursor, and a redeploy is a migration that seeds the new addresses at their deploy block, safe to rerun.
Trusting the Indexer, Not the Wallet
A wallet returning a transaction hash does not mean the sale or bid happened; the UI still had to feel immediate.
The UI marks the transaction pending on the hash, subscribes to the token's STOMP channel and flips the status only when the indexed event arrives, with a 5-minute timeout for dropped or replaced transactions.