Skip to main content
This page is kept honest rather than flattering. The distinction between a test exists and a test has run is the one that matters, and for most of this project’s life it cut the wrong way. It no longer does — but the parts that are still only written say so.

What has actually run

268, all green, all on every push. Everything but the LiteSVM suite runs with no phone and no validator — which is the whole reason the arithmetic was pulled out of the app and the program in the first place. The 40 LiteSVM tests were the long-standing gap. They went unexecuted for weeks because no machine that had the repo had the Solana toolchain — release.anza.xyz is unreachable from the development environment. The first run scored 39 of 40, and the one failure was worth more than the 39 passes.

What CI runs

.github/workflows/ci.yml, on every push and every pull request: The Rust job is deliberately separate from the Anchor one: the curve carries no Anchor types precisely so it can be checked without a validator, and it answers in seconds instead of waiting on a toolchain install. The Anchor job pins from Anchor.toml’s own solana_version and anchor_version, and is week 4’s graded deliverable — clone → install → anchor test green — run on a machine that starts with nothing.
The typecheck job exists because of a pattern, not a principle. Every defect that has reached main was a TypeScript error found by a person reading code. See below.Each check is negative-tested rather than trusted: removing the ScrollView import gives TS2304, renaming an export in @nelo/pay gives TS2724, and a malformed function makes cargo fmt --check exit 1. A job that cannot fail is not worth adding.

What still has never run

Neither app has rendered. An EAS development build compiles the entire native side, so @nelo/attest’s Kotlin is no longer unproven at the compiler — but a development client does not embed the JS bundle; it loads it from Metro at runtime. So no screen in either app has been drawn, no component has mounted, and the JS has never been bundled at all.Both apps typecheck clean under strict. That is a real result and it is not the same result.
StrongBox has never run on hardware. @nelo/attest compiles; whether a real secure element produces the attestation chain and the low-S signature the chain expects is untested. The devnet gate deliberately uses a software P-256 key, which isolates “does the chain do what we think” from “does the handset do what we think”. Only the first is answered.
The Privy calls have never executed. The flow machine around them has 29 tests, but useLoginWithSMS and useEmbeddedSolanaWallet need an app ID and a device. The call sites were typechecked against the published @privy-io/expo declarations, which catches shape errors and nothing about behaviour.

What that gap already cost

Not hypothetical. While adding the balance display:
packages/pay/src/index.ts never re-exported either of them. awaitPayment would have been undefined at runtime — the till would have crashed the moment a merchant pressed Charge. No test caught it, and at the time none could have: the app had never been bundled and there was no CI. It was found by reading imports while adding an unrelated feature. Then it happened again. While wiring onboarding:
ScrollView was never imported. Tapping Today would have thrown. Found the same way — by eye, adding something else. Then a third, when tsc was pointed at the apps for the first time: they could not typecheck the packages at all. The config error stopped the check before it reached a line of app code.
All three are TypeScript errors. All three were found by a person reading code. None needed an emulator, a handset or an Android toolchain to catch.That is why the typecheck job exists, and why it is four separate invocations rather than a hopeful one. Reading is not a method; it is what is left when there is nothing to run — and now there is.
The gap that remains is narrower but real: a typecheck proves the types, not the render. Nothing in either app has drawn a screen.

How the tests are written

Everywhere in the vault. The first test in vault.rs is the one that must fail: a valid P-256 signature over different bytes has to be rejected.Get the precompile offsets wrong and every positive test below still passes while the program verifies nothing at all. A test that passes because nothing was checked is worse than no test.
packages/voucher/vectors/voucher-v1.json is generated by the Rust side — the chain is authoritative — and asserted by both. TypeScript also verifies signatures that Rust produced, so the check covers the crypto and not only the byte layout.
The curve is tested for sublinearity at every doubling across six orders of magnitude, not at one convenient point. isqrt is brute-forced against its floor property including at u128::MAX. The money splits are checked to add back exactly, at every amount in a list chosen to include the awkward ones.
The LiteSVM tests read the SPL token account, deliberately not the vault’s balance field. The two must agree, and reading the field to check the field proves nothing.

Things that went wrong, and were caught

Kept because they are more informative than the passes: Most were caught by a tool or a test rather than by reading. The one that was not — the FX legs — was a design error, and the ledger’s own balance rule would have rejected it at runtime anyway.

The one worth reading twice

The first-ever anchor test run scored 39 of 40. The failure was only_the_risk_authority_can_publish_reputation, and it was not the program. The test helper passed the same keypair twice — once as fee payer, once as the instruction’s signer. A message dedupes its account keys, so it required one signature while the transaction builder was handed two, and failed with NotEnoughSigners before the program was entered. So the test got an error, is_err() was satisfied, and the property it names — reputation you can set yourself is not reputation — had never been checked. The authority constraint was there and correct, but correct-by-inspection is not proven.
Failing was the good outcome. Had the assertion been on is_err() alone, it would be green today and still empty. Its sibling test passes only because an impostor keypair happens to differ from the payer, so it never hit the trap at all.That is the argument for asserting on the reason rather than the fact of a refusal, and it is why every check added to CI was negative-tested before being trusted.

The devnet gate

The week-1 trust model has been run end to end on devnet against the Trust Stake build: vault funded, RiskConfig initialised, voucher redeemed with the device signature verified by the secp256r1 precompile on a real validator, double-spend refused, signature-over-other-bytes refused. That distinction matters. The gate had passed before, but against a build that predated the Trust Stake. This run is the first time RiskConfig has existed on a real validator and the first time redeem_voucher has been exercised on chain with its new account struct — exactly the kind of change LiteSVM can accept and a validator reject.
#[ignore] so it never runs in the default suite. It creates its own 6-decimal mint, so a run is self-contained, and it costs devnet SOL.
It uses a software P-256 key in place of StrongBox. That is deliberate: it isolates “does the chain do what we think” from “does the handset do what we think”. The second question is still open.