Skill Name
defi/evm_tx_handler
What should this skill do?
Summary
A Skillware skill that lets AI agents operate a dedicated EVM agent wallet to perform basic on-chain actions — buy, sell, and send — from structured trade intent, not slash commands.
The LLM handles natural language (e.g. “buy 10 Degen on Base”). The skill handles deterministic Web3: resolve names → quote → preview → (optional confirm) → sign → broadcast → wait for receipt.
Inspiration only (do not copy): OPSIE web3_handler.py — useful for feature ideas (multi-chain, token registry, Uni V2 swaps, gas policy, preview/confirm). Reimplement cleanly for Skillware; no /0x commands, no input() loops, no editing Python to add tokens.
Problem
Agents need a safe, repeatable way to trade and transfer on EVM chains without hard-coded CLI syntax. Users speak in NL; the skill enforces typed requests, previews, and optional confirmation before signing.
Agent vs skill
| Layer |
Responsibility |
| Agent |
Parse user NL → partial intent JSON; ask for missing fields in natural language; show preview; pass confirmed: true when user approves |
| Skill |
Merge intent with config/YAML registries; validate; quote on-chain; build/sign txs; async receipt wait; return structured JSON |
No regex NLP inside the skill — the agent fills the schema per instructions.md.
Architecture (bundle)
skills/defi/evm_tx_handler/
├── manifest.yaml
├── skill.py # resolve | quote | preview | execute | transfer | balances | wallet_info | update_preferences
├── instructions.md # NL patterns, missing-field dialogue, safety
├── card.json
├── test_skill.py
├── config.yaml.example # long-term prefs (no secrets)
└── data/
├── chains.yaml # chain id, RPC env key, native token, explorer, router
├── tokens.yaml # friendly name → contract per chain
└── addressbook.yaml # label → address (e.g. mom, george)
Configuration layers
| Layer |
Holds |
Persists |
.env |
AGENT_WALLET_PRIVATE_KEY, *_RPC_URL, optional API keys |
secrets only |
config.yaml |
default chain, default spend asset, gas policy, slippage, confirm toggle, optional caps |
long-term; change only via update_preferences or explicit user intent |
Per-request intent / overrides |
fields for this tx (e.g. aggressive gas once) |
does not overwrite config unless user asks to change defaults |
chains.yaml / tokens.yaml / addressbook.yaml |
registry data |
user-editable YAML |
Defaults: chain ethereum; spend asset native ETH when buying unless config or user specifies; gas normal unless user says low / high / aggressive; confirm_before_send: true unless config disables it.
Example agent flow
- User: “Buy 10 Degen on Base”
- Agent →
resolve with partial intent → skill returns missing_fields: ["spend_asset"], suggested_defaults: { "spend_asset": "usdc" } from config
- Agent: “Sure — what should I pay with? I can use USDC from your config.”
- User: “Use USDC”
- Agent →
quote → preview (you give X USDC, get ~10 DEGEN, rate, gas, router)
- User confirms (if enabled) → agent →
execute with confirmed: true
- Skill returns
tx_hash, explorer URL, receipt status
Send example: “Send 10 degen to mom” — resolves token + addressbook label; fills chain/gas from config.
MVP scope
- Chains: Ethereum + Base (extensible via
chains.yaml)
- Actions: buy, sell, transfer (native + ERC20)
- DEX: Uniswap V2-compatible router per chain (verify addresses; do not trust OPSIE literals)
- web3.py v6+, EIP-1559 gas, correct ERC20 decimals, async receipt wait
- Tests: mocked Web3; no live keys in CI
Out of scope (v1)
- Uniswap V3 / aggregators (1inch, 0x)
- Cross-chain bridges
- HD wallet generation inside skill (document: create dedicated wallet externally, key in
.env only)
- NLP/regex parsing inside
skill.py
- Using personal/main wallet — dedicated agent wallet only
Security & constitution (manifest)
- Dedicated agent wallet only; strong disclaimer that agents mis-parse and markets move
- Private key only in
.env; never in tool args, YAML, or logs
- Optional:
max_trade_usd, chain/token allowlists
- Fail closed if required env or registry entry missing
- Not financial or legal advice
Reference features from OPSIE (reimplement, don’t port)
Worth studying: multi-chain connect, token dict, address labels, buy/sell path quotes, approve+swap, gas strategy, preview before send, wait_for_transaction_receipt. Avoid: slash commands, interactive prompts, mutating source for config, hardcoded pub key, legacy-only gas, wrong decimal handling.
Ideal Inputs & Outputs
Tool shape
Single skill, action-based execute() (model-agnostic). Agent passes structured JSON; skill returns structured JSON.
Actions
| action |
Purpose |
resolve |
Merge intent + config + YAML registries; return missing fields + suggestions |
quote |
On-chain quote (amounts in/out, path, slippage) |
preview |
Human/agent-readable summary (also included in quote) |
execute |
Sign and send swap (requires confirmed: true when config says so) |
transfer |
Send native or ERC20 to address or addressbook label |
balances |
Wallet balances on a chain |
wallet_info |
Agent address, supported chains, prefs summary (no secrets) |
update_preferences |
Persist long-term config.yaml changes only when user explicitly intends |
Intent object (partial OK on resolve)
{
"side": "buy | sell | send",
"chain": "base",
"target_asset": "degen",
"spend_asset": "usdc",
"amount": 10,
"amount_kind": "target_out | spend_in",
"recipient": "mom",
"gas_policy": "normal | low | high | aggressive",
"slippage_bps": 50,
"confirmed": false
}
Field notes
chain — default from config (ethereum) if omitted
spend_asset — default from config for buys; else native ETH on chain
gas_policy — default normal from config; per-tx override does not persist
recipient — addressbook label or 0x… for send
amount_kind — for “buy 10 Degen”, amount is target_out
Example: resolve (missing spend asset)
Input
{
"action": "resolve",
"intent": {
"side": "buy",
"chain": "base",
"target_asset": "degen",
"amount": 10,
"amount_kind": "target_out"
}
}
Output
{
"status": "needs_input",
"resolved": {
"chain": "base",
"target_asset": "degen",
"amount": 10,
"amount_kind": "target_out",
"gas_policy": "normal"
},
"missing_fields": ["spend_asset"],
"suggested_defaults": {
"spend_asset": "usdc"
},
"agent_hint": "Ask which token to pay with; suggest USDC from config."
}
Example: quote + preview
Input
{
"action": "quote",
"intent": {
"side": "buy",
"chain": "base",
"target_asset": "degen",
"spend_asset": "usdc",
"amount": 10,
"amount_kind": "target_out"
}
}
Output
{
"status": "ready",
"preview": {
"side": "buy",
"chain": "base",
"you_pay": { "asset": "usdc", "amount": "123.45" },
"you_receive": { "asset": "degen", "amount": "10" },
"rate": "1 usdc = 0.081 degen",
"gas_estimate": { "policy": "normal", "max_fee_gwei": "0.05" },
"router": "uniswap_v2",
"usd": { "pay": 123.45, "receive": 0.10 },
"warnings": ["Rates are indicative; slippage may apply."]
},
"quote": {
"path": ["0x...", "0x..."],
"amount_in_wei": "...",
"amount_out_wei": "...",
"min_out_wei": "...",
"deadline": 1710000000
},
"requires_confirmation": true
}
Example: execute (after user confirms)
Input
{
"action": "execute",
"intent": {
"side": "buy",
"chain": "base",
"target_asset": "degen",
"spend_asset": "usdc",
"amount": 10,
"amount_kind": "target_out"
},
"confirmed": true
}
Output
{
"status": "confirmed",
"tx_hash": "0x...",
"explorer_url": "https://basescan.org/tx/0x...",
"receipt": {
"block_number": 123,
"gas_used": 250000,
"success": true
}
}
Example: transfer
Input
{
"action": "transfer",
"intent": {
"chain": "base",
"target_asset": "degen",
"amount": 10,
"recipient": "mom"
},
"confirmed": true
}
Output
{
"status": "confirmed",
"tx_hash": "0x...",
"explorer_url": "https://basescan.org/tx/0x...",
"recipient_resolved": "0x..."
}
config.yaml.example (long-term prefs, no secrets)
default_chain: ethereum
default_spend_asset: usdc
gas_policy: normal
confirm_before_send: true
slippage_bps: 50
max_trade_usd: 500
Environment variables (manifest env_vars)
| Variable |
Required |
Purpose |
AGENT_WALLET_PRIVATE_KEY |
yes |
Dedicated agent wallet only |
ETHEREUM_RPC_URL |
yes (if chain used) |
Ethereum JSON-RPC |
BASE_RPC_URL |
yes (if chain used) |
Base JSON-RPC |
COINGECKO_API_KEY |
no |
USD estimates in preview |
ETHERSCAN_API_KEY |
no |
Optional verification/links |
Private key referenced in config as private_key_env: AGENT_WALLET_PRIVATE_KEY — never inline.
Dependencies
web3 ≥ 6.x
pyyaml
requests or httpx (CoinGecko, optional)
Acceptance criteria (for implementation PR)
Targeted Models (if applicable)
Model Agnostic (All)
Skill Name
defi/evm_tx_handler
What should this skill do?
Summary
A Skillware skill that lets AI agents operate a dedicated EVM agent wallet to perform basic on-chain actions — buy, sell, and send — from structured trade intent, not slash commands.
The LLM handles natural language (e.g. “buy 10 Degen on Base”). The skill handles deterministic Web3: resolve names → quote → preview → (optional confirm) → sign → broadcast → wait for receipt.
Inspiration only (do not copy): OPSIE
web3_handler.py— useful for feature ideas (multi-chain, token registry, Uni V2 swaps, gas policy, preview/confirm). Reimplement cleanly for Skillware; no/0xcommands, noinput()loops, no editing Python to add tokens.Problem
Agents need a safe, repeatable way to trade and transfer on EVM chains without hard-coded CLI syntax. Users speak in NL; the skill enforces typed requests, previews, and optional confirmation before signing.
Agent vs skill
intentJSON; ask for missing fields in natural language; show preview; passconfirmed: truewhen user approvesNo regex NLP inside the skill — the agent fills the schema per
instructions.md.Architecture (bundle)
Configuration layers
.envAGENT_WALLET_PRIVATE_KEY,*_RPC_URL, optional API keysconfig.yamlupdate_preferencesor explicit user intentintent/overrideschains.yaml/tokens.yaml/addressbook.yamlDefaults: chain
ethereum; spend asset native ETH when buying unless config or user specifies; gasnormalunless user says low / high / aggressive;confirm_before_send: trueunless config disables it.Example agent flow
resolvewith partial intent → skill returnsmissing_fields: ["spend_asset"],suggested_defaults: { "spend_asset": "usdc" }from configquote→ preview (you give X USDC, get ~10 DEGEN, rate, gas, router)executewithconfirmed: truetx_hash, explorer URL, receipt statusSend example: “Send 10 degen to mom” — resolves token + addressbook label; fills chain/gas from config.
MVP scope
chains.yaml)Out of scope (v1)
.envonly)skill.pySecurity & constitution (manifest)
.env; never in tool args, YAML, or logsmax_trade_usd, chain/token allowlistsReference features from OPSIE (reimplement, don’t port)
Worth studying: multi-chain connect, token dict, address labels, buy/sell path quotes, approve+swap, gas strategy, preview before send,
wait_for_transaction_receipt. Avoid: slash commands, interactive prompts, mutating source for config, hardcoded pub key, legacy-only gas, wrong decimal handling.Ideal Inputs & Outputs
Tool shape
Single skill,
action-basedexecute()(model-agnostic). Agent passes structured JSON; skill returns structured JSON.Actions
resolveintent+ config + YAML registries; return missing fields + suggestionsquotepreviewquote)executeconfirmed: truewhen config says so)transferbalanceswallet_infoupdate_preferencesconfig.yamlchanges only when user explicitly intendsIntent object (partial OK on
resolve){ "side": "buy | sell | send", "chain": "base", "target_asset": "degen", "spend_asset": "usdc", "amount": 10, "amount_kind": "target_out | spend_in", "recipient": "mom", "gas_policy": "normal | low | high | aggressive", "slippage_bps": 50, "confirmed": false }Field notes
chain— default from config (ethereum) if omittedspend_asset— default from config for buys; else native ETH on chaingas_policy— defaultnormalfrom config; per-tx override does not persistrecipient— addressbook label or0x…for sendamount_kind— for “buy 10 Degen”, amount is target_outExample: resolve (missing spend asset)
Input
{ "action": "resolve", "intent": { "side": "buy", "chain": "base", "target_asset": "degen", "amount": 10, "amount_kind": "target_out" } }Output
{ "status": "needs_input", "resolved": { "chain": "base", "target_asset": "degen", "amount": 10, "amount_kind": "target_out", "gas_policy": "normal" }, "missing_fields": ["spend_asset"], "suggested_defaults": { "spend_asset": "usdc" }, "agent_hint": "Ask which token to pay with; suggest USDC from config." }Example: quote + preview
Input
{ "action": "quote", "intent": { "side": "buy", "chain": "base", "target_asset": "degen", "spend_asset": "usdc", "amount": 10, "amount_kind": "target_out" } }Output
{ "status": "ready", "preview": { "side": "buy", "chain": "base", "you_pay": { "asset": "usdc", "amount": "123.45" }, "you_receive": { "asset": "degen", "amount": "10" }, "rate": "1 usdc = 0.081 degen", "gas_estimate": { "policy": "normal", "max_fee_gwei": "0.05" }, "router": "uniswap_v2", "usd": { "pay": 123.45, "receive": 0.10 }, "warnings": ["Rates are indicative; slippage may apply."] }, "quote": { "path": ["0x...", "0x..."], "amount_in_wei": "...", "amount_out_wei": "...", "min_out_wei": "...", "deadline": 1710000000 }, "requires_confirmation": true }Example: execute (after user confirms)
Input
{ "action": "execute", "intent": { "side": "buy", "chain": "base", "target_asset": "degen", "spend_asset": "usdc", "amount": 10, "amount_kind": "target_out" }, "confirmed": true }Output
{ "status": "confirmed", "tx_hash": "0x...", "explorer_url": "https://basescan.org/tx/0x...", "receipt": { "block_number": 123, "gas_used": 250000, "success": true } }Example: transfer
Input
{ "action": "transfer", "intent": { "chain": "base", "target_asset": "degen", "amount": 10, "recipient": "mom" }, "confirmed": true }Output
{ "status": "confirmed", "tx_hash": "0x...", "explorer_url": "https://basescan.org/tx/0x...", "recipient_resolved": "0x..." }config.yaml.example (long-term prefs, no secrets)
Environment variables (manifest
env_vars)AGENT_WALLET_PRIVATE_KEYETHEREUM_RPC_URLBASE_RPC_URLCOINGECKO_API_KEYETHERSCAN_API_KEYPrivate key referenced in config as
private_key_env: AGENT_WALLET_PRIVATE_KEY— never inline.Dependencies
web3≥ 6.xpyyamlrequestsorhttpx(CoinGecko, optional)Acceptance criteria (for implementation PR)
SkillLoader.load_skill("defi/evm_tx_handler")succeedsresolvereturns missing fields + defaults from configquote/previewfor buy and sell on mocked Uni V2 routerexecuterefuses whenconfirm_before_send: trueandconfirmedis falsetransferresolves addressbook labelsconfig.yamlupdate_preferencespersists only explicit preference changespytestpasses with mocked Web3 (no live keys in CI)instructions.md,card.json,docs/skills/evm_tx_handler.mdper skill standardTargeted Models (if applicable)
Model Agnostic (All)