@continuumdao/ctm-mpc-defi 0.2.2 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,43 +1,149 @@
1
1
  ---
2
2
  name: aave-v4
3
- description: Aave v4 Spoke supply, withdraw, borrow, and repay via Continuum MPC multiSignRequest tools.
3
+ description: Aave v4 lending and borrowing via Continuum MPC — hubs, spokes, supply, withdraw, borrow, repay.
4
4
  ---
5
5
 
6
6
  # Aave v4 (MCP)
7
7
 
8
+ Aave v4 organizes liquidity into **hubs** (market segments) and **spokes** (on-chain contracts you call). Each reserve (e.g. WETH, USDC) is listed in a hub and executed through that reserve’s spoke.
9
+
10
+ ## Architecture (explain this to users)
11
+
12
+ | Concept | What it is | Agent needs to know |
13
+ |---------|------------|---------------------|
14
+ | **Hub** | Off-chain market segment (Core, Plus, Prime) | Maps to UI `marketId`: `main` → Plus, `core` → Core, `bluechip` → Prime |
15
+ | **Spoke** | On-chain contract (`supply`, `withdraw`, `borrow`, `repay`) | **Do not guess.** The MCP server resolves spoke from the Aave v4 API using `chainId` + `underlying` + `marketId` |
16
+ | **Reserve** | One asset in one hub (underlying + APY + spoke) | Use `get_defi_protocol_supported_tokens` for valid underlyings |
17
+ | **Position** | User’s supplies/debt on a spoke | Withdraw/borrow/repay risk depends on health factor on that spoke |
18
+
19
+ **Important:** The UI market (`main`) and the hub that lists an asset can differ. Example on Ethereum: WETH is in **Core**, not Plus, even when the user picks “main”. The server auto-picks the hub that actually lists the asset.
20
+
8
21
  ## Before any action
9
22
 
10
23
  1. `load_defi_protocol({ protocolId: "aave-v4" })`
11
- 2. `get_defi_protocol_supported_chains` — intersect with `get_chain_registry` (node must have RPC)
12
- 3. `get_defi_protocol_supported_tokens({ protocolId: "aave-v4", chainId })` — market underlyings
13
- 4. `get_token_registry` — map symbols (USDC, stETH, WETH) to addresses
24
+ 2. `get_defi_protocol_supported_chains` — intersect with `get_chain_registry` (node must have `rpcGateway` per chain)
25
+ 3. `get_defi_protocol_supported_tokens({ protocolId: "aave-v4", chainId })` — listed underlyings + `nativeWrapped`
26
+ 4. `get_token_registry` — map symbols (USDC, WETH, stETH) to addresses
14
27
 
15
28
  ## Tool selection
16
29
 
17
30
  | User intent | MCP tool |
18
31
  |-------------|----------|
19
- | Supply / deposit asset | `ctm_aave_v4_build_deposit_multisign` |
20
- | Withdraw supplied asset | `ctm_aave_v4_build_withdraw_multisign` |
32
+ | Supply / deposit (lend) | `ctm_aave_v4_build_deposit_multisign` |
33
+ | Withdraw supplied balance | `ctm_aave_v4_build_withdraw_multisign` |
21
34
  | Borrow against collateral | `ctm_aave_v4_build_borrow_multisign` |
22
35
  | Repay debt | `ctm_aave_v4_build_repay_multisign` |
23
36
 
24
- Example: **borrow USDC against stETH** → deposit/wrap collateral first if needed, then `ctm_aave_v4_build_borrow_multisign` with `underlying` = USDC address, `marketId` from Aave UI/API.
37
+ ## Common inputs (all four tools)
25
38
 
26
- ## Required inputs (per action)
39
+ | Field | Required | Notes |
40
+ |-------|----------|-------|
41
+ | `keyGenId` | yes | Preferred KeyGen from the node |
42
+ | `chainId` | yes | EVM chain id; RPC/gas from chain registry |
43
+ | `purposeText` | yes | Human-readable reason for the sign request |
44
+ | `underlying` | yes | Token address for the action (see per-action below) |
45
+ | `amountHuman` | yes | Human amount (e.g. `"0.5"`, `"100"`) |
46
+ | `marketId` | no | `main` (default), `core`, or `bluechip` |
47
+ | `useCustomGas` | no | `true` to use chain registry gas config |
48
+ | `spoke` | no | **Omit** — server resolves from Aave API |
27
49
 
28
- - `keyGenId`, `chainId`, `purposeText`
29
- - `spoke`, `underlying`, `amountHuman`, `marketId` (hub/market segment)
50
+ ## Per-action: what `underlying` means
30
51
 
31
- ## After submit (`{ requestId }`)
52
+ ### Deposit (supply / lend)
53
+
54
+ - **ERC-20:** pass the token address (e.g. USDC).
55
+ - **Native ETH:** pass `0x0000000000000000000000000000000000000000`, or set `isNativeIn: true` when `underlying` is the chain’s wrapped native (WETH on Ethereum).
56
+ - Optional: `enableAsCollateralAfterSupply: true` to enable the new supply as borrow collateral in the same batch.
57
+ - Flow: may wrap native → approve spoke → `spoke.supply` → optional `setUsingAsCollateral`.
58
+
59
+ ### Withdraw
60
+
61
+ - `underlying` = asset to withdraw (same address you supplied).
62
+ - Server runs a **health-factor preview** when the user has borrow debt on that spoke. Risky withdrawals are blocked or require `acknowledgeHealthRisk: true`.
63
+
64
+ ### Borrow
65
+
66
+ - `underlying` = **debt asset** to borrow (e.g. USDC), not collateral.
67
+ - User must already have **collateral supplied** on the same chain (deposit first if needed).
68
+ - Optional: `collateralUnderlying` — collateral token address (e.g. stETH). Helps pick the correct hub when the same debt exists on multiple hubs.
69
+ - Server runs a **health-factor preview** before submit. Dangerous borrows are blocked; borderline cases need `acknowledgeHealthRisk: true`.
70
+
71
+ ### Repay
72
+
73
+ - `underlying` = debt token being repaid (e.g. USDC).
74
+ - May batch ERC-20 `approve` + `spoke.repay` when allowance is insufficient.
75
+
76
+ ## Basic workflows (teach users)
77
+
78
+ ### Lend only (supply)
79
+
80
+ 1. Confirm chain in registry and Aave-supported.
81
+ 2. Resolve token address (registry or `get_defi_protocol_supported_tokens`).
82
+ 3. `ctm_aave_v4_build_deposit_multisign` with `underlying`, `amountHuman`, `marketId` (default `main`).
83
+ 4. Complete sign lifecycle → `{ requestId }`.
32
84
 
33
- Use base MCP lifecycle:
85
+ ### Borrow against collateral
86
+
87
+ 1. **Deposit collateral** if none: e.g. supply stETH or WETH via deposit tool.
88
+ 2. **Borrow debt asset:** `ctm_aave_v4_build_borrow_multisign` with `underlying` = debt token (USDC), `amountHuman`, optional `collateralUnderlying` = collateral address.
89
+ 3. Explain: health factor must stay above liquidation thresholds; server may refuse or ask for risk acknowledgment.
90
+
91
+ ### Repay and withdraw
92
+
93
+ 1. **Repay:** reduce debt with repay tool (`underlying` = debt token).
94
+ 2. **Withdraw:** pull supplied collateral after debt is manageable; watch health-factor gates on withdraw.
95
+
96
+ ## Health factor preflight
97
+
98
+ For **withdraw**, **borrow**, and **repay**, the server calls the Aave v4 preview API before building the transaction:
99
+
100
+ | Outcome | Behavior |
101
+ |---------|----------|
102
+ | Allow | Proceeds to build multisign |
103
+ | Block | Returns error (e.g. HF would fall below 1.0) — do not retry with same params |
104
+ | Confirm | Returns error unless `acknowledgeHealthRisk: true` — tell the user the risk and get explicit consent |
105
+
106
+ Set `skipHealthPreview: true` only when the user explicitly accepts skipping simulation (not recommended).
107
+
108
+ ## After submit (`{ requestId }`)
34
109
 
35
110
  1. `wait_for_sign_request_ready` (if needed)
36
111
  2. `sign_request_agree` (multi-agree)
37
112
  3. `trigger_sign_result`
38
113
  4. `broadcast_sign_result`
39
114
 
115
+ ## Example: deposit 0.001 ETH on Ethereum
116
+
117
+ ```json
118
+ {
119
+ "keyGenId": "<keygen-id>",
120
+ "chainId": 1,
121
+ "purposeText": "Supply 0.001 ETH to Aave v4",
122
+ "useCustomGas": true,
123
+ "underlying": "0x0000000000000000000000000000000000000000",
124
+ "marketId": "main",
125
+ "amountHuman": "0.001"
126
+ }
127
+ ```
128
+
129
+ ## Example: borrow USDC against stETH collateral
130
+
131
+ After stETH is supplied:
132
+
133
+ ```json
134
+ {
135
+ "keyGenId": "<keygen-id>",
136
+ "chainId": 1,
137
+ "purposeText": "Borrow 100 USDC against stETH collateral",
138
+ "underlying": "<USDC address>",
139
+ "collateralUnderlying": "<stETH address>",
140
+ "marketId": "main",
141
+ "amountHuman": "100"
142
+ }
143
+ ```
144
+
40
145
  ## Notes
41
146
 
42
- - Native ETH supply uses wrapped native from Aave API on that chain.
43
- - Token filter: `api_underlyings` — only listed underlyings are valid per chain.
147
+ - Native supply uses wrapped native from `get_defi_protocol_supported_tokens` (`nativeWrapped`).
148
+ - Token filter: `api_underlyings` — only API-listed underlyings are valid per chain.
149
+ - Merkl rewards and advanced position actions are available in the node app UI, not as separate MCP tools today.
@@ -1,13 +1,132 @@
1
1
  ---
2
2
  name: curve-dao
3
- description: Curve Router NG swaps via @curvefi/api session and multisign batch.
3
+ description: Curve Router NG swaps via pool-graph routing and Continuum MPC multisign.
4
4
  ---
5
5
 
6
6
  # Curve DAO (MCP)
7
7
 
8
- 1. Confirm chain via `get_defi_protocol_supported_chains` and `isCurveApiChainSupported`.
9
- 2. `get_defi_protocol_supported_tokens` with `rpcUrl` from chain registry loads pool-graph swappable tokens.
10
- 3. `ctm_curve_dao_quote` — `chainId`, `tokenIn`, `tokenOut`, `amountHuman` (optional `tokenInDecimals`). Server resolves `rpcUrl` from chain registry.
11
- 4. `ctm_curve_dao_build_swap_multisign` — `keyGenId`, `chainId`, `purposeText`, `tokenIn`, `tokenOut`, `amountHuman`, `slippagePercent`.
8
+ Curve swaps on Continuum use **Curve Router NG** (`@curvefi/api`): the SDK loads the on-chain **pool graph** for your chain, finds a route between two tokens, then builds an optional **ERC-20 approve** + **`exchange`** batch for the MPC wallet.
12
9
 
13
- Base MCP lifecycle after `{ requestId }`.
10
+ ## Architecture (explain this to users)
11
+
12
+ | Concept | What it is | Agent needs to know |
13
+ |---------|------------|---------------------|
14
+ | **Pool graph** | Curve pools and swappable coins on a chain | Loaded from JSON-RPC via `get_defi_protocol_supported_tokens` (needs `rpcGateway`) |
15
+ | **Router NG** | On-chain contract that executes multi-hop swaps | Calldata from `populateSwap`; server does not need the router address upfront |
16
+ | **Native placeholder** | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` | Curve’s id for native ETH (and chain native) in routes — **not** the WETH contract |
17
+ | **Wrapped native** | WETH / WMATIC etc. | For **quotes**, WETH is treated as `0xeeee` in routing. For **build**, pass the **wrapped native contract address** as `tokenIn` when selling native ETH |
18
+
19
+ There is no separate “hub” or “spoke” — routing is entirely determined by Curve’s pool connectivity on that chain.
20
+
21
+ ## Before any swap
22
+
23
+ 1. `load_defi_protocol({ protocolId: "curve-dao" })`
24
+ 2. `get_defi_protocol_supported_chains` — must intersect with `get_chain_registry` (node needs `rpcGateway`)
25
+ 3. `get_defi_protocol_supported_tokens({ protocolId: "curve-dao", chainId })` — tokens reachable in Curve’s pool graph on that RPC
26
+ 4. `get_token_registry` — map user symbols (USDC, crvUSD, ETH) to addresses
27
+
28
+ If token discovery returns empty, configure `rpcGateway` for the chain first, then retry.
29
+
30
+ ## Tool flow (two steps → one sign request)
31
+
32
+ | Step | Tool | Creates sign request? |
33
+ |------|------|------------------------|
34
+ | 1. Quote | `ctm_curve_dao_quote` | No — returns route + expected output |
35
+ | 2. Execute | `ctm_curve_dao_build_swap_multisign` | Yes → `{ requestId }` |
36
+
37
+ Always quote first unless the user already has a verified route. Explain quote output (`output`, `route`, `priceImpactPercent`) before submitting.
38
+
39
+ ## `ctm_curve_dao_quote`
40
+
41
+ | Field | Required | Notes |
42
+ |-------|----------|-------|
43
+ | `chainId` | yes | Must be Curve-supported (see supported chains below) |
44
+ | `tokenIn` | yes | ERC-20 address, or native: `0xeeee…`, `0x0`, `eth` |
45
+ | `tokenOut` | yes | ERC-20 address or `0xeeee…` for native out |
46
+ | `amountHuman` | yes | Human decimal string of **tokenIn** (e.g. `"100"`, `"0.5"`) |
47
+ | `tokenInDecimals` | no | Omit to read decimals on-chain (native = 18) |
48
+ | `rpcUrl` | no | **Do not pass** — server injects from chain registry |
49
+
50
+ **Quote output (typical):** `inputAmount`, `output`, `route`, `priceImpactPercent`, `tokenInRouterId`, `tokenOutRouterId`.
51
+
52
+ ## `ctm_curve_dao_build_swap_multisign`
53
+
54
+ | Field | Required | Notes |
55
+ |-------|----------|-------|
56
+ | `keyGenId` | yes | MPC KeyGen |
57
+ | `chainId` | yes | Same chain as quote |
58
+ | `purposeText` | yes | Human-readable purpose |
59
+ | `tokenIn` | yes | **ERC-20 address** sold. Native ETH in: use **wrapped native** address (e.g. WETH on Ethereum), not `0xeeee` |
60
+ | `tokenOut` | yes | Output token address or `0xeeee…` for native |
61
+ | `amountHuman` | yes | Same human amount as quote (tokenIn units) |
62
+ | `slippagePercent` | yes | Number strictly between 0 and 100 (e.g. `0.5` for 0.5%) |
63
+ | `useCustomGas` | no | `true` for chain registry gas settings |
64
+
65
+ Server resolves `keyGen`, `executorAddress`, `rpcUrl`, `chainDetail` from `keyGenId` + registry.
66
+
67
+ **Transaction batch:** optional `approve` to Curve router when allowance is low, then `CurveRouterNG.exchange` (native-in may be 1 tx without approve).
68
+
69
+ ## Native ETH swaps
70
+
71
+ | Phase | tokenIn |
72
+ |-------|---------|
73
+ | Quote | `0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee` or `0x0` |
74
+ | Build | Wrapped native from `get_defi_protocol_supported_tokens` / chain (e.g. WETH `0xC02…` on Ethereum) |
75
+
76
+ Tell users: quoting uses Curve’s native placeholder; the MPC builder maps wrapped native → native route automatically.
77
+
78
+ ## Supported chains (indicative)
79
+
80
+ Use `get_defi_protocol_supported_chains` as source of truth. Curve SDK commonly supports:
81
+
82
+ Ethereum (1), Optimism (10), BSC (56), Gnosis (100), Polygon (137), Sonic (146), Unichain (196), Fantom (250), Fraxtal (252), zkSync (324), HyperEVM (999), Moonbeam (1284), Kava (2222), Mantle (5000), Base (8453), Arbitrum (42161), Celo (42220), Avalanche (43114), Aurora (1313161554).
83
+
84
+ ## Example: swap 100 USDC → ETH on Ethereum
85
+
86
+ **1. Quote**
87
+
88
+ ```json
89
+ {
90
+ "chainId": 1,
91
+ "tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
92
+ "tokenOut": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
93
+ "amountHuman": "100"
94
+ }
95
+ ```
96
+
97
+ **2. Build** (after user confirms quote output)
98
+
99
+ ```json
100
+ {
101
+ "keyGenId": "<keygen-id>",
102
+ "chainId": 1,
103
+ "purposeText": "Swap 100 USDC to ETH via Curve",
104
+ "useCustomGas": true,
105
+ "tokenIn": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
106
+ "tokenOut": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
107
+ "amountHuman": "100",
108
+ "slippagePercent": 0.5
109
+ }
110
+ ```
111
+
112
+ ## After submit (`{ requestId }`)
113
+
114
+ 1. `wait_for_sign_request_ready` (if needed)
115
+ 2. `sign_request_agree` (multi-agree)
116
+ 3. `trigger_sign_result`
117
+ 4. `broadcast_sign_result`
118
+
119
+ ## Troubleshooting
120
+
121
+ | Symptom | Likely cause |
122
+ |---------|----------------|
123
+ | Empty route / zero output | Pair not connected in pool graph, or amount precision too high for token decimals |
124
+ | Chain not supported | `chainId` missing from Curve SDK networks |
125
+ | No rpcGateway | Chain not in node registry or missing RPC URL |
126
+ | Quote works, build fails | `tokenIn`/`tokenOut`/`amountHuman`/`slippagePercent` differ from quote; native build used `0xeeee` instead of wrapped native for `tokenIn` |
127
+
128
+ ## Notes
129
+
130
+ - Curve discovers tokens from the **pool graph**, not an allowlist — `get_defi_protocol_supported_tokens` lists what is swappable on your RPC.
131
+ - Slippage applies to the router `populateSwap` call; explain it to users before building.
132
+ - There is no Curve MCP “preview health factor” — swaps are not lending actions.
@@ -1,22 +1,198 @@
1
1
  ---
2
2
  name: uniswap-v4
3
- description: Uniswap V4 swaps via Trade API quote, swap calldata, and multisign submit.
3
+ description: Uniswap v4 Universal Router swaps via Trade API quote, calldata, and MPC multisign.
4
4
  ---
5
5
 
6
6
  # Uniswap v4 (MCP)
7
7
 
8
- ## Multi-step swap flow
8
+ Uniswap swaps on Continuum use the **Uniswap Trade API** (quote + swap calldata) and the on-chain **Universal Router**. The MCP path uses **classic ERC-20 approve** (Permit2 disabled), matching the node app — not Permit2 signatures.
9
9
 
10
- 1. `ctm_uniswap_v4_quote` requires `UNISWAP_API_KEY` env on MCP server
11
- 2. `ctm_uniswap_v4_create_swap` — Universal Router calldata
12
- 3. `ctm_uniswap_v4_build_swap_multisign` — submits sign request → `{ requestId }`
10
+ ## Architecture (explain this to users)
13
11
 
14
- ## Chains / tokens
12
+ | Concept | What it is | Agent needs to know |
13
+ |---------|------------|---------------------|
14
+ | **Trade API** | Uniswap-hosted routing/quote service | Requires `UNISWAP_API_KEY` on the node (not in tool input) |
15
+ | **Universal Router** | On-chain entry contract per chain | Address is embedded in Trade API `swap` calldata — do not guess |
16
+ | **Swapper** | Address that executes the swap | MPC wallet = KeyGen `ethereumaddress`; pass `keyGenId` and server resolves `swapper` |
17
+ | **Permit2** | Optional allowance path | **Disabled** in MCP (`permit2Disabled: true`) — uses direct ERC-20 approve batch instead |
15
18
 
16
- - Chains: `get_defi_protocol_supported_chains` (Universal Router map)
17
- - Tokens: any ERC-20 on supported chain; use `get_token_registry` for wallet tokens
18
- - `tokenOut` may be any address on same chain
19
+ Unlike Aave, there are no hubs/spokes — routing is entirely inside the Trade API response.
19
20
 
20
- ## After submit
21
+ ## Before any swap
21
22
 
22
- Base MCP: `trigger_sign_result` `broadcast_sign_result`.
23
+ 1. `load_defi_protocol({ protocolId: "uniswap-v4" })`
24
+ 2. Confirm `UNISWAP_API_KEY` is set: `list_environment_variables` or `load_defi_protocol` response (`uniswapApiKeyConfigured`). Create a key at [Uniswap developer dashboard](https://developers.uniswap.org/dashboard/welcome) and add it in **Node → AI Agent → Variables**.
25
+ 3. `get_defi_protocol_supported_chains` — Universal Router must exist on `chainId`
26
+ 4. `get_token_registry` — resolve `tokenIn` / `tokenOut` addresses (any ERC-20 on supported chains)
27
+
28
+ ## Tool flow (three steps → one sign request)
29
+
30
+ | Step | Tool | Creates sign request? |
31
+ |------|------|------------------------|
32
+ | 1. Quote | `ctm_uniswap_v4_quote` | No — routing + amounts |
33
+ | 2. Calldata | `ctm_uniswap_v4_create_swap` | No — Universal Router tx skeleton |
34
+ | 3. Submit | `ctm_uniswap_v4_build_swap_multisign` | Yes → `{ requestId }` |
35
+
36
+ Do not skip steps. Pass the **full quote JSON** into `create_swap`, then pass **both** `create_swap` output and quote snapshot into `build_swap_multisign`.
37
+
38
+ ## `ctm_uniswap_v4_quote`
39
+
40
+ | Field | Required | Notes |
41
+ |-------|----------|-------|
42
+ | `type` | yes | `EXACT_INPUT` (sell exact tokenIn) or `EXACT_OUTPUT` (receive exact tokenOut) |
43
+ | `amount` | yes | **Base units** string (wei for 18-decimal tokens). For human amounts, convert with token decimals first |
44
+ | `tokenIn` | yes | Input token; native ETH: `0x0000000000000000000000000000000000000000` |
45
+ | `tokenOut` | yes | Output token address (same chain) |
46
+ | `chainId` | yes | Must be Uniswap-supported |
47
+ | `keyGenId` | recommended | Server resolves `swapper` (MPC executor) |
48
+ | `swapper` | optional | MPC wallet address if `keyGenId` omitted |
49
+ | `slippage` | no | Defaults to **0.5** (percent) when Permit2 disabled |
50
+ | `permit2Disabled` | no | Defaults **true** (classic approve path) |
51
+ | `uniswapApiKey` | no | **Do not pass** — injected from `UNISWAP_API_KEY` Variable |
52
+
53
+ **Tip:** For `EXACT_INPUT` with human amounts, read token decimals on-chain (or registry) and convert to wei before quoting.
54
+
55
+ ## `ctm_uniswap_v4_create_swap`
56
+
57
+ | Field | Required | Notes |
58
+ |-------|----------|-------|
59
+ | `fullQuoteFromPermit` | yes | **Entire** JSON object returned by `ctm_uniswap_v4_quote` |
60
+ | `swapTransactionDeadlineUnix` | no | On-chain deadline; default ~30 minutes from now |
61
+ | `uniswapApiKey` | no | Injected by server |
62
+
63
+ **Output:** `{ swap: { to, data, value, gasLimit? }, requestId?, gasFee? }` — keep the full object for the next step.
64
+
65
+ ## `ctm_uniswap_v4_build_swap_multisign`
66
+
67
+ | Field | Required | Notes |
68
+ |-------|----------|-------|
69
+ | `keyGenId` | yes | MPC KeyGen |
70
+ | `chainId` | yes | Same as quote |
71
+ | `purposeText` | yes | Human-readable purpose |
72
+ | `tokenIn` | yes | Same as quote (`0x0` for native ETH in) |
73
+ | `swap` | yes | `swap` object from `create_swap` response |
74
+ | `createSwapResponse` | yes | Full `create_swap` JSON |
75
+ | `fullQuoteSnapshot` | yes | Full quote JSON from step 1 |
76
+ | `swapDeadlineUnix` | yes | **Same** deadline passed to `create_swap` |
77
+ | `slippagePercent` | no | Extra approve headroom for `EXACT_OUTPUT` swaps |
78
+ | `useCustomGas` | no | `true` for chain registry gas |
79
+
80
+ Server resolves `keyGen`, `executorAddress`, `rpcUrl`, `chainDetail`.
81
+
82
+ **Transaction batch:** 1–3 txs — optional ERC-20 approve(s), then Universal Router `swap` (native-in may skip approve).
83
+
84
+ ## Supported chains
85
+
86
+ Use `get_defi_protocol_supported_chains` as source of truth. Universal Router is deployed on major EVM chains including:
87
+
88
+ Ethereum (1), Sepolia (11155111), Polygon (137), Optimism (10), Arbitrum (42161), Base (8453), BSC (56), Avalanche (43114), Celo (42220), zkSync (324), Blast (81457), and others in the router map.
89
+
90
+ **Tokens:** Any ERC-20 on a supported chain; `tokenOut` can be any valid address with liquidity.
91
+
92
+ ## Example: EXACT_INPUT — sell 0.1 ETH for USDC on Ethereum
93
+
94
+ Assume 0.1 ETH = `100000000000000000` wei.
95
+
96
+ **1. Quote**
97
+
98
+ ```json
99
+ {
100
+ "type": "EXACT_INPUT",
101
+ "amount": "100000000000000000",
102
+ "tokenIn": "0x0000000000000000000000000000000000000000",
103
+ "tokenOut": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
104
+ "chainId": 1,
105
+ "keyGenId": "<keygen-id>"
106
+ }
107
+ ```
108
+
109
+ **2. Create swap**
110
+
111
+ ```json
112
+ {
113
+ "fullQuoteFromPermit": "<full quote JSON from step 1>"
114
+ }
115
+ ```
116
+
117
+ **3. Build multisign**
118
+
119
+ ```json
120
+ {
121
+ "keyGenId": "<keygen-id>",
122
+ "chainId": 1,
123
+ "purposeText": "Swap 0.1 ETH to USDC via Uniswap v4",
124
+ "useCustomGas": true,
125
+ "tokenIn": "0x0000000000000000000000000000000000000000",
126
+ "swap": "<swap object from create_swap>",
127
+ "createSwapResponse": "<full create_swap JSON>",
128
+ "fullQuoteSnapshot": "<full quote JSON from step 1>",
129
+ "swapDeadlineUnix": 1735689600
130
+ }
131
+ ```
132
+
133
+ Use the same `swapTransactionDeadlineUnix` in step 2 and `swapDeadlineUnix` in step 3.
134
+
135
+ ## Teaching users about slippage
136
+
137
+ - **Quote slippage** (`slippage` on quote, default 0.5%): Trade API routing tolerance.
138
+ - **On-chain batch** may add approve headroom; for `EXACT_OUTPUT`, optional `slippagePercent` on build adjusts approval sizing.
139
+ - Explain expected input/output from quote JSON before submitting the sign request.
140
+
141
+ ## After submit (`{ requestId }`)
142
+
143
+ 1. `wait_for_sign_request_ready` (if needed)
144
+ 2. `sign_request_agree` (multi-agree)
145
+ 3. `trigger_sign_result`
146
+ 4. `broadcast_sign_result`
147
+
148
+ ## Troubleshooting
149
+
150
+ | Symptom | Likely cause |
151
+ |---------|----------------|
152
+ | Missing API key error | `UNISWAP_API_KEY` not in node Variables |
153
+ | Quote fails | Unsupported chain, illiquid pair, or `amount` not in base units |
154
+ | Build fails after quote | Quote/swap/deadline mismatch between steps; stale quote (re-quote) |
155
+ | Wrong swapper | Missing or wrong `keyGenId`; swapper must be MPC executor |
156
+
157
+ ## Liquidity provision (V4 positions)
158
+
159
+ Uniswap V4 LP uses the **Liquidity Provisioning API** (`POST /lp/create`, `/increase`, `/decrease`, `/claim`) and on-chain **Position Manager** NFTs. There is **no separate LP-token staking** — fees accrue in-position; collect via `/lp/claim`.
160
+
161
+ MCP uses **classic ERC-20 approve** batches (Permit2 disabled), matching swaps.
162
+
163
+ ### LP tool flows
164
+
165
+ | Action | Prep tool | Build multisign |
166
+ |--------|-----------|-----------------|
167
+ | Mint new position | `ctm_uniswap_v4_lp_create_position` | `ctm_uniswap_v4_build_mint_liquidity_multisign` |
168
+ | Increase | `ctm_uniswap_v4_lp_increase` | `ctm_uniswap_v4_build_increase_liquidity_multisign` |
169
+ | Decrease | `ctm_uniswap_v4_lp_decrease` | `ctm_uniswap_v4_build_decrease_liquidity_multisign` |
170
+ | Collect fees | `ctm_uniswap_v4_lp_collect` | `ctm_uniswap_v4_build_collect_fees_multisign` |
171
+ | List positions | `ctm_uniswap_v4_lp_list_positions` | — (token registry ERC721) |
172
+ | Register position NFT | `ctm_uniswap_v4_register_position_nft` | — (management-signed) |
173
+ | Register from mint tx | `ctm_uniswap_v4_register_position_from_mint_tx` | — (after mint execute) |
174
+
175
+ ### Mint workflow
176
+
177
+ 1. Resolve pool (`existingPool` with `poolReference` = V4 pool id, or `newPool` to initialize).
178
+ 2. Set price range via `priceBounds` (human prices) or `tickBounds`.
179
+ 3. `independentToken`: one token + amount in base units; API computes the other.
180
+ 4. `ctm_uniswap_v4_lp_create_position` → full JSON with `create` tx + `token0`/`token1` amounts.
181
+ 5. `ctm_uniswap_v4_build_mint_liquidity_multisign` with `lpResponse` = full create JSON, `purposeText`, `keyGenId`, `chainId`.
182
+ 6. Pass `nativeWrapped` when pool uses native ETH (`0x0`) as token0 or token1.
183
+ 7. After execute: `ctm_uniswap_v4_register_position_from_mint_tx` with the mint step `txHash` — adds the position ERC721 to the node token registry so `lp_list_positions` can find it.
184
+
185
+ ### Manage existing positions (MCP — token registry, no block scan)
186
+
187
+ Agents **do not** scan blockchain logs for positions. Positions must appear as **ERC721** entries in the node token registry (Uniswap V4 Position Manager contract + `tokenId`).
188
+
189
+ 1. `ctm_uniswap_v4_lp_list_positions` with `keyGenId` + `chainId` (or `walletAddress` + `chainId`).
190
+ 2. If a position is missing: `ctm_uniswap_v4_register_position_nft` (known `tokenId`), `add_to_token_registry` (ERC721), or in the Continuum app use **Add Token** or **Manage liquidity → Refresh positions** (blockchain scan).
191
+ 3. For increase/decrease/collect, pass `nftTokenId` / `tokenId` from the list output plus `token0Address`/`token1Address` from position context. Prep tools error with registry hints when the NFT is not saved.
192
+ 4. Decrease uses `liquidityPercentageToDecrease` (1–100).
193
+
194
+ ## Notes
195
+
196
+ - Do **not** pass `uniswapApiKey` in tool calls — the MCP server injects it.
197
+ - Native ETH always uses `tokenIn: 0x0` for Uniswap (not `0xeeee` — that is Curve’s placeholder).
198
+ - `get_defi_protocol_skill` (this file) is the canonical workflow reference for agents.