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.
./install.sh wallet-safetyname: "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 }
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.
chmod 600 as fallback. Never a .env that is committed — add .env and key files to .gitignore immediately if they live in a repo.export HISTCONTROL=ignorespace or run with history off).stat -c '%a %n' <keyfile> — must show 600 (owner read/write only). Fix with chmod 600 <keyfile>.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).type(uint256).max) allowances to unknown contracts, allowances to contracts you no longer use, or allowances you never knowingly granted.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.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).uint256.max allowance from an unfamiliar contract is a red flag.approve (often unlimited) or a malicious permit. Never sign a claim you didn't seek out; never sign a permit from an unfamiliar dapp.eth_getTransactionCount / explorer history; note anything unauthorized.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.
permit requests from unfamiliar dapps.chmod 600 file, never in logs/env-commits/chat; DON'T echo, print, or paste a raw key for "debugging."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.
publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e
No ratings yet — be the first to rate it.