Skip to content

feat: add wallet policy management commands and update documentation - #46

Merged
Zuhwa merged 2 commits into
mainfrom
feat/policy
Jun 16, 2026
Merged

feat: add wallet policy management commands and update documentation#46
Zuhwa merged 2 commits into
mainfrom
feat/policy

Conversation

@Zuhwa

@Zuhwa Zuhwa commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

No description provided.

…nts and improve user guidance for policy changes
@Zuhwa
Zuhwa merged commit 9ccd057 into main Jun 16, 2026
Zuhwa added a commit that referenced this pull request Jun 16, 2026
…46)

* feat: add wallet policy management commands and update documentation

* refactor: update CLI commands to clarify dashboard approval requirements and improve user guidance for policy changes

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>
Zuhwa added a commit that referenced this pull request Jun 22, 2026
* feat(wallet): add Solana wallet support with commands for address, balance, message signing, and transfers

* feat(trade): sign Solana trade legs — ownership proofs (base64) and versioned txs

Wires the trade loop to the backend's new Solana sign actions:
- sigType 'solana-message': Privy signs the raw challenge bytes (no
  envelope); the adapter's base58 output is re-encoded to BASE64 — the
  Treasures ownership-proof contract (base58 is their #1 documented
  rejection cause).
- sigType 'solana-tx': sign the serialized versioned tx WITHOUT
  broadcasting (server/venue broadcast the signed bytes), via the
  adapter's Privy signTransaction (base64 in/out).
- solWallet rides every /plan that could route through Solana: explicit
  sol venue/source, or a tokenized-stock BUY with no venue pinned — the
  backend then quotes both venues and executes the better one. Sells
  stay explicit (the backend can't see which venue holds shares).

Verified against the live Privy signer for Mochi3DSTest: the signature
checks out as raw ed25519 over the exact challenge bytes (local
ed25519.verify — the same check Treasures runs server-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up)

The backend emits a 'solana-instructions' action before a sol-venue
trade when the wallet's native SOL is below the gas floor — a Jupiter
USDC→SOL swap. Run it through the adapter's sponsored sendInstructions
(Alchemy fee payer) so a zero-SOL wallet can bootstrap, mapping the
backend's role strings to AccountRole bitflags and posting back the
broadcast signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Revert "feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up)"

This reverts commit d561aef.

* fix(trade): address Bugbot review on PR #44

- package-lock: re-resolve @virtuals-protocol/acp-node-v2 to the published
  0.1.4 registry tarball instead of the sibling ../acp-node-v2 link, so
  `npm ci` works on machines without that local path (was breaking CI/releases).
- trade: recognize the Privy Solana chain ids (500 devnet / 501 mainnet) in
  isSolanaChainRef, so a swap with --chain-in 501 attaches solWallet.
- trade: route opts.chain through isSolanaChainRef instead of an exact
  `=== "sol"` match, so a Treasures sell with --chain solana (or other casing)
  also attaches solWallet. isSolanaChainRef is now the single source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): attach solWallet for Solana --chain-out too

Bugbot follow-up on PR #44: couldRouteViaSolana covered --chain and
--chain-in but not --chain-out, so a swap/bridge whose destination is
Solana reached /trade/plan without the agent's Solana pubkey — the
recipient the backend needs to route/sign the destination leg. Add the
symmetric isSolanaChainRef(opts.chainOut) check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): don't attach solWallet when a non-sol venue is pinned

Bugbot follow-up on PR #44: the unpinned-buy heuristic fired on --token
alone, so `--token AAPL --amount-usdc 1 --chain eth` still attached
solWallet. With solWallet present the backend quotes both venues and may
pick sol, overriding the user's explicit --chain eth pin. Gate the buy
clause on opts.chain === undefined; an explicit --chain sol still routes
via the isSolanaChainRef(opts.chain) clause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): define unpinned Treasures buy positively (require spend amount)

Bugbot follow-up on PR #44: the negative formulation classified any
--token without --side/--amount-shares/--chain as an unpinned buy, so a
malformed perp like `--token BTC --size 0.01` (missing --side) attached
solWallet. Define a buy positively: --token plus a spend amount
(--amount-usdc on eth, or --amount-in funded from another chain). This
covers both documented buy shapes and excludes perp/incomplete shapes
that carry no spend signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): only swallow genuine no-Solana-wallet errors

Bugbot follow-up on PR #44: the empty catch around getSolanaWalletAddress
flattened every failure (network, auth, agent lookup) into "no wallet",
silently dropping solWallet. Swallow only the NO_SOLANA_WALLET signal, and
only for a speculative unpinned buy; real failures now surface, and an
explicit Solana route (--chain/--chain-in/--chain-out sol) propagates any
error rather than planning a route it can't sign.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: document tokenized-stock trading + Solana routing (README + SKILL)

The Solana/Treasures work added spot tokenized-stock buy/sell, sol-venue
auto-routing, and swaps in/out of Solana — none of which were in the
docs. Adds to both README and SKILL.md:
- intent-routing rows for Solana-source swaps and tokenized-stock spot
- the stock-vs-perp rule (route by FLAG --amount-usdc/-shares vs --side,
  never by the ticker — AAPL is both a stock and an HL equity perp)
- examples: buy with held USDC, buy funded from another chain, sell,
  USDC@sol → USDC@Base
- command-table row + capability description for tokenized stocks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(trade): add `acp trade stock-list` discovery command

Read-only discovery wrapping the backend's GET /trade/instruments:
- no symbol → spot markets { stocks, hlSpot } (tokenized stocks + HL spot)
- with a symbol → every route for that asset, each naming the exact
  `token` ticker to pass (e.g. xyz:AAPL for the equity perp, AAPL spot)

Adds a GET helper (the trade client only had POST). Documents the command
and the funding model in README.md and SKILL.md — USDC is the settlement
currency, not a prerequisite; trades fund from any chain/token and the
backend auto-bridges, so the listing never implies pre-holding USDC on HL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump @virtuals-protocol/acp-node-v2 version to 0.1.5 in package.json and package-lock.json

* fix: add approval gate for trade command

* fix: surface approval urls from wallet gate

* fix: restrict approval url detection

* fix: tighten approval text matching

* fix: surface trade approval errors

* fix: keep approval url change scoped

* fix: mirror approval urls from sdk output

* feat: simplify error message handling

* feat: add wallet policy management commands and update documentation (#46)

* feat: add wallet policy management commands and update documentation

* refactor: update CLI commands to clarify dashboard approval requirements and improve user guidance for policy changes

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>

* feat: enhance wallet balance command to support querying all supporte… (#47)

* feat: enhance wallet balance command to support querying all supported chain

* feat: add native currency resolution for token display in wallet commands

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>

* feat: update wallet balance command to include Solana support and improve documentation

* docs: update HL account status documentation to clarify on-chain token balance support for all sponsored EVM chains and Solana

* feat(wallet): show Treasures tokenized-stock positions in balance

The backend now returns the agent's Treasures stock portfolio under
`data.stocks` on GET /agents/:id/assets. Surface it in the wallet
balance views.

- Add StockPosition type + data.stocks to AgentAssetsResponse (was
  silently dropped before).
- `wallet balance` and `wallet sol balance` now emit `stocks` in --json
  output and render a "Tokenized Stocks" table (TICKER, TOKEN, TOKENS,
  SHARES, USD) in TTY. The full portfolio is shown regardless of the
  queried chain (it isn't tied to the network).
- usd_value (precomputed upstream) preferred; falls back to
  tokens × usd_per_token; nulls render as "—".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(wallet): unify stock USD computation across TTY and piped output

Extract stockUsd(): prefer usd_value, fall back to tokens × usd_per_token,
"—" when unknown. Piped output previously used `usd_value ?? "0"`, showing
$0 for unknown values and disagreeing with the TTY table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(wallet): show stock price, avg entry, and PnL in balance table

The tokenized-stock table only displayed a computed USD value, hiding the
per-position pricing the backend already returns. Surface $/share,
$/token, average entry price, USD value, and unrealized PnL (signed) in
both the TTY table and the piped output. --json was already complete
(verbatim positions passthrough); this brings the human views in line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): --dry-run no longer requires a registered signer

A dry run signs and submits nothing — the server returns a `preview`
action and runTradeLoop returns before any send/sign — yet the CLI eagerly
built the signer adapter (createProviderAdapter), so `--dry-run` failed
with "No signer configured" on agents without a key.

Pass `undefined` for the dry-run provider and assert it only in the
execution branches (send / EVM sign) via requireSigner(), which a dry run
never reaches. Live trades are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): add request timeouts so a stalled call can't hang the trade loop

A live HL-exit froze indefinitely mid-trade: a /trade/next call stalled with the
connection open, and since fetch had no timeout it never returned or threw — so
the trade loop's existing transient-retry never fired and it hung past the
backend's 10-min settle-timeout (the withdraw had settled on Arbitrum, but the
bridge leg was never reached).

- trade.ts post()/get(): add AbortSignal.timeout(120s) to the fetch. A stall now
  throws → `wait`-poll calls retry (existing path), others surface a clean
  REQUEST_TIMEOUT (code "TIMEOUT") instead of hanging.
- api/client.ts: same timeout on the shared ApiClient fetch (every CLI command),
  via a fetchWithTimeout helper.

120s is generous for slow legitimate calls (e.g. a LiFi quote inside /trade/next)
but bounded. Follow-up: auto-resume a timed-out /trade/next by tradeId once the
backend's per-step idempotency is confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(trade): refresh the access token on a 401 mid-trade-loop

A long trade (e.g. an HL-exit's multi-minute settle wait) can outlive the access
token: the loop captures it once at the start and never refreshes, and
resolveToken only refreshes when the token is ALREADY expired — so a token with
a few minutes left passes the start check, then expires mid-loop and the next
/trade/next returns 401, killing the trade (observed live: a ~7-min HL-exit died
401 after the withdraw settled but before the bridge).

- client.ts: export forceTokenRefresh() — mint a new access token via the stored
  refresh token unconditionally (the local expiry check can disagree with the
  server, so re-resolving isn't enough).
- trade.ts post(): on a 401, call the onAuthRefresh callback once and retry with
  the fresh token (not counted as a transient retry).
- runTradeLoop: hold a mutable currentToken and pass an onAuthRefresh that
  force-refreshes and updates it, so the rest of the loop uses the new token.

Pairs with the request-timeout fix: together, long-running trades no longer hang
on a stalled connection or die on an expired token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Andrew Khor <andrew@virtuals.io>
Co-authored-by: ai-virtual-b <bryan@virtuals.io>
Co-authored-by: Zuhwa <chooizuhwa@gmail.com>
Zuhwa added a commit that referenced this pull request Jun 22, 2026
* feat(wallet): add Solana wallet support with commands for address, balance, message signing, and transfers

* feat(trade): sign Solana trade legs — ownership proofs (base64) and versioned txs

Wires the trade loop to the backend's new Solana sign actions:
- sigType 'solana-message': Privy signs the raw challenge bytes (no
  envelope); the adapter's base58 output is re-encoded to BASE64 — the
  Treasures ownership-proof contract (base58 is their #1 documented
  rejection cause).
- sigType 'solana-tx': sign the serialized versioned tx WITHOUT
  broadcasting (server/venue broadcast the signed bytes), via the
  adapter's Privy signTransaction (base64 in/out).
- solWallet rides every /plan that could route through Solana: explicit
  sol venue/source, or a tokenized-stock BUY with no venue pinned — the
  backend then quotes both venues and executes the better one. Sells
  stay explicit (the backend can't see which venue holds shares).

Verified against the live Privy signer for Mochi3DSTest: the signature
checks out as raw ed25519 over the exact challenge bytes (local
ed25519.verify — the same check Treasures runs server-side).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up)

The backend emits a 'solana-instructions' action before a sol-venue
trade when the wallet's native SOL is below the gas floor — a Jupiter
USDC→SOL swap. Run it through the adapter's sponsored sendInstructions
(Alchemy fee payer) so a zero-SOL wallet can bootstrap, mapping the
backend's role strings to AccountRole bitflags and posting back the
broadcast signature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Revert "feat(trade): run sponsored solana-instructions actions (native-SOL gas top-up)"

This reverts commit d561aef.

* fix(trade): address Bugbot review on PR #44

- package-lock: re-resolve @virtuals-protocol/acp-node-v2 to the published
  0.1.4 registry tarball instead of the sibling ../acp-node-v2 link, so
  `npm ci` works on machines without that local path (was breaking CI/releases).
- trade: recognize the Privy Solana chain ids (500 devnet / 501 mainnet) in
  isSolanaChainRef, so a swap with --chain-in 501 attaches solWallet.
- trade: route opts.chain through isSolanaChainRef instead of an exact
  `=== "sol"` match, so a Treasures sell with --chain solana (or other casing)
  also attaches solWallet. isSolanaChainRef is now the single source of truth.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): attach solWallet for Solana --chain-out too

Bugbot follow-up on PR #44: couldRouteViaSolana covered --chain and
--chain-in but not --chain-out, so a swap/bridge whose destination is
Solana reached /trade/plan without the agent's Solana pubkey — the
recipient the backend needs to route/sign the destination leg. Add the
symmetric isSolanaChainRef(opts.chainOut) check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): don't attach solWallet when a non-sol venue is pinned

Bugbot follow-up on PR #44: the unpinned-buy heuristic fired on --token
alone, so `--token AAPL --amount-usdc 1 --chain eth` still attached
solWallet. With solWallet present the backend quotes both venues and may
pick sol, overriding the user's explicit --chain eth pin. Gate the buy
clause on opts.chain === undefined; an explicit --chain sol still routes
via the isSolanaChainRef(opts.chain) clause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): define unpinned Treasures buy positively (require spend amount)

Bugbot follow-up on PR #44: the negative formulation classified any
--token without --side/--amount-shares/--chain as an unpinned buy, so a
malformed perp like `--token BTC --size 0.01` (missing --side) attached
solWallet. Define a buy positively: --token plus a spend amount
(--amount-usdc on eth, or --amount-in funded from another chain). This
covers both documented buy shapes and excludes perp/incomplete shapes
that carry no spend signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): only swallow genuine no-Solana-wallet errors

Bugbot follow-up on PR #44: the empty catch around getSolanaWalletAddress
flattened every failure (network, auth, agent lookup) into "no wallet",
silently dropping solWallet. Swallow only the NO_SOLANA_WALLET signal, and
only for a speculative unpinned buy; real failures now surface, and an
explicit Solana route (--chain/--chain-in/--chain-out sol) propagates any
error rather than planning a route it can't sign.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs: document tokenized-stock trading + Solana routing (README + SKILL)

The Solana/Treasures work added spot tokenized-stock buy/sell, sol-venue
auto-routing, and swaps in/out of Solana — none of which were in the
docs. Adds to both README and SKILL.md:
- intent-routing rows for Solana-source swaps and tokenized-stock spot
- the stock-vs-perp rule (route by FLAG --amount-usdc/-shares vs --side,
  never by the ticker — AAPL is both a stock and an HL equity perp)
- examples: buy with held USDC, buy funded from another chain, sell,
  USDC@sol → USDC@Base
- command-table row + capability description for tokenized stocks

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(trade): add `acp trade stock-list` discovery command

Read-only discovery wrapping the backend's GET /trade/instruments:
- no symbol → spot markets { stocks, hlSpot } (tokenized stocks + HL spot)
- with a symbol → every route for that asset, each naming the exact
  `token` ticker to pass (e.g. xyz:AAPL for the equity perp, AAPL spot)

Adds a GET helper (the trade client only had POST). Documents the command
and the funding model in README.md and SKILL.md — USDC is the settlement
currency, not a prerequisite; trades fund from any chain/token and the
backend auto-bridges, so the listing never implies pre-holding USDC on HL.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: bump @virtuals-protocol/acp-node-v2 version to 0.1.5 in package.json and package-lock.json

* fix: add approval gate for trade command

* fix: surface approval urls from wallet gate

* fix: restrict approval url detection

* fix: tighten approval text matching

* fix: surface trade approval errors

* fix: keep approval url change scoped

* fix: mirror approval urls from sdk output

* feat: simplify error message handling

* feat: add wallet policy management commands and update documentation (#46)

* feat: add wallet policy management commands and update documentation

* refactor: update CLI commands to clarify dashboard approval requirements and improve user guidance for policy changes

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>

* feat: enhance wallet balance command to support querying all supporte… (#47)

* feat: enhance wallet balance command to support querying all supported chain

* feat: add native currency resolution for token display in wallet commands

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>

* feat: update wallet balance command to include Solana support and improve documentation

* docs: update HL account status documentation to clarify on-chain token balance support for all sponsored EVM chains and Solana

* feat(wallet): show Treasures tokenized-stock positions in balance

The backend now returns the agent's Treasures stock portfolio under
`data.stocks` on GET /agents/:id/assets. Surface it in the wallet
balance views.

- Add StockPosition type + data.stocks to AgentAssetsResponse (was
  silently dropped before).
- `wallet balance` and `wallet sol balance` now emit `stocks` in --json
  output and render a "Tokenized Stocks" table (TICKER, TOKEN, TOKENS,
  SHARES, USD) in TTY. The full portfolio is shown regardless of the
  queried chain (it isn't tied to the network).
- usd_value (precomputed upstream) preferred; falls back to
  tokens × usd_per_token; nulls render as "—".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(wallet): unify stock USD computation across TTY and piped output

Extract stockUsd(): prefer usd_value, fall back to tokens × usd_per_token,
"—" when unknown. Piped output previously used `usd_value ?? "0"`, showing
$0 for unknown values and disagreeing with the TTY table.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(wallet): show stock price, avg entry, and PnL in balance table

The tokenized-stock table only displayed a computed USD value, hiding the
per-position pricing the backend already returns. Surface $/share,
$/token, average entry price, USD value, and unrealized PnL (signed) in
both the TTY table and the piped output. --json was already complete
(verbatim positions passthrough); this brings the human views in line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): --dry-run no longer requires a registered signer

A dry run signs and submits nothing — the server returns a `preview`
action and runTradeLoop returns before any send/sign — yet the CLI eagerly
built the signer adapter (createProviderAdapter), so `--dry-run` failed
with "No signer configured" on agents without a key.

Pass `undefined` for the dry-run provider and assert it only in the
execution branches (send / EVM sign) via requireSigner(), which a dry run
never reaches. Live trades are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(trade): add request timeouts so a stalled call can't hang the trade loop

A live HL-exit froze indefinitely mid-trade: a /trade/next call stalled with the
connection open, and since fetch had no timeout it never returned or threw — so
the trade loop's existing transient-retry never fired and it hung past the
backend's 10-min settle-timeout (the withdraw had settled on Arbitrum, but the
bridge leg was never reached).

- trade.ts post()/get(): add AbortSignal.timeout(120s) to the fetch. A stall now
  throws → `wait`-poll calls retry (existing path), others surface a clean
  REQUEST_TIMEOUT (code "TIMEOUT") instead of hanging.
- api/client.ts: same timeout on the shared ApiClient fetch (every CLI command),
  via a fetchWithTimeout helper.

120s is generous for slow legitimate calls (e.g. a LiFi quote inside /trade/next)
but bounded. Follow-up: auto-resume a timed-out /trade/next by tradeId once the
backend's per-step idempotency is confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(trade): refresh the access token on a 401 mid-trade-loop

A long trade (e.g. an HL-exit's multi-minute settle wait) can outlive the access
token: the loop captures it once at the start and never refreshes, and
resolveToken only refreshes when the token is ALREADY expired — so a token with
a few minutes left passes the start check, then expires mid-loop and the next
/trade/next returns 401, killing the trade (observed live: a ~7-min HL-exit died
401 after the withdraw settled but before the bridge).

- client.ts: export forceTokenRefresh() — mint a new access token via the stored
  refresh token unconditionally (the local expiry check can disagree with the
  server, so re-resolving isn't enough).
- trade.ts post(): on a 401, call the onAuthRefresh callback once and retry with
  the fresh token (not counted as a transient retry).
- runTradeLoop: hold a mutable currentToken and pass an onAuthRefresh that
  force-refreshes and updates it, so the rest of the loop uses the new token.

Pairs with the request-timeout fix: together, long-running trades no longer hang
on a stalled connection or die on an expired token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: document chain-out defaulting + Solana buy routing

The trade routing tables in README and SKILL only covered Solana→EVM and
treated --chain-out as always-required. Reflect the shipped behavior:

- --chain-out is optional, defaulting to --chain-in (omit to stay on the
  source chain); single-chain tokens like `sol` infer their own chain.
- Add the EVM→Solana (buy SOL/SPL) and Solana→Solana routing rows + a
  buy-SOL example; delivery recipient is auto-derived from the agent's
  Solana wallet, so --recipient is only for sending elsewhere.
- Fix the SKILL swap flag-reference row (was "both chains EVM", --chain-out
  required) to cover Solana and mark --chain-out/--recipient optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: tell agents acp trade is one command for multi-leg routes

Agents reading the trade docs could miss that a single `acp trade` is
decomposed into a full multi-leg route (e.g. HL sell → transfer → withdraw
→ bridge+swap) and try to chain `deposit`/`spot`/`bridge`/`swap` calls
themselves. Add an explicit "one command, never chain trades yourself"
callout to both README and SKILL, a real multi-leg example (PURR@HL →
ETH@Base), and a note that a multi-leg route can take minutes to settle
(not a hang) so agents don't re-issue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Zuhwa <zuhwa@virtuals.io>
Co-authored-by: brianna <bpschang@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Andrew Khor <andrew@virtuals.io>
Co-authored-by: ai-virtual-b <bryan@virtuals.io>
Co-authored-by: miratisu_virtuals <89718498+psmiratisu@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants