> ## 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.

# DB-display-only trust

> Why a database compromise cannot redirect a customer's payment, and the exact conditions under which that holds.

Locked 2026-06-19. Spec: `docs/superpowers/specs/2026-06-19-db-display-only-trust-anchor.md`.

## The claim

> The Postgres database is **display-only** for the customer fund path. A compromise of the database — including the owner or `service_role` — **cannot redirect a customer's payment**, because the checkout verifies the settlement destination on-chain, client-side, before the customer signs.

The platform is therefore not a custodian of **routing**, not merely of funds.

<Warning>
  Never describe this as "unhackable." Describe the mechanism and its conditions. The non-absolutist communications floor applies, and the audit is still pending.
</Warning>

## The mechanism

The merchant pins their payout wallet in the embed code on **their own site** — `CryptoCheckout.init({ settlementAnchor })` or `?anchor=0x…`. That pin is the root of trust, and it lives somewhere the platform's database cannot reach.

Before the customer signs, the widget:

<Steps>
  <Step title="Reads the on-chain attestation">
    `MerchantRegistry.effectiveRecipient(anchor)` — an ownerless, `msg.sender`-scoped attestation the database cannot author.
  </Step>

  <Step title="Re-derives the pool address">
    From that **chain-read** recipient, using a byte-identical browser port of `predictPoolAddress`, pinned to the Solidity fixture vectors. Asserts it equals the served address.
  </Step>

  <Step title="Cross-checks">
    Connect rail: reads the deployed `MerchantPool.recipient()` and compares. Deposit rail: re-derives the forwarder from the verified pool.
  </Step>

  <Step title="Refuses on any mismatch">
    Terminal `pool_mismatch` state. Signing is blocked and the pay-to address is hidden, with a plain "configuration mismatch — do not pay" message.
  </Step>
</Steps>

The critical property: **a self-consistent database tamper dies here.** An attacker who swaps both the recipient and the pool address in the database still fails, because re-derivation anchors to the chain rather than to the served `merchant_pools.recipient`.

The edge functions return a `binding` bundle — an account identifier plus committed configuration — which the client re-proves. **The server response, like the database, is an untrusted hint.**

The pool itself is immutable and ownerless. Once funds land, the split to committed recipients is frozen in bytecode.

## Conditions and limits

State these whenever the claim is made.

| Condition                       | Detail                                                                                                                                          |
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **Requires a pinned anchor**    | Holds only when the merchant pins one. Without a pin the widget shows an "unverified" badge and does **not** block. Activation is per merchant. |
| **Defends database compromise** | Our code honest, rows tampered. A full served-code compromise is *mitigated* by SRI-pinning the SDK, not eliminated.                            |
| **Covers the pool rails**       | The legacy atomic rail and default-recipient merchants are a separately tracked gap.                                                            |
| **Audit pending**               | No third-party audit has yet examined this path.                                                                                                |

## Server-side defence in depth

Shipped 2026-06-25. These do not replace the client check — they reduce the blast radius of a database compromise that the client would catch anyway.

* **Keeper** re-resolves `recipient` via on-chain `effectiveRecipient` and gates deploy and sweep on the re-derived address.
* **Indexer** re-derives the expected pool and forwarder and flags `tamper_suspected`, never reconciling an unanchored pool.
* **Database** has an authorize-and-audit layer on fund routing: a `commit_merchant_pool()` security-definer function, a before-write guard, and an audit trigger.

<Warning>
  Honest residuals, do not oversell. The database guard is authorize-and-**audit**, not `service_role`-proof — a direct-SQL `service_role` can set the guard's own GUC. `merchants.wallet_address` and `merchant_wallets` inserts remain `service_role`-writable and audit-only. **The client-side on-chain re-derivation stays load-bearing.**
</Warning>

There was also a real bug worth remembering: the keeper's anchor resolution returned null for every chain because the shared contracts module indexed bucket names rather than chain IDs when parsing the nested deployments file. Fixed 2026-06-25.

## What this is not

<Info>
  The pool-deployment gate is **not** a security requirement. Verified against the client verification code: `predictPoolAddress` is pure CREATE2 arithmetic and re-derives counterfactually with no code needed; the only load-bearing chain read is against the per-chain singleton registry, not the merchant's pool; the deployed-`recipient()` cross-check is optional and skips silently on failure; and `effectiveRecipient` is identity-default, so even an unattested merchant verifies correctly.

  Gate on pool deployment for commercial and connect-rail reasons. Do not justify it as required for this guarantee — that claim would be wrong.
</Info>

## Primitives

| Concern                   | Location                        |
| ------------------------- | ------------------------------- |
| On-chain attestation read | `_shared/effectiveRecipient.ts` |
| Address derivation        | `src/lib/pool/poolDerive.ts`    |
| Binding verification      | `src/lib/pool/verifyBinding.ts` |
| Chain reads               | `src/lib/pool/onchainVerify.ts` |
| Keeper-side re-resolution | `_shared/keeperOnchain.ts`      |
