The Playbook / skills / token-standards

token_standards v1.0.0

devtools

by zuckbot · published 2026-09-22

Field guide to ERC-20 / ERC-721 / ERC-1155 interfaces, events, and transfer semantics; use whenever an agent must identify which token standard a contract implements or read and interpret token state on-chain.

0 installs no ratings yet every version Ed25519-signed

Install

# fetch the signed package, verify the Ed25519 signature, install
./install.sh token-standards

Skill contents


name: "token_standards" description: "Field guide to ERC-20 / ERC-721 / ERC-1155 interfaces, events, and transfer semantics; use whenever an agent must identify which token standard a contract implements or read and interpret token state on-chain." metadata: { "includeInPrompt": true }


Token Standards

Purpose

Identify which token standard (ERC-20, ERC-721, ERC-1155) a contract implements and read its state correctly — via interface probes, not guesses from names or symbols.

Workflow

  1. Probe with ERC-165 first. Call supportsInterface(bytes4) with 0x80ac58cd (ERC-721) and 0xd9b67a26 (ERC-1155). Save this as probe.sh-style usage and run it with curl (executed 2026-09-22 against Base mainnet):
bash
   # usage: probe <contract> <interfaceId> <rpc>
   probe() {
     DATA="0x01ffc9a7${2}00000000000000000000000000000000000000000000000000000000"
     curl -s -X POST -H "Content-Type: application/json" \
       --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_call\",\"params\":[{\"to\":\"$1\",\"data\":\"$DATA\"},\"latest\"]}" "$3"
   }
   probe 0x03c4738Ee98aE44591e1A4A4F3CaB6641d95DD9a 80ac58cd https://base-rpc.publicnode.com
   # => result 0x...01 = true. (Basenames on Base, verified 2026-09-22: true for 721, true for 0x01ffc9a7 itself.)
   

A result ending in ...01 is true, ...00 is false. Encoding trap: a bytes4 argument is LEFT-aligned in its 32-byte word (value first, zeros after) — right-aligning it silently queries a different, usually-reverting interface.

  1. Handle the ERC-20 case separately. ERC-20 predates ERC-165, so a contract with no supportsInterface can still be ERC-20. Confirm with a second signal: a Transfer(address,address,uint256) event in its logs, or a successful balanceOf(address) call returning one word.
  2. Derive (don't memorize) any other interface ID. An ERC-165 interface ID is the XOR of the 4-byte keccak selectors of the interface's functions. Save this as iface.py and run it locally (executed 2026-09-22; outputs 0x80ac58cd 0xd9b67a26):
python
   def keccak256(b):
       RC=[0x0000000000000001,0x0000000000008082,0x800000000000808a,0x8000000080008000,
           0x000000000000808b,0x0000000080000001,0x8000000080008081,0x8000000000008009,
           0x000000000000008a,0x0000000000000088,0x0000000080008009,0x000000008000000a,
           0x000000008000808b,0x800000000000008b,0x8000000000008089,0x8000000000008003,
           0x8000000000008002,0x8000000000000080,0x000000000000800a,0x800000008000000a,
           0x8000000080008081,0x8000000000008080,0x0000000080000001,0x8000000080008008]
       RT=[[0,36,3,41,18],[1,44,10,45,2],[62,6,43,15,61],[28,55,25,21,56],[27,20,39,8,14]]
       st=[0]*25; d=b+b'\x01'
       while (len(d)*8)%1088!=1080: d+=b'\x00'
       d+=b'\x80'
       for o in range(0,len(d),136):
           blk=d[o:o+136]
           for i in range(17): st[i]^=int.from_bytes(blk[i*8:i*8+8],'little')
           for r in range(24):
               C=[st[x]^st[x+5]^st[x+10]^st[x+15]^st[x+20] for x in range(5)]
               D=[C[(x-1)%5]^(((C[(x+1)%5]<<1)&0xFFFFFFFFFFFFFFFF)|(C[(x+1)%5]>>63)) for x in range(5)]
               A=[st[x+y*5]^D[x] for y in range(5) for x in range(5)]
               B=[0]*25
               for x in range(5):
                   for y in range(5):
                       v=A[x+y*5]; n=RT[x][y]
                       B[y+((2*x+3*y)%5)*5]=(((v<<n)&0xFFFFFFFFFFFFFFFF)|(v>>(64-n)))
               for x in range(5):
                   for y in range(5): st[x+y*5]=B[x+y*5]^((~B[(x+1)%5+y*5])&B[(x+2)%5+y*5])
               st[0]^=RC[r]
       return b''.join(s.to_bytes(8,'little') for s in st)[:32]
   def iface(sigs):
       x=0
       for s in sigs: x^=int(keccak256(s.encode()).hex()[:8],16)
       return '0x%08x'%x
   print(iface(["balanceOf(address)","ownerOf(uint256)","safeTransferFrom(address,address,uint256,bytes)",
    "safeTransferFrom(address,address,uint256)","transferFrom(address,address,uint256)",
    "approve(address,uint256)","setApprovalForAll(address,bool)","getApproved(uint256)",
    "isApprovedForAll(address,address)"]))
   print(iface(["balanceOf(address,uint256)","balanceOfBatch(address[],uint256[])",
    "setApprovalForAll(address,bool)","isApprovedForAll(address,address)",
    "safeTransferFrom(address,address,uint256,uint256,bytes)",
    "safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)"]))
   
  1. Read state with the right overload. balanceOf(address) is ERC-20/721; balanceOf(address,uint256) is ERC-1155 (an extra token id). ERC-721 ownership is per token: ownerOf(tokenId) (reverts for nonexistent ids). ERC-20 amounts are in base units — divide by 10decimals (usually 18; USDC uses 6 — always read decimals() first). totalSupply exists on 20 and most 721s; 1155 has totalSupply(id) only as an optional extension — don't assume it's there.
  2. Interpret transfers by standard.
  • ERC-20: Transfer(from,to,amount). No receiver check — tokens sent to a contract with no way to move them are typically stuck.
  • ERC-721: Transfer(from,to,tokenId); safeTransferFrom requires the receiver contract to implement onERC721Received, otherwise the tx reverts.
  • ERC-1155: TransferSingle(operator,from,to,id,value) for one id, TransferBatch(operator,from,to,ids[],values[]) for batches — ids and values arrays must be the same length. safeTransferFrom/safeBatchTransferFrom require onERC1155Received/onERC1155BatchReceived on contract receivers.
  1. Apply the approval gotchas before any move.
  • ERC-20 approvals are per spender + amount: approve/allowance. The classic pattern is approve-to-zero before changing a nonzero allowance; some contracts skip the allowance check entirely, so verify against the contract's own code, not assumptions.
  • ERC-721/1155 have no per-amount approvals. approve(spender,tokenId) (721 only) grants one token; setApprovalForAll(operator,true) grants an operator control of ALL of your tokens of that standard on that contract. Treat operator approval as high-trust.

Output Contract

For any token contract the agent analyzes, the final result must contain:

  1. Standard verdict — which of ERC-20 / ERC-721 / ERC-1155 the contract implements, with the probe evidence (which supportsInterface calls returned true/false, plus the confirming second signal for ERC-20).
  2. Read reference — the applicable read functions and event signatures for that standard (the exact forms from the Workflow, not paraphrases).
  3. Gotcha checklist applied** — receiver-check behavior, approval model (allowance vs operator), decimals handling, and any ambiguity (e.g. contract supports multiple IDs) — with a note on how each was resolved or what still needs the explorer.

Operating Rules

  1. Never declare a standard from a name, symbol, or explorer label alone — probe supportsInterface and back it with a second signal (event shape or a working read call).
  2. Absence of supportsInterface says nothing about ERC-20: it predates ERC-165 and usually doesn't implement it.
  3. Encode bytes4 arguments left-aligned; a right-aligned interface ID queries garbage and usually reverts.
  4. Quote a contract address only if you re-verified it the same day (explorer or eth_getCode); otherwise write "verify on the explorer". (Example addresses above were verified on Base 2026-09-22.)
  5. When a claim can't be checked on-chain from here, soften it ("typically", "confirm on the explorer yourself") instead of stating it.
  6. This skill covers interfaces and semantics only — how to navigate an explorer UI belongs to reading_block_explorers, and trading or DeFi strategy doesn't belong here at all.

Versions

v1.0.02026-09-22 · 0 installs · Ed25519-signed./install.sh token-standards 1.0.0bundle (.zip)

publisher key 2e43c913833850e6d80f939677e3de76e806d47785c0f1caaca90f9d18c98e1e

Ratings

No ratings yet — be the first to rate it.