Midnight Buildathon · Wave 2 · Compact + MidnightJS

Kymider

Prove solvency privately. Post 110% collateral, not 150%.

A borrower proves in zero knowledge that their committed balance, debts and income clear a lender’s bar. The Loan contract then lets the lender offer only the tier’s collateral, 110%, and only the borrower’s acceptance makes it binding. The figures never enter a transaction.

Live console

turnless.github.io/kymider

Repository

github.com/Turnless/kymider

On-chain proof

PROOF.md: 37 tx on Preprod, 54/54 checks

The problem

DeFi lenders cannot see a borrower’s finances, so every borrower pays the worst-case price.

A lending protocol cannot ask for bank statements. It treats every borrower as unknown and asks for 150% collateral. A borrower with a clean balance sheet pays the same as one without.

Disclosing finances is the fix the setting cannot accept: it puts a financial history on a public chain or in a counterparty’s database. The lender does not want the data. It wants one answer: does this borrower clear our bar?

The borrower

Locks up capital it could put to work, because it cannot show what it has without showing everything.

The lender

Cannot offer a cheaper tier, because it has no signal it can rely on.

Disclosure

Turns a one-bit question into a permanent data liability for both sides.

The number · a 10,000 loan

One yes/no answer, 4,000 less collateral.

Not verified · Tier STANDARD

15,000

150% of principal. What every borrower posts today.

Verified · Tier VERIFIED

11,000

110% of principal. Enforced in-circuit: the exact floor, nothing else passes.

27% less collateral for the same loan (4,000 of 15,000), or 36% more borrowing on the same collateral (1.5 / 1.1). The lender learns VERIFIED. It never learns the balance.

How it works · one Loan instance per loan

The proof fixes the price. Only the borrower makes it binding.

LOCALThe borrower commits H(tag, H(balance, debts, income), salt). Figures and the 32-byte salt stay on the device.
CHAINdeploy Loan(lender, terms, commitment) and LoanDirectory.list. Terms are public.
CHAINquoteTerms   A net-worth floor, a max DTI, an expiry at least 30 min out. At most 3 per loan; none while a verified tier is live or the last quote is unanswered.
PROVEproveTier   Facts are private circuit inputs, checked against the commitment; writes VERIFIED or STANDARD. Or waiveProof: reveal nothing, take 150%.
CHAINunderwrite(collateral)   An offer, accepted only if collateral = floor(principal × 110% or 150%).
REFUSED150% on a verified borrower: collateral does not match the tier. 150% before the borrower answers: the borrower can prove until the quote lapses.
ACCEPTaccept(expectedCollateral)   The borrower names the figure (any other: offer changed); only then is the loan ACTIVE. Or declineOffer: back to APPLIED.
CHAINdisburse, repay × N, markDefault after a 3-day grace. Lateness is block time.
PROVEproveTwoRepaid   Two private Merkle paths, checkRoot against the directory. Public result: “2 repaid loans”. On the next Loan, proveRepaidHistory drops its ratios a step: 105% / 130%.

Compact has no division. The client computes each quotient; the circuit checks it by cross-multiplying: v×10000 ≤ p×ratio < (v+1)×10000.

Midnight’s dual ledger · exactly what is private

The facts are private. The loan is public, because a lender has to enforce it.

DatumLedgerMechanism
Balance, debts, incomePrivateCircuit inputs to proveTier; only a salted persistentHash of them is public
Secret key, facts salt, history seed, payment noncesPrivateWitnesses localSk, factsSalt, paymentNonce
Which loans back a history proofOff the ledger, not secretHistoricMerkleTree paths + checkRoot are circuit inputs, but a key’s repaid listings are public: with two, anyone can tell which
Tier VERIFIED / STANDARD and its expiryPublicdisclose(): one yes/no answer per proof, at most 3 per loan
Quote, terms, collateral, balance owed, due datePublicEnforced by block time and caller checks
Each repay amount, payments made, late payments, history chain headPublicAmounts are transaction inputs; Counter + hash chain an auditor can check offline

Live console · the borrower and the lender

Kymider landing page
1No install. The console runs the compiled contracts in the browser.
Borrower's loans with both collateral prices
2Both prices up front. 11,000 if verified, 15,000 if not.
Borrower proves the tier: VERIFIED
3Prove tier → VERIFIED. One word reaches the ledger.
Lender underwrites at 110% with figures not disclosed
4The lender’s view. Tier VERIFIED; balance, debts, income not disclosed. The only offer: 11,000.

Live console · refusal, history, auditor, chain

The contract refuses 150% collateral for a verified borrower
5150% refused. “collateral does not match the tier”, the contract’s own assert.
Repaid loan: the private payment log beside the public record
6Repaid, 1 of 3 late. Each amount is a public transaction input; the device’s log adds the nonces that rebuild the on-chain history hash.
Auditor verifies a payment-history disclosure
7Auditor (Wave 3 preview). The exported history lands exactly on the on-chain hash: complete and unaltered.
Live view: the Preprod deployment, every contract and transaction recorded
8Live. The Preprod deployment: 6 contracts and 37 transactions, from the run’s record, which CI re-read from Preprod.

Engineering evidence · every number links to the repo

Refusals are the product, so every refusal has a test.

4

Compact contracts

24 circuits plus 13 exported pure helpers. Language 0.23, toolchain 0.31.1, compiled in CI on every push.

366

Offline tests

Drive the compiled contracts, every refusal included. npm run test:unit

37

Transactions on Preprod

The full flow on Midnight’s public testnet: 3 refusals, 54/54 checks read back from Preprod, PROOF.md. Also 19 devnet cases with real proofs on every push (DEVNET-PROOF.md).

48

Browser tests

Playwright on the console build, 24 at 1440 and 24 at 390 px: the judge path and each refusal, in CI.

Time is tested to the second: quote expiry, a payment exactly on its due date, a default one second inside the grace period. The payment-history chain is rebuilt from the seed and compared byte for byte. Forged Merkle paths, someone else’s record and a loan vouching for itself are all refused.

Market and adoption

The buyer is a DeFi lender that wants a cheaper tier without KYC.

Who

Lending protocols that over-collateralize at 150% and up because they cannot assess a borrower. Their borrowers with balance sheets the protocol cannot see.

Adoption path

One protocol adds a verified tier. The borrower generates one proof per loan. No regulatory change, no new identity system. Then regulated lenders, through the Wave 3 auditor and attestation channel.

The dependency

Figures are self-reported today, so anyone can get VERIFIED. A data provider co-signing facts into private state makes the tier worth a lower price. That is a partnership, not more cryptography.

What changed since Wave 1

Wave 1 proved creditworthiness. Wave 2 makes the proof buy a price.

Wave 1Wave 2
Contracts / circuits2 / 94 / 23 · + Loan, LoanDirectory
What a proof buysPASS / FAIL110% vs 150% collateral, enforced in-circuit, binding on the borrower’s acceptance
Compact featuresWitness, commitments, Map/Set, Counter+ block time, HistoricMerkleTree + checkRoot, salt and nonce witnesses, division-free quotient checks
Offline / browser tests / devnet cases54 / 0 / 11366 / 48 / 19
On-chainLocal devnetMidnight Preprod: 37 proven transactions, 54/54 checks read back (PROOF.md); the same flow on a devnet in CI on every push
ConsoleSolvency screens+ loan desk, portfolio, auditor preview, Live view
AKINDO recordRepo unlinked, private, tagged BaseFix pending: connect the repo, make it public, tag Midnight

Roadmap · and what is simulated today

Wave 3 closes the gaps we name.

Auditor views

The preview exists: the auditor checks an exported payment history against the on-chain hash, offline. It proves integrity; the amounts are already public. Wave 3 adds a read-only regulator dashboard.

Attested provenance

A data provider co-signs facts at ingestion. The same allowlist stops a borrower minting repayment records by lending to themselves.

Bank channel

Signed, expirable attestations a bank verifies offline, with no Midnight node on its side.

Simulated today: no token moves (collateral and balances are figures in contract state); figures are self-reported, so VERIFIED is free today; the browser console executes circuits without generating proofs or using a wallet. Unaudited.

Kymider · Wave 2

Prove it privately.
Post 110%.

Team

Kymider team. One builder: contracts, client, console, CI.

The ask

An introduction to one Midnight lending protocol, and one data provider willing to co-sign facts.

Try it

turnless.github.io/kymider
github.com/Turnless/kymider
Apache-2.0

Kymider · 110%, not 150% 1 / 12
← → navigate · F fullscreen · P print to PDF