Five projects, one Discord server, five invites. Each invite is a door to one project's rooms: press Verify at the gate, pass a Cloudflare Turnstile check, and that project's channels appear. Nothing else does. The server went up on 19 September; the doors went out on every account on 20 September. The door page, which lists all five: plex.meex.work.
The shape
Discord has no per-user sharding. Visibility is a role per project (V:corpus, V:playset, V:retcg, V:paired, V:shyguy) and a category per project whose overwrites deny @everyone and allow that role. Until the role exists on you, the category does not.
Each project has four rooms: announcements (followable, so another server can subscribe), general, support as a forum with bug / question / idea / answered tags so an answer stays findable instead of scrolling away, and a log. One shared area, THE PLEX, opens to anyone verified through any gate. Staff has an ops room and a gate log.
The door is three steps:
-
The invite lands you in the channel it was created for. That is the one reliable thing Discord promises about an invite, so each project's invite was created on that project's gate channel.
-
The gate channel holds one pinned message with one Verify button. Pressing it returns an ephemeral link, signed with an HMAC and good for ten minutes, to a Turnstile page.
-
Passing Turnstile grants the role. The page calls siteverify, the Worker patches the member, the gate log records it.
A member can walk to another project's gate and verify there too. The invite is a default, not a lock; a lock would be Discord's join-request approval, which is a different product.
The bot has no process. Discord's Interactions Endpoint URL delivers every button press and slash command as a signed HTTP request (Ed25519 headers, a PING to answer with PONG, three seconds to respond or defer), so the whole thing is one Cloudflare Worker with a D1 database behind it. Turnstile runs on the same Worker, because a Discord modal cannot host a captcha.
Findings
What was assumed, what was measured, what the fix is.
Which invite a member used is not in the join event. GUILD_MEMBER_ADD does not carry it. Two ways exist: diff every invite's uses counter over the gateway, which races when two people join at once, or POST /guilds/{id}/members-search, which returns source_invite_code and inviter_id. The second is officially undocumented — Discord declined the pull request that would have documented it — and it is backed by Elasticsearch, so a member who joined twenty seconds ago can be missing from the answer. The fix is not to depend on it: the invite places the member in the gate channel, the gate grants the role, and members-search records the invite in parallel for the log. Nobody waits on the index.
The bot must hold every permission it writes into an overwrite, in that channel. Denying @everyone the right to send in the start category stripped that right from the bot too, and channel creation answered 403 until the bot's own managed role had an explicit allow on every category.
Two overwrites for one role collapse to the last one written. An allow and a deny for the same role, sent as two entries, leave one. Merge them per role before sending.
A new role lands at position one. Below everything it is meant to grant. Reorder roles after creating them so the bot sits above the project roles.
A text channel cannot become a forum. The channel PATCH allows type changes between text and announcement only. The support rooms were created as forums and the text rooms deleted.
A server banner needs boost level two, and the API will not tell you. The PATCH answers 200 and keeps nothing. Read the guild back after every write that depends on a boost tier. Level one, which the server has, unlocks the invite splash.
Native onboarding was refused on purpose. Discord's onboarding flow requires seven default channels, five of them writable by everyone. A /ventures command that lists the five gates does the job without opening the server up.
Cost
Zero for the infrastructure: the Worker, the database and Turnstile are all inside Cloudflare's free tier, and the bot costs nothing because it has no process. One boost, for the splash. The build was one day, 19 September, plus the branding and the seeded first posts the same evening.
What is left
No stranger has walked a gate yet. Every test so far was the operator, as himself and as each role. The first person who joins from one of these posts is the first real run of invite → gate → Verify → role, and the gate log is where it shows.
The doors
Each project, one thing worth a look, and its links.
Corpus — index baskets on Solana. 1,618 quant seats, each an on-chain basket with readable manager permissions. A manager change on the reference fund waits 14 days on chain; it delays the manager, never a withdrawal.
solcorpus.xyz · Discord · Telegram
Re:TCG — the card shop, the slab desk (every tokenized slab a wallet holds across six chains, comps beside each), and Price the Slab, a daily on Farcaster and Lens.
retcg.xyz/slabs · Discord · Telegram
Playset — one pool per character × set × grader × grade. Cards weigh in at comps from a signed daily table; pulls are commit–reveal. Devnet for now, no mainnet date.
playset.cards · Discord · Telegram
Paired — coins paired with tokenized stocks, as a Farcaster mini app. One tape for Ember's coins: hot, new, graduating, graduated — filtered to the ones with a stock on the other side, or by ticker. Launches go through Ember.
paired.meex.work · Discord
ShyGuy Studio — the iOS side. Apps shipped one at a time, each with its own launch film.
shyguy.studio · Discord
Or start at the door: plex.meex.work.