---
name: "wallet_safety"
description: "Defensive crypto wallet safety for agents working with EVM wallets: private-key and seed storage hygiene, token-approval auditing and revocation, a pre-flight checklist before any send or approval, scam-pattern spotting (dusting, airdrop phishing, address poisoning, clipboard tampering), and an incident-response checklist for suspected key exposure. Triggers whenever an agent must store a key, sign a transaction, grant or review an allowance, handle an unexpected token/NFT, or respond to anything that looks off. Defensive only — no trading, no speculation."
metadata: { "includeInPrompt": true }
---

## Purpose

Keep an agent's wallet keys secret, its token allowances minimal, its sends deliberate, and its response to compromise fast. This is the defensive safety complement to execution skills (like the `bankr` trading API skill): this skill decides what is safe to do; execution skills carry it out.

## Workflow

### 1. Key and seed storage setup

1. Never put a raw private key or seed phrase in: chat messages, logs, shell history, URLs/query strings, issue trackers, screenshots, AI tool inputs, or files committed to version control.
2. Store keys in exactly one place: a secrets manager/vault preferred; a single key file with `chmod 600` as fallback. Never a `.env` that is committed — add `.env` and key files to `.gitignore` immediately if they live in a repo.
3. Load the key into memory only at signing time; overwrite or drop it after. Disable shell history for sessions that touch keys (`export HISTCONTROL=ignorespace` or run with history off).
4. Verify file permissions with `stat -c '%a %n' <keyfile>` — must show `600` (owner read/write only). Fix with `chmod 600 <keyfile>`.
5. Separate concerns: a hot wallet that interacts with contracts should never hold the full treasury. Label every wallet address with its purpose.

### 2. Approval hygiene (audit and revoke)

1. Audit allowances on a schedule and after every incident: open a known revocation UI (e.g. revoke.cash — type the URL yourself, check the address bar; phishing lookalikes exist) or the chain's block-explorer approval checker, paste your address, and list all ERC-20/721/1155 spenders.
2. Programmatic check via JSON-RPC (read-only, safe): `eth_call` to the token contract with calldata = `0xdd62ed3e` + your 32-byte-padded address + spender 32-byte-padded address. The returned 32-byte value is the current allowance (`0xdd62ed3e` = the standard `allowance(address,address)` selector).
3. Flag anything suspicious: unlimited (`type(uint256).max`) allowances to unknown contracts, allowances to contracts you no longer use, or allowances you never knowingly granted.
4. Revoke by sending `approve(spender, 0)` to the token contract (`0x095ea7b3` = standard `approve(address,uint256)` selector). Confirm the revocation on-chain with `eth_getTransactionReceipt` or the explorer, then re-check the allowance.
5. Prefer minimal allowances when approving: the exact amount needed, or as close as the flow allows — never unlimited by default.
6. Treat Permit2 signatures as approvals too: an off-chain `permit` signature can drain via Permit2 later. Decline permit prompts you didn't initiate, and verify the canonical Permit2 contract address on a block explorer before interacting with it (its address is widely documented; verify fresh, never quote it from memory into a transaction).

### 3. Pre-flight checklist — before every send or approval

1. Checksum-verify the destination address with the EIP-55 checker below (test vectors included). An address that fails checksum must never be used.
2. Compare the full destination against its trusted source (copy from the official site/docs, not from a chat or comment thread). Eyeball at least the first and last 6 characters — address-poisoning scams send lookalikes that differ in the middle.
3. Confirm the network/chainId of your RPC endpoint matches the intended chain; confirm the recipient supports that chain. Sending on the wrong chain = lost funds.
4. Confirm the approval or transfer amount, token contract, and spender match your intent — read the wallet/signer prompt, never blind-sign. A request for `uint256.max` allowance from an unfamiliar contract is a red flag.
5. For a new destination, send a tiny test amount first and confirm receipt, unless the flow makes a test send impossible or the amount is trivial — then skip deliberately and say so.
6. Record what you did: destination, amount, token, chain, tx hash, and the reason — in your run log (never the key itself).

### 4. Scam patterns — how to spot and handle

- **Dusting:** tiny unexpected tokens/NFTs appear in your wallet. Do nothing: don't sell, don't transfer, don't interact. Dust contracts can contain malicious approval code.
- **Fake airdrop / claim phishing:** a site promises free tokens if you "connect and claim" — the claim transaction is really an `approve` (often unlimited) or a malicious `permit`. Never sign a claim you didn't seek out; never sign a permit from an unfamiliar dapp.
- **Address poisoning:** your transaction history fills with lookalike addresses (same first/last characters as a real counterparty). Never copy a destination from tx history — always from the trusted source.
- **Clipboard / QR tampering:** malware swaps a copied address. Always re-verify the pasted address with the EIP-55 check against the source before sending.
- General rule: unsolicited money, urgency ("claim in 10 minutes"), and any request to sign something you can't read are all stop signals. Stop, verify via an independent channel.

### 5. Incident response — suspected key exposure

1. Stop all signing from the exposed wallet immediately.
2. Generate a fresh key on a clean machine; do not reuse the old key or seed anywhere.
3. Move all funds (native + tokens) to the new address in one sweep. Assume an attacker watches the mempool — sweep fast, with adequate gas.
4. Revoke every approval granted by the old wallet (it can still be drained via allowances even after funds move — new deposits would be at risk).
5. Check the old wallet's outgoing transactions via `eth_getTransactionCount` / explorer history; note anything unauthorized.
6. Report to the human: what was exposed, what moved, tx hashes, and what remains. Never share the new key anywhere.

## Output Contract

Every run that touches a wallet must return:

- `wallet`: the address acted on (checksummed), labeled with its purpose.
- `action`: what was done — `key_setup`, `approval_audit`, `revoke`, `preflight`, `send`, `incident_response`.
- `checks_passed`: the checklist items completed (EIP-55 verify, amount/token/spender confirmation, test send result or explicit skip reason).
- `tx_hashes`: any transactions submitted.
- `findings`: suspicious allowances, unexpected tokens, or scam patterns observed — `none` if clean.
- `residual_risk`: anything left unaddressed and who owns it.

If any pre-flight check fails, the run returns `aborted` with the reason instead of a transaction.

## Operating Rules

1. DO verify every address with EIP-55 before use; DON'T ever send to an address that fails checksum.
2. DO keep allowances minimal and revoke what you don't use; DON'T leave unlimited approvals on idle wallets.
3. DO read every signer prompt (token, spender, amount) before signing; DON'T blind-sign, especially `permit` requests from unfamiliar dapps.
4. DO copy destinations only from trusted, official sources; DON'T copy from tx history, chat, or comments.
5. DO test-send a small amount to a new destination when the flow allows it; DON'T invent one when it doesn't — record the explicit skip instead.
6. DO store keys in a vault or a `chmod 600` file, never in logs/env-commits/chat; DON'T echo, print, or paste a raw key for "debugging."
7. DO treat unexpected tokens/NFTs as hostile until proven otherwise; DON'T interact with dust or claim airdrops you didn't seek out.
8. DO assume a compromised key means the whole wallet is burned: sweep, revoke, rotate; DON'T reuse the key or seed.
9. DO confirm the chainId/network before every send; DON'T assume your RPC is on the chain you think it is.
10. DON'T quote token addresses, permit2 addresses, or revocation-UI URLs from memory into a transaction or link — verify on a block explorer or by typing the URL yourself first.

## Appendix: EIP-55 checksum verifier (inline, save and run)

Spec (verified against the EIP-55 reference text, 2026-09-21): take the lowercase hex address (40 chars, no `0x`), hash it with **Keccak-256** (note: NOT NIST SHA3-256 — they differ), then uppercase each letter `a–f` whose corresponding hash nibble is ≥ 8.

This code was **not** executed locally (no Keccak implementation available in this environment). Run it in an environment with a Keccak library and validate against the spec's own test vectors first — if the vectors fail, the Keccak implementation is wrong, not your address.

```python
# Save as checksum.py, then: python3 checksum.py 0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed
import sys

# Needs a Keccak-256 implementation, e.g.:
#   pip install pycryptodome   -> from Crypto.Hash import keccak
# If you don't have one, verify the address in a block explorer instead.
try:
    from Crypto.Hash import keccak
    def keccak256(data: bytes) -> bytes:
        k = keccak.new(digest_bits=256); k.update(data); return k.digest()
except ImportError:
    raise SystemExit("No Keccak-256 implementation found. Install pycryptodome, or verify via a block explorer.")

def to_checksum(addr: str) -> str:
    hex_addr = addr.lower().replace("0x", "")
    assert len(hex_addr) == 40 and all(c in "0123456789abcdef" for c in hex_addr), "not a 20-byte hex address"
    hashed = keccak256(hex_addr.encode("ascii")).hex()
    out = "".join(
        c.upper() if (c in "abcdef" and int(hashed[i], 16) >= 8) else c
        for i, c in enumerate(hex_addr)
    )
    return "0x" + out

def is_valid_checksum(addr: str) -> bool:
    return to_checksum(addr) == addr

if __name__ == "__main__":
    # Spec test vectors (from EIP-55): must all pass before trusting the code
    vectors = [
        "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed",
        "0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359",
        "0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB",
        "0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb",
    ]
    for v in vectors:
        assert is_valid_checksum(v), f"vector failed: {v}"
    print("self-test: all EIP-55 vectors pass")
    if len(sys.argv) > 1:
        a = sys.argv[1]
        print(f"{a} -> {'VALID' if is_valid_checksum(a) else 'INVALID'} checksum")
```

Quick read-only allowance check via JSON-RPC (`eth_call`, standard method): calldata = `0xdd62ed3e` + owner address (no `0x`, left-padded to 64 hex chars) + spender address (same padding), `to` = token contract address, `data` field in the call object. Returned value is a 32-byte big-endian allowance.
