Phase 1: hosted checkout, CCTP V2 cutover, managed Stellar settlement#136
Merged
Merged
Conversation
Three intertwined tracks shipped together, ordered by dependency:
## CCTP V2 cutover (PR A–F + follow-ups)
Replaces the pre-V2 bridge stack (Wormhole + Layerswap + per-EVM HTLC
contracts) with Circle's CCTP V2 + Forwarding Service. Stellar is now
domain 27; USDC-only for cross-chain. Net delta: ~3,800 fewer LOC, no
hot destination wallets, no audited HTLC contracts to maintain.
- New `apps/api/src/modules/cctp/` — attestation, forwarder, router,
EVM + Stellar clients, ~1,200 LOC + specs. Per-chain USDC registry
added. EVM `depositForBurnWithHook` calldata is now real (was zeroed
placeholder). Stellar destination encoding: forwarder contract id as
bytes32 `mintRecipient`, merchant strkey rides in the hook.
- Deleted `apps/api/src/modules/{bridge,relay}/` (2,711 LOC), all
on-chain HTLC code (`contract/evm`, `contract/soroban/contracts/htlc`,
`contract/starknet`).
- Schema: drop `Payment.{sourceLockId, stellarLockId, hashlock,
htlcSecret, secretRevealed}`, add `cctpNonce + cctpAttestation`.
- Marketing: dropped "non-custodial by architecture" claims across
apps/www; updated TrustStrip ("Circle CCTP V2", "Bridged in seconds
not minutes"); rewrote stale HTLC paragraphs in use-cases.
- `/readyz` now probes Iris + Better Stack in addition to Postgres,
Redis, Stellar. Status-page runbook updated with the new monitor +
External Dependencies component grouping.
- Decision doc: Circle Gateway evaluated and deferred — Stellar isn't
on Gateway mainnet, and the pre-deposit model is wrong for one-time
checkout. See `apps/api/docs/architecture/circle-gateway-evaluation.md`.
## Phase 1 — hosted checkout (PR 5–7.8)
End-to-end customer pay-by-link flow:
- `POST /v1/links/:shortCode` (public) — resolves link, returns
customer-safe metadata including merchant branding (logo, brandColor,
companyName). 410 Gone enforces active/expired/single-use.
- `POST /v1/checkout/from-link/:shortCode` — creates a pre-quote
Payment row, atomically marks the link used. Schema migration makes
`Payment.{quoteId, sourceChain, sourceAsset, sourceAmount,
destAmount}` nullable so link-initiated payments can exist before
method choice.
- `POST /v1/checkout/:id/select-crypto { sourceChain }` — locks a CCTP
V2 quote, patches Payment to QUOTE_LOCKED, returns wallet-signable
approve + burn calldata (real CCTP V2 ABI, USDC address per chain).
- `POST /v1/checkout/:id/burn-submitted { sourceTxHash }` — records
the burn, enqueues a BullMQ `cctp.observe` job.
- `GET /v1/checkout/:id/crypto-status` — poll surface with explorer
URLs + attestation status.
- `CctpProcessor` (BullMQ) — wraps `CctpService.observe`, drives
SOURCE_LOCKED → PROCESSING → COMPLETED via Iris attestation +
Forwarding Service mint detection.
- Card + bank session creators auto-fill source fields from
destAmount on link-initiated payments (PR 7.7).
Frontend (`apps/checkout`):
- Full rewrite of `CryptoPayment.tsx` — drops HTLC code, uses wagmi
`useSendTransaction` with server-encoded calldata, 5 enabled CCTP
V2 chains, two-prompt approve+burn flow, 3s status polling.
- New hooks: `useCryptoSelect`, `useCryptoStatus`,
`useProvisionSettlement`.
- `apps/checkout/app/l/[linkId]` → `app/l/[shortCode]`; page reads
brand color as `--primary` CSS variable override.
- `ApiError` class surfaces HTTP status so 404 vs 410 routes to the
right `LinkError` variant; only triggers refresh-token dance when
the failing request actually had a token (was masking real 401s
on /auth/login as "Session expired").
## Managed Stellar settlement onboarding (PR 7.9)
Approach A from `apps/api/docs/architecture/merchant-settlement-onboarding.md`:
zero-friction onboarding for business merchants who shouldn't have to
touch Stellar Laboratory.
- New `MerchantSettlementKey` table — AES-256-GCM-encrypted seed,
per-row IV + authTag, KEK from `SETTLEMENT_KEY_KEK` env.
- `MerchantSettlementService.provision()` — generates keypair,
funds (Friendbot on testnet / sponsored CreateAccount on mainnet
stub), adds USDC trustline, encrypts + persists, mirrors
`Merchant.settlementAddress`. Idempotent.
- Hook in `AuthService.register` — provision runs in try/catch so
Horizon outage doesn't block signup.
- `POST /v1/merchants/me/settlement/provision` — manual retry
endpoint for existing merchants / outage recovery.
- Dashboard settings card with two states ("Settlement active" with
truncated G… address + "managed by Useroutr" badge, or amber
"No settlement wallet yet" with one-click provision button).
## Audit fixes folded in
- LinksController `@UsePipes` at method level was validating
`@CurrentMerchant` string against the body schema. Moved pipe to
`@Body()` parameter — link creation now works.
- PaymentLink response shape rewritten to match `@useroutr/types`
contract (camelCase, derived `status` + `type` + `usageCount`,
ISO strings, `status` query filter wired on the list endpoint).
Was crashing the dashboard `/links` page on `BrandStatusBadge`.
- Dashboard `lib/auth.ts` refresh URL was missing `/v1` prefix and
wasn't unwrapping `{ data: ... }` from the API envelope; fallback
origin was `:3000` (marketing) instead of `:3333` (API). All three
fixed.
- API `getCheckoutPayment` defaults `paymentMethods` to
`['card', 'bank', 'crypto']` when merchant doesn't restrict —
link-initiated payments never carry that metadata.
- Stripe `paymentIntents.create` errors now surface real
`StripeAPIError.message` instead of generic 500.
## Test coverage
21 suites, 202/202 tests pass. New specs:
- `cctp.processor.spec.ts` (5 tests) — happy / no-forward /
retry-remaining / retry-exhausted / unknown-job branches
- `public-links.controller.spec.ts` (3 tests) — delegation, 404, 410
- `links.service.spec.ts` extended with status-filter tests
- `payments.service.spec.ts` extended with 6 `createFromLink` tests
## Architecture docs added
- `cctp-v2-migration-plan.md` (PR A sign-off)
- `circle-gateway-evaluation.md` (PR E decision: pass on Gateway)
- `crypto-pay-flow.md` (PR 7.8 state machine + endpoints + ABI)
- `merchant-settlement-onboarding.md` (PR 7.9 Approach A vs B vs C)
## What's still ahead
- PR 8/9/10 — dashboard /links list polish, link detail page, E2E
smoke test for the full link → pay flow
- PR 7.9c — passkey-derived settlement wallet (Approach B from the
onboarding doc) as the long-term self-custody upgrade
- Withdrawal endpoint + UI (the "honest custody" lever)
- Checkout app type-cleanup of pre-existing drift in
PaymentPageClient / ConfirmPageClient / CardForm / etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three intertwined tracks shipped together — sequenced by dependency, but
they're all needed for the customer-facing pay flow to work end-to-end.
TL;DR for reviewers
What's in this PR
CCTP V2 cutover (PR A–F + D-follow-up)
Hosted checkout pay-by-link (PR 5–7.8)
Backend (all public, paymentId/shortCode is the credential):
Frontend (`apps/checkout`):
Managed Stellar settlement onboarding (PR 7.9)
Approach A from `apps/api/docs/architecture/merchant-settlement-onboarding.md` — zero-friction onboarding for business merchants who shouldn't have to touch Stellar Laboratory.
Audit fixes folded in
Schema migrations (3 new)
Architecture docs added
New env vars
Test plan
What's deferred to follow-up PRs
🤖 Generated with Claude Code