Veil is an experimental Solana DvP protocol for tokenised assets. It atomically delivers an eligibility-controlled asset against an exact confidential Token-2022 payment, rejecting valid-looking underpayments without revealing the negotiated price publicly.
A buyer receives a tokenized bond if and only if the seller receives payment — and the price nobody else gets to see is still, provably, the price that was paid.
This is an unaudited educational prototype on a test cluster. It is not audited, not a securities platform, not legally compliant with any regulatory regime, and not institution-ready. Every token here is a mock asset minted for a demo:
- the "Treasury bill" is not a Treasury bill;
- the "settlement dollar" is not a dollar and is not USDC;
- "issuer", "eligibility administrator" and "auditor" are demo roles, not legal authorities.
Never deployed to mainnet. Never handles real money or real securities. There is no token, no sale, and no fundraising instrument associated with this project. Read
docs/LIMITATIONS.mdbefore forming any opinion about what this does.
For an evergreen, non-milestone explanation of the problem, product thesis, architecture, settlement flow, guarantees and long-term opportunity, see the eight-page Viel DVP portfolio architecture PDF.
The PDF uses the user-confirmed project spelling Viel DVP. Some repository files retain the earlier Veil DvP spelling.
- Portfolio architecture overview
- The problem, in plain English
- What delivery-versus-payment means
- What Veil does
- Why this needs Solana specifically
- What is genuinely novel here
- A trade, walked through in plain language
- Verified status
- Architecture
- The privacy model
- The security model, and INV-22
- Tests and measurements
- The web application
- Quick start
- Full verification
- Repository structure
- Known limitations
- Contributing and licence
Suppose you want to sell me a bond for a million pounds.
One of us has to move first. If I pay first, you might keep my money and never send the bond. If you send the bond first, I might keep it and never pay. That exposure has a name in finance — principal risk — and it is not theoretical: the 1974 collapse of Bankhaus Herstatt happened precisely because one side of a currency trade had settled and the other had not.
Markets solved this by inserting an institution in the middle. A central securities depository holds both sides and swaps them at the same instant, so neither of us has to go first. It works. It also means we both have to trust the depository, pay it, wait for it, and operate during its business hours.
There is a second problem the depository does not solve. In a bilateral over-the-counter trade, the price you agreed is commercially sensitive. If every trade's price is visible to every competitor, you have given away your pricing, your margin and your inventory position. Public blockchains are famously bad at this: everything is visible to everyone, permanently.
So a settlement system for real financial assets has to deliver three things at once — atomic exchange, control over who is allowed to hold the asset, and confidentiality over the price. Individually these are all solved problems. Together, they fight each other.
Delivery versus payment (DvP) is the settlement rule that says the delivery of a security and the payment for it must happen as a single, indivisible event. Not "at the same time, roughly". Indivisible: either both legs occur, or neither does, and there is no observable instant in between where one has happened and the other has not.
Veil did not invent DvP; it is decades-old market infrastructure. What Veil does is implement it as a property of a public ledger rather than a service sold by an institution, and then keep the price confidential while doing so.
Veil is two Solana programs.
A seller publishes a quote. The quote's economic terms — both parties, both
assets, the quantity, the expiry, a nonce, the program deployment and the
cluster — are hashed into a single terms_hash that is stored on chain. Nothing
in that trade can be altered afterwards without changing the hash, and every
settlement path recomputes the hash from the accounts actually supplied and
refuses to proceed on a mismatch.
The bond is escrowed immediately. Not delegated — escrowed, into a vault derived from that one quote, which cannot be reused for anything else.
The buyer accepts, and reserves cash. The cash goes into a second vault and is then converted into a confidential balance: an ElGamal ciphertext whose plaintext only the buyer can read.
Settlement is one instruction. It emits the public bond transfer and the confidential cash transfer from a single instruction of a single transaction. If either leg fails, the Solana runtime discards every account write the whole transaction made. Not compensated, not reversed — never committed.
Only eligible participants may hold the bond. The bond mint carries a Token-2022 transfer hook pointing at the eligibility program. Token-2022 calls it on every transfer of that mint, including transfers that never touch Veil. The hook can veto; it can never move tokens.
And the amount is bound. This is the part that is easy to get wrong. The three standard confidential-transfer proofs establish that a transfer is well formed — the sender owns the balance, the ciphertexts are consistent, nothing was created from nothing. None of them says the buyer paid the agreed price. Veil adds a fourth proof and an on-chain check that ties the transfer's destination ciphertext to the commitment inside the immutable signed quote. That is invariant INV-22, and it is the reason a confidential Veil settlement is delivery-versus-payment rather than delivery-versus-something.
Three properties are load-bearing, and no two of them are commonly available together:
1. Transaction atomicity across programs. A Solana transaction commits all-or-nothing across every instruction and every cross-program invocation. Both transfer legs live in one instruction, so "both or neither" is enforced by the runtime rather than promised by our code. This is the foundation; without it there is no DvP.
2. Token-2022 transfer hooks. The eligibility policy lives on the asset, not on our program. Token-2022 invokes the hook on every transfer of the mint — so bypassing the Veil UI, or calling the token program directly, still runs the compliance check. A policy that only your own application enforces is not a policy; it is a suggestion.
3. Token-2022 confidential transfers and the ZK ElGamal Proof program. Amount confidentiality is a native token extension verified by a runtime builtin, not a bolt-on. That builtin is what makes a hidden-amount transfer settle in the same transaction as a public one at a cost we could actually measure.
Solana is also where those three coexist and where a settlement fits inside one 1,232-byte packet at a compute cost we measured rather than estimated — see Tests and measurements.
Stated carefully, because most of the parts are not new.
Not novel: delivery-versus-payment, atomic swaps, transfer hooks, confidential transfers, or ElGamal commitments. All of these are existing market practice or existing Solana primitives.
The contribution is INV-22 and the split it forces. A standard confidential transfer proves the transfer is well formed. It does not prove the amount matches an agreement made earlier. Veil closes that gap by:
- committing the agreed price as a ciphertext under the buyer's vault key, inside the terms hash, at quote creation — so it is immutable from the moment the seller signs;
- requiring a fourth proof (
CiphertextCiphertextEquality) relating that commitment to the transfer's actual destination ciphertext; - recombining the transfer's own low and high destination ciphertexts on
chain —
ct = ct_lo + ct_hi · 2¹⁶, using ristretto curve syscalls — and byte-comparing the result, so the proof cannot be about some other transfer; - adding an off-chain reveal step where the seller recomputes the commitment
from the buyer's disclosed
(amount, opening)before signing, because the seller cannot decrypt a ciphertext held under the buyer's key.
Step 3 is what makes the binding real rather than decorative. Without it a buyer can supply a proof that is entirely true and entirely irrelevant — about a transfer that is not the one being submitted. That exact attack is executed against the deployed program and rejected; see below.
The second contribution is the architectural split: the eligibility hook is on the bond, confidentiality is on the cash, and they never apply to the same mint. That is not a limitation worked around — it mirrors how real markets treat the two legs (who may hold the security is a public compliance fact; what you paid is a private commercial one), and it keeps the two hard features from having to interact.
No protocol vocabulary. This is the same journey the web application presents.
Before the trade. Two institutions have already been approved by the eligibility administrator to hold this particular bond. They negotiate a price privately — by phone, by chat, however OTC desks already do it. Veil is not a price-discovery venue and does not try to be.
1. The seller creates the settlement. They state what they are selling, to whom, how much, and by when. The bond leaves their account immediately and goes into a locked account belonging to this one trade. The price is written down only as a sealed commitment — a number nobody can read, that nobody can change.
2. The buyer confirms. Their eligibility is checked. They move cash into a second locked account for this trade. They deliberately reserve more than the price — the envelope, not the amount — because the size of the reservation is public and the price is not.
3. The buyer protects the cash. The reserved cash is converted from a visible number into a sealed balance that only the buyer can read.
4. The buyer proves the payment is correct. Four separate proofs are checked by Solana itself: that the buyer really has the money, that the sealed figures are internally consistent, that nothing is negative, and — the important one — that the amount about to be paid is exactly the price sealed into the agreement in step 1.
5. The exchange happens. In one step, the bond goes to the buyer and the payment goes to the seller. If anything at all is wrong, neither moves.
6. The seller reads what they were paid. Using their own key. Nobody else can. The buyer cannot read the seller's balance either.
7. The unused reservation comes back. The buyer withdraws whatever they reserved but did not spend.
8. Everything closes. Both locked accounts and the trade record are closed and their deposits refunded.
What an outside observer sees, permanently: that these two wallets traded this bond, in this quantity, at this second, and that the payment was confidential. What they never see: the price. What the parties see: each their own side. What an authorised auditor could see — if an auditor key were registered on the mint, which in this build it is not — is every amount on that mint. That is a capability, not a convenience, and it is off.
Everything below is separated by how strongly it is evidenced. Nothing in the "freshly verified" column is asserted from a document; each was executed and observed during the release run.
A separate minimal Anchor DvP program was deployed to Solana devnet and used for
a finalised two-wallet exchange of two mock SPL tokens. This baseline proves the
public-cluster mechanics of create, deposit and atomic settlement. It is not
a deployment of the repository's veil-settlement or eligibility-hook
programs and does not prove Token-2022 eligibility, confidential payment or
INV-22 on a public cluster. See
docs/DEVNET_BASELINE_EVIDENCE.md for the
successful exchange and
docs/DEVNET_FAILURE_RECOVERY_EVIDENCE.md
for rejected actions and exact refunds.
| Claim | Evidence |
|---|---|
| Both programs build with zero warnings | anchor build --ignore-keys in scripts/verify.sh |
| 188 Rust workspace tests pass | cargo test --workspace |
| 17 confidential-balance verifier tests pass | out-of-workspace crate, own lockfile |
| 77 frontend tests across 11 suites pass | cd apps/web && npm run verify |
Format, lint (-D warnings), typecheck, production build all clean |
both gates |
| A complete confidential DvP executes end to end on a real validator | scripts/verify.sh --full |
| 20/20 settlement claims hold | artifacts/m4-integrated-settlement.json |
| INV-22 enforced: three underpayment attacks rejected on chain | same run, same live quote |
| The confidential lifecycle terminates — residual withdrawn, vaults and quote closed, rent recovered | same run |
| Claim | Evidence |
|---|---|
| ZK ElGamal Proof program is invocable (established by invocation, not inference) | docs/M4_FEASIBILITY.md |
| Split-authority key custody, 9/9 claims | artifacts/m4-split-authority.json |
| Proof contexts survive between preparation and settlement; replay rejected | artifacts/m4-context-replay.json |
| Confidential transfer by CPI under a PDA authority | artifacts/m4-cpi-spike.json |
The main apps/web dashboard is deterministic and in-memory. It exposes the
settlement journey, labels every simulated screen and never invents a
transaction signature. A separate /devnet route can connect an installed
Solana wallet to the separate minimal public-devnet baseline program described
above. It supports create, asset deposit, payment deposit, atomic settlement,
cancellation/refund and on-chain state inspection using test tokens only.
The protocol proof is separate: real Veil programs, real Token-2022 confidential
transfers and the full lifecycle executed on an isolated local validator. Those
measurements live in artifacts/ and are surfaced under How it works.
- Browser-side proof generation (
solana-zk-sdk 7.0.1compiled to WebAssembly). - Auditor decryption key. Supported by the mint, deliberately set to
None. - Dual-leg confidentiality. The bond leg is public by design.
- Per-trade confidential keys (one key per wallet today).
- A permissionless crank for a stalled buyer — and there cannot be one; see
docs/TERMINAL_ROUTES.md§4.
Neither of the repository's confidential settlement programs has been
deployed to devnet, testnet or mainnet. Every
validator used is local, isolated, on its own ports and ledger, started and
stopped by its own script. Token-2022 is read from devnet at genesis via
--clone-upgradeable-program; their settlement artifact records
public_cluster_deployment: false. The separate minimal baseline program and
the /devnet browser workspace do send test transactions to public devnet, but
they do not implement the confidential Token-2022 architecture.
This is why the project is described as a pre-deployment release candidate and not as complete.
Two Anchor programs, split by which half of the trade they govern.
veil-settlement owns the quote lifecycle and settlement.
Config["config"] · Quote["quote", seller, quote_id] · vaults
["vault", quote, mint]. Its central instruction, settle_public, emits both
transfer_checked CPIs from one instruction, so the runtime's atomicity is
the DvP guarantee. settle_confidential does the same with a confidential cash
leg and the INV-22 binding check.
eligibility-hook is a Token-2022 transfer hook.
Registry["registry", mint] · Participant["participant", registry, owner] ·
ExtraAccountMetaList["extra-account-metas", mint]. It receives information and
a veto — nothing else.
flowchart TB
subgraph browser["apps/web · Next.js 16 + React 19"]
UI["5 public destinations<br/>overview · settlements · create · demo · proof"]
ADP{{"VeilAdapter<br/>typed boundary"}}
DEMO["demo adapter<br/>deterministic, labelled"]
LOCAL["local-validator adapter<br/>reports unavailable"]
UI --> ADP
ADP --> DEMO
ADP --> LOCAL
end
subgraph chain["SOLANA · isolated local validator only"]
direction TB
SET["veil-settlement<br/>Config · Quote · vaults"]
HOOK["eligibility-hook<br/>Registry · Participant · ELAM"]
T22["Token-2022"]
ZK["ZK ElGamal Proof program"]
SET -->|"CPI: transfer_checked ×2<br/>ONE instruction"| T22
T22 -->|"CPI on every transfer<br/>of the bond mint"| HOOK
HOOK -.->|"veto only —<br/>never moves tokens"| T22
T22 -->|"reads verified<br/>proof contexts"| ZK
SET -->|"INV-22: reads the<br/>4th proof context"| ZK
end
subgraph assets["mock assets"]
VBILL["VBILL bond<br/>TransferHook ext<br/>decimals 0"]
VUSD["VUSD cash<br/>ConfidentialTransfer ext<br/>decimals 6"]
end
T22 --- VBILL
T22 --- VUSD
LOCAL -.->|"NOT WIRED —<br/>needs browser proof generation"| chain
style LOCAL stroke-dasharray: 5 5
style DEMO fill:#f6f6f6
The settlement itself, and why the ordering matters:
sequenceDiagram
participant S as Seller
participant P as veil-settlement
participant T as Token-2022
participant H as eligibility-hook
participant B as Buyer
S->>P: create_quote_confidential
Note over P: terms_hash binds parties, mints,<br/>amounts, expiry, nonce, program, cluster<br/>price sealed as a commitment
P->>T: bond → bond_vault
T->>H: hook runs on the bond leg
B->>P: accept_quote (cash → cash_vault)
B->>P: prepare_confidential_escrow
Note over P,T: PDA-signed: Reallocate, ConfigureAccount,<br/>Deposit, ApplyPendingBalance
B->>T: 4 proof contexts (buyer is the authority)
Note over B: range proof needs its own<br/>transaction — 1,205 B, 200,000 CU
rect rgb(240,245,255)
B->>P: settle_confidential
Note over P: ONE INSTRUCTION<br/>status · expiry · terms hash · PDAs · destinations<br/>INV-22: commitment == recombined destination ct
P->>T: CPI 1 — bond_vault → buyer
T->>H: hook may veto here
P->>T: CPI 2 — confidential cash_vault → seller
Note over P,T: if either fails, the runtime discards<br/>EVERY write in the transaction
end
B->>P: withdraw_confidential_escrow (residual)
B->>P: reclaim_escrow ×2 → close_quote
Full detail: docs/ARCHITECTURE.md.
Veil hides exactly one thing: the cash amount. No anonymity claim is made
anywhere, and the honest inventory of what stays public is
docs/PRIVACY_MODEL.md §5.
Permanently visible to anyone: both wallets, both vaults, the quote PDA, both mints, the timing to the second, the entire bond leg including the quantity, the fact that a confidential transfer occurred, and both parties' compliance status.
The design is split authority, and the one-line summary is:
The program can move the cash but cannot read it. The buyer can read the cash but cannot move it alone.
The quote PDA is the token authority. The buyer holds the vault's ElGamal and
AES keys. The seller holds the destination's keys. The program holds no secret
material at all — there is nothing to steal from it or subpoena out of it.
Both halves were proven insufficient alone against a real validator: the key
holder signing directly fails with OwnerMismatch, and the authority using
foreign proofs fails with ConfidentialTransferElGamalPubkeyMismatch.
This deserves to be said plainly rather than buried:
- The bond quantity is public and the price is not, but they are linked. Anyone who knows the market price of the bond at that slot can estimate the cash amount to within a spread. For a liquid instrument that makes the confidentiality weak in practice, however strong the cryptography is.
- Repeat trading reveals structure. Same parties, same mint, same size, at regular intervals — the amount adds little once the pattern is visible.
- A single confidential transfer out of a freshly funded vault reveals almost everything. The deposit is public. If it equals the trade, so does the price.
The mitigation is behavioural, not cryptographic, and Veil does not enforce it.
Twenty-two invariants, INV-01…INV-22, in
docs/SECURITY_INVARIANTS.md. The rule that
governs them: an invariant with no passing test is not implemented. Every
enforcement site in program code carries a greppable tag:
rg "INV-[0-9]" programs/The seven that shape most design decisions:
- INV-01 Atomicity — both legs in one instruction.
- INV-03 Exact terms —
terms_hashbinds parties, mints, amounts, expiry, nonce, program ID and cluster domain. - INV-06 Single execution — status machine plus
(seller, quote_id)PDA seeding. - INV-14 No hidden custody — escrow vaults, never open-ended delegates.
- INV-16 Non-bypassability — the policy is on the asset; calling Token-2022 directly still triggers it, and the hook refuses to run outside a real transfer.
- INV-19 Safe terminal path — expiry moves no tokens, so the state machine's liveness never depends on the compliance policy.
- INV-22 Exact payment binding — the confidential amount paid equals the amount committed in the signed quote.
Three underpayment attacks run against the real deployed program, on the same live quote that then settles correctly — so the quote that refuses the underpayment is provably the one that accepts the correct payment.
| # | Attack | Result |
|---|---|---|
| 1 | A fully coherent transfer of 90,000 instead of 95,000, with its own valid equality, validity and range proofs, and a binding proof that genuinely proves what it claims | rejected · AmountBindingCommitmentMismatch |
| 2 | The honest binding proof — truly about the agreed price — paired with the cheaper transfer | rejected · AmountBindingTransferMismatch |
| 3 | Correct commitment and correct price, binding proof stated under a third party's ElGamal key | rejected · AmountBindingDestinationKeyMismatch |
Balances and ciphertexts are byte-identical afterwards, and each rejection names its own binding failure.
Attack 1 is the one that matters: nothing cryptographic is wrong with the attacker's submission. Every individual proof verifies and the ZK program accepts all four contexts. It is the quote's immutable commitment — inside the terms hash since creation — that refuses it.
Attack 2 earns its place separately, because a proof can be true and irrelevant. Without the on-chain ciphertext recombination that catches it, the binding would be decoration.
Design derivation, read from pinned crate source:
docs/INV22_DESIGN.md.
| Suite | Count | Command |
|---|---|---|
| Rust workspace (unit · integration · adversarial · confidential) | 188 | cargo test --workspace |
| Confidential-balance verifier (out of workspace, own lockfile) | 17 | cargo test --locked --manifest-path tools/m4-balance-verifier/Cargo.toml |
| Frontend (9 suites) | 75 | cd apps/web && npm run test |
| Integrated settlement claims, on a real validator | 20/20 | bash scripts/verify.sh --full |
All measured by execution against an isolated local validator, never estimated.
Recorded in artifacts/m4-integrated-settlement.json.
| Transaction | Size | Accounts | Compute |
|---|---|---|---|
settle_confidential |
977 B (limit 1,232) | 21, 7 writable | 115,243 – 197,741 CU |
create_quote_confidential (hook fires) |
752 B | 15 | 83,459 – 140,459 CU |
prepare_confidential_escrow |
570 B | 11 | 48,177 – 52,677 CU |
withdraw_confidential_escrow |
526 B | 11 | 26,384 – 30,884 CU |
| Range-proof context verification | 1,205 B | 3 | 200,000 CU |
| Rent | Lamports |
|---|---|
| Proof-context rent, recovered | 31,208,640 |
| Vault + quote rent, recovered | 9,187,200 |
Two things about these numbers that matter more than the numbers.
Compute is a range, not a point. Repeated runs of byte-identical code measured
settle_confidential between 115,243 and 197,741 CU. The spread is the
find_program_address bump-miss effect — six PDAs are searched during a hooked
settlement, each miss costs roughly 1,500 CU, and the canonical bump depends on
addresses that are effectively random. Never quote the low end. The worst
observed is 2,259 CU under the 200,000 default, which is why the explicit
SetComputeUnitLimit is load-bearing rather than cosmetic.
The range proof cannot share a transaction. At 1,205 B and a full 200,000 CU it needs its own. Making the buyer the proof-context authority rather than the vault PDA was forced by measurement: with the PDA the range-proof transaction serialises to 1,237 B — five bytes over the 1,232-byte limit.
977 B is also exactly what tests/confidential/layout.rs projected before the
instruction existed. That projection had never previously been checked against
reality.
apps/web is a Next.js 16 / React 19 product interface. Its main dashboard is a
simulated product walkthrough, written in the user's vocabulary rather than
the protocol's. A person does not have "a
quote with a confidential cash leg"; they have "a settlement whose private
agreed payment is protected". The mapping between those vocabularies is
documented on every type in lib/domain.ts so the two can be checked against
each other.
The simulation remains clearly separated from the live public-devnet baseline.
Only /devnet requests wallet signatures or sends Solana transactions.
| Route | What it does |
|---|---|
/ |
Explicitly labelled Demo workspace and settlement activity |
/devnet |
Wallet-signed operations against the separate minimal public-devnet DvP program |
/settlements |
Four tabs, search, sort, privacy filter, full history |
/settlements/new |
Guided four-step create flow |
/settlements/[id] |
Detail, timeline, primary action, Advanced details |
/settlements/[id]/receipt |
Receipt plus a sanitized evidence download |
/how-it-works |
Six protections plus separate real validator evidence |
/demo |
Exactly nine simulated settlement steps, including underpayment refusal |
There is exactly one interface, VeilAdapter, with two implementations. The
rules are not stylistic:
- Every value that reaches the UI carries a
source, and the UI shows it. The application header saysSimulated UIon every screen. - The demo adapter never invents a transaction signature. Where a real
adapter would return one it returns
undefined, and the UI says the step was simulated rather than showing a plausible-looking lie. There is not one fake signature in the codebase. - A real adapter never falls back to fixtures. The local-validator adapter
returns
unavailablewith the reason, so "connected" and "not connected" are visible in the product rather than hidden behind demo data. - The confidential amount is never in a shape that could be persisted.
revealPayment()is a separate, explicit call; the plaintext never enters a list, a route, a URL, a log, or browser storage.
Real captures of the running application, not mockups. Reproduce them from a
clean clone with bash scripts/capture-screenshots.sh while the dev server is
up. Provenance and what each one is evidence of:
docs/screenshots/README.md.
The settlement detail screen — private agreed payment behind an explicit Reveal privately action, the public cash reserve shown separately, the seven-stage timeline tagged on-chain / visible / protected, and the banner stating plainly that no transaction signatures exist for demo data:
![]() |
![]() |
/ — explicit Demo workspace |
/settlements — full settlement history |
![]() |
![]() |
/how-it-works — separate real evidence |
/demo — nine steps, including refusal |
Also captured: /settlements/new and
the receipt.
No wallet is connected in any of these, no real settlement is shown, and no confidential amount was revealed before capturing — so no plaintext price is committed to this repository.
Requirements: macOS or Linux, ~10 GB free disk for the full Solana toolchain, Node 22.
cd apps/web && npm ci && npm run devThen open http://localhost:3000. This runs the demo adapter, which is deterministic, offline and clearly labelled.
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -ysh -c "$(curl -sSfL https://release.anza.xyz/v4.1.2/install)"cargo install --git https://github.com/solana-foundation/anchor avm --tag v1.1.2 --force && avm install 1.1.2 && avm use 1.1.2Add to your shell profile:
export PATH="$HOME/.avm/bin:$HOME/.cargo/bin:$HOME/.local/share/solana/install/active_release/bin:$PATH"The two-Solana gotcha. Installing Anchor switches the active Solana release to 3.1.10. That is expected: Anchor 1.1.2 manages its own toolchain and 3.1.10 is what its CI validates against. Keep 4.1.2 installed for running a validator, because Agave 4.x refuses to load an sbpf v0 executable and is where the ZK ElGamal program is invocable. Switch with
agave-install init <version>.
anchor build --ignore-keys && cargo test --workspaceExpected: two .so artifacts, two IDLs, 188 passing tests.
One command, and it needs no network, no validator and no wallet:
bash scripts/verify.sh17 checks — preflight with exact fix instructions, a real secret scan, format, build, clippy with warnings denied, the full Rust suite, the out-of-workspace balance verifier, and artifact/architecture assertions.
Everything above plus a real confidential settlement:
bash scripts/verify.sh --full21 checks. Starts an isolated local validator on its own ports and ledger, deploys both programs, drives a complete confidential DvP through them, executes the three INV-22 attacks, terminates the lifecycle, and asserts all 20 claims — then asserts the artifact was written by that run, so a stale committed file can never make this gate look greener than it is.
The frontend has its own single gate:
cd apps/web && npm run verifyPrettier, ESLint, tsc --noEmit, 75 tests, and a production next build. The
same command runs in CI on Node 22.
The one thing that needs the network.
--fullclones the real Token-2022 program from devnet at genesis. That is a read of a public cluster and the only network access anywhere in the project; no transaction is ever sent to a public cluster. The public devnet RPC rate-limits and intermittently times out, so the script retries three times and then fails loudly, naming the cause. An environmental failure is reported as one — it is never allowed to look green.
Individual M4 evidence runs, each starting and stopping its own isolated validator:
bash scripts/run-m4-confidential-lifecycle.shbash scripts/run-m4-context-replay.shbash scripts/run-m4-split-authority.shbash scripts/run-m4-integrated-settlement.shveil-dvp/
├── programs/
│ ├── veil-settlement/ quote lifecycle, atomic DvP, INV-22 binding
│ │ ├── src/instructions/ 15 instructions
│ │ ├── src/confidential_binding.rs the INV-22 check
│ │ └── src/token_cpi.rs hook-aware transfer_checked
│ └── eligibility-hook/ Token-2022 transfer hook
├── tests/ veil-tests — one Cargo crate, four categories
│ ├── unit/ pure logic and property tests
│ ├── integration/ happy paths, LiteSVM
│ ├── adversarial/ attacks that must fail
│ ├── confidential/ layout, binding, encoding parity, guards
│ └── src/bin/integrated.rs the real-validator settlement harness
├── tools/
│ ├── m4-balance-verifier/ confidential decryption (own lockfile)
│ ├── m4-context-probe/ proof-context inspection
│ └── m4-cpi-spike/ throwaway CPI feasibility program
├── apps/web/ Next.js 16 simulated walkthrough, 75 tests
│ ├── app/ routes
│ ├── components/ shell and UI primitives
│ └── lib/adapter/ the typed VeilAdapter boundary
├── scripts/ verification, secret scan, M4 evidence runs
├── artifacts/ measured evidence, committed
└── docs/
tests/ declares explicit [[test]] targets so the four categories stay real
directories. The adversarial suite is the part a reviewer will actually want to
read.
packages/ contains empty scaffolding directories (client, shared,
test-utils) left from the initial Anchor layout. Nothing is tracked in them and
nothing imports them — the TypeScript client was never needed, because the
integrated harness encodes instructions from the programs' own Rust types.
The complete inventory is docs/LIMITATIONS.md. The ones
a reader should know before forming an opinion:
- No public-cluster deployment. Everything is a local isolated validator.
- Unaudited. No independent professional review has taken place. Automated secret scanning is a control against accidents; it is not an audit.
- Confidentiality is weak in practice for a liquid instrument, because the public bond quantity plus a known market price bounds the hidden price.
- A stalled buyer leaves the quote open.
withdraw_confidential_escrowneeds proofs only the buyer can produce, so a buyer who never withdraws strands their own residual and leaves the seller's quote rent unrecovered. There is no permissionless crank and there cannot be one. - A buyer who loses their confidential keys cannot recover the residual. Unmitigated by design — an administrator recovery path would recreate exactly the custody the protocol exists to remove.
- A compromised registry administrator is not defended against. They can approve anyone. This is a demo registry, not a governance system.
- The signed RFQ is one step short of fully delegated. The seller must still sign the transaction, because escrowing the bond moves their own tokens.
- Everything in the LiteSVM suite runs against Token-2022 v10. Devnet runs v11. That assumption is unverified.
settle_publiccarries noSetComputeUnitLimitand its cost is a range (86,413 – 176,382 CU). An unlucky-but-legal set of addresses can approach the 200,000 default.- No auditor key, no dual-leg confidentiality, no per-trade keys.
This is a solo-built prototype for a hackathon application, not a project seeking contributions. Issues pointing out a factual error in the documentation or a defect in an invariant are genuinely welcome; feature pull requests are likely to be declined, because the value of this repository is that every claim in it has been executed and observed.
If you do open something, the standard the project holds itself to is in
CLAUDE.md: never claim it works without running it, and no mock
masquerading as a feature.
Licence: MIT — see LICENSE. Provided as-is for educational
purposes, with no warranty of any kind, and with the additional notice recorded
in that file.
| Document | What it covers |
|---|---|
docs/ARCHITECTURE_LAYMAN.md |
Start here. The whole system with no jargon at all |
docs/FOUNDER_WALKTHROUGH.md |
Every step mapped to the code that implements it and the test that proves it |
docs/DEMO_SCRIPT.md |
A rehearsable live demo, the questions you will get, and what must never be claimed |
docs/DEMO_VIDEO_SCRIPT.md |
The recorded 90–120 second version |
docs/RELEASE_EVIDENCE.md |
Every gate run for this release, with its observed result |
docs/RELEASE_CHECKLIST.md |
What would be published, what would not, and the order to do it in |
docs/DEPLOYING_THE_WALKTHROUGH.md |
Deploying the simulated walkthrough to Vercel, and what not to claim about the URL |
docs/MASTER_SPEC.md |
Authoritative specification: assets, programs, quote integrity, pinned versions |
docs/ARCHITECTURE.md |
System map, PDA map, settlement sequence, why escrow over delegates |
docs/SECURITY_INVARIANTS.md |
All 22 invariants, their enforcing code and proving tests |
docs/INV22_DESIGN.md |
How exact payment binding was derived from pinned crate source |
docs/PRIVACY_MODEL.md |
Threat model, key custody, and what stays public |
docs/TERMINAL_ROUTES.md |
Every way a quote can end, and what happens to the money |
docs/LIMITATIONS.md |
What the system does not do |
docs/M4_FEASIBILITY.md |
The confidential-transfer feasibility investigation |
docs/REPRODUCIBILITY.md |
Verifying from a clean clone; what is excluded from Git and why |
docs/ROADMAP.md |
Milestones and risk register (historical — see status above) |
CLAUDE.md |
Working context, commands, and the version traps found the hard way |




