Skip to content

Instantly share code, notes, and snippets.

@doitian
Last active June 8, 2026 09:02
Show Gist options
  • Select an option

  • Save doitian/eaecadb3e74c2432db72468458312005 to your computer and use it in GitHub Desktop.

Select an option

Save doitian/eaecadb3e74c2432db72468458312005 to your computer and use it in GitHub Desktop.

Mint Gate — Mentor Review

Overall verdict

Mint Gate is a viable MVP for CKB: wallet connect, community creation, paid join, and gated link delivery all work as a coherent story. The core idea — prove membership with on-chain activity, serve gated content off-chain — is a sensible semi-decentralized pattern for Nervos.

The project is not mainnet-ready yet. The biggest risks are inconsistent chain/DB state (create and join both write on-chain before the database), misleading “delete” language when only the database row is removed, and access control that ultimately trusts the API rather than the chain. Fix those before adding marketplace, on-chain chat, or optional xUDT/NFT layers on top of membership cells.

Problems

1. Should a creator be allowed to delete a community when the on-chain record remains?

Short answer: Allow unlisting, not deletion — and be explicit with users.

What the app does today is reasonable for an MVP if named correctly. The delete flow verifies on-chain ownership, then removes the community and members from Supabase only. The on-chain cell (community ID + creator address) remains forever. That is not deletion; it is delisting from the directory.

Recommendation:

  • Rename the action in UI and API from “Delete” to “Hide community” or “Archive”.
  • Update copy to state: “This removes the community from search and dashboards. Your on-chain ownership record remains.”
  • Do not add member voting on removal for v1 — it adds governance complexity you have not designed for.
  • If members paid a mint price, add a simple policy: archived communities should not accept new members; consider whether existing members retain access to the hidden link (today they lose it when the DB row is gone — that may surprise paying users).

2. Viability on mainnet

Idea: Viable on CKB for small paid communities, creator clubs, and access to private channels.

This codebase: Not ready for mainnet without addressing:

Blocker Why it matters
Chain-then-DB ordering on create If /api/community/create fails after the tx is sent, an on-chain community exists with no listing — invisible unless rebuilt from chain
Same pattern on join User can pay gate fee, server crashes before DB insert — may be asked to pay again with no way to recover from chain
DB treated as source of truth Membership and communities should be derivable from on-chain cells/txs; DB should be a cache, not the authority
Join treats mempool acceptance as success sendTransaction only means the tx entered the node pool — not that it will confirm; see Review log
Gated content is API-gated hidden_link is returned by Supabase when isMember is true in the DB — not verified against an on-chain membership cell on each request
Shared hidden_link + wallet identity One payment + shared private key = unlimited access; no session limits or per-member credentials — see Credential sharing
No reconciliation job Nothing detects or repairs DB/chain drift
Placeholder / broken UI Community detail page still renders lorem ipsum after the description; footer links point to #
Economic model incomplete You as the service operator is not involved in the payment

3. On-chain first or DB first when creating a community?

Short answer: Do not treat this as a strict ordering problem (“DB first” vs “chain first”). Treat the chain as the source of truth and the database as a cache/index that can always be rebuilt from on-chain data.

Today the create flow signs and broadcasts the chain tx first, then POSTs to /api/community/create. If the POST fails, the user has spent ~301 CKB + fees for a community that does not appear anywhere. The same class of failure exists on join: if the user has sent the gate fee and the server crashes after sendTransaction but before the members row is inserted, the app has no record of payment and the user may be forced to pay again — even though the payment may already be confirmed on-chain.

Can both validate each other? Yes — but only if enough information lives in the transaction (cell data and/or witness) for your app to derive state without trusting a single successful API call. The DB row should be a derived copy, not the authority.

Recommended procedure (applies to both community creation and membership join):

  1. Encode the facts you need on-chain — e.g. communityId, creatorAddress, and for joins the member address and community reference in cell data or witness (metadata that does not need to stay private). If privacy is a concern, encrypt the data and only your app can see the plain text.
  2. Broadcast and wait for confirmation — do not grant membership or list a community on mempool acceptance alone; see Review log.
  3. Index from chain → DB — a background job (or on-demand “sync”) scans new confirmed transactions / live cells and upserts into Supabase.
  4. Treat Supabase as a cache — optimized for search, dashboards, and gated URL delivery; if the DB is empty, stale, or lost, rebuild it from chain data.
  5. Keep rich metadata off-chain when appropriate — name, description, guidelines, and hidden_link can remain in DB/IPFS; the chain cell proves who owns what and who paid to join, not the full community profile.
flowchart LR
    User -->|signed tx| Chain
    Chain -->|indexer scans confirmed cells/txs| DB
    DB -->|fast reads| App
    Chain -->|recovery / audit| App
Loading

Community creation specifically:

  • On-chain cell: communityId + creatorAddress (you already do this). Alternative: use a specified type script for the community cell so you can add business logic related verification on-chain. You can use on-chain features to generate unique communitId, like what type-id script does.
  • Off-chain: name, description, guidelines, hidden link — keyed by communityId.
  • If chain tx confirms but DB insert fails: indexer (or “Restore from chain” in UI) recreates the listing; creator does not redeploy.

Membership join specifically:

  • On-chain: gate fee to creator lock plus a membership cell (community type script, member lock) minted as a byproduct of the join tx; type script verifies the required fee was paid (see §9).
  • Indexer scans live membership cells (or confirmed join txs) and upserts members; if DB insert fails after confirmation, user does not pay again.

Practical MVP path:

  1. Short term: pending row + wait for confirmation + retry DB write (stops double-pay in the common crash case).
  2. Medium term: chain indexer that syncs communities and memberships into DB automatically.
  3. Long term: DB is fully replaceable from chain; API never invents membership that chain cannot prove.

Hoping users will “just try again” after a failed DB write is not acceptable once real CKB is involved. A user who already paid the gate fee must be recoverable from chain history alone.

4. Should memberships be transferable?

Short answer: Depends on product direction — pick one primary model.

Model Transferable? Best for
Community membership (access to a private link) No — soulbound to wallet Discord/Telegram groups, creator communities
Ticketing / event access Yes — or resell with rules Concerts, workshops, IRL events
Marketplace of communities Yes — with ownership transfer Secondary market for “seats”

For the current app (gated links to external communities), non-transferable memberships are the right default. Transferability invites scalping and support burden (“I bought this wallet’s membership”) without clear benefit.

If you later add gated ticketing, make transferability a per-community or per-event setting, not a global rule.

5. Credential sharing and private-key leakage

Concern: A user pays the gate fee, becomes a member, then shares or publishes the wallet private key so a group gets access to gated content from a single payment. Centralized services (e.g. YouTube) limit concurrent sessions and detect account-sharing patterns. Can Mint Gate do the same?

Short answer:****Not with the current wallet-only model. On CKB, whoever holds the private key is the member. Sharing the key is equivalent to sharing the account — there is no separate “session” layer today. Mint Gate checks user_address against the members table and reveals the same hidden_link to every requester who appears as that member. One key, unlimited concurrent users.

This is a product and architecture limit, not a small bug fix. Blockchain wallets are portable by design; you cannot cryptographically stop someone from copying a private key the way Netflix stops password sharing on a centralized account system.

What you can do (realistic mitigations):

Approach Effectiveness Fit for Mint Gate
Accept leakage for low-stakes communities Partial OK for cheap Discord/Telegram access if price assumes some sharing
Per-member invite links High for link leakage Generate a unique invite URL per member at join time (Discord single-use invite, etc.) instead of one shared hidden_link for the whole community.
Wallet sign-in + short-lived server session Medium After signMessage, issue a JWT; cap concurrent sessions per membership; revoke on abuse — recentralizes access control (fits semi-decentralized)
Periodic re-sign Medium Require a fresh signature every N hours to refresh session
Rate limits on API Low–medium Throttle hidden_link / community API per address and IP; slows abuse, does not stop key sharing
Gate at the destination Medium Discord invite limits, Telegram membership approval, manual kicks — the real gate may be the chat platform, not Mint Gate
Non-transferable membership cell / soulbound token Low for this threat Stops resale of the credential, not key sharing — same key still holds the cell/token
Terms of use Low Prohibit sharing; enforcement is social/legal, not technical

Comparison to YouTube: YouTube owns the player, the CDN, and the account system — it can count devices, IPs, and simultaneous streams. Mint Gate only proves “this address paid once” and (today) returns a static URL. Once that URL or key is public, replay is trivial.

Recommendation:

  1. Be explicit in creator docs: wallet membership is one key = one seat; sharing the key shares the seat. Price and community rules should assume some leakage.
  2. Strongest technical upgrade: move from one community hidden_link to per-member gated credentials (unique invite or proxied token issued only after wallet signature).
  3. If you add sessions: wallet proves ownership once; server issues time-limited access — closest analogue to YouTube’s concurrent-session limits, at the cost of more centralization.
  4. Do not promise “NFT membership stops piracy.” It does not. Key sharing bypasses transfer restrictions entirely.
  5. For streaming (e.g. video): a payment-channel network can meter delivery with frequent micropayments (per second, per chunk, or per segment) instead of a one-time gate fee — so access stays tied to an active payment flow, closer to how a centralized stream is billed for ongoing use. This is a different product shape from “pay once, reveal a link,” but it fits CKB’s micropayment story if you pursue premium media later.

For high-value gated content, the current “single shared link + wallet address” model is weak against intentional sharing. For CKB builder communities at low mint prices, social norms plus destination-platform controls may be enough. For anything resembling paid streaming or premium courses, you need per-user credentials and/or session-based access — not wallet address alone.

Review

6. Is the idea viable? What features are most useful?

Yes, with a narrow scope. The features that already deliver value:

  • Wallet connection and CKB identity
  • Creator-defined mint price (payment to creator on join)
  • On-chain proof of community creation (creator + community ID in cell data)
  • Membership dashboard and search
  • Hidden link revealed only to members/creators

Most useful to keep investing in:

  1. Reliable create/join (no orphan state)
  2. Clear creator tooling (edit description, archive, see members)
  3. Gate pass economics (creator receives funds; fees documented)

Defer until core is solid:

  • On-chain immutable chat
  • Full marketplace
  • xUDT / NFT protocol for every community by default

7. Better suited for gated ticketing or private chat rooms?

Use case Fit today What would need to change
Private chat / community link Strong fit You already gate a URL (Discord, Telegram, etc.)
Gated ticketing Weak fit Needs event date, ticket quantity, check-in/validation, refund rules, optional QR
Generic “protocol” Too broad Landing page mentions governance and portable identity — not built yet

Recommendation: Lead with “paid access to a private community link.” Add ticketing as a separate product mode later (limited supply + date + non-transferable or controlled transfer), not as a bolt-on to the same community object.

8. Marketplace for buying and selling token-gated communities?

Interesting as phase 2+, not now.

A marketplace needs:

  • Transferable membership or clear ownership of the community object
  • Escrow or atomic swap patterns
  • Dispute/refund policy
  • Discovery and reputation

Without fixing trust and state consistency, a marketplace amplifies every bug (orphan txs, delisted communities, disputed ownership).

Suggestion: If you want marketplace energy early, allow secondary sale of memberships only for communities that opt in — still simpler than trading whole communities.

9. Per-community xUDT or NFT protocol for membership?

Terminology first: xUDT is for fungible tokens (many identical units). It is not an NFT protocol. Limited “one membership = one seat” is closer to a unique membership cell or a dedicated NFT-style protocol — only add xUDT or NFT support when they bring a concrete benefit to the app (fungible rewards, fractional shares, transferable passes with fungible liquidity, etc.). Do not adopt xUDT just because it sounds on-chain-native.

Recommended starting point (before xUDT):

  1. Deploy a unique type script per community (or one shared script with communityId in args).
  2. When a user sends the gate fee and builds the joining transaction, mint a membership cell as a byproduct of that tx — an output carrying that community’s type script, locked to the member’s address.
  3. The type script (or the unlock conditions it enforces) verifies on-chain that the required gate fee was paid to the creator — membership is proven by possession of a valid cell, not by a Supabase row alone.
  4. Index those live membership cells into the DB (cache), same as Problem 3.

This gives you on-chain verifiable membership without jumping to fungible token economics on day one.

When xUDT is worth adding later:

  • Creators want fungible community tokens (rewards, tips, fractional ownership).
  • You need a standard fungible interface for wallets/exchanges.
  • You want members to aggregate or split balances in a fungible way.

Migration path: If you later adopt xUDT, allow users to migrate existing membership cells → xUDT tokens (burn or consume the membership cell, mint equivalent xUDT in a defined 1:1 or policy-driven ratio). Do not strand early adopters with two incompatible on-chain representations.

Recommendation: MVP = gate fee tx + membership cell + type-script fee check + chain indexer. xUDT or a separate NFT protocol only when a product feature clearly needs fungibility or NFT semantics. The helpers in lib/ckb/xudt.ts may be useful for a later migration layer — not as the default join path for every community.

10. Transfer of community ownership?

Useful “plus” feature, lower priority than create/join reliability.

Use cases: creator sells the community, steps down, or hands ops to a DAO.

Requirements if you add it:

  • On-chain field or new cell version for creatorAddress
  • Explicit transfer transaction signed by current owner
  • DB update only after chain transfer confirms
  • UI: pending transfers, member notification

Do not build this before ADR-level clarity on what “owning” a community means (on-chain record vs DB listing vs revenue).

Guidance

11. Process to move from testnet to mainnet

  1. Document the trust model — what is authoritative on-chain vs in Supabase (one page in the repo).
  2. Chain-indexed create/join — encode communityId / member identity in tx data; sync confirmed state from chain to DB; DB is a cache that can be rebuilt.
  3. Harden API access — membership checks should trace to a confirmed on-chain payment (or cell), not a self-reported tx_hash or DB row alone, before granting hidden_link.
  4. Testnet pilot — one real community, 10–20 real joins, measure failure cases.
  5. Security pass — review service role key usage, input validation, rate limits on search/create.
  6. Economics — confirm mint price flow, minimum balances, fee copy; planned creator payout behavior tested.
  7. Mainnet config — separate Supabase project/env, CCC client network, monitoring/alerts.
  8. Launch small — invite-only creators first; public listing after a week of stable ops.

12. Viability and how to make it better

The idea fits CKB when you embrace semi-decentralization: chain for payments and ownership proofs, hosted app for UX and metadata.

Three improvements with highest leverage:

  1. Honest architecture — say “semi-decentralized” in marketing; stop implying full on-chain governance until it exists.
  2. State machine for communitiesdraft → active → archived instead of binary create/delete.
  3. Member trust — after paying, membership and hidden link access must be guaranteed or refundable; never silent failure after tx.

13. Market reach — where to get users; is this built for CKB?

Who it is for:

  • CKB-native creators ( educators, DAO working groups, builder circles )
  • Communities that want wallet-based access without building their own contracts
  • Early adopters already on CCC-compatible wallets

Where to find them:

  • Nervos / CKB Discord and Telegram
  • CKB devrel demos and hackathon alumni
  • Partner with one existing community as a pilot (“mint to join our private builder chat”)
  • Show at local meetups with live create + join on testnet/mainnet

CKB fit: Strong if you lean into low-fee micropayments and wallet-native identity. Weak if you compete head-on with Patreon + Discord invites — your edge is on-chain verifiability and CKB settlement, not generic “membership SaaS.”

14. Fully decentralized vs semi-decentralized?

Recommendation: semi-decentralized for this product.

Layer Semi-decentralized (recommended) Fully decentralized
Ownership On-chain cell (creator + community ID) Same
Membership payment On-chain CKB transfer Same
Membership registry DB today; optionally indexed from chain later On-chain cells or contract state
Metadata, search, images Supabase / app server IPFS + indexer (much harder)
Gated URL delivery Server checks membership Hard — URLs leak; usually still need off-chain gate
Delete/archive Creator-controlled delist Governance votes, timelocks

Member voting on every decision (delete, guidelines, pricing) is expensive in UX and engineering. If you want “everyone’s opinion counts,” start with lightweight signals (polls in app, non-binding) rather than on-chain governance.

Move to more decentralization incrementally: e.g. index membership from chain events into DB instead of trusting POST bodies alone.

15. What parts of the app are better handled by AI?

Good fits:

  • Drafting community descriptions and guideline templates from a short creator prompt
  • Suggesting mint price ranges based on community type (informative, not automatic)
  • Search ranking and “similar communities”
  • FAQ / support bot for “how do I join?” and wallet errors
  • Moderation assist for public fields (name, description) — flag spam before publish

Poor fits (do not automate):

  • Whether a wallet is a member (must be deterministic: chain + DB rules)
  • Ownership verification
  • Sending or holding funds
  • Storing or guessing hidden links

AI can help creators write; it should not decide access.

16. How should the UI be improved?

Concrete items from the current app:

  1. Remove placeholder content — community detail page appends lorem ipsum after the real description; that must go before any public review.
  2. Rename Delete → Archive — match actual behavior; explain on-chain record remains.
  3. Create/join progress states — show steps: “Signing tx → Saving community → Done” and a recovery card if save fails after tx (with tx_hash and “Retry save”).
  4. Align navigation — home links to /dashboard but routes live under /my-communities, /create-community, etc.; fix dead ends.
  5. Footer links — Docs, GitHub, Terms currently #; link to repo and a short ARCHITECTURE or README section.
  6. Hidden link UX — restore blur/reveal pattern (commented out in code) so links are not always visible in plain text on the page; add “Open link” beside Copy.
  7. Match marketing to reality — homepage promises “governance” and “portable memberships”; either build minimal versions or soften copy.
  8. Mobile — test join and create flows on small screens; forms are usable but error toasts need clearer recovery actions.

17. Necessary vs plus features

Necessary (practical MVP):

  • Chain-indexed create/join: encode identity in tx (cell data / witness), wait for confirmation, sync chain → DB; DB rebuildable from chain
  • active / archived community status
  • Accurate delete/archive copy and member expectations
  • Membership gated hidden_link with join tx_hash validation after block confirmation (not on mempool acceptance alone)
  • Per-member invite credentials (or session-based access) if creators expect to charge enough that key-sharing would matter
  • Creator payout flow documented and tested
  • Basic project README (setup, env vars, architecture) — replace create-ccc-app boilerplate
  • Remove debug placeholders and broken links

Plus (later):

  • Gated ticketing (events, QR, capacity)
  • Optional xUDT or NFT protocol when a feature needs it; migration path from membership cells
  • Membership transfer / resale (opt-in)
  • Community ownership transfer
  • On-chain chat (high cost; consider hybrid: hash of messages on-chain, content off-chain)
  • Marketplace discovery and ratings
  • AI-assisted community setup
  • Analytics for creators (revenue, member growth)

18. Treat each community as an xUDT with limited supply?

No — not as the default model. xUDT is fungible; capped “100 seats” membership is not inherently an xUDT problem.

For v1: use a community-specific type script and issue a membership cell when the join tx pays the gate fee (see §9). The script verifies payment; the cell proves membership; the DB indexes cells for speed.

Add xUDT later only if fungible tokens clearly help (rewards, splits, fungible secondary market). Add an NFT-style protocol only if you need non-fungible, uniquely identifiable seats with protocol-level transfer rules.

Migration: if you introduce xUDT (or NFT) later, support membership cell → token migration so early members are not left on a dead format.

Until membership cells + indexer land, xUDT per community adds complexity without fixing your P0 problems (orphan state, misleading delete, API trust).

Top 3 prioritized suggestions

  1. Make chain authoritative; DB a cache — put community/membership facts in tx data, wait for confirmation, index into Supabase; users who paid but hit a server crash must recover without paying again.

  2. Rebrand “delete” as “archive” and document the trust model — one short doc: what lives on-chain, what lives in Supabase, what happens to paying members when a community is archived.

  3. Pick one positioning and trim the UI — “Paid CKB membership for private community links”; remove lorem ipsum, fix nav/footer, align homepage claims with shipped features; run one pilot community on testnet and iterate from real feedback.

Review log

Additional findings recorded during review (newest last).

Join flow treats tx pool acceptance as confirmed payment

Reference: components/community-card.tsx (join handler)

When joining a community, the client calls signer.sendTransaction(tx) and immediately POSTs to /api/community/join-community with the returned tx_hash. On CKB, a successful sendTransaction only means the transaction was accepted into the node’s tx pool — it does not mean the tx will be included in a block.

Risk: A user (maliciously or accidentally) can broadcast a second transaction that spends the same inputs with a higher fee. Miners/validators prioritize the higher-fee tx, so the join payment may never confirm while the app has already granted membership in Supabase.

Recommendation:

  1. After sendTransaction, poll or subscribe until the tx reaches a chosen confirmation depth (or times out).
  2. Only then call /api/community/join-community, or insert membership with status: pending until confirmation.
  3. On the server, verify the confirmed tx (correct inputs, output to creator lock, amount ≥ mint_price) before setting isMember or returning hidden_link.
  4. UI copy: distinguish “Transaction submitted” from “Membership active.”

Gate fee paid but DB insert failed (server crash)

Relates to: Problem 3 — on-chain vs DB ordering

If a user sends the gate fee, sendTransaction succeeds, and the server crashes before /api/community/join-community inserts the members row, the payment may be confirmed on-chain while the app has no membership record. Today the user would likely need to join again and pay a second time.

Root cause: Membership is created by a one-shot API call after broadcast, not derived from chain history. The same applies to community creation when the on-chain cell exists but /api/community/create never ran.

Recommendation: Store the information required to reconstruct membership (and community ownership) in the transaction — cell data and/or witness — e.g. communityId, creatorAddress, memberAddress. Run an indexer that watches confirmed txs / live cells and upserts into Supabase. Then:

  • DB is a cache for fast reads (search, dashboards, hidden link delivery).
  • Any missing or corrupted DB row can be recreated from chain data.
  • A user returning after a crash sees “Syncing membership…” or triggers “Restore from wallet history,” not “Pay again.”

Apply the same pattern to community creation: on-chain cell proves ownership; off-chain metadata is keyed by communityId and backfilled by the indexer if the initial POST failed.

Private-key sharing defeats one-seat-per-wallet membership

Relates to: §5 — Credential sharing and private-key leakage

A user can pay the gate fee once, then share or publish the wallet private key so many people import the same key and all appear as the same user_address. Mint Gate has no equivalent to YouTube’s concurrent-session limits or account-sharing detection — membership is “whoever controls this key.”

Today all members of a community also receive the same hidden_link, so leakage via key sharing or link reposting is equivalent from the creator’s perspective.

Recommendation: Treat wallet membership as one key = one seat in creator-facing docs. For stronger protection: issue per-member invite links at join time and/or add sign-in + short-lived server sessions with concurrent-session caps. Soulbound tokens alone do not solve key sharing.

Membership cells before xUDT; xUDT only when it earns its place

Relates to: §9, §18

xUDT is for fungible tokens. Do not default every community to xUDT or treat membership as “NFT-like” without a specific reason. Support xUDT or an NFT protocol only when they add clear product value.

Suggested on-chain path for join:

  1. Each community has a unique type script (per-community or shared code + communityId args).
  2. The join transaction sends the gate fee to the creator and creates a membership cell (typed output locked to the member) as a byproduct.
  3. The type script enforces that the required fee was paid as part of unlocking/minting that cell.
  4. An indexer syncs live membership cells → DB; DB remains a cache.

Later, if needed: allow migration from membership cells to xUDT (or to an NFT protocol) for communities that adopt fungible or NFT features — e.g. rewards, resale, or wallet-standard compatibility. Early members should not be stranded on an abandoned cell format.

Closing note

You have a working vertical slice that demonstrates CKB wallet flows and a clear creator/member story. The rebukes above are about trust and honesty — users will forgive missing features; they will not forgive lost CKB after a failed database write or a “deleted” community that still charged them for access.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment