Skill: canuc/wayfinder (version 0.4.1)

--- name: wayfinder description: DeFi trading, yield strategies, and portfolio management via the Wayfinder Paths CLI (`poetry run wayfinder`). Use when the user wants to check balances, swap tokens, bridge assets, trade perps, trade prediction markets (Polymarket), run automated yield strategies (stablecoin yield, basis trading, Moonwell loops, HyperLend, Boros HYPE), manage wallets, discover DeFi pools, look up token metadata, manage LP positions (Uniswap V3 / ProjectX), or execute one-off DeFi scripts. Supports Ethereum, Base, Arbitrum, Polygon, BSC, Avalanche, Plasma, and HyperEVM via protocol adapters. metadata: {"openclaw":{"emoji":"🧭","homepage":"https://github.com/WayfinderFoundation/wayfinder-paths-sdk","requires":{"bins":["poetry"]},"install":[{"id":"brew","kind":"brew","formula":"poetry","bins":["poetry"],"label":"Install poetry"}]}} --- # Wayfinder DeFi trading, yield strategies, and portfolio management powered by [poetry run wayfinder Paths](https://github.com/WayfinderFoundation/wayfinder-paths-sdk). ## Pre-Flight Check Before running any commands, verify that poetry run wayfinder Paths is installed and reachable: ```bash # SDK location (override by setting WAYFINDER_SDK_PATH) export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}" # Check if wayfinder-paths-sdk directory exists if [ ! -d "$WAYFINDER_SDK_PATH" ]; then echo "ERROR: wayfinder-paths-sdk is not installed at: $WAYFINDER_SDK_PATH" echo "Set WAYFINDER_SDK_PATH or run the First-Time Setup below." exit 1 fi # Config path (override by setting WAYFINDER_CONFIG_PATH) export WAYFINDER_CONFIG_PATH="${WAYFINDER_CONFIG_PATH:-$WAYFINDER_SDK_PATH/config.json}" # Check if the config exists if [ ! -f "$WAYFINDER_CONFIG_PATH" ]; then echo "ERROR: config not found at $WAYFINDER_CONFIG_PATH. Run the First-Time Setup below." exit 1 fi # Check if the CLI is functional cd "$WAYFINDER_SDK_PATH" if ! poetry run wayfinder --help > /dev/null 2>&1; then echo "ERROR: poetry run wayfinder CLI is not working. Run 'cd $WAYFINDER_SDK_PATH && poetry install' to fix." exit 1 fi echo "poetry run wayfinder Paths is installed and ready." ``` If either check fails, follow the **First-Time Setup** instructions below before proceeding. ## Quick Start ### First-Time Setup **Important:** The SDK must be installed from GitHub via `git clone`. Do NOT install from PyPI (`pip install wayfinder-paths` will not work). **Before starting:** You need a Wayfinder API key (format: `wk_...`). Get one at **https://strategies.wayfinder.ai**. The guided setup will prompt you for this key. ```bash # Clone wayfinder-paths-sdk from GitHub (required — do NOT pip install) export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}" if [ ! -d "$WAYFINDER_SDK_PATH" ]; then git clone https://github.com/WayfinderFoundation/wayfinder-paths-sdk.git "$WAYFINDER_SDK_PATH" fi cd "$WAYFINDER_SDK_PATH" poetry install # Run guided setup (creates/updates config.json + local dev wallets + MCP config) # You will need your API key from https://strategies.wayfinder.ai (format: wk_...) python3 scripts/setup.py ``` **Wallet security:** - **NEVER output private keys or seed phrases into the conversation.** These are secrets — they must stay on the machine, never in chat. - For a long-running bot, prefer a seed phrase stored in your backend/secret manager rather than generating random wallets on the server. - On first-time setup, the user should retrieve the seed phrase directly from their machine or secret manager. Only offer to display the seed phrase if the user explicitly confirms they cannot access the machine to retrieve it themselves. - See `references/setup.md` for detailed wallet setup instructions. ### Verify Setup ```bash export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}" export WAYFINDER_CONFIG_PATH="${WAYFINDER_CONFIG_PATH:-$WAYFINDER_SDK_PATH/config.json}" cd "$WAYFINDER_SDK_PATH" poetry run wayfinder resource wayfinder://strategies poetry run wayfinder resource wayfinder://wallets poetry run wayfinder resource wayfinder://balances/main ``` ## Command Reference All commands should be run from `$WAYFINDER_SDK_PATH` and require `WAYFINDER_CONFIG_PATH` (default: `$WAYFINDER_SDK_PATH/config.json`). All responses return `{"ok": true, "result": {...}}` on success or `{"ok": false, "error": {"code": "...", "message": "..."}}` on failure. --- ### `resource` — Read MCP resources by URI Read-only access to adapters, strategies, wallets, balances, tokens, and Hyperliquid market data via URI-based resources. Use `--list` to see all available resources and templates. **Asset/data sourcing rule:** When the user asks you to look up token/pool/market/protocol data, first use Wayfinder’s adapter/strategy discovery resources (`poetry run wayfinder resource wayfinder://adapters`, `wayfinder://adapters/{name}`, `wayfinder://strategies`, `wayfinder://tokens/*`). Only fall back to other methods if Wayfinder doesn’t expose the required data or the user explicitly asks. ```bash # List all available resources and templates poetry run wayfinder resource --list ``` #### Static Resources | URI | Description | |-----|-------------| | `wayfinder://adapters` | List all adapters with capabilities | | `wayfinder://strategies` | List all strategies with adapter dependencies | | `wayfinder://wallets` | List all configured wallets | | `wayfinder://hyperliquid/prices` | All Hyperliquid mid prices | | `wayfinder://hyperliquid/markets` | Perp market metadata, funding rates, and asset contexts | | `wayfinder://hyperliquid/spot-assets` | Spot asset metadata | ```bash poetry run wayfinder resource wayfinder://adapters poetry run wayfinder resource wayfinder://strategies poetry run wayfinder resource wayfinder://wallets poetry run wayfinder resource wayfinder://hyperliquid/prices poetry run wayfinder resource wayfinder://hyperliquid/markets poetry run wayfinder resource wayfinder://hyperliquid/spot-assets ``` #### Resource Templates | URI Template | Description | |--------------|-------------| | `wayfinder://adapters/{name}` | Describe a single adapter (e.g. `moonwell_adapter`) | | `wayfinder://strategies/{name}` | Describe a single strategy (e.g. `stablecoin_yield_strategy`) | | `wayfinder://wallets/{label}` | Get a single wallet by label | | `wayfinder://balances/{label}` | Enriched multi-chain balances for a wallet | | `wayfinder://activity/{label}` | Recent transaction activity for a wallet | | `wayfinder://tokens/search/{chain_code}/{query}` | **Fuzzy token search** (hits `/tokens/fuzzy/`) — ALWAYS use this first | | `wayfinder://tokens/resolve/{query}` | Resolve a token by known ID (hits `/tokens/detail/`) — only use with IDs from search | | `wayfinder://tokens/gas/{chain_code}` | **Native gas token** for a chain (ETH, HYPE) — use for native tokens | | `wayfinder://hyperliquid/{label}/state` | Perp positions + PnL for a wallet | | `wayfinder://hyperliquid/{label}/spot` | Spot balances on Hyperliquid for a wallet | | `wayfinder://hyperliquid/prices/{coin}` | Mid price for a single coin | | `wayfinder://hyperliquid/book/{coin}` | Order book for a coin | **Token lookup order — always search or use gas endpoint first:** ```bash # 1. For native gas tokens (ETH, HYPE): use the gas endpoint poetry run wayfinder resource wayfinder://tokens/gas/ethereum # ETH on Ethereum poetry run wayfinder resource wayfinder://tokens/gas/base # ETH on Base poetry run wayfinder resource wayfinder://tokens/gas/hyperevm # HYPE on HyperEVM # 2. For ERC20 tokens: ALWAYS fuzzy search first poetry run wayfinder resource wayfinder://tokens/search/base/usdc poetry run wayfinder resource wayfinder://tokens/search/arbitrum/eth poetry run wayfinder resource wayfinder://tokens/search/ethereum/weth # 3. Then resolve with the exact ID from search results poetry run wayfinder resource wayfinder://tokens/resolve/usd-coin-base ``` ```bash poetry run wayfinder resource wayfinder://adapters/moonwell_adapter poetry run wayfinder resource wayfinder://strategies/stablecoin_yield_strategy poetry run wayfinder resource wayfinder://wallets/main poetry run wayfinder resource wayfinder://balances/main poetry run wayfinder resource wayfinder://activity/main poetry run wayfinder resource wayfinder://hyperliquid/main/state poetry run wayfinder resource wayfinder://hyperliquid/main/spot poetry run wayfinder resource wayfinder://hyperliquid/prices/ETH poetry run wayfinder resource wayfinder://hyperliquid/book/ETH ``` --- ### `wallets` — Manage wallets and discover positions Create, annotate, and discover cross-protocol positions. Use `resource wayfinder://wallets` to list wallets and `resource wayfinder://wallets/{label}` to get a single wallet. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `action` | `"create"` \| `"annotate"` \| `"discover_portfolio"` | **Yes** | — | — | | `label` | string | **create** | — | Must be non-empty; duplicate labels are idempotent | | `wallet_label` | string | **annotate, discover_portfolio** | — | Or use `wallet_address` | | `wallet_address` | string | No | — | Alternative to `wallet_label` | | `protocol` | string | **annotate** | — | Protocol name for annotation | | `annotate_action` | string | **annotate** | — | Action being annotated | | `tool` | string | **annotate** | — | Tool name for annotation | | `status` | string | **annotate** | — | Status for annotation | | `chain_id` | string | No | — | — | | `details` | string (JSON) | No | — | Extra metadata for annotation | | `protocols` | string (JSON) | No | — | Filter `discover_portfolio` to specific protocols | | `parallel` | bool | No | `false` | **Required if querying >= 3 protocols** without a `protocols` filter | | `include_zero_positions` | bool | No | `false` | Include empty positions in portfolio | Supported protocols for `discover_portfolio`: `hyperliquid`, `hyperlend`, `moonwell`, `boros`, `pendle`. ```bash poetry run wayfinder wallets --action create --label my_new_strategy poetry run wayfinder wallets --action discover_portfolio --wallet_label main --parallel poetry run wayfinder wallets --action discover_portfolio --wallet_label main --protocols '["hyperliquid","moonwell"]' ``` **Validations:** - `create`: `label` must be non-empty. Duplicate labels return the existing wallet (idempotent). - `annotate`/`discover_portfolio`: must resolve a wallet address from `wallet_label` or `wallet_address`. - `annotate`: all of `protocol`, `annotate_action`, `tool`, `status` are required. - `discover_portfolio` with >= 3 protocols requires `parallel=true` or an explicit `protocols` filter (returns `requires_confirmation` otherwise). --- ### `quote_swap` — Get a swap/bridge quote (read-only) Returns a quote for swapping or bridging tokens. No on-chain effects. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `wallet_label` | string | **Yes** | — | Must resolve to a wallet with an address | | `from_token` | string | **Yes** | — | Token ID from search results (e.g. `usd-coin-base`). **Always search first** — do not guess. | | `to_token` | string | **Yes** | — | Token ID from search results. **Always search first.** | | `amount` | string | **Yes** | — | Human-readable amount (e.g. `"500"`). Must be positive, Decimal-parseable, and > 0 after scaling to token decimals | | `slippage_bps` | int | No | `50` | Slippage tolerance in basis points (50 = 0.5%) | | `recipient` | string | No | — | Defaults to sender address | | `include_calldata` | bool | No | `false` | Include raw calldata in response | **Always resolve token IDs before calling quote_swap.** Run `poetry run wayfinder resource wayfinder://tokens/search/<chain>/<symbol>` for each token first, then use the exact ID from the result. Do not pass raw symbols or guessed `symbol-chain` strings — they may resolve incorrectly or fail. **Note:** Native gas tokens (e.g., unwrapped ETH) may fail in swaps with `from_token_address: null`. Use the wrapped ERC20 version instead (e.g., WETH). Search for it: `resource wayfinder://tokens/search/<chain>/weth`. **Bridging to a new chain for the first time:** the wallet needs **native gas on the destination chain** before it can do anything. Bridge the native gas token (e.g. ETH) to the destination chain first, then bridge or swap for the target token. Use the native token IDs from the supported-chains table below (e.g. `ethereum-base` for ETH on Base). - Use the native token IDs from the supported-chains table below when bridging gas (e.g. `ethereum-base` for ETH on Base, `plasma-plasma` for PLASMA on Plasma). ```bash poetry run wayfinder quote_swap --wallet_label main --from_token usd-coin-base --to_token ethereum-base --amount 500 poetry run wayfinder quote_swap --wallet_label main --from_token "USDC-base" --to_token "ETH-base" --amount 1000 --slippage_bps 100 ``` **Errors:** `not_found` (wallet), `invalid_wallet`, `token_error`, `invalid_token` (missing chain_id/address), `invalid_amount`, `quote_error`. --- ### `execute` — Execute on-chain transactions Execute swaps, token sends, or Hyperliquid deposits. **This broadcasts transactions** and can move real funds. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `kind` | `swap` \| `send` \| `hyperliquid_deposit` | **Yes** | — | Operation type | | `wallet_label` | string | **Yes** | — | Must resolve to a wallet with private key | | `amount` | string | **Yes** | — | Human-readable amount (e.g. `"500"`) | | `from_token` | string | **swap** | — | Source token ID. **Always search first.** | | `to_token` | string | **swap** | — | Destination token ID. **Always search first.** | | `slippage_bps` | int | No | `50` | Swap only; basis points | | `deadline_seconds` | int | No | `300` | Swap only | | `recipient` | string | **send** | — | Recipient address | | `token` | string | **send** | — | Token ID (or `"native"` with `chain_id`). **Always search first.** | | `chain_id` | string | No | — | Required for `send` when `[REDACTED_SECRET]"` | | `force` | flag | No | `false` | Do not rely on this as a “dry-run vs live” gate. Treat `execute` as live and require explicit user confirmation before calling it. | **Hyperliquid deposit validations (critical):** - Amount **must be >= 5 USDC** (deposits below 5 are lost on the bridge). - Hard-codes: [REDACTED_SECRET] USDC, recipient = `HYPERLIQUID_BRIDGE_ADDRESS`, chain = Arbitrum (42161). **Additional runtime validations:** - Wallet must have both `address` and `private_key_hex`. - Token resolution must succeed (chain_id + token address required). - Swap quotes must return a `best_quote` with `calldata`. - For USDT-style tokens, a zero-allowance reset transaction is sent before approval. ```bash # Swap poetry run wayfinder execute --kind swap --wallet_label main --from_token usd-coin-base --to_token ethereum-base --amount 500 # Send tokens poetry run wayfinder execute --kind send --wallet_label main --token usd-coin-base --recipient 0x... --amount 100 # Hyperliquid deposit (min 5 USDC) poetry run wayfinder execute --kind hyperliquid_deposit --wallet_label main --amount 100 ``` --- ### `hyperliquid` — Wait for Hyperliquid deposits/withdrawals Wait for deposits or withdrawals to settle on Hyperliquid. For read-only queries (user state, prices, order books), use the `resource` command with Hyperliquid URIs. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `action` | `"wait_for_deposit"` \| `"wait_for_withdrawal"` | **Yes** | — | — | | `wallet_label` | string | No | — | Or use `wallet_address` | | `wallet_address` | string | No | — | Alternative to `wallet_label` | | `expected_increase` | string | No | — | Expected USDC increase for deposit | | `timeout_s` | int | No | `120` | Timeout for `wait_for_deposit` | | `poll_interval_s` | int | No | `5` | Poll interval for wait actions | | `lookback_s` | int | No | `5` | For `wait_for_withdrawal` | | `max_poll_time_s` | int | No | `900` | Max wait for `wait_for_withdrawal` (15 min) | ```bash poetry run wayfinder hyperliquid --action wait_for_deposit --wallet_label main --expected_increase 100 poetry run wayfinder hyperliquid --action wait_for_withdrawal --wallet_label main ``` **Read-only queries via resources:** ```bash # Perp positions + PnL poetry run wayfinder resource wayfinder://hyperliquid/main/state # Spot balances poetry run wayfinder resource wayfinder://hyperliquid/main/spot # All mid prices poetry run wayfinder resource wayfinder://hyperliquid/prices # Single coin price poetry run wayfinder resource wayfinder://hyperliquid/prices/ETH # Market metadata + funding rates poetry run wayfinder resource wayfinder://hyperliquid/markets # Spot asset metadata poetry run wayfinder resource wayfinder://hyperliquid/spot-assets # Order book poetry run wayfinder resource wayfinder://hyperliquid/book/ETH ``` --- ### `hyperliquid_execute` — Hyperliquid trading operations Place/cancel orders, update leverage, and withdraw USDC. **These operations are live** and can place real orders / move real funds. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `action` | `place_order` \| `cancel_order` \| `update_leverage` \| `withdraw` \| `spot_to_perp_transfer` \| `perp_to_spot_transfer` | **Yes** | — | — | | `wallet_label` | string | **Yes** | — | Must resolve to wallet with private key | | `coin` | string | **place_order, cancel_order, update_leverage** | — | Or use `asset_id`. Strips `-perp`/`_perp` suffixes automatically | | `asset_id` | string | No | — | Direct asset ID (alternative to `coin`) | | `is_spot` | string | No | — | `true` for spot orders, `false` for perp. **Must be explicit for place_order.** | | `order_type` | `market` \| `limit` | No | `market` | — | | `is_buy` | string | **place_order** | — | `true` or `false` | | `size` | string | No | — | **Mutually exclusive with `usd_amount`**; coin units | | `usd_amount` | string | No | — | **Mutually exclusive with `size`**; USD amount | | `usd_amount_kind` | string | **when `usd_amount` is used** | — | `notional` or `margin` | | `leverage` | string | **when `usd_amount_kind=margin`; update_leverage** | — | Must be positive | | `price` | string | **limit orders** | — | Must be positive | | `slippage` | float | No | `0.01` | Market orders only; 0–0.25 (25% cap) | | `reduce_only` | flag | No | `false` | `--reduce_only` / `--no-reduce_only` | | `cloid` | string | No | — | Client order ID | | `order_id` | string | **cancel_order** | — | Or use `cancel_cloid` | | `cancel_cloid` | string | No | — | Alternative to `order_id` for cancel | | `is_cross` | flag | No | `true` | `--is_cross` / `--no-is_cross` | | `amount_usdc` | string | **withdraw, transfers** | — | USDC amount for withdraw or transfers | | `builder_fee_tenths_bp` | string | No | — | Falls back to config default | | `force` | flag | No | `false` | Do not rely on this as a “dry-run vs live” gate. Treat `hyperliquid_execute` as live and require explicit user confirmation before calling it. | **Key validations for `place_order`:** - Exactly one of `size` or `usd_amount` (not both, not neither). - If `usd_amount` is used, `usd_amount_kind` is required. - If `usd_amount_kind=margin`, then `leverage` is required. - Limit orders require `price` > 0. - After lot-size rounding, size must still be > 0. - Builder fee is mandatory (auto-configured; approval is auto-submitted if needed). ```bash # Market buy poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main --coin ETH --is_buy true --usd_amount 200 --usd_amount_kind margin --leverage 5 # Spot buy poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main --coin HYPE --is_spot true --is_buy true --usd_amount 20 # Limit sell poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main --coin ETH --is_buy false --size 0.1 --price 4000 --order_type limit # Close position (reduce-only) poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main --coin ETH --is_buy false --size 0.5 --reduce_only # Update leverage poetry run wayfinder hyperliquid_execute --action update_leverage --wallet_label main --coin ETH --leverage 5 # Cancel order poetry run wayfinder hyperliquid_execute --action cancel_order --wallet_label main --coin ETH --order_id 12345 # Withdraw USDC poetry run wayfinder hyperliquid_execute --action withdraw --wallet_label main --amount_usdc 100 # Transfer USDC between spot and perp wallets poetry run wayfinder hyperliquid_execute --action spot_to_perp_transfer --wallet_label main --amount_usdc 50 poetry run wayfinder hyperliquid_execute --action perp_to_spot_transfer --wallet_label main --amount_usdc 50 ``` --- ### `polymarket` — Polymarket market + account reads Read-only access to Polymarket markets, prices, order books, and user status. **Tradability filter:** a market can be “found” but not tradable. Filter for `enableOrderBook`, `acceptingOrders`, `active`, `closed != true`, and non-empty `clobTokenIds`. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `action` | `status` \| `search` \| `trending` \| `get_market` \| `get_event` \| `price` \| `order_book` \| `price_history` \| `bridge_status` \| `open_orders` | **Yes** | — | — | | `wallet_label` | string | No | — | Resolves `account` from config; required for `open_orders` | | `wallet_address` | string | No | — | Alternative to `wallet_label` for account-based reads | | `account` | string | No | — | Direct account address (alternative to wallet inputs) | | `include_orders` | bool | No | `true` | `status` only | | `include_activity` | bool | No | `false` | `status` only | | `activity_limit` | int | No | `50` | `status` only | | `include_trades` | bool | No | `false` | `status` only | | `trades_limit` | int | No | `50` | `status` only | | `positions_limit` | int | No | `500` | `status` only | | `max_positions_pages` | int | No | `10` | `status` only | | `query` | string | **search** | — | Query string for fuzzy market search | | `limit` | int | No | `10` | `search`, `trending` | | `page` | int | No | `1` | `search` | | `keep_closed_markets` | bool | No | `false` | `search` | | `rerank` | bool | No | `true` | `search` | | `offset` | int | No | `0` | `trending` | | `market_slug` | string | **get_market** | — | Market slug | | `event_slug` | string | **get_event** | — | Event slug | | `token_id` | string | **price, order_book, price_history** | — | Polymarket CLOB token id (optional for `open_orders` filter) | | `side` | `BUY` \| `SELL` | No | `BUY` | `price` only | | `interval` | string | No | `"1d"` | `price_history` only | | `start_ts` | int | No | — | `price_history` only (unix seconds) | | `end_ts` | int | No | — | `price_history` only (unix seconds) | | `fidelity` | int | No | — | `price_history` only | **Action-specific requirements:** - `status`, `bridge_status`: require an `account` (via `--account`, `--wallet_address`, or `--wallet_label`). - `open_orders`: requires `--wallet_label` and a wallet with `private_key_hex` in `config.json` (Level-2 auth). Optional: `--token_id` to filter. ```bash # Search markets poetry run wayfinder polymarket --action search --query "bitcoin above 100k" --limit 5 # User status (positions + balances) poetry run wayfinder polymarket --action status --wallet_label main # CLOB order book poetry run wayfinder polymarket --action order_book --token_id 123456 ``` --- ### `polymarket_execute` — Polymarket execution (bridge + orders) Execute Polymarket actions (bridging and trading). **This command is live (no dry-run flag).** | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `action` | `bridge_deposit` \| `bridge_withdraw` \| `buy` \| `sell` \| `close_position` \| `place_limit_order` \| `cancel_order` \| `redeem_positions` | **Yes** | — | — | | `wallet_label` | string | **Yes** | — | Wallet must include `address` and `private_key_hex` in config | | `from_chain_id` | int | No | `137` | `bridge_deposit` only | | `from_token_address` | string | No | Polygon USDC | `bridge_deposit` only | | `amount` | float | **bridge_deposit** | — | Amount of USDC to deposit | | `recipient_address` | string | No | sender | `bridge_deposit` only | | `amount_usdce` | float | **bridge_withdraw** | — | Amount of USDC.e to withdraw | | `to_chain_id` | int | No | `137` | `bridge_withdraw` only | | `to_token_address` | string | No | Polygon USDC | `bridge_withdraw` only | | `recipient_addr` | string | No | sender | `bridge_withdraw` only | | `token_decimals` | int | No | `6` | Bridge token decimals | | `market_slug` | string | No | — | Used by `buy`, `sell`, `close_position` | | `outcome` | string \| int | No | `"YES"` | Used with `market_slug` (e.g. `YES`/`NO`) | | `token_id` | string | No | — | Alternative to `market_slug` for `buy`, `sell`, `place_limit_order` | | `amount_usdc` | float | **buy** | — | Buy amount in USDC | | `shares` | float | **sell** | — | Shares to sell | | `side` | `BUY` \| `SELL` | No | `BUY` | `place_limit_order` only | | `price` | float | **place_limit_order** | — | Limit price (0–1) | | `size` | float | **place_limit_order** | — | Order size (shares) | | `post_only` | bool | No | `false` | `place_limit_order` only | | `order_id` | string | **cancel_order** | — | — | | `condition_id` | string | **redeem_positions** | — | Required for `redeem_positions`; also accepted by `close_position` as a fallback | **Approvals + API creds:** handled automatically before order placement (idempotent). **Collateral:** Polymarket CLOB trading collateral is **USDC.e on Polygon**, not native Polygon USDC. Use `bridge_deposit` / `bridge_withdraw` to convert. These methods prefer a fast on-chain BRAP swap on Polygon when possible (sender == recipient); otherwise they fall back to the Polymarket Bridge service (`method: "polymarket_bridge"` in the result) and you can monitor via `polymarket --action bridge_status`. **Trade semantics:** - `buy` uses `amount_usdc` as **collateral ($) to spend** - `sell` uses `shares` as **shares to sell** **Always require explicit user confirmation before running `polymarket_execute`.** ```bash # Bridge USDC -> USDC.e collateral (Polymarket) poetry run wayfinder polymarket_execute --action bridge_deposit --wallet_label main --amount 10 # Buy shares by market slug + outcome poetry run wayfinder polymarket_execute --action buy --wallet_label main --market_slug "some-market-slug" --outcome YES --amount_usdc 2 # Close a position (sells full size; resolves token_id from market slug) poetry run wayfinder polymarket_execute --action close_position --wallet_label main --market_slug "some-market-slug" --outcome YES ``` --- ### `run_strategy` — Strategy lifecycle management Run strategy actions: check status, analyze, quote, deposit, update, withdraw, or exit. | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `strategy` | string | **Yes** | — | Strategy directory name; must have `manifest.yaml` | | `action` | `status` \| `analyze` \| `snapshot` \| `policy` \| `quote` \| `deposit` \| `update` \| `withdraw` \| `exit` | **Yes** | — | — | | `amount_usdc` | float | No | `1000.0` | **Read-only analysis:** hypothetical deposit for `analyze`, `snapshot`, `quote` | | `amount` | string | No | — | Generic amount parameter (strategy-specific) | | `main_token_amount` | string | **deposit** | — | **Actual deposit:** amount of strategy's deposit token | | `gas_token_amount` | float | No | `0.0` | **Actual deposit:** optional gas token amount | **Amount parameter rules:** - **For read-only analysis** (`analyze`, `snapshot`, `quote`): use `--amount_usdc` - **For actual deposits** (`deposit`): use `--main_token_amount` (required) + optionally `--gas_token_amount` - The deposit token varies by strategy (USDC on Base for stablecoin_yield, USDC on Arbitrum for boros_hype, etc.) ```bash poetry run wayfinder resource wayfinder://strategies poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action status poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action analyze --amount_usdc 100 poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action quote --amount_usdc 100 poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action deposit --main_token_amount 100 --gas_token_amount 0.01 poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action update poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action withdraw poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action exit ``` **Errors:** `invalid_request` (empty strategy), `not_found` (missing manifest), `not_supported` (strategy lacks the method), `strategy_error` (runtime exception). **Note:** `withdraw` liquidates positions but funds stay in the strategy wallet. `exit` transfers funds from the strategy wallet back to the main wallet. These are separate steps. --- ### `run_script` — Execute sandboxed Python scripts Run a local Python script in a subprocess. Scripts must live inside the runs directory (`$WAYFINDER_RUNS_DIR` or `.wayfinder_runs/`). | Parameter | Type | Required | Default | Notes | |-----------|------|----------|---------|-------| | `script_path` | string | **Yes** | — | Must be `.py`, must exist, **must be inside the runs directory** | | `args` | string | No | — | Arguments passed to the script (JSON list) | | `timeout_s` | int | No | `600` | Clamped to min 1 second | | `env` | string | No | — | Additional env vars for subprocess (JSON object) | | `wallet_label` | string | No | — | For profile annotation | | `force` | flag | No | `false` | Do not rely on this as a “dry-run vs live” gate. Prefer implementing `--dry-run` / `--force` inside your script and passing it via `--args`. | **Validations:** - Script path must resolve to inside the runs directory (sandboxed — no arbitrary file execution). - Must be a `.py` file. - Must exist on disk. - Output is truncated to 20,000 chars. ```bash # Recommended: implement --dry-run / --force in your script and pass it via --args poetry run wayfinder run_script --script_path .wayfinder_runs/my_flow.py --args '["--dry-run"]' --wallet_label main poetry run wayfinder run_script --script_path .wayfinder_runs/my_flow.py --args '["--force"]' --wallet_label main # With timeout poetry run wayfinder run_script --script_path .wayfinder_runs/my_flow.py --wallet_label main --timeout_s 120 ``` --- ## Config Structure Config is loaded from `$WAYFINDER_CONFIG_PATH` (default: `$WAYFINDER_SDK_PATH/config.json`). ```json { "system": { "api_base_url": "https://strategies.wayfinder.ai/api/v1", "api_key": "wk_..." }, "strategy": { "rpc_urls": {} }, "wallets": [ { "label": "main", "address": "0x...", "private_key_hex": "0x..." } ], "ccxt": { "aster": { "apiKey": "", "secret": "" }, "binance": { "apiKey": "", "secret": "" } } } ``` - `system.api_key` falls back to `$WAYFINDER_API_KEY` env var. - `strategy.rpc_urls` is optional; if a chain id is not configured, on-chain calls use Wayfinder’s RPC proxy at `system.api_base_url` (auth via your API key). - Most write operations require a wallet entry with `address` + `private_key_hex`. ## Available Strategies | Strategy | Status | Chain | Token | Risk | Description | |----------|--------|-------|-------|------|-------------| | `basis_trading_strategy` | stable | Hyperliquid | USDC | Medium | Delta-neutral funding rate capture with matched spot/perp positions | | `boros_hype_strategy` | stable | Arbitrum + HyperEVM + Hyperliquid | HYPE/USDC | Medium | Multi-leg HYPE yield with fixed-rate funding lock via Boros | | `hyperlend_stable_yield_strategy` | stable | HyperEVM | USDT0 | Low | Stablecoin yield optimization on HyperLend with rotation policy | | `moonwell_wsteth_loop_strategy` | stable | Base | USDC/WETH/wstETH | Medium-High | Leveraged wstETH carry trade via Moonwell looping | | `stablecoin_yield_strategy` | wip | Base | USDC | Low | Auto-rotates across best stablecoin pools on Base | | `projectx_thbill_usdc_strategy` | wip | HyperEVM | THBILL/USDC | Medium | Concentrated liquidity market making on ProjectX (V3 fork) | **Reference**: [references/strategies.md](references/strategies.md) ## Available Adapters | Adapter | Protocol | Capabilities | |---------|----------|-------------| | `balance_adapter` | EVM wallets | `balance.read`, `transfer.main_to_strategy`, `transfer.strategy_to_main`, `transfer.send` | | `boros_adapter` | Boros (Arbitrum) | `market.read`, `market.quote`, `position.open`, `position.close`, `collateral.deposit`, `collateral.withdraw` | | `brap_adapter` | Cross-chain swaps | `swap.quote`, `swap.execute`, `swap.compare_routes`, `bridge.quote`, `gas.estimate` | | `ccxt_adapter` | Centralized exchanges (CCXT) | `exchange.factory` | | `hyperlend_adapter` | HyperLend (HyperEVM) | `market.stable_markets`, `market.assets_view`, `market.rate_history`, `lending.lend`, `lending.unlend` | | `hyperliquid_adapter` | Hyperliquid DEX | `market.read`, `market.meta`, `market.funding`, `market.candles`, `market.orderbook`, `order.execute`, `order.cancel`, `position.manage`, `transfer`, `withdraw` | | `ledger_adapter` | Local bookkeeping | `ledger.read`, `ledger.record`, `ledger.snapshot` | | `moonwell_adapter` | Moonwell (Base) | `lending.lend`, `lending.unlend`, `lending.borrow`, `lending.repay`, `collateral.set`, `collateral.remove`, `rewards.claim`, `position.read`, `market.apy`, `market.collateral_factor` | | `multicall_adapter` | EVM batch calls | `multicall.aggregate` | | `pendle_adapter` | Pendle | `pendle.markets.read`, `pendle.market.snapshot`, `pendle.swap.quote`, `pendle.swap.execute`, `pendle.convert.quote`, `pendle.positions.database`, and more | | `polymarket_adapter` | Polymarket | `market.read`, `market.search`, `market.orderbook`, `market.candles`, `position.read`, `order.execute`, `order.cancel`, `bridge.deposit`, `bridge.withdraw` | | `pool_adapter` | DeFi Llama | `pool.read`, `pool.discover` | | `projectx_adapter` | ProjectX (V3 fork) | `projectx.pool.overview`, `projectx.positions.list`, `projectx.liquidity.mint`, `projectx.liquidity.increase`, `projectx.liquidity.decrease`, `projectx.fees.collect`, `projectx.swap.exact_in` | | `token_adapter` | Token metadata | `token.read`, `token.price`, `token.gas` | | `uniswap_adapter` | Uniswap V3 | `uniswap.liquidity.add`, `uniswap.liquidity.increase`, `uniswap.liquidity.remove`, `uniswap.fees.collect`, `uniswap.position.get`, `uniswap.positions.list`, `uniswap.fees.uncollected`, `uniswap.pool.get` | **Reference**: [references/adapters.md](references/adapters.md) ## Token ID Format — ALWAYS SEARCH FIRST **CRITICAL: NEVER guess or construct token IDs.** Always look up the correct token using the appropriate endpoint before using it in any command. **Three token endpoints — know which to use:** - **`tokens/search`** → fuzzy search (hits `/blockchain/tokens/fuzzy/`) — **always use this first for ERC20 tokens** - **`tokens/resolve`** → exact lookup (hits `/blockchain/tokens/detail/`) — only use with an ID you got from search - **`tokens/gas`** → native gas tokens (hits `/blockchain/tokens/gas/`) — **use for ETH, HYPE, and other native tokens** Token IDs use `<coingecko_id>-<chain_code>` format (NOT symbol-chain): - `usd-coin-base` (USDC on Base) — NOT `usdc-base` - `ethereum-arbitrum` (ETH on Arbitrum) — NOT `ETH-arbitrum` - `usdt0-arbitrum` (USDT on Arbitrum) — NOT `USDT-arbitrum` - `hyperliquid-hyperevm` (HYPE on HyperEVM) — NOT `HYPE-hyperevm` **You cannot reliably guess coingecko IDs from token symbols.** For example, ETH's coingecko ID is `ethereum`, USDC's is `usd-coin`, HYPE's is `hyperliquid`. These are not derivable from the symbol alone. **Native gas tokens** are best discovered via `tokens/gas/<chain_code>`. Use the table below as a convenient reference, but prefer `tokens/gas` when in doubt. Valid chain codes (common): `ethereum`, `base`, `arbitrum`, `polygon`, `bsc`, `avalanche`, `plasma`, `hyperevm`. Note: `mainnet` is NOT a valid chain code — use `ethereum` instead. ### Supported chains | Chain | ID | Code | Symbol | Native token ID | |------|----|------|--------|-----------------| | Ethereum | 1 | `ethereum` | ETH | `ethereum-ethereum` | | Base | 8453 | `base` | ETH | `ethereum-base` | | Arbitrum | 42161 | `arbitrum` | ETH | `ethereum-arbitrum` | | Polygon | 137 | `polygon` | POL | `polygon-ecosystem-token-polygon` | | BSC | 56 | `bsc` | BNB | `binancecoin-bsc` | | Avalanche | 43114 | `avalanche` | AVAX | `avalanche-avalanche` | | Plasma | 9745 | `plasma` | PLASMA | `plasma-plasma` | | HyperEVM | 999 | `hyperevm` | HYPE | `hyperliquid-hyperevm` | **Before every swap, send, or token operation:** ```bash # For native gas tokens (ETH, HYPE): poetry run wayfinder resource wayfinder://tokens/gas/<chain_code> # For ERC20 tokens — REQUIRED: fuzzy search first poetry run wayfinder resource wayfinder://tokens/search/<chain_code>/<symbol> # Then use the exact token ID from the search result ``` ## Sizing for Perp Orders When a user says "$X at Yx leverage", clarify: - `--usd_amount_kind margin` = $X is collateral (notional = X * leverage) - `--usd_amount_kind notional` = $X is position size `--usd_amount` and `--size` are mutually exclusive. When using `--usd_amount` with `--usd_amount_kind margin`, `--leverage` is required. ## Safety - **NEVER output private keys or seed phrases into the conversation.** These are secrets that must stay on the machine. Only offer to display a seed phrase if the user explicitly confirms they cannot access the machine to retrieve it themselves. - **Execution commands are live.** Require explicit user confirmation before running `execute`, `hyperliquid_execute`, `polymarket_execute`, or any script that broadcasts transactions. - **NEVER guess or fabricate token IDs.** Before any token operation (swap, send, quote, balance check): - For **native gas tokens** (ETH, HYPE): use `poetry run wayfinder resource wayfinder://tokens/gas/<chain_code>` - For **ERC20 tokens**: use `poetry run wayfinder resource wayfinder://tokens/search/<chain_code>/<query>` (fuzzy search) and use the exact token ID from the result - Do not construct IDs by combining symbols with chain names — the coingecko ID is unpredictable. Do not call `tokens/resolve` with a guessed ID — it hits a different API than search. - **Bridging to a new chain (first time):** bridge native gas to the destination chain first (use the supported-chains table or `tokens/gas/<chain_code>`), then bridge/swap for the target asset. - Start with small test amounts. - Withdraw and exit are separate steps: `withdraw` liquidates positions, `exit` transfers funds home. - **Hyperliquid deposits must be >= 5 USDC** — amounts below 5 are lost on the bridge. - Market order slippage is capped at 25% (`--slippage 0.25`). - Scripts are sandboxed to the runs directory — no arbitrary file execution. ## Common Workflows ### Check Before Trading ```bash poetry run wayfinder resource wayfinder://balances/main # ALWAYS look up tokens first — never guess IDs poetry run wayfinder resource wayfinder://tokens/search/base/usdc # Search for USDC → get token ID from result poetry run wayfinder resource wayfinder://tokens/gas/base # Get native ETH on Base # Use the exact token IDs from the lookup results poetry run wayfinder resource wayfinder://hyperliquid/prices/ETH poetry run wayfinder quote_swap --wallet_label main --from_token usd-coin-base --to_token ethereum-base --amount 1000 ``` ### Deploy a Strategy ```bash poetry run wayfinder resource wayfinder://strategies poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action status poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action deposit --main_token_amount 100 --gas_token_amount 0.01 poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action update ``` ### Open a Hyperliquid Position ```bash poetry run wayfinder resource wayfinder://hyperliquid/main/state poetry run wayfinder hyperliquid_execute --action update_leverage --wallet_label main --coin ETH --leverage 5 poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main --coin ETH --is_buy true --usd_amount 200 --usd_amount_kind margin --leverage 5 ``` ### Wind Down Everything ```bash poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action withdraw poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action exit ``` ## Custom Scripts via the Coding Interface **For any operation that goes beyond a single CLI command, you SHOULD write a custom Python script.** The `wayfinder-paths-sdk` provides a full coding interface — use it whenever you need multi-step flows, conditional logic, batched operations, or protocol combinations. ### When to Write a Script - **Multi-step atomic flows** — operations that must succeed together - **Custom logic** — conditional execution based on market state - **Batched operations** — multiple protocol interactions in sequence - **Protocol combinations** — bridging multiple adapters in one flow - **Complex calculations** — position sizing, rebalancing, PnL analysis - **Anything the user asks that isn't a single CLI call** ### Script Location All generated scripts **must** be saved to `.wayfinder_runs/` inside the SDK directory: ``` $WAYFINDER_SDK_PATH/.wayfinder_runs/my_script.py ``` This directory is sandboxed — `run_script` only executes scripts inside it. Create it if it doesn't exist: ```bash mkdir -p "$WAYFINDER_SDK_PATH/.wayfinder_runs" ``` ### Referencing the SDK Source Before writing any script, **pull the detailed reference docs** for the adapter or strategy you're working with. The SDK ships comprehensive skill docs covering method signatures, gotchas, unit conventions, and execution patterns. **Use the reference script** (bash or PowerShell): ```bash # List available topics ./wayfinder/scripts/pull-sdk-ref.sh --list # Pull docs for specific adapters (supports multiple topics) ./wayfinder/scripts/pull-sdk-ref.sh moonwell ./wayfinder/scripts/pull-sdk-ref.sh boros hyperliquid ./wayfinder/scripts/pull-sdk-ref.sh strategies # Pull everything ./wayfinder/scripts/pull-sdk-ref.sh --all # Check the pinned SDK version ./wayfinder/scripts/pull-sdk-ref.sh --version # Override with a specific commit ./wayfinder/scripts/pull-sdk-ref.sh --commit abc123 moonwell ``` ```powershell # Windows .\wayfinder\scripts\pull-sdk-ref.ps1 moonwell .\wayfinder\scripts\pull-sdk-ref.ps1 -All .\wayfinder\scripts\pull-sdk-ref.ps1 -Version .\wayfinder\scripts\pull-sdk-ref.ps1 -Commit abc123 moonwell ``` **Available topics:** `strategies`, `setup`, `boros`, `brap`, `hyperlend`, `hyperliquid`, `polymarket`, `moonwell`, `pendle`, `uniswap`, `projectx`, `data` The SDK ref is tracked in `wayfinder/sdk-version.md` (default: `main`). The pull script checks out that ref when reading docs, then restores the SDK to its previous state. **Always run this before writing a script** — the docs cover critical details like: - Exact method signatures and required parameters - Unit conventions (raw base units vs human-readable, wei vs native) - Gotchas (e.g., `unlend()` takes mToken amounts not underlying, withdrawal cooldowns, funding sign conventions) - Execution patterns and safety rails - Token/contract addresses You can also read the adapter source code directly: ``` $WAYFINDER_SDK_PATH/wayfinder_paths/adapters/ # All adapter implementations $WAYFINDER_SDK_PATH/wayfinder_paths/mcp/scripting.py # get_adapter() helper $WAYFINDER_SDK_PATH/wayfinder_paths/strategies/ # Strategy implementations ``` ### Quick Start ```python #!/usr/bin/env python3 import asyncio from wayfinder_paths.mcp.scripting import get_adapter from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter async def main(): adapter = get_adapter(MoonwellAdapter, "main") # Auto-wires config + signing success, result = await adapter.lend(mtoken="0x...", amount=100_000_000) print(f"Result: {result}" if success else f"Error: {result}") if __name__ == "__main__": asyncio.run(main()) ``` ### Testing Workflow **Always test before live execution.** Follow this workflow: 1. **Write** the script to `.wayfinder_runs/` 2. **Safe run** — run the script in a non-fund-moving mode first (recommended: implement `--dry-run` / `--force` in your script and pass it via `--args`): ```bash cd "$WAYFINDER_SDK_PATH" poetry run wayfinder run_script --script_path .wayfinder_runs/my_script.py --args '["--dry-run"]' --wallet_label main ``` 3. **Review** the output. Verify the operations, amounts, and addresses are correct. 4. **Live execution** — only after confirming the safe run looks right, run with `--force`: ```bash poetry run wayfinder run_script --script_path .wayfinder_runs/my_script.py --args '["--force"]' --wallet_label main ``` **Never skip the safe-run step for scripts that move funds.** **Reference**: [references/coding-interface.md](references/coding-interface.md) — Full adapter API reference, examples, and patterns ## Protocol References - [references/setup.md](references/setup.md) — First-time setup, configuration, and wallet management - [references/strategies.md](references/strategies.md) — Strategy details, parameters, and workflows - [references/adapters.md](references/adapters.md) — Adapter capabilities and method signatures - [references/coding-interface.md](references/coding-interface.md) — Custom Python scripting with adapters - [references/hyperliquid.md](references/hyperliquid.md) — Hyperliquid trading, deposits, funding - [references/polymarket.md](references/polymarket.md) — Polymarket markets, bridging, and trading - [references/ccxt.md](references/ccxt.md) — Centralized exchanges (Aster/Binance/etc.) via CCXT (use carefully) - [references/moonwell.md](references/moonwell.md) — Moonwell lending, mToken addresses, gotchas - [references/pendle.md](references/pendle.md) — Pendle PT/YT markets, swap execution - [references/boros.md](references/boros.md) — Boros fixed-rate markets, rate locking - [references/uniswap.md](references/uniswap.md) — Uniswap V3 LP positions and fee collection - [references/projectx.md](references/projectx.md) — ProjectX (V3 fork) LP positions, swaps, and strategy notes - [references/tokens-and-pools.md](references/tokens-and-pools.md) — Token IDs, pool discovery, balance reads - [references/hyperlend.md](references/hyperlend.md) — HyperLend lending, supply/withdraw flows ## Error Handling All errors return structured JSON: `{"ok": false, "error": {"code": "...", "message": "...", "details": ...}}`. ### Error Categories #### Validation Errors — bad input, fixable by the caller | Error Code | Meaning | Common Causes | User-Facing Guidance | |------------|---------|---------------|----------------------| | `invalid_request` | Missing or invalid required parameters | Omitted `action`, empty `strategy`, missing `token_id` for balance query | Tell the user which parameter is missing and show the correct command format | | `invalid_wallet` | Wallet missing `address` or `private_key_hex` | Wallet label exists but entry is incomplete; read-only wallet used for execution | Ask the user to check their config.json wallet entry has both fields | | `invalid_token` | Token resolution failed — missing `chain_id` or contract `address` after lookup | Typo in token ID, token not indexed, ambiguous symbol without chain qualifier | Suggest running `resource wayfinder://tokens/search/<chain>/<query>` and show the closest matches | | `invalid_amount` | Amount not parseable, not positive, or zero after decimal scaling | Non-numeric string, negative value, amount like `0.000000001` that rounds to 0 for a low-decimal token | Show the parsed value and the token's decimals so the user understands the rounding | #### Resource Errors — something doesn't exist | Error Code | Meaning | Common Causes | User-Facing Guidance | |------------|---------|---------------|----------------------| | `not_found` | Directory, manifest, wallet, or resource not found | Strategy name typo, adapter not installed, wallet label doesn't match config | List available resources (`resource wayfinder://strategies`) so the user can pick the right name | | `not_supported` | Strategy does not implement the requested action | Calling `withdraw` on a strategy that only supports `status`/`deposit` | Show which actions the strategy does support (from its manifest) | | `requires_confirmation` | Operation needs explicit user confirmation before proceeding | `discover_portfolio` across >= 3 protocols without `--parallel` flag | Explain the operation scope and ask the user to confirm or pass `--parallel` | #### API & Integration Errors — upstream service failures | Error Code | Meaning | Common Causes | User-Facing Guidance | |------------|---------|---------------|----------------------| | `token_error` | Token adapter API call failed | Wayfinder API down, network timeout, invalid API key | Check API key validity; retry after a moment; show the raw error message from details | | `quote_error` | Swap/bridge quote generation failed | No liquidity for pair, amount too small for routing, bridge route unavailable | Suggest trying a different amount, checking if the pair is supported, or using a different route | | `balance_error` | Balance query failed | RPC node down, rate-limited, invalid chain_id | Retry; if persistent, check RPC URL configuration | | `activity_error` | Activity/transaction history query failed | Indexer lag, unsupported chain for activity | Inform user the history service may be temporarily unavailable | | `price_error` | Price lookup failed | Token not priced by CoinGecko, API rate limit | Note that the token may be too new or illiquid for price data; balances are still valid without prices | #### Execution Errors — on-chain failures | Error Code | Meaning | Common Causes | User-Facing Guidance | |------------|---------|---------------|----------------------| | `executor_error` | On-chain transaction failed | Insufficient gas, contract revert, nonce conflict, allowance issue | Show the revert reason if available; check gas balance; for USDT-style tokens, mention the zero-allowance reset | | `strategy_error` | Strategy runtime exception | Unhandled edge case in strategy code, external dependency failure mid-execution | Show the exception message; suggest checking `status` before retrying | ### Error Details Object The `details` field varies by error code and may contain: | Field | Present On | Description | |-------|-----------|-------------| | `parameter` | `invalid_request` | The specific parameter that failed validation | | `wallet_label` | `invalid_wallet`, `not_found` | The wallet label that was looked up | | `query` | `invalid_token` | The token query that failed resolution | | `candidates` | `invalid_token` | Fuzzy match candidates when available | | `raw_amount` | `invalid_amount` | The original amount string provided | | `scaled_amount` | `invalid_amount` | The amount after decimal scaling (shows why it became zero) | | `decimals` | `invalid_amount` | Token decimals used for scaling | | `tx_hash` | `executor_error` | Transaction hash if the tx was submitted before failing | | `revert_reason` | `executor_error` | Decoded revert reason from the contract | | `strategy` | `strategy_error`, `not_supported` | Strategy name | | `supported_actions` | `not_supported` | List of actions the strategy does implement | | `protocols` | `requires_confirmation` | The protocols that would be queried | | `upstream_error` | `token_error`, `quote_error`, `balance_error`, `activity_error`, `price_error` | Raw error message from the upstream service | ### Presenting Errors to Users When an error is returned, follow this pattern: 1. **Translate the code** — don't show raw error codes. Map to plain language (e.g., `invalid_token` -> "I couldn't find that token"). 2. **Include the actionable fix** — every error above has a recovery path. Always tell the user what to do next. 3. **Show relevant details** — if `details.candidates` exists, list the closest token matches. If `details.revert_reason` exists, explain what the contract rejected. 4. **Offer to retry** — for transient errors (`token_error`, `balance_error`, `quote_error`, `activity_error`, `price_error`), offer to retry. For validation errors, show the corrected command. ### Common User-Facing Issues | Symptom | Error Code | Resolution | |---------|-----------|------------| | "Missing config" | `not_found` | Run setup or create `config.json` manually | | "strategy_wallet not configured" | `invalid_wallet` | Add wallet with matching label to config.json | | "Minimum deposit" | `invalid_amount` | Check strategy minimum requirements (e.g., Hyperliquid >= 5 USDC) | | "Insufficient gas" | `executor_error` | Fund wallet with native gas token for the target chain | | "Token not found" | `invalid_token` | Use `resource wayfinder://tokens/search/<chain>/<query>` to find the correct coingecko ID | | "No quote available" | `quote_error` | Try a different amount, check pair liquidity, or use an alternative route | | "Nonce too low" | `executor_error` | A previous transaction is pending; wait or speed it up | | "Allowance reset needed" | `executor_error` | For USDT-style tokens, the CLI auto-resets allowance — retry if it was a transient RPC issue | | "Rate limited" | `token_error` / `balance_error` | Wait a few seconds and retry the request | ## Best Practices ### Security 1. Never share private keys or commit config.json 2. Start with small test amounts 3. Use dedicated wallets per strategy for isolation 4. Verify addresses before large transfers 5. Use stop losses for leverage trading ### Trading 1. Always quote before executing swaps 2. Specify chain for lesser-known tokens 3. Consider gas costs (use Base for small amounts) 4. Check balance before trades 5. Use limit orders for better prices on Hyperliquid

path: sdk-version.md
content: main


path: references/uniswap.md
content: # Uniswap V3

## Overview

Uniswap V3 is a concentrated-liquidity AMM. The Uniswap adapter provides LP position reads and liquidity/fee management on supported EVM chains (defaults to Base).

- **Type**: `UNISWAP`
- **Module**: `wayfinder_paths.adapters.uniswap_adapter.adapter.UniswapAdapter`
- **Capabilities**: `uniswap.liquidity.add`, `uniswap.liquidity.increase`, `uniswap.liquidity.remove`, `uniswap.fees.collect`, `uniswap.position.get`, `uniswap.positions.list`, `uniswap.fees.uncollected`, `uniswap.pool.get`

## Usage (via custom scripts)

Uniswap operations are executed via one-off scripts under `.wayfinder_runs/` using `get_adapter()`:

```python
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.uniswap_adapter import UniswapAdapter

# Default chain_id is Base (8453). Override via config_overrides if needed.
adapter = get_adapter(UniswapAdapter, "main", config_overrides={"chain_id": 8453})

ok, positions = await adapter.get_positions()  # list all positions owned by the wallet
```

### High-value read methods

| Method | Purpose |
|--------|---------|
| `get_positions(owner?)` | List all Uniswap V3 positions for an owner |
| `get_position(token_id)` | Read a single position by NFT token id |
| `get_pool(token_a, token_b, fee)` | Resolve the pool address for a pair + fee tier |
| `get_uncollected_fees(token_id)` | Estimate uncollected fees for a position |

### Execution methods (fund-moving)

| Method | Purpose | Notes |
|--------|---------|-------|
| `add_liquidity(token0, token1, fee, tick_lower, tick_upper, amount0_desired, amount1_desired, slippage_bps=...)` | Mint a new LP position | Amounts are **raw units** |
| `increase_liquidity(token_id, amount0_desired, amount1_desired, slippage_bps=...)` | Add liquidity to an existing position | Amounts are **raw units** |
| `remove_liquidity(token_id, liquidity?, collect=True, burn=False)` | Decrease liquidity and optionally collect/burn | Uses NPM multicall |
| `collect_fees(token_id)` | Collect fees | — |

## Gotchas

- **Requires signing wallet:** Uniswap write methods require a wallet with `private_key_hex` and sufficient gas on the target chain.
- **Chain selection:** `UniswapAdapter` reads `chain_id` from config (default 8453). Make sure you’re on the intended chain.
- **Raw units:** Liquidity amounts and token amounts are in **raw integer units** (respect decimals).


path: references/projectx.md
content: # ProjectX (HyperEVM)

## Overview

ProjectX is a Uniswap V3-style concentrated-liquidity DEX on HyperEVM. The ProjectX adapter supports:
- Pool overview reads (tick/price/liquidity + token metadata)
- Listing positions for a specific pool
- Minting/increasing liquidity using wallet balances (with optional balancing swaps)
- Fee collection / burning positions
- Exact-in swaps via the ProjectX router

- **Type**: `PROJECTX`
- **Module**: `wayfinder_paths.adapters.projectx_adapter.adapter.ProjectXLiquidityAdapter`
- **Capabilities**: `projectx.pool.overview`, `projectx.positions.list`, `projectx.liquidity.mint`, `projectx.liquidity.increase`, `projectx.liquidity.decrease`, `projectx.fees.collect`, `projectx.position.burn`, `projectx.swap.exact_in`

## Configuration

- **RPC resolution (chain id 999 / HyperEVM)**: Do not hardcode RPC URLs in scripts. Use `web3_from_chain_id(999)` (internally used by the adapter). If `strategy.rpc_urls["999"]` is not set, the SDK falls back to Wayfinder’s RPC proxy at `system.api_base_url` (auth via `system.api_key` / `WAYFINDER_API_KEY`).
- **`pool_address` is optional**, but many pool-specific methods require it.
- The adapter accepts `pool_address` via config in multiple keys (including nested `strategy` config): `pool_address`, `pool`, `projectx_pool_address`, `projectx_pool`.

## Usage (via custom scripts)

ProjectX operations are executed via one-off scripts under `.wayfinder_runs/`:

```bash
poetry run wayfinder run_script --script_path .wayfinder_runs/projectx_lp.py --wallet_label main
```

### Pool-agnostic mode (no `pool_address`)

Use this for cross-pool reads and operations that don’t need a specific pool:
- `get_full_user_state()` (positions + points; skips pool overview/balances without a configured pool)
- `_list_all_positions()` (all active positions across all pools)
- `fetch_prjx_points()` (points lookup)
- `burn_position(token_id)` (close any position by NFT token id)
- `swap_exact_in(...)` (routes automatically; no fee hint from a configured pool)

```python
import asyncio

from wayfinder_paths.adapters.projectx_adapter.adapter import ProjectXLiquidityAdapter
from wayfinder_paths.mcp.scripting import get_adapter


async def main():
    adapter = get_adapter(ProjectXLiquidityAdapter, "main")
    ok, state = await adapter.get_full_user_state()
    print("ok:", ok)
    if ok:
        print("positions:", state.get("positions"))
        print("points:", state.get("points"))


asyncio.run(main())
```

### Pool-scoped mode (with `pool_address`)

Required for pool-specific reads and helpers:
- `pool_overview()` / `current_balances()` / `list_positions()`
- `fetch_swaps()` (subgraph swap history for a specific pool)
- `live_fee_snapshot()`
- `mint_from_balances()` / `increase_liquidity_balanced()`

```python
import asyncio

from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.projectx_adapter import ProjectXLiquidityAdapter

POOL = "0x..."  # ProjectX pool address


async def main():
    adapter = get_adapter(ProjectXLiquidityAdapter, "main", config_overrides={"pool_address": POOL})
    ok, overview = await adapter.pool_overview()
    ok, positions = await adapter.list_positions()
    print("overview ok:", ok)
    print("positions ok:", ok, "n=", len(positions) if ok else None)


asyncio.run(main())
```

## High-value methods

| Method | Purpose | Notes |
|--------|---------|-------|
| `get_full_user_state(...)` | One-shot state (positions/balances/overview/points) | Pool-agnostic (overview/balances require pool) |
| `pool_overview()` | Pool tick/spacing/fee + token metadata | Requires `pool_address` |
| `current_balances(owner=...)` | Raw balances for pool token0/token1 | Requires `pool_address` |
| `list_positions(owner=...)` | Active NPM positions for this pool | Requires `pool_address` |
| `fetch_swaps(...)` | Swap history | Subgraph (HTTP), requires `pool_address` |
| `fetch_prjx_points(wallet_address)` | Points | HTTP API |
| `mint_from_balances(tick_lower, tick_upper, slippage_bps=...)` | Mint new position using balances | Uses pool tick spacing |
| `increase_liquidity_balanced(...)` | Increase liquidity after balancing | Uses pool tick spacing |
| `burn_position(token_id)` | Remove liquidity + collect + burn | Does not require `pool_address` |
| `swap_exact_in(from_token, to_token, amount_in, slippage_bps=...)` | Swap exact-in | Routes automatically |

## Strategy Note

The `projectx_thbill_usdc_strategy` strategy uses this adapter for concentrated-liquidity market making on the THBILL/USDC stable pair.

## Gotchas

- **`pool_address` is optional (two modes):** Pool-scoped methods raise `ValueError("pool_address is required …")` when called without a configured pool.
- **ProjectX pools can have non-standard tick spacing:** Prefer `mint_from_balances()` / `increase_liquidity_balanced()` (they use the pool’s `tick_spacing`). If you call low-level Uniswap-style methods directly, pass `tick_spacing=...` explicitly.
- **`fetch_swaps()` is subgraph-based:** Swap history reads can fail due to subgraph downtime or missing config. Always check `(ok, swaps)` and fall back to on-chain reads when needed.
- **Units are raw ints:** amounts are raw base units (respect token decimals).
- **ERC20-only swaps:** `swap_exact_in(...)` uses ERC20 addresses; for “native HYPE” behavior, use the wrapped ERC20 address.


path: references/hyperliquid.md
content: # Hyperliquid

## Overview

Hyperliquid is a decentralized perpetuals exchange with spot trading. The Hyperliquid adapter provides comprehensive trading capabilities.

- **Type**: `HYPERLIQUID`
- **Module**: `wayfinder_paths.adapters.hyperliquid_adapter.adapter.HyperliquidAdapter`
- **Capabilities**: `market.read`, `market.meta`, `market.funding`, `market.candles`, `market.orderbook`, `order.execute`, `order.cancel`, `position.manage`, `transfer`, `withdraw`

## Market Data (via Resources)

```bash
# For any of the commands make sure you are in the SDK dir
cd "${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}"

# Perp positions + PnL
poetry run wayfinder resource wayfinder://hyperliquid/main/state

# Spot balances on Hyperliquid
poetry run wayfinder resource wayfinder://hyperliquid/main/spot

# All mid prices
poetry run wayfinder resource wayfinder://hyperliquid/prices

# Single coin price
poetry run wayfinder resource wayfinder://hyperliquid/prices/ETH

# Funding rates + metadata for all assets
poetry run wayfinder resource wayfinder://hyperliquid/markets

# Spot asset metadata
poetry run wayfinder resource wayfinder://hyperliquid/spot-assets

# Order book
poetry run wayfinder resource wayfinder://hyperliquid/book/ETH
```

### High-Value Read Methods

| Method | Purpose |
|--------|---------|
| `get_meta_and_asset_ctxs()` | Perp market metadata + contexts (enumerate markets, map asset_id↔coin) |
| `get_spot_meta()` | Spot metadata (tokens + universe pairs) |
| `get_spot_assets()` | Spot asset mapping (e.g. `{"HYPE/USDC": 10107}`) |
| `get_l2_book(coin)` | Perp/spot order book by coin string |
| `get_spot_l2_book(spot_asset_id)` | Spot order book by asset ID |
| `get_user_state(address)` | Perp account state |
| `get_spot_user_state(address)` | Spot balances |
| `get_open_orders(address)` | Open orders |
| `get_frontend_open_orders(address)` | Open + trigger orders |
| `get_user_fills(address)` | Recent fills |
| `get_order_status(address, order_id)` | Single order status |

### Funding History

There is **no** `HyperliquidAdapter.get_funding_history()` method. Use one of:
- **Wayfinder API** (preferred): `HyperliquidDataClient.get_funding_history(coin, start_ms, end_ms)`
- **SDK direct**: `adapter.info.funding_history(name, startTime, endTime)` (milliseconds, not async)

### Hyperliquid deposits + withdrawals (Bridge2)

This repo uses Hyperliquid’s **Bridge2** deposit/withdraw flow and assumes **Arbitrum (chain_id = 42161)** as the EVM side.

**TL;DR:** To deposit to Hyperliquid, you send **native USDC on Arbitrum** to the Hyperliquid Bridge2 address. Do **not** send USDC from other chains or other assets.

Primary reference:
- Hyperliquid docs: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/bridge2
- Funding cadence (hourly): https://hyperliquid.gitbook.io/hyperliquid-docs/trading/funding

## What you can deposit/withdraw

- **Deposit asset:** native **USDC on Arbitrum**
  - This repo’s constant: `ARBITRUM_USDC_ADDRESS = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831`
- **Deposit target:** Bridge2 address on Arbitrum
  - This repo’s constant: `HYPERLIQUID_BRIDGE_ADDRESS = 0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7`

## Minimums, fees + timing (operational expectations)

From Hyperliquid's Bridge2 docs:
- **Minimum deposit is 5 USDC**; deposits below that are **lost**.
- Deposits are typically credited **in < 1 minute**.
- Withdrawals typically arrive **in several minutes** (often longer than deposits).
- **Withdrawal fee is $1 USDC** — deducted from the withdrawn amount (e.g., withdraw $6.93 → receive $5.93).

Treat these as *best-effort expectations*, not guarantees. In orchestration code, always:
- poll for confirmation
- time out safely
- avoid taking downstream risk (hedges/allocations) until funds are confirmed

## Who gets credited (common pitfall)

Baseline Bridge2 deposit behavior:
- **The Hyperliquid account credited is the sender** of the Arbitrum USDC transfer to the bridge address.

Bridge2 also supports “deposit on behalf” via a permit flow (`batchedDepositWithPermit`) per the docs, but this repo’s strategy patterns assume the simple “send USDC to bridge” path.

## How to monitor deposits/withdrawals

Adapter: `wayfinder_paths/adapters/hyperliquid_adapter/adapter.py`

### Deposit initiation

Bash shortcut:
```bash
poetry run wayfinder execute --kind hyperliquid_deposit --wallet_label main --amount 8
```

This hard-codes:
- [REDACTED_SECRET] Arbitrum USDC (`usd-coin-arbitrum`)
- recipient: `HYPERLIQUID_BRIDGE_ADDRESS`
- chain: Arbitrum (42161)

### Withdrawal initiation

- Call: `HyperliquidAdapter.withdraw(amount, address)` (USDC withdraw to Arbitrum via executor)

Bash shortcut:
```bash
poetry run wayfinder hyperliquid_execute --action withdraw --wallet_label main --amount_usdc 100
```

### Deposit monitoring (recommended)

- Call: `HyperliquidAdapter.wait_for_deposit(address, expected_increase, timeout_s=..., poll_interval_s=...)`
- Mechanism: polls `get_user_state(address)` and checks perp margin increase.

Bash shortcut:
```bash
poetry run wayfinder hyperliquid --action wait_for_deposit --wallet_label main --expected_increase 100 --timeout_s 300
```

### Withdrawal monitoring (best-effort)

- Call: `HyperliquidAdapter.wait_for_withdrawal(address, max_poll_time_s=..., poll_interval_s=...)`
- Mechanism: polls Hyperliquid ledger updates for a `withdraw` record.

Bash shortcut:
```bash
poetry run wayfinder hyperliquid --action wait_for_withdrawal --wallet_label main
```

If you need strict "arrived on Arbitrum" confirmation, add an Arbitrum-side receipt check (RPC/Explorer) for the resulting tx hash.

## Orchestration tips

- **Hyperliquid funding is paid hourly**; if you're rate-locking funding with Boros, align your observations to this cadence.
- Prefer explicit "funding stages" in strategies:
  1) deposit to Hyperliquid
  2) wait for credit
  3) open/adjust hedge
  4) only then deploy spot/yield legs


## Trading

### Market Orders

```bash
# Market buy
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin ETH --is_buy true --usd_amount 200 --usd_amount_kind margin --leverage 5

# Market sell / short
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin ETH --is_buy false --usd_amount 200 --usd_amount_kind margin --leverage 5
```

### Limit Orders

```bash
# Limit buy
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin ETH --is_buy true --size 0.1 --price 3000 --order_type limit

# Limit sell
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin ETH --is_buy false --size 0.1 --price 4000 --order_type limit
```

### Close Position

```bash
# Close with reduce-only
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin ETH --is_buy false --size 0.5 --reduce_only
```

### Leverage

```bash
# Update leverage (cross margin)
poetry run wayfinder hyperliquid_execute --action update_leverage --wallet_label main \
  --coin ETH --leverage 5 --is_cross

# Update leverage (isolated margin)
poetry run wayfinder hyperliquid_execute --action update_leverage --wallet_label main \
  --coin ETH --leverage 5 --no-is_cross
```

### Cancel Orders

```bash
# Cancel by order ID
poetry run wayfinder hyperliquid_execute --action cancel_order --wallet_label main \
  --coin ETH --order_id 12345

# Cancel by client order ID
poetry run wayfinder hyperliquid_execute --action cancel_order --wallet_label main \
  --coin ETH --cancel_cloid my-order-1
```

## Transfers

### Internal Transfers

USDC transfers between spot and perp wallets are available via `hyperliquid_execute`:

```bash
# Move USDC from spot wallet to perp wallet
poetry run wayfinder hyperliquid_execute --action spot_to_perp_transfer --wallet_label main --amount_usdc 50

# Move USDC from perp wallet to spot wallet
poetry run wayfinder hyperliquid_execute --action perp_to_spot_transfer --wallet_label main --amount_usdc 50
```

> **Note:** Other internal transfers (HyperCore→HyperEVM, arbitrary spot transfers) require a custom script via the [coding interface](coding-interface.md).

Available adapter methods for scripting: `transfer_spot_to_perp()`, `transfer_perp_to_spot()`, `spot_transfer()`, `hypercore_to_hyperevm()`.


### Deposit USDC to Hyperliquid

```bash
# Deposit
poetry run wayfinder execute --kind hyperliquid_deposit --wallet_label main --amount 100

# Wait for deposit to arrive (polls perp margin increase)
poetry run wayfinder hyperliquid --action wait_for_deposit --wallet_label main \
  --expected_increase 100 --timeout_s 300
```

### Withdraw USDC from Hyperliquid

```bash
# Withdraw
poetry run wayfinder hyperliquid_execute --action withdraw --wallet_label main \
  --amount_usdc 100

# Wait for withdrawal to settle (polls ledger for withdraw record)
poetry run wayfinder hyperliquid --action wait_for_withdrawal --wallet_label main
```

### Orchestration Tips

- Always poll for confirmation and time out safely before taking downstream risk.
- Hyperliquid funding is paid hourly — if rate-locking with Boros, align observations to this cadence.
- Prefer explicit funding stages: deposit → wait for credit → open hedge → then deploy other legs.

## Sizing

When a user says "$X at Yx leverage", clarify intent:

| `--usd_amount_kind` | Meaning | Example ($200 at 5x) |
|---------------------|---------|----------------------|
| `margin` | $X is collateral | Notional = $1,000 |
| `notional` | $X is position size | Margin = $40 |

## Execution Architecture

- **Read methods** work with the `Info` client only — no executor needed.
- **Write methods** require a `HyperliquidExecutor` with signing configured. Without it, execution methods raise `NotImplementedError`.

### Execution Methods

| Method | Purpose |
|--------|---------|
| `place_market_order(asset_id, is_buy, slippage, size, ...)` | Market order |
| `place_limit_order(asset_id, is_buy, price, size, ...)` | Limit order |
| `place_stop_loss(asset_id, is_buy, trigger_price, size, ...)` | Stop loss |
| `cancel_order(asset_id, order_id, ...)` | Cancel by order ID |
| `cancel_order_by_cloid(asset_id, cloid, ...)` | Cancel by client order ID |
| `update_leverage(asset_id, leverage, is_cross, ...)` | Set leverage + margin mode |
| `approve_builder_fee(builder, max_fee_rate, ...)` | Approve builder fee |
| `withdraw(amount, address)` | USDC withdraw to Arbitrum |

### Builder Fee

Builder attribution uses a fixed wallet [REDACTED_SECRET]. Fee value `f` is in **tenths of a basis point** (e.g. `30` = 0.030%). Set in `config.json` under `strategy.builder_fee`. The CLI auto-approves if needed.

## Spot Orders

For spot trading, you **must** set `is_spot` explicitly when using `hyperliquid_execute`:

```bash
# Spot buy
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin HYPE --is_spot true --is_buy true --usd_amount 20

# Perp buy (default)
poetry run wayfinder hyperliquid_execute --action place_order --wallet_label main \
  --coin HYPE --is_spot false --is_buy true --usd_amount 20 --usd_amount_kind notional
```

**Available spot pairs are limited.** Common assets like BTC and ETH are NOT directly available. Use wrapped versions:
- `UBTC/USDC` for wrapped BTC
- `UETH/USDC` for wrapped ETH
- `HYPE/USDC` is native and available

Spot orders don't use leverage — `usd_amount` is always treated as notional. `leverage` and `reduce_only` are ignored for spot.

**Spot balance location:** Spot tokens live in your spot wallet, separate from perp margin. Use scripts with `transfer_spot_to_perp()` / `transfer_perp_to_spot()` to move USDC between them.

## Gotchas

- **Minimum amounts**: Deposits require **>= $5 USDC** (below $5 is lost). All orders (perp and spot) require a minimum of **$10 USD notional**.
- **Asset IDs**: Perp assets: `asset_id < 10000`. Spot assets: `asset_id >= 10000` (spot_index = asset_id - 10000).
- **Spot naming quirks**: Spot index 0 uses `"PURR/USDC"`, otherwise `"@{spot_index}"`. Use `get_spot_assets()` for the mapping.
- **`is_spot` must be explicit**: When placing orders, `is_spot=True` for spot, `is_spot=False` for perp. Omitting returns an error.
- **Funding**: Funding is paid/received every hour. Use `resource wayfinder://hyperliquid/markets` for current funding rates.
- **Slippage**: Default slippage is applied to market orders. Override with `--slippage` (as a decimal, e.g., 0.01 = 1%).
- **No guessing**: Do not invent funding rates or prices. Always fetch via adapter and label timestamps.
- **USD sizing ambiguity**: When a user says "$X at Yx leverage", always clarify if $X is notional (position size) or margin (collateral). See the Sizing table above.
- **Builder fee approvals**: Builder fees are opt-in per user/builder pair. Fee value `f` is in **tenths of a basis point** (e.g. `30` = 0.030%). The CLI auto-approves if needed.
- **Funding history**: There is no `HyperliquidAdapter.get_funding_history()` — use `HyperliquidDataClient` or the SDK's `Info.funding_history()` directly.


path: references/boros.md
content: # Boros

## Overview

Boros provides fixed-rate markets on Arbitrum. It allows locking in a fixed funding rate for delta-neutral strategies, removing variable rate risk.

- **Type**: `BOROS`
- **Module**: `wayfinder_paths.adapters.boros_adapter.adapter.BorosAdapter`
- **Capabilities**: `market.read`, `market.quote`, `position.open`, `position.close`, `collateral.deposit`, `collateral.withdraw`

## Market Data

```bash
# Describe Boros adapter
poetry run wayfinder resource wayfinder://adapters/boros_adapter

# Discover positions
poetry run wayfinder wallets --action discover_portfolio --wallet_label main --protocols '["boros"]'
```

## Execution

Boros operations are executed via one-off scripts:

```bash
# Run a Boros script (dry run)
poetry run wayfinder run_script --script_path .wayfinder_runs/boros_lock_rate.py --wallet_label main

# Run live
poetry run wayfinder run_script --script_path .wayfinder_runs/boros_lock_rate.py --wallet_label main --force
```

### Script Example

```python
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.boros_adapter import BorosAdapter

adapter = get_adapter(BorosAdapter, "main")
markets = await adapter.discover_markets()
```

## Key Concepts

- **Yield Units (YU)**: The core trading unit. 1 YU ≈ $1 for USDT collateral; 1 YU = 1 HYPE (at unit price) for HYPE collateral. YU sizing is determined by margin formula, not 1:1 with collateral.
- **Implied APR**: The orderbook price — what the market expects the rate to be.
- **Underlying APR**: The actual funding rate that settles (e.g., Hyperliquid hourly funding).
- **Settlement cadence**: Mirrors the underlying venue — hourly for Hyperliquid, 8-hour for Binance/OKX. Must sample funding and rates on the same cadence.
- **Margin types**: **Cross** (shared across all positions) vs **Isolated** (locked to a specific market).
- **Collateral types**: WBTC (1), WETH (2), USDT (3), BNB (4), HYPE (5).
- **Funding sign convention**: Negative = shorts pay longs (longs receive). Positive = longs pay shorts.

### Rate Locking Recipes

- **Short hedge** (you're short a perp with positive funding): Open a **SHORT YU** on Boros to lock fixed funding you're paying.
- **Long hedge** (you're long a perp with positive funding): Open a **LONG YU** on Boros to lock fixed funding payment.
- "Current rate" = `BorosMarketQuote.mid_apr` — always fetch fresh via adapter.

## High-Value Reads

All BorosAdapter methods return `tuple[bool, result]` — always unpack. All fields use **snake_case** (not camelCase).

| Method | Purpose | Best For |
|--------|---------|----------|
| `list_tenor_quotes(underlying_symbol, platform)` | Fast market+rate snapshot (no orderbooks) | Quick tenor-level APR scan |
| `quote_market(market)` / `quote_market_by_id(market_id)` | Detailed APR quote with orderbook data | Single market analysis |
| `quote_markets_for_underlying(underlying_symbol)` | Quotes across all tenors for an underlying | Tenor curve building |
| `list_markets()` / `list_markets_all()` | Discover all markets (auto-paginates) | Market discovery |
| `get_orderbook(market_id, tick_size)` | Raw orderbook snapshot | Slippage estimation |
| `get_assets()` / `get_asset_by_token_id(token_id)` | Collateral asset addresses and metadata | Token address lookups |
| `list_available_underlyings(active_only)` | Unique underlying symbols with market counts | What's tradeable |
| `list_markets_by_collateral(token_id)` | Filter markets by collateral type | Collateral-specific queries |
| `get_enriched_market(market_id)` | Single market with all metadata joined | Full market context |
| `get_market_history(market_id, time_frame)` | OHLCV + rate history (`5m`, `1h`, `1d`, `1w`) | Historical analysis |

### Quote Fields (BorosMarketQuote)

- `mid_apr`, `best_bid_apr`, `best_ask_apr` — current implied fixed rates
- `mark_apr`, `floating_apr`, `long_yield_apr` — mark and floating rates
- `funding_7d_ma_apr`, `funding_30d_ma_apr` — moving averages
- `volume_24h`, `notional_oi`, `asset_mark_price` — market data

### Account State Reads (MUST check before trading)

**Always fetch current state before suggesting or executing any Boros trade:**

| Method | Purpose |
|--------|---------|
| `get_active_positions()` | Existing rate positions |
| `get_account_balances(token_id)` | Collateral summary (isolated/cross/total) |
| `get_collaterals()` | Full raw collateral data |
| `get_open_limit_orders()` | Pending limit orders |
| `get_withdrawal_status()` / `get_pending_withdrawal_amount()` | Withdrawal state |

**Why check first:** Avoid duplicate positions, unnecessary deposits, or trading with pending withdrawals.

## Collateral Types

| token_id | Token | Decimals | How to acquire on Arbitrum |
|----------|-------|----------|---------------------------|
| 1 | WBTC | 8 | BRAP swap |
| 2 | WETH | 18 | BRAP swap |
| 3 | USDT | 6 | BRAP swap to `usdt0-arbitrum` |
| 4 | BNB | 18 | BRAP swap |
| 5 | HYPE | 18 | OFT bridge from HyperEVM |

Each market accepts a specific collateral — check `market["tokenId"]` to know which one. Use `get_assets()` or `get_asset_by_token_id()` to get token addresses dynamically.

## YU Sizing (critical for order placement)

| Collateral | YU Meaning | $50 Position |
|------------|------------|--------------|
| USDT (token_id=3) | 1 YU ≈ $1 | `size_yu = 50` |
| HYPE (token_id=5) | 1 YU = 1 HYPE | `size_yu = 50 / hype_price` |

**Do NOT** set `target_yu = deposit_amount` — collateral does not cap YU 1:1. Max YU is determined by the margin formula. Apply a safety buffer (e.g., 50-70% of theoretical max) to avoid liquidation.

## Rate Locking Flow

1. **Pre-trade check** — Always fetch positions, balances, and collateral state first
2. **Discover markets** — Find available markets and tenors
3. **Get quote** — Check current fixed rates for your desired size (`mid_apr`)
4. **Check market collateral type** → acquire collateral if needed
5. **Deposit collateral** — Fund your Boros margin account
6. **Sweep isolated→cross** — If deposits land in isolated, sweep with `cash_transfer`
7. **Place order** — `place_rate_order(market_id, token_id, size_yu_wei, side, ...)`
8. **Monitor** — Track position until expiry or early close

## Gotchas

- **Units are not uniform**: Different calls use different decimals — native decimals vs 1e18 cash units vs YU. Always check what each method expects.
- **Collateral vs YU sizing**: Deposited collateral does NOT cap YU 1:1. Max YU is determined by margin formula.
- **Withdrawal cooldowns**: Withdrawals are two-step — request → cooldown period → finalize. Monitor withdrawal status.
- **Isolated cash issue**: Deposits can land in isolated margin even when requesting cross. Sweep with `cash_transfer`.
- **Min cross cash**: Some actions require minimum cross cash (`MMInsufficientMinCash` error).
- **Calldata sequencing**: Multi-tx payloads must execute sequentially (approve → deposit → place). Never parallelize.
- **Tick math**: Use adapter helpers for tick↔rate conversions. Don't compute manually.
- **Chain**: Boros operates on Arbitrum (42161). Ensure your wallet has Arbitrum ETH for gas.
- **HYPE acquisition paths**: Either BRAP→HyperEVM HYPE→OFT bridge, or Hyperliquid spot→HyperEVM→OFT bridge. OFT bridge requires `msg.value = amount + fee`, amounts rounded to `decimalConversionRate()`.
- **Markets endpoint**: `marketId` queries return lists. Underlying symbol lives at `metadata.assetSymbol`.
- **Funding sign convention**: Negative = shorts pay longs. Positive = longs pay shorts. Get this wrong and your hedge is backwards.


path: references/hyperlend.md
content: # HyperLend

## Overview

HyperLend is a lending protocol on HyperEVM. It provides stablecoin lending yield with straightforward supply/withdraw mechanics.

- **Type**: `HYPERLEND`
- **Module**: `wayfinder_paths.adapters.hyperlend_adapter.adapter.HyperlendAdapter`
- **Capabilities**: `market.stable_markets`, `market.assets_view`, `market.rate_history`, `lending.lend`, `lending.unlend`

## Market Data

```bash
# Describe HyperLend adapter
poetry run wayfinder resource wayfinder://adapters/hyperlend_adapter

# Discover positions
poetry run wayfinder wallets --action discover_portfolio --wallet_label main --protocols '["hyperlend"]'
```

### High-Value Reads

| Method | Purpose | Output |
|--------|---------|--------|
| `get_stable_markets(chain_id, ...)` | Opportunity list | `list[StableMarket]` — `chain_id`, `token_address`, `symbol`, liquidity/buffer fields |
| `get_assets_view(chain_id, user_address)` | Portfolio view | `AssetsView` — `assets: list[dict]`, optional `total_value` |
| `get_lend_rate_history(chain_id, token_address, lookback_hours)` | Rate time series | `LendRateHistory` — `rates: list[dict]` (timestamped records) |

### Data Accuracy

- Only report values fetched from HyperLend endpoints (`market_entry`, `lend_rate_history`). Do **not** estimate or invent APYs.
- HyperlendClient methods use `_authed_request(...)` even for `/public/hyperlend/*` routes — auth via `config.json` or env vars is always required.

## Execution

HyperLend operations are executed via one-off scripts:

```bash
# Run a HyperLend script (dry run)
poetry run wayfinder run_script --script_path .wayfinder_runs/hyperlend_supply.py --wallet_label main

# Run live
poetry run wayfinder run_script --script_path .wayfinder_runs/hyperlend_supply.py --wallet_label main --force
```

### Script Example (lend)

```python
import asyncio

from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.hyperlend_adapter import HyperlendAdapter

CHAIN_ID = 999  # HyperEVM
UNDERLYING_TOKEN = "0x..."  # HyperEVM underlying token address
QTY = 100_000_000  # raw base units (example only)


async def main():
    adapter = get_adapter(HyperlendAdapter, "main")
    ok, tx_hash = await adapter.lend(
        underlying_token=UNDERLYING_TOKEN,
        qty=QTY,
        chain_id=CHAIN_ID,
    )
    print(ok, tx_hash)


if __name__ == "__main__":
    asyncio.run(main())
```

### Execution Methods

| Method | Purpose | Notes |
|--------|---------|-------|
| `lend(underlying_token, qty, chain_id, native=False)` | Supply to HyperLend | `qty` is raw int (wei); handles ERC20 approval automatically |
| `unlend(underlying_token, qty, chain_id, native=False)` | Withdraw from HyperLend | Same unit rules as `lend` |

### Unit Conversion Best Practice

1. Resolve token decimals via TokenClient
2. Convert human-readable → raw int units
3. Call `lend`/`unlend` with raw units

## HyperLend Stable Yield Strategy

For automated yield on HyperLend, use the dedicated strategy:

```bash
poetry run wayfinder run_strategy --strategy hyperlend_stable_yield_strategy --action status
poetry run wayfinder run_strategy --strategy hyperlend_stable_yield_strategy --action analyze --amount_usdc 100
poetry run wayfinder run_strategy --strategy hyperlend_stable_yield_strategy --action deposit --main_token_amount 100 --gas_token_amount 0.1
poetry run wayfinder run_strategy --strategy hyperlend_stable_yield_strategy --action update
```

## Gotchas

- **Chain**: HyperLend runs on HyperEVM. Ensure your wallet has HyperEVM gas tokens.
- **RPC resolution**: Don’t hardcode RPC URLs in scripts. The SDK resolves JSON-RPC via `web3_from_chain_id(999)` using `strategy.rpc_urls` when set, otherwise it falls back to Wayfinder’s RPC proxy at `system.api_base_url` (auth via `system.api_key` / `WAYFINDER_API_KEY`).
- **Units are raw ints**: `lend`/`unlend` expect **raw integer units** (wei for ERC20). Do not pass floats or human-readable values directly.
- **Supported assets**: Primarily stablecoins (USDT0). Check market snapshots for current offerings.
- **No guessing yields**: Do not claim extra yield sources (e.g. "~3-4% staking APY") unless fetched from a concrete data source. Rates change frequently.


path: references/coding-interface.md
content: # Coding Interface for Custom Operations

Use this guide when pre-built commands aren't sufficient and you need to write custom Python scripts for complex multi-step DeFi operations.

## When to Use Custom Scripts

- **Multi-step atomic flows** — operations that must succeed together or not at all
- **Custom logic** — conditional execution based on market state
- **Batched operations** — multiple protocol interactions in sequence
- **Complex calculations** — position sizing, rebalancing logic
- **Protocol combinations** — bridging multiple adapters in one flow

## Script Location

All scripts must live in the `.wayfinder_runs/` directory inside `$WAYFINDER_SDK_PATH` (default: `$HOME/wayfinder-paths-sdk`) (or `$WAYFINDER_RUNS_DIR`). This directory is:
- Git-ignored (except README.md)
- Sandboxed — `run_script` only executes scripts inside this directory
- Session-specific — use for one-off executions, not permanent strategies

```bash
mkdir -p "${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}/.wayfinder_runs"
```

## Referencing the SDK Source

Before writing a script, **pull the detailed reference docs** for the relevant adapter or strategy:

```bash
# Pull docs for the adapter you're using (reads from pinned SDK version in sdk-version.md)
./wayfinder/scripts/pull-sdk-ref.sh moonwell
./wayfinder/scripts/pull-sdk-ref.sh boros hyperliquid

# Available topics: strategies, setup, boros, brap, hyperlend, hyperliquid, polymarket, moonwell, pendle, uniswap, projectx, data
./wayfinder/scripts/pull-sdk-ref.sh --list

# Check the pinned SDK version
./wayfinder/scripts/pull-sdk-ref.sh --version

# Override the pinned version for a specific pull
./wayfinder/scripts/pull-sdk-ref.sh --commit abc123 moonwell
```

These docs cover method signatures, unit conventions, gotchas, execution patterns, and contract addresses. **Always pull them before writing a script.**

The SDK ref is tracked in `wayfinder/sdk-version.md`. The pull script checks out that ref when reading docs, then restores the SDK to its previous state.

You can also read the adapter source code directly:

```
$WAYFINDER_SDK_PATH/wayfinder_paths/adapters/          # All adapter implementations
$WAYFINDER_SDK_PATH/wayfinder_paths/mcp/scripting.py   # get_adapter() helper
$WAYFINDER_SDK_PATH/wayfinder_paths/strategies/        # Strategy implementations
```

Never guess adapter method names or parameters — read the reference docs or source first.

## Basic Script Structure

```python
#!/usr/bin/env python3
"""
Script: my_complex_operation.py
Description: Brief description of what this script does
"""
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter

async def main():
    # Your logic here
    pass

if __name__ == "__main__":
    asyncio.run(main())
```

## Using `get_adapter()`

The `get_adapter()` helper auto-wires configuration and signing:

```python
from wayfinder_paths.mcp.scripting import get_adapter

# Pattern: get_adapter(AdapterClass, wallet_label, **config_overrides)

# For write operations (need signing)
from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter
adapter = get_adapter(MoonwellAdapter, "main")

# For read-only operations (no wallet needed)
from wayfinder_paths.adapters.pool_adapter import PoolAdapter
pool_adapter = get_adapter(PoolAdapter)

# With config overrides
adapter = get_adapter(MoonwellAdapter, "main", config_overrides={"custom_key": "value"})
```

### What `get_adapter()` Does

1. Loads `config.json` from `$WAYFINDER_CONFIG_PATH`
2. Finds wallet by label from `config["wallets"]`
3. Extracts `private_key_hex` and creates a signing callback
4. Detects the adapter's signing callback parameter names
5. Passes everything to the adapter constructor

## Web3 / RPC Access

If you need raw on-chain reads/writes that aren’t exposed by an adapter, use the SDK’s chain helper:

```python
import asyncio

from wayfinder_paths.core.utils.web3 import web3_from_chain_id

CHAIN_ID = 8453  # Base


async def main():
    async with web3_from_chain_id(CHAIN_ID) as w3:
        block = await w3.eth.block_number
        print("block:", block)


if __name__ == "__main__":
    asyncio.run(main())
```

Do **not** hardcode RPC URLs in scripts. `web3_from_chain_id(...)` resolves RPCs from `strategy.rpc_urls` when set; otherwise it falls back to Wayfinder’s RPC proxy at `system.api_base_url` (auth via `system.api_key` / `WAYFINDER_API_KEY`).

## Adapter Quick Reference

### BalanceAdapter — Wallet Operations

```python
from wayfinder_paths.adapters.balance_adapter import BalanceAdapter

adapter = get_adapter(BalanceAdapter, "main")

# Read balances
success, balances = await adapter.get_balances(chain_id=8453)  # Base

# Transfer tokens
success, result = await adapter.transfer(
    token_address=[REDACTED_SECRET],  # USDC
    to_address="0x...",
    amount=100_000_000,  # 100 USDC (6 decimals)
    chain_id=8453
)
```

### TokenAdapter — Token Metadata

```python
from wayfinder_paths.adapters.token_adapter import TokenAdapter

adapter = get_adapter(TokenAdapter)

# Resolve token by ID or query
success, token = await adapter.get_token("usd-coin-base")
success, token = await adapter.get_token("USDC", chain_id=8453)

# Get gas token for chain
success, gas_token = await adapter.get_gas_token(chain_id=8453)

# Fuzzy search
success, results = await adapter.search_tokens("usdc", chain_id=8453)
```

### BRAPAdapter — Swaps and Bridges

```python
from wayfinder_paths.adapters.brap_adapter import BRAPAdapter

adapter = get_adapter(BRAPAdapter, "main")

# Get quote
success, quote = await adapter.get_quote(
    from_token_id="usd-coin-base",
    to_token_id="ethereum-base",
    amount=100.0,  # Human-readable
    wallet_address="0x..."
)

# Execute swap (if quote successful)
if success and quote.get("best_quote"):
    success, result = await adapter.execute_swap(quote["best_quote"])
```

### MoonwellAdapter — Lending Protocol

```python
from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter

adapter = get_adapter(MoonwellAdapter, "main")

# mToken addresses (Base)
USDC_MTOKEN = [REDACTED_SECRET]
WETH_MTOKEN = [REDACTED_SECRET]

# Supply (lend)
success, result = await adapter.lend(
    mtoken=USDC_MTOKEN,
    amount=100_000_000  # 100 USDC in wei
)

# Set as collateral
success, result = await adapter.set_collateral(mtoken=USDC_MTOKEN)

# Borrow
success, result = await adapter.borrow(
    mtoken=WETH_MTOKEN,
    amount=50_000_000_000_000_000  # 0.05 WETH in wei
)

# Repay
success, result = await adapter.repay(
    mtoken=WETH_MTOKEN,
    amount=50_000_000_000_000_000
)

# Withdraw (unlend)
success, result = await adapter.unlend(
    mtoken=USDC_MTOKEN,
    amount=100_000_000
)
```

### HyperliquidAdapter — Perps Trading

```python
from wayfinder_paths.adapters.hyperliquid_adapter import HyperliquidAdapter

adapter = get_adapter(HyperliquidAdapter, "main")

# Get account state
success, state = await adapter.get_user_state()

# Place market order
success, result = await adapter.place_order(
    coin="ETH",
    is_buy=True,
    sz=0.1,  # Size in base asset
    order_type="market",
    slippage=0.01  # 1%
)

# Place limit order
success, result = await adapter.place_order(
    coin="ETH",
    is_buy=True,
    sz=0.1,
    order_type="limit",
    limit_px=3000.0
)

# Update leverage
success, result = await adapter.update_leverage(
    coin="ETH",
    leverage=5,
    is_cross=True
)

# Close position (reduce-only)
success, result = await adapter.place_order(
    coin="ETH",
    is_buy=False,  # Opposite of position direction
    sz=0.1,
    order_type="market",
    reduce_only=True
)
```

### PendleAdapter — PT/YT Markets

```python
from wayfinder_paths.adapters.pendle_adapter import PendleAdapter

adapter = get_adapter(PendleAdapter, "main")

# Discover markets
success, markets = await adapter.get_markets(chain_id=8453)

# Get market time series
success, series = await adapter.get_market_time_series(
    market_address="0x...",
    chain_id=8453
)

# Execute swap via Pendle Hosted SDK
success, result = await adapter.swap(
    market_address="0x...",
    token_in="0x...",
    token_out="0x...",
    amount_in=100_000_000,
    chain_id=8453
)
```

### BorosAdapter — Fixed-Rate Markets

```python
from wayfinder_paths.adapters.boros_adapter import BorosAdapter

adapter = get_adapter(BorosAdapter, "main")

# Discover markets
success, markets = await adapter.get_markets()

# Quote a rate lock
success, quote = await adapter.quote(
    market_id="...",
    size=1000.0,
    direction="long"  # or "short"
)

# Open position
success, result = await adapter.open_position(
    market_id="...",
    size=1000.0,
    direction="long",
    max_rate=0.10  # Max acceptable rate
)
```

### HyperlendAdapter — HyperEVM Lending

```python
from wayfinder_paths.adapters.hyperlend_adapter import HyperlendAdapter

adapter = get_adapter(HyperlendAdapter, "main")

# Get market snapshot
success, snapshot = await adapter.get_market_snapshot()

# Supply
success, result = await adapter.supply(
    token="USDT0",
    amount=100_000_000  # 100 USDT0 in wei
)

# Withdraw
success, result = await adapter.withdraw(
    token="USDT0",
    amount=100_000_000
)
```

## Example: Complex Multi-Step Flow

```python
#!/usr/bin/env python3
"""
Script: moonwell_supply_and_borrow.py
Description: Supply USDC as collateral and borrow ETH on Moonwell
"""
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter

USDC_MTOKEN = [REDACTED_SECRET]
WETH_MTOKEN = [REDACTED_SECRET]

async def main():
    adapter = get_adapter(MoonwellAdapter, "main")

    # Step 1: Supply USDC
    print("Supplying 100 USDC...")
    success, result = await adapter.lend(mtoken=USDC_MTOKEN, amount=100_000_000)
    if not success:
        print(f"Supply failed: {result}")
        return
    print(f"Supply tx: {result}")

    # Step 2: Enable as collateral
    print("Enabling USDC as collateral...")
    success, result = await adapter.set_collateral(mtoken=USDC_MTOKEN)
    if not success:
        print(f"Set collateral failed: {result}")
        return
    print(f"Collateral tx: {result}")

    # Step 3: Borrow ETH (conservative amount)
    print("Borrowing 0.01 ETH...")
    success, result = await adapter.borrow(mtoken=WETH_MTOKEN, amount=10_000_000_000_000_000)
    if not success:
        print(f"Borrow failed: {result}")
        return
    print(f"Borrow tx: {result}")

    print("Done! Position opened successfully.")

if __name__ == "__main__":
    asyncio.run(main())
```

## Example: Conditional Execution Based on Market State

```python
#!/usr/bin/env python3
"""
Script: conditional_swap.py
Description: Only swap if price is favorable
"""
import asyncio
from decimal import Decimal
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.brap_adapter import BRAPAdapter
from wayfinder_paths.adapters.token_adapter import TokenAdapter

TARGET_RATE = Decimal("2500")  # Only swap if ETH < $2500

async def main():
    brap = get_adapter(BRAPAdapter, "main")
    [REDACTED_SECRET])

    # Get current ETH price
    success, eth_token = await token.get_token("ethereum-base")
    if not success:
        print(f"Failed to get ETH [REDACTED_SECRET]")
        return

    current_price = Decimal(str(eth_token.get("price_usd", 0)))
    print(f"Current ETH price: ${current_price}")

    if current_price >= TARGET_RATE:
        print(f"Price ${current_price} >= target ${TARGET_RATE}, skipping swap")
        return

    # Price is favorable, get quote
    success, quote = await brap.get_quote(
        from_token_id="usd-coin-base",
        to_token_id="ethereum-base",
        amount=100.0,
        wallet_address="0x..."  # Your address
    )

    if not success:
        print(f"Quote failed: {quote}")
        return

    print(f"Quote received: {quote}")

    # Execute swap
    if quote.get("best_quote"):
        success, result = await brap.execute_swap(quote["best_quote"])
        print(f"Swap result: {result}" if success else f"Swap failed: {result}")

if __name__ == "__main__":
    asyncio.run(main())
```

## Running Scripts

**Always test before live execution.** Follow this workflow:

### 1. Safe Run (recommended)

```bash
export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}"
cd "$WAYFINDER_SDK_PATH"

# Recommended: implement your own --dry-run / --force flags inside the script
poetry run wayfinder run_script --script_path .wayfinder_runs/my_script.py --args '["--dry-run"]' --wallet_label main
```

Review the output carefully — verify operations, amounts, and addresses are correct.

### 2. Live Execution (only after safe run passes)

```bash
export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}"
cd "$WAYFINDER_SDK_PATH"
poetry run wayfinder run_script --script_path .wayfinder_runs/my_script.py --args '["--force"]' --wallet_label main
```

**Never skip the safe-run step for scripts that move funds.**

### 3. Direct Execution (bypasses sandbox — use sparingly)

```bash
export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}"
cd "$WAYFINDER_SDK_PATH"
poetry run python .wayfinder_runs/my_script.py
```

## Return Pattern Convention

All adapter mutation methods return `(success: bool, result_or_error: Any)`:

```python
success, result = await adapter.some_method(...)
if success:
    # result contains the data (tx hash, response object, etc.)
    print(f"Success: {result}")
else:
    # result contains error message
    print(f"Error: {result}")
```

## Error Handling Best Practices

```python
async def safe_operation():
    adapter = get_adapter(SomeAdapter, "main")

    success, result = await adapter.risky_operation()
    if not success:
        # Log error, don't proceed
        print(f"Operation failed: {result}")
        return None

    # Continue with next step
    return result
```

## Environment Variables

Scripts inherit these from the execution environment:

| Variable | Purpose |
|----------|---------|
| `WAYFINDER_CONFIG_PATH` | Path to config.json |
| `WAYFINDER_RUNS_DIR` | Override runs directory |
| `WAYFINDER_API_KEY` | Fallback API key |

## Security Notes

1. **Scripts are sandboxed** — only `.wayfinder_runs/` scripts can be executed via `run_script`
2. **Private keys stay in config.json** — never hardcode keys in scripts
3. **Safe run first** — implement a non-fund-moving mode in your script and run it before `--force`
4. **Review before execution** — safety hooks show confirmation prompts for fund-moving operations


path: references/ccxt.md
content: # CCXT (Centralized Exchanges)

## Overview

The SDK includes a `ccxt_adapter` that acts as a **multi-exchange factory** for centralized exchanges (CEXes). Each configured exchange becomes a property on the adapter (e.g. `adapter.aster`, `adapter.binance`), and you call the CCXT unified API on that exchange object.

- **Type**: `CCXT`
- **Module**: `wayfinder_paths.adapters.ccxt_adapter.adapter.CCXTAdapter`
- **Capabilities**: `exchange.factory`

## When to use (and when not to)

- Use for CEX workflows (Aster, Binance, etc.) **when the user has API credentials** and explicitly wants centralized exchange data or trading.
- Do **not** use CCXT for Hyperliquid by default. Prefer the native Wayfinder Hyperliquid surfaces (`hyperliquid` resources + `hyperliquid_execute`) unless the user explicitly asks for CCXT/Hyperliquid.

## Config (`config.json`)

Add a `ccxt` section with exchange IDs and credentials (exchange IDs must match CCXT exchange ids):

```json
{
  "ccxt": {
    "aster": { "apiKey": "…", "secret": "…" },
    "binance": { "apiKey": "…", "secret": "…", "enableRateLimit": true },
    "hyperliquid": { "walletAddress": "0x...", "privateKey": "0x..." }
  }
}
```

Notes:
- Credentials are passed straight through to each CCXT exchange constructor; exchange-specific params (e.g. `password`, `uid`, `options`) are supported.
- Exchange IDs must match CCXT’s exchange ids (e.g. `binance`, `bybit`, `aster`, `hyperliquid`).

### Credentials quick reference

| Exchange | Required params |
|----------|----------------|
| binance | `apiKey`, `secret` |
| hyperliquid | `walletAddress`, `privateKey` |
| aster | `apiKey`, `secret` |
| bybit | `apiKey`, `secret` |
| dydx | `apiKey`, `secret`, `password` |

## Init patterns

### Config-driven (recommended)

```python
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.ccxt_adapter import CCXTAdapter

async def main():
    adapter = get_adapter(CCXTAdapter)
    try:
        ticker = await adapter.binance.fetch_ticker("BTC/USDT")
        print(ticker.get("last"))
    finally:
        await adapter.close()

asyncio.run(main())
```

### Explicit exchanges kwarg (no `config.json` required)

```python
import asyncio
from wayfinder_paths.adapters.ccxt_adapter import CCXTAdapter

async def main():
    adapter = CCXTAdapter(exchanges={"aster": {}, "binance": {}})
    try:
        ticker = await adapter.aster.fetch_ticker("ETH/USDT")
        print(ticker.get("last"))
    finally:
        await adapter.close()

asyncio.run(main())
```

The `exchanges=` kwarg takes priority over `config["ccxt"]`.

## Running CCXT scripts

CCXT is not exposed as a top-level `poetry run wayfinder` command. Use a one-off script via `run_script`.

```python
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.ccxt_adapter import CCXTAdapter

async def main():
    adapter = get_adapter(CCXTAdapter)
    try:
        ticker = await adapter.aster.fetch_ticker("ETH/USDT")
        print(ticker.get("last"))
    finally:
        await adapter.close()

if __name__ == "__main__":
    asyncio.run(main())
```

Run it:

```bash
poetry run wayfinder run_script --script_path .wayfinder_runs/ccxt_ticker.py --wallet_label main
```

## Examples

### Multi-exchange comparison

```python
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.ccxt_adapter import CCXTAdapter

async def main():
    adapter = get_adapter(CCXTAdapter)
    try:
        binance = await adapter.binance.fetch_ticker("ETH/USDT")
        aster = await adapter.aster.fetch_ticker("ETH/USDT")
        print("Binance:", binance.get("last"))
        print("Aster:  ", aster.get("last"))
    finally:
        await adapter.close()

asyncio.run(main())
```

### Hyperliquid via CCXT (when explicitly requested)

Hyperliquid defaults to swap/perps in CCXT; perp symbols use the `:USDC` suffix:

```python
import asyncio
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.ccxt_adapter import CCXTAdapter

async def main():
    adapter = get_adapter(CCXTAdapter)
    try:
        ticker = await adapter.hyperliquid.fetch_ticker("ETH/USDC:USDC")
        print("ETH perp last:", ticker.get("last"))
    finally:
        await adapter.close()

asyncio.run(main())
```

## Gotchas (read before writing automation)

- **Always close**: Each exchange holds open HTTP sessions; always `await adapter.close()` in a `finally`.
- **Hyperliquid auth**: Hyperliquid CCXT uses wallet auth (`walletAddress`, `privateKey`), not `apiKey`/`secret`.
- **Hyperliquid spot vs perp**: CCXT Hyperliquid defaults to `swap`. For spot, use spot symbols and set `params={"type":"spot"}` as needed.
- **Symbol formats vary**: Many perps/futures use suffixes like `ETH/USDT:USDT`. Always `await exchange.load_markets()` and inspect `exchange.markets` for available symbols.
- **Rate limits**: If you’re making many calls, set `enableRateLimit: true` (or manage concurrency yourself). Don’t double-throttle with both strict semaphores and `enableRateLimit`.
- **`get_adapter(CCXTAdapter)` with no `ccxt` config is “empty”**: If `config.json` has no `ccxt` section, `get_adapter(CCXTAdapter)` loads zero exchanges (accessing `adapter.binance` raises `AttributeError`). For public-data-only access, construct `CCXTAdapter(exchanges={...})` directly.
- **Prefer native Hyperliquid surfaces for reads**: For funding history/meta/orderbooks, prefer the SDK/Wayfinder Hyperliquid adapter and resources unless CCXT is explicitly required.
- **Exchange instances are properties**: `await adapter.binance.fetch_ticker(...)` is correct; `await adapter.fetch_ticker("binance", ...)` is not.
- **Aster quirks**:
  - Minimum order sizes can be large (e.g., BTC min ~0.001). Check `exchange.markets[symbol]["limits"]["amount"]["min"]` before ordering.
  - `fetch_balance()` may underreport futures margin; don’t hard-gate execution on it.
  - Market orders can return `status="open"` and `filled=0` initially; confirm via `fetch_positions()` after a short delay.

## Common CCXT calls

- `fetch_ticker(symbol)` / `fetch_order_book(symbol)`
- `fetch_balance()`
- `create_order(symbol, "market"|"limit", "buy"|"sell", amount, price?)`
- `cancel_order(id, symbol?)`
- `fetch_open_orders(symbol?)`

Always `await adapter.close()` to avoid leaking sessions.


path: references/pendle.md
content: # Pendle

## Overview

Pendle splits yield-bearing assets into Principal Tokens (PTs) and Yield Tokens (YTs). PTs offer fixed yield at a discount; YTs offer leveraged exposure to variable yield.

- **Type**: `PENDLE`
- **Module**: `wayfinder_paths.adapters.pendle_adapter.adapter.PendleAdapter`
- **Capabilities**: `pendle.markets.read`, `pendle.market.snapshot`, `pendle.market.history`, `pendle.prices.ohlcv`, `pendle.prices.assets`, `pendle.swap.quote`, `pendle.swap.best_pt`, `pendle.swap.execute`, `pendle.convert.quote`, `pendle.convert.best_pt`, `pendle.convert.execute`, `pendle.positions.database`, `pendle.limit_orders.taker.read`, `pendle.limit_orders.maker.read`, `pendle.limit_orders.maker.write`, `pendle.deployments.read`, `pendle.router_static.rates`

## PT vs YT Mental Model

- **PT (Principal Token)**: "Fixed yield" leg. `fixedApy` = Pendle's `impliedApy`. Buy PT to lock in a fixed rate at a discount.
- **YT (Yield Token)**: "Floating yield" leg. `floatingApy` = `underlyingApy - impliedApy`. Leveraged exposure to variable yield.

## Market Discovery

```bash
# Describe Pendle adapter capabilities
poetry run wayfinder resource wayfinder://adapters/pendle_adapter

# Discover via wallet positions
poetry run wayfinder wallets --action discover_portfolio --wallet_label main --protocols '["pendle"]'
```

### Discovery Methods

| Method | Returns | Best for |
|--------|---------|----------|
| `list_active_pt_yt_markets(chain)` | Flattened list with `fixedApy`, `underlyingApy`, `liquidityUsd`, `daysToExpiry` | Market discovery, scanners (RECOMMENDED) |
| `fetch_markets(chain_id)` | Raw API (data **nested under `details`**) | When you need all raw fields |
| `fetch_market_snapshot(chain_id, market)` | Single market state | Point-in-time checks |
| `fetch_market_history(chain_id, market)` | Time series | Historical analysis |

### Chain IDs

Pendle-supported chain strings in this SDK:

`ethereum` → `1`, `bsc` → `56`, `arbitrum` → `42161`, `base` → `8453`, `plasma` → `9745`, `hyperevm` → `999`

## Execution

Pendle swaps are executed via one-off scripts using the Pendle Hosted SDK:

```bash
# Run a Pendle script (dry run)
poetry run wayfinder run_script --script_path .wayfinder_runs/pendle_buy_pt.py --wallet_label main

# Run live
poetry run wayfinder run_script --script_path .wayfinder_runs/pendle_buy_pt.py --wallet_label main --force
```

### Execution Methods

| Method | Purpose |
|--------|---------|
| `execute_swap(chain, market_address, token_in, token_out, amount_in, slippage, ...)` | Full execution: quote → approvals → broadcast. Returns `(True, {"tx_hash": ..., "quote": ...})`. Requires `strategy_wallet_signing_callback`. |
| `sdk_swap_v2(...)` | Quote only — returns `tx`, `tokenApprovals`, and quote metadata (`amountOut`, `priceImpact`, `impliedApy`) |
| `build_best_pt_swap_tx(...)` | Auto-selects best PT by `effectiveApy`, filters by liquidity/volume/expiry, quotes up to `max_markets_to_quote` |

**`execute_swap` inputs:**
- `amount_in` — **string in raw base units** (convert using token decimals)
- `slippage` — **decimal fraction** (`0.01` = 1%)
- `token_in` / `token_out` — ERC20 addresses (PTs and YTs are both valid `token_out` targets)

### Script Example (swap USDC into PT)

```python
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.pendle_adapter import PendleAdapter

adapter = get_adapter(PendleAdapter, "main")

# Discover best markets
markets = await adapter.list_active_pt_yt_markets(chain="base", min_liquidity_usd=250_000, sort_by="fixed_apy", descending=True)
market = markets[0]

# Execute swap into PT (amount_in is STRING in raw base units)
success, result = await adapter.execute_swap(
    chain="base",
    market_address=market["marketAddress"],
    token_in=[REDACTED_SECRET],  # USDC
    token_out=market["ptAddress"],
    amount_in="1000000",  # 1 USDC (6 decimals)
    slippage=0.01,  # 1% as decimal fraction
)
```

## Gotchas

- **`fetch_markets()` vs `list_active_pt_yt_markets()` (CRITICAL)**: `fetch_markets()` nests data under `details` — `m.get("impliedApy")` returns 0! Use `m.get("details", {}).get("impliedApy")`. `list_active_pt_yt_markets()` returns **flattened** data with renamed fields (e.g. `fixedApy`). **Always prefer `list_active_pt_yt_markets()` for discovery.**
- **Units are raw strings**: Hosted SDK expects `amountIn` in **raw base units as a string**. Resolve token decimals and convert explicitly.
- **Address formats**: `fetch_markets()` returns IDs like `"42161-0xabc..."`. `list_active_pt_yt_markets()` normalizes to plain `0x...` addresses.
- **Chain IDs**: Accepts both `chain=42161` and `chain="arbitrum"`.
- **Expiry**: PTs have fixed expiry dates. After expiry, PTs can be redeemed 1:1 for the underlying.
- **Liquidity**: Check pool liquidity and volume before large trades — thin pools have high slippage.
- **Approvals**: `execute_swap()` handles approvals automatically. For `sdk_swap_v2()`, you must execute `tokenApprovals` yourself before broadcasting.
- **Quote fields optional**: Hosted SDK may omit `effectiveApy`/`impliedApy` depending on market state. Always use `.get()` with defaults.
- **Receiver vs signer**: If `receiver != signer`, treat as high-risk and require explicit user confirmation.


path: references/polymarket.md
content: # Polymarket

## Overview

Polymarket is a prediction market platform. The Polymarket adapter supports:
- Market discovery (search/trending)
- Market/event details
- Prices, order books, and price history
- User status (balances, positions, orders)
- Collateral bridging and trade execution (requires signing wallet)

- **Type**: `POLYMARKET`
- **Module**: `wayfinder_paths.adapters.polymarket_adapter.adapter.PolymarketAdapter`
- **Capabilities**: `market.read`, `market.search`, `market.orderbook`, `market.candles`, `position.read`, `order.execute`, `order.cancel`, `bridge.deposit`, `bridge.withdraw`

## Read-Only Actions (CLI)

Polymarket reads use the `polymarket` tool (not `resource` URIs):

```bash
# Search markets
poetry run wayfinder polymarket --action search --query "bitcoin above" --limit 5

# Trending markets (by 24h volume)
poetry run wayfinder polymarket --action trending --limit 10

# Market details
poetry run wayfinder polymarket --action get_market --market_slug "some-market-slug"

# Account status (positions + balances; provide wallet_label or account)
poetry run wayfinder polymarket --action status --wallet_label main

# CLOB order book + price for a specific token_id
poetry run wayfinder polymarket --action order_book --token_id 123456
poetry run wayfinder polymarket --action price --token_id 123456 --side BUY
```

## Execution (CLI) — Live

Polymarket execution uses `polymarket_execute` and is **always live** (no dry-run flag).

**Wallet requirement:** `wallet_label` must resolve to a wallet in `config.json` that includes **both** `address` and `private_key_hex` (local dev only).

```bash
# Bridge USDC -> USDC.e collateral (Polymarket)
poetry run wayfinder polymarket_execute --action bridge_deposit --wallet_label main --amount 10

# Buy shares by market slug + outcome
poetry run wayfinder polymarket_execute --action buy --wallet_label main --market_slug "some-market-slug" --outcome YES --amount_usdc 2

# Close position (convenience: sells full size for the resolved token_id)
poetry run wayfinder polymarket_execute --action close_position --wallet_label main --market_slug "some-market-slug" --outcome YES
```

## Gotchas

- **USDC vs USDC.e (collateral mismatch):** Polymarket trading collateral is **USDC.e** on Polygon ([REDACTED_SECRET], 6 decimals), not native Polygon USDC ([REDACTED_SECRET]). Use `bridge_deposit` / `bridge_withdraw` to convert.
- **Bridge utilities (BRAP vs bridge service):** `bridge_deposit` (USDC → USDC.e) and `bridge_withdraw` (USDC.e → USDC/destination token) prefer a fast on-chain BRAP swap on Polygon when possible (sender == recipient). Otherwise they fall back to the Polymarket Bridge service; the result includes `method: "polymarket_bridge"` and you may need to poll `polymarket --action bridge_status` until it clears.
- **`market_slug` vs `token_id`:** You can trade using `market_slug`+`outcome` or directly via the CLOB `token_id`. Prefer `market_slug` when possible.
- **Market found but not tradable:** Filter for `enableOrderBook`, `acceptingOrders`, `active`, `closed != true`, and non-empty `clobTokenIds`. Fallback to `trending` when fuzzy search returns stale/closed items.
- **Outcomes are not always YES/NO:** Some markets are multi-outcome. If `YES` doesn’t exist, retry with `outcome=0` (first outcome) or pick the exact outcome string from the market’s outcomes list.
- **Approvals:** Trading requires on-chain approvals on Polygon. These are handled automatically before order placement (idempotent).
- **Bridging:** Collateral flows may involve USDC/USDC.e conversions. Always verify balances via `polymarket --action status` after bridge operations.
- **Open orders:** `polymarket --action open_orders` requires a signing wallet (private key) due to Level-2 auth. Optional: pass `--token_id` to filter.
- **Buy then immediately sell can fail:** CLOB settlement/match can lag; you may not have shares available to sell instantly. If chaining BUY → SELL, wait for the buy confirmation first.
- **Rate limiting:** Avoid large concurrent scans of `price_history`. Use a semaphore (e.g. 4–8 concurrent calls) and retry/backoff on 429s.
- **Token IDs aren’t ERC20 addresses:** `clobTokenIds` are CLOB identifiers. Outcome shares are ERC1155 positions under ConditionalTokens.
- **Redemption requires `conditionId`:** Resolved markets redeem via ConditionalTokens `redeemPositions()` using the market’s `conditionId`.


path: references/moonwell.md
content: # Moonwell

## Overview

Moonwell is a lending/borrowing protocol on Base. The Moonwell adapter provides market data reads and execution for lend/borrow/collateral operations.

- **Type**: `MOONWELL`
- **Module**: `wayfinder_paths.adapters.moonwell_adapter.adapter.MoonwellAdapter`
- **Capabilities**: `lending.lend`, `lending.unlend`, `lending.borrow`, `lending.repay`, `collateral.set`, `collateral.remove`, `rewards.claim`, `position.read`, `market.apy`, `market.collateral_factor`

## Market Data

```bash
# Describe the adapter (capabilities + market info)
poetry run wayfinder resource wayfinder://adapters/moonwell_adapter

# Check positions via wallet discovery
poetry run wayfinder wallets --action discover_portfolio --wallet_label main --protocols '["moonwell"]'
```

### Key Read Methods

| Method | Purpose | Wallet needed? |
|--------|---------|----------------|
| `get_apy(mtoken, apy_type, include_rewards)` | Supply/borrow APY | No |
| `get_collateral_factor(mtoken)` | Collateral factor (e.g. 0.88) | No |
| `get_pos(mtoken, account?, include_usd?)` | Single market position | Yes (or pass account) |
| `get_full_user_state(account?, include_rewards?, include_usd?, include_apy?)` | All positions + rewards | Yes (or pass account) |
| `is_market_entered(mtoken, account?)` | Check if collateral enabled | Yes (or pass account) |
| `get_borrowable_amount(account?)` | Account liquidity (USD) | Yes (or pass account) |
| `max_withdrawable_mtoken(mtoken, account?)` | Max withdraw without liquidation | Yes (or pass account) |

- **Comptroller**: [REDACTED_SECRET] (Base)
- Only report values fetched from Moonwell contracts. Do not invent or estimate APYs.

## Execution

Moonwell operations are executed via one-off scripts under `.wayfinder_runs/`:

```bash
# Run a Moonwell script (dry run)
poetry run wayfinder run_script --script_path .wayfinder_runs/moonwell_lend.py --wallet_label main

# Run live
poetry run wayfinder run_script --script_path .wayfinder_runs/moonwell_lend.py --wallet_label main --force
```

### Script Example (supply USDC)

```python
from wayfinder_paths.mcp.scripting import get_adapter
from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter

USDC_MTOKEN = [REDACTED_SECRET]
BASE_USDC = [REDACTED_SECRET]

adapter = get_adapter(MoonwellAdapter, "main")
ok, result = await adapter.lend(mtoken=USDC_MTOKEN, underlying_token=BASE_USDC, amount=10_000_000)  # 10 USDC
```

### Key Execution Methods

| Method | Purpose | Params |
|--------|---------|--------|
| `lend(mtoken, underlying_token, amount)` | Supply underlying | amount in raw units |
| `unlend(mtoken, amount)` | Withdraw (takes **mToken amount**, not underlying!) | amount in raw mToken units |
| `borrow(mtoken, amount)` | Borrow against collateral | amount in raw units |
| `repay(mtoken, underlying_token, amount, repay_full=False)` | Repay borrow | amount in raw units |
| `set_collateral(mtoken)` | Enable as collateral | — |
| `remove_collateral(mtoken)` | Disable collateral | — |
| `claim_rewards(min_rewards_usd?)` | Claim WELL rewards | returns dict of claimed rewards |
| `wrap_eth(amount)` | Wrap ETH to WETH | amount in raw units (wei) |

## Gotchas

- **mToken addresses, not underlying**: All adapter methods take **mToken addresses**, not underlying token addresses. `adapter.lend("0x833...", amount)` is wrong — use `adapter.lend("0xEdc...", amount)`.
- **Units are raw ints**: All `amount` parameters are **raw int units** (USDC: 6 decimals, so 10 USDC = `10_000_000`; WETH: 18 decimals; mTokens: 8 decimals).
- **unlend() takes mToken amount**: `unlend()` calls `redeem()` expecting **mToken amount**, not underlying. Use `max_withdrawable_mtoken()` first to get the correct value.
- **Exchange rate scaling**: When manually converting: `underlying = mTokenBalance * exchangeRate / 1e18`.
- **Collateral must be explicitly enabled**: Supplying tokens does NOT auto-enable them as collateral. Call `set_collateral()` separately.
- **Check before borrow**: Always call `get_borrowable_amount()` before borrowing — returns account liquidity in USD. Reverts are expensive.
- **Two USDC markets on Base**: Main: [REDACTED_SECRET] (use this). Secondary: [REDACTED_SECRET].
- **Transaction receipts**: A tx hash does **not** mean success. The SDK waits for receipt and raises `TransactionRevertedError` when `status=0`. If a step reverts, stop and fix before proceeding.
- **Health factor**: Monitor health factor before borrowing — liquidation risk increases with leverage.
- **APY composition**: Supply APY includes base rate + WELL token rewards. Both are shown in market data.
- **`get_borrowable_amount()` has no mtoken param**: Returns account-level liquidity in USD, not per-market.
- **Script execution**: Always run scripts via `run_script` with `--wallet_label` so the wallet profile tracks the Moonwell interaction for portfolio discovery.

## Moonwell wstETH Loop Strategy

For automated leveraged wstETH yield via Moonwell, use the dedicated strategy:

```bash
poetry run wayfinder run_strategy --strategy moonwell_wsteth_loop_strategy --action status
poetry run wayfinder run_strategy --strategy moonwell_wsteth_loop_strategy --action analyze --amount_usdc 500
```


path: references/tokens-and-pools.md
content: # Tokens and Pools

> **RULE: Always search, never guess.** Before using any token in a command: for **native gas tokens** (ETH, HYPE) use `wayfinder://tokens/gas/<chain_code>`; for **ERC20 tokens** use `wayfinder://tokens/search/<chain_code>/<symbol>` and take the exact token ID from the result. Never fabricate token IDs — coingecko IDs are unpredictable. Never call `tokens/resolve` with a guessed ID.

## Token ID Format

Wayfinder uses `<coingecko_id>-<chain_code>` as the canonical token identifier format:

| Token | Chain | ID |
|-------|-------|----|
| USDC | Base | `usd-coin-base` |
| ETH | Arbitrum | `ethereum-arbitrum` |
| USDT | Arbitrum | `usdt0-arbitrum` |
| HYPE | HyperEVM | `hyperliquid-hyperevm` |
| WETH | Base | `weth-base` |
| wstETH | Base | `wrapped-steth-base` |

**Do NOT use symbol-chain** (e.g., `usdc-base` will fail). Use the coingecko ID.

### Chain-Scoped Address Format

For specific ERC20 contracts: `<chain_code>_<address>`

Example: [REDACTED_SECRET]

## Token Lookups

There are three separate token endpoints. **Always start with fuzzy search** — it hits a different API (`/blockchain/tokens/fuzzy/`) than resolve (`/blockchain/tokens/detail/`). Do NOT skip search and go straight to resolve with a guessed ID.

```bash
# Step 1: ALWAYS fuzzy search first (hits /blockchain/tokens/fuzzy/)
poetry run wayfinder resource wayfinder://tokens/search/base/usdc
poetry run wayfinder resource wayfinder://tokens/search/arbitrum/eth
poetry run wayfinder resource wayfinder://tokens/search/ethereum/eth

# Step 2: Use the exact token ID from search results with resolve (hits /blockchain/tokens/detail/)
poetry run wayfinder resource wayfinder://tokens/resolve/usd-coin-base

# For native gas tokens (ETH, HYPE, etc.) — use the gas endpoint (hits /blockchain/tokens/gas/)
poetry run wayfinder resource wayfinder://tokens/gas/base
poetry run wayfinder resource wayfinder://tokens/gas/arbitrum
poetry run wayfinder resource wayfinder://tokens/gas/ethereum
```

### Native Gas Tokens (ETH, HYPE)

Native gas tokens (like ETH on Ethereum mainnet, ETH on Base/Arbitrum, HYPE on HyperEVM) are **not regular ERC20 tokens** and may not have standard token IDs. For native tokens:

1. **Use the gas endpoint**: `resource wayfinder://tokens/gas/<chain_code>` — this returns the correct native token metadata (address, decimals, symbol)
2. **Or search first**: `resource wayfinder://tokens/search/<chain>/<symbol>` — the fuzzy search will find the right entry
3. **Do NOT guess** IDs like `ethereum-ethereum` or `ETH-mainnet` — these may not exist in the token database

**Known limitation — native gas tokens in swaps:** The swap/bridge tool (`execute` with `kind: swap`) may fail to resolve native gas tokens because the API can return `null` for the address field. The swap code expects a sentinel address (`0x0000...0000`) but doesn't normalize null → zero-address like the send code does. **Workaround:** When swapping from/to native ETH, use the wrapped ERC20 version instead (e.g., search for WETH on the target chain). For sends, native tokens work correctly via `[REDACTED_SECRET]"` with `chain_id`.

## Balance Reads

```bash
# Enriched balances (all tokens, all chains, with USD values)
poetry run wayfinder resource wayfinder://balances/main

# Recent activity / transaction history
poetry run wayfinder resource wayfinder://activity/main
```

### Balance Read Methods

| Method | Source | Purpose |
|--------|--------|---------|
| `BALANCE_CLIENT.get_enriched_wallet_balances(wallet_address, exclude_spam_tokens=True)` | Wayfinder API | Full portfolio view with USD totals + chain breakdown |
| `BALANCE_CLIENT.get_token_balance(wallet_address, token_id, human_readable=True)` | Wayfinder API | Single token balance |
| `BALANCE_CLIENT.get_pool_balance(pool_address, chain_id, user_address, human_readable=True)` | Wayfinder API | Pool/share balance |
| `BalanceAdapter.get_balance(wallet_address, token_id/token_address, chain_id)` | On-chain RPC | Raw ERC20 balance (int) |
| `BalanceAdapter.get_vault_wallet_balance(token_id)` | On-chain RPC | Strategy wallet balance (`token_id` is a Wayfinder identifier, e.g. `usd-coin-arbitrum`) |
| `BalanceAdapter.get_wallet_balances_multicall(assets, wallet_address)` | On-chain multicall | Batch balance reads (grouped by chain, returns `balance_raw`, `decimals`, optional `balance_decimal`) |

### Local Ledger Bookkeeping

The LedgerAdapter writes JSON into `.ledger/` (gitignored) for tracking swaps, deposits, and withdrawals. Treat ledger writes as best-effort — don't break strategy execution if ledger fails.

## Pool Discovery

```bash
# List all adapters (includes pool-related adapters)
poetry run wayfinder resource wayfinder://adapters

# Describe pool adapter
poetry run wayfinder resource wayfinder://adapters/pool_adapter
```

### Pool Read Methods

| Method | Purpose |
|--------|---------|
| `PoolAdapter.get_pools(chain_id=None, project=None)` | Broad discovery — returns matches with `apy`, `apyBase`, `apyReward`, `tvlUsd` |
| `PoolAdapter.get_pools_by_ids(pool_ids=[...])` | Shortlist by IDs — normalized entries |

### Pool Discovery Pattern

"Screen then validate":
1. Pull a broad list with `get_pools()`
2. Filter by chain + stablecoin-only + TVL floor
3. Only then request deeper analytics for the finalists

## Supported Chains

| Chain | Code | Chain ID |
|-------|------|----------|
| Base | `base` | 8453 |
| Arbitrum | `arbitrum` | 42161 |
| Ethereum | `ethereum` | 1 |
| HyperEVM | `hyperevm` | 999 |

## Token Metadata Notes

- **Always search for token IDs** — run `resource wayfinder://tokens/search/<chain>/<symbol>` before any operation. Never construct or guess token IDs. The coingecko ID portion is not derivable from the symbol.
- Treat API responses as schema-flexible — check key presence rather than assuming fields exist.
- Never assume decimals; always fetch and cache per token.
- Convert human units → raw units using `decimals` before building txs. Convert raw → human for reporting only.

## Token Resolution Workflow — MANDATORY

**CRITICAL: You MUST always search for tokens before using them. Never guess or construct token IDs.** Coingecko IDs are not derivable from token symbols (e.g., ETH = `ethereum`, USDC = `usd-coin`, HYPE = `hyperliquid`). Guessing will produce wrong IDs like `ETH-mainnet`, `ethereum-ethereum`, or `usdc-base` that will fail or resolve to the wrong token.

**Search and resolve use different API endpoints:**
- **Search** (`wayfinder://tokens/search/...`) → hits `/blockchain/tokens/fuzzy/` — fuzzy matching, returns candidates
- **Resolve** (`wayfinder://tokens/resolve/...`) → hits `/blockchain/tokens/detail/` — exact lookup by known ID
- **Gas** (`wayfinder://tokens/gas/...`) → hits `/blockchain/tokens/gas/` — native chain tokens

**Never call resolve with a guessed ID.** Always search first to get the correct ID, then use that ID with resolve.

When a user mentions a token, follow this workflow:

1. **Is it a native gas token (ETH, HYPE)?** → Use `resource wayfinder://tokens/gas/<chain_code>` first. This is the most reliable way to get native token metadata.
2. **Search by symbol + chain** — `resource wayfinder://tokens/search/<chain_code>/<symbol>`. This is the **required step** for any ERC20 token the user mentions, even common ones like USDC or WETH.
3. **Use the exact ID from the search result** — copy the token ID verbatim from the search response. Do not modify it or construct your own.
4. **Verify with resolve** — optionally confirm with `resource wayfinder://tokens/resolve/<token_id>` using the ID you got from search.
5. **Address** — if the user gives a contract address, use the chain-scoped format `<chain_code>_<address>`.

**Important chain codes**: `base`, `arbitrum`, `ethereum`, `hyperevm`. Note: `mainnet` is NOT a valid chain code — use `ethereum` for Ethereum mainnet.

Always confirm the resolved token before executing swaps or sends. Show the user: token name, chain, contract address (truncated), and decimals.

## Common Token Quick Reference

These are frequently used tokens. **Always verify via search before using** — do not rely on this table as a substitute for `resource wayfinder://tokens/search/<chain>/<symbol>`.

| Symbol | Chain | Canonical ID | Decimals | Notes |
|--------|-------|-------------|----------|-------|
| USDC | Base | `usd-coin-base` | 6 | Primary stablecoin |
| USDC | Arbitrum | `usd-coin-arbitrum` | 6 | |
| ETH | Base | `ethereum-base` | 18 | Use `tokens/gas/base` for native |
| ETH | Arbitrum | `ethereum-arbitrum` | 18 | Use `tokens/gas/arbitrum` for native |
| ETH | Ethereum | — | 18 | **Use `tokens/gas/ethereum`** — native gas token, no standard token ID |
| WETH | Base | `weth-base` | 18 | Wrapped ETH (ERC20) |
| wstETH | Base | `wrapped-steth-base` | 18 | Lido wrapped staked ETH |
| USDT0 | Arbitrum | `usdt0-arbitrum` | 6 | Tether on Arbitrum |
| HYPE | HyperEVM | `hyperliquid-hyperevm` | 18 | Use `tokens/gas/hyperevm` for native |

### Native gas tokens

For native chain tokens (ETH, HYPE), always use the gas endpoint:

```bash
poetry run wayfinder resource wayfinder://tokens/gas/ethereum   # ETH on Ethereum
poetry run wayfinder resource wayfinder://tokens/gas/base       # ETH on Base
poetry run wayfinder resource wayfinder://tokens/gas/arbitrum   # ETH on Arbitrum
poetry run wayfinder resource wayfinder://tokens/gas/hyperevm   # HYPE on HyperEVM
```

## Presenting Token Data to Users

- **Always show the human-readable symbol** alongside the canonical ID when confirming actions
- **Include the chain** — "USDC on Base" not just "USDC", since the same symbol exists on multiple chains
- **Show USD value** when available — `1,200.00 USDC ($1,200.00)` gives immediate context
- **For unfamiliar tokens** — include the contract address so users can verify on a block explorer
- **Fuzzy results** — when showing fuzzy matches, list them as a numbered selection so the user can pick by number

## Gotchas

- **ALWAYS search before using any token** — run `resource wayfinder://tokens/search/<chain>/<query>` and use the token ID from the result. Never construct token IDs by guessing the coingecko ID. Even for common tokens like ETH, the coingecko ID (`ethereum`) is not obvious from the symbol.
- **Enriched balances** (`resource wayfinder://balances/<label>`) include USD valuations and are the best starting point for portfolio overview.
- **Activity** (`resource wayfinder://activity/<label>`) shows recent on-chain transactions recorded by the ledger adapter.
- **Token decimals matter for amounts** — 1 USDC = `1000000` (6 decimals), 1 ETH = `1000000000000000000` (18 decimals). The CLI handles conversion when you pass human-readable amounts, but be aware when reading raw on-chain values.
- **Same symbol, different tokens** — "USDC" on Base and "USDC" on Arbitrum are different tokens with different contract addresses. Always qualify with the chain.
- **Native ETH in swaps** — Swapping native ETH (not WETH) may fail with `from_token_address: null` because the API returns null for native gas token addresses and the swap code doesn't normalize it. Use WETH (the wrapped ERC20 version) for swaps instead. Native ETH works fine for sends (`[REDACTED_SECRET]"`).


path: references/strategies.md
content: # Strategies

## Strategy Interface

All strategies support these actions via `poetry run wayfinder run_strategy`:

### Read-Only Actions (no confirmation needed)

| Action | Description |
|--------|-------------|
| `status` | Current positions, balances, and state |
| `analyze` | Run strategy analysis with a given deposit amount |
| `snapshot` | Build batch snapshot for scoring |
| `policy` | Get strategy policies and constraints |
| `quote` | Get expected APY for a deposit amount |

### Fund-Moving Actions (require confirmation)

| Action | Description |
|--------|-------------|
| `deposit` | Add funds to the strategy (requires `--main_token_amount`) |
| `update` | Rebalance or execute the strategy logic |
| `withdraw` | **Liquidate**: Close all positions, convert to stablecoins (funds stay in strategy wallet) |
| `exit` | **Transfer**: Move funds from strategy wallet to main wallet (call after withdraw) |

## Available Strategies

### Basis Trading (`basis_trading_strategy`)

- **Status**: `stable`
- **Module**: `wayfinder_paths.strategies.basis_trading_strategy.strategy.BasisTradingStrategy`
- **Platform**: Hyperliquid
- **Token**: USDC
- **Risk**: Medium

Delta-neutral basis trading on Hyperliquid for funding rate capture. Opens matched positions:
- **Long Spot**: Buy the underlying asset (e.g., HYPE)
- **Short Perp**: Short the perpetual contract for the same asset

Price movements cancel out, and profit comes from collecting funding payments when longs pay shorts.

**Position Sizing** (given deposit `D` USDC and leverage `L`):
- Order Size: `D * (L / (L + 1))`
- Margin Reserved: `D / (L + 1)`

**Adapters Used**: BALANCE, LEDGER, TOKEN, HYPERLIQUID

```bash
poetry run wayfinder run_strategy --strategy basis_trading_strategy --action status
poetry run wayfinder run_strategy --strategy basis_trading_strategy --action analyze --amount_usdc 500
```

### Boros HYPE (`boros_hype_strategy`)

- **Status**: `stable`
- **Module**: `wayfinder_paths.strategies.boros_hype_strategy.strategy.BorosHypeStrategy`
- **Chains**: Arbitrum (42161), HyperEVM (999), Hyperliquid
- **Token**: HYPE/USDC
- **Risk**: Medium
- **Collateral Token (Arbitrum)**: LayerZero OFT HYPE at [REDACTED_SECRET]

Multi-leg HYPE yield strategy across Boros + HyperEVM + Hyperliquid. Holds spot HYPE, shorts perp on Hyperliquid, locks funding rate on Boros.

**Entry Flow**: Buys HYPE on Hyperliquid spot → withdraws to HyperEVM → bridges HyperEVM native HYPE → Arbitrum OFT HYPE for Boros collateral.

**Exit Gotchas**:
1. Boros withdraw delivers OFT HYPE on Arbitrum (no DEX liquidity)
2. Unwind path: Arbitrum OFT HYPE → (LayerZero) HyperEVM native HYPE → Hyperliquid spot → sell to USDC

**Adapters Used**: BALANCE, LEDGER, TOKEN, HYPERLIQUID, BOROS, BRAP

```bash
poetry run wayfinder run_strategy --strategy boros_hype_strategy --action status
poetry run wayfinder run_strategy --strategy boros_hype_strategy --action analyze --amount_usdc 1000
```

### HyperLend Stable Yield (`hyperlend_stable_yield_strategy`)

- **Status**: `stable`
- **Module**: `wayfinder_paths.strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy`
- **Chain**: HyperEVM (999)
- **Token**: USDT0
- **Risk**: Low

Stablecoin yield optimization on HyperLend. Allocates USDT0 across HyperLend stablecoin markets by:
1. Transferring USDT0 (plus HYPE gas buffer) from main wallet to strategy wallet
2. Sampling HyperLend hourly rate history
3. Running bootstrap tournament analysis to identify best-performing stablecoin
4. Swapping and supplying to HyperLend
5. Enforcing hysteresis rotation policy to prevent excessive churn

**Key Parameters**:
| Parameter | Value | Description |
|-----------|-------|-------------|
| `MIN_USDT0_DEPOSIT_AMOUNT` | 1 | Minimum deposit |
| `GAS_MAXIMUM` | 0.1 HYPE | Max gas per deposit |
| `HYSTERESIS_DWELL_HOURS` | 168 | Rotation cooldown |
| `APY_REBALANCE_THRESHOLD` | 0.0035 | 35 bps edge required to rotate |

**Adapters Used**: BALANCE, LEDGER, TOKEN, HYPERLEND, BRAP

```bash
poetry run wayfinder run_strategy --strategy hyperlend_stable_yield_strategy --action status
```

### Moonwell wstETH Loop (`moonwell_wsteth_loop_strategy`)

- **Status**: `stable`
- **Module**: `wayfinder_paths.strategies.moonwell_wsteth_loop_strategy.strategy.MoonwellWstethLoopStrategy`
- **Chain**: Base (8453)
- **Tokens**: USDC, WETH, wstETH
- **Risk**: Medium-High

Leveraged wstETH carry trade on Base via Moonwell:
1. Deposits USDC as initial collateral on Moonwell
2. Borrows WETH against the USDC collateral
3. Swaps WETH to wstETH via Aerodrome/BRAP
4. Lends wstETH back to Moonwell as additional collateral
5. Repeats the loop until target leverage is reached

The position is **delta-neutral**: WETH debt offsets wstETH collateral, so PnL is driven by the spread between wstETH staking yield and WETH borrow cost.

**Key Parameters**:
| Parameter | Value | Description |
|-----------|-------|-------------|
| `MIN_GAS` | 0.002 ETH | Minimum gas buffer |
| `MIN_USDC_DEPOSIT` | 20 USDC | Minimum initial collateral |
| `MAX_DEPEG` | 0.01 (1%) | Max stETH/ETH depeg threshold |
| `MIN_HEALTH_FACTOR` | 1.2 | Triggers deleveraging if below |
| `MAX_HEALTH_FACTOR` | 1.5 | Triggers leverage loop if above |
| `leverage_limit` | 10 | Maximum leverage multiplier |

**Adapters Used**: BALANCE, LEDGER, TOKEN, MOONWELL, BRAP

```bash
poetry run wayfinder run_strategy --strategy moonwell_wsteth_loop_strategy --action status
```

### Stablecoin Yield (`stablecoin_yield_strategy`)

- **Status**: `wip` (work in progress)
- **Module**: `wayfinder_paths.strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy`
- **Chain**: Base (8453)
- **Token**: USDC
- **Risk**: Low

Automated USDC yield optimization on Base chain:
1. Transfers USDC (plus ETH gas buffer) from main wallet to strategy wallet
2. Searches Base-native pools for the best USD-denominated APY
3. Monitors DeFi Llama feeds and Wayfinder pool analytics
4. Rebalances to higher-yield pools when APY improvements exceed thresholds
5. Respects rotation cooldowns to avoid excessive churn

**Key Parameters**:
| Parameter | Value | Description |
|-----------|-------|-------------|
| `MIN_AMOUNT_USDC` | 2 | Minimum deposit |
| `MIN_TVL` | 1,000,000 | Minimum pool TVL |
| `ROTATION_MIN_INTERVAL` | 14 days | Cooldown between rotations |
| `DUST_APY` | 0.01 (1%) | APY threshold below which pools are ignored |
| `MIN_GAS` | 0.001 ETH | Minimum gas buffer |

**Adapters Used**: BALANCE, LEDGER, TOKEN, POOL, BRAP

```bash
poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action status
poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action deposit --main_token_amount 100 --gas_token_amount 0.01
poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action update
```

### ProjectX THBILL/USDC (`projectx_thbill_usdc_strategy`)

- **Status**: `wip` (work in progress)
- **Module**: `wayfinder_paths.strategies.projectx_thbill_usdc_strategy.strategy.ProjectXThbillUsdcStrategy`
- **Chain**: HyperEVM (999)
- **Tokens**: THBILL, USDC
- **Risk**: Medium

Concentrated-liquidity market making on ProjectX (HyperEVM) for the THBILL/USDC stable pair. Pulls USDC from `main` wallet, swaps to an optimal split, mints/adds liquidity around the current tick, and periodically collects/compounds fees and recenters when out of range.

**Adapters Used**: BALANCE, LEDGER, TOKEN, PROJECTX

```bash
poetry run wayfinder run_strategy --strategy projectx_thbill_usdc_strategy --action status
poetry run wayfinder run_strategy --strategy projectx_thbill_usdc_strategy --action analyze --amount_usdc 1000
```

## Withdraw vs Exit

These are separate steps:

1. **`withdraw`** — Liquidates all positions and converts to stablecoins. Funds remain in the strategy wallet.
2. **`exit`** — Transfers funds from the strategy wallet back to the main wallet.

**Full exit flow:**
```bash
poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action withdraw
poetry run wayfinder run_strategy --strategy stablecoin_yield_strategy --action exit
```

## Strategy Development

### Data Sources Golden Rule

Strategies call **adapters** for domain actions. Clients are low-level wrappers:
`Strategy → Adapter → Client(s) → Network/API`

Never invent or "ballpark" rates/APYs/funding. Prefer a concrete adapter/client/tool call. If you can't fetch, say "unavailable" and show the exact call needed.

### Manifests

Each strategy directory has a `manifest.yaml` (source of truth) with `entrypoint`, `name`, `permissions.policy`, `adapters`. Validate with `just validate-manifests`.

### Testing Contract

- Maintain `examples.json` per strategy — load test inputs from it (never hardcode).
- Provide smoke coverage for lifecycle: `deposit → update → status → withdraw`.
- Optional methods: `quote()`, `analyze()`, `build_batch_snapshot()` for APY queries and batch scoring.

### Safety Patterns

- Wallets in `config.json` are matched by label (label == strategy directory name).
- Never commit private keys or live credentials.
- EVM execution goes through `send_transaction` (waits for receipts) and `ensure_allowance` (ERC20 approvals).
- Prefer MCP tools for side effects: `execute()`, `hyperliquid_execute()`, `run_script()`.

### Explore-First Approach

When exploring an unfamiliar strategy:
1. Start from its `manifest.yaml` (capabilities, entrypoint, dependencies)
2. Read its `examples.json` (expected inputs and runtime assumptions)
3. Prefer read-only calls first; only move to execution after validating inputs/units

## Discovering Strategies

Always discover before guessing strategy names:

```bash
# List all available strategies
poetry run wayfinder resource wayfinder://strategies

# Get detailed description for a specific strategy
poetry run wayfinder resource wayfinder://strategies/stablecoin_yield_strategy
```

Strategy names use `snake_case` (e.g., `boros_hype_strategy`, not `hype_boros_strategy`).


path: references/setup.md
content: # Setup Guide

## Prerequisites

- Python 3.12+
- Poetry
- Git

## Installation

**Important:** The SDK must be installed from GitHub via `git clone`. Do NOT install from PyPI (`pip install wayfinder-paths` will not work).

### 0. Get your API key

Get your Wayfinder API key at **https://strategies.wayfinder.ai** (format: `wk_...`). You will need this during setup.

### 1. Choose an SDK location (recommended)

```bash
# Path to your local clone of wayfinder-paths-sdk
export WAYFINDER_SDK_PATH="${WAYFINDER_SDK_PATH:-$HOME/wayfinder-paths-sdk}"

# Path to your config file
export WAYFINDER_CONFIG_PATH="${WAYFINDER_CONFIG_PATH:-$WAYFINDER_SDK_PATH/config.json}"
```

### 2. Clone the SDK from GitHub

```bash
# Must clone from GitHub — do NOT pip install
if [ ! -d "$WAYFINDER_SDK_PATH" ]; then
  git clone https://github.com/WayfinderFoundation/wayfinder-paths-sdk.git "$WAYFINDER_SDK_PATH"
fi
cd "$WAYFINDER_SDK_PATH"
```

### 3. Install Dependencies

```bash
cd "$WAYFINDER_SDK_PATH"
poetry install
```

### 4. Run Guided Setup

```bash
cd "$WAYFINDER_SDK_PATH"
python3 scripts/setup.py
```

Options:
- `--api-key KEY` — Provide API key non-interactively (key from https://strategies.wayfinder.ai)
- `--non-interactive` — Fail instead of prompting (for CI)

This creates:
- `config.json` with API key (and optional RPC overrides under `strategy.rpc_urls`)
- A `main` wallet for trading
- MCP server configuration

## Bot Wallets (Prefer Seed Phrase)

`scripts/setup.py` creates **random local dev wallets** by default (it writes `private_key_hex` into `config.json`). For a long-running bot, prefer a **single seed phrase** stored in your backend/secret manager so the wallet can be recovered/reprovisioned.

**CRITICAL: NEVER output private keys or seed phrases into the conversation.** These are secrets — they must stay on the machine, never in chat.

If this is the **first time** setting up a bot, the bot/operator flow should:
- Ask the user to **retrieve the seed phrase directly from their machine or secret manager** (recommended), if one already exists. Tell them where/how:
  1. SSH into the bot host and `cd "$WAYFINDER_SDK_PATH"` (default: `$HOME/wayfinder-paths-sdk`).
  2. Confirm the config location: `$WAYFINDER_CONFIG_PATH` (default: `$WAYFINDER_SDK_PATH/config.json`).
  3. Fetch the seed phrase from your secret manager (avoid printing/logging it).
  4. Use it to provision the bot wallet in `config.json` (label `main`) with derived `address` + `private_key_hex`.
- **Only if the user explicitly confirms they cannot access the machine to retrieve it themselves**, offer to generate a new seed phrase and display it once for the user to record, then store it in the backend/secret manager. Do not proactively offer to show it.

**Important:** Never log or paste the seed phrase into chat. Never output private keys into the conversation. Avoid printing secrets on shared servers where shells/logging/audit trails may capture them. If the bot host is lost/rotated and the seed phrase only existed on that box, you can lose access to the wallet forever. Always store the seed phrase in your backend/secret manager and ensure it's recoverable. After you have the seed phrase stored safely, provision the bot wallet into `config.json` under `wallets` (typically label `main`) with the derived `address` and `private_key_hex`.

### 5. API Key

Your API key (from **https://strategies.wayfinder.ai**, format: `wk_...`) should already be configured if you provided it during guided setup. If not:

API key precedence:
1. `config.json` → `system.api_key` (recommended)
2. Environment variable `WAYFINDER_API_KEY`
3. CLI flag `--api-key`

### 6. Verify

```bash
cd "$WAYFINDER_SDK_PATH"
poetry run wayfinder resource wayfinder://strategies
poetry run wayfinder resource wayfinder://wallets
poetry run wayfinder resource wayfinder://balances/main
```

## Configuration File

The main config lives at `$WAYFINDER_CONFIG_PATH` (default: `$WAYFINDER_SDK_PATH/config.json`). It contains:

- **API keys**: Wayfinder API key for pool/token data
- **RPC overrides**: Optional chain-specific RPC URLs under `strategy.rpc_urls` (if omitted, on-chain calls use Wayfinder’s RPC proxy at `system.api_base_url` with your API key)
- **Wallets**: Wallet labels, addresses, and private keys

### Manual Config

```bash
cd "$WAYFINDER_SDK_PATH"
cp config.example.json "$WAYFINDER_CONFIG_PATH"
# Edit $WAYFINDER_CONFIG_PATH with your settings
```

### Create Additional Wallets

```bash
# Generate test wallets
cd "$WAYFINDER_SDK_PATH"

# Create a labeled wallet
poetry run wayfinder wallets --action create --label my_strategy
```

## Environment Variables

| Variable | Purpose |
|----------|---------|
| `WAYFINDER_SDK_PATH` | Local path to `wayfinder-paths-sdk` |
| `WAYFINDER_CONFIG_PATH` | Path to `config.json` (default: `$WAYFINDER_SDK_PATH/config.json`) |
| `WAYFINDER_API_KEY` | API key (fallback if not in config.json) |

## Updating

```bash
cd "$WAYFINDER_SDK_PATH"
git pull
poetry install
```

## Development Workflow

Quick setup loop for strategy/adapter development:

```bash
cd "$WAYFINDER_SDK_PATH"
poetry install                  # Install deps
just create-wallets             # Generate local wallets
just validate-manifests         # Validate manifests
just test-smoke                 # Run smoke tests
```

## Troubleshooting

- **"Python 3.10 not supported"** — Ensure Python 3.12+ is installed and poetry uses it
- **"Missing config"** — Run `python3 scripts/setup.py` or create `config.json` manually
- **"api_key not set"** — Check `config.json` has `system.api_key`, or set `WAYFINDER_API_KEY` env var. Key format: `wk_...`
- **"poetry not found"** — Install poetry: `curl -sSL https://install.python-poetry.org | python3 -`
- **"just not found"** — Install just: `cargo install just` or `brew install just`
- **Key not working** — Verify at https://strategies.wayfinder.ai, check for typos/whitespace. Key should start with `wk_`. Verify with: `poetry run python -c "from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient; print('API key configured!')"`


path: references/adapters.md
content: # Adapters

## Overview

Adapters are protocol integrations that provide read and write capabilities. Strategies compose adapters to build trading logic.

## Discovering Adapters

```bash
# List all adapters with capabilities
poetry run wayfinder resource wayfinder://adapters

# Describe a specific adapter
poetry run wayfinder resource wayfinder://adapters/moonwell_adapter
```

## Adapter Reference

### Balance Adapter (`balance_adapter`)

- **Type**: `BALANCE`
- **Module**: `wayfinder_paths.adapters.balance_adapter.adapter.BalanceAdapter`
- **Protocol**: EVM wallets (Base, Arbitrum, Ethereum, HyperEVM)
- **Capabilities**: `balance.read`, `transfer.main_to_strategy`, `transfer.strategy_to_main`, `transfer.send`

Provides token balance queries for any wallet, cross-wallet transfers between main and strategy wallets, and automatic ledger recording for deposits/withdrawals.

```bash
poetry run wayfinder resource wayfinder://balances/main
poetry run wayfinder resource wayfinder://tokens/resolve/usd-coin-base
poetry run wayfinder resource wayfinder://activity/main
```

### BRAP Adapter (`brap_adapter`)

- **Type**: `BRAP`
- **Module**: `wayfinder_paths.adapters.brap_adapter.adapter.BRAPAdapter`
- **Protocol**: Cross-chain swap aggregator (Bridge/Router/Adapter Protocol)
- **Capabilities**: `swap.quote`, `swap.execute`, `swap.compare_routes`, `bridge.quote`, `gas.estimate`

Handles cross-chain swaps and bridges with quote fetching, route optimization, and execution.

```bash
poetry run wayfinder quote_swap --wallet_label main --from_token usd-coin-base --to_token ethereum-base --amount 100
poetry run wayfinder execute --kind swap --wallet_label main --from_token usd-coin-base --to_token ethereum-base --amount 100
```

**Key Methods:**
- `BRAPClient.get_quote(from_token, to_token, from_chain, to_chain, from_wallet, from_amount, slippage?)` — Low-level quote (amount in **raw base units** as string)
- `BRAPAdapter.best_quote(...)` — Returns single best route; supports `preferred_providers`
- `BRAPAdapter.swap_from_token_ids(from_token_id, to_token_id, from_address, amount, slippage, ...)` — Execute swap by token IDs
- `BRAPAdapter.swap_from_quote(from_token, to_token, from_address, quote, ...)` — Execute from a pre-fetched quote

**Recommended Quote Loop:**
1. Call `quote_swap` (does token lookup + human→raw conversion + returns preview)
2. Inspect `from_token`/`to_token` in response to verify correct asset + chain
3. Pass `suggested_execute_request` directly into `execute`

**BRAP Gotchas:**
- **Broadcast ≠ success**: A tx hash does not mean the swap succeeded. The SDK waits for receipt and raises `TransactionRevertedError` on `status=0`.
- **Units**: Quote input `from_amount` is **raw base units**. Resolve decimals via TokenClient first.
- **Slippage formats**: BRAP adapter uses **decimal fraction** (`0.005` = 0.5%). MCP may use bps (`50` = 0.5%). Don't mix.
- **Approvals**: Some tokens are "strict approve" and require setting allowance to 0 before increasing — the adapter has a built-in allowlist.
- **Recipient safety**: Treat `recipient != sender` as high-risk; require explicit user confirmation.
- **USD enrichment is best-effort**: USD fields in quotes may fail; rely on raw amounts for correctness.
- **Native token sends**: For tiny amounts use scientific notation (`"1e-18"`); long decimal strings may cause serialization errors.
- **Native sends**: Require `[REDACTED_SECRET]"` and `chain_id` in the request.

### CCXT Adapter (`ccxt_adapter`)

- **Type**: `CCXT`
- **Module**: `wayfinder_paths.adapters.ccxt_adapter.adapter.CCXTAdapter`
- **Protocol**: Centralized exchanges (CEXes) via CCXT
- **Capabilities**: `exchange.factory`

Use this only when the user explicitly wants CEX data/trading and has API credentials configured in `config.json` under `ccxt`. For Hyperliquid, prefer the native Hyperliquid tools/adapters unless the user explicitly asks for CCXT.

See [ccxt.md](ccxt.md) for setup + examples.

### Boros Adapter (`boros_adapter`)

- **Type**: `BOROS`
- **Module**: `wayfinder_paths.adapters.boros_adapter.adapter.BorosAdapter`
- **Protocol**: Boros (Arbitrum) - Fixed-rate funding markets
- **Capabilities**: `market.read`, `market.quote`, `position.open`, `position.close`, `collateral.deposit`, `collateral.withdraw`

Provides fixed-rate market discovery, quoting, orderbook data, deposits, withdrawals, and position management on Boros.

See [boros.md](boros.md) for details.

### HyperLend Adapter (`hyperlend_adapter`)

- **Type**: `HYPERLEND`
- **Module**: `wayfinder_paths.adapters.hyperlend_adapter.adapter.HyperlendAdapter`
- **Protocol**: HyperLend (HyperEVM)
- **Capabilities**: `market.stable_markets`, `market.assets_view`, `market.rate_history`, `lending.lend`, `lending.unlend`

Provides stable market snapshots, rate history time series, and stablecoin supply/withdraw operations on HyperLend.

See [hyperlend.md](hyperlend.md) for details.

### Hyperliquid Adapter (`hyperliquid_adapter`)

- **Type**: `HYPERLIQUID`
- **Module**: `wayfinder_paths.adapters.hyperliquid_adapter.adapter.HyperliquidAdapter`
- **Protocol**: Hyperliquid DEX
- **Capabilities**: `market.read`, `market.meta`, `market.funding`, `market.candles`, `market.orderbook`, `order.execute`, `order.cancel`, `position.manage`, `transfer`, `withdraw`

Comprehensive Hyperliquid integration for perp/spot state, funding rates, mid prices, order books, candles, market/limit orders, leverage, deposits, and withdrawals.

See [hyperliquid.md](hyperliquid.md) for details.

### Polymarket Adapter (`polymarket_adapter`)

- **Type**: `POLYMARKET`
- **Module**: `wayfinder_paths.adapters.polymarket_adapter.adapter.PolymarketAdapter`
- **Protocol**: Polymarket (prediction markets)
- **Capabilities**: `market.read`, `market.search`, `market.orderbook`, `market.candles`, `position.read`, `order.execute`, `order.cancel`, `bridge.deposit`, `bridge.withdraw`

Read Polymarket markets/events, prices and order books, and (with a signing key) place trades and bridge collateral.

**Read-only examples:**

```bash
poetry run wayfinder polymarket --action search --query "bitcoin above" --limit 5
poetry run wayfinder polymarket --action status --wallet_label main
```

**Execution examples (live):**

```bash
poetry run wayfinder polymarket_execute --action bridge_deposit --wallet_label main --amount 10
poetry run wayfinder polymarket_execute --action buy --wallet_label main --market_slug "some-market" --outcome YES --amount_usdc 2
```

See [polymarket.md](polymarket.md) for details.

### Ledger Adapter (`ledger_adapter`)

- **Type**: `LEDGER`
- **Module**: `wayfinder_paths.adapters.ledger_adapter.adapter.LedgerAdapter`
- **Protocol**: Local bookkeeping
- **Capabilities**: `ledger.read`, `ledger.record`, `ledger.snapshot`

Provides transaction history tracking, net deposit calculations, deposit/withdrawal recording, and strategy operation logging.

### Moonwell Adapter (`moonwell_adapter`)

- **Type**: `MOONWELL`
- **Module**: `wayfinder_paths.adapters.moonwell_adapter.adapter.MoonwellAdapter`
- **Protocol**: Moonwell (Base)
- **Capabilities**: `lending.lend`, `lending.unlend`, `lending.borrow`, `lending.repay`, `collateral.set`, `collateral.remove`, `rewards.claim`, `position.read`, `market.apy`, `market.collateral_factor`

Full Moonwell integration including lending (supply/withdraw), borrowing (borrow/repay), collateral management, WELL rewards claiming, and position/market queries.

**Supported Markets (Base)**:

| Token | mToken Address | Underlying Address |
|-------|----------------|-------------------|
| USDC | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH | [REDACTED_SECRET] | `0x4200000000000000000000000000000000000006` |
| wstETH | [REDACTED_SECRET] | [REDACTED_SECRET] |

See [moonwell.md](moonwell.md) for details.

### Multicall Adapter (`multicall_adapter`)

- **Type**: `MULTICALL`
- **Module**: `wayfinder_paths.adapters.multicall_adapter.adapter.MulticallAdapter`
- **Protocol**: EVM batch calls
- **Capabilities**: `multicall.aggregate`

Batches multiple on-chain read calls into a single RPC request for efficiency.

### Pendle Adapter (`pendle_adapter`)

- **Type**: `PENDLE`
- **Module**: `wayfinder_paths.adapters.pendle_adapter.adapter.PendleAdapter`
- **Protocol**: Pendle
- **Capabilities**: `pendle.markets.read`, `pendle.market.snapshot`, `pendle.market.history`, `pendle.prices.ohlcv`, `pendle.prices.assets`, `pendle.swap.quote`, `pendle.swap.best_pt`, `pendle.swap.execute`, `pendle.convert.quote`, `pendle.convert.best_pt`, `pendle.convert.execute`, `pendle.positions.database`, `pendle.limit_orders.taker.read`, `pendle.limit_orders.maker.read`, `pendle.limit_orders.maker.write`, `pendle.deployments.read`, `pendle.router_static.rates`

Comprehensive Pendle integration for PT/YT market discovery, historical metrics, execution planning, swap quotes, and limit orders via the Pendle Hosted SDK.

See [pendle.md](pendle.md) for details.

### Uniswap Adapter (`uniswap_adapter`)

- **Type**: `UNISWAP`
- **Module**: `wayfinder_paths.adapters.uniswap_adapter.adapter.UniswapAdapter`
- **Protocol**: Uniswap V3 (concentrated liquidity)
- **Capabilities**: `uniswap.liquidity.add`, `uniswap.liquidity.increase`, `uniswap.liquidity.remove`, `uniswap.fees.collect`, `uniswap.position.get`, `uniswap.positions.list`, `uniswap.fees.uncollected`, `uniswap.pool.get`

Provides Uniswap V3 LP position reads and liquidity/fee management.

See [uniswap.md](uniswap.md) for details.

### ProjectX Adapter (`projectx_adapter`)

- **Type**: `PROJECTX`
- **Module**: `wayfinder_paths.adapters.projectx_adapter.adapter.ProjectXLiquidityAdapter`
- **Protocol**: ProjectX (Uniswap V3 fork on HyperEVM)
- **Capabilities**: `projectx.pool.overview`, `projectx.positions.list`, `projectx.liquidity.mint`, `projectx.liquidity.increase`, `projectx.liquidity.decrease`, `projectx.fees.collect`, `projectx.position.burn`, `projectx.swap.exact_in`

Provides concentrated liquidity reads and execution on ProjectX, plus exact-in swaps.

See [projectx.md](projectx.md) for details.

### Pool Adapter (`pool_adapter`)

- **Type**: `POOL`
- **Module**: `wayfinder_paths.adapters.pool_adapter.adapter.PoolAdapter`
- **Protocol**: DeFi Llama pool data
- **Capabilities**: `pool.read`, `pool.discover`

Provides pool information, yield analytics via DeFi Llama integration, and pool discovery with filtering.

```bash
poetry run wayfinder resource wayfinder://adapters/pool_adapter
```

### Token Adapter (`token_adapter`)

- **Type**: `TOKEN`
- **Module**: `wayfinder_paths.adapters.token_adapter.adapter.TokenAdapter`
- **Protocol**: Token metadata service
- **Capabilities**: `token.read`, `token.price`, `token.gas`

Provides token metadata (address, decimals, symbol), live price data, and gas token lookups by chain.

```bash
poetry run wayfinder resource wayfinder://tokens/resolve/usd-coin-base
poetry run wayfinder resource wayfinder://tokens/search/base/usdc
poetry run wayfinder resource wayfinder://tokens/gas/base
```

---

## Presenting Adapter Data to Users

Adapters return raw JSON results. When presenting this data to users, follow these patterns to make the information clear and actionable.

### Balance Presentation

When showing `resource wayfinder://balances/{label}` results:

- **Group by chain** — show Base tokens, Arbitrum tokens, etc. as separate sections
- **Lead with USD value** — users care about dollar amounts first, then token quantities
- **Hide dust** — tokens worth less than $0.01 are noise; mention their count but don't list them
- **Show totals** — always include a total portfolio value across all chains

Example format:
```
Base ($2,450.32)
  1,200.00 USDC     $1,200.00
  0.5123   ETH      $1,230.32
  150.00   USDbC    $20.00

Arbitrum ($500.00)
  500.00   USDC     $500.00

Total: $2,950.32 across 2 chains (3 dust tokens hidden)
```

### Token Resolution Presentation

When showing `resource wayfinder://tokens/search/{chain}/{query}` results:

- **Show top 3-5 matches** with their full token ID, chain, and contract address
- **Highlight the best match** if the score is significantly higher than others
- **Always show the canonical ID** that the user should use in subsequent commands

### Swap Quote Presentation

When showing `quote_swap` results:

- **Show the exchange rate** — e.g., "1 ETH = 2,460.50 USDC"
- **Show price impact** — if available in the quote response
- **Show fees** — gas cost estimate, protocol fees, bridge fees (if cross-chain)
- **Show the net amount received** — this is what the user ultimately cares about
- **Compare to mid price** — if you have `mid_prices` data, show how the quote compares

### Strategy Status Presentation

When showing `run_strategy --action status` results:

- **Show current positions** — what's deployed and where
- **Show P&L** — unrealized gains/losses if available
- **Show APY** — current yield rate
- **Show health** — any warnings (low health factor, approaching liquidation, etc.)

### Hyperliquid State Presentation

When showing `resource wayfinder://hyperliquid/{label}/state` results:

- **Separate perp positions from account summary** — show margin, equity, and unrealized PnL at the top, then list positions
- **Per-position details** — coin, side, size, entry price, mark price, unrealized PnL, leverage
- **Funding rate context** — if showing positions, include current funding rate so the user knows their carry cost/income

### Error Presentation

See the Error Handling section in the main SKILL.md for how to translate error codes into user-friendly messages with actionable recovery steps.

### General Principles

1. **Numbers need context** — raw wei amounts or 18-decimal values are useless. Always convert to human-readable amounts.
2. **Currency formatting** — use commas for thousands, 2 decimal places for USD, appropriate decimals for crypto (4 for ETH, 2 for stablecoins, 6+ for micro-cap).
3. **Timestamps** — convert Unix timestamps to relative time ("2 hours ago") or readable dates.
4. **Addresses** — truncate to `0x1234...abcd` unless the user needs the full address.
5. **Status indicators** — use clear labels: "Active", "Pending", "Failed" rather than numeric status codes.
6. **Chain labels** — always include the chain name next to token amounts when showing multi-chain data.


path: scripts/pull-sdk-ref.sh
content: #!/usr/bin/env bash
# pull-sdk-ref.sh — Pull reference docs from wayfinder-paths-sdk skill files.
#
# Usage:
#   ./pull-sdk-ref.sh <topic>          Show docs for a specific topic
#   ./pull-sdk-ref.sh --list           List available topics
#   ./pull-sdk-ref.sh --all            Show all reference docs
#   ./pull-sdk-ref.sh --commit <hash>  Checkout a specific SDK commit before reading
#   ./pull-sdk-ref.sh --version        Show the pinned SDK version from sdk-version.md
#
# Topics:
#   strategies   Developing Wayfinder strategies (workflow, manifests, safety, data sources)
#   setup        First-time SDK setup
#   boros        Boros adapter (fixed-rate markets, rate locking, funding swaps)
#   brap         BRAP adapter (cross-chain swaps and bridges)
#   hyperlend    HyperLend adapter (HyperEVM lending)
#   hyperliquid  Hyperliquid adapter (perps, spot, deposits/withdrawals)
#   polymarket   Polymarket adapter (prediction markets, trading, bridging)
#   moonwell     Moonwell adapter (Base lending/borrowing)
#   pendle       Pendle adapter (PT/YT markets)
#   uniswap      Uniswap V3 adapter (concentrated liquidity)
#   projectx     ProjectX adapter (Uniswap V3 fork on HyperEVM)
#   data         Pool, token, and balance data (pool discovery, token metadata, ledger)

set -euo pipefail

# --- Parse --commit flag ---
SDK_COMMIT=""
RESTORE_REF=""

# Check for sdk-version.md file
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
SDK_VERSION_FILE="$SCRIPT_DIR/../sdk-version.md"

if [[ -f "$SDK_VERSION_FILE" ]]; then
    SDK_COMMIT="$(tr -d '[:space:]' < "$SDK_VERSION_FILE")"
fi

# Command-line --commit overrides sdk-version.md
ARGS=()
while [[ $# -gt 0 ]]; do
    case "$1" in
        --commit)
            SDK_COMMIT="$2"
            shift 2
            ;;
        --version|-v)
            if [[ -n "$SDK_COMMIT" ]]; then
                echo "Pinned SDK version: $SDK_COMMIT"
            else
                echo "No SDK version pinned (no sdk-version.md file and no --commit flag)."
            fi
            exit 0
            ;;
        *)
            ARGS+=("$1")
            shift
            ;;
    esac
done
set -- "${ARGS[@]+"${ARGS[@]}"}"

# --- Resolve SDK path ---
if [[ -n "${WAYFINDER_SDK_PATH:-}" ]] && [[ -d "$WAYFINDER_SDK_PATH" ]]; then
    SDK_ROOT="$WAYFINDER_SDK_PATH"
elif [[ -d "$REPO_ROOT/../wayfinder-paths-sdk" ]]; then
    SDK_ROOT="$(cd "$REPO_ROOT/../wayfinder-paths-sdk" && pwd)"
elif [[ -d "$HOME/wayfinder-paths-sdk" ]]; then
    SDK_ROOT="$HOME/wayfinder-paths-sdk"
else
    echo "ERROR: Cannot find wayfinder-paths-sdk." >&2
    echo "Tried:" >&2
    echo "  \$WAYFINDER_SDK_PATH (not set or missing)" >&2
    echo "  $REPO_ROOT/../wayfinder-paths-sdk" >&2
    echo "  $HOME/wayfinder-paths-sdk" >&2
    echo "" >&2
    echo "Set WAYFINDER_SDK_PATH or clone the SDK next to this repo." >&2
    exit 1
fi

# --- Checkout pinned ref if specified ---
if [[ -n "$SDK_COMMIT" ]]; then
    RESTORE_REF="$(cd "$SDK_ROOT" && { git symbolic-ref -q --short HEAD 2>/dev/null || git rev-parse HEAD; })"
    echo "Checking out SDK ref: $SDK_COMMIT" >&2
    (cd "$SDK_ROOT" && git checkout --quiet "$SDK_COMMIT")
    trap '(cd "$SDK_ROOT" && git checkout --quiet "$RESTORE_REF") 2>/dev/null' EXIT
fi

SKILLS_DIR="$SDK_ROOT/.claude/skills"

if [[ ! -d "$SKILLS_DIR" ]]; then
    echo "ERROR: Skills directory not found at $SKILLS_DIR" >&2
    exit 1
fi

# --- Topic → directory mapping ---
declare -A TOPIC_MAP=(
    [strategies]="developing-wayfinder-strategies"
    [setup]="setup"
    [boros]="using-boros-adapter"
    [brap]="using-brap-adapter"
    [hyperlend]="using-hyperlend-adapter"
    [hyperliquid]="using-hyperliquid-adapter"
    [polymarket]="using-polymarket-adapter"
    [moonwell]="using-moonwell-adapter"
    [pendle]="using-pendle-adapter"
    [uniswap]="using-uniswap-adapter"
    [projectx]="using-projectx-adapter"
    [data]="using-pool-token-balance-data"
)

# --- Functions ---

print_topic_header() {
    local topic="$1"
    local dir_name="$2"
    echo ""
    echo "================================================================================"
    echo "  $topic  ($dir_name)"
    echo "================================================================================"
    echo ""
}

print_file() {
    local filepath="$1"
    local relpath="${filepath#$SKILLS_DIR/}"
    echo "--- $relpath ---"
    echo ""
    cat "$filepath"
    echo ""
}

show_topic() {
    local topic="$1"
    local dir_name="${TOPIC_MAP[$topic]:-}"

    if [[ -z "$dir_name" ]]; then
        echo "ERROR: Unknown topic '$topic'." >&2
        echo "Run with --list to see available topics." >&2
        exit 1
    fi

    local skill_dir="$SKILLS_DIR/$dir_name"

    if [[ ! -d "$skill_dir" ]]; then
        echo "ERROR: Skill directory not found: $skill_dir" >&2
        exit 1
    fi

    print_topic_header "$topic" "$dir_name"

    # Print SKILL.md first
    if [[ -f "$skill_dir/SKILL.md" ]]; then
        print_file "$skill_dir/SKILL.md"
    fi

    # Print all rules files sorted
    if [[ -d "$skill_dir/rules" ]]; then
        for rule_file in "$skill_dir/rules"/*.md; do
            [[ -f "$rule_file" ]] && print_file "$rule_file"
        done
    fi
}

show_list() {
    echo "Available topics:"
    echo ""
    echo "  strategies   Developing Wayfinder strategies (workflow, manifests, safety, data sources)"
    echo "  setup        First-time SDK setup"
    echo "  boros        Boros adapter (fixed-rate markets, rate locking, funding swaps)"
    echo "  brap         BRAP adapter (cross-chain swaps and bridges)"
    echo "  hyperlend    HyperLend adapter (HyperEVM lending)"
    echo "  hyperliquid  Hyperliquid adapter (perps, spot, deposits/withdrawals)"
    echo "  polymarket   Polymarket adapter (prediction markets, trading, bridging)"
    echo "  moonwell     Moonwell adapter (Base lending/borrowing)"
    echo "  pendle       Pendle adapter (PT/YT markets)"
    echo "  uniswap      Uniswap V3 adapter (concentrated liquidity)"
    echo "  projectx     ProjectX adapter (Uniswap V3 fork on HyperEVM)"
    echo "  data         Pool, token, and balance data (pool discovery, token metadata, ledger)"
    echo ""
    echo "Usage:"
    echo "  $0 <topic>          Show docs for a topic"
    echo "  $0 --all            Show all docs"
    echo "  $0 --list           This list"
    echo "  $0 --commit <hash>  Checkout a specific SDK commit before reading"
    echo "  $0 --version        Show the pinned SDK version"
    echo ""
    echo "SDK path: $SDK_ROOT"
    if [[ -n "$SDK_COMMIT" ]]; then
        echo "SDK ref: $SDK_COMMIT"
    fi
}

show_all() {
    for topic in strategies setup boros brap hyperlend hyperliquid polymarket moonwell pendle uniswap projectx data; do
        show_topic "$topic"
    done
}

# --- Main ---

if [[ $# -eq 0 ]]; then
    show_list
    exit 0
fi

case "$1" in
    --list|-l)
        show_list
        ;;
    --all|-a)
        show_all
        ;;
    --help|-h)
        show_list
        ;;
    *)
        # Support multiple topics: pull-sdk-ref.sh boros moonwell
        for topic in "$@"; do
            show_topic "$topic"
        done
        ;;
esac


path: scripts/sync-skill-json-from-sdk.py
content: #!/usr/bin/env python3
"""Sync wayfinder/skill.json commands + resources from wayfinder-paths-sdk.

This repo is the source of truth for the OpenClaw/ClawHub skill packaging, but
the CLI surface area is defined by the Wayfinder Paths SDK MCP server.

What this script updates:
- skill.json.commands (adds/removes tools, excluding runner)
- skill.json.resources.static/templates (from mcp.resource(...) URIs)
- skill.json.sdk_version (from wayfinder/sdk-version.md)

It does NOT attempt to rewrite SKILL.md or reference docs.
"""

from __future__ import annotations

import json
import os
import re
import subprocess
import sys
from pathlib import Path


def _repo_root() -> Path:
    return Path(__file__).resolve().parents[2]


def _skill_dir() -> Path:
    return _repo_root() / "wayfinder"


def _sdk_ref() -> str:
    ref = (_skill_dir() / "sdk-version.md").read_text(encoding="utf-8").strip()
    return ref or "main"


def _find_sdk_root(repo_root: Path) -> Path:
    env = os.getenv("WAYFINDER_SDK_PATH", "").strip()
    if env and Path(env).expanduser().is_dir():
        return Path(env).expanduser().resolve()

    sibling = (repo_root.parent / "wayfinder-paths-sdk").resolve()
    if sibling.is_dir():
        return sibling

    home = (Path.home() / "wayfinder-paths-sdk").resolve()
    if home.is_dir():
        return home

    raise FileNotFoundError(
        "Cannot find wayfinder-paths-sdk. Set WAYFINDER_SDK_PATH or clone it next to this repo."
    )


def _git_current_ref(sdk_root: Path) -> str:
    try:
        out = subprocess.check_output(
            ["git", "-C", str(sdk_root), "symbolic-ref", "-q", "--short", "HEAD"],
            stderr=subprocess.DEVNULL,
            text=True,
        ).strip()
        if out:
            return out
    except subprocess.CalledProcessError:
        pass

    return subprocess.check_output(
        ["git", "-C", str(sdk_root), "rev-parse", "HEAD"],
        text=True,
    ).strip()


def _git_checkout(sdk_root: Path, ref: str) -> None:
    subprocess.check_call(["git", "-C", str(sdk_root), "checkout", "--quiet", ref])


def _parse_server_py(server_py: str) -> tuple[list[str], list[str]]:
    # Tools are registered like: mcp.tool()(quote_swap)
    tool_names = re.findall(r"mcp\.tool\(\)\((\w+)\)", server_py)

    # Resources are registered like: mcp.resource("wayfinder://adapters")(list_adapters)
    resource_uris = re.findall(r'mcp\.resource\("([^"]+)"\)', server_py)

    return tool_names, resource_uris


def _ordered_unique(items: list[str]) -> list[str]:
    seen: set[str] = set()
    out: list[str] = []
    for x in items:
        if x in seen:
            continue
        seen.add(x)
        out.append(x)
    return out


def main() -> int:
    repo_root = _repo_root()
    skill_json_path = _skill_dir() / "skill.json"
    sdk_root = _find_sdk_root(repo_root)

    sdk_ref = _sdk_ref()
    do_checkout = "--checkout" in sys.argv[1:]
    restore_ref = _git_current_ref(sdk_root) if do_checkout else None

    try:
        if do_checkout:
            _git_checkout(sdk_root, sdk_ref)

        server_path = sdk_root / "wayfinder_paths" / "mcp" / "server.py"
        if not server_path.exists():
            raise FileNotFoundError(f"Missing SDK MCP server: {server_path}")

        tool_names, resource_uris = _parse_server_py(
            server_path.read_text(encoding="utf-8")
        )

        # Exclude intentionally-undocumented tool(s)
        tool_names = [t for t in tool_names if t != "runner"]

        # Keep a stable preferred ordering; append new tools at the end.
        preferred = [
            "resource",
            "quote_swap",
            "execute",
            "hyperliquid",
            "hyperliquid_execute",
            "polymarket",
            "polymarket_execute",
            "run_strategy",
            "wallets",
            "run_script",
        ]
        tools_set = set(tool_names)

        commands: list[str] = []
        for c in preferred:
            if c == "resource" or c in tools_set:
                commands.append(c)

        extra = sorted([t for t in tool_names if t not in set(preferred)])
        commands.extend(extra)
        commands = _ordered_unique(commands)

        static_resources = sorted([u for u in resource_uris if "{" not in u])
        template_resources = sorted([u for u in resource_uris if "{" in u])

        skill = json.loads(skill_json_path.read_text(encoding="utf-8"))
        skill["sdk_version"] = sdk_ref
        skill["commands"] = commands
        skill.setdefault("resources", {})
        skill["resources"]["static"] = static_resources
        skill["resources"]["templates"] = template_resources

        skill_json_path.write_text(
            json.dumps(skill, indent=2) + "\n", encoding="utf-8"
        )

        print("Updated:", skill_json_path)
        print("SDK root:", sdk_root)
        print("SDK ref:", sdk_ref)
        if not do_checkout:
            print("note: SDK ref was not checked out (run with --checkout to sync from a specific ref).")
        print("commands:", ", ".join(commands))
        return 0
    finally:
        if restore_ref:
            try:
                _git_checkout(sdk_root, restore_ref)
            except Exception:
                pass


if __name__ == "__main__":
    raise SystemExit(main())


path: skill.json
content: {
  "name": "wayfinder",
  "version": "0.4.1",
  "sdk_version": "main",
  "description": "DeFi trading, yield strategies, and portfolio management via Wayfinder Paths CLI. Supports token swaps, perps, prediction markets, automated yield strategies, wallet management, and protocol adapters across Ethereum, Base, Arbitrum, Polygon, BSC, Avalanche, Plasma, and HyperEVM.",
  "author": "Wayfinder Foundation",
  "entry": "SKILL.md",
  "runtime": "python",
  "commands": [
    "resource",
    "quote_swap",
    "execute",
    "hyperliquid",
    "hyperliquid_execute",
    "polymarket",
    "polymarket_execute",
    "run_strategy",
    "wallets",
    "run_script"
  ],
  "resources": {
    "static": [
      "wayfinder://adapters",
      "wayfinder://hyperliquid/markets",
      "wayfinder://hyperliquid/prices",
      "wayfinder://hyperliquid/spot-assets",
      "wayfinder://strategies",
      "wayfinder://wallets"
    ],
    "templates": [
      "wayfinder://activity/{label}",
      "wayfinder://adapters/{name}",
      "wayfinder://balances/{label}",
      "wayfinder://hyperliquid/book/{coin}",
      "wayfinder://hyperliquid/prices/{coin}",
      "wayfinder://hyperliquid/{label}/spot",
      "wayfinder://hyperliquid/{label}/state",
      "wayfinder://strategies/{name}",
      "wayfinder://tokens/gas/{chain_code}",
      "wayfinder://tokens/resolve/{query}",
      "wayfinder://tokens/search/{chain_code}/{query}",
      "wayfinder://wallets/{label}"
    ]
  },
  "references": [
    "references/setup.md",
    "references/strategies.md",
    "references/adapters.md",
    "references/coding-interface.md",
    "references/hyperliquid.md",
    "references/polymarket.md",
    "references/ccxt.md",
    "references/moonwell.md",
    "references/pendle.md",
    "references/boros.md",
    "references/uniswap.md",
    "references/projectx.md",
    "references/tokens-and-pools.md",
    "references/hyperlend.md"
  ],
  "permissions": {
    "network": true,
    "filesystem": true
  }
}


Security scan verdicts — ClawScan: suspicious | static analysis:  | VirusTotal: suspicious | SkillSpector:  ()