Add Level-B for SideShift: the server creates a SideShift variable shift
via the authenticated API and shows the deposit address in-wallet; BCH
settles to the user's OWN address, so self-custody is preserved end-to-end
(funds never touch the wallet operator). Other providers stay link-out.
- src/onramp/sideshift-shift.ts: createVariableShift / getShiftStatus /
verifySideShiftSecret (injected fetch, typed errors). Endpoints verified
against docs.sideshift.ai; live secret check passes GET /v2/account.
- web: OnrampContext.sideshiftSecret/affiliate from SIDESHIFT_SECRET /
SIDESHIFT_AFFILIATE env; handlers + routes GET /api/onramp/config,
POST /api/onramp/shift, GET /api/onramp/shift?id=. Secret stays
server-side (a test asserts it never appears in the response).
- ui: SideShift card gets "Swap in wallet" (deposit address + range +
6s status poll) when shiftEnabled, with an "Open site" link-out fallback.
- guard: mainnet-only — a bchtest:/bchreg: settle address is rejected with
a switch-to-mainnet message (SideShift settles mainnet BCH).
- scripts/deviant-web.mjs auto-loads .env; .env.example documents the keys.
- ADR-0036 amended: Tier-3 admitted narrowly for non-custodial API swap
providers only.
Full suite 478 pass / 0 fail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>