Network
devnet only
every figure on this site was read against devnet, and the app is locked to it
Security
Three of the six risks below end in nothing standing in the way at all. They are here because a security page that only lists its defences is a page you cannot check.
Security overview
Network
devnet only
every figure on this site was read against devnet, and the app is locked to it
Mainnet writes
locked
no mainnet transaction has ever been built by this code, and opening that gate needs more than a green build
Independent audit
none
no firm, no report, no score. There is nothing to link to and this page does not pretend there is
Real Phantom wallet
not completed
every browser run was driven by a throwaway devnet keypair over the debug protocol: the signatures are genuine, the human is not
Served at verx.fun under a certificate for verx.fun that expires 2027-01-15, read 2026-10-17. Being reachable is not the same as being audited, and none of the three no's above changed when it went up.
Key security
Your ElGamal keypair and AES key are derived in the browser from one wallet signature. They live in memory, in one tab, and a reload asks for the signature again.
The signature is the key
Signing solana-conf-bal/v1 is what produces your vault keys. Approving it on a site that is not this one hands that site the same keys, and no header here can stop that.
VERX will never ask for a seed phrase, a private key or an export of anything. There is no server to send one to. If a page calling itself VERX asks, it is not this page.
| directive | what it stops |
|---|---|
| connect-src 'self' https://api.devnet.solana.com | an injected script posting your keys to a server of its own |
| script-src 'self' 'wasm-unsafe-eval' | a CDN, an analytics tag or a font host running code here |
| frame-ancestors 'none' | this page being framed invisibly and clicked through into signing |
| object-src 'none' | a plugin object being embedded at all |
| form-action 'none' | anything on the page posting a form somewhere else |
| base-uri 'self' | a rewritten base tag pointing every relative url elsewhere |
Also set: X-Content-Type-Options nosniff, X-Frame-Options DENY, Referrer-Policy no-referrer, Cross-Origin-Opener-Policy same-origin, Strict-Transport-Security for 180 days, and a Permissions-Policy denying camera, microphone, geolocation, payment and usb. A probe reads them back off the wire rather than out of the source.
Mainnet
Mainnet reads are allowed: that is how the compatibility audit was done. A mainnet WRITE is refused in the one place this app broadcasts anything, before a wallet is asked for anything, until every item below is true.
npm run audit-mainnet: Token-2022, the ZK ElGamal Proof program and SPL Record all run on mainnet, and an SDK encoded Deposit decodes there. 20 ok, 0 FAIL, 10 controls, 2026-10-17.
No Token-2022 mint with the confidential transfer extension exists on mainnet for this project. One must be created deliberately, with its authorities decided first: see MAINNET in lib/verx.ts.
A person has to drive connect, unlock, configure, shield, apply, send, withdraw and recovery with the real Phantom extension on devnet first. npm run phantom prints the checklist and reads the chain afterwards.
No independent security review has been done, so no critical finding has been closed, because none has been opened.
The owner has to set VERX_MAINNET_WRITE_APPROVED to the exact phrase "i-approve-mainnet-writes" in the server environment. Nothing in the repository can set it for them.
The programs a confidential transfer needs do run on mainnet, measured 2026-10-17: Token-2022, the ZK ElGamal Proof program and SPL Record all answered, and an instruction encoded by the SDK decoded there. Mainnet runs 4.3.0 and devnet runs 4.4.0-beta.0, so those matching answers are not a tautology. What does not exist is a mint: none has been created, and creating one is a set of decisions listed in lib/verx.ts.
Transaction safety
The first two nodes are what any product draws. The third is the one that was measured by abandoning real flows on purpose, at every boundary they have.
Every transaction is simulated against the cluster before your wallet is asked for anything, and each proof is verified by the ZK ElGamal Proof program inside the validator. A proof that does not hold is refused there, not here.
simulated before signing · verified by the validator
A signature is not inclusion. The executor waits for each transaction to confirm, shows the signature the moment there is one, and the activity list marks a row confirmed only after the ledger has been asked about it.
one approval per transaction · status read from the chain
Every temporary account carries a closing authority from the moment it exists, and the planner refuses any plan where that is not true. An interrupted flow leaves only accounts your own wallet can close, and Home offers to close them and return the rent.
0 transactions can strand lamports · 21 assertions, 4 controls
The old plan, the one that could strand an account nobody could close, is kept as the control: the context-account transfer plan, which strands one account. It must still be refused, and it is. Measured 2026-10-17 by npm run probe-cleanup.
Known limitations
Open any of them for what does NOT stand in the way. Three of the six have nothing there at all, and those three are the ones worth your attention.
Nothing third party is loaded: no CDN, no analytics, no font host, no icon service. The fonts are self hosted at build time, and connect-src reaches only this origin and the RPC.
An RPC accepts arbitrary strings, so a determined injected script could still smuggle bytes out through it. Next needs unsafe-inline for styles and its own hydration script. This raises the cost of an injection; it does not make one harmless.
Nothing. The keys come from a signature over eighteen fixed bytes, so any site that gets you to approve the same message derives the same keys.
No header, no CSP and no code on this page can defend a signature you gave to a different origin. Check the address bar before you approve, every time.
VERX never asks for a seed phrase and has no server to send one to. It asks a wallet provider to sign, and reads back what it returns.
A compromised wallet sees every message it is asked to sign, including the one that derives your vault. A page cannot audit the extension it is talking to.
frame-ancestors none and X-Frame-Options DENY, both set on every route, plus Cross-Origin-Opener-Policy same-origin so an explorer link cannot reach back through window.opener.
Nothing material. This one is a solved problem and the headers solve it.
Every temporary account is given a closing authority in the same transaction that creates it, and the planner refuses to send a plan where that is not true. 0 transactions across both flows can strand lamports.
An interrupted flow can still leave a balance mid-move: some of it applied, some not. Nothing is lost, and the next run picks it up, but the screens will not read as a clean number until it does.
The keys live in the memory of one tab, are never written to any store, and are gone on reload. Nothing persists for malware to read at rest.
Anything that can read the memory of your browser while the vault is unlocked can read the keys. That is true of every wallet and every web app, and it is true here.
The devnet figures were read at slot 511,942,023 on 2026-10-17, and the browser figures at slot 511,962,211 on 2026-10-17. The proofs are checked by ZkE1Gama1Proof11111111111111111111111111111, a program built into the validator. VERX deploys none of its own.
What an encrypted amount buys, and what it does not.