Shielded settlement — demo.pay.vauban.tech/shielded
We paid. The amount we transferred is not on the chain.
Below is one payment we made on Starknet Sepolia, its coordinates, and a command that checks our claim against a chain endpoint you pick. It takes about a minute. You do not need to clone anything, compile anything, run any code of ours, or talk to us.
We would rather you disbelieved us and checked than believed us and did not.
The payment
| Pool | 0x07889af20edd41ed80c7e5a4163384b7428b6a980836de5a1f4fa3e78b09e1f0 |
| Class hash | 0x067dddd89d80fedadc06b6f160798f94800a4a70164e5a24301cd0d6076b554d |
| Transaction | 0x40a9089446efdabff4bfa882693b0b22071c7b03e3e00ef23359ac6ab3d8d69 |
| Block | 13575128 |
| Deposited, public | 987654321000000 wei = 0x3824430f6ce40 |
| Transferred, hidden | 123456789000000 wei = 0x7048860daf40 |
| Proving time | 11.9 s |
| Whole payment | 35–41 s approval, proving, submission, discovery |
Two amounts, because a privacy pool has two legs. The deposit is what
entered the pool and it is public by construction — an ERC-20 transfer
is visible to everyone. The second number is what moved inside, and it
is the one that should be nowhere.
Check it yourself
This reads every event our pool has emitted since the block it was
deployed in, and counts how many times each amount appears. It is
curl and jq. Point RPC at any
Sepolia endpoint you trust — ours is deliberately not the default.
POOL=0x07889af20edd41ed80c7e5a4163384b7428b6a980836de5a1f4fa3e78b09e1f0
RPC=${RPC:-https://starknet-sepolia-rpc.publicnode.com}
curl -sS -X POST -H 'content-type: application/json' "$RPC" \
-d '{"jsonrpc":"2.0","id":1,"method":"starknet_getEvents","params":[{
"from_block":{"block_number":13457017},"to_block":"latest",
"address":"'"$POOL"'","chunk_size":1000}]}' \
| jq '[.result.events[] | (.data[], .keys[])]
| {values: length,
deposit_public: (map(select(. == "0x3824430f6ce40")) | length),
amount_we_hid: (map(select(. == "0x7048860daf40")) | length)}'
What it printed for us:
{
"values": 126,
"deposit_public": 6,
"amount_we_hid": 0
}
126 values published by the pool. The public deposit appears six times —
we have made that same deposit on several runs. The amount we transferred
appears zero times.
A count of zero is only worth something if the same command can find
something. That is why it looks for both amounts: if the deposit came
back zero too, the query would be reading the wrong thing and the
interesting zero would mean nothing.
Two counts of the same reading. The script below reports 35
events; the command above counts 126 values.
An event carries several fields, and it is the fields that hold
amounts, so that is what gets searched. Same events, counted one level
down.
How to read this transaction
A pool transaction has a shape worth knowing, and it is not obvious
the first time. Five events, four visible numbers, each doing a
different job:
Event 1 · STRK token 987 654 321 000 000 The deposit, leaving the payer and entering the pool. An ordinary token transfer, and public like every other one. Event 2 · the pool 987 654 321 000 000 The pool recording that same deposit. One movement, written twice, by each side of it. Event 3 and 4 · the pool a long opaque number each The two notes created: one for the recipient, one for the change. Commitments, not amounts; the value cannot be read back out of them. Event 5 · STRK token 3 027 251 739 211 163 664 Gas, paid to the sequencer for including the transaction. The largest number in the whole transaction, and unrelated to what changed hands.
Notice what has no line of its own: the transfer. Money entered the
pool in public, gas was paid in public, two commitments were written
down, and the amount that moved between them
(123456789000000) never appears. Not in the pool’s
events, not in the token’s, not in the receipt.
That absence is the whole product. Everything else on this page
exists so you can confirm it rather than take it from us.
The longer version
strk20-verify-payment.sh
does the same thing with the parts a one-liner skips: it checks the
chain id, checks the class hash deployed under the pool address,
follows event pagination to the end, confirms the transaction actually
touched the pool, and refuses in five distinct ways when a claim does
not hold. It takes the coordinates as arguments, so it checks
your payment as readily as ours.
curl -O https://demo.pay.vauban.tech/strk20-verify-payment.sh
less strk20-verify-payment.sh # read it first
bash strk20-verify-payment.sh 0x07889af20edd41ed80c7e5a4163384b7428b6a980836de5a1f4fa3e78b09e1f0 \
0x40a9089446efdabff4bfa882693b0b22071c7b03e3e00ef23359ac6ab3d8d69 123456789000000
Read it before you run it. We are not going to ask you to pipe a URL
into a shell.
What this shows, and what it does not
Established
- A real transaction, on a public chain, against a contract whose class hash you can check.
- The deposit into the pool is visible — entering a pool is an ERC-20 movement and cannot be hidden.
- The transferred amount appears in none of what the pool published: not in that transaction, not in its receipt, not in any event the pool has emitted since deployment. That is the scan’s reach, and it is stated rather than implied.
- You read all of this through an endpoint of your choosing, running no code of ours.
Not established — by this page or by anything behind it
- Unlinkability
- Who paid whom is not measured here, by this page or by the rail. Absence of the amount is a statement about value privacy, not about anonymity.
- Post-quantum settlement
- The STRK20 rail is classical cryptography. Only the proof layer is post-quantum. Any sentence that lets those two blur is an over-claim.
- That we wrote the pool contract
- We did not. It is our deployment of StarkWare’s open-source contract. StarkWare deploys on mainnet only; there is no canonical STRK20 pool on Sepolia, so we say "our deployment of the STRK20 pool on Sepolia" and never "the STRK20 pool".
- That this is mainnet
- Sepolia. A testnet. The value moved is testnet value.
What we control
This is our pool, and none of what follows can be read off the chain
scan above. So it belongs here, where it is part of the picture
rather than a discovery.
- We hold the screener key
- It authorises who may deposit into this pool. We generated it rather than use upstream’s test key, whose private half is published in their SDK — that would have left a door anyone could open under our name. It also means deposits here happen on our say-so.
- We hold the auditor key
- A tracing key, escrowed at registration by the contract’s own design. It is not a spending key: it cannot move value. It can trace the payments this pool handled.
- We chose the amounts
- Both are distinctive markers picked so that an accidental match in the scan is implausible. That cuts both ways, which is why the command looks for the deposit too: the same query that finds nothing for one number finds the other six times, so it is demonstrably able to find things.
- We operate the prover
- The proof is produced by a proving service we run, on our rebuild of upstream’s image. The chain rejects a wrong proof, so this does not let us fake a settlement — but it is ours, not a third party’s.
What none of it lets us do is put the transferred amount back on the
chain, or take it off. The scan you ran reads a public ledger through
an endpoint we do not operate; no key of ours changes what it returns.
The pool contract is StarkWare’s, open-source, deployed by us on
Sepolia. The proving, the settlement rail and the receipt format are
ours. Read on a chain you choose, on 2026-08-16.