@quackai/q402-mcp 0.4.1 → 0.4.3
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 -192
- package/dist/index.js +118 -33
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,192 +1,209 @@
|
|
|
1
|
-
# @quackai/q402-mcp
|
|
2
|
-
|
|
3
|
-
> MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 8 EVM chains, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.
|
|
4
|
-
|
|
5
|
-
[](https://www.npmjs.com/package/@quackai/q402-mcp)
|
|
6
|
-
[](./LICENSE)
|
|
7
|
-
|
|
8
|
-
> **🎟️ Free trial available (2026-05-19 → 2026-06-30)** — 2,000 gasless transactions on BNB Chain (USDC + USDT), 30-day window, no card. One wallet signature: <https://q402.quackai.ai>.
|
|
9
|
-
>
|
|
10
|
-
> **Trial-scope policy:** API keys minted under the free-trial program (`plan: "trial"`) are restricted to BNB Chain with USDC/USDT — server-side enforcement, returns `403 TRIAL_BNB_ONLY` otherwise. **Paid API keys see the full 8-chain matrix at all times.**
|
|
11
|
-
|
|
12
|
-
Claude can now reason about stablecoin payments end to end — quote a transfer across 8 chains, pick the cheapest route, and (optionally) settle the transaction over [Q402](https://q402.quackai.ai)'s EIP-7702 relayer infrastructure. The recipient receives the full amount; the sender pays $0 in gas.
|
|
13
|
-
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
## Quick start
|
|
17
|
-
|
|
18
|
-
The server speaks stdio MCP, so any MCP-compatible client can use it. The two paths verified end-to-end today are **Claude (Desktop / Code)** and **OpenAI Codex CLI**.
|
|
19
|
-
|
|
20
|
-
### Claude Desktop / Claude Code
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
claude mcp add q402 -- npx -y @quackai/q402-mcp
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
Or edit `claude_desktop_config.json` directly:
|
|
27
|
-
|
|
28
|
-
```json
|
|
29
|
-
{
|
|
30
|
-
"mcpServers": {
|
|
31
|
-
"q402": {
|
|
32
|
-
"command": "npx",
|
|
33
|
-
"args": ["-y", "@quackai/q402-mcp"]
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Restart Claude Desktop and ask:
|
|
40
|
-
|
|
41
|
-
> *"Compare gas costs to send 50 USDC to vitalik.eth across all 8 Q402 chains."*
|
|
42
|
-
|
|
43
|
-
### OpenAI Codex CLI
|
|
44
|
-
|
|
45
|
-
Three install paths — pick the one that matches your workflow.
|
|
46
|
-
|
|
47
|
-
**(a) Codex plugin marketplace** (recommended — bundles the MCP config so users don't write TOML):
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
codex plugin marketplace add bitgett/q402-mcp
|
|
51
|
-
codex /plugins # browse and install "q402"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
This repo carries a Codex plugin manifest at [`.codex-plugin/plugin.json`](./.codex-plugin/plugin.json) and a marketplace catalog at [`.agents/plugins/marketplace.json`](./.agents/plugins/marketplace.json), so any signed-in Codex user can register it as a marketplace source and install with one click.
|
|
55
|
-
|
|
56
|
-
**(b) Single MCP server via `codex mcp add`** (no plugin wrapper — just register the stdio server):
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
codex mcp add q402 -- npx -y @quackai/q402-mcp
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**(c) Direct `~/.codex/config.toml` edit** (`.codex/config.toml` for per-project scope):
|
|
63
|
-
|
|
64
|
-
```toml
|
|
65
|
-
[mcp_servers.q402]
|
|
66
|
-
command = "npx"
|
|
67
|
-
args = ["-y", "@quackai/q402-mcp"]
|
|
68
|
-
startup_timeout_sec = 20.0
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
To enable real on-chain payments, pass the three live-mode env vars **explicitly** under `env` — Codex does **not** forward host env vars by default:
|
|
72
|
-
|
|
73
|
-
```toml
|
|
74
|
-
[mcp_servers.q402]
|
|
75
|
-
command = "npx"
|
|
76
|
-
args = ["-y", "@quackai/q402-mcp"]
|
|
77
|
-
startup_timeout_sec = 20.0
|
|
78
|
-
env = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Q402_ENABLE_REAL_PAYMENTS=1
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
|
154
|
-
|
|
155
|
-
| `Q402_MAX_AMOUNT_PER_CALL` |
|
|
156
|
-
| `Q402_ALLOWED_RECIPIENTS` |
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
|
167
|
-
|
|
|
168
|
-
|
|
|
169
|
-
|
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
1
|
+
# @quackai/q402-mcp
|
|
2
|
+
|
|
3
|
+
> MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 8 EVM chains, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@quackai/q402-mcp)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
> **🎟️ Free trial available (2026-05-19 → 2026-06-30)** — 2,000 gasless transactions on BNB Chain (USDC + USDT), 30-day window, no card. One wallet signature: <https://q402.quackai.ai>.
|
|
9
|
+
>
|
|
10
|
+
> **Trial-scope policy:** API keys minted under the free-trial program (`plan: "trial"`) are restricted to BNB Chain with USDC/USDT — server-side enforcement, returns `403 TRIAL_BNB_ONLY` otherwise. **Paid API keys see the full 8-chain matrix at all times.**
|
|
11
|
+
|
|
12
|
+
Claude can now reason about stablecoin payments end to end — quote a transfer across 8 chains, pick the cheapest route, and (optionally) settle the transaction over [Q402](https://q402.quackai.ai)'s EIP-7702 relayer infrastructure. The recipient receives the full amount; the sender pays $0 in gas.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
The server speaks stdio MCP, so any MCP-compatible client can use it. The two paths verified end-to-end today are **Claude (Desktop / Code)** and **OpenAI Codex CLI**.
|
|
19
|
+
|
|
20
|
+
### Claude Desktop / Claude Code
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
claude mcp add q402 -- npx -y @quackai/q402-mcp
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or edit `claude_desktop_config.json` directly:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"q402": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "@quackai/q402-mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Restart Claude Desktop and ask:
|
|
40
|
+
|
|
41
|
+
> *"Compare gas costs to send 50 USDC to vitalik.eth across all 8 Q402 chains."*
|
|
42
|
+
|
|
43
|
+
### OpenAI Codex CLI
|
|
44
|
+
|
|
45
|
+
Three install paths — pick the one that matches your workflow.
|
|
46
|
+
|
|
47
|
+
**(a) Codex plugin marketplace** (recommended — bundles the MCP config so users don't write TOML):
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
codex plugin marketplace add bitgett/q402-mcp
|
|
51
|
+
codex /plugins # browse and install "q402"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This repo carries a Codex plugin manifest at [`.codex-plugin/plugin.json`](./.codex-plugin/plugin.json) and a marketplace catalog at [`.agents/plugins/marketplace.json`](./.agents/plugins/marketplace.json), so any signed-in Codex user can register it as a marketplace source and install with one click.
|
|
55
|
+
|
|
56
|
+
**(b) Single MCP server via `codex mcp add`** (no plugin wrapper — just register the stdio server):
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
codex mcp add q402 -- npx -y @quackai/q402-mcp
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**(c) Direct `~/.codex/config.toml` edit** (`.codex/config.toml` for per-project scope):
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[mcp_servers.q402]
|
|
66
|
+
command = "npx"
|
|
67
|
+
args = ["-y", "@quackai/q402-mcp"]
|
|
68
|
+
startup_timeout_sec = 20.0
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
To enable real on-chain payments, pass the three live-mode env vars **explicitly** under `env` — Codex does **not** forward host env vars by default:
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[mcp_servers.q402]
|
|
75
|
+
command = "npx"
|
|
76
|
+
args = ["-y", "@quackai/q402-mcp"]
|
|
77
|
+
startup_timeout_sec = 20.0
|
|
78
|
+
env = {
|
|
79
|
+
# Two-key model (v0.4.3+): set whichever applies — both is best.
|
|
80
|
+
# The server auto-routes by chain: BNB → trial key, else multichain key.
|
|
81
|
+
Q402_TRIAL_API_KEY = "q402_live_trial_...", # BNB-only sponsored
|
|
82
|
+
Q402_MULTICHAIN_API_KEY = "q402_live_...", # paid 8-chain
|
|
83
|
+
# Legacy fallback — used if neither scoped key above is set.
|
|
84
|
+
Q402_API_KEY = "q402_live_...",
|
|
85
|
+
Q402_PRIVATE_KEY = "0xabc...",
|
|
86
|
+
Q402_ENABLE_REAL_PAYMENTS = "1",
|
|
87
|
+
Q402_MAX_AMOUNT_PER_CALL = "5",
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> If you'd rather not inline secrets in `config.toml`, use the `env_vars` allow-list form to forward specific names from your shell environment instead — see the [Codex config reference](https://developers.openai.com/codex/config-reference) for the full schema.
|
|
92
|
+
|
|
93
|
+
Then run `codex` and ask the same kind of question. The first call may take a few seconds while `npx` warms its cache; subsequent calls are instant.
|
|
94
|
+
|
|
95
|
+
### Any other MCP client
|
|
96
|
+
|
|
97
|
+
The server has no client-specific code. If your client speaks stdio MCP, point it at `npx -y @quackai/q402-mcp` and the five tools listed below will appear.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
> `Q402_RELAY_BASE_URL` overrides the relay endpoint. Set it explicitly when running against a self-hosted Q402 deployment or a non-canonical environment.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Tools exposed
|
|
106
|
+
|
|
107
|
+
| Tool | Auth | Purpose |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `q402_quote` | none | Compare gas cost and supported tokens across chains. Read-only. |
|
|
110
|
+
| `q402_balance` | API key | Verify the API key and report its plan tier + remaining quota credits (live vs sandbox). |
|
|
111
|
+
| `q402_pay` | API key + private key + flag | Send a gasless payment to a single recipient. **Sandbox by default** — see [Sandbox vs live mode](#sandbox-vs-live-mode). |
|
|
112
|
+
| `q402_batch_pay` | API key + private key + flag | Send a gasless payment to **multiple** recipients in one call on a single chain × token. Trial keys: 5 rows max. Paid keys: 20 rows max. **Supported chains: avax, bnb, eth, mantle, injective, monad** (default EIP-7702 mode). xlayer + stable are NOT batchable — use `q402_pay` in a loop for those. Same sandbox gating as `q402_pay`. **Rate-limit note:** the inner `/api/relay` budget (30/min per key) is consumed per row, so a paid 20-row batch leaves ~10 inner slots for the next minute. |
|
|
113
|
+
| `q402_receipt` | none | Look up a Trust Receipt by `rct_…` id and locally verify its ECDSA signature against the relayer EOA. Returns the public settlement record + a `verified` boolean. *receiptId-only today; tx-hash lookup reserved for a future release.* |
|
|
114
|
+
|
|
115
|
+
`q402_pay` and `q402_batch_pay` follow a "confirm in chat first" contract: the tool description instructs the model to never call it without explicit user approval of the recipient address(es), amount(s), chain, and token. For batch calls the user must approve the **full batch**, not the individual rows.
|
|
116
|
+
|
|
117
|
+
`q402_receipt` is the natural follow-up: after `q402_pay` returns a `receiptUrl`, hand the agent the `rct_…` id and ask *"verify this receipt"* — the tool re-runs the same canonical-JSON + EIP-191 recovery the receipt page does in the browser, so the verification doesn't depend on trusting any UI. Example prompts that work today:
|
|
118
|
+
|
|
119
|
+
> *"Pay 0.10 USDT on BNB to vitalik.eth, then verify the receipt."*
|
|
120
|
+
> *"Is `rct_afa5f50bc49a65ebba3b28ab` a real Q402 receipt? Verify the signature."*
|
|
121
|
+
|
|
122
|
+
> Per-chain gas tank balances and full transaction history live in the [dashboard](https://q402.quackai.ai/dashboard) — those endpoints require a wallet signature, not a bare API key, so the MCP server points the agent there instead of exposing them.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Sandbox vs live mode
|
|
127
|
+
|
|
128
|
+
By default the MCP server operates in **sandbox mode**: `q402_pay` returns a deterministic-looking fake transaction hash, no funds move, no gas-tank credit is consumed. That makes it safe to plug into any MCP client without worrying about an LLM hallucinating a payment.
|
|
129
|
+
|
|
130
|
+
To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set, and `Q402_ENABLE_REAL_PAYMENTS=1`:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Two-key model (v0.4.3+) — set whichever applies. Both is best.
|
|
134
|
+
# Auto-routing: chain="bnb" → trial key (if set), otherwise multichain key.
|
|
135
|
+
# Override per call with keyScope: "auto" | "trial" | "multichain".
|
|
136
|
+
Q402_TRIAL_API_KEY=q402_live_trial_... # BNB-only sponsored Trial key
|
|
137
|
+
Q402_MULTICHAIN_API_KEY=q402_live_... # paid 8-chain key (per-chain Gas Tank)
|
|
138
|
+
|
|
139
|
+
# Legacy fallback. Used for both scopes when the two above are unset —
|
|
140
|
+
# pre-v0.4.3 users keep working without any config change.
|
|
141
|
+
Q402_API_KEY=q402_live_...
|
|
142
|
+
|
|
143
|
+
Q402_PRIVATE_KEY=0xabc... # signer for the payer EOA
|
|
144
|
+
Q402_ENABLE_REAL_PAYMENTS=1 # explicit opt-in
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Anything missing for the resolved scope → automatic sandbox fallback with a hint pointing at what to set.
|
|
148
|
+
|
|
149
|
+
### Hard caps
|
|
150
|
+
|
|
151
|
+
Two additional guards run before every payment regardless of mode:
|
|
152
|
+
|
|
153
|
+
| Env var | Default | Effect |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `Q402_MAX_AMOUNT_PER_CALL` | `5` | Reject any single call where `amount > N` USD-equivalent. |
|
|
156
|
+
| `Q402_ALLOWED_RECIPIENTS` | (empty = off) | Comma-separated address allowlist. When set, all other recipients are rejected. |
|
|
157
|
+
|
|
158
|
+
Combined with the `confirm: true` argument the tool requires, this means the model needs (a) explicit user OK in chat, (b) amount ≤ cap, (c) recipient on allowlist if one exists, (d) all three live-mode env vars set, before a single wei moves.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Configuration reference
|
|
163
|
+
|
|
164
|
+
| Env var | Required for | Notes |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Used automatically for `chain="bnb"` when set. |
|
|
167
|
+
| `Q402_MULTICHAIN_API_KEY` | live-pay (8-chain) | Paid 8-chain key. Get one at https://q402.quackai.ai/payment. Used for all non-BNB chains and for BNB when no Trial key is set. |
|
|
168
|
+
| `Q402_API_KEY` | legacy fallback | Pre-v0.4.3 single-env path. Used for both scopes when the two above are unset. Keep set if you only have one key. |
|
|
169
|
+
| `Q402_PRIVATE_KEY` | live-pay | Signer for the payer EOA. **Never share. Never paste in chat.** |
|
|
170
|
+
| `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
|
|
171
|
+
| `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `5`. |
|
|
172
|
+
| `Q402_ALLOWED_RECIPIENTS` | optional | Comma-separated lowercase addresses. Defaults to no allowlist. |
|
|
173
|
+
| `Q402_RELAY_BASE_URL` | optional | Defaults to `https://q402.quackai.ai/api`. Override for self-hosted Q402. |
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Supported chains
|
|
178
|
+
|
|
179
|
+
| Chain | Chain ID | Token(s) | Notes |
|
|
180
|
+
|---|---|---|---|
|
|
181
|
+
| BNB Chain | 56 | USDC, USDT | |
|
|
182
|
+
| Ethereum | 1 | USDC, USDT, **RLUSD** | L1 — gas is volatile, quote is a snapshot. RLUSD (Ripple USD, NY DFS regulated, decimals 18) Ethereum-only. |
|
|
183
|
+
| Avalanche C-Chain | 43114 | USDC, USDT | |
|
|
184
|
+
| X Layer | 196 | USDC, USDT | |
|
|
185
|
+
| Stable | 988 | USDT0 (USDC and USDT both alias) | Gas paid in USDT0. |
|
|
186
|
+
| Mantle | 5000 | USDC, USDT0 | LayerZero OFT USDT0 since 2025-11-27. |
|
|
187
|
+
| Injective EVM | 1776 | USDT only | Native USDC via Circle CCTP announced for Q2 2026. |
|
|
188
|
+
| Monad | 143 | USDC, USDT0 | Native Circle USDC (CCTP V2) + USDT0 (LayerZero OFT). |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## Why this exists
|
|
193
|
+
|
|
194
|
+
x402 standardised "402 Payment Required" semantics for AI agents but the official Coinbase facilitator only covers a few chains and assumes ERC-3009 token support — which excludes BNB USDT, Mantle USDT0, Injective USDT, and the chains where most stablecoin volume actually lives.
|
|
195
|
+
|
|
196
|
+
Q402 implements the same payer experience (single signature, $0 gas, instant settlement) on all 8 of those chains using EIP-7702 delegated execution, which works with any ERC-20. This MCP server makes that infrastructure addressable from Claude itself.
|
|
197
|
+
|
|
198
|
+
If you want to dig into how the wire protocol differs from x402, see [Q402 docs](https://q402.quackai.ai/docs).
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Repository
|
|
203
|
+
|
|
204
|
+
Source code: https://github.com/bitgett/q402-mcp
|
|
205
|
+
Issues / requests: https://github.com/bitgett/q402-mcp/issues
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
Apache-2.0 — see [LICENSE](./LICENSE).
|
package/dist/index.js
CHANGED
|
@@ -29,12 +29,18 @@ function parseMaxAmount(raw) {
|
|
|
29
29
|
return n;
|
|
30
30
|
}
|
|
31
31
|
function loadConfig() {
|
|
32
|
-
const
|
|
32
|
+
const trialApiKey = process.env.Q402_TRIAL_API_KEY ?? null;
|
|
33
|
+
const multichainApiKey = process.env.Q402_MULTICHAIN_API_KEY ?? null;
|
|
34
|
+
const legacyApiKey = process.env.Q402_API_KEY ?? null;
|
|
35
|
+
const apiKey = multichainApiKey ?? trialApiKey ?? legacyApiKey;
|
|
33
36
|
const apiKeyKind = classifyApiKey(apiKey);
|
|
34
37
|
const privateKey = process.env.Q402_PRIVATE_KEY ?? null;
|
|
35
38
|
const realPaymentsRequested = process.env.Q402_ENABLE_REAL_PAYMENTS === "1";
|
|
36
39
|
const live = realPaymentsRequested && apiKeyKind === "live" && typeof privateKey === "string" && privateKey.length > 0;
|
|
37
40
|
return {
|
|
41
|
+
trialApiKey,
|
|
42
|
+
multichainApiKey,
|
|
43
|
+
legacyApiKey,
|
|
38
44
|
apiKey,
|
|
39
45
|
apiKeyKind,
|
|
40
46
|
privateKey,
|
|
@@ -46,6 +52,35 @@ function loadConfig() {
|
|
|
46
52
|
};
|
|
47
53
|
}
|
|
48
54
|
var CONFIG = loadConfig();
|
|
55
|
+
function resolveApiKey(chain, scope = "auto") {
|
|
56
|
+
const effectiveScope = scope === "auto" ? chain === "bnb" && CONFIG.trialApiKey ? "trial" : "multichain" : scope;
|
|
57
|
+
if (effectiveScope === "trial") {
|
|
58
|
+
if (chain !== "bnb") {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Trial API Key supports BNB Chain only \u2014 got "${chain}". Use a Multichain API Key (set Q402_MULTICHAIN_API_KEY) for ${chain} and other paid chains, or omit keyScope to let the server auto-pick.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
const key2 = CONFIG.trialApiKey ?? CONFIG.legacyApiKey;
|
|
64
|
+
if (!key2) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"keyScope='trial' was requested but neither Q402_TRIAL_API_KEY nor Q402_API_KEY is set. Get a Trial key at https://q402.quackai.ai/event."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
return { apiKey: key2, scope: "trial", fromLegacyFallback: !CONFIG.trialApiKey };
|
|
70
|
+
}
|
|
71
|
+
const key = CONFIG.multichainApiKey ?? CONFIG.legacyApiKey;
|
|
72
|
+
if (!key) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
"keyScope='multichain' was requested but neither Q402_MULTICHAIN_API_KEY nor Q402_API_KEY is set. Activate a paid plan at https://q402.quackai.ai/payment to get one."
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
return { apiKey: key, scope: "multichain", fromLegacyFallback: !CONFIG.multichainApiKey };
|
|
78
|
+
}
|
|
79
|
+
function isLiveModeFor(resolved) {
|
|
80
|
+
if (!CONFIG.realPaymentsRequested) return false;
|
|
81
|
+
if (!CONFIG.privateKey) return false;
|
|
82
|
+
return resolved.apiKey.startsWith("q402_live_");
|
|
83
|
+
}
|
|
49
84
|
|
|
50
85
|
// src/tools/quote.ts
|
|
51
86
|
import { z } from "zod";
|
|
@@ -628,6 +663,9 @@ var PayInputSchema = z2.object({
|
|
|
628
663
|
token: z2.enum(["USDC", "USDT", "RLUSD"]).describe(
|
|
629
664
|
"Stablecoin symbol. USDC / USDT supported on most chains (Injective is USDT-only). RLUSD (Ripple USD, NY DFS regulated, decimals 18) is Ethereum-only."
|
|
630
665
|
),
|
|
666
|
+
keyScope: z2.enum(["auto", "trial", "multichain"]).optional().describe(
|
|
667
|
+
'Which API key to use. "auto" (default) picks the Trial key for BNB when Q402_TRIAL_API_KEY is set, and the Multichain key otherwise. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 8-chain key.'
|
|
668
|
+
),
|
|
631
669
|
confirm: z2.literal(true).describe(
|
|
632
670
|
"MUST be true. Prove the user explicitly approved this exact recipient and amount in the conversation right before this tool was called. Setting this to true on behalf of the user without confirmation is a violation of the tool contract."
|
|
633
671
|
)
|
|
@@ -666,18 +704,22 @@ async function runPay(input) {
|
|
|
666
704
|
if (CONFIG.allowedRecipients.length > 0) {
|
|
667
705
|
guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
|
|
668
706
|
}
|
|
669
|
-
|
|
707
|
+
const scopeRequest = input.keyScope ?? "auto";
|
|
708
|
+
const resolved = resolveApiKey(input.chain, scopeRequest);
|
|
709
|
+
guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
|
|
710
|
+
const live = isLiveModeFor(resolved);
|
|
711
|
+
if (!live) {
|
|
670
712
|
const result2 = sandboxPay(chain, {
|
|
671
713
|
to: input.to,
|
|
672
714
|
amount: input.amount,
|
|
673
715
|
token: input.token
|
|
674
716
|
});
|
|
675
717
|
guardsApplied.push("mode=sandbox");
|
|
676
|
-
const setupHint = describeSandboxReason();
|
|
718
|
+
const setupHint = describeSandboxReason(resolved.apiKey);
|
|
677
719
|
return { result: result2, guardsApplied, setupHint };
|
|
678
720
|
}
|
|
679
721
|
const client = new Q402NodeClient({
|
|
680
|
-
apiKey:
|
|
722
|
+
apiKey: resolved.apiKey,
|
|
681
723
|
privateKey: CONFIG.privateKey,
|
|
682
724
|
chain,
|
|
683
725
|
relayBaseUrl: CONFIG.relayBaseUrl
|
|
@@ -690,9 +732,9 @@ async function runPay(input) {
|
|
|
690
732
|
guardsApplied.push("mode=live");
|
|
691
733
|
return { result, guardsApplied };
|
|
692
734
|
}
|
|
693
|
-
function describeSandboxReason() {
|
|
735
|
+
function describeSandboxReason(resolvedKey) {
|
|
694
736
|
const missing = [];
|
|
695
|
-
if (
|
|
737
|
+
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
696
738
|
if (!CONFIG.privateKey) missing.push("Q402_PRIVATE_KEY");
|
|
697
739
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
698
740
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
@@ -700,7 +742,7 @@ function describeSandboxReason() {
|
|
|
700
742
|
}
|
|
701
743
|
var PAY_TOOL = {
|
|
702
744
|
name: "q402_pay",
|
|
703
|
-
description:
|
|
745
|
+
description: "Send a gasless USDC, USDT, or RLUSD payment via Q402. Uses Q402_TRIAL_API_KEY (BNB-only sponsored Trial) when chain='bnb' and the Trial env is set, and Q402_MULTICHAIN_API_KEY (paid 8-chain) otherwise. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set, and Q402_ENABLE_REAL_PAYMENTS=1. The recipient receives the full amount; the sender pays $0 in gas. ALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
|
|
704
746
|
inputSchema: {
|
|
705
747
|
type: "object",
|
|
706
748
|
properties: {
|
|
@@ -722,6 +764,11 @@ var PAY_TOOL = {
|
|
|
722
764
|
enum: ["USDC", "USDT", "RLUSD"],
|
|
723
765
|
description: "Stablecoin to send. USDC / USDT supported on most chains; Injective is USDT-only. RLUSD (Ripple USD, NY DFS regulated, decimals 18) is Ethereum-only."
|
|
724
766
|
},
|
|
767
|
+
keyScope: {
|
|
768
|
+
type: "string",
|
|
769
|
+
enum: ["auto", "trial", "multichain"],
|
|
770
|
+
description: 'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise. "trial" forces the BNB-only sponsored key. "multichain" forces the paid 8-chain key.'
|
|
771
|
+
},
|
|
725
772
|
confirm: {
|
|
726
773
|
type: "boolean",
|
|
727
774
|
const: true,
|
|
@@ -752,6 +799,9 @@ var BatchPayInputSchema = z3.object({
|
|
|
752
799
|
).min(1, "recipients must contain at least one row").max(CLIENT_RECIPIENT_CAP, `recipients cannot exceed ${CLIENT_RECIPIENT_CAP} (server enforces tighter cap by key scope)`).describe(
|
|
753
800
|
`Array of {to, amount} pairs. All recipients share the same chain and token. Trial keys: max ${RECIPIENT_LIMIT_TRIAL} rows. Paid keys: max ${RECIPIENT_LIMIT_PAID} rows.`
|
|
754
801
|
),
|
|
802
|
+
keyScope: z3.enum(["auto", "trial", "multichain"]).optional().describe(
|
|
803
|
+
'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise. Trial forces the BNB-only sponsored key; "multichain" forces the paid 8-chain key.'
|
|
804
|
+
),
|
|
755
805
|
confirm: z3.literal(true).describe(
|
|
756
806
|
"MUST be true. The user must have explicitly approved this exact set of recipients, amounts, chain, and token in the conversation right before this tool was called. Setting confirm=true on behalf of the user without that approval is a violation of the tool contract."
|
|
757
807
|
)
|
|
@@ -796,21 +846,26 @@ async function runBatchPay(input) {
|
|
|
796
846
|
if (CONFIG.allowedRecipients.length > 0) {
|
|
797
847
|
guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
|
|
798
848
|
}
|
|
799
|
-
|
|
849
|
+
const scopeRequest = input.keyScope ?? "auto";
|
|
850
|
+
const resolved = resolveApiKey(input.chain, scopeRequest);
|
|
851
|
+
guardsApplied.push(`scope=${resolved.scope}${resolved.fromLegacyFallback ? "(legacy)" : ""}`);
|
|
852
|
+
const live = isLiveModeFor(resolved);
|
|
853
|
+
if (!live) {
|
|
800
854
|
const sandboxResults = input.recipients.map(
|
|
801
855
|
(r) => sandboxPay(chain, { to: r.to, amount: r.amount, token: input.token })
|
|
802
856
|
);
|
|
803
857
|
guardsApplied.push("mode=sandbox");
|
|
858
|
+
const reason = describeSandboxReason2(resolved.apiKey);
|
|
804
859
|
return {
|
|
805
860
|
mode: "sandbox",
|
|
806
861
|
status: "sandbox",
|
|
807
|
-
result: { sandbox: sandboxResults, reason
|
|
862
|
+
result: { sandbox: sandboxResults, reason },
|
|
808
863
|
guardsApplied,
|
|
809
|
-
setupHint:
|
|
864
|
+
setupHint: reason
|
|
810
865
|
};
|
|
811
866
|
}
|
|
812
867
|
const client = new Q402NodeClient({
|
|
813
|
-
apiKey:
|
|
868
|
+
apiKey: resolved.apiKey,
|
|
814
869
|
privateKey: CONFIG.privateKey,
|
|
815
870
|
chain,
|
|
816
871
|
relayBaseUrl: CONFIG.relayBaseUrl
|
|
@@ -849,9 +904,9 @@ async function runBatchPay(input) {
|
|
|
849
904
|
throw err;
|
|
850
905
|
}
|
|
851
906
|
}
|
|
852
|
-
function describeSandboxReason2() {
|
|
907
|
+
function describeSandboxReason2(resolvedKey) {
|
|
853
908
|
const missing = [];
|
|
854
|
-
if (
|
|
909
|
+
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
855
910
|
if (!CONFIG.privateKey) missing.push("Q402_PRIVATE_KEY");
|
|
856
911
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
857
912
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
@@ -859,7 +914,7 @@ function describeSandboxReason2() {
|
|
|
859
914
|
}
|
|
860
915
|
var BATCH_PAY_TOOL = {
|
|
861
916
|
name: "q402_batch_pay",
|
|
862
|
-
description: `Send gasless payments to MULTIPLE recipients on a single chain \xD7 token in one call. Trial
|
|
917
|
+
description: `Send gasless payments to MULTIPLE recipients on a single chain \xD7 token in one call. Uses Q402_TRIAL_API_KEY for chain='bnb' when the Trial env is set, Q402_MULTICHAIN_API_KEY otherwise. Set keyScope='trial' or 'multichain' to force one. Trial keys: max ${RECIPIENT_LIMIT_TRIAL} recipients per call, BNB Chain + USDC/USDT only. Multichain keys: max ${RECIPIENT_LIMIT_PAID} recipients per call across 6 EIP-7702 default chains (avax, bnb, eth, mantle, injective, monad). xlayer + stable are NOT batchable \u2014 use q402_pay in a loop. SANDBOX BY DEFAULT \u2014 real on-chain TX only when the resolved key is live (q402_live_*), Q402_PRIVATE_KEY is set, and Q402_ENABLE_REAL_PAYMENTS=1. Every recipient receives the full amount; the sender pays $0 in gas for the entire batch. ALWAYS get explicit user confirmation of the complete recipient + amount list, chain, and token in conversation immediately before calling this tool \u2014 the user must approve the full batch, not the individual rows.`,
|
|
863
918
|
inputSchema: {
|
|
864
919
|
type: "object",
|
|
865
920
|
properties: {
|
|
@@ -897,6 +952,11 @@ var BATCH_PAY_TOOL = {
|
|
|
897
952
|
additionalProperties: false
|
|
898
953
|
}
|
|
899
954
|
},
|
|
955
|
+
keyScope: {
|
|
956
|
+
type: "string",
|
|
957
|
+
enum: ["auto", "trial", "multichain"],
|
|
958
|
+
description: 'Which API key to use. "auto" (default) picks Trial for BNB when Q402_TRIAL_API_KEY is set, Multichain otherwise.'
|
|
959
|
+
},
|
|
900
960
|
confirm: {
|
|
901
961
|
type: "boolean",
|
|
902
962
|
const: true,
|
|
@@ -915,39 +975,64 @@ function mask(key) {
|
|
|
915
975
|
if (!key || key.length < 12) return null;
|
|
916
976
|
return `${key.slice(0, 12)}\u2026${key.slice(-4)}`;
|
|
917
977
|
}
|
|
918
|
-
async function
|
|
919
|
-
if (CONFIG.apiKeyKind === "missing") {
|
|
920
|
-
return {
|
|
921
|
-
apiKeyKind: "missing",
|
|
922
|
-
apiKeyMasked: null,
|
|
923
|
-
dashboardUrl: "https://q402.quackai.ai/dashboard",
|
|
924
|
-
setupHint: "Set Q402_API_KEY to a key issued at https://q402.quackai.ai/dashboard. Test-tier keys (q402_test_*) work too \u2014 they show sandbox quota."
|
|
925
|
-
};
|
|
926
|
-
}
|
|
978
|
+
async function verifyOne(apiKey) {
|
|
927
979
|
const resp = await fetch(`${CONFIG.relayBaseUrl}/keys/verify`, {
|
|
928
980
|
method: "POST",
|
|
929
981
|
headers: { "Content-Type": "application/json" },
|
|
930
|
-
body: JSON.stringify({ apiKey
|
|
982
|
+
body: JSON.stringify({ apiKey })
|
|
931
983
|
});
|
|
932
|
-
|
|
984
|
+
return resp.ok ? await resp.json() : { error: `HTTP ${resp.status}` };
|
|
985
|
+
}
|
|
986
|
+
function extractTrial(verifyJson) {
|
|
933
987
|
const v = verifyJson;
|
|
934
|
-
|
|
988
|
+
if (!v || !v.isTrial || typeof v.trialExpiresAt !== "string") return void 0;
|
|
989
|
+
return {
|
|
935
990
|
daysLeft: typeof v.trialDaysLeft === "number" ? v.trialDaysLeft : 0,
|
|
936
991
|
expiresAt: v.trialExpiresAt,
|
|
937
992
|
creditsRemaining: typeof v.remainingCredits === "number" ? v.remainingCredits : 0,
|
|
938
993
|
signupUrl: "https://q402.quackai.ai"
|
|
939
|
-
}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
async function runBalance() {
|
|
997
|
+
const targets = [];
|
|
998
|
+
if (CONFIG.trialApiKey) targets.push({ scope: "trial", key: CONFIG.trialApiKey });
|
|
999
|
+
if (CONFIG.multichainApiKey) targets.push({ scope: "multichain", key: CONFIG.multichainApiKey });
|
|
1000
|
+
if (targets.length === 0 && CONFIG.legacyApiKey) {
|
|
1001
|
+
targets.push({ scope: "legacy", key: CONFIG.legacyApiKey });
|
|
1002
|
+
}
|
|
1003
|
+
if (targets.length === 0) {
|
|
1004
|
+
return {
|
|
1005
|
+
apiKeyKind: "missing",
|
|
1006
|
+
apiKeyMasked: null,
|
|
1007
|
+
scopes: [],
|
|
1008
|
+
dashboardUrl: "https://q402.quackai.ai/dashboard",
|
|
1009
|
+
setupHint: "Set Q402_TRIAL_API_KEY (BNB-only sponsored, free at /event) and/or Q402_MULTICHAIN_API_KEY (paid 8-chain from /dashboard). Single-env legacy: Q402_API_KEY also works."
|
|
1010
|
+
};
|
|
1011
|
+
}
|
|
1012
|
+
const scopes = await Promise.all(
|
|
1013
|
+
targets.map(async ({ scope, key }) => {
|
|
1014
|
+
const verify = await verifyOne(key);
|
|
1015
|
+
return {
|
|
1016
|
+
scope,
|
|
1017
|
+
apiKeyMasked: mask(key) ?? key,
|
|
1018
|
+
verify,
|
|
1019
|
+
trial: extractTrial(verify)
|
|
1020
|
+
};
|
|
1021
|
+
})
|
|
1022
|
+
);
|
|
1023
|
+
const primary = scopes.find((s) => s.scope === "multichain") ?? scopes.find((s) => s.scope === "trial") ?? scopes[0];
|
|
940
1024
|
return {
|
|
941
1025
|
apiKeyKind: CONFIG.apiKeyKind,
|
|
942
|
-
apiKeyMasked:
|
|
943
|
-
verify:
|
|
944
|
-
|
|
945
|
-
|
|
1026
|
+
apiKeyMasked: primary.apiKeyMasked,
|
|
1027
|
+
verify: primary.verify,
|
|
1028
|
+
trial: primary.trial,
|
|
1029
|
+
scopes,
|
|
1030
|
+
dashboardUrl: "https://q402.quackai.ai/dashboard"
|
|
946
1031
|
};
|
|
947
1032
|
}
|
|
948
1033
|
var BALANCE_TOOL = {
|
|
949
1034
|
name: "q402_balance",
|
|
950
|
-
description: "Verify the configured API key and report
|
|
1035
|
+
description: "Verify the configured API key(s) and report each one's plan tier (live vs sandbox vs trial). Read-only. When both Q402_TRIAL_API_KEY and Q402_MULTICHAIN_API_KEY are set, returns BOTH summaries so the agent can show the user trial credits AND paid credits in one view. For trial-scoped keys, returns days-left + credits-remaining for the trial allotment. Free trial available at https://q402.quackai.ai/event \u2014 2,000 gasless TX over 30 days. For per-chain gas tank balances, point the user at https://q402.quackai.ai/dashboard \u2014 those need a wallet signature, not a bare key.",
|
|
951
1036
|
inputSchema: {
|
|
952
1037
|
type: "object",
|
|
953
1038
|
properties: {},
|
|
@@ -1120,7 +1205,7 @@ var RECEIPT_TOOL = {
|
|
|
1120
1205
|
|
|
1121
1206
|
// src/index.ts
|
|
1122
1207
|
var PACKAGE_NAME = "@quackai/q402-mcp";
|
|
1123
|
-
var PACKAGE_VERSION = "0.4.
|
|
1208
|
+
var PACKAGE_VERSION = "0.4.2";
|
|
1124
1209
|
function jsonText(value) {
|
|
1125
1210
|
return { type: "text", text: JSON.stringify(value, null, 2) };
|
|
1126
1211
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 8 EVM chains, callable from Claude (Desktop / Code), OpenAI Codex CLI, and any other Model Context Protocol client.",
|
|
5
5
|
"mcpName": "io.github.bitgett/q402-mcp",
|
|
6
6
|
"keywords": [
|