Skip to content

Repository files navigation

Veil DvP

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.


⚠️ Read this before anything else

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.md before forming any opinion about what this does.


Portfolio architecture overview

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.


Contents


The problem, in plain English

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.


What delivery-versus-payment means

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.


What Veil does

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.


Why this needs Solana specifically

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.


What is genuinely novel here

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:

  1. 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;
  2. requiring a fourth proof (CiphertextCiphertextEquality) relating that commitment to the transfer's actual destination ciphertext;
  3. recombining the transfer's own low and high destination ciphertexts on chainct = ct_lo + ct_hi · 2¹⁶, using ristretto curve syscalls — and byte-comparing the result, so the proof cannot be about some other transfer;
  4. 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.


A trade, walked through in plain language

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.


Verified status

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.

Separate minimal public-devnet baseline

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.

✅ Implemented and freshly verified

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

📦 Implemented, with committed earlier evidence

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

🟡 Simulated walkthrough plus minimal live-devnet workspace

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.

⬜ Not implemented

  • Browser-side proof generation (solana-zk-sdk 7.0.1 compiled 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.

🚫 Confidential-program public-cluster deployment: not attempted

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.


Architecture

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
Loading

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
Loading

Full detail: docs/ARCHITECTURE.md.


The privacy model

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.

Where the privacy is genuinely weak

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.


The security model, and INV-22

Twenty-two invariants, INV-01INV-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 termsterms_hash binds 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.

INV-22, proven by attack

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.


Tests and measurements

Tests

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

Measurements

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.


The web application

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

The rules the adapter boundary enforces

There is exactly one interface, VeilAdapter, with two implementations. The rules are not stylistic:

  1. Every value that reaches the UI carries a source, and the UI shows it. The application header says Simulated UI on every screen.
  2. 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.
  3. A real adapter never falls back to fixtures. The local-validator adapter returns unavailable with the reason, so "connected" and "not connected" are visible in the product rather than hidden behind demo data.
  4. 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.

Screenshots

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:

Settlement detail

Overview Settlements
/ — explicit Demo workspace /settlements — full settlement history
Protocol proof Guided demo
/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.


Quick start

Requirements: macOS or Linux, ~10 GB free disk for the full Solana toolchain, Node 22.

1. The frontend alone — no Solana toolchain needed

cd apps/web && npm ci && npm run dev

Then open http://localhost:3000. This runs the demo adapter, which is deterministic, offline and clearly labelled.

2. The full toolchain

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
sh -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.2

Add 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>.

3. Build and test

anchor build --ignore-keys && cargo test --workspace

Expected: two .so artifacts, two IDLs, 188 passing tests.


Full verification

One command, and it needs no network, no validator and no wallet:

bash scripts/verify.sh

17 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 --full

21 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 verify

Prettier, 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. --full clones 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.sh
bash scripts/run-m4-context-replay.sh
bash scripts/run-m4-split-authority.sh
bash scripts/run-m4-integrated-settlement.sh

Repository structure

veil-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.


Known limitations

The complete inventory is docs/LIMITATIONS.md. The ones a reader should know before forming an opinion:

  1. No public-cluster deployment. Everything is a local isolated validator.
  2. Unaudited. No independent professional review has taken place. Automated secret scanning is a control against accidents; it is not an audit.
  3. Confidentiality is weak in practice for a liquid instrument, because the public bond quantity plus a known market price bounds the hidden price.
  4. A stalled buyer leaves the quote open. withdraw_confidential_escrow needs 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.
  5. 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.
  6. A compromised registry administrator is not defended against. They can approve anyone. This is a demo registry, not a governance system.
  7. 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.
  8. Everything in the LiteSVM suite runs against Token-2022 v10. Devnet runs v11. That assumption is unverified.
  9. settle_public carries no SetComputeUnitLimit and its cost is a range (86,413 – 176,382 CU). An unlucky-but-legal set of addresses can approach the 200,000 default.
  10. No auditor key, no dual-leg confidentiality, no per-trade keys.

Contributing and licence

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.


Documentation index

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

About

Experimental Solana DvP protocol: atomically delivers an eligibility-gated Token-2022 asset against an exact confidential payment, rejecting valid-looking underpayments. Unaudited prototype, isolated validator only.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages