@arcnow/mcp 0.1.1 → 0.2.0
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/README.md +209 -241
- package/dist/config.d.ts +13 -4
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +27 -16
- package/dist/config.js.map +1 -1
- package/dist/format.d.ts +31 -2
- package/dist/format.d.ts.map +1 -1
- package/dist/format.js +88 -1
- package/dist/format.js.map +1 -1
- package/dist/sdk-port.d.ts +23 -6
- package/dist/sdk-port.d.ts.map +1 -1
- package/dist/sdk-port.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +35 -4
- package/dist/server.js.map +1 -1
- package/dist/tools/errors.d.ts.map +1 -1
- package/dist/tools/errors.js +8 -7
- package/dist/tools/errors.js.map +1 -1
- package/dist/tools/pool.d.ts +24 -18
- package/dist/tools/pool.d.ts.map +1 -1
- package/dist/tools/pool.js +63 -50
- package/dist/tools/pool.js.map +1 -1
- package/dist/tools/read.d.ts +6 -2
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +110 -96
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/venue.d.ts +3 -3
- package/dist/tools/venue.d.ts.map +1 -1
- package/dist/tools/venue.js +8 -7
- package/dist/tools/venue.js.map +1 -1
- package/dist/tools/write.d.ts.map +1 -1
- package/dist/tools/write.js +85 -63
- package/dist/tools/write.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -22,8 +22,48 @@ npx -y @arcnow/mcp # read-only. The default, and the useful par
|
|
|
22
22
|
npx -y @arcnow/mcp --allow-writes # can spend, if ARCNOW_PRIVATE_KEY is in the environment.
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
Every client below runs that same command with `ARCNOW_MCP_NETWORK` set. The
|
|
26
|
+
examples say `arc-mainnet`, where [arcnow.io](https://arcnow.io) is live — Arc
|
|
27
|
+
mainnet, chain 5042. `arc-testnet` is the rehearsal network: the same contracts
|
|
28
|
+
at other addresses, with test funds. With the variable unset the server starts
|
|
29
|
+
on `arc-testnet`, so nobody is pointed at real money by omission.
|
|
30
|
+
|
|
31
|
+
**Write mode, in every client:** add `--allow-writes` to the arguments and put
|
|
32
|
+
the signing key **in a file** the server reads through `ARCNOW_PRIVATE_KEY_FILE`
|
|
33
|
+
— never the key itself in a client config, which gets committed and
|
|
34
|
+
screenshotted. Read-only needs no key at all. **On `arc-mainnet` every write is
|
|
35
|
+
real money**: the spend caps below are what bounds a mistake, and the server
|
|
36
|
+
says so at startup and in every session. Every variable is in
|
|
37
|
+
[Configuration](#configuration); [`examples/`](examples) has both shapes — the
|
|
38
|
+
writes-enabled one on `arc-testnet`, as a rehearsal.
|
|
39
|
+
|
|
40
|
+
### Claude Code
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
claude mcp add --env ARCNOW_MCP_NETWORK=arc-mainnet --scope user arcnow -- npx -y @arcnow/mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`--scope` is `local` (this project, you only; the default), `project` (checked
|
|
47
|
+
into `.mcp.json` at the project root, shared with the team) or `user` (every
|
|
48
|
+
project). The project-file form:
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"mcpServers": {
|
|
53
|
+
"arcnow": {
|
|
54
|
+
"command": "npx",
|
|
55
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
56
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Claude Desktop
|
|
63
|
+
|
|
64
|
+
`claude_desktop_config.json` — macOS
|
|
65
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`, Windows
|
|
66
|
+
`%APPDATA%\Claude\claude_desktop_config.json`:
|
|
27
67
|
|
|
28
68
|
```json
|
|
29
69
|
{
|
|
@@ -31,17 +71,89 @@ take this shape in their MCP server configuration:
|
|
|
31
71
|
"arcnow": {
|
|
32
72
|
"command": "npx",
|
|
33
73
|
"args": ["-y", "@arcnow/mcp"],
|
|
34
|
-
"env": { "ARCNOW_MCP_NETWORK": "arc-
|
|
74
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
35
75
|
}
|
|
36
76
|
}
|
|
37
77
|
}
|
|
38
78
|
```
|
|
39
79
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
80
|
+
### Codex
|
|
81
|
+
|
|
82
|
+
`~/.codex/config.toml` (or `.codex/config.toml` in a project):
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
[mcp_servers.arcnow]
|
|
86
|
+
command = "npx"
|
|
87
|
+
args = ["-y", "@arcnow/mcp"]
|
|
88
|
+
|
|
89
|
+
[mcp_servers.arcnow.env]
|
|
90
|
+
ARCNOW_MCP_NETWORK = "arc-mainnet"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Or from the CLI: `codex mcp add arcnow --env ARCNOW_MCP_NETWORK=arc-mainnet -- npx -y @arcnow/mcp`.
|
|
94
|
+
|
|
95
|
+
### Cursor
|
|
96
|
+
|
|
97
|
+
`.cursor/mcp.json` in the project (or `~/.cursor/mcp.json` for every project):
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"mcpServers": {
|
|
102
|
+
"arcnow": {
|
|
103
|
+
"command": "npx",
|
|
104
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
105
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### VS Code (Copilot agent mode)
|
|
112
|
+
|
|
113
|
+
`.vscode/mcp.json` — note the key is `servers`, not `mcpServers`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"servers": {
|
|
118
|
+
"arcnow": {
|
|
119
|
+
"type": "stdio",
|
|
120
|
+
"command": "npx",
|
|
121
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
122
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
For write mode, VS Code's `inputs` can prompt for the key-file path instead of
|
|
129
|
+
writing it into the file; see its MCP documentation for the `${input:…}` form.
|
|
130
|
+
|
|
131
|
+
### Gemini CLI
|
|
132
|
+
|
|
133
|
+
`~/.gemini/settings.json` (or `.gemini/settings.json` in a project):
|
|
134
|
+
|
|
135
|
+
```json
|
|
136
|
+
{
|
|
137
|
+
"mcpServers": {
|
|
138
|
+
"arcnow": {
|
|
139
|
+
"command": "npx",
|
|
140
|
+
"args": ["-y", "@arcnow/mcp"],
|
|
141
|
+
"env": { "ARCNOW_MCP_NETWORK": "arc-mainnet" }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Or `gemini mcp add -e ARCNOW_MCP_NETWORK=arc-mainnet arcnow npx -y @arcnow/mcp`.
|
|
148
|
+
|
|
149
|
+
### Windsurf, Cline
|
|
150
|
+
|
|
151
|
+
Both take the generic `mcpServers` JSON above — the same `command`, `args` and
|
|
152
|
+
`env` — in their MCP settings file.
|
|
153
|
+
|
|
154
|
+
### From source
|
|
155
|
+
|
|
156
|
+
Clone [arcnow-io/arcnow-io-mcp](https://github.com/arcnow-io/arcnow-io-mcp), then
|
|
45
157
|
`npm ci && npm run build && node dist/index.js`; the SDK comes from npm.
|
|
46
158
|
|
|
47
159
|
---
|
|
@@ -97,13 +209,13 @@ Nine read tools, always published. Six write tools, published only on opt-in.
|
|
|
97
209
|
| tool | what it answers |
|
|
98
210
|
| --- | --- |
|
|
99
211
|
| `arcnow_network` | Which chain, which contracts, which graduation venues, whether a v4 router is configured for graduated tokens, **and which mode this server is in**. The first call of any session that might trade. |
|
|
100
|
-
| `arcnow_quote_tokens` | The quote tokens a launch may use — native USDC and the ERC-20s the quote registry allowlists, such as EURC — each with its symbol, name, decimals, address, whether it is native, its launch fee in its own units, whether the registry accepts it now, and **this server's spend cap for it**. At most three `eth_call`s. On a chain with no quote registry
|
|
212
|
+
| `arcnow_quote_tokens` | The quote tokens a launch may use — native USDC and the ERC-20s the quote registry allowlists, such as EURC — each with its symbol, name, decimals, address, whether it is native, its launch fee in its own units (zero on both networks: launching is free, and the figure is read from the registry rather than assumed), whether the registry accepts it now, and **this server's spend cap for it**. At most three `eth_call`s. On a chain with no quote registry, it says so, shows the error, and lists the network's own quote-token metadata with nothing known to be accepted. |
|
|
101
213
|
| `arcnow_list_tokens` | Recent launches, newest first, from the launchpad's `Launched` log, back to the block it was deployed in. Reports the block window it actually covered — see [the SDK gap](#what-made-a-clean-surface-awkward). |
|
|
102
|
-
| `arcnow_token` | One token in full: metadata, **its quote token** (address, symbol, decimals, native or ERC-20), its curve's parameters (`r0Wad`, `y0Wad`), curve state, price, progress, and **where it trades now** — its curve, its v4 pool (with the router, whether it reaches the pool, the PoolManager, the pool's LP fee
|
|
103
|
-
| `arcnow_quote_buy` | What an amount of the token's quote (`quoteIn`) would buy, wherever the token trades. On a curve: tokens out, the 1% fee
|
|
214
|
+
| `arcnow_token` | One token in full: metadata, **its quote token** (address, symbol, decimals, native or ERC-20), its curve's parameters (`r0Wad`, `y0Wad`), curve state, price, progress, and **where it trades now** — its curve, its v4 pool (with the router, whether it reaches the pool, the PoolManager, what a trade there costs — the hook's 0.80% and the pool's 0.20% LP fee, read off the pool — how the hook splits its part, and what it holds accrued), or nowhere. Takes a token *or* a curve address. |
|
|
215
|
+
| `arcnow_quote_buy` | What an amount of the token's quote (`quoteIn`) would buy, wherever the token trades. On a curve: tokens out, the 1% fee split four ways — creator, platform, referrer, protocol — with exactly who receives each share, the average fill price, minimum-out floors at four tolerances. In a pool: a quote that says it is a pool quote, with the fee hook's 0.80% and the pool's own 0.20% LP fee apart (1.00% in all, both read off the pool), the hook's three-way split, the average fill against the pool's spot price, and the price impact. |
|
|
104
216
|
| `arcnow_quote_sell` | The same for a sell — plus, on a curve, that selling needs no approval, ever, and in a pool, that it does, and how much the holder has approved already. |
|
|
105
|
-
| `arcnow_quote_launch` | What a launch in a given `quote` (native USDC by default, or an ERC-20 by symbol or address) would cost before anything is spent
|
|
106
|
-
| `arcnow_platform` | A platform's fee split, its default migrator and its curve template — every share printed both as bps of the fee and as a percentage of a trade. |
|
|
217
|
+
| `arcnow_quote_launch` | What a launch in a given `quote` (native USDC by default, or an ERC-20 by symbol or address) would cost before anything is spent: the launch fee the registry reports (zero — launching is free), the initial buy and its own trade fee, with the predicted token and curve addresses. |
|
|
218
|
+
| `arcnow_platform` | A platform's four-way fee split, its default migrator and its curve template — every share printed both as bps of the fee and as a percentage of a trade. |
|
|
107
219
|
| `arcnow_list_platforms` | The registry's actual enumeration. Complete, unlike the token list. |
|
|
108
220
|
|
|
109
221
|
The read tools are the valuable part. An assistant that can answer *what is this
|
|
@@ -118,13 +230,16 @@ mistake that costs money:
|
|
|
118
230
|
reports an *average fill price* next to the spot price, and the tool
|
|
119
231
|
descriptions tell the model to quote rather than multiply.
|
|
120
232
|
- **A share of the fee is not a share of the trade.** 3000 bps of the fee is
|
|
121
|
-
0.30% of a trade. Both are printed, every time.
|
|
233
|
+
0.30% of a trade. Both are printed, every time. The fee has **four parties** —
|
|
234
|
+
creator, platform, referrer, protocol; arcnow.io's own split is 3000 / 3500 /
|
|
235
|
+
1000 / 2500 — and no developer share: no tool takes a `developer`.
|
|
122
236
|
- **Graduated and migrated are different states.** A curve can have stopped
|
|
123
237
|
trading permanently while its pool was never created, and then the token
|
|
124
238
|
trades *nowhere*. That is reported as its own thing, with the rescue named.
|
|
125
239
|
- **A pool quote is not a curve quote.** It says so, and it names two charges,
|
|
126
|
-
not one:
|
|
127
|
-
Uniswap LP fee
|
|
240
|
+
not one: the fee hook's **0.80%**, taken in the pool's quote and split creator /
|
|
241
|
+
platform / protocol, and the pool's own **0.20%** Uniswap LP fee — 1.00% in all,
|
|
242
|
+
the same as the curve charged. Both rates are read off the pool, never assumed.
|
|
128
243
|
|
|
129
244
|
### Write
|
|
130
245
|
|
|
@@ -163,8 +278,8 @@ set a boolean — but does make the assertion explicit in the transcript.
|
|
|
163
278
|
Zero means "fill me at any price", which on a public mempool is a donation,
|
|
164
279
|
so it has to be typed rather than omitted.
|
|
165
280
|
5. **Nothing that does not apply.** A curve trade pays the sender and has no
|
|
166
|
-
`recipient`; a pool swap has no `referrer`
|
|
167
|
-
|
|
281
|
+
`recipient`; a pool swap has no `referrer` and cannot graduate anything, so
|
|
282
|
+
`gasLimit` guards nothing there. Each is **refused** on the
|
|
168
283
|
venue it does not exist on, before anything is quoted or sent — never
|
|
169
284
|
silently dropped. The SDK refuses the same things; this server says so first.
|
|
170
285
|
6. **The report.** Each tool states the exact cost and the exact effect, and
|
|
@@ -202,15 +317,21 @@ four tools work across that line. Three states, told apart out loud:
|
|
|
202
317
|
| state | quote | buy / sell |
|
|
203
318
|
| --- | --- | --- |
|
|
204
319
|
| on its curve | a curve quote, as before | through the curve; `recipient` refused |
|
|
205
|
-
| graduated **and** migrated | a pool quote, labelled as one | through arcnow.io's router; `gasLimit`, `referrer
|
|
320
|
+
| graduated **and** migrated | a pool quote, labelled as one | through arcnow.io's router; `gasLimit`, `referrer` refused |
|
|
206
321
|
| graduated, **not** migrated | refused: tradeable nowhere until `migrate()` | refused, naming `arcnow_migrate` |
|
|
207
322
|
|
|
208
323
|
**What a pool quote contains.** The SDK prices a pool trade by simulating the
|
|
209
324
|
real swap through the router, so the figures are the real fill with two charges
|
|
210
|
-
inside them. The quote takes them back out and names each:
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
325
|
+
inside them. The quote takes them back out and names each: the fee hook's 0.80%,
|
|
326
|
+
which it takes in the pool's quote and splits creator 5000 / platform 1875 /
|
|
327
|
+
protocol 3125 bps (a pool swap has no referrer), and the pool's own 0.20% LP fee
|
|
328
|
+
— the pool key's `fee`, in hundredths of a bip — which Uniswap charges and the
|
|
329
|
+
pool's liquidity keeps. 1.00% in all, the same as the curve. Every rate comes
|
|
330
|
+
from the SDK's `Pool.fees()`, which reads the hook's `feeBps()` and
|
|
331
|
+
`feeConfigOf()` and the key; this server holds no fee constant, so a pool whose
|
|
332
|
+
key carries another LP fee is reported at the fee it carries. It shows the
|
|
333
|
+
average fill price, the pool's spot price, and the price impact between them.
|
|
334
|
+
The SDK has no
|
|
214
335
|
reader for a pool's price, so the spot price is the SDK's own quote of a
|
|
215
336
|
probe buy with both fees taken out — 0.000001 of an 18-decimal quote, or 10,000 raw
|
|
216
337
|
units of a smaller one (0.01 EURC), so the fees' raw-unit rounding stays below one
|
|
@@ -253,32 +374,35 @@ decoded contract error or revert *failed on-chain*. `RpcFailure` *could not
|
|
|
253
374
|
reach the chain*: the public endpoint rate-limits, and that says nothing about
|
|
254
375
|
the token.
|
|
255
376
|
|
|
256
|
-
### One curve
|
|
377
|
+
### One curve, on two networks
|
|
257
378
|
|
|
258
379
|
arcnow.io has one bonding curve — the constant-product curve,
|
|
259
|
-
`arcnow/bonding-curve@
|
|
380
|
+
`arcnow/bonding-curve@4.x.x`, priced in a quote token, with parameters `r0Wad`
|
|
260
381
|
(the virtual quote reserve at launch, in WAD) and `y0Wad` (the virtual token
|
|
261
382
|
reserve at launch) — launched through one contract stack, and a fee hook,
|
|
262
|
-
`arcnow/arc-now-fee-hook@
|
|
263
|
-
|
|
264
|
-
|
|
383
|
+
`arcnow/arc-now-fee-hook@4.x.x`, that takes its 0.80% in the pool, accrues each
|
|
384
|
+
fee as a PoolManager claim and pays it out at the start of a later swap. The same
|
|
385
|
+
build is live on Arc mainnet (`arc-mainnet`) and Arc testnet (`arc-testnet`), at
|
|
386
|
+
different addresses; a token address from one means nothing on the other, and
|
|
387
|
+
the server's instructions say which network a session is on.
|
|
265
388
|
|
|
266
389
|
- **Nothing a tool prints names a curve kind or a stack.** There is one of
|
|
267
390
|
each. `arcnow_token` prints the curve's own parameters, and a pool trade
|
|
268
391
|
reports the fees it paid out from earlier trades (`FeesDistributed`) apart
|
|
269
392
|
from the trader's fill.
|
|
270
393
|
- **Any other version is refused by name, and never priced.** A curve or a
|
|
271
|
-
platform of another version — the
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
394
|
+
platform of another version — the retired multi-quote stack, `@3.x.x`, which
|
|
395
|
+
carried a developer share and whose data was wiped; the `@2.x.x` contracts
|
|
396
|
+
before it; the retired linear curve, `@1.x.x` — is the SDK's
|
|
397
|
+
`UnknownCurveVersion`, naming the version, before any quote or send. A fee hook
|
|
398
|
+
of another version — the `@3.x.x` hook that charged 1% in the pool included —
|
|
399
|
+
is `UnknownHookVersion`, and neither its accrual nor its rates are read.
|
|
276
400
|
- **An address that is not a curve is refused by name.** The SDK's
|
|
277
401
|
`AddressIsNotACurve` for an arcnow.io token's address is followed to its curve;
|
|
278
402
|
for anything else — an ordinary ERC-20, an account, nothing at all — it is the
|
|
279
403
|
refusal, saying the address is not an arcnow.io token either.
|
|
280
404
|
- **`arcnow_list_tokens` reads the one launchpad**, back to the block its stack
|
|
281
|
-
was deployed in (
|
|
405
|
+
was deployed in (21,179,866 on Arc mainnet, 62,386,232 on Arc testnet).
|
|
282
406
|
|
|
283
407
|
---
|
|
284
408
|
|
|
@@ -290,7 +414,7 @@ where an operator's decisions belong. See
|
|
|
290
414
|
|
|
291
415
|
| variable | default | what it does |
|
|
292
416
|
| --- | --- | --- |
|
|
293
|
-
| `ARCNOW_MCP_NETWORK` | `arc-testnet` | The network preset
|
|
417
|
+
| `ARCNOW_MCP_NETWORK` | `arc-testnet` | The network preset: `arc-mainnet` (Arc mainnet, chain 5042, where arcnow.io is live — **real money**) or `arc-testnet` (the rehearsal, chain 5042002). Both come from the SDK's `networks.json`; a name it does not know refuses to start, naming the two it does. The default is the rehearsal on purpose. |
|
|
294
418
|
| `ARCNOW_MCP_NETWORK_FILE` | unset | A path to a JSON document in the SDK's `CustomNetwork` shape, for a deployment no preset names — a local anvil stack, or a stack deployed onto a fork. **Mutually exclusive with a preset name.** See [below](#a-network-the-sdk-has-no-preset-for). |
|
|
295
419
|
| `ARCNOW_RPC_URL` | the preset's endpoint | Override the endpoint. Redacted of credentials before it is ever printed. |
|
|
296
420
|
| `ARCNOW_MCP_ALLOW_WRITES` | unset | `1` enables the write tools. `--allow-writes` does the same. |
|
|
@@ -313,7 +437,9 @@ in EURC. So every quote token has its own cap, in its own units:
|
|
|
313
437
|
- `ARCNOW_MCP_MAX_SPEND_USDC` caps native USDC, and defaults to `100`.
|
|
314
438
|
- `ARCNOW_MCP_MAX_SPEND_<SYMBOL>` caps each other quote — `ARCNOW_MCP_MAX_SPEND_EURC=50`
|
|
315
439
|
is 50 EURC. The symbol is upper-cased; a character that is not a letter or a
|
|
316
|
-
digit is written `_`.
|
|
440
|
+
digit is written `_`. The cap is matched to the network's own token by
|
|
441
|
+
address: on `arc-mainnet` that is EURC at `0xbEf5f6d5…`, on `arc-testnet` at
|
|
442
|
+
`0x89b50855…`, each read from the preset.
|
|
317
443
|
|
|
318
444
|
It fails closed, four ways:
|
|
319
445
|
|
|
@@ -330,8 +456,8 @@ It fails closed, four ways:
|
|
|
330
456
|
- **Two quote tokens of the network sharing a symbol refuses to start** it: one
|
|
331
457
|
variable cannot mean two caps.
|
|
332
458
|
|
|
333
|
-
What counts: a launch spends its `totalCost` — the
|
|
334
|
-
|
|
459
|
+
What counts: a launch spends its `totalCost` — the initial buy plus any launch
|
|
460
|
+
fee (zero on both networks), in the launch's quote; a buy spends its `quoteIn`. For an ERC-20 quote the
|
|
335
461
|
SDK approves exactly that spend, so the same check covers the approve. The
|
|
336
462
|
caller's own `maxTotalCost` is read in the same quote. The startup banner,
|
|
337
463
|
`arcnow_network` and `arcnow_quote_tokens` show every cap, and
|
|
@@ -341,7 +467,7 @@ caller's own `maxTotalCost` is read in the same quote. The startup banner,
|
|
|
341
467
|
### A network the SDK has no preset for
|
|
342
468
|
|
|
343
469
|
`ARCNOW_MCP_NETWORK_FILE=/path/to/network.json` points the server at a
|
|
344
|
-
deployment by its addresses — a local anvil stack, or the
|
|
470
|
+
deployment by its addresses — a local anvil stack, or the stack this
|
|
345
471
|
repository's fork proof deploys onto a fork of Arc testnet:
|
|
346
472
|
|
|
347
473
|
```json
|
|
@@ -393,7 +519,7 @@ token. Gas is always native USDC, whatever a token's quote.
|
|
|
393
519
|
`@arcnow/sdk` is published to npm from
|
|
394
520
|
[arcnow-io/arcnow-io-sdk](https://github.com/arcnow-io/arcnow-io-sdk), one tagged
|
|
395
521
|
release per version, and this server depends on it at an **exact version** —
|
|
396
|
-
`"@arcnow/sdk": "0.
|
|
522
|
+
`"@arcnow/sdk": "0.2.0"`, never `^0.2.0`. The tool descriptions promise what one
|
|
397
523
|
known SDK does; a range would let `npm install` move the code under them with no
|
|
398
524
|
commit here saying so. Moving the pin is a pull request.
|
|
399
525
|
|
|
@@ -402,10 +528,10 @@ commit here saying so. Moving the pin is a pull request.
|
|
|
402
528
|
```json
|
|
403
529
|
"sdk": {
|
|
404
530
|
"package": "@arcnow/sdk",
|
|
405
|
-
"version": "0.
|
|
531
|
+
"version": "0.2.0",
|
|
406
532
|
"integrity": "sha512-…",
|
|
407
533
|
"public_repo": "arcnow-io/arcnow-io-sdk",
|
|
408
|
-
"tag": "v0.
|
|
534
|
+
"tag": "v0.2.0",
|
|
409
535
|
"why": ["what this server uses from that SDK, in prose"]
|
|
410
536
|
}
|
|
411
537
|
```
|
|
@@ -417,214 +543,56 @@ surface**: every module, the generated ABIs that encode every call, the
|
|
|
417
543
|
published this file hashed its sources file by file and the gate recompiled its
|
|
418
544
|
`dist/`; the integrity replaces all of that.)
|
|
419
545
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
1. `package.json` depends on the SDK at **exactly** `sdk.version`. A range, a
|
|
424
|
-
`file:` path or another number fails, by name.
|
|
425
|
-
2. `package-lock.json`'s entry for it carries that version, resolves to the npm
|
|
426
|
-
registry, and its `integrity` **is** `sdk.integrity` — the link between the
|
|
427
|
-
version and the bytes; npm refuses to install a tarball that does not hash to
|
|
428
|
-
it.
|
|
429
|
-
3. `node_modules/@arcnow/sdk/package.json` is that version — what the server
|
|
430
|
-
actually compiles against and imports.
|
|
431
|
-
4. The registry has that version and serves it with that `dist.integrity`, so
|
|
432
|
-
the pinned bytes are the published bytes. This needs the network: unreachable
|
|
433
|
-
is a **warning** (a laptop on a train is not a broken pin) unless
|
|
434
|
-
`ARCNOW_REQUIRE_REGISTRY=1`, which CI sets; a 404 is always a failure.
|
|
435
|
-
|
|
436
|
-
Together: the manifest asks for one version, the lockfile binds it to one
|
|
437
|
-
tarball, `node_modules` holds it, and the registry says those are the bytes it
|
|
438
|
-
serves under that number. `test/unit/check-pins.test.ts` builds real roots with
|
|
439
|
-
a fake `npm` on `PATH` and breaks each link on purpose — a range, a path, a
|
|
440
|
-
foreign tarball, a stale install, a version the registry never had, a registry
|
|
441
|
-
that cannot be reached.
|
|
442
|
-
|
|
443
|
-
Moving the pin is a commit of its own: `npm install @arcnow/sdk@X.Y.Z
|
|
444
|
-
--save-exact`, `npm ci`, `scripts/check-pins.sh --record` — which records the
|
|
445
|
-
version, the lockfile's integrity and the tag, and refuses a range or a version
|
|
446
|
-
the registry does not have — update `sdk.why`, run preflight, and say what
|
|
447
|
-
changed in the SDK surface and what it meant for the tools. A tool description
|
|
448
|
-
that still promises what an older SDK did is a model quoting a wrong price.
|
|
546
|
+
Before every release the maintainers verify that the manifest, the lockfile, the
|
|
547
|
+
installed package and the registry all name that one tarball; moving the pin is a
|
|
548
|
+
pull request that reads the SDK's changelog and re-reads every tool description.
|
|
449
549
|
|
|
450
550
|
---
|
|
451
551
|
|
|
452
|
-
##
|
|
552
|
+
## Testing
|
|
453
553
|
|
|
454
554
|
```bash
|
|
455
|
-
|
|
456
|
-
./scripts/preflight.sh --no-install
|
|
457
|
-
./scripts/preflight.sh --no-chain # everything but the fork proof
|
|
458
|
-
./scripts/check-pins.sh # just the pin
|
|
459
|
-
npm test # just the unit suite — no chain, no container, no key
|
|
460
|
-
npm run test:fork # just the fork proof — needs Docker
|
|
555
|
+
npm test # the unit suite: no chain, no container, no key
|
|
461
556
|
```
|
|
462
557
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
workflow is a transcription of it, kept because a clean-checkout run is the one
|
|
466
|
-
thing a local run cannot prove.
|
|
467
|
-
|
|
468
|
-
Everything but the fork proof needs nothing beyond `npm ci`: the SDK comes from
|
|
469
|
-
npm. The fork proof is the maintainers' **private infrastructure**: it starts one
|
|
470
|
-
container, an anvil fork of Arc testnet from the Foundry image `pins.json` pins,
|
|
471
|
-
labelled `io.arcnow.mcp.test`, and deploys arcnow.io's 3.x multi-quote contracts
|
|
472
|
-
onto it with the private `arcnow-io/sdk` checkout's `scripts/fork-deploy-stack.sh`
|
|
473
|
-
(`ARCNOW_SDK_DIR`, default `../sdk`). Arc testnet now runs a 3.x stack of its own,
|
|
474
|
-
deployed on 2026-09-15, but the fork is taken at a pinned, already-cached block
|
|
475
|
-
from before that, where the live contracts are the 2.x ones the SDK refuses — and
|
|
476
|
-
a proof that deploys its own stack does not depend on what happens to be live
|
|
477
|
-
anyway. That also needs `ARCNOW_CONTRACTS_DIR`, a checkout
|
|
478
|
-
of arcnow-io/contracts at the commit that SDK checkout's `pins.json` names, and
|
|
479
|
-
forge at the release it names. Its harness —
|
|
480
|
-
copied from `arcnow-io/sdk`'s, not imported, because that repository's tests are
|
|
481
|
-
not part of the pinned surface — removes it on every exit path, sweeps only its
|
|
482
|
-
own label, and touches nothing else on a shared daemon.
|
|
483
|
-
|
|
484
|
-
### What the tests prove
|
|
558
|
+
It runs against a fake chain that does exactly what each test says, and proves
|
|
559
|
+
what can be proved offline:
|
|
485
560
|
|
|
486
561
|
- **A key is never an argument.** Every published schema is walked for
|
|
487
|
-
credential-shaped field names and
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
- **
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
server.
|
|
522
|
-
- **Against the 3.x contracts deployed onto a fork of Arc testnet**
|
|
523
|
-
(`test/fork/mcp.fork.test.ts`), the built server — started with
|
|
524
|
-
`ARCNOW_MCP_NETWORK_FILE` naming the deployed stack — driven by a real MCP
|
|
525
|
-
client lists the quote tokens from the deployed registry in at most three
|
|
526
|
-
`eth_call`s with each one's cap, launches a native USDC
|
|
527
|
-
token on the launchpad, reads it
|
|
528
|
-
back with its quote token, `r0Wad` and `y0Wad`, buys it on its curve,
|
|
529
|
-
watches an impersonated Arc account graduate it, then quotes, buys and sells it
|
|
530
|
-
in its Uniswap v4 pool through the live router — and sells a second token back
|
|
531
|
-
to its curve — and launches a token in EURC, with its exact approve and exactly
|
|
532
|
-
the total pulled, buys it in EURC, and has a buy above the 50 EURC cap refused
|
|
533
|
-
with nothing sent. Every figure is checked against the chain with viem, not through
|
|
534
|
-
the server: every quote equals its fill, to the wei; exactly the amount and
|
|
535
|
-
the gas left the signer; the sell's approval is one `Approval` log to the
|
|
536
|
-
router for exactly the amount sold, used up by the sale; the recipient is paid
|
|
537
|
-
exactly what the report says; a swap's fee payout matches the hook's
|
|
538
|
-
`FeesDistributed` log; the refusals leave the signer's nonce where it was; and
|
|
539
|
-
the USDC ERC-20 predeploy comes back as `AddressIsNotACurve`. The server's RPC
|
|
540
|
-
goes through a counting proxy, and the per-call request counts are printed.
|
|
541
|
-
- **One curve, and nothing else priced.** Against the fake, whose version
|
|
542
|
-
checks are the SDK's own `assertCurveVersion` and `assertPlatformVersion`: no
|
|
543
|
-
report names a curve kind or a stack, an `@1` curve, platform or fee hook is
|
|
544
|
-
refused by name with nothing quoted or sent, and the launch scan reads the one
|
|
545
|
-
launchpad from its deployment block.
|
|
546
|
-
|
|
547
|
-
### What they do not prove
|
|
548
|
-
|
|
549
|
-
- **Not Arc's own execution semantics.** A fork re-executes locally with
|
|
550
|
-
anvil's EVM and disagrees with Arc about blocklisted transfers, EIP-1153, the
|
|
551
|
-
EIP-7708 system emitter and burn-to-zero without saying so.
|
|
552
|
-
- **Not a EURC pool against a chain.** The fork proof launches a EURC token,
|
|
553
|
-
buys it on its curve with the exact approve, and holds the 50 EURC cap; a
|
|
554
|
-
EURC token's pool is unit-tested against the fake.
|
|
555
|
-
- **Not every path against a chain.** The fork proof covers a launch, curve
|
|
556
|
-
trades, graduation and pool trades. A
|
|
557
|
-
migration through `arcnow_migrate`, a graduating buy through `arcnow_buy` (the
|
|
558
|
-
proof graduates its token through the SDK, so that nothing credits the
|
|
559
|
-
server's signer), a token that graduated but never migrated — there is none on
|
|
560
|
-
Arc testnet to fork — and a curve, platform or hook whose version the SDK
|
|
561
|
-
refuses are
|
|
562
|
-
unit-tested here against the fake, and proved against a chain, where they are
|
|
563
|
-
at all, by the SDK's own fork suite. If you change how a tool calls the SDK,
|
|
564
|
-
run the SDK's preflight too.
|
|
565
|
-
- **Not that the tool descriptions are true.** Those are prose, read by a model
|
|
566
|
-
deciding whether to spend somebody's money. When the pin moves, that is a
|
|
567
|
-
person's job.
|
|
568
|
-
- **Not that a model behaves.** Every guard in this server is a bound on
|
|
569
|
-
damage, not a guarantee of judgement.
|
|
570
|
-
|
|
571
|
-
---
|
|
572
|
-
|
|
573
|
-
## What made a clean surface awkward
|
|
574
|
-
|
|
575
|
-
Five things, all worth fixing upstream rather than here — and one of them now is.
|
|
576
|
-
|
|
577
|
-
**The SDK cannot enumerate tokens.** There is no `recentLaunches()` anywhere in
|
|
578
|
-
`@arcnow/sdk`, and its `networks.json` explains why a preset cannot carry one: a
|
|
579
|
-
token and its curve come from the `Launched` log, one pair per launch, and there
|
|
580
|
-
are as many as there have been launches. But "show me the recent tokens" is the
|
|
581
|
-
first thing anybody asks an assistant, so `arcnow_list_tokens` reads that log
|
|
582
|
-
directly — with the SDK's own pinned ABI, through the SDK's own configured
|
|
583
|
-
client, at the SDK's own launchpad address, adding nothing but the scan. It is
|
|
584
|
-
the only place in this repository that talks to the chain outside an SDK method,
|
|
585
|
-
and it is in `src/sdk-port.ts` with a comment saying so. A
|
|
586
|
-
`launchpad.recentLaunches()` belongs in the SDK, where the forked-chain suite
|
|
587
|
-
could test it against a real chain; here it is tested against a fake.
|
|
588
|
-
|
|
589
|
-
Because the scan is a bounded walk backwards from the tip rather than an index,
|
|
590
|
-
`arcnow_list_tokens` reports the block window it covered and says plainly when
|
|
591
|
-
it stopped on its budget with history unread. An assistant must not conclude a
|
|
592
|
-
token does not exist from a tool that only ever saw a window.
|
|
593
|
-
|
|
594
|
-
**The SDK's handles are classes with private fields.** `Curve`, `Token`,
|
|
595
|
-
`Launchpad` and `PlatformRegistry` are nominally typed, so nothing can be
|
|
596
|
-
assigned to them — a test cannot construct a stand-in, and there is no seam to
|
|
597
|
-
substitute one. That is why `src/sdk-port.ts` exists: a set of interfaces the
|
|
598
|
-
SDK's handles already satisfy, wrapping nothing and computing nothing, so that a
|
|
599
|
-
fake can drive every tool through the states that matter and are hardest to
|
|
600
|
-
reach on a real chain (a buy that graduates, a curve that graduated and never
|
|
601
|
-
migrated, a cost that moved between the quote and the order). If the SDK exposed
|
|
602
|
-
interfaces alongside its classes, that file would be a re-export.
|
|
603
|
-
|
|
604
|
-
**The SDK has no reader for a pool's price.** A pool quote is the real fill, but
|
|
605
|
-
"how far is that from the pool's price" needs the pool's price, and there is no
|
|
606
|
-
`slot0`/`sqrtPriceX96` read anywhere in `@arcnow/sdk`. Reading the PoolManager's
|
|
607
|
-
storage here would be the second, unpinned copy of chain code this server exists
|
|
608
|
-
not to have. So the spot price is the SDK's own quote of a tiny probe buy with
|
|
609
|
-
both fees taken out — accurate to far below the printed digits, one extra
|
|
610
|
-
`eth_call`, and labelled as what it is. A `pool.spotPrice()` belongs in the SDK.
|
|
611
|
-
The pool's LP fee is likewise not in a quote; it is read from `pool.key()`.
|
|
612
|
-
|
|
613
|
-
**Fixed upstream: a revert inside the pool.** A revert from inside the fee hook,
|
|
614
|
-
or a failed transfer out of the PoolManager, arrives wrapped in Uniswap v4's
|
|
615
|
-
`WrappedError`. It used to surface as a bare `UnknownRevert`. Since sdk#7 the SDK
|
|
616
|
-
unwraps it and names the failed transfer (`NativeTransferFailed` /
|
|
617
|
-
`ERC20TransferFailed`), so this server only shows what the SDK decoded.
|
|
618
|
-
|
|
619
|
-
**A pool sell quote needs a real holder.** The SDK overrides the router
|
|
620
|
-
allowance for a sell simulation but deliberately not the balance, so
|
|
621
|
-
`arcnow_quote_sell` on a pool needs `holder` on a read-only server. That is the
|
|
622
|
-
right call, and it is why that argument exists.
|
|
623
|
-
|
|
624
|
-
Nothing else got in the way. In particular, the SDK's four amount types, its
|
|
625
|
-
refusal to default a slippage floor, its explicit `gasLimit`, its
|
|
626
|
-
`migratedInThisTransaction` flag and its by-selector error decoding are each the
|
|
627
|
-
reason a corresponding class of mistake is not reachable from here.
|
|
562
|
+
credential-shaped field names and asserted strict; passing `privateKey` or
|
|
563
|
+
`mnemonic` anyway is refused, named, answered with "rotate it", and sends
|
|
564
|
+
nothing.
|
|
565
|
+
- **A write refuses without the opt-in**, names `--allow-writes` and
|
|
566
|
+
`ARCNOW_PRIVATE_KEY`, and has sent nothing.
|
|
567
|
+
- **Both spend ceilings stop a transaction**, on a curve and in a pool, per
|
|
568
|
+
quote token, on either preset — mainnet's EURC is capped by its mainnet
|
|
569
|
+
address; a typo'd cap variable refuses to start; a USDC cap does not apply
|
|
570
|
+
to EURC or the reverse.
|
|
571
|
+
- **Every amount is labelled with its own quote**, an input with more decimals
|
|
572
|
+
than its quote is refused, and an ERC-20 approve is reported whether it was
|
|
573
|
+
sent or not.
|
|
574
|
+
- **The gas trap is handled**, the venues are told apart (a stranded token is
|
|
575
|
+
refused everywhere with `arcnow_migrate` named), the sell approval is never
|
|
576
|
+
sent without `approveRouter: true` and never for more than the amount sold,
|
|
577
|
+
and the reports say true things: a curve fee broken out four ways, a pool's
|
|
578
|
+
0.80% and 0.20% read off the pool and never assumed, a free launch printed
|
|
579
|
+
from the registry's zero, average fill price apart from spot, graduated apart
|
|
580
|
+
from migrated.
|
|
581
|
+
- **Both presets resolve** to the SDK's live deployments, `arc-mainnet` says it
|
|
582
|
+
is real money, and nothing of one network's addresses appears in a report
|
|
583
|
+
about the other.
|
|
584
|
+
- **The wire works**: a real MCP client against a real MCP server over an
|
|
585
|
+
in-memory transport.
|
|
586
|
+
|
|
587
|
+
What the unit suite cannot prove — that the built server, driven by a real MCP
|
|
588
|
+
client over stdio, lists the quote tokens, launches, buys and sells a token on its
|
|
589
|
+
curve and in its Uniswap v4 pool, and launches and buys a token priced in EURC
|
|
590
|
+
under the EURC cap, against arcnow.io's real contracts, every quote equal to its
|
|
591
|
+
fill to the wei — the maintainers prove before every release, on an anvil fork of
|
|
592
|
+
Arc testnet with those contracts deployed onto it. A fork re-executes with anvil's
|
|
593
|
+
EVM, so Arc's own execution semantics are outside even that; and no test can
|
|
594
|
+
prove that a tool description is true or that a model behaves — every guard here
|
|
595
|
+
is a bound on damage, not a guarantee of judgement.
|
|
628
596
|
|
|
629
597
|
---
|
|
630
598
|
|
package/dist/config.d.ts
CHANGED
|
@@ -57,7 +57,15 @@
|
|
|
57
57
|
* the network sharing a symbol is a startup error, because their two caps would
|
|
58
58
|
* be one variable.
|
|
59
59
|
*
|
|
60
|
-
* #
|
|
60
|
+
* # Two presets, and a network the SDK has no preset for
|
|
61
|
+
*
|
|
62
|
+
* `ARCNOW_MCP_NETWORK` is `arc-mainnet` — Arc mainnet, chain 5042, where
|
|
63
|
+
* arcnow.io is live and every write spends real money — or `arc-testnet`, the
|
|
64
|
+
* rehearsal network at chain 5042002, which runs the same contracts at other
|
|
65
|
+
* addresses. Both resolve through the SDK's `resolveNetwork`, which is the only
|
|
66
|
+
* source of an address in this server. The default is `arc-testnet`: a server
|
|
67
|
+
* started with no network named should not be pointed at real money by
|
|
68
|
+
* omission.
|
|
61
69
|
*
|
|
62
70
|
* `ARCNOW_MCP_NETWORK_FILE` names a JSON document in the SDK's `CustomNetwork`
|
|
63
71
|
* shape — `rpcUrl`, `chainId`, `contracts`, and optionally `quoteTokens` and
|
|
@@ -176,9 +184,10 @@ export declare const DEFAULTS: {
|
|
|
176
184
|
readonly network: "arc-testnet";
|
|
177
185
|
/**
|
|
178
186
|
* 100 USDC a call. Low enough that a prompt-injected "buy everything" is a
|
|
179
|
-
* bounded accident and not a bounded-by-your-balance one; high enough that
|
|
180
|
-
*
|
|
181
|
-
* operator who means to spend more says so once, at startup, on
|
|
187
|
+
* bounded accident and not a bounded-by-your-balance one; high enough that a
|
|
188
|
+
* real first buy fits under it (launching itself is free on arcnow.io's
|
|
189
|
+
* networks). An operator who means to spend more says so once, at startup, on
|
|
190
|
+
* purpose — and on arc-mainnet every one of these is real money.
|
|
182
191
|
*
|
|
183
192
|
* Native USDC only. Every other quote has NO default: spending it is refused
|
|
184
193
|
* until the operator names a cap for it.
|