> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cryptocheckout.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Partner referrals

> Account-bound, first-touch, server-authoritative attribution across five layers.

A referral partner earns **0.25% of gross** on every payment their referred merchants process. The payout address is committed on-chain in the pool's init code, so it is paid automatically and immutably at every distribution — the platform cannot redirect it without deploying a visibly different pool.

## Two levels

| Level                                                  | Where it lives                             | Mutable                          |
| ------------------------------------------------------ | ------------------------------------------ | -------------------------------- |
| **Attribution** — which partner referred this merchant | `merchants.partner_referrer_id`, off-chain | Only via platform-admin override |
| **Payout** — where the 0.25% goes                      | Committed on-chain in the pool init code   | No                               |

<Warning>
  Hard rule: **resolve the partner before the pool is created.** Once the pool exists, its recipients are frozen. A partner bound afterwards cannot be paid from that pool.
</Warning>

## Five layers

<Steps>
  <Step title="Capture">
    `?ref=<CODE>` on any landing page writes to `localStorage["cc:ref-code"]` via `captureRefFromUrl()`, called on app mount. Deliberately not a cookie — no Safari or ad-blocker loss.
  </Step>

  <Step title="Carry">
    The stashed code travels as a `cc:ref:<CODE>` SIWE resource line, the same channel as the partner role hint.
  </Step>

  <Step title="Bind">
    `siwe-verify`'s `resolveRoles` stamps `merchants.partner_referrer_id` **only on the first merchant-row insert** — new-merchant-only, first-touch. Guarded against inactive partners and self-referral. Bound once, never overwritten by a later link.
  </Step>

  <Step title="Manual recovery">
    A field in merchant Settings calls `bind-referral`, which binds only while `partner_referrer_id IS NULL`. Recovers a capture lost to cleared local storage.
  </Step>

  <Step title="Platform override">
    `/platform-admin → Merchants` calls `platform-admin-set-referrer` — the only path that can change an already-bound merchant. Writes an `audit_logs` row.
  </Step>
</Steps>

## Codes

Six characters, canonical, generated by `generateReferralCode`, excluding `0`, `1`, and `O` to avoid transcription errors.

<Warning>
  Hand-seeded non-canonical codes — for example the demo checkout's `demo-partner` — do **not** validate for binding. If a test binding silently fails, check the code shape first.
</Warning>

## Compliance

The partner payout address is screened for sanctions **before** it is baked into a pool's immutable init code, regardless of whether that partner has ever signed in. A referrer auto-created from a code may never have completed SIWE and therefore never been screened at sign-in.

This is self-protection as much as compliance: a sanctioned or frozen address committed into an immutable pool permanently locks funds for everyone in that pool.

## Partner acquisition

`/partners` is the public landing page. It sets `setPartnerRoleHint("partner")`, and `siwe-verify` auto-creates the `referrers` row on first sign-in, so there is no separate signup.

## Latent bug worth remembering

`merchants.name` is `NOT NULL` with no default. The auto-create path in `resolveRoles` did not seed it, so the insert errored, the error was swallowed, and **all new merchant self-onboarding silently failed**. It now seeds `name: "Merchant <0x6>"`.

The lesson generalises: a swallowed insert error in an auth path produces a silent, total onboarding failure with no visible symptom.

## Open gap

<Warning>
  Partner earnings display **\$0.00** everywhere, and partner-visible merchants RLS is incomplete. Both are open from the 2026-07-28 audit. Any change to the fee split must carry a regression test asserting the partner's 0.25% is computed on gross and unaffected — the dashboard will not catch a regression.
</Warning>
