// TINL · WALLET_SECURITY · BLIND_SIGNING
Every wallet presents raw, machine-encoded transaction data for a human to sign — expecting authorization of a machine-readable state transition nobody can actually read. TINL — the Transaction Intent Normalization Layer — decodes that intent, checks it against a policy, and can block the worst patterns before the wallet signs — when it successfully sits in front of the wallet. It is a real safety layer, not a magic shield. Read the plain-language limits below before you install anything from a store.
Where TINL runs: a Chrome browser extension for desktop wallets (MetaMask / Phantom). An Edge port is planned — see Platform Support below.
Arrived without context? How this sits in a game or platform is the layer above this page: what the Transaction Intent Normalization Layer is, where it can be placed behind an action you already own, and which half of it ships today.
The evaluator connects a real MetaMask + Solana wallet on public testnets — no real funds involved. Requires evaluator access.
// READ_THIS_BEFORE_YOU_INSTALL
If you only remember one thing: always read your wallet's own confirmation screen (MetaMask / Phantom). TINL is an extra guard in front of that screen — not a replacement for it, and not a guarantee that every bad site is stopped.
Full technical write-up for reviewers: in the TINL repo, doc/TINL_THREAT_MODEL_AND_SAFEGUARDS.md and doc/CHROME_WEB_STORE_RELEASE.md. Evaluators: see the decision table in the TINL evaluator brief (WARN/NORMALIZE marked advisory).
// ROADMAP · CLOSING_THE_GAPS
These are real product commitments tracked in the TINL engineering backlog — not vaporware marketing. Until an item is marked shipped, assume the limits above still apply.
| PriorityPriority Where the item sits in TINL's engineering backlog: P0 first, then P1, then P2. A priority says what is worked on first, not that it is finished; the Status column says that. | What users feelWhat users feel The difference a person holding a wallet would notice once the item is done, in plain words. | Engineering workEngineering work The change in the extension or engine that delivers it, including what is still open. | StatusStatus Partial: some of the work ships and the rest does not; the cell names which. Planned and Roadmap: not built. Until an item is marked shipped, assume the limits above on this page still apply. |
|---|---|---|---|
| P0P0 The top tier of TINL's backlog: gaps that decide whether what TINL shows a person can be trusted against a hostile page. | Warnings and "fix my approval amount" cannot be faked by an evil website | Consent moved out of the page's JavaScript world (MessagePort between the ISOLATED and MAIN worlds, extension-owned popup). Still open: binding proceed to a digest of the exact bytes TINL approved | Partial — the page-forged proceed path was closed 2026-08-08; digest-bind remains |
| P0P0 The top tier of TINL's backlog: gaps that decide whether what TINL shows a person can be trusted against a hostile page. | Fewer cases where a site talks to MetaMask/Phantom without TINL noticing | Stronger provider wrap / re-wrap; diagnostics when wrap failed; document residual browser limits | Partial today — residual races documented |
| P1P1 The next tier: hardening the installed extension and TINL's defaults for sites it has not seen before. | Store install cannot be quietly swapped for a tampered engine on disk | Load-time check of the on-device policy engine (WASM hash pin); store-only retail distribution | Partial — the load-time SHA-256 pin ships (Wave 2). Store-only retail distribution does not |
| P1P1 The next tier: hardening the installed extension and TINL's defaults for sites it has not seen before. | First visit to a brand-new site is treated more carefully | Stricter defaults for unknown origins; optional "hostile site mode" (WARN → BLOCK) | Planned |
| P2P2 Later work, such as reaching another browser. Not built. | Edge browser support | Edge extension port | Roadmap |
// PLATFORM_SUPPORT
TINL is not "a website feature only." v0.1 is the Chrome browser extension: real BLOCK when the wrapper is on the path; WARN/NORMALIZE advisory as a whole against a hostile page (see limits above). An Edge port of the same extension is planned.
The primary browser form factor. Intercepts before either wallet's own extension ever sees the signing request. BLOCK is enforced; WARN and NORMALIZE remain advisory as a whole against a hostile page — the page-forged proceed path was closed in August 2026, but digest-bind and MAIN-world wrap races remain, so wallet confirmation is the backstop for those two. Scope is complete for independent evaluator UAT — not a claim of public Chrome Web Store GA.
The same browser extension, ported to Edge. Planned, not built.
A MetaMask Snap also exists in the TINL codebase, but Snap sandboxing makes it insight/warning-only by design — it can show a warning inside MetaMask's own confirmation screen but cannot block a transaction or rewrite a payload. The browser extension is the only surface that can enforce BLOCK today; its WARN and NORMALIZE decisions remain advisory as a whole against a hostile page — the consent channel left the page's JavaScript world in August 2026, but digest-bind and MAIN-world wrap races are still open — so the wallet's own confirmation screen is the real backstop for those two decisions.
// THE_PROBLEM · BLIND_SIGNING
Token drainer kits, unlimited approvals, and malicious permit signatures aren't exotic exploits — they're the direct, predictable consequence of asking a human to authorize hex they cannot read. Four patterns account for most of it.
The 98.6% figure is specific to Pump.fun, Solana's dominant meme-token launchpad — not every token ever created on Solana. Precision matters more to us than the punchier rounded-off numbers that circulate.
A DApp requests approve(spender, uint256_max) — technically a single line of calldata, functionally a blank check. The wallet shows a hex string. Most users sign it. The spender can now drain that token from the wallet at any point in the future, with no further signature required.
permit() moves approval off-chain into a typed-data signature — no gas, no on-chain trace until it's used. A phishing site can request a permit signature that looks like routine DApp interaction and looks nothing like a transaction, because it isn't one yet.
One signature grants an operator control over an entire NFT collection in a wallet, not just one token. Drainer kits default to this call because it's the highest-value single signature available.
Every wallet on the market presents the same raw, machine-encoded transaction data for human sign-off, regardless of what it actually authorizes. The wallet isn't broken — it's doing exactly what it was built to do. That's the gap TINL closes.
// IN_PLAIN_ENGLISH
Everything below is a real rule from the product engine — not a simplified summary. Decisions shown are the retail-strict posture's real defaults — what a first-time holder gets automatically.
| What's happeningWhat's happening The request a site puts in front of your wallet, described by what it does rather than by its technical name. | In plain EnglishIn plain English Why that request matters to the person signing it. | TINL's default moveTINL's default move ALLOW: Nothing to decide. The request goes to your wallet as usual. WARN: A TINL window explains it first. Cancel stops it; Proceed hands it to your wallet's own confirmation, which is still yours to refuse. NORMALIZE: A TINL window offers a smaller permission instead: nothing, an amount you type, or everything the site asked for. Cancel stops it. BLOCK: The request never reaches your wallet, and TINL's window offers no way to continue. TINL's default profile, retail_strict, warns rather than blocks: it stops nothing on its own unless the person turns on "stop instead of warning". Only the institutional profile blocks on an administrator's authority. |
|---|---|---|
Unlimited spending permissionUnlimited spending permission The checks behind it: ERC-20 unlimited approve() (ERC20_UNLIMITED_APPROVE); SPL Token unlimited Approve (delegate) (SPL_UNLIMITED_APPROVE). This grants another address standing permission to move tokens out of your wallet whenever it chooses — not a one-time transfer, an open-ended authorization with no cap and no expiry. ERC20_UNLIMITED_APPROVE ships as: retail_strict NORMALIZE, trader_balanced NORMALIZE, institutional_strict BLOCK. SPL_UNLIMITED_APPROVE ships as: retail_strict NORMALIZE, trader_balanced NORMALIZE, institutional_strict BLOCK. | A site asks for permission to spend an unlimited amount of one of your tokens — forever, without asking again. Blocking this outright would break the legitimate swap it's usually part of, so TINL proposes a capped, exact-amount version and asks you to confirm — you can still cancel entirely. Always check the wallet confirmation before signing. | NORMALIZE |
Handing over your whole NFT collectionHanding over your whole NFT collection The check behind it: ERC-721 setApprovalForAll() (ERC721_SET_APPROVAL_ALL). This grants another address permission to transfer ANY token from this entire collection out of your wallet, not just one — one signature covers everything you hold or will ever hold from this contract. ERC721_SET_APPROVAL_ALL ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. | One click grants control over every NFT you own, not just the one you're looking at. | WARN |
A "free" signature that isn't freeA "free" signature that isn't free The check behind it: EIP-2612 unlimited permit (EIP2612_PERMIT_UNLIMITED). EIP-2612 "permit" grants the exact same standing spending permission as an on-chain unlimited approval, but through a signed message instead of a transaction — no gas fee, which means it shows up constantly in ordinary DeFi flows and gets signed with far less scrutiny than a real transaction. EIP2612_PERMIT_UNLIMITED ships as: retail_strict NORMALIZE, trader_balanced WARN, institutional_strict BLOCK. | You're asked to sign a message — no gas fee, easy to mistake for something harmless — that secretly grants unlimited spending power. | NORMALIZE |
A known scam patternA known scam pattern The checks behind it: Selector flagged by A3E9 Threat Intelligence (THREAT_INTEL_FLAGGED_SELECTOR); Spender or operator listed as a known drainer (COUNTERPARTY_KNOWN_DRAINER). This calls a function specifically identified and listed in TINL's Threat Intelligence Tier — a known drainer selector or malicious permit variant someone has already identified in the wild, not merely an unfamiliar one. THREAT_INTEL_FLAGGED_SELECTOR ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. COUNTERPARTY_KNOWN_DRAINER ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. | The action matches something already caught being used by real attackers, not a guess. | WARN |
Something TINL has never seen beforeSomething TINL has never seen before The check behind it: Unrecognized function selector (SELECTOR_UNKNOWN). This calls a contract function TINL doesn't recognize — not a standard ERC-20/721/1155 method, and not present in the open selector registry or the Threat Intel Tier. It might be a legitimate, less common contract TINL simply doesn't know about yet, or it might be a custom function chosen specifically because it doesn't look familiar to anyone reviewing it. SELECTOR_UNKNOWN ships as: retail_strict WARN, trader_balanced ALLOW, institutional_strict BLOCK. | The action doesn't match any pattern TINL recognizes — safe or dangerous. | WARN |
A signature request with a strange shapeA signature request with a strange shape The check behind it: Unrecognized EIP-712 typed data (UNRECOGNIZED_TYPED_DATA). This is a structured (EIP-712) signature request that doesn't match the EIP-2612 permit schema TINL knows how to interpret. It could authorize almost anything — TINL can't say more about what it actually does. UNRECOGNIZED_TYPED_DATA ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. | You're asked to sign something whose structure doesn't match any known safe pattern. | WARN |
TINL can't verify who's involvedTINL can't verify who's involved The check behind it: SPL account not resolvable (address lookup table) (SPL_UNRESOLVABLE_ACCOUNT). This transaction references an account (via a Solana address-lookup table) that TINL wasn't able to resolve to a real address — rather than guess or skip it, TINL treats this as unreviewable and flags it, since evaluating the wrong account would be worse than not evaluating at all. SPL_UNRESOLVABLE_ACCOUNT ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. | One of the accounts in a Solana transaction can't be checked. | WARN |
Spending past your own limitSpending past your own limit The check behind it: Transaction value over the profile's spending cap (SPENDING_CAP_EXCEEDED). This is a bounded, finite approval or transfer — but its real-world USD value (resolved via a live price oracle) is higher than the spending cap your active profile allows for a single request. SPENDING_CAP_EXCEEDED ships as: retail_strict WARN, trader_balanced WARN, institutional_strict BLOCK. | The dollar value of what's being approved is higher than the cap you set for yourself. | WARN |
A specific, limited approvalA specific, limited approval The checks behind it: ERC-20 bounded approve() (ERC20_BOUNDED_APPROVE); SPL Token bounded Approve (delegate) (SPL_BOUNDED_APPROVE). This grants another address permission to move up to a specific, finite amount of tokens out of your wallet — the normal, legitimate shape of an approval, e.g. authorizing an exchange to swap a specific amount. ERC20_BOUNDED_APPROVE ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. SPL_BOUNDED_APPROVE ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. | A site asks to spend a specific, capped amount — e.g. approving $50 for one swap. | ALLOW |
Sending tokens to someoneSending tokens to someone The checks behind it: ERC-20 transfer() (ERC20_TRANSFER); SPL Token transfer (SPL_TRANSFER). This moves tokens out of your wallet right now, for a specific amount — not a standing permission for later. You always know exactly what's leaving the moment you sign. ERC20_TRANSFER ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. SPL_TRANSFER ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. | You're sending a specific amount directly — the way a normal payment works. | ALLOW |
Taking back a permissionTaking back a permission The checks behind it: ERC-721 revoke setApprovalForAll() (ERC721_REVOKE_APPROVAL_ALL); SPL Token Revoke (SPL_REVOKE_APPROVAL). This removes a previously granted collection-wide transfer permission — the safe direction. Revoking access someone else already had is generally something to encourage, not flag. ERC721_REVOKE_APPROVAL_ALL ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. SPL_REVOKE_APPROVAL ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. | You're revoking access you previously granted. | ALLOW |
A well-known, everyday actionA well-known, everyday action The check behind it: Recognized standard function selector (SELECTOR_RECOGNIZED_STANDARD). This calls a function TINL's registry recognizes by name (e.g. a common DeFi or token-standard operation), but doesn't have a dedicated risk-pattern decoder for — distinct from a fully unknown selector, since at least the operation itself is a known, named one. SELECTOR_RECOGNIZED_STANDARD ships as: retail_strict ALLOW, trader_balanced ALLOW, institutional_strict ALLOW. | The action matches a standard, widely-used operation TINL already recognizes as safe. | ALLOW |
This reflects the product's real rule set — broader than the simplified evaluator below — and each decision is read from the shipped retail_strict profile, not typed here. Retail Strict warns rather than blocks: it explains the risk and you decide, and it stops a request only if you turn on stop-instead-of-warning. Every profile is user-adjustable — a trader or developer sees a lighter touch; this is the out-of-the-box default for a first-time holder.
// TINL_ARCHITECTURE
The same separation-of-concerns principle A3E9 applies at the HSM boundary applies here: the interceptor, the normalization engine, and the audit logger are loosely coupled — no single module has complete end-to-end control.
| LayerLayer Where in the path from the web page to the signature the component runs. | ComponentComponent The part of TINL that does the job. The three are loosely coupled, so no single one has end-to-end control. | What it doesWhat it does Its job, and what it hands to the next layer. |
|---|---|---|
| Client-SideClient-side Runs in the browser, in front of the wallet, and sees a request before the wallet does. It can only act on requests that pass through it. Residual cases where a site reaches the wallet without TINL noticing are documented; see the limits and roadmap on this page. | Provider Interceptor | A lightweight proxy that wraps the wallet's JSON-RPC surface (eth_sendTransaction, eth_signTypedData_v4, and the Solana equivalents). It halts execution before the signing enclave is invoked and sends the raw request to the Normalization Engine. |
| Execution LayerExecution layer Where the decision is made: the request is decoded, matched against the registry and evaluated against the active profile. It runs embedded as WASM (Mode A) or as a REST service (Mode B). | Normalization Engine | Identifies exactly what a transaction or signature request will do, checks it against a continuously updated registry of known operations, and evaluates it against the active policy profile — before any signature exists. Runs in one of two deployment modes (below): compiled to WASM and embedded in the wallet, or called as a REST service. |
| Cryptographic LayerCryptographic layer Where each decision is recorded. Chaining every record to the previous one by HMAC makes an edited or deleted entry detectable from that point forward. The chain shows the record was not changed afterwards. It does not show that the decision recorded was the right one. | HMAC-Chained Audit Logger | Every policy decision — ALLOW, WARN, BLOCK, or NORMALIZE — is written to an append-only log before the API returns. Each record embeds the previous record's HMAC, so deleting or editing any entry breaks the chain from that point forward. If the write fails, the request fails closed. |
// DUAL-MODE_DEPLOYMENT
A centralized REST API alone adds latency a high-frequency trader can't absorb and a single point of failure a self-sovereign holder shouldn't have to trust. The same core engine compiles to two co-equal deployment targets — neither is a fallback for the other, they target different user classes.
Normalization Engine compiled to WASM, bundled in the wallet/extension
Target: Retail investors, active DeFi traders, developer bots
Provider Interceptor calls an authenticated REST API (A3E9-hosted or self-hosted)
Target: Enterprise custody platforms, institutional wallets, regulated entities
In Mode B, a remote service can become unreachable. What happens next is bound to the active profile, not a system-wide default — the user's own risk tolerance decides.
| ProfileProfile The policy profile in force. What happens in an outage is set per profile, not system-wide. | service_outage_behaviorservice_outage_behavior What a Mode B client does when the REST service cannot be reached. FAIL_TO_LAST_KNOWN_GOOD: fall back to the same engine embedded in the extension, using the last cached profile. FAIL_CLOSED: refuse to sign until the service recovers. Only applies in Mode B. In Mode A the engine is already on the device and there is no service to lose. | RationaleRationale Why that profile trades availability and protection the way it does. |
|---|---|---|
| retail_strictretail_strict For a first-time or non-expert holder. Dangerous intents are WARNed with the reason, or NORMALIZEd down to a capped amount the user must confirm — never passed through silently. It stops nothing on the user's behalf unless the user turns on stop-instead-of-warning. | FAIL_TO_LAST_KNOWN_GOOD | Protects without locking the user out |
| trader_balancedtrader_balanced For an active trader or arbitrage operator who understands the risk but still wants a visible warning before signing. Like retail_strict, it stops nothing unless the user asks it to. | FAIL_TO_LAST_KNOWN_GOOD | Maintains some protection; a trader cannot afford a frozen wallet |
| institutional_strictinstitutional_strict For a firm, where an administrator sets the policy for the people who sign. It blocks what it does not recognise as safe, on the administrator's authority. Not offered in this site's demo, and distinct from the three base profiles in the matrix below. | FAIL_CLOSED | Regulator-grade: security over availability |
institutional_strict is a Mode-B deployment profile for regulator-grade fail-closed enforcement — documented here for its outage behavior specifically, distinct from the three base profiles below.
// MULTI-CHAIN_DECODING_MODELS
The Normalization Engine abstracts these differences from the Policy Engine — every decoded intent lands in the same shape before evaluation — but the analysis itself is chain-native, not a lowest-common-denominator guess. Both models read the transaction directly, immune to the kind of spoofing that defeats wallet security tools relying on network-based transaction previews.
A transaction carries its full instruction in a single encoded field. TINL identifies exactly which operation it represents and decodes its parameters before evaluating it. Off-chain signature requests — like token-spending permits — are recognized directly from their structure, without needing a network lookup.
A transaction carries a list of instructions, each targeting a different on-chain program. TINL evaluates every instruction in the transaction. Standard token operations are recognized directly, with no network lookup required; less common program interactions are checked against a registry.
Registry-based recognition for less common program interactions is on the roadmap — standard token operations and native transfers are fully enforced today. Anything not yet recognized is treated as unverified rather than silently allowed.
// HYBRID_REGISTRY
New drainer contracts and novel permit variants appear constantly — a registry frozen at release time decays fast. But a purely proprietary registry means standard operations stop working the moment a subscription lapses. TINL splits the difference.
Open-source and community-maintained, covering standard operations across every supported chain. Always available offline, without a subscription — no vendor lock-in.
A continuously updated feed aggregating multiple security research sources: newly identified drainer patterns, zero-day permit variants, and flagged malicious contract addresses, pushed within a 24-hour SLA of identification in the wild.
When TINL encounters an operation in neither registry tier, that's a first-class rule in the same profile matrix as everything else, not a hardcoded default. A retail investor gets a visible warning on an unrecognized action; a developer bot's traffic passes through untouched — either way, the decision is logged to the audit chain.
// TIERED_POLICY_PROFILES
A retail investor needs maximum protection against phishing. A DApp developer or arbitrage operator needs zero friction. Both are the same wallet, running the same engine, pointed at a different profile — a per-rule matrix, not a single global switch. This is the exact matrix the backend enforces.
| RuleRule A check TINL's engine runs on every wallet request. Each row is one rule, and each profile column is what that profile does when the rule fires. | Retail — StrictRetail — Strict For a first-time or non-expert holder. Dangerous intents are WARNed with the reason, or NORMALIZEd down to a capped amount the user must confirm — never passed through silently. It stops nothing on the user's behalf unless the user turns on stop-instead-of-warning. ALLOW: Nothing to decide. The request goes to your wallet as usual. WARN: A TINL window explains it first. Cancel stops it; Proceed hands it to your wallet's own confirmation, which is still yours to refuse. NORMALIZE: A TINL window offers a smaller permission instead: nothing, an amount you type, or everything the site asked for. Cancel stops it. BLOCK: The request never reaches your wallet, and TINL's window offers no way to continue. | Active DeFi User — BalancedActive DeFi User — Balanced For an active trader or arbitrage operator who understands the risk but still wants a visible warning before signing. Like retail_strict, it stops nothing unless the user asks it to. ALLOW: Nothing to decide. The request goes to your wallet as usual. WARN: A TINL window explains it first. Cancel stops it; Proceed hands it to your wallet's own confirmation, which is still yours to refuse. NORMALIZE: A TINL window offers a smaller permission instead: nothing, an amount you type, or everything the site asked for. Cancel stops it. BLOCK: The request never reaches your wallet, and TINL's window offers no way to continue. | DApp Operator / Bot — PassthroughDApp Operator / Bot — Passthrough Zero friction for a DApp developer or CI environment. Every intent is allowed; every decision is still written to the audit chain. ALLOW: Nothing to decide. The request goes to your wallet as usual. WARN: A TINL window explains it first. Cancel stops it; Proceed hands it to your wallet's own confirmation, which is still yours to refuse. NORMALIZE: A TINL window offers a smaller permission instead: nothing, an amount you type, or everything the site asked for. Cancel stops it. BLOCK: The request never reaches your wallet, and TINL's window offers no way to continue. |
|---|---|---|---|
| ERC-20 unlimited approve()ERC-20 unlimited approve() This grants another address standing permission to move tokens out of your wallet whenever it chooses — not a one-time transfer, an open-ended authorization with no cap and no expiry. ERC20_UNLIMITED_APPROVE | NORMALIZE | NORMALIZE | ALLOW |
| ERC-20 bounded approve()ERC-20 bounded approve() This grants another address permission to move up to a specific, finite amount of tokens out of your wallet — the normal, legitimate shape of an approval, e.g. authorizing an exchange to swap a specific amount. ERC20_BOUNDED_APPROVE | ALLOW | ALLOW | ALLOW |
| ERC-20 transfer()ERC-20 transfer() This moves tokens out of your wallet right now, for a specific amount — not a standing permission for later. You always know exactly what's leaving the moment you sign. ERC20_TRANSFER | ALLOW | ALLOW | ALLOW |
| ERC-20 transferFrom() (self)ERC-20 transferFrom() (self) This moves tokens right now, pulled from an account that matches your own wallet address — functionally the same as an ordinary transfer, just using a different function to do it. ERC20_TRANSFER_FROM | ALLOW | ALLOW | ALLOW |
| ERC-20 transferFrom() pulling a third party's fundsERC-20 transferFrom() pulling a third party's funds This moves tokens out of an account that is NOT your own wallet — you would be the one signing it, but the funds would come from somebody else's balance, using an allowance they previously granted. ERC20_TRANSFER_FROM_THIRD_PARTY | WARN | WARN | ALLOW |
| ERC-721 setApprovalForAll()ERC-721 setApprovalForAll() This grants another address permission to transfer ANY token from this entire collection out of your wallet, not just one — one signature covers everything you hold or will ever hold from this contract. ERC721_SET_APPROVAL_ALL | WARN | WARN | ALLOW |
| ERC-721 revoke setApprovalForAll()ERC-721 revoke setApprovalForAll() This removes a previously granted collection-wide transfer permission — the safe direction. Revoking access someone else already had is generally something to encourage, not flag. ERC721_REVOKE_APPROVAL_ALL | ALLOW | ALLOW | ALLOW |
| EIP-2612 unlimited permitEIP-2612 unlimited permit EIP-2612 "permit" grants the exact same standing spending permission as an on-chain unlimited approval, but through a signed message instead of a transaction — no gas fee, which means it shows up constantly in ordinary DeFi flows and gets signed with far less scrutiny than a real transaction. EIP2612_PERMIT_UNLIMITED | NORMALIZE | WARN | ALLOW |
| EIP-2612 bounded permitEIP-2612 bounded permit Same mechanism as an unlimited permit above, but for a specific, finite amount — the normal, legitimate use of a gasless approval. EIP2612_PERMIT_BOUNDED | ALLOW | ALLOW | ALLOW |
| EIP-2612 permit with a far-future deadlineEIP-2612 permit with a far-future deadline This gasless approval's deadline is set so far in the future that it functions as permanent — most legitimate DeFi permits expire within minutes to hours. An approval that stays valid indefinitely can be used at any point later, long after you've forgotten you signed it, including if your wallet is ever compromised in the future. EIP2612_PERMIT_DEADLINE_FAR_FUTURE | WARN | WARN | ALLOW |
| Unrecognized function selectorUnrecognized function selector This calls a contract function TINL doesn't recognize — not a standard ERC-20/721/1155 method, and not present in the open selector registry or the Threat Intel Tier. It might be a legitimate, less common contract TINL simply doesn't know about yet, or it might be a custom function chosen specifically because it doesn't look familiar to anyone reviewing it. SELECTOR_UNKNOWN | WARN | ALLOW | ALLOW |
| Unrecognized function selector that also sends native valueUnrecognized function selector that also sends native value This calls a contract function TINL doesn't recognize, and it also sends native currency (ETH or the chain's equivalent) along with the call. Whatever that function does with the money, you can't get it back by cancelling afterwards. SELECTOR_UNKNOWN_WITH_VALUE | WARN | WARN | ALLOW |
| Native ETH transfer (no calldata)Native ETH transfer (no calldata) This sends the network's native coin (ETH on Ethereum, BNB on BNB Chain, and so on) directly to an address, with no contract call attached. EVM_NATIVE_TRANSFER | ALLOW | ALLOW | ALLOW |
| Readable message signature (EVM)Readable message signature (EVM) This signs a plain, human-readable message — not a transaction, and not something that moves tokens or grants a permission on its own. Common for logging into a website or proving you control this wallet. EVM_MESSAGE_SIGNED | ALLOW | ALLOW | ALLOW |
| EVM message is not valid UTF-8EVM message is not valid UTF-8 You're being asked to sign data that isn't readable text, so neither you nor TINL can see what it says. Some apps do use raw-byte login challenges, but you can't confirm what you're agreeing to the way you normally could. EVM_MESSAGE_NON_UTF8 | WARN | ALLOW | ALLOW |
| Signature over a hash the user cannot read (eth_sign / 32-byte personal_sign)Signature over a hash the user cannot read (eth_sign / 32-byte personal_sign) This asks you to sign a 32-byte fingerprint of something else — not words, not a transaction you can review. Some contracts accept a signature like this as your consent to an order, a listing or a transfer, so what you are really approving is whatever that fingerprint stands for. EVM_MESSAGE_BLIND_HASH | WARN | WARN | ALLOW |
| Sign-in message names a different site than the one askingSign-in message names a different site than the one asking You're being asked to sign a "Sign in with..." message for one website, but the page asking is a different website. A login signature is proof you control this wallet; a lookalike page collecting one for the real site can use it there as you. SIGN_IN_DOMAIN_MISMATCH | WARN | WARN | ALLOW |
| Request would move most of one asset the wallet holdsRequest would move most of one asset the wallet holds This request would move almost your entire balance of an asset out of your wallet in one go. That is sometimes exactly what you mean — moving everything to an exchange or a new wallet — but it is also exactly what a drain looks like. BALANCE_SWEEP | WARN | WARN | ALLOW |
| Request would empty several different assets at onceRequest would empty several different assets at once This single request would move nearly all of two or more different assets out of your wallet. Moving everything to one new place is rare in normal use; emptying several balances in one signature is how wallet drains are built. BALANCE_SWEEP_MULTI_ASSET | WARN | WARN | ALLOW |
| Signature requested seconds after connecting to a brand-new siteSignature requested seconds after connecting to a brand-new site You connected your wallet to this site seconds ago, and it is already asking you to sign a transaction or an approval. You have not used this site before. SESSION_CONNECT_THEN_SIGN | WARN | WARN | ALLOW |
| New site, first payment to a never-seen counterparty (recorded)New site, first payment to a never-seen counterparty (recorded) You have not used this site before, and this request sends value to an address you have never sent to anywhere. Neither is wrong on its own; together they are worth a second look at the address. SESSION_NEW_SITE_NEW_COUNTERPARTY | ALLOW | ALLOW | ALLOW |
| Follow-up request moves assets the earlier approved one did notFollow-up request moves assets the earlier approved one did not Earlier on this same site you approved something else. This request pays a different address, or moves assets you did not move before, than anything you let through here in the last half hour. SESSION_INTENT_DEVIATION | WARN | WARN | ALLOW |
| Follow-up request deviates and would also empty a balanceFollow-up request deviates and would also empty a balance After what you already approved on this site, this new request would move nearly all of an asset — to an address or in a way you did not use here before. SESSION_DEVIATION_WITH_SWEEP | WARN | WARN | ALLOW |
| Over the per-site spending limit for a new siteOver the per-site spending limit for a new site You have not used this site before, and together with what you have already approved here, this request would move more of one of your balances than your limit for a new site allows. SESSION_BUDGET_EXCEEDED | WARN | WARN | ALLOW |
| Look-alike of a known official domainLook-alike of a known official domain The website asking for this signature is not the site it looks like. Its address is built to pass for a well-known wallet or app — letters swapped for look-alikes, a one-letter typo, or the real address hidden inside a longer one. SITE_LOOKALIKE_DOMAIN | WARN | WARN | ALLOW |
| Brand name inside a domain that is not the brand'sBrand name inside a domain that is not the brand's The website's address contains the name of a well-known wallet or app, but it is not that project's official domain. It may be a fan site, an unrelated project — or an imitation. SITE_BRAND_IN_UNKNOWN_DOMAIN | WARN | ALLOW | ALLOW |
| Unrecognized EIP-712 typed dataUnrecognized EIP-712 typed data This is a structured (EIP-712) signature request that doesn't match the EIP-2612 permit schema TINL knows how to interpret. It could authorize almost anything — TINL can't say more about what it actually does. UNRECOGNIZED_TYPED_DATA | WARN | WARN | ALLOW |
| SPL Token unlimited Approve (delegate)SPL Token unlimited Approve (delegate) This delegates standing permission to move tokens out of this token account, for an amount at or near the maximum a token account can ever hold — the Solana equivalent of an unlimited ERC-20 approval, and the same drainer technique: sign once, and the delegate can empty this token account at any point later. SPL_UNLIMITED_APPROVE | NORMALIZE | NORMALIZE | ALLOW |
| SPL Token bounded Approve (delegate)SPL Token bounded Approve (delegate) This delegates permission to move up to a specific, finite amount from this token account — the normal, legitimate shape of an SPL approval. SPL_BOUNDED_APPROVE | ALLOW | ALLOW | ALLOW |
| SPL Token transferSPL Token transfer This moves tokens out of this token account right now, for a specific amount — not a standing delegation for later. SPL_TRANSFER | ALLOW | ALLOW | ALLOW |
| SPL Token RevokeSPL Token Revoke This removes a previously granted delegate authority from this token account — the safe direction, generally something to encourage. SPL_REVOKE_APPROVAL | ALLOW | ALLOW | ALLOW |
| Unrecognized SPL Token instructionUnrecognized SPL Token instruction This transaction includes an SPL Token program instruction with a discriminant TINL doesn't recognize as approve, transfer, or revoke — TINL can't say what it actually does. SPL_UNKNOWN_INSTRUCTION | WARN | WARN | ALLOW |
| SPL account not resolvable (address lookup table)SPL account not resolvable (address lookup table) This transaction references an account (via a Solana address-lookup table) that TINL wasn't able to resolve to a real address — rather than guess or skip it, TINL treats this as unreviewable and flags it, since evaluating the wrong account would be worse than not evaluating at all. SPL_UNRESOLVABLE_ACCOUNT | WARN | WARN | ALLOW |
| SPL Token SetAuthority (authority handed to someone else)SPL Token SetAuthority (authority handed to someone else) This changes who controls one of your token accounts (or a token mint). When the change is to the account's owner, the new owner can move everything in that account afterwards, without asking you again. SPL_SET_AUTHORITY | WARN | WARN | ALLOW |
| Transaction contains no SPL Token instructionsTransaction contains no SPL Token instructions This transaction doesn't contain any SPL Token program instruction TINL can decode — it might move SOL directly, call an entirely different program, or use an instruction shape TINL doesn't parse. This is TINL's own blind spot on Solana, flagged explicitly rather than silently allowed. SPL_NO_TOKEN_INSTRUCTIONS | WARN | WARN | ALLOW |
| Native SOL transfer (no other instructions)Native SOL transfer (no other instructions) This transfers SOL, Solana's own coin, from your account to another address using the System Program — no token program and no contract call involved. SOLANA_NATIVE_TRANSFER | ALLOW | ALLOW | ALLOW |
| Solana durable-nonce transaction (no expiry)Solana durable-nonce transaction (no expiry) This transaction uses a durable nonce instead of a normal, short-lived one — most Solana transactions expire on their own within a minute or two if not submitted, but this one doesn't. It can sit around and be submitted much later than you might expect. SOLANA_DURABLE_NONCE_TRANSACTION | WARN | WARN | ALLOW |
| Solana instruction nobody can read, beside readable onesSolana instruction nobody can read, beside readable ones TINL recognized some of what this transaction does, but it also contains an instruction for a program TINL cannot read. The part TINL could read may be harmless; the part it could not is the part that matters. SOLANA_UNRECOGNIZED_INSTRUCTION | WARN | WARN | ALLOW |
| Solana account's owning program is being changedSolana account's owning program is being changed This changes which program controls an account — if it is your wallet account, the SOL in it would then be controlled by that program's code instead of by your signature. SOLANA_ACCOUNT_ASSIGN | WARN | WARN | ALLOW |
| Simulation shows an authority over the user's account changing handsSimulation shows an authority over the user's account changing hands Running this transaction shows no money moving — and one of your accounts changing hands: a spending delegate, the account's owner, its close authority, or the program that controls it. SOLANA_SIMULATED_AUTHORITY_CHANGE | WARN | WARN | ALLOW |
| Unreadable, but simulation showed nothing leaving — not the same as safeUnreadable, but simulation showed nothing leaving — not the same as safe TINL does not recognize the program this transaction calls. Running it against your own node shows none of your balances leaving and nothing of yours changing hands, so it is not treated as a refusal. SOLANA_SIMULATED_NO_MOVEMENT | ALLOW | ALLOW | ALLOW |
| Solana off-chain message signatureSolana off-chain message signature This signs a plain, human-readable message — not a transaction, and not something that moves tokens or grants any permission on its own. Common for logging into a website ('Sign In With Solana') or proving you control this wallet. SOLANA_MESSAGE_SIGNED | ALLOW | ALLOW | ALLOW |
| Solana message is not valid UTF-8Solana message is not valid UTF-8 You're being asked to sign a message that isn't ordinary readable text — it's raw data TINL can't display as words. Some legitimate apps do use raw-byte challenges for logging in, but it's unusual, and you can't visually confirm what you're actually signing the way you normally could. SOLANA_MESSAGE_NON_UTF8 | WARN | ALLOW | ALLOW |
| Solana 'message' is actually a transactionSolana 'message' is actually a transaction You were asked to sign a plain message — but the data underneath is actually a real, fully-formed transaction, not text. This is a real phishing technique: a site asks your wallet to 'just sign a message' (which normally feels lower-stakes and skips your wallet's transaction-review screen), then submits your signature on-chain as if you'd approved a real transaction. SOLANA_MESSAGE_LOOKS_LIKE_TRANSACTION | WARN | WARN | ALLOW |
| Token-2022 transfer with a fee extensionToken-2022 transfer with a fee extension This token charges a small fee on every transfer, built into the token itself — the amount that actually arrives at the destination is a little less than the amount being sent, with the difference collected as a fee. TOKEN2022_TRANSFER_FEE | WARN | ALLOW | ALLOW |
| Token-2022 permanent delegate (issuer can take the token back)Token-2022 permanent delegate (issuer can take the token back) This token was created with a permanent delegate: a fixed address that can transfer or burn it out of anyone's account at any time, without that person approving anything. It is set when the token is created and can never be removed. TOKEN2022_PERMANENT_DELEGATE | WARN | WARN | ALLOW |
| Token mint has a freeze authority (recorded; normal for stablecoins)Token mint has a freeze authority (recorded; normal for stablecoins) The token has a freeze authority — an address that can freeze holders' accounts, after which the balance cannot be moved or sold by the person who owns it. SPL_MINT_FREEZE_AUTHORITY | ALLOW | ALLOW | ALLOW |
| Token mint created recentlyToken mint created recently One of the tokens in this transaction was created only days ago, and whoever created it can still make more of it, freeze your account holding it, or both. A new token is not wrong by itself: on a busy day more than 200,000 are created, and most launch with those powers already given up. SPL_MINT_NEWLY_CREATED | WARN | WARN | ALLOW |
| Recognized standard function selectorRecognized standard function selector This calls a function TINL's registry recognizes by name (e.g. a common DeFi or token-standard operation), but doesn't have a dedicated risk-pattern decoder for — distinct from a fully unknown selector, since at least the operation itself is a known, named one. SELECTOR_RECOGNIZED_STANDARD | ALLOW | ALLOW | ALLOW |
| Selector flagged by A3E9 Threat IntelligenceSelector flagged by A3E9 Threat Intelligence This calls a function specifically identified and listed in TINL's Threat Intelligence Tier — a known drainer selector or malicious permit variant someone has already identified in the wild, not merely an unfamiliar one. THREAT_INTEL_FLAGGED_SELECTOR | WARN | WARN | ALLOW |
| Spender or operator listed as a known drainerSpender or operator listed as a known drainer The address this request would let spend your tokens or move your NFTs is listed in your threat intelligence feed as one used by a wallet-draining operation. Once it has that permission, it can take those assets whenever it chooses, without asking you again. COUNTERPARTY_KNOWN_DRAINER | WARN | WARN | ALLOW |
| Transaction value over the profile's spending capTransaction value over the profile's spending cap This is a bounded, finite approval or transfer — but its real-world USD value (resolved via a live price oracle) is higher than the spending cap your active profile allows for a single request. SPENDING_CAP_EXCEEDED | WARN | WARN | ALLOW |
| Constant-product swap detectedConstant-product swap detected This calls a decentralized exchange function that swaps one token for another through an automated market maker (AMM) liquidity pool. AMM_SWAP_DETECTED | ALLOW | ALLOW | ALLOW |
| Concentrated-liquidity swap detected (not scored offline)Concentrated-liquidity swap detected (not scored offline) This swaps one token for another through a concentrated-liquidity pool (Uniswap V3 and pools like it), where liquidity is placed in price ranges rather than spread across the whole curve. AMM_V3_SWAP_DETECTED | ALLOW | ALLOW | ALLOW |
| Swap detected but could not be priced — not the same as safeSwap detected but could not be priced — not the same as safe This is a token swap, but TINL could not read the pool it trades against, so it has no live reserve figure and cannot tell you what this trade would cost in price impact. AMM_SWAP_UNPRICED | WARN | ALLOW | ALLOW |
| Elevated price impact for pool depthElevated price impact for pool depth TINL checked this swap against the target liquidity pool's real, live on-chain reserves. This trade would consume enough of the pool that you'd receive meaningfully less than the current market price — a sign the pool may be too shallow for a trade this size. AMM_PRICE_IMPACT_WARN | WARN | WARN | ALLOW |
| Severe price impact — mathematically guaranteed lossSevere price impact — mathematically guaranteed loss TINL checked this swap against the target liquidity pool's real, live on-chain reserves. This trade is large enough relative to the pool that a very significant share of your value would be lost to price impact — not a hypothetical risk, a direct consequence of the pool's own reserve math. AMM_PRICE_IMPACT_BLOCK | WARN | WARN | ALLOW |
| A cheaper route existed among the pools suppliedA cheaper route existed among the pools supplied TINL quoted this trade against other liquidity pools it could read at the same block, and at least one of them would have returned more of the token you are buying. AMM_ROUTE_INFERIOR | ALLOW | ALLOW | ALLOW |
| How much better the best route wasHow much better the best route was The measured difference between what this trade returns and what the best route TINL could read would have returned, carried alongside the decision rather than acting as one. AMM_ROUTE_EDGE | ALLOW | ALLOW | ALLOW |
For a first-time or non-expert holder. Dangerous intents are WARNed with the reason, or NORMALIZEd down to a capped amount the user must confirm — never passed through silently. It stops nothing on the user's behalf unless the user turns on stop-instead-of-warning.
For an active trader or arbitrage operator who understands the risk but still wants a visible warning before signing. Like retail_strict, it stops nothing unless the user asks it to.
Zero friction for a DApp developer or CI environment. Every intent is allowed; every decision is still written to the audit chain.
CONTRACT_UNVERIFIED is part of the documented profile schema above; live enforcement requires a contract-verification data source that isn't wired into the engine yet — every other rule shown is enforced today.
A request for an unlimited token allowance — an on-chain approve(), an EIP-2612 permit signature, or an SPL Token delegate Approve on Solana — isn't simply refused, and it isn't silently rewritten either. TINL proposes a modified value / amount field capped to what the transaction actually needs, then discloses what would change — original request, rewritten amount — in the Normalization Consent Dialog: Approve Once, Approve All Future (revocable any time), or Cancel. retail_strict and trader_balanced both normalize the on-chain approve cases — an unlimited allowance left sitting on a contract is a standing drainer target regardless of how careful the user who approved it was. The EIP-2612 permit case stays a plain WARN under trader_balanced: a permit is a one-time-use off-chain signature, not a standing on-chain allowance, so it doesn't carry the same residual risk. When the user accepts the rewrite, what reaches the wallet is the capped payload — and the wallet's own confirmation screen remains the last line of defense. Against a cooperative demo or a wallet-integrated hook that is true end to end; against a hostile page WARN and NORMALIZE stay advisory as a whole — the consent channel itself no longer lives in the page's JavaScript world, but the payload can still be swapped after the decision — so always read the wallet confirmation before signing.
// WHY_THE_USUAL_ADVICE_DOESNT_WORK
They require the user to already know drainers exist, remember to periodically check, and find and correctly operate a third-party revocation site — itself a documented phishing target. This only protects someone who already survived one scare and got paranoid.
Understanding what a burner wallet is, remembering to actually use it instead of the main one, and funding it separately every time is expert workflow discipline — not something a first-time user does correctly under a "mint ends in 2 minutes" countdown.
They cost money, have to be bought and set up before the incident, and even then only stop the signature step — they don't explain what's being approved. A hardware wallet will still make you press the button to sign an unlimited approval you don't understand.
All three assume the user already has the literacy to know they're at risk. The people getting drained are, by definition, the ones who don't. TINL removes the requirement that the user understand anything — the decision moves in front of them automatically, at the moment it matters, in the wallet they already have open. No separate tool, no separate wallet, no separate hardware, no after-the-fact cleanup.
TINL protects against the attack patterns it has rules for: unlimited approvals, malicious permits, setApprovalForAll, and the Solana SPL Approve equivalent. BLOCK is enforced in the browser extension — a blocked request never reaches the wallet. WARN and NORMALIZE remain advisory as a whole against a hostile page. The page-forged proceed path is closed: since 2026-08-08 consent resolves over a MessagePort between the ISOLATED and MAIN worlds, in an extension-owned popup a page cannot answer for itself. What keeps the pair advisory is what is still open — the decision is not yet bound to a digest of the exact bytes, and MAIN-world wrap and inject races remain — so the wallet's own confirmation screen is still the real backstop for those two decisions, and no interception percentage is claimed. TINL also does not protect against a user manually typing an amount into a legitimate-looking-but-malicious DApp UI with no drainer signature involved, address-poisoning scams, fake airdrops the user actively chooses to interact with, or a WARN the user clicks through anyway. It narrows the blind-signing gap. It is not a complete answer to social-engineering loss.
The TINL Evaluator (separate from the HSM evaluator) includes a live wallet demo: connect a real MetaMask (Sepolia) and Phantom (Solana devnet), switch policy profiles, and watch TINL's own decision popup appear before the wallet's real confirmation dialog — for every ALLOW, WARN, BLOCK, and NORMALIZE case.