Skip to content

Connector: ClubCoin (off-chain raffle experience) #112

Description

@UvRoxx

ClubCoin connector — off-chain raffle experience in the demo backend

Type: Subtask
Parent: #106 — EPIC, demo backend with connectors
Owner: Utkarsh (build) · Hector (partner call) · Karmel (positioning)
Labels: demo, connector, off-chain, partner
Source: 2026/07/24 Passport check-in


Problem / Goal

The Passport demo needs at least one partner experience that works end to end and is not
gated on Dynamic. ClubCoin is that experience: a raffle, fully off-chain, a pure Web2
integration, with nothing on chain
(Hector, 2026/07/24). On the Midnight Wallet-SDK
compatibility requirement Hector's words were: "Cloud [Club]Coin is off chain. So I don't
think like the wallet SDK requirement is necessary. It's fully Web2 integration."
He gave
no reasoning beyond that; our inference — that it follows because ClubCoin never needs
to build a contract transaction — is ours, not his.

The goal is a ClubCoin-specific connector inside the demo backend that lets a ClubCoin
raffle receive consented Passport profile data from a user, and a demonstrable journey that
proves it. The connector is deliberately case-by-case: no generic connector framework, no
partner-agnostic abstraction. Hector rejected that explicitly — the abstraction is the SDK,
which is a separate work stream in a separate repository and is not part of the demo.

Two phases, as with #113. The meeting settled only that ClubCoin
is a raffle, off-chain, and pure Web2. The wire mechanism was never discussed, so it is not
pre-committed here: Phase A is the joint call and a written integration surface;
Phase B is the build. The candidate approach below is prep work, not the shape of the
integration.

Everything in this ticket is demo-grade and carries the positioning statement — see
#108.


Background (grounded in the repo)

The consent machinery this connector should build on already exists on the PR #105 branch
(demo/passport-full-flow):

  • sdk/src/profile.ts — the org.midnight.passport.profile/v1 message protocol, with the
    field set displayName, passportContract, and midnightAddresses, plus parsers for the
    ready / request / response messages and the denied, profile_unavailable, and
    invalid_request error values. This module moves into the demo backend when the SDK
    package is extracted out of PR Passport demo: installable PWA, demo backend with connectors, and separate-origin profile client (refs #102) #105 (Hector's review ask).
  • examples/passport-demo/src/profileConsent.tsx — the Passport-side consent dialogue. It
    is launched with passportRequestId and passportNonce query parameters, checks
    event.source === window.opener, matches the request id and nonce, and replies only to
    the requesting origin.
  • examples/passport-profile-client/src/main.tsx — a separate-origin client that opens
    Passport in a popup, pins VITE_PASSPORT_ORIGIN, checks event.source === popup.current,
    and accepts only the matching request id and nonce. This is the closest existing analogue
    to a Web2 raffle partner and is the natural base for the ClubCoin stub.
  • examples/passport-demo/src/publicProfile.ts — the browser-side profile record
    (subjectId, passkey reference, optional passportContract, optional alias).

Note: there is no hosted HTTP surface on the branch. The Node-side code under
experiments/account-custody-prototype/src/node/ (wallet.ts, setup.ts, transfers.ts)
is a localnet test harness, not a service, and infra/docker-compose.yml stands up local
infrastructure rather than a Passport API. Whether the ClubCoin connector needs a hosted
surface is an open question below.

Issue #102 already records the separate-origin profile-read scenario as delivered, so this
ticket is an application of that surface to a named partner, not a rebuild of it.


Scope

Phase A — partner alignment (blocking Phase B's design)

  1. Joint call with ClubCoin (Hector to arrange: "maybe the ClubCoin we can get together
    into a call"
    ).
  2. A written integration surface, recorded on this issue or as a short note in docs/:
    the journey direction, the transport, the field set ClubCoin actually needs, their
    Sybil-resistance requirement, and whether they need a hosted surface on our side. Each of
    open questions 1–7 below closes as either agreed scope or an explicit non-goal.
  3. Working agreement (EPIC: Demo backend with connectors #106): confirm what ClubCoin's side actually has
    today and name any gap before we commit to a shape or an estimate.
  4. Partner-facing language check: every partner-facing string and document on this path
    says "connector", never "SDK", and carries the positioning statement.

Phase B — build (design gated on Phase A)

  1. ClubCoin-specific connector module inside the demo backend, under a ClubCoin-specific
    path (for example examples/passport-demo/src/connectors/clubcoin/). Case-by-case by
    construction: no plugin registry, no partner-agnostic interface, no shared connector base
    class. This much is settled regardless of what Phase A decides.
  2. Raffle-entry journey, built to whatever Phase A agreed.
  3. Connector note in docs/ describing what the demo backend exposes to ClubCoin.

Unblocked prep work — permitted before Phase A closes

  1. ClubCoin partner stub — a local stand-in for the ClubCoin raffle site, modelled on
    examples/passport-profile-client, so a journey is demonstrable before ClubCoin's real
    endpoints exist. The stub is prep, not a commitment to its mechanism.

Candidate approach — pending the call, not agreed

Recorded so Phase B has a running start, not as scope. The meeting said only "raffle,
off chain, nothing on chain, fully Web2 integration"; the mechanism below is ours.

  • Build the raffle-entry journey on the existing consent-gated profile exchange: the
    ClubCoin surface requests a named field set, the Passport consent dialogue names ClubCoin
    and enumerates exactly those fields, and on approval only those fields are returned
    (org.midnight.passport.profile/v1, delivered under Progressive Web App Prototype, Pro/Cons analysis and feasibility report. #102).
  • Partner-origin pinning: the Passport side accepts and replies to a configured ClubCoin
    origin allowlist rather than any opener origin.
  • Raffle entry identifier: a stable, per-raffle, per-Passport identifier ClubCoin can
    deduplicate entries on, so the journey is meaningful without any chain interaction.

Pre-committing to this before the call cuts against the case-by-case decision, which was
precisely that each connector is built for what that partner actually has. If ClubCoin turns
out to need a server-to-server callback or a redirect code exchange, none of the above
survives.


Out of scope

  • Anything on chain. No contract, no Compact circuit, no proof generation, no transaction
    submission, no C1 write for the raffle. Hector: ClubCoin is off-chain, nothing on chain.
  • Wallet-SDK compatibility. No mapping onto the Midnight wallet API surface for this
    connector. Hector explicitly removed that requirement for off-chain partners.
  • A generic connector architecture. No abstraction, framework, registry, or reusable
    partner interface. That is the separate SDK work stream, in a different repository, owned
    by Hector.
  • Dynamic work. The two-call architecture (Proof Generation from Dynamic integration with demo #101 proof generation, Dynamic integration by pass. #103 signature binding)
    is not on this ticket's path.
  • Midnight City (Min ICC) and Midnames. Separate tickets in the same epic.
  • Firebase push notifications. Separate ticket; the agreed sequence is PWA first, Firebase
    after.
  • ClubCoin's own product. Raffle draw mechanics, fairness or randomness proofs, prize
    fulfilment, KYC, and their user database remain theirs.
  • Production hosting, uptime commitments, or mainnet. The demo runs on preview only.

Acceptance criteria

Phase A — partner alignment (gates the Journey criteria below)

  • The joint call with ClubCoin has taken place (Hector to arrange) and its outcome is
    recorded as a comment on this ticket, converting each open question below into either
    agreed scope or an explicit non-goal.
  • A written integration surface exists naming: journey direction, transport, field set,
    Sybil-resistance requirement, and whether a hosted surface is needed on our side.
  • The field set ClubCoin actually receives is written down in the connector note and
    matches what the consent dialogue enumerates.
  • No effort estimate is posted to this issue until the above are recorded.

Case-by-case construction

  • A ClubCoin connector module exists under a ClubCoin-specific path in the demo backend.
  • No generic connector interface, registry, or partner-agnostic base module is introduced
    by this change; reviewer confirms this explicitly in the PR review.

Fully off-chain

  • An automated check shows the ClubCoin connector path imports no Midnight contract,
    proof, or transaction module and no @dynamic-labs/* transaction API.
  • The full raffle-entry journey completes with the proof server stopped and the Midnight
    network unreachable.

Journey — gated on Phase A closing

These criteria assume the candidate popup/postMessage profile-exchange approach. They
are only binding once Phase A confirms that transport.
If Phase A settles on a
server-to-server callback or a redirect exchange, replace this block with the equivalent
criteria for that mechanism; do not tick these against a stub built to a mechanism the
partner did not agree to.

  • From the ClubCoin partner stub, a user opens Passport, sees a consent dialogue that
    names ClubCoin and lists exactly the requested fields, approves, and the stub displays
    only those fields.
  • On decline, the stub receives approved: false with error denied and zero profile
    fields; covered by a test.
  • A response is rejected when the request id, the nonce, the origin, or the window handle
    does not match; regression tests cover a mismatched origin and a mismatched nonce.
  • A request from an origin outside the configured ClubCoin allowlist is refused by the
    Passport side; covered by a test.
  • The same Passport entering the same raffle twice produces the same entry identifier, and
    two different Passports produce different identifiers; covered by a test.

Positioning and documentation

  • A connector note exists under docs/ describing what the demo backend exposes to
    ClubCoin.
  • A repository check confirms no partner-facing document or UI string on the ClubCoin path
    uses the word "SDK".
  • Partner-facing copy carries the positioning statement — see
    Write and adopt the demo positioning statement #108 — in full: preview/testnet only, not mainnet, not the
    final product, not audited, and real rather than mocked
    .

Demonstrability

  • A recorded end-to-end run (script output or screen capture) is linked from this ticket.
  • If the joint call has not happened by the time the stub journey works, the connector is
    delivered against the stub, and the ticket remains open pending ClubCoin's real
    integration points, with that status noted in a comment.

Dependencies / blockers

Item Nature Notes
Joint call with ClubCoin Blocking Phase B's design Hector to arrange ("maybe the ClubCoin we can get together into a call"). Stub and prep work proceed without it; the integration's shape does not.
SDK extraction from PR #105#107 Sequencing The profile-exchange module the candidate approach reuses currently sits in the sdk/ package inside PR #105. Build after the extraction to avoid rework.
Demo backend epic — #106 Administrative This ticket sits as a subtask under the epic Hector asked Karmel to create.
Dynamic — #109 (#101, #103) Not a blocker ClubCoin is off-chain, so the Dynamic two-call architecture is not on this path.
Hector's availability Risk Hector is off for roughly two weeks starting the week after the check-in; Utkarsh and Karmel pick up follow-through. If the ClubCoin call does not land before he leaves, the stub path must carry the demo.

Timeline signal. In the check-in, a first connector version was described as deliverable
by the Monday or Tuesday after the check-in if unblocked, with the caveat that the
blocker is Dynamic's missing calls rather than our lift. Relative wording is used
deliberately: see the dating caveat in #106 — the recorded check-in date
(2026/07/24) is a Friday, which is inconsistent with the transcript's "tomorrow we have a
call" and "tomorrow or on Friday", so absolute dates derived from it are not trustworthy
until the calendar entry is checked. That estimate was given in the general connector
discussion, so whether it applies to ClubCoin specifically is an open question — although,
since ClubCoin needs nothing from Dynamic, the stated blocker does not apply here.


Open questions

Recorded rather than decided — the 2026/07/24 check-in did not settle any of these.

  1. Journey direction. Does the user start on ClubCoin's site and get sent to Passport, or
    start in Passport and choose a raffle? Not discussed.
  2. Transport. Browser popup with postMessage (as today), a server-to-server callback with
    a signed payload, or a redirect with a code exchange? Not discussed.
  3. Fields ClubCoin actually needs. The existing exchange offers displayName,
    passportContract, and midnightAddresses. Does a raffle need a Midnight address at all,
    or only evidence of a distinct Passport?
  4. Sybil resistance. What does ClubCoin need for one-entry-per-person, and is a
    Passport-derived identifier acceptable to them? This determines the entry identifier's
    derivation and whether it must be raffle-scoped.
  5. Payload verification. Does ClubCoin need to verify anything about the response —
    a signature over the profile payload, for example? Nothing of the sort is designed today.
  6. Server-side surface. The demo backend is browser-resident on the PR Passport demo: installable PWA, demo backend with connectors, and separate-origin profile client (refs #102) #105 branch. If
    ClubCoin needs a server-to-server callback, the demo backend needs a hosted surface —
    hosting, environment, and secrets ownership are all undecided.
  7. Attestations. Does ClubCoin need any credential or attestation beyond profile fields?
    Selective disclosure (docs/plans/components/C20-selective-disclosure-proof.md) and the
    attestation tree (docs/plans/components/C18-attestation-tree.md) are plan components, not
    demo scope — confirm on the call that they stay out.
  8. Surfacing in the PWA. Whether the ClubCoin raffle appears as an in-PWA experience or
    only as an inbound partner integration is undecided. The broader question — what else
    Passport surfaces as experiences — is Karmel's, tracked as open question 11 on
    EPIC: Demo backend with connectors #106
    (owner Karmel, for the week following the check-in), not here.
    This entry is only the ClubCoin-shaped slice of it.
  9. Origin allowlist values. Which ClubCoin origins (production, staging, local) the
    Passport side should pin.
  10. Data retention and revocation. What ClubCoin may store, for how long, and whether the
    demo needs any revocation path.
  11. Estimate applicability. Whether the "Monday or Tuesday after the check-in" signal was
    meant for ClubCoin specifically or for the first connector in general.
  12. Relationship ownership. Who owns the ClubCoin relationship while Hector is off.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions