contract-learner
Contract Learner — SKILL.md Generator for keypo-wallet
Given a deployed smart contract address, this skill generates a complete, portable SKILL.md file that teaches any agent how to interact with that contract using keypo-wallet as the execution backend.
This skill does not execute transactions itself. It produces SKILL.md files that do.
Prerequisites — Check These First
Before doing anything else, resolve these two dependencies. Do not proceed until both are confirmed.
Locate cast
Foundry's cast is required. Check for it in this order:
# Check PATH first
which cast 2>/dev/null || \
# Foundry's default install location
ls ~/.foundry/bin/cast 2>/dev/null || \
echo "CAST_NOT_FOUND"
If found at ~/.foundry/bin/cast, use the full path for all commands in this skill (e.g., ~/.foundry/bin/cast call ...). Set a variable for convenience:
CAST=$(which cast 2>/dev/null || echo ~/.foundry/bin/cast)
If not found at either location, tell the user to install Foundry: curl -L https://foundry.paradigm.xyz | bash && foundryup
Verify Etherscan API key
An Etherscan API key is required to fetch contract ABIs. Check for it immediately:
echo $ETHERSCAN_API_KEY
If the variable is set, use it silently — do not print it or include it in any output.
If empty, tell the user to set it in their shell environment. Do not ask them to paste the key into the chat — keys entered in conversation are stored in chat history and may be synced or logged. Instead, instruct them:
Please set your Etherscan API key as an environment variable before proceeding.
Add this to your ~/.zshrc (or ~/.bashrc) and restart your terminal:
export ETHERSCAN_API_KEY="your-key-here"
Free keys are available at https://etherscan.io/myapikey and work across all chains.
Then re-run this command.
Do not proceed without the key. Do not attempt to fetch ABIs without a key — it will fail.
Input
The user provides:
- Contract address —
0x...(42 characters, required) - Chain — chain name or ID (required)
- Skill name — lowercase hyphenated name for the output skill (optional, will be inferred from contract name)
Process
1. Resolve the chain
Map the user's chain input to a chain ID and RPC URL:
| Chain | Chain ID | RPC URL | Explorer API Base |
|---|---|---|---|
| Ethereum | 1 | https://eth.llamarpc.com |
https://api.etherscan.io/v2/api?chainid=1 |
| Base | 8453 | https://mainnet.base.org |
https://api.etherscan.io/v2/api?chainid=8453 |
| Base Sepolia | 84532 | https://sepolia.base.org |
https://api.etherscan.io/v2/api?chainid=84532 |
| Arbitrum | 42161 | https://arb1.arbitrum.io/rpc |
https://api.etherscan.io/v2/api?chainid=42161 |
| Optimism | 10 | https://mainnet.optimism.io |
https://api.etherscan.io/v2/api?chainid=10 |
| Polygon | 137 | https://polygon-rpc.com |
https://api.etherscan.io/v2/api?chainid=137 |
| Sepolia | 11155111 | https://rpc.sepolia.org |
https://api.etherscan.io/v2/api?chainid=11155111 |
2. Fetch the ABI
Only use the Etherscan API to fetch ABIs. Never try to scrape or fetch block explorer web pages (basescan.org, etherscan.io, etc.) — they return 403 errors and HTML, not ABI data.
Use cast interface with the API key:
$CAST interface <address> --chain <chain-id> --etherscan-api-key $ETHERSCAN_API_KEY
If cast interface fails, fall back to the Etherscan V2 REST API:
curl -s "https://api.etherscan.io/v2/api?chainid=<chain-id>&module=contract&action=getabi&address=<address>&apikey=$ETHERSCAN_API_KEY"
The response JSON has result containing the ABI as a JSON string. Parse it to get the function list.
If the contract is not verified: Stop and tell the user. You cannot generate a skill for an unverified contract. They can provide the ABI manually as a JSON file.
If the contract is a proxy: Detect proxy patterns (implementation(), upgradeTo()). Follow the proxy to the implementation:
$CAST call <proxy-address> "implementation()(address)" --rpc-url <rpc-url>
$CAST interface <implementation-address> --chain <chain-id> --etherscan-api-key $ETHERSCAN_API_KEY
Use the proxy address in the generated skill (that's what users send transactions to) but note it's a proxy in the skill's description.
3. Gather contract metadata
Before generating the skill, collect these details using read calls:
# For tokens — try these, they may revert on non-token contracts
$CAST call <address> "name()(string)" --rpc-url <rpc-url>
$CAST call <address> "symbol()(string)" --rpc-url <rpc-url>
$CAST call <address> "decimals()(uint8)" --rpc-url <rpc-url>
# Verify the contract has code deployed
$CAST code <address> --rpc-url <rpc-url>
If name/symbol return values, this is likely a token contract. Include the decimals value in the generated skill — this is critical for correct amount encoding.
4. Categorize functions
From the ABI, sort functions into three groups:
- Write functions — not
vieworpure, these require keypo-wallet to execute - Read functions —
vieworpure, these usecast calldirectly - Payable functions — subset of write functions that accept ETH, require
--valueflag
5. Generate the SKILL.md
Produce a SKILL.md file following the output template below. Replace all <placeholders> with actual values from the contract analysis. Remove any sections that don't apply (e.g., remove the Payable section if there are no payable functions).
Write the file to ./<skill-name>/SKILL.md.
Output Template
The generated SKILL.md must follow this structure exactly. This ensures consistency across all generated skills and compatibility with keypo-wallet.
---
name: <skill-name>
description: Interact with <contract-name> (<symbol-if-token>) at <address> on <chain-name>. <one-sentence description of what the contract does based on its functions>. Use with keypo-wallet for transaction execution — use `cast calldata` to encode function calls and pipe to `keypo-wallet send` or `keypo-wallet batch`.
license: MIT
metadata:
author: auto-generated by contract-learner
version: "1.0.0"
source-contract: <address>
chain: <chain-name>
chain-id: <chain-id>
generated: <YYYY-MM-DD>
---
# <Contract Name> (<chain-name>)
| Field | Value |
|-------|-------|
| Address | `<address>` |
| Chain | <chain-name> (Chain ID: <chain-id>) |
| Type | <token / protocol / proxy — inferred from functions> |
| Decimals | <decimals — only if token> |
| Symbol | <symbol — only if token> |
| RPC | `<rpc-url>` |
**Auto-generated from verified ABI.** Do not modify the address or function signatures.
---
## Read Functions
Use `cast call` — no wallet needed, no gas cost.
### <function-name>
```bash
cast call <address> "<signature>(<return-types>)" <arg-placeholders> --rpc-url <rpc-url>
```
<Repeat for each read function>
---
## Write Functions
Encode calldata with `cast calldata`, then execute via keypo-wallet.
### <function-name>
```bash
CALLDATA=$(cast calldata "<signature>" <arg-placeholders>)
keypo-wallet send --key <key-name> --to <address> --data $CALLDATA
```
<If payable, add: `# This function is payable — add --value <wei> to send ETH`>
<Add verification step: show the read function to confirm the result>
<Repeat for each write function>
---
## Common Patterns
<Include this section only if the contract has functions that naturally compose>
### Approve + <Action>
```bash
APPROVE_DATA=$(cast calldata "approve(address,uint256)" <this-contract-address> <amount>)
ACTION_DATA=$(cast calldata "<action-signature>" <args>)
echo "[
{\"to\": \"<token-address>\", \"value\": \"0\", \"data\": \"$APPROVE_DATA\"},
{\"to\": \"<address>\", \"value\": \"0\", \"data\": \"$ACTION_DATA\"}
]" | keypo-wallet batch --key <key-name> --calls -
```
---
## Amount Encoding
<Include this section only for token contracts>
This token uses **<decimals> decimals**. Always convert human-readable amounts:
| Human Amount | Raw Amount |
|-------------|------------|
| 1.0 | <10^decimals> |
| 0.01 | <10^(decimals-2)> |
| 100 | <100*10^decimals> |
To transfer 1.0 <symbol>:
```bash
CALLDATA=$(cast calldata "transfer(address,uint256)" <recipient> <10^decimals>)
keypo-wallet send --key <key-name> --to <address> --data $CALLDATA
```
Rules for Generation
-
Include only functions from the actual ABI. Never add functions that aren't in the contract.
-
Always hardcode the verified contract address. The generated skill must contain the exact address — never let the consuming agent guess or substitute addresses.
-
Include the chain ID and RPC URL. The generated skill must be chain-specific. If the same contract exists on multiple chains, generate separate skills or include a chain table.
-
For token contracts, always include the Amount Encoding section. This prevents the most common agent error — sending wrong amounts.
-
Use
cast calldatafor encoding, not manual hex. The consuming agent runscast calldataat execution time. This keeps generated skills readable and correct. -
For proxy contracts, note it in the description but use the proxy address (not the implementation) for all execution commands.
-
Group related functions. If the contract has approve + transferFrom, show the batched pattern. If it has deposit + withdraw, group them.
-
Omit admin-only functions like
onlyOwner,renounceOwnership,upgradeTounless the user specifically asks for them. -
Include verification steps. After every write function, show the corresponding read function to check the result (e.g., after
transfer, showbalanceOf). -
Test the generated skill before saving. Run at least one read function to verify the address and RPC are working:
$CAST call <address> "<simplest-read-function>" --rpc-url <rpc-url>If this fails, the generated skill is broken — fix before writing to disk.
After Generation
- Write the file to
./<skill-name>/SKILL.md - Print a summary: skill name, contract address, chain, number of read/write/payable functions
- Run one read function to verify the skill works
- Show the user how to install it:
# Use in current project (Claude Code) cp -r ./<skill-name> .claude/skills/ # Or add to keypo-wallet's skills directory cp -r ./<skill-name> skills/ # Or publish to GitHub for npx skills add # Push the <skill-name>/ folder to a repo, then: # npx skills add <owner>/<repo> --skill <skill-name>