Built for ![]()
Test your Soroban token
against SEP-41.
Sixteen checks run on-chain from your terminal. Every verdict carries its evidence — and a check that cannot reach an answer says so instead of guessing.
A real run, printed in full
The logged run of 2026-09-27 against fixtures/vulnerable-token, row for row — not an illustration. Open any row to see the clause it asserts, and what an unverifiable one would need.
- EXPECTED
- balance() returns a non-negative integer for any address
- EXPECTED
- a spend with no allowance must be refused
- EXPECTED
- a zero transfer must not change either balance
- TRANSACTION
- 4a6129a464ff08db5eb2d83f6861508b6f0798ea86885f52a3a42882f191c8aa
- EXPECTED
- debiting and crediting the same address must net zero, so a change means one side was applied without the other
- TRANSACTION
- 2969d5ffd800aa79a577b8e03b2c57d15026ec418c9fb1b629a2d059c04fa7dc
- EXPECTED
- a negative amount must be refused
- EXPECTED
- more than the balance must be refused
- TO REACH A VERDICT
- a contract whose accounting has not already gone negative
- EXPECTED
- transfer() moves exactly the amount between the two parties
- TO REACH A VERDICT
- a readable before-state to compare against
- TRANSACTION
- 17dcefa66339200ba744be506b9726d8c32a462a057db668d1d764f1d5a1a551
- EXPECTED
- approve() sets the allowance it was given
- TRANSACTION
- 9517b3f5e8ba9e502c1c7c1e8b909ee9f387b1fa8f5f181758b7c910d9ef5ca2
- EXPECTED
- transfer_from() spends against the allowance
- TO REACH A VERDICT
- a readable before-state to compare against
- TRANSACTION
- 15f5690fb74ddea06d94c1ac0d7920dd0dc310729af7b3e1cec79a3b0a68a679
- EXPECTED
- burn() reduces the holder's balance by the amount
- TRANSACTION
- bebdd4730b024264180f37f4e37629178e8af0a54a7481c1406d4b7a6cff28ea
- EXPECTED
- burn_from() reduces the holder's balance and leaves the spender's untouched
- TO REACH A VERDICT
- a readable spender balance
- TRANSACTION
- 559dea4f68b9c02a249ca4bf8ad5dc364d5669939d4e59209db8c1cbf11ddb57
- EXPECTED
- a lapsed allowance must be refused
- TO REACH A VERDICT
- a contract whose accounting has not already gone negative
The transfer-self defect was not planted — the fixture was written with three deliberate flaws and the tool found a fourth.
For developers
One command.
Sixteen checks.
On npm as soroban-guard. Run it with npx on Node 24 — nothing to install and nothing to configure. Point it at a deployed contract and it reports every clause it could exercise.
- 16
- Checks per run
- 5
- Verdicts, not two
- 0
- Simulated writes
# reads only — no install, no keysnpx soroban-guard <contract-id> # a full run: both parties sign, all sixteen answerOWNER_SECRET=$(stellar keys secret owner) \SPENDER_SECRET=$(stellar keys secret spender) \ npx soroban-guard <contract-id>Exits 0 conformant · 1 a finding · 2 no verdict reached — so a pipeline gates on the answer instead of parsing text.
Run it yourself
fixtures/vulnerable-token is live on testnet: a SEP-41 token written with three deliberate flaws. Paste the command and the reads answer in seconds, no keys needed.
The writes need an account that holds the token, so a full run takes two keys. Here is one, with the fixture's own. Point the same command at your token, with your keys, and you get the same report about yours.
No keys to hand? The browser checker runs all sixteen against a demo copy of this token: connect Freighter, take five test units from its faucet, press run.
Try now — no keys
npx soroban-guard CDYOSYSK6C334LXIZULHLKOYRKF4HBQHLK7EIH5VEHQDCTJIBPZOHGXYA full run, both keys
$ OWNER_SECRET=$(stellar keys secret alice) \ SPENDER_SECRET=$(stellar keys secret bob) \ npx soroban-guard CDYOSYSK…PZOHGXYSEP-41 Conformance CDYOSYSK…PZOHGXYinterface (3/3)✓sep41-decimalsreturned 7✓sep41-namename is "Vulnerable Test Token"✓sep41-symbolsymbol is "VULN"behavior (6/13)✓sep41-balancebalance is 1001✓sep41-transfer_from-unauthorizedan unauthorized spend was refused✗sep41-transfer-selfthe holder's balance moved from 1001 to 1002; debiting and crediting the same address must net zero, so a change means one side was applied without the other✗sep41-transfer-negative-amounta transfer of -1 succeeded; the holder gained, consistent with the contract reading it as a transfer in the opposite direction — anyone can withdraw from anyone… 9 more rows: 4 held, 5 unverifiable once the accounting had gone negative2 violations found · 9/16 checks heldExits 1: a finding. The balances move by one on every run — the self-transfer flaw adds a unit each time it is exercised.
Five verdicts, not two
Pass and fail are the only two about the contract. The other three are about the run — and each names what would turn it into a verdict.
What it covers
Conformance, not security: a pass shows the contract honours the SEP-41 interface as this suite exercises it.
Covers
- SEP-41 token interfaceAll ten members. Five reads observed, five writes signed and submitted, and six checks that assert what a contract must refuse.
- Both kinds of tokenStellar Asset Contracts, native XLM included, and custom WASM tokens. For a WASM token the contract spec is read first, so a member it does not declare reports NOT IMPLEMENTED without spending a call.
- Reports you can useA terminal report, a CHECKS.md, and JSON — with exit codes a CI pipeline can gate on: 0 conformant, 1 a finding, 2 no verdict reached.
Does not cover
- EventsNot observed yet. A token that moves balances correctly while emitting wrong or missing events still passes.
- Security auditA pass means the contract honours SEP-41 as this suite exercises it. It says nothing about admin mint, upgradeability or blacklists, all of which are SEP-41-conformant.
- Other standardsSEP-41 only. It does not review WASM or check arbitrary Soroban contracts or any other SEP.
- Mainnet writesReads work anywhere. The CLI signs writes on a test network only unless given an explicit flag; the browser checker is testnet-only.