NFT marketplace, chain indexer and Discord staking bot for WEYU
The backend of a Solana NFT ecosystem, 2021 to 2022: an escrow marketplace with its own payouts and integrity checks, an indexer that read Magic Eden, and a Discord bot that gives roles to whoever is staking.
WEYU was a Dutch NFT ecosystem on Solana: a collection of its own called WEYU Pieces, a marketplace, a launchpad, an earn programme and a Discord bot. I worked on the backend from November 2021 to November 2022, in a team of about eight, and I wrote the bot alone. The public site was a Vue application and it is gone. What survives is the marketplace front end, the backend and the bot, so everything below is read off the code and the commit history. The company wound down in late 2022.
An address per order
My first job was the lottery for the pieces. Buying a ticket meant sending SOL, and a wallet cannot say who sent what to a shared address, so every order got an address of its own: a keypair derived from one BIP32 seed at m/44'/501'/<order id>', so the server stores no keys, only a row number. A poll compared the balance with the expected amount, with a hundred lamports of slack, and moved the ticket through open, fulfilled, refunded or collected. The draw itself was weighted by tickets, capped at a hundred per person, and 6,043 pieces were minted on mainnet.
That derivation trick is what the marketplace was later built on.
Three ways to sell the same NFT
The marketplace is a Next.js site. You connect a Solana wallet, sign a message with a timestamp in it, and the backend issues a token that lasts seven days and is renewed in a response header before it runs out. Each NFT page picks one of three listing providers.
The first attempt was Metaplex auctions, with bids and an instant sale price. It went into the front end in March 2022 and out again within three weeks, under a branch called marketplace-hotfix. Solana was congested that spring, the site carried a permanent banner about it, and an auction is a chain of five or six transactions that all have to land. The auction controller is still there; nothing routes to it.
What replaced it in two days was an escrow the backend runs. A listing gets a derived escrow address; the seller transfers the NFT there, and a poll marks the listing live when the token arrives. A buyer gets a derived deposit address and sends the price. When the balance covers it, the backend queues the payouts in priority order, the seller first, then the creator at the royalty written in the NFT metadata, then two per cent to WEYU, tops the escrow up to 0.05 SOL from a credit fund so it can pay the transfer fee, sends the NFT, and sweeps the escrow back into the fund. If the transfer fails, the buyer sees a Claim button and an unclaimed tab on their profile, and the backend tries again.
Later a real on-chain listing program was written, with a vanity address starting in WEYU. The backend builds the transaction, listing account seeded from a hash of the mint, the creators passed in so royalties settle on chain, and returns the serialised message for the wallet to sign. The program's own source is not in these three repositories. A Zilliqa chain was added in July 2022. The escrow stayed as a fallback, and by the end it still had listings in it.
An escrow sale as the database sees it. Nothing is a private key; every address is a row number run through the seed, and the integrity checks are what make the whole thing safe to run without a smart contract.
When the payout goes wrong
Moving SOL from a derived address is where it breaks. A transfer needs the fee on top, so if a deposit is a whisker short the send fails in simulation with a message saying how many lamports were missing; the code reads that number out of the log and retries with that much less. The transfer that pays the seller can succeed while the database write after it does not. So every sold or retracted listing is re-examined from three minutes after it finished, every ten minutes, until four checks pass: the buyer holds the NFT, every queued payment has a signature or can be found on the ledger by reading the deposit address's history, any deposit that never completed is refunded, and the escrow is empty. A payment found on chain but missing from the database is recorded rather than paid again.
One thing I never closed properly is in a comment: "there is a background task somewhere we forgot to cover". Listings that had left the escrow kept showing as for sale, and the fix I shipped deletes the stale row at the moment someone asks for it, rather than finding the task. A refund for a double deposit is a TODO that stayed a TODO.
Reading Magic Eden without permission
A marketplace with only its own listings is empty, so the backend indexed other people's. Collections are imported by update authority through Metaplex metadata, fifty-four of them whitelisted in code plus whatever the launchpad finished, with traits and a rarity rank per NFT. Listings and sales came from Magic Eden. Its public API pages twenty listings at a time and rate-limits, so a queue works through collections by how stale they are, with featured ones on a faster lane and a stuck sync reset after thirty minutes.
For sales as they happened I went to Magic Eden's RPC. The query I wanted sits behind a Cloudflare captcha; the global activity feed does not. So my sync asks for the last ten minutes of everything, five hundred rows a page with a pause between pages and a timeout that doubles up to eighty seconds before giving up, keeps only the latest event per mint, and turns initializeEscrow, cancelEscrow and exchange into list, delist and sale. An hourly check messaged Telegram when the feed fell more than two hours behind.
By the last commits most of those crons are commented out, with the reason written above them: the Alchemy plan's request limit.
A role that follows the chain
The bot came last, in nine commits over a week in October 2022. A project's Discord admin runs /configure-staking with the address of their staking protocol. A member runs /staking-set and gets a DM with a link carrying an encrypted server-and-user token, since Discord cannot verify a wallet itself. The page connects Phantom or Solflare, asks for a signature on a fixed sentence, and the bot checks it with tweetnacl and files the address. Every two minutes it loads every stake account of the Anchor program, the IDL fetched from the chain rather than shipped, and matches owners to members: "Current staker" is granted and taken away as the stake comes and goes, "Has staked" is granted once and never removed. State is two JSON files in an S3 bucket.
The pieces and how they talk. The backend never holds a wallet key; it holds seeds and row numbers, serialises transactions for the browser to sign, and keeps its own picture of the chain in MySQL.
The last commit to any of the three repositories is from 14 November 2022 and changes an RPC URL.