| name | actions-cli |
| description | Invoke the Actions SDK from the shell - query assets/chains, derive an EOA address from a PRIVATE_KEY env var, read balances. Use when an agent needs to interact with the Actions SDK without embedding TypeScript. |
| compatibility | Requires Node.js >=18 and the PRIVATE_KEY env var for wallet-scoped commands. |
Actions CLI - Agent Skill
Invocation
Spawn the actions binary as a subprocess. Always pass --json (as
the first flag) - the default output is human-readable and not intended
for parsing. With --json, stdout is a bare JSON document on success
and stderr is the error envelope on failure.
actions --json assets
actions --json wallet balance --chain base-sepolia
Command tree
actions assets - configured asset allowlist.
actions chains - configured chain shortnames + IDs.
actions lend markets [--asset <symbol>] [--chain <name> | --chain-id <id>] -
lending markets across configured providers, optionally filtered to one
asset and/or one chain (no wallet).
actions lend market --market <name> - inspect one market by name
(no wallet).
actions borrow markets [--collateral <symbol>] [--borrow-asset <symbol>] [--chain <name> | --chain-id <id>] - borrow markets across configured
providers, optionally filtered (no wallet).
actions borrow market --market <name> - inspect one borrow market
by name (no wallet).
actions borrow position --market <name> --wallet <address> -
read any wallet's borrow position (no PRIVATE_KEY required).
actions swap markets [--chain <name>] - all swap markets across
configured providers (no wallet).
actions swap market --pool <id> --chain <name> - inspect one swap
market by pool id (no wallet).
actions swap quote --in <symbol> --out <symbol> (--amount-in <n> | --amount-out <n>) --chain <name> [--provider uniswap|velodrome] [--slippage <pct>] - best quote
(no wallet).
actions swap quotes ... - same flag set; returns every provider's
quote sorted best price first.
actions ens address <name> - forward-resolve an ENS name to its
address on Ethereum mainnet (no wallet).
actions ens name <address> - reverse-resolve an address to its
primary ENS name, or name: null when none is set (no wallet).
actions ens info <input> - fetch the standard ENS profile text
records for a name or address (no wallet).
actions wallet address - EOA address derived from PRIVATE_KEY.
actions wallet balance [--chain <name> | --chain-id <id>] - balances
per chain + asset; the chain flags are mutually exclusive.
actions wallet lend position --market <name> - the wallet's current
balance and shares in a market.
actions wallet lend open --market <name> --amount <n> [--approval-mode <exact|max>] -
supply assets to a market. --approval-mode max approves max-uint to
amortise approvals across future supplies (default: exact).
actions wallet lend close --market <name> (--amount <n> | --max) -
withdraw assets. Pass --max to withdraw the wallet's full balance in
the market (the CLI fetches the position first; subject to inflight
interest accrual).
actions wallet borrow position --market <name> - the wallet's current
collateral, debt, LTV, and health factor in a borrow market.
actions wallet borrow open --market <name> --borrow-amount <n> [--collateral-amount <n>] [--approval-mode <exact|max>] - borrow
against existing or newly-deposited collateral. No --max (open path
only accepts strict amounts).
actions wallet borrow close --market <name> [--borrow-amount <n> | --borrow-max] [--collateral-amount <n> | --collateral-max] - unwind a
position. Each leg is independently xor'd; the borrow leg is required.
--*-max resolves on-chain at dispatch time so interest-accrual dust
doesn't strand the position.
actions wallet borrow deposit-collateral --market <name> --amount <n> [--approval-mode <exact|max>] - top up collateral without changing
debt.
actions wallet borrow withdraw-collateral --market <name> (--amount <n> | --max) - pull collateral back without touching debt.
actions wallet borrow repay --market <name> (--amount <n> | --max) [--approval-mode <exact|max>] - repay debt without touching collateral.
actions wallet swap execute --in <symbol> --out <symbol> (--amount-in <n> | --amount-out <n>) --chain <name> [--provider uniswap|velodrome] [--slippage <pct>] - execute a swap
on the resolved chain.
Wallet model
The CLI derives a viem LocalAccount from PRIVATE_KEY and wraps it in
an EOA-backed Actions wallet via
actions.wallet.toActionsWallet(localAccount). No smart wallet, no
bundler, no ERC-4337 UserOps - the signer pays gas directly. For the
demo, fund the EOA with testnet ETH on Base Sepolia.
Resolution rules
- Assets - pass the
metadata.symbol value from the allowlist
(e.g. USDC_DEMO, OP_DEMO, ETH). Case-insensitive. Run
actions --json assets for the current list.
- Chains - pass a shortname (
base-sepolia, op-sepolia) via
--chain, or a numeric id via --chain-id (mutually exclusive).
Both flags accept a comma-separated list to scope the SDK fan-out
to multiple chains. Run actions --json chains for the current
list.
- Markets (lend) - pass the market
name from the config allowlist
(e.g. Gauntlet USDC, Aave ETH). Case-insensitive; whitespace
and hyphens are ignored, so gauntlet-usdc and gauntletusdc
resolve to the same entry. The market entry carries its own chain
and asset, so no --chain is needed.
- Markets (borrow) - same name-based resolution as lend, plus
/
is stripped (so Demo dUSDC / OP and demo-dusdc-op collapse to the
same key). Borrow market identifiers are discriminated unions (e.g.
{ kind: 'morpho-blue', marketId: '0x...', chainId: ... }); the CLI
forwards the resolved config to the SDK so a future second provider
variant adds no CLI work.
- Markets (swap) - addressed pair-wise via
--in/--out/--chain for
quotes and execution. --pool <id> is only used for direct
swap market lookups; the poolId surfaces in swap markets.
- Amounts - human-readable decimal numbers (e.g.
10, 0.5).
The SDK converts to wei using the asset's decimals.
- Slippage -
--slippage accepts a percent (e.g. 0.5 for 0.5%);
the CLI converts to the SDK's decimal form internally.
- Amount direction - exactly one of
--amount-in (exact-in) or
--amount-out (exact-out) is required for swap quote,
swap quotes, and wallet swap execute.
- Provider selection -
--provider uniswap|velodrome forces a
provider and skips routing. Omit to let the SDK pick the best
available.
Presentation hints (for LLM/agent callers)
These are rules for rendering CLI output to humans, not rules for the
CLI itself.
- Chain labels - only when disambiguating. When showing a list
(balances, markets, positions, pools), mention the chain only for
entries that share their name/symbol/market with another entry on a
different chain in the same response. If every row is uniquely
identifiable by its name alone, drop the chain label. Count chain
occurrences after skipping zero balances. Example: two chains
in the raw payload, but only one has a non-zero balance of
X -
render as X <amount> with no chain. When the user explicitly scopes
a question to one chain, still omit the label.
- Zero rows - skip. Don't render zero balances, empty positions,
or pools with no meaningful data, unless the user specifically asked
about that zero value ("do I have any X on op-sepolia").
- Raw addresses - omit by default. Wallet/pool/market/contract
addresses in a listing add noise. Show them only when the user asks
for them explicitly, and even then truncate (
0xabc…def).
Output
With --json:
- Success: bare JSON document on stdout, exit 0. No envelope (matches
gh and AWS CLI conventions).
- Error: JSON
{error, code, retryable, retry_after_ms?, details?} on
stderr, non-zero exit. retryable: true means the caller may retry
(typically network failures). retry_after_ms is present when a
specific back-off is recommended. details is redacted - bundler
URLs with API keys, signer metadata, and raw viem request bodies are
scrubbed.
Without --json (default):
- Success: plain text on stdout intended for human reading. Not stable
across versions.
- Error:
Error (<code>): <message> on stderr, exit code per the table
below.
Balance semantics
Within a single actions wallet balance call, the SDK fans out via
Promise.all over (asset x chain), so any single failing RPC rejects
the whole call with a network error. Retries may succeed on a
different call - do not assume per-chain isolation.
To shrink the failure surface, scope the call with --chain or
--chain-id (both accept a comma-separated list). The SDK only
queries the chains you pass.
Lend semantics
wallet lend open and wallet lend close emit a structured envelope
on stdout:
{
"action": "open" | "close",
"market": { "name": "...", "address": "0x...", "chainId": ..., "provider": "..." },
"asset": { "symbol": "..." },
"amount": <number>,
"transactions": [ { "transactionHash": "0x...", "status": "success", ... } ]
}
transactions is always an array. On EOA the SDK sends approval +
position as two sequential transactions when an approval is required,
so open returns 1-2 receipts and close returns 1. Bigint receipt
fields (blockNumber, gasUsed) are stringified.
A receipt with status: "reverted" is normalised to a code: "onchain"
error envelope on stderr (exit 5), so callers do not need to inspect
receipt status to detect failure.
wallet lend position returns the SDK LendMarketPosition shape
verbatim: { balance, balanceFormatted, shares, sharesFormatted, marketId }
with bigint fields stringified.
lend markets and lend market return the SDK LendMarket shape(s)
verbatim: { marketId, name, asset, supply, apy, metadata }. These do
not require PRIVATE_KEY.
NL -> command examples:
- "what markets can I lend in" ->
actions --json lend markets
- "supply 10 USDC to Gauntlet" ->
actions --json wallet lend open --market gauntlet-usdc --amount 10
- "deposit 0.5 ETH into Aave on op-sepolia" ->
actions --json wallet lend open --market aave-eth --amount 0.5
- "withdraw 5 USDC from Gauntlet" ->
actions --json wallet lend close --market gauntlet-usdc --amount 5
- "how much do I have in Gauntlet" ->
actions --json wallet lend position --market gauntlet-usdc
Borrow semantics
The wallet-scoped write verbs (open, close, deposit-collateral,
withdraw-collateral, repay) emit a structured envelope on stdout:
{
"action": "open" | "close" | "depositCollateral" | "withdrawCollateral" | "repay",
"market": {
"name": "...",
"marketId": { "kind": "morpho-blue", "marketId": "0x...", "chainId": ... },
"chainId": ...,
"provider": "morpho"
},
"borrowAmount": <number | "max">,
"collateralAmount": <number | "max">,
"transactions": [ { "transactionHash": "0x...", "status": "success", ... } ],
"ltv": <number | null>,
"healthFactor": <number | null>,
"liquidationPriceFormatted": "<string>"
}
borrowAmount / collateralAmount echo what the verb touched (the
inverse pair is omitted; e.g. repay reports only borrowAmount). The
literal string "max" means the SDK resolved the full balance at
dispatch time. ltv and healthFactor come from the SDK's
positionAfter snapshot and are null when the action left no debt
(divide-by-zero guard); they are omitted when the SDK did not surface a
positionAfter. transactions is always an array, normalised the same
way as lend / swap.
A receipt with status: "reverted" is normalised to a code: "onchain"
error envelope on stderr (exit 5).
actions borrow position --market <name> --wallet <address> reads any wallet's
position without needing PRIVATE_KEY. wallet borrow position uses the
connected wallet.
borrow markets / borrow market / both position commands return the
SDK shapes verbatim with bigints stringified. Position fields ltv and
healthFactor are null when there is no outstanding debt.
NL -> command examples:
- "what borrow markets are available" ->
actions --json borrow markets
- "borrow 1 OP against 2 USDC of collateral on the demo market" ->
actions --json wallet borrow open --market demo-dusdc-op --borrow-amount 1 --collateral-amount 2
- "close my borrow position completely" ->
actions --json wallet borrow close --market demo-dusdc-op --borrow-max --collateral-max
- "repay all my debt on demo dUSDC/OP" ->
actions --json wallet borrow repay --market demo-dusdc-op --max
- "withdraw all collateral from demo dUSDC/OP" ->
actions --json wallet borrow withdraw-collateral --market demo-dusdc-op --max
- "top up 5 USDC of collateral on demo dUSDC/OP" ->
actions --json wallet borrow deposit-collateral --market demo-dusdc-op --amount 5
- "what's the health factor of 0x... on demo dUSDC/OP" ->
actions --json borrow position --market demo-dusdc-op --wallet 0x...
- "how am I doing in demo dUSDC/OP" ->
actions --json wallet borrow position --market demo-dusdc-op
Swap semantics
swap quote returns the SDK SwapQuote shape verbatim: amounts (both
display and Raw bigint), price + price-impact, slippage (decimal),
deadline, and pre-built execution calldata. swap quotes is the
multi-provider variant sorted by amountOutRaw desc.
wallet swap execute emits a structured envelope on stdout:
{
"action": "execute",
"assetIn": { "symbol": "USDC_DEMO" },
"assetOut": { "symbol": "OP_DEMO" },
"amountIn": 5, "amountOut": 4.9,
"amountInRaw": "5000000",
"amountOutRaw": "4900000000000000000",
"price": 0.98, "priceImpact": 0.001,
"transactions": [ { "transactionHash": "0x...", "status": "success", ... } ]
}
transactions is always an array. EOA execution can fan out into
token-approval + Permit2-approval + swap (up to 3 receipts); smart
wallets collapse to a single UserOp receipt. A receipt with
status: "reverted" is normalised to code: "onchain" exit 5.
wallet swap execute --recipient <addr|ens> accepts either a 0x address
or an ENS name. Pass ENS names through this field when the user wants the
swap output sent somewhere other than the connected wallet.
NL -> command examples:
- "swap 5 USDC for OP on Unichain" ->
actions --json wallet swap execute --in USDC_DEMO --out OP_DEMO --amount-in 5 --chain unichain
- "buy 1 OP with USDC" ->
actions --json wallet swap execute --in USDC_DEMO --out OP_DEMO --amount-out 1 --chain unichain
- "swap 1 USDC for OP and send it to vitalik.eth" ->
actions --json wallet swap execute --in USDC_DEMO --out OP_DEMO --amount-in 1 --chain unichain --recipient vitalik.eth
- "send swap output to 0xd8dA...96045" ->
actions --json wallet swap execute --in USDC_DEMO --out OP_DEMO --amount-in 1 --chain unichain --recipient 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
- "what's the best price for 100 USDC -> OP" ->
actions --json swap quote --in USDC_DEMO --out OP_DEMO --amount-in 100 --chain unichain
- "compare provider quotes" ->
actions --json swap quotes --in USDC_DEMO --out OP_DEMO --amount-in 100 --chain unichain
- "execute on Velodrome with 1% slippage" ->
actions --json wallet swap execute --in USDC_DEMO --out OP_DEMO --amount-in 100 --chain unichain --provider velodrome --slippage 1
ENS semantics
Read-only name resolution on Ethereum mainnet (chain ID 1). No
PRIVATE_KEY is required. Setting MAINNET_RPC_URL uses an
operator-configured mainnet RPC; when it is unset, the SDK's public RPC
fallback is used and the CLI prints a warning to stderr.
address takes an ENS name (must be dot-separated, e.g. vitalik.eth)
and emits { name, address }. A non-name input (raw address, bare
label) exits validation (2).
name takes a 0x address and emits { address, name }, where
name is null when the address has no primary ENS record. A
non-address input exits validation (2).
info takes either a name or an address and emits the SDK EnsInfo
shape verbatim: the standard ENSIP-5 / ENSIP-18 profile text records
(avatar, display, description, url, email, keywords,
twitter, github, discord, reddit), each string or null. An
all-null result is a normal success (the name exists but set no
records), not an error.
Setting MAINNET_RPC_URL adds mainnet to the CLI's shared chain set, so
it also becomes a valid --chain mainnet / --chain-id 1 target and is
included in the default wallet balance fan-out. The demo lend/borrow/swap
markets are testnet-only, so this affects reads (balances), not write targets.
NL -> command examples:
- "what address is vitalik.eth" ->
actions --json ens address vitalik.eth
- "resolve vitalik.eth" ->
actions --json ens address vitalik.eth
- "what's the ENS name for 0xd8dA...96045" ->
actions --json ens name 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
- "show vitalik.eth's profile" ->
actions --json ens info vitalik.eth
- "get the ENS records for 0xd8dA...96045" ->
actions --json ens info 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
RPC trust
*_RPC_URL env vars must point to operator-trusted endpoints. A
malicious RPC can return fake balance data, which will confuse the
caller. The same trust concern applies to MAINNET_RPC_URL for ENS
reads: a fake RPC can return a wrong address for a name. If it is unset,
the SDK public mainnet fallback is used and the CLI warns on stderr.
Exit codes
| Code | Meaning | Retryable |
|---|
| 0 | Success | - |
| 1 | Unknown error | false |
| 2 | Validation (bad input) | false |
| 3 | Config error (missing env, malformed) | false |
| 4 | Network error (RPC, timeout) | true |
| 5 | Onchain error (revert, UserOp failure) | false (†) |
(†) Specific onchain sub-classes (nonce conflicts, gas underpricing)
may set retryable: true via the retryableOverride mechanism. Treat
retryable as the source of truth; the table row shows the default.
Unknown commands
Typos (actions nonsense) exit 1 with commander's default plain-text
error on stderr - not the JSON error envelope. This distinction is
deliberate: the JSON envelope is only emitted for errors thrown from
within a registered handler.