[SO-336] Add the Dakota stablecoin on/off-ramp integration (sandbox, staging-only) - #382
Open
ewitulsk wants to merge 1 commit into
Open
[SO-336] Add the Dakota stablecoin on/off-ramp integration (sandbox, staging-only)#382ewitulsk wants to merge 1 commit into
ewitulsk wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…staging-only) Adds dakota-service fronting Dakota's sandbox and the dakota-dashboard console. Stacked on SO-341, which reworks auth-service into the multi-method identity service this depends on. Verified end to end against the live sandbox: 62 unit tests, a live signing test, and a 31-assertion smoke script. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0152Pn9P1PWogKrSdsS1jmrr
ewitulsk
force-pushed
the
ewitulsk/dakota/sandbox-mvp
branch
from
August 2, 2026 23:41
e5a7f9c to
a18fdc4
Compare
ewitulsk
changed the base branch from
staging
to
ewitulsk/auth/multi-method-identity
August 2, 2026 23:41
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.
SO-336
Adds dakota-service fronting Dakota's sandbox and the dakota-dashboard console. Runbook in docs/dakota-rollout.md; verified API behaviour in docs/dakota-sandbox-notes.md.
Dakota's object model carries the three tiers we wanted directly:
One dashboard build serves all of it. The JWT's
role+scopedecide what renders, and dakota-service enforces the same boundary server-side — the UI never filters data it wasn't already scoped out of.Never storing PII, and why that's structural
Dakota responses are full of it —
GET /customersreturnsemailandname,POST /accountsreturnsbank_account.account_holder_nameandaccount_number,GET /eventsreturnssender_details. None is persisted. Three rules hold the line:indexed_events.payloadenvelope. A delivery that fails to parse is recorded as a SHA-256 of the body, never the body.serde_json::Valuestraight to the browser instead of binding a struct.Onboarding follows: customers go to Dakota's hosted
application_url, and beneficial owners, documents and SSNs never touch our code.Six bugs that only running it could find
Each of these passes unit tests and fails against the real API.
Amounts must be normalized before signing. Dakota normalizes the decimal before verifying, so a signature over
"1.00"is checked against"1"and fails asendorsement validation failed— a message naming nothing. Every whole-dollar transfer a user typed would have been rejected. Isolated by holding key and wallet fixed and varying only the amount:"1""1.00""0.50""0.01"The transmitted intent must be the canonical form. A Rust struct serializes in declaration order, which differs from the signed bytes.
endorse()now returns the intent as aValuerebuilt from the canonical bytes, so wire form equals signed form by construction.simulate/onboardingleft the local status stale — Approve reported success, then the very next ramp was refused aspending. Caught by the smoke script; an earlier manual run only passed because it happened to re-read the customer in between./eventsuses a flat receipt shape (outgoing_amount/output_currency, bare-string fee) unlike/auto-transactions. Handling only the nested form left every webhook-sourced row with a NULL amount.Events name
auto_account_id, nevercustomer_id— without joining through our accounts table, every per-customer total stayed empty.Postgres widens
SUM(bigint)toNUMERIC, which broke the flow aggregation until cast back with::bigint.Also undocumented and now handled: onramps require
capabilities;kyb_approveis the transition that advances individuals too (kyc_approveno-ops fromnot_started);applicant_idwants the application id, not the customer id;/events?limitcaps at 100.What Dakota does not have
/capabilities/networksreturns bare network strings.GET /self-serve/credits/pricing403s "Credit management is only available for self-serve customers." So the expected schedule is admin-entered and labelled as such, while realised rates come from transaction receipts and are shown beside it.Staging-only, enforced four ways
deploy.shfilters the requested set against the env's compose file, so a service absent there can never be planned or health-gated — the same mechanism excludingcctp-relayandmarket-sim. For dakota-service this is by design: it integrates a sandbox with testnet custody, mocked banking and a $2 cap, so there is nothing useful it could do in prod.docker-compose.prod.ymlnginx.prod.confconfig.prod.tomloptions/prod/dakota-serviceVerification
smoke.shaffected.pyparitysmoke.shcovers admin bootstrap, the hierarchy, cross-scope isolation (a business cannot read an outsider; a forgedsub_client_idis refused), the approval gate, all three ramps, both allow-lists, the $2 cap, funding, the ledger, and webhook authenticity.Worth knowing when reading the live signing test: an insufficient-balance rejection is success — it means the signature verified and Dakota reached policy evaluation.
Before deploying
options/staging/dakota-servicein Secrets Manager, adakota_stagingdatabase, andterraform applyfor the ECR repo (a missing repo fails the push with a 403). Then register the webhook target once from the dashboard's Ops screen — nothing reaches the activity feed until that's done. Full list in docs/dakota-rollout.md.Deferred
Sumsub import is designed for but not enabled: it needs a Dakota-issued sandbox partner token (from a Dakota rep, 30-day expiry) plus "Share applicants data" on our Sumsub app token. Individual applicants only, and imports land in draft missing SSN/attestations — so the hosted form is the only no-PII completion path either way. KYC ships hosted-redirect-only.
🤖 Generated with Claude Code
https://claude.ai/code/session_0152Pn9P1PWogKrSdsS1jmrr