Transaction preview
Explain a raw EVM transaction, an EIP-712 signature request or a Solana transaction in plain language before anyone signs it, with balance changes, approvals and drainer red flags.
Wallet drainers work because a confirmation screen shows hex. POST /v1/preview turns what a dapp asked a wallet to sign into plain language: what leaves the wallet, what comes back, which permissions it grants, who gets what, and red flags. It never asks for a signature and never sends anything. The same tool runs at layerhop.com/preview and app.layerhop.com/preview.
curl -X POST https://api.layerhop.com/v1/preview \
-H "content-type: application/json" \
-d '{
"kind": "evm_tx",
"chain": 8453,
"tx": {
"from": "0x1111111111111111111111111111111111111111",
"to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"data": "0x095ea7b30000000000000000000000002222222222222222222222222222222222222222ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
}
}'{
"preview": {
"kind": "evm_tx",
"chainId": 8453,
"summary": "Danger: lets 0x2222…2222 spend unlimited USDC from your wallet",
"risk": "danger",
"approvals": [{ "token": "0x8335…2913", "symbol": "USDC", "spender": "0x2222…2222", "unlimited": true, "via": "approve", "spenderIsContract": false }],
"flags": [{ "code": "approval_to_eoa", "level": "danger", "message": "0x2222…2222 is a wallet, not an app's contract. Giving a wallet permission to move your USDC is how most drainers work." }],
"simulation": { "method": "eth_simulateV1", "ok": true, "gasUsed": "55906" }
}
}What you can send#
kind |
Fields | How it's explained |
|---|---|---|
evm_tx |
chain, tx.from, tx.to, tx.data, tx.value (base units) |
Simulated against the latest block, and the calldata decoded |
typed_data |
typedData (object or JSON string), optional from, chain |
Decoded (a signature can't be simulated) |
solana_tx |
tx (base64, legacy or v0), optional from |
simulateTransaction with the accounts before and after |
How EVM transactions are simulated#
eth_simulateV1withtraceTransfers: every ERC-20, ERC-721 and ERC-1155 transfer, plus native-coin transfers as synthetic logs. Most public RPCs serve it (checked on Ethereum, Base and Arbitrum, 2026-10-28).- Else
debug_traceCallwith the call tracer's logs (native transfers from the call frames; reverted frames are skipped). - Else a plain
eth_call: it tells whether the transaction would revert, and the balance changes come from decoding the calldata.
simulation.method says which one ran; simulation.ok: false with error means the transaction would fail right now (it also adds a simulation_failed flag). A revert is told apart from a node that can't simulate even when the node reports it as -32000 / -32603. method: "none" (no RPC could run it) adds a simulation_failed caution too: what the transaction moves is unknown.
The calldata is decoded either way: approve, increaseAllowance, setApprovalForAll, transfer, transferFrom, safeTransferFrom (ERC-721 and ERC-1155), safeBatchTransferFrom, EIP-2612 permit, Permit2 approve / permit / transferFrom, and the calls inside multicall and Multicall aggregate.
Signature requests#
A signature can move tokens later without another confirmation, which is why drainers ask for one. Decoded types:
- EIP-2612
Permitand DAI-stylePermit(allowed: trueis unlimited and, withexpiry: 0, never expires). - Permit2:
PermitSingle,PermitBatch,PermitTransferFrom,PermitBatchTransferFromand their witness variants. - Seaport
OrderComponentsandBulkOrder(a tree of orders under one signature, each order checked): what the signer offers and who receives what.gives_away_assetsflags an order that pays the signer nothing back, only dust (under 1/10,000 of a token), sends most of a payment token to someone else, or returns less of a token than it takes (the fake "listing" NFT drain). Prices aren't known, so an order paid in a worthless token isn't caught.
Anything else is reported as unknown_function: only sign it on a site you trust. An unreadable order-like message (Blur, 1inch, 0x and other …Order types) says so: treat signing it as handing over everything it offers.
Red flags#
| Code | Level | When |
|---|---|---|
approval_to_eoa, permit_to_eoa |
danger | The spender is a wallet, not a contract |
approval_to_unknown, permit_to_unknown |
danger if unlimited, else caution | A contract LayerHop can't name |
approve_all_nfts |
danger (unknown) / caution (named) | setApprovalForAll(true) |
unlimited_approval |
caution | Unlimited, to a named spender |
long_permit |
caution | The permission lasts more than a year or never expires |
value_to_eoa |
caution, danger with calldata | Native coin sent straight to a wallet |
unverified_contract, new_contract |
caution (new_contract spender: danger) |
Source not published / deployed less than 7 days ago (needs ETHERSCAN_API_KEY on the deployment) |
gives_away_assets |
danger | Assets leave the wallet, nothing comes back, and they go to an address the call doesn't name and LayerHop doesn't recognize (a fake claim() spending an old allowance, value paid into an unknown contract; a plain transfer you send yourself isn't flagged); a Seaport order as above; on Solana, a signer other than the fee payer losing assets, a token account handed to someone else, or a signer's account assigned to another program |
community_reports |
caution, danger from 3 approved reports | An address involved has community reports a LayerHop admin approved, on the preview's chain. Unreviewed reports are listed in community without a flag; Permit2, named spenders and listed tokens never get one |
simulation_failed, unknown_function, wrong_chain |
caution | The transaction would revert (or couldn't be simulated); we can't read the call (even when the simulation shows what it moved); the signature names another chain |
Named spenders are the approval targets LayerHop's own routes use (Permit2, LI.FI, 0x, Relay, deBridge, Mayan, KyberSwap …) and the major DEX routers: the same list as the approvals page. risk is the worst flag's level and summary starts with "Danger:" when it's danger.
Solana#
simulateTransaction runs with sigVerify: false and a replaced blockhash, so an unsigned transaction works. Address lookup tables are resolved read-only (up to 100 accounts). For from (default: the fee payer) the preview reports SOL changes (network fee included) and token balance changes per mint in balanceChanges. For every signer it reports new token delegates (unlimited_approval when the delegate can move everything), token accounts whose owner would change and signer accounts assigned to another program (gives_away_assets). Other signers' changes are listed in actions; without from, a signer other than the fee payer that loses assets and gets nothing back is gives_away_assets (drainers pay the fee themselves and make the victim a second signer). Send from with a multi-signer transaction to see your own wallet's changes.
Limits#
20 previews per minute per API key or IP (RATE_LIMITED), and a shared budget for all clients; a preview answers within 25 seconds or fails with PROVIDER_ERROR. Identical requests within 30 seconds are served from cache (x-cache: hit). A preview is a snapshot of the latest block: the result can change by the time the user signs. Flags catch common drainer patterns, not every scam.
In the app#
- app.layerhop.com/health scores any wallet 0–100 from open approvals, risky tokens held, address-poisoning traps, stuck withdrawals and dust, each with a fix-it link; the share card shows only the score.
- Community reports show up in the recipient check, the token risk badge and the preview page. See Community reports.
Last updated