@yummybait/uniswap-tx-builder-mcp 0.3.1
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.
- package/.claude-plugin/marketplace.json +11 -0
- package/.claude-plugin/plugin.json +16 -0
- package/LICENSE +201 -0
- package/README.md +195 -0
- package/bin/install-skill.mjs +34 -0
- package/dist/abi.d.ts +310 -0
- package/dist/abi.js +205 -0
- package/dist/abi.js.map +1 -0
- package/dist/builder.d.ts +149 -0
- package/dist/builder.js +335 -0
- package/dist/builder.js.map +1 -0
- package/dist/config.d.ts +11 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/mcp.d.ts +2 -0
- package/dist/mcp.js +363 -0
- package/dist/mcp.js.map +1 -0
- package/dist/operations.d.ts +82 -0
- package/dist/operations.js +114 -0
- package/dist/operations.js.map +1 -0
- package/dist/ticks.d.ts +69 -0
- package/dist/ticks.js +174 -0
- package/dist/ticks.js.map +1 -0
- package/package.json +64 -0
- package/skills/uniswap-tx-builder/SKILL.md +100 -0
package/dist/ticks.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Uniswap v3 tick math — no RPC, no I/O. Converts human-readable prices
|
|
3
|
+
* into the aligned ticks a position uses, and live pool readings into
|
|
4
|
+
* range suggestions and mint amounts.
|
|
5
|
+
*
|
|
6
|
+
* A pool price is `token1` per `token0` expressed in each token's *smallest*
|
|
7
|
+
* unit. A human price `P` ("how many whole token1 per whole token0") relates
|
|
8
|
+
* to the raw price by `raw = P * 10^(decimals1 - decimals0)`, and
|
|
9
|
+
* `tick = log(raw) / log(1.0001)`.
|
|
10
|
+
*/
|
|
11
|
+
// Uniswap v3 tick bounds.
|
|
12
|
+
export const MIN_TICK = -887272;
|
|
13
|
+
export const MAX_TICK = 887272;
|
|
14
|
+
const LOG_BASE = Math.log(1.0001);
|
|
15
|
+
/** Standard fee tier → tick spacing. */
|
|
16
|
+
export const FEE_TO_TICK_SPACING = {
|
|
17
|
+
100: 1,
|
|
18
|
+
500: 10,
|
|
19
|
+
3000: 60,
|
|
20
|
+
10000: 200,
|
|
21
|
+
};
|
|
22
|
+
export function feeToTickSpacing(fee) {
|
|
23
|
+
const spacing = FEE_TO_TICK_SPACING[fee];
|
|
24
|
+
if (!spacing) {
|
|
25
|
+
const known = Object.keys(FEE_TO_TICK_SPACING).join(", ");
|
|
26
|
+
throw new Error(`Unknown fee tier ${fee}. Supported: ${known}.`);
|
|
27
|
+
}
|
|
28
|
+
return spacing;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Convert a human price (token1 per token0) to the (unaligned) tick.
|
|
32
|
+
* `price` must be > 0.
|
|
33
|
+
*/
|
|
34
|
+
export function priceToTick(price, decimals0, decimals1) {
|
|
35
|
+
if (!(price > 0)) {
|
|
36
|
+
throw new Error(`Price must be a positive number, got ${price}`);
|
|
37
|
+
}
|
|
38
|
+
const raw = price * 10 ** (decimals1 - decimals0);
|
|
39
|
+
return Math.log(raw) / LOG_BASE;
|
|
40
|
+
}
|
|
41
|
+
/** Snap a tick to the nearest usable tick for `spacing`, clamped to bounds. */
|
|
42
|
+
export function alignTick(tick, spacing) {
|
|
43
|
+
const snapped = Math.round(tick / spacing) * spacing;
|
|
44
|
+
const minAligned = Math.ceil(MIN_TICK / spacing) * spacing;
|
|
45
|
+
const maxAligned = Math.floor(MAX_TICK / spacing) * spacing;
|
|
46
|
+
return Math.min(Math.max(snapped, minAligned), maxAligned);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Convert a human price range to aligned [tickLower, tickUpper] for `fee`.
|
|
50
|
+
* Prices are reordered if given high→low, and the bounds are guaranteed to
|
|
51
|
+
* differ by at least one tick spacing.
|
|
52
|
+
*/
|
|
53
|
+
export function priceRangeToTicks(priceLower, priceUpper, fee, decimals0, decimals1) {
|
|
54
|
+
const tickSpacing = feeToTickSpacing(fee);
|
|
55
|
+
const [lo, hi] = priceLower <= priceUpper
|
|
56
|
+
? [priceLower, priceUpper]
|
|
57
|
+
: [priceUpper, priceLower];
|
|
58
|
+
let tickLower = alignTick(priceToTick(lo, decimals0, decimals1), tickSpacing);
|
|
59
|
+
let tickUpper = alignTick(priceToTick(hi, decimals0, decimals1), tickSpacing);
|
|
60
|
+
if (tickLower >= tickUpper) {
|
|
61
|
+
// Range collapsed after alignment — widen to one spacing, staying in bounds.
|
|
62
|
+
const maxAligned = Math.floor(MAX_TICK / tickSpacing) * tickSpacing;
|
|
63
|
+
if (tickLower >= maxAligned) {
|
|
64
|
+
tickLower = maxAligned - tickSpacing;
|
|
65
|
+
tickUpper = maxAligned;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
tickUpper = tickLower + tickSpacing;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { tickLower, tickUpper, tickSpacing };
|
|
72
|
+
}
|
|
73
|
+
// ─── TickMath.getSqrtRatioAtTick (exact port of Uniswap v3-core) ─────
|
|
74
|
+
const Q96 = 1n << 96n;
|
|
75
|
+
const MAX_UINT256 = (1n << 256n) - 1n;
|
|
76
|
+
// One 128-bit multiplier per tick bit: sqrt(1.0001)^-(2^i) in Q128.
|
|
77
|
+
const TICK_MULTIPLIERS = [
|
|
78
|
+
0xfffcb933bd6fad37aa2d162d1a594001n,
|
|
79
|
+
0xfff97272373d413259a46990580e213an,
|
|
80
|
+
0xfff2e50f5f656932ef12357cf3c7fdccn,
|
|
81
|
+
0xffe5caca7e10e4e61c3624eaa0941cd0n,
|
|
82
|
+
0xffcb9843d60f6159c9db58835c926644n,
|
|
83
|
+
0xff973b41fa98c081472e6896dfb254c0n,
|
|
84
|
+
0xff2ea16466c96a3843ec78b326b52861n,
|
|
85
|
+
0xfe5dee046a99a2a811c461f1969c3053n,
|
|
86
|
+
0xfcbe86c7900a88aedcffc83b479aa3a4n,
|
|
87
|
+
0xf987a7253ac413176f2b074cf7815e54n,
|
|
88
|
+
0xf3392b0822b70005940c7a398e4b70f3n,
|
|
89
|
+
0xe7159475a2c29b7443b29c7fa6e889d9n,
|
|
90
|
+
0xd097f3bdfd2022b8845ad8f792aa5825n,
|
|
91
|
+
0xa9f746462d870fdf8a65dc1f90e061e5n,
|
|
92
|
+
0x70d869a156d2a1b890bb3df62baf32f7n,
|
|
93
|
+
0x31be135f97d08fd981231505542fcfa6n,
|
|
94
|
+
0x9aa508b5b7a84e1c677de54f3e99bc9n,
|
|
95
|
+
0x5d6af8dedb81196699c329225ee604n,
|
|
96
|
+
0x2216e584f5fa1ea926041bedfe98n,
|
|
97
|
+
0x48a170391f7dc42444e8fa2n,
|
|
98
|
+
];
|
|
99
|
+
/**
|
|
100
|
+
* Q64.96 sqrt price at a tick — bit-for-bit identical to the on-chain
|
|
101
|
+
* `TickMath.getSqrtRatioAtTick`, so amounts derived from it match what the
|
|
102
|
+
* pool computes.
|
|
103
|
+
*/
|
|
104
|
+
export function getSqrtRatioAtTick(tick) {
|
|
105
|
+
if (!Number.isInteger(tick) || tick < MIN_TICK || tick > MAX_TICK) {
|
|
106
|
+
throw new Error(`Tick out of range: ${tick}`);
|
|
107
|
+
}
|
|
108
|
+
const absTick = Math.abs(tick);
|
|
109
|
+
let ratio = (absTick & 1) !== 0 ? TICK_MULTIPLIERS[0] : 1n << 128n;
|
|
110
|
+
for (let i = 1; i < TICK_MULTIPLIERS.length; i++) {
|
|
111
|
+
if ((absTick >> i) & 1) {
|
|
112
|
+
ratio = (ratio * TICK_MULTIPLIERS[i]) >> 128n;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (tick > 0)
|
|
116
|
+
ratio = MAX_UINT256 / ratio;
|
|
117
|
+
// Q128.128 → Q64.96, rounding up.
|
|
118
|
+
return (ratio >> 32n) + (ratio % (1n << 32n) === 0n ? 0n : 1n);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Ticks within ±`rangePct` of the spot tick, rounded INWARD to `tickSpacing`
|
|
122
|
+
* — outward rounding would exceed the caller's stated max width.
|
|
123
|
+
*/
|
|
124
|
+
export function suggestRangeTicks(tick, tickSpacing, rangePct) {
|
|
125
|
+
if (!(rangePct > 0)) {
|
|
126
|
+
throw new Error(`rangePct must be positive, got ${rangePct}`);
|
|
127
|
+
}
|
|
128
|
+
const delta = Math.log(1 + rangePct / 100) / LOG_BASE;
|
|
129
|
+
const tickLower = Math.ceil((tick - delta) / tickSpacing) * tickSpacing;
|
|
130
|
+
const tickUpper = Math.floor((tick + delta) / tickSpacing) * tickSpacing;
|
|
131
|
+
if (tickLower >= tickUpper) {
|
|
132
|
+
throw new Error(`Range ±${rangePct}% is too narrow for tick spacing ${tickSpacing} — widen it.`);
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
tickLower,
|
|
136
|
+
tickUpper,
|
|
137
|
+
pctBelow: (1.0001 ** (tickLower - tick) - 1) * 100,
|
|
138
|
+
pctAbove: (1.0001 ** (tickUpper - tick) - 1) * 100,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The largest both-sided deposit the balances allow at the LIVE pool price:
|
|
143
|
+
* per-side liquidity `L0`/`L1` from the full balances, `L = min(L0, L1)`,
|
|
144
|
+
* then back out the exact amounts that liquidity consumes. Feeding these to
|
|
145
|
+
* `mint` matches the pool's own math, so slippage mins can stay tight.
|
|
146
|
+
*
|
|
147
|
+
* Throws when the spot price is outside [tickLower, tickUpper] — such a mint
|
|
148
|
+
* is single-sided and the caller should recompute the range instead.
|
|
149
|
+
*/
|
|
150
|
+
export function computeMintAmounts(sqrtPriceX96, tickLower, tickUpper, balance0, balance1) {
|
|
151
|
+
if (tickLower >= tickUpper) {
|
|
152
|
+
throw new Error(`tickLower must be < tickUpper (${tickLower} >= ${tickUpper})`);
|
|
153
|
+
}
|
|
154
|
+
const sqrtLower = getSqrtRatioAtTick(tickLower);
|
|
155
|
+
const sqrtUpper = getSqrtRatioAtTick(tickUpper);
|
|
156
|
+
if (!(sqrtLower < sqrtPriceX96 && sqrtPriceX96 < sqrtUpper)) {
|
|
157
|
+
throw new Error(`Spot price is outside [${tickLower}, ${tickUpper}] — a mint there would be ` +
|
|
158
|
+
"single-sided; recompute the range around the current tick.");
|
|
159
|
+
}
|
|
160
|
+
const l0 = (balance0 * ((sqrtPriceX96 * sqrtUpper) / Q96)) / (sqrtUpper - sqrtPriceX96);
|
|
161
|
+
const l1 = (balance1 * Q96) / (sqrtPriceX96 - sqrtLower);
|
|
162
|
+
const liquidity = l0 < l1 ? l0 : l1;
|
|
163
|
+
return {
|
|
164
|
+
amount0Desired: (liquidity * (sqrtUpper - sqrtPriceX96) * Q96) / (sqrtUpper * sqrtPriceX96),
|
|
165
|
+
amount1Desired: (liquidity * (sqrtPriceX96 - sqrtLower)) / Q96,
|
|
166
|
+
limitingSide: l0 < l1 ? "token0" : "token1",
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** Human price (token1 per token0) from a Q64.96 sqrt price. */
|
|
170
|
+
export function sqrtPriceX96ToPrice(sqrtPriceX96, decimals0, decimals1) {
|
|
171
|
+
const ratio = Number(sqrtPriceX96) / Number(Q96);
|
|
172
|
+
return ratio * ratio * 10 ** (decimals0 - decimals1);
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=ticks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ticks.js","sourceRoot":"","sources":["../src/ticks.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yummybait/uniswap-tx-builder-mcp",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "Keyless MCP server that builds unsigned Uniswap v3 liquidity-position transactions (collect, close, mint, increase, wrap/swap via Universal Router) and simulates them — you sign with your own wallet.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"uniswap-tx-builder-mcp": "dist/mcp.js",
|
|
9
|
+
"uniswap-tx-builder-skill": "bin/install-skill.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin",
|
|
14
|
+
"skills",
|
|
15
|
+
".claude-plugin"
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/Yummybait-fin/uniswap-tx-builder-mcp.git"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://github.com/Yummybait-fin/uniswap-tx-builder-mcp#readme",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Yummybait-fin/uniswap-tx-builder-mcp/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"uniswap",
|
|
29
|
+
"defi",
|
|
30
|
+
"liquidity",
|
|
31
|
+
"ethereum",
|
|
32
|
+
"base",
|
|
33
|
+
"unsigned-transactions"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=20"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public",
|
|
40
|
+
"provenance": true
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"dev": "tsx watch src/mcp.ts",
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"start": "node dist/mcp.js",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"verify:pack": "npm run build && node scripts/verify-tarball.mjs",
|
|
50
|
+
"prepack": "npm run build",
|
|
51
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run verify:pack"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
55
|
+
"viem": "^2.31.3",
|
|
56
|
+
"zod": "3.25.76"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.15.21",
|
|
60
|
+
"tsx": "^4.19.4",
|
|
61
|
+
"typescript": "^5.8.3",
|
|
62
|
+
"vitest": "^4.1.0"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: uniswap-tx-builder
|
|
3
|
+
description: Build unsigned Uniswap v3 liquidity-position transactions with the uniswap-tx-builder MCP — collect fees, close (remove liquidity + collect, optionally burn), mint a new position, increase liquidity, wrap native ETH / swap WETH via the Universal Router, read live pool state, and plan a position from a human price range — simulate them, then hand the calldata (or the ready-made unsigned RLP) to your own wallet to sign. Use whenever you need to manage, rebalance, open, or close a Uniswap v3 LP position through this MCP. Generic: no app- or wallet-specific knowledge.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# uniswap-tx-builder
|
|
7
|
+
|
|
8
|
+
This skill ships with the **uniswap-tx-builder MCP** — a public, **keyless** server that builds
|
|
9
|
+
*unsigned* Uniswap v3 position transactions and (optionally) simulates them. **It never holds
|
|
10
|
+
keys and never signs.** You build calldata here, then sign + broadcast with *your own* wallet
|
|
11
|
+
(any signer — a CDP wallet MCP, viem, etc.).
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
All build tools return `tx = { to, data, value, chainId }`, `rlp`, and a `description`.
|
|
16
|
+
`value` is `"0"` except `build_wrap`/`build_swap` with native ETH (payable). Addresses are
|
|
17
|
+
`0x…40`; `positionId`/amounts are decimal **strings** (they exceed JS safe integers).
|
|
18
|
+
|
|
19
|
+
`rlp` is the **unsigned EIP-1559 (type-2)** serialization of `tx` with nonce, fees and gas
|
|
20
|
+
zeroed — pass it directly to signing services that populate those at signing time (e.g. a CDP
|
|
21
|
+
wallet MCP `send_transaction`). If your signer manages nonces itself, serialize `tx` instead.
|
|
22
|
+
|
|
23
|
+
| Tool | Args | Notes |
|
|
24
|
+
|------|------|-------|
|
|
25
|
+
| `build_collect` | `chainId, positionId, recipient, simulate?` | Collect all uncollected fees to `recipient`. |
|
|
26
|
+
| `build_close` | `chainId, positionId, recipient, burn?, simulate?` | Remove all liquidity **+** collect. `burn: true` also burns the now-empty NFT in the same multicall. Returns the read `position` (token0/1, fee, tickLower/Upper, liquidity). |
|
|
27
|
+
| `build_mint` | `chainId, token0, token1, fee, tickLower, tickUpper, amount0Desired, amount1Desired, recipient, slippageBps?, simulate?` | Mint a new position. Get the amounts from `get_pool_state` **immediately before** this call. |
|
|
28
|
+
| `build_increase` | `chainId, positionId, amount0Desired, amount1Desired, recipient, slippageBps?, simulate?` | Add liquidity to an **existing** position. |
|
|
29
|
+
| `build_wrap` | `chainId, amountWei, recipient?, sender?, deadline?, simulate?` | Native ETH → WETH via Universal Router `WRAP_ETH` (payable; works under UR-allowlisting wallet policies where `WETH.deposit()` doesn't). |
|
|
30
|
+
| `build_swap` | `chainId, amountInWei, tokenOut, fee, amountOutMin, recipient?, wrapWei?, sender?, deadline?, simulate?` | Exact-in single-hop WETH → `tokenOut`. With `wrapWei` (≥ `amountInWei`): wraps that much native ETH, swaps `amountInWei`, sweeps the WETH remainder — one payable tx. Without it: pays WETH via **Permit2** (needs a Permit2 approval). |
|
|
31
|
+
| `plan_position` | `chainId, token0, token1, fee, priceLower, priceUpper, amount0?, amount1?` | **Read-only.** Human price range + human amounts → aligned ticks + wei amounts. |
|
|
32
|
+
| `get_pool_state` | `chainId, token0, token1, fee, rangePct?, tickLower?, tickUpper?, balance0?, balance1?` | **Read-only.** Live `pool`, `tick`, `sqrtPriceX96`, `price` (token1 per token0, human), `tickSpacing`. See below. |
|
|
33
|
+
|
|
34
|
+
- **`simulate`** runs an `eth_call` dry-run. **On by default for collect/close** — keep it on so
|
|
35
|
+
you only ever sign txs that would succeed. **Off by default for mint/increase** (they need token
|
|
36
|
+
approvals + balances); pass `simulate: true` only if those are already in place. For
|
|
37
|
+
**wrap/swap**, pass `sender` (the wallet that will sign) — simulation then runs by default with
|
|
38
|
+
the correct `from` and `value`.
|
|
39
|
+
- A simulation failure comes back as an error — **do not sign** a tx that failed to simulate.
|
|
40
|
+
- `recipient` on wrap/swap **defaults to the tx sender** (the router's `MSG_SENDER` placeholder).
|
|
41
|
+
Omit it unless the output must go to a different address.
|
|
42
|
+
|
|
43
|
+
### `get_pool_state` (live spot → range → mint amounts)
|
|
44
|
+
|
|
45
|
+
The one tool to call **right before every mint**:
|
|
46
|
+
|
|
47
|
+
1. `rangePct: 2` → `suggested.tickLower/tickUpper` within ±2% of spot, **rounded inward** to the
|
|
48
|
+
tick spacing (the range never exceeds your stated width).
|
|
49
|
+
2. `tickLower + tickUpper + balance0 + balance1` (raw wei balances) → `mintAmounts.
|
|
50
|
+
amount0Desired/amount1Desired` computed from the **live** sqrtPrice ratio, plus
|
|
51
|
+
`limitingSide` (which balance caps the position). Feed these straight into `build_mint`.
|
|
52
|
+
It errors if spot has moved outside the range — recompute the range, don't force the mint.
|
|
53
|
+
|
|
54
|
+
Amounts computed from a stale price revert the mint with **"Price slippage check"** — always
|
|
55
|
+
recompute in the same breath as `build_mint`.
|
|
56
|
+
|
|
57
|
+
### `plan_position` (human price range → ticks)
|
|
58
|
+
|
|
59
|
+
When you start from a human price range instead of a ±pct width: `priceLower`/`priceUpper` are
|
|
60
|
+
**token1 per token0** in whole-token units, reordered if given high→low, snapped to the fee
|
|
61
|
+
tier's tick spacing. It reads `decimals` over RPC to convert `amount0`/`amount1` to wei, but does
|
|
62
|
+
**not** compute the range-optimal ratio — use `get_pool_state` with balances for that.
|
|
63
|
+
|
|
64
|
+
`token0` **must be `<` `token1`** by address (Uniswap's ordering) for both planning tools. If
|
|
65
|
+
they aren't, swap the pair and invert the prices — the tools error otherwise.
|
|
66
|
+
|
|
67
|
+
## Signing (your wallet, not this MCP)
|
|
68
|
+
|
|
69
|
+
Take the returned `rlp` (for signing services that fill nonce/fees) or `tx` (for signers that
|
|
70
|
+
serialize themselves) and sign + broadcast with your own signer. This MCP is keyless, so the only
|
|
71
|
+
limits that apply are **your wallet's** (e.g. a CDP Wallet Policy). If your wallet rejects the
|
|
72
|
+
tx, report it — never try to route around the wallet's policy.
|
|
73
|
+
|
|
74
|
+
For `build_mint` and `build_increase`, your wallet must hold the input tokens **and have an
|
|
75
|
+
ERC-20 approval to the NonfungiblePositionManager** beforehand — that's the signer's
|
|
76
|
+
responsibility, not this MCP's. (This is also why their `simulate` is off by default.)
|
|
77
|
+
`build_wrap`/`build_swap` use the Universal Router v1.2 (`0x3fC91A3a…B2b7FAD`) — the address
|
|
78
|
+
wallet policies typically allowlist.
|
|
79
|
+
|
|
80
|
+
## Position lifecycle
|
|
81
|
+
|
|
82
|
+
- **Collect fees:** `build_collect` → sign.
|
|
83
|
+
- **Add liquidity:** `build_increase` → sign (after approvals).
|
|
84
|
+
- **Close:** `build_close` → sign. Use the returned `position` (pair, range, liquidity) to inform
|
|
85
|
+
the decision. Pass `burn: true` if you want the empty NFT gone too.
|
|
86
|
+
- **Open around spot:** `get_pool_state` (rangePct → ticks, balances → amounts) → `build_mint` →
|
|
87
|
+
sign. If the wallet holds native ETH instead of WETH: `build_swap` with `wrapWei` (or
|
|
88
|
+
`build_wrap`) first, confirm, then recompute amounts and mint.
|
|
89
|
+
- **Open with a price range:** `plan_position` → `get_pool_state` (balances → amounts) →
|
|
90
|
+
`build_mint` → sign.
|
|
91
|
+
- **Rebalance** = **close → mint a recentered range.** The mint amounts depend on the tokens
|
|
92
|
+
freed by the close, so **do the close first**, then `get_pool_state` + `build_mint` the new
|
|
93
|
+
range once those balances are realized (typically a later step/cycle). Center the new range on
|
|
94
|
+
the current price; widen it for volatile pairs, tighten it for stable pairs.
|
|
95
|
+
|
|
96
|
+
## Chains
|
|
97
|
+
|
|
98
|
+
Supports the chains configured in the MCP (NFPM/factory/Universal Router/WETH9 per chain):
|
|
99
|
+
Ethereum (1), Optimism (10), Polygon (137), Base (8453), Arbitrum (42161). On Polygon the
|
|
100
|
+
"wrapped native" is WMATIC. Calling an unconfigured chain returns an "Unsupported chain" error.
|