Pavise is a pre-sign firewall for Solana. It takes the bytes a wallet is about to sign, turns every instruction into a sentence, replays them on mainnet and scores how much of your money or authority they hand to someone else.
A pavise was the tall shield a crossbowman planted in front of himself while he reloaded. Signing is your reload: the one moment you stand still and trust what is in front of you. Most drains do not break any cryptography. They ask politely, the wallet shows a vague popup, and the user presses Approve.
Everything runs in your browser. The site, the Chrome guard and your own code all use the same engine, lib/pavise.js: one ES module, no dependencies, MIT. Network calls go to a public Solana RPC, DexScreener for market data and Jupiter for quotes. There are no accounts, no keys and no server that sees your transactions.
NotePavise never asks for a private key or a seed phrase and cannot sign anything. If something that calls itself Pavise asks for one, it is not Pavise.
Start / Quick start
Quick start
Use the site, or import the engine straight from it. No install, no build step.
On the site
Copy the serialized transaction a dApp or a Blink gives you (base64 or base58).
# Node 18+: save the file, then import it as a module
curl -o pavise.mjs https://usepavise.vercel.app/lib/pavise.js
node -e 'import("./pavise.mjs").then(P => console.log(P.inspect(process.argv[1]).report.verdict))' <BASE64_TX>
Try it
This block imports the module from ../lib/pavise.js, the same file the snippet above loads. Press a sample or paste your own transaction.
Live · pavise.js
Start / How it works
How it works
Four passes over the same bytes. Each one can only add information, none of them changes the transaction.
1 · Parse
The payload is decoded from base64 or base58 and split into signatures, header, account keys, lifetime, instructions and lookup tables. Pavise knows all three layouts in use on mainnet today. See Transaction formats.
2 · Resolve
Versioned v0 transactions can load accounts from address lookup tables. Those addresses are not in the message, so Pavise reads the tables on mainnet and puts the real addresses back before any rule runs.
3 · Decode and assess
Every instruction of a known program is decoded into a name, arguments and roles: from, to, delegate, newAuthority. Then 21 rules look at the roles. Most of them ask one question: does this give something to an address that is not one of the signers?
4 · Simulate
Last, the same message runs through simulateTransaction on mainnet with signature checks off. The SOL balance of up to ten writable accounts is read before and after. See Simulation.
NoteThe score never depends on the simulation. A drainer that fails to simulate is still a drainer, and a clean simulation does not clear a SetAuthority.
Firewall / Transaction formats
Transaction formats
Solana has three wire formats in use. Pavise detects them from the first bytes and parses each one.
Legacy
A compact-u16 count of signatures, the signatures, then the message: three header bytes, account keys, recent blockhash and instructions. All accounts sit in the message.
Versioned v0
Same as legacy, but the message starts with 0x80 and ends with address lookup tables: for each table, the indexes of writable and read-only accounts it contributes.
Versioned v1
Starts with 0x81 and drops the signature prefix: the message comes first and the signatures last. Compute budget settings moved out of instructions into a config mask with fixed slots.
which config slots follow (bits 0–1 priority price, 2 compute limit, 3 loaded data size, 4 heap)
lifetime
32 bytes
recent blockhash
counts
u8, u8
instructions, addresses
addresses
n × 32
account keys
config values
k × u32
one per set bit of the mask
instruction headers
n × 4
program index, account count, data length (u16)
payloads
varies
account indexes then data, per instruction
signatures
s × 64
at the end
WarningThe v1 layout above was read from live mainnet transactions and checked against what RPC returns for the same signature. If the network changes it again, Pavise refuses to parse rather than guess.
Accepted input
Full transaction in base64 (what dApps and Blinks return).
Full transaction in base58 (what some explorers copy).
Bare message bytes (what tx.serializeMessage() or tx.message.serialize() return).
A Uint8Array or plain array of bytes.
Firewall / Risk rules
Risk rules
Each rule that fires adds its weight. The score is the sum, capped at 100. The table is generated from the RULES export of the library you are reading this with.
All rules
Rule
Weight
Level
What it means
“Not you”
Most rules compare an address with the signers. The owner is the fee payer unless you pass owner, or set a wallet in the policy vault. Any address that signs the transaction counts as you, so a transfer between two of your own signing wallets does not fire.
Thresholds
Unlimited approve: amount ≥ 263.
Large SOL out: more than 1 SOL in plain transfers from a signer to a non-signer.
Token sweep: two or more token transfers to the same outside account.
High priority fee: price × compute limit above 0.01 SOL.
Unknown programs: counted for the first three only.
Firewall / Verdicts & policy
Verdicts and policy
The score maps to three verdicts. Your policy can turn any of them into a block.
Verdicts
Score
Verdict
Meaning
0–19
CLEAR
Nothing hands over funds or authority.
20–49
CAUTION
Moves value or grants a right. Read every line.
50–100
BLOCK
Takes control of funds or authority.
Policy vault
The vault on the site stores four fields in localStorage under pavise.policy: wallet, 24 h SOL cap, maximum risk and a frozen flag, plus a ledger of spends you confirmed with I signed it.
const report = assess(tx, {
owner: "YourWallet…",
policy: { dailyCapSol: 5, spentSol: 1.2, maxRisk: 30, frozen: false }
});
report.blockedByPolicy; // ["POLICY_SPEND_CAP"] when SOL out > 3.8
Frozen blocks every transaction that moves anything.
Spend cap blocks when plain SOL out is more than what is left of the 24 h cap.
Max risk blocks a CAUTION above your number. A BLOCK stays a BLOCK with its own reason.
NoteThis is a policy for your eyes, not an on-chain lock. It cannot stop a wallet you sign with elsewhere. It stops you from signing here without seeing that you went over.
Firewall / Simulation
Simulation
The transaction runs against the current mainnet state, without a signature, and nothing is sent.
const sim = await simulate(base64Tx);
sim.ok; // false if a program returned an error
sim.deltas; // [{ account, before, after, delta }] in SOL
sim.logs; // program log lines
sim.unitsConsumed;
sigVerify: false and replaceRecentBlockhash: true, so unsigned and stale transactions still run.
Up to 10 writable accounts are watched: that is the limit of the public RPC.
Samples on the site are built with made-up accounts, so they skip simulation.
WarningA simulation is a snapshot. A program can behave differently a second later, and a durable nonce transaction can be sent days later. That is why the score does not trust it.
Tools / Mint audit
Mint audit
Who can still change a token after you buy it.
const a = await auditMint("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263");
a.score; a.verdict; // 0 "CLEAR"
a.checks; // [{ id, ok, weight, text }]
a.market; // { symbol, priceUsd, liquidityUsd, … } from DexScreener
Check
Weight
Fires when
MINT_AUTHORITY
20
Someone can still print more supply.
FREEZE_AUTHORITY
20
Someone can freeze your token account.
PERMANENT_DELEGATE
35
Token-2022: one wallet can move tokens from every holder.
TRANSFER_HOOK
15
Token-2022: every transfer calls another program.
TRANSFER_FEE
15
Token-2022: transfer fee above 1%.
DEFAULT_FROZEN
25
New token accounts start frozen.
NON_TRANSFERABLE
40
The token cannot move at all.
PAUSABLE
20
An authority can pause the token.
LIQUIDITY
10
The deepest pool holds less than $10,000, or there is none.
Stablecoins like USDC keep mint and freeze authority on purpose and score CAUTION. That is a correct reading: the issuer can freeze you, and you should know.
Tools / X-ray & Watchman
X-ray and Watchman
Paste anything with a base58 shape and Pavise says what it is.
Input
Kind
What you get
87–88 char signature
transaction
Landed or failed, slot, fee, decoded instructions, score, SOL changes
Wallet
wallet
SOL balance and live token delegates
Token mint
mint
Full mint audit
Token account
token-account
Owner, mint, amount, delegate
Program
program
Name if known, loader
Anything else
data / empty
Owner program and size, or nothing
const x = await xray("JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4");
x.kind; // "program"
x.text; // "Jupiter Aggregator v6. Executable code, owned by Upgradeable Loader."
Watchman on the site is the same function with a chat log around it. It is rule-based: no model reads your input and nothing is stored.
Tools / Intent check
Intent check
Say what you want in plain words, see the price and whether your policy allows it, before any app builds a transaction.
Pattern
Example
swap|sell|convert N A to|for B
swap 2 SOL to USDC
buy N A of B
buy 1 SOL of BONK
send|pay N A to ADDRESS
send 0.5 SOL to 7xKX…
Tokens by ticker: SOL, USDC, USDT, JUP, BONK, WIF, JTO, PYTH, RAY. Anything else by mint address.
const r = await quoteIntent(parseIntent("swap 2 SOL to USDC"), { dailyCapSol: 5 });
r.allowed; // true
r.quote.out; // USDC you get
r.quote.impactPct; // above 5% the intent is blocked
A Blink is a Solana Action behind a link. X renders it as a card with buttons, and each button returns a transaction to sign. Pavise opens the same card and checks that transaction.
Flow
GET the action URL: title, icon, description and buttons.
Press a button: POST {"account": "<your wallet>"} to its href.
The action answers {"transaction": "<base64>"}, and Pavise inspects it like any other.
Links can be a direct URL, solana-action:https://…, or a page URL with ?action=. Your wallet address is sent to the action only when you press a button.
Our check-in Blink
Pavise serves its own action at /api/actions/verify, listed in /actions.json. It returns a zero-value Memo transaction signed by you, so the whole Blink flow can be watched with nothing at stake. It scores 0 and simulates clean.
const b = await fetchBlink("solana-action:https://usepavise.vercel.app/api/actions/verify");
const r = await runBlink(b.meta.links.actions[0].href, wallet);
r.report.verdict; // "CLEAR"
WarningThe Actions spec requires CORS, but some actions only answer known hosts. If a Blink fails to load here, that is the reason, and it still deserves the same suspicion.
Tools / Delegate revoker
Delegate revoker
Every Approve you ever signed leaves a delegate on a token account until you revoke it. Quiet drains months later come from these.
const list = await delegations(wallet); // SPL Token + Token-2022// [{ account, mint, delegate, delegated, amount, unlimited, program }]const t = await buildRevoke(wallet, list); // unsigned, fresh blockhash
t.base64; // sign it in any wallet tool that accepts a raw transaction
The transaction has one Revoke (tag 5) per account and nothing else. Paste it back into the firewall and it reads CLEAR. The blockhash expires in about a minute, so build it right before you sign.
NotePublic RPCs refuse getTokenAccountsByOwner to browsers, so this one call goes through /api/rpc on this site: a read-only proxy that forwards that single method, in jsonParsed form, and returns only the token accounts that still have a delegate.
Guard / Chrome guard
Chrome guard
The guard stands between a dApp and your wallet. It sees the transaction at the moment a site asks for a signature, before the wallet popup.
Open chrome://extensions and switch on Developer mode.
Press Load unpacked and pick the pavise-guard folder.
What it wraps
signTransaction, signAllTransactions, signAndSendTransaction and signAndSendAllTransactions of window.phantom.solana, window.solflare, window.backpack and window.solana. The guard serializes the message, runs the same inspect() and, if the score is above 0, shows a card. Block rejects with code 4001, the same error a wallet gives when you press Cancel. Continue hands the untouched transaction to the wallet.
File
What it is
manifest.json
MV3, one content script in the page's main world, no permissions
pavise.js
The engine, the same code as lib/pavise.js without network calls
guard.js
Wraps providers and draws the card in a closed shadow root
WarningWallet Standard adapters that never touch the injected providers are not covered in v0.1.0, and neither is signMessage. The guard reads transactions offline: no RPC, no simulation, no lookup tables.
Reference / API reference
API reference
Everything lib/pavise.js exports. Offline functions work anywhere, network ones call public endpoints.
Authority handoffs: token account owner, mint and freeze authority, program upgrade authority, stake authority, nonce authority.
Wallet reassignment through Assign.
Unlimited and outside delegates.
Sweeps: several token transfers to one outside account, rent sent elsewhere on close.
Durable nonces that let a signature wait for weeks.
Bait memos that talk about airdrops while funds leave.
Does not catch
What an unknown program does with your accounts. Pavise names it as unread and the simulation shows SOL deltas, but token deltas inside a custom program are not decoded.
Phishing that never asks for a signature, such as a fake site asking for your seed phrase.
A signMessage request that is later replayed off-chain.
A compromised machine. If malware controls the page, it controls Pavise too.
WarningA CLEAR verdict means none of the rules fired. It does not mean the dApp is honest. A clean transfer to the wrong address is still clean.
Token / $PAVISE
$PAVISE
The tool is free and stays free. The token is how the people who use it say so, and how the next feature gets voted onto the list.
The contract above comes from the same settings line as the main page. If someone sends you another address, Pavise would flag that before anything else.
NoteAudit our mint the way you audit any other: paste it into Mint audit and read every line.
Token / FAQ
FAQ
Does Pavise see my transactions?
No server of ours does. Parsing and scoring happen in your browser. Simulation and lookups go straight from your browser to a public RPC. The one proxied call, getTokenAccountsByOwner, carries a wallet address and nothing else.
Can it sign or send for me?
No. It has no keys and asks for none. It builds unsigned transactions, like the revoke, and you sign them in your wallet.
Why did a normal swap get a few points?
Swaps go through programs Pavise does not decode in full and load accounts from lookup tables. That adds 4 points per unknown program, three at most, and 4 for lookup tables. It stays CLEAR.
Why is USDC CAUTION in the mint audit?
Because the issuer can mint and freeze. That is by design for a regulated stablecoin, and still worth knowing.
Is the score an AI guess?
No. It is 21 fixed rules with fixed weights, listed in Risk rules. Same bytes, same score, every time.
Can I run my own copy?
Yes. Save lib/pavise.js, call setRpc() with your RPC and you have the whole engine. MIT.