Imported from ggonzalez94/defi-cli (
AGENTS.md). Install upstream withnpx skills add ggonzalez94/defi-cli. Copyright stays with the author.
AGENTS.md
Short guide for agents working on defi-cli.
Project intent
defi-cli is an agent-first DeFi CLI for querying and acting on-chain. Core priorities are:
- stable JSON contract (envelope + fields + deterministic ordering)
- stable exit codes
- canonical IDs/amounts for automation (CAIP + base units)
First 5 minutes
go build -o defi ./cmd/defi
go test ./...
go test -race ./...
go vet ./...
./defi providers list --results-only
./defi lend markets --provider aave --chain 1 --asset USDC --results-only
./defi lend positions --provider aave --chain 1 --address 0x000000000000000000000000000000000000dEaD --type all --limit 3 --results-only
./defi yield opportunities --chain 1 --asset USDC --providers aave,morpho --limit 5 --results-only
./defi yield positions --chain 1 --address 0x000000000000000000000000000000000000dEaD --providers aave,morpho --limit 5 --results-only
Folder structure
cmd/
defi/main.go # CLI entrypoint
internal/
app/runner.go # command wiring, provider routing, cache flow
providers/ # external adapters
aave/ morpho/ moonwell/ # lending + yield (read + execution)
defillama/ # market/yield normalization + fallback + bridge analytics
across/ lifi/ # bridge quotes + lifi execution planning
oneinch/ uniswap/ taikoswap/ tempo/ # swap quotes + execution planning providers
types.go # provider interfaces
execution/ # action persistence + planner helpers + signer abstraction + tx execution
registry/ # canonical execution endpoints/contracts/ABI fragments + default chain RPC map
config/ # defaults + file/env/flags precedence
cache/ # sqlite cache + file lock
id/ # CAIP parsing + amount normalization
model/ # output envelope + domain models
out/ # json/plain rendering and field selection
errors/ # typed errors -> exit codes
schema/ # machine-readable command schema
policy/ # command allowlist
httpx/ # shared HTTP client/retry behavior
.github/workflows/ci.yml # CI (test/vet/build)
.github/workflows/nightly-execution-smoke.yml # nightly execution planning drift checks
.github/workflows/release.yml # tagged release pipeline (GoReleaser)
scripts/install.sh # macOS/Linux installer from GitHub Releases
.goreleaser.yml # cross-platform release artifact config
assets/ # static assets (logo, images)
docs/ # Mintlify docs site
docs.json # Mintlify docs site config
*.mdx + concepts/ guides/ ... # Mintlify docs content pages
README.md # user-facing usage + caveats
Non-obvious but important
- Error output always returns a full envelope, even with
--results-onlyor--select. - Config precedence is
flags > env > config file > defaults. yield --providersexpects provider names (aave,morpho,kamino,moonwell), not protocol categories.- Lending routes by
--provideruse direct protocol adapters (aave,morpho,kamino,moonwell). lend positionscurrently supports--provider aave|morpho|moonwell;kaminodoes not expose positions yet.yield positionscurrently supportsaave|morpho|moonwell;kaminodoes not expose positions yet.lend positions --type allintentionally returns non-overlapping intents (supply,borrow,collateral) for automation-friendly filtering.- Most commands do not require provider API keys.
- Key-gated routes:
swap quote --provider 1inch(DEFI_1INCH_API_KEY),swap quote --provider uniswap(DEFI_UNISWAP_API_KEY),chains assets, andbridge list/bridge detailsvia DefiLlama (DEFI_DEFILLAMA_API_KEY). - Multi-provider command paths require explicit selector choice via
--provider; no implicit defaults. - Tempo quote/planning does not require an API key; execution uses native Tempo type 0x76 transactions via the TempoStepExecutor and currently settles Tempo DEX swaps back to the sender only.
- Tempo Stablecoin DEX swaps are currently USD TIP-20 only; the DEX auto-routes supported pairs through quote-token relationships, so non-USD assets should fail as
unsupportedrather thanunavailable. - TaikoSwap quote/planning does not require an API key; standard EVM/TaikoSwap execution supports
--wallet(OWS, recommended) and--from-address(local signer) (--private-keyoverride,DEFI_PRIVATE_KEY{,_FILE}, keystore envs, or auto-discovered~/.config/defi/key.hex/$XDG_CONFIG_HOME/defi/key.hex). swap quote(on-chain quote providers) and executionplancommands support optional--rpc-urloverrides (swap,bridge,approvals,transfer,lend,yield,rewards);submit/statususe stored action step RPC URLs.- Execution
planandsubmitcommands also support--input-jsonand--input-file(-reads stdin); explicit flags override structured input values. - Standard EVM execution planning is OWS-first: use
--walletas the primary identity input for newbridge|approvals|transfer|lend|yield|rewardsplans and TaikoSwapswap plan. - Swap execution planning validates sender/recipient inputs as EVM hex addresses before building calldata.
--from-addressis the local signer identity input for planning; it produceslegacy_localactions that use local key inputs for submit.schemanow includes inherited flags plus command/flag metadata (required,enum,format,input_modes,auth, and request/response structure hints).- Metadata ownership is split by intent:
internal/registry: canonical execution endpoints/contracts/ABIs and default chain RPC map (used when no--rpc-urlis provided).internal/providers/*/client.go: provider quote/read API base URLs.internal/id/id.go: bootstrap token symbol/address registry for deterministic asset parsing.
- Execution commands currently available:
swap plan|submit|statusbridge plan|submit|status(Across, LiFi)approvals plan|submit|statustransfer plan|submit|statuslend supply|withdraw|borrow|repay plan|submit|status(Aave, Morpho, Moonwell)yield deposit|withdraw plan|submit|status(Aave, Morpho, Moonwell)rewards claim|compound plan|submit|status(Aave)actions list|show|estimate
- Execution builder architecture is intentionally split:
swap/bridgeaction construction is provider capability based (BuildSwapAction/BuildBridgeAction) because route payloads are provider-specific.lend/yield/rewards/approvals/transferaction construction uses internal planners for deterministic contract-call composition.
- All execution
submitcommands can broadcast transactions. - Wallet-backed submit for standard EVM actions uses persisted
wallet_idplusDEFI_OWS_TOKEN; normal OWS-backed submit does not take owner-mode private keys or legacy signer flags. - Execution pre-sign checks enforce bounded ERC-20 approvals by default;
--allow-max-approvalopts into larger approvals when required. - Bridge execution pre-sign checks validate canonical execution targets plus provider settlement metadata/endpoints on covered Across/LiFi source chains by default;
--unsafe-provider-txbypasses these guardrails. - LiFi bridge quote/plan support optional
--from-amount-for-gas(source token base units reserved for destination native gas top-up). - Bridge execution status for Across/LiFi waits for destination settlement (
/deposit/statusor/status) before marking bridge steps complete. - Rewards
--assetsflag accepts comma-separated on-chain addresses used by Aave incentives contracts; structured input accepts a JSON string array. - Aave execution has default pool-address-provider coverage for chain IDs
1,10,137,8453,42161, and43114; override with--pool-address/--pool-address-providerotherwise. - Morpho lend execution requires
--market-id(Morpho market unique key bytes32). - Morpho yield execution requires
--vault-address(Morpho vault contract address). - Moonwell lending/yield uses on-chain RPC reads (no API key required); supported on Base and Optimism.
- Moonwell execution targets mToken contracts (Compound v2 style); use
--pool-addressto specify the mToken directly or let auto-resolution match by underlying asset viaComptroller.getAllMarkets(). - Moonwell's WETH mToken (mWETH) auto-unwraps to native ETH on borrow/withdraw and expects native ETH (not WETH) for supply/repay on some chains. Callers (UIs, automation) must wrap ETH → WETH before calling
repayBorrowor handle the native ETH received fromborrow/redeemUnderlying. The CLI planner currently uses the standard ERC-20 path (approve + call with value=0), so WETH wrapping is the caller's responsibility. - Key requirements are command + provider specific;
providers listis metadata only and should remain callable without provider keys. - Prefer env vars for provider keys in docs/examples; keep config file usage optional and focused on non-secret defaults.
--chainsupports CAIP-2, numeric chain IDs, and aliases; aliases includetempo/tempo mainnet/presto,tempo testnet/moderato,tempo devnet,mantle,megaeth/mega eth/mega-eth,ink,scroll,berachain,gnosis/xdai,linea,sonic,blast,fraxtal,world-chain,celo,taiko/taiko alethia,taiko hoodi/hoodi,zksync,hyperevm/hyper evm/hyper-evm,monad, andcitrea.- Bungee Auto quote calls use deterministic placeholder sender/receiver addresses for quote-only mode (
0x000...001). - Swap quote type defaults to
exact-input;exact-outputcurrently routes through Uniswap and Tempo (--type exact-outputwith--amount-outor--amount-out-decimal). - Swap planning supports Tempo exact-output execution; TaikoSwap remains exact-input only.
- Uniswap quote calls require a real
swapperaddress viaswap quote --from-addressand default to provider auto slippage unlessswap quote --slippage-pctis provided. actions estimatereturns fee-token-denominated estimates for Tempo actions withfee_unitandfee_tokenfields (instead of EIP-1559 native-gas pricing used on EVM chains).--signer temporeads the agent wallet fromtempo wallet -j whoamiand requires the Tempo CLI installed and configured with delegated access keys and expiry checks.- Tempo is a separate execution path: Tempo
swap planuses--from-address(not--wallet), and Tempo submit uses--signer tempo. - Tempo execution uses type 0x76 transactions with batched calls (approve+swap are atomic in a single transaction).
--fee-tokendefaults to USDC.e on Tempo mainnet; applies to Tempo execution commands only.- MegaETH bootstrap symbol parsing currently supports
MEGA,WETH, andUSDT(USDTmaps to the chain'sUSDT0contract address oneip155:4326). Official Mega token list currently has no Ethereum L1MEGAtoken entry. - Symbol parsing depends on the local bootstrap token registry; on chains without registry entries use token address or CAIP-19.
chains gasreturns live EVM gas prices via RPC (EVM-only, no API key, bypasses cache); supports--rpc-urloverride and comma-separated--chainfor parallel multi-chain queries (returns array;--rpc-urldisallowed with multiple chains).- APY values are percentage points (
2.3means2.3%), not ratios. - Morpho can emit extreme APYs in tiny markets; use
--min-tvl-usdin ranking/filters. - Fresh cache hits (
age <= ttl) skip provider calls; once TTL expires, the CLI re-fetches providers and only serves stale data withinmax_staleon temporary provider failures. - Metadata commands (
version,schema,providers list,chains list,chains gas) bypass cache initialization. - Execution commands (
swap|bridge|approvals|transfer|lend|yield|rewards ... plan|submit|status,actions list|show|estimate) bypass cache initialization. - For
lend/yield, unresolved symbols are treated as symbol filters; on chains without bootstrap token entries, prefer token address or CAIP-19 for deterministic matching. - Amounts used for swaps/bridges are base units; keep both base and decimal forms consistent.
- Release artifacts are built on
v*tags via.github/workflows/release.ymland.goreleaser.yml. - Mintlify production docs should use the
docs-livebranch; the release workflow force-syncsdocs-liveonly for stable release tags (non-prerelease). scripts/install.shinstalls the latest tagged release artifact into a writable user-spacePATHdirectory by default (fallback~/.local/bin) and never uses sudo unless explicitly requested.- Docs site local checks (from
docs/):npx --yes mint@4.2.378 validate,npx --yes mint@4.2.378 broken-links, andnpx --yes mint@4.2.378 a11y.
Change patterns
- New provider:
- implement adapter in
internal/providers/<name>/client.go - register routes/info in
internal/app/runner.go - add
httptest-based adapter tests - update README caveats if data quality/semantics differ
- document any command that requires an API key explicitly
- implement adapter in
- Contract changes:
- treat as breaking unless explicitly intended
- update
internal/model+internal/outtests first
- Behavior changes:
- keep cache keys deterministic
- add runner-level tests for routing/fallback/strict mode
- Docs sync for user-facing changes:
- if adding a feature/command or changing behavior, update Mintlify docs + README + CHANGELOG
- if changing output schema/fields/exit codes, update contract/reference docs before merge
- if adding providers/chains/assets/aliases/key requirements, update provider/auth and examples docs
Quality bar
go test ./...passesgo test -race ./...passesgo vet ./...passes- smoke at least one command on each touched provider path
- README updated for user-visible changes
- CHANGELOG updated for user-visible changes
Changelog workflow
- Keep
CHANGELOG.mdin a simple release-notes format with## [Unreleased]at the top. - Add user-facing changes under
Unreleasedusing sections in this order:Added,Changed,Deprecated,Fixed,Docs,Security. - Keep entries concise and action-oriented (what changed for users, not internal refactors unless user impact exists).
- On release, move
Unreleaseditems into## [vX.Y.Z] - YYYY-MM-DDand update compare links at the bottom. - If a section has no updates while editing, use
- None yet.to keep structure stable. - Keep README/AGENTS focused on current behavior; track version-to-version deltas in CHANGELOG/release notes instead of adding temporary in-progress migration notes.
Maintenance note
- Keep
README.md,AGENTS.md,CHANGELOG.md, and Mintlify docs (docs/docs.json+docs/**/*.mdx) aligned when commands, routing, caveats, or release-relevant behavior change.
Do not commit transient binaries like ./defi.