Attest, never hold.
A person proves what they hold by signing, not sending; every lender for their state is shown with the two custody answers first; every consent is a signed receipt any lender can verify without calling us. Below: each invariant, and the test that fails the build if it stops being true.
The Bridge is two things and no third: an attestation of the borrower's own facts, and a referral to licensed lenders. Every crypto licence — money transmission, custody, BitLicense, California's DFAL — attaches to control: the power to move, block or unilaterally execute against someone's assets. The Bridge is built so it cannot acquire that power, and the tests below are the proof that it has not.
- TestA test that must pass before the code ships. Named, with its file, so the claim is checkable by anyone who can run the test suite.
- PolicyA decision people keep, with the plan section that made it.
No lender is named on this page or anywhere on this host. A lender's published terms appear on the Bridge itself, attributed and dated, once an agreement exists — never before.
The credential
A verified-holdings credential attests that a wallet the borrower controls held at least an amount band — never an exact figure — at a moment. The wallet signs one purpose-bound message (EIP-4361 for Ethereum, ed25519 for Solana). The attestor verifies the signature by recovery, reads the balance, checks the asserted band against floor(min(two price feeds) × balance), and stores the band, a keyed fingerprint of the address (HMAC-SHA-256 under a server key) and an ed25519-signed body hash. The address itself dies with the request.
- TestNo wallet address, exchange identifier or on-chain attestation UID is ever stored in cleartext — not in a field made for one, and not smuggled into a field made for something else.convex/cryptoBridge.test.ts — “nothing address-shaped can be stored, even by our own internals” and “no stored crypto row carries a cleartext identifier after a full flow”; tests/crypto-core.test.ts — “cleartext identifiers are refused, our own hashes pass”
- TestThe credential carries a band, never a balance. The valuation that supported the band is computed and discarded.convex/cryptoSchema.ts has no balance field; tests/crypto-core.test.ts — “bucket support is a boolean against the floor, nothing finer”
- TestThe signed statement is purpose-bound and disclaims proof-of-funds in the text the wallet signs, so it cannot be replayed to a lender as something it is not.tests/crypto-core.test.ts — “the statement is purpose-bound: not valid as proof of funds”; lib/bridge/statement.ts is the single source the client, the challenge and the verifier share
- TestValuation is conservative by construction: floor of the minimum of two independent price feeds. One bad feed can understate; it cannot overstate.tests/crypto-core.test.ts — “conservative valuation takes the floor of the minimum feed”, “balances beyond 2^53 base units stay exact” (BigInt over base units, never a float)
- TestA challenge nonce is single-use, expires in minutes, and belongs to one session.convex/cryptoBridge.test.ts — “a challenge is single-use and expires”
- TestA credential expires in thirty days and can be revoked by the borrower at any time; revocation is itself a receipted act.convex/cryptoBridge.test.ts — “re-attesting the same wallet supersedes; revocation receipts chain on”; convex/crons.ts sweeps lapsed rows daily
The receipts
Every disclosure, every hand-off and every revocation appends a receipt to a per-subject hash chain: hash = SHA-256(prevHash ‖ canonical JSON of the body), signed ed25519 by DuckWater's published attestor key. The receipt is written inside the same mutation as the act it records, so there is no code path — including a crash between two writes — that produces one without the other. A lender verifies the whole chain offline against the public key; DuckWater need not be called.
- TestA hand-off cannot exist without its signed consent receipt. One transaction, or neither.convex/cryptoBridge.test.ts — “a hand-off cannot exist without its receipt — same transaction”, “issuing a credential writes a chained disclosure receipt”
- TestEditing, deleting or re-signing any receipt breaks the chain at that receipt, and the break is detectable by anyone holding the public key.tests/crypto-core.test.ts — “editing any receipt breaks the chain at that receipt”, “deleting a receipt breaks the link for everything after it”, “a signature from the wrong key is rejected”
- TestCanonicalisation is explicit and pinned. A hash that depends on key order or runtime locale is not evidence.tests/crypto-core.test.ts — “canonical JSON is key-order independent and drops undefined”
The tile
Every live lender serving the state, alphabetical, with the lender's own published terms attributed and dated. Two columns are mandatory: the named custodian, and a three-value re-lending class. A lender that will not state both in writing cannot be given a row — the write refuses. The tile endpoint never receives the borrower's band; the band-to-minimum comparison happens in the borrower's own view. DuckWater computes no LTV, no eligibility, no ranking, no score.
- TestAlphabetical — never by fit, never by any commercial consideration. The test fixture is built so that a list ordered by anything but the name cannot pass by accident.convex/cryptoBridge.test.ts — “the tile is alphabetical, never ordered by comp or fit”; ordering policy versioned and hashed into every hand-off log row
- TestNo commercial term reaches a customer projection. The projection is an allowlist; a new payout, comp or score field fails the test.convex/cryptoBridge.test.ts — “a tile row carries custody and rehypothecation and no commercial term”
- TestA box cannot exist without a named custodian and a written re-lending class, and only a crypto-lender may carry one.convex/cryptoBridge.test.ts — “a box cannot exist without a named custodian, or on a non-crypto lender”
- TestCalifornia and Connecticut are off, before any lender's own list is consulted; an unknown or unserved state fails closed.convex/cryptoBridge.test.ts — “California is off even when a lender lists it, in the plan's words”, “Connecticut is off, and an unserved state fails closed”
- TestThe hand-off log records the full ordered list the borrower saw and the one they chose, under the ordering policy's version and hash.convex/cryptoBridge.test.ts — “the hand-off log records the full ordered tile and the choice”
The boundary
What DuckWater is not. The whole legal footing of the Bridge is that every crypto licence attaches to control — the power to move, block or unilaterally execute against someone's assets — and the Bridge is built so it cannot acquire that power.
- PolicyNo key material, ever. No transaction is constructed, signed or broadcast. No wallet deep link prefills a destination, an amount or a memo.Master plan §8.6, D13. The attestor verifies signatures; nothing in convex/lib/ownership.ts or convex/cryptoAttest.ts constructs one.
- PolicyNo holdings data is furnished to any lender. The hand-off carries an email the borrower typed and an attribution id — nothing else — until a written FCRA position exists.Master plan §8.2, D12. Credential v2 (a payload sealed to a lender's key) is built only behind a signed LOI from a lender that will consume it.
- PolicyNo yield, no earn, no vault. The credential attests; it does not pay.Master plan §8.6; SEC staff statement on crypto vaults and lending strategies, July 2026.
- TestNo crypto field of any kind reaches the contractor portal or any renovation-side projection.tests/bridge-copy.test.ts — “no renovation-side module touches a crypto table or field” (a source-scan over convex/partners.ts, convex/options.ts, convex/applications.ts, convex/invites.ts and the portal)
- TestThe Bridge is dark until its flag is set. Every route and the page check the gate before doing anything.tests/bridge-copy.test.ts — “the bridge stays dark without its flag”
- TestThe one permitted privacy sentence appears verbatim; the banned privacy word appears nowhere on the surface; and the headline claim is “without selling it” — the true one, since custodial lenders take possession to lend.tests/bridge-copy.test.ts — the three copy tests, scanning app/bridge, components/bridge and lib/bridge
