@quackai/q402-mcp 0.6.1 → 0.6.2
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 +51 -24
- package/dist/index.js +416 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,35 +57,55 @@ The agent calls `q402_doctor`. On first install, the tool tells the agent to:
|
|
|
57
57
|
|
|
58
58
|
1. Offer to create `~/.q402/mcp.env` (placeholder values only)
|
|
59
59
|
2. Open the file in your editor (`code` / `open` / `start` / `xdg-open`)
|
|
60
|
-
3. Walk you through pasting in (a) your API key from <https://q402.quackai.ai/event> (free Trial) or <https://q402.quackai.ai/payment> (paid Multichain), and (b)
|
|
60
|
+
3. Walk you through pasting in (a) your API key from <https://q402.quackai.ai/event> (free Trial) or <https://q402.quackai.ai/payment> (paid Multichain), and (b) a signing path — **into the file, not into chat**
|
|
61
61
|
4. Restart the client and run `q402_doctor` again to verify
|
|
62
62
|
|
|
63
|
-
🔒 **Q402 never asks you to paste your private key into chat.**
|
|
63
|
+
🔒 **Q402 never asks you to paste your private key into chat.** When you use a local signing mode the MCP server signs payments LOCALLY on your machine — your key never leaves your device. Mode C (server-managed Agent Wallet) requires no private key on the client at all.
|
|
64
|
+
|
|
65
|
+
### Pick a signing mode
|
|
66
|
+
|
|
67
|
+
Q402 supports three signing modes — pick ONE:
|
|
68
|
+
|
|
69
|
+
| Mode | Env you set | Signer | Notes |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| **A** | `Q402_PRIVATE_KEY` | your MetaMask EOA, local | Simplest. After the first payment that EOA shows "Smart account" in MetaMask (EIP-7702 delegation, reversible via `q402_clear_delegation`). |
|
|
72
|
+
| **B** | `Q402_AGENTIC_PRIVATE_KEY` | dedicated Agent Wallet, local | Export the PK from the [dashboard](https://q402.quackai.ai/dashboard) Agent tab → Export. Signs locally just like Mode A, but the signer is your Agent Wallet — MetaMask is never touched. Recommended for AI-agent automation. |
|
|
73
|
+
| **C** | (just the paid API key) | dedicated Agent Wallet, server-managed | The encrypted Agent Wallet key lives on the Q402 server and signs on your behalf. No private key on the client. Optionally set `Q402_AGENT_WALLET_ADDRESS` to pick among multiple wallets (max 10 per owner). Mode C requires a paid Multichain API key — not available on the free Trial. |
|
|
74
|
+
|
|
75
|
+
When more than one signing mode is set at once, `q402_pay` asks the user which to use rather than picking silently. The picker lives in the `walletMode` argument: `"agentic-server"` (Mode C), `"agentic-local"` (Mode B), `"eoa"` (Mode A).
|
|
64
76
|
|
|
65
77
|
### Manual setup (no AI)
|
|
66
78
|
|
|
67
|
-
Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes —
|
|
79
|
+
Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes — every secret line ships empty, with `Q402_ENABLE_REAL_PAYMENTS=1`. Fill in (a) at least one API key and (b) the row(s) for the signing mode you picked. The server only flips into live mode once both a `q402_live_*` API key AND a valid signing path are present, so saving the template as-is is safe (empty values fail the gate and stay in sandbox). Change the flag to `0` to force sandbox even with real keys (e.g. for chained testing).
|
|
68
80
|
|
|
69
81
|
```bash
|
|
70
82
|
# ~/.q402/mcp.env
|
|
71
83
|
|
|
72
|
-
#
|
|
73
|
-
Q402_TRIAL_API_KEY=
|
|
84
|
+
# ── API key (pick one or both for auto-routing) ──
|
|
85
|
+
Q402_TRIAL_API_KEY= # Free Trial, BNB only (from /event)
|
|
86
|
+
Q402_MULTICHAIN_API_KEY= # Paid Multichain, all 9 chains (from /payment)
|
|
74
87
|
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# Hex EVM private key (0x + 64 hex). A separate MetaMask account
|
|
79
|
-
# dedicated to Q402 keeps your existing balances and history tidy.
|
|
80
|
-
# Hardware wallets (Ledger / Trezor) are not supported yet — Q402
|
|
88
|
+
# ── Signing path — pick ONE of Mode A / B / C ──
|
|
89
|
+
# Mode A: your MetaMask EOA's hex private key.
|
|
90
|
+
# Hardware wallets (Ledger / Trezor) are NOT supported here — Q402
|
|
81
91
|
# needs a raw hex key it can sign EIP-7702 type-4 authorizations with.
|
|
82
92
|
Q402_PRIVATE_KEY=
|
|
83
93
|
|
|
94
|
+
# Mode B: exported Agent Wallet pk from the dashboard. Keeps your
|
|
95
|
+
# MetaMask untouched. Get it at:
|
|
96
|
+
# https://q402.quackai.ai/dashboard → Agent tab → Export
|
|
97
|
+
Q402_AGENTIC_PRIVATE_KEY=
|
|
98
|
+
|
|
99
|
+
# Mode C: no PK needed. Set ONLY the paid Multichain key above, leave
|
|
100
|
+
# both PK lines blank. Q402 signs with the server-managed Agent Wallet.
|
|
101
|
+
# Optional: pin one of your Agent Wallets when you have multiple (max 10).
|
|
102
|
+
# Q402_AGENT_WALLET_ADDRESS=0x...
|
|
103
|
+
|
|
84
104
|
# Live mode switch:
|
|
85
105
|
# 0 = sandbox (test mode, no funds move)
|
|
86
106
|
# 1 = real on-chain payments
|
|
87
|
-
# Default 1 — safe because mode only flips to live when
|
|
88
|
-
#
|
|
107
|
+
# Default 1 — safe because mode only flips to live when an API key AND
|
|
108
|
+
# at least one valid signing path (A/B/C) are populated above.
|
|
89
109
|
Q402_ENABLE_REAL_PAYMENTS=1
|
|
90
110
|
|
|
91
111
|
# Default Q402 deployment. Only change for self-hosted.
|
|
@@ -114,6 +134,8 @@ env_vars = [
|
|
|
114
134
|
"Q402_TRIAL_API_KEY",
|
|
115
135
|
"Q402_MULTICHAIN_API_KEY",
|
|
116
136
|
"Q402_PRIVATE_KEY",
|
|
137
|
+
"Q402_AGENTIC_PRIVATE_KEY",
|
|
138
|
+
"Q402_AGENT_WALLET_ADDRESS",
|
|
117
139
|
"Q402_ENABLE_REAL_PAYMENTS",
|
|
118
140
|
"Q402_RELAY_BASE_URL",
|
|
119
141
|
]
|
|
@@ -146,6 +168,7 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
|
|
|
146
168
|
| `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. **Auto-routing:** same rule as `q402_pay` (BNB + Trial key set ⇒ Trial, else Multichain). **Ambiguity gate:** 6+ recipient BNB batches with Trial set return `status="ambiguous"` instead of executing — the agent asks the user to pick `keyScope="trial"` (first 5), `"multichain"` (all paid), or two calls (5 free + remainder paid). **Supported chains: avax, bnb, eth, mantle, injective, monad, scroll** (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. |
|
|
147
169
|
| `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.* |
|
|
148
170
|
| `q402_wallet_status` | private key | Per-chain EIP-7702 delegation status for the EOA derived from `Q402_PRIVATE_KEY`, across all 9 Q402 chains. Read-only — no on-chain action, no quota consumption. Useful as the diagnostic step before `q402_clear_delegation`. |
|
|
171
|
+
| `q402_agentic_info` | API key | Introspect your server-managed Agent Wallets — addresses, per-wallet caps, daily-spend used, ERC-8004 agent id, per-chain sub-balances. Drives Mode C (server-managed) so the agent can see what wallet it's about to pay from without ever holding the private key. Auth is API-key only; no PK needed. Read-only. |
|
|
149
172
|
| `q402_clear_delegation` | private key | Clear the EIP-7702 delegation on a single chain for the configured wallet. Local signing with `Q402_PRIVATE_KEY`; Q402 sponsors the on-chain TX so the user pays $0 gas. After clearing, the next `q402_pay` on that chain recreates a fresh delegation automatically. |
|
|
150
173
|
|
|
151
174
|
`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.
|
|
@@ -165,30 +188,32 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
|
|
|
165
188
|
|
|
166
189
|
By default the MCP server operates in **sandbox mode**: `q402_pay` returns a random fake transaction hash with `success: false` and `sandbox: true`, no funds move, no gas-tank credit is consumed. That makes it safe to plug into any MCP client without worrying about an accidental payment — if the agent misreads the conversation and fires `q402_pay` before you intended, nothing moves AND the response cannot be mistaken for a confirmed settlement.
|
|
167
190
|
|
|
168
|
-
To enable real on-chain transactions
|
|
191
|
+
To enable real on-chain transactions you need (a) a live API key (`q402_live_*`), (b) at least one valid signing path — Mode A `Q402_PRIVATE_KEY`, Mode B `Q402_AGENTIC_PRIVATE_KEY`, or Mode C (paid Multichain key alone, server-managed Agent Wallet, no PK on the client) — and (c) `Q402_ENABLE_REAL_PAYMENTS=1`. The block below is the template `q402_doctor` writes to `~/.q402/mcp.env`; every secret line ships empty, the live flag defaults to `1`, and the gate only flips when an API key + a valid signing path are populated. Change the flag to `0` to force sandbox even with real keys (e.g. for chained testing on a paid plan):
|
|
169
192
|
|
|
170
193
|
```bash
|
|
171
|
-
#
|
|
194
|
+
# ── API key — fill ONE (or both for auto-routing) ──
|
|
172
195
|
# Auto-routing (same for q402_pay AND q402_batch_pay):
|
|
173
196
|
# chain="bnb" + Q402_TRIAL_API_KEY set → Trial (free sponsored)
|
|
174
197
|
# anything else → Multichain (paid 9-chain)
|
|
175
198
|
# Batch ambiguity: 6+ recipient BNB batch with Trial set returns
|
|
176
199
|
# status="ambiguous" instead of executing — agent asks user to pick.
|
|
177
200
|
# Override per call with keyScope: "auto" | "trial" | "multichain".
|
|
178
|
-
|
|
179
201
|
Q402_TRIAL_API_KEY= # BNB-only sponsored Trial key (from /event)
|
|
180
202
|
Q402_MULTICHAIN_API_KEY= # paid 9-chain key (per-chain Gas Tank)
|
|
181
203
|
|
|
182
|
-
|
|
204
|
+
# ── Signing path — pick ONE of Mode A / B / C ──
|
|
205
|
+
Q402_PRIVATE_KEY= # Mode A: real EOA pk (0x + 64 hex)
|
|
206
|
+
Q402_AGENTIC_PRIVATE_KEY= # Mode B: exported Agent Wallet pk (from dashboard)
|
|
207
|
+
# Mode C: leave both PK lines blank, set only the paid Multichain key
|
|
208
|
+
# above. Q402 signs with the server-managed Agent Wallet. Optionally:
|
|
209
|
+
# Q402_AGENT_WALLET_ADDRESS=0x... # pin one of your wallets when you have multiple
|
|
183
210
|
|
|
184
211
|
# Live mode switch:
|
|
185
212
|
# 0 = sandbox (test mode, no funds move — every q402_pay returns a fake hash)
|
|
186
213
|
# 1 = real on-chain payments (live mode)
|
|
187
|
-
# Default 1
|
|
188
|
-
#
|
|
189
|
-
#
|
|
190
|
-
# stay in sandbox with a hint. Change to 0 to force sandbox even with
|
|
191
|
-
# real keys (e.g. for chained testing on a paid plan).
|
|
214
|
+
# Default 1. Safe because the gate only flips to live when an API key AND
|
|
215
|
+
# at least one valid signing path (A/B/C) are populated. Empty values
|
|
216
|
+
# fail the gate, so partial setups stay in sandbox with a hint.
|
|
192
217
|
Q402_ENABLE_REAL_PAYMENTS=1
|
|
193
218
|
```
|
|
194
219
|
|
|
@@ -214,8 +239,10 @@ Combined with the `confirm: true` argument the tool requires, this means the mod
|
|
|
214
239
|
| Env var | Required for | Notes |
|
|
215
240
|
|---|---|---|
|
|
216
241
|
| `Q402_TRIAL_API_KEY` | live-pay (BNB) | BNB-only sponsored Trial key. Free at https://q402.quackai.ai/event. Auto-routed for `chain="bnb"` in both `q402_pay` and `q402_batch_pay` (≤5 recipients) when set. 6+ recipient BNB batches return `status="ambiguous"` so the agent can ask the user how to split. |
|
|
217
|
-
| `Q402_MULTICHAIN_API_KEY` | live-pay (9-chain) | Paid 9-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. |
|
|
218
|
-
| `Q402_PRIVATE_KEY` |
|
|
242
|
+
| `Q402_MULTICHAIN_API_KEY` | live-pay (9-chain) | Paid 9-chain key. Get one at https://q402.quackai.ai/payment. Auto-routed for non-BNB chains AND for BNB when no Trial key is set. Cap: 20 recipients per batch. Required for Mode C (server-managed Agent Wallet). |
|
|
243
|
+
| `Q402_PRIVATE_KEY` | Mode A | Hex private key of your MetaMask EOA. Signer for local Mode A. **Never share. Never paste in chat.** |
|
|
244
|
+
| `Q402_AGENTIC_PRIVATE_KEY` | Mode B | Exported Agent Wallet hex private key from the dashboard (Agent tab → Export). Signs locally, but the signer is your dedicated Agent Wallet — MetaMask is never touched. **Never share. Never paste in chat.** |
|
|
245
|
+
| `Q402_AGENT_WALLET_ADDRESS` | Mode C (optional) | When you have multiple server-managed Agent Wallets (max 10 per owner), set this to the lowercased 0x… address of the one Q402 should spend from. Omit to use the default wallet. Ignored in Modes A/B. |
|
|
219
246
|
| `Q402_ENABLE_REAL_PAYMENTS` | live-pay | Set to `1` to opt in. Any other value (or unset) → sandbox. |
|
|
220
247
|
| `Q402_MAX_AMOUNT_PER_CALL` | optional | USD-equivalent cap. Defaults to `200`. Lower for tighter agent blast-radius. |
|
|
221
248
|
| `Q402_ALLOWED_RECIPIENTS` | optional | Comma-separated lowercase addresses. Defaults to no allowlist. |
|
package/dist/index.js
CHANGED
|
@@ -211,7 +211,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
|
|
|
211
211
|
|
|
212
212
|
// src/version.ts
|
|
213
213
|
var PACKAGE_NAME = "@quackai/q402-mcp";
|
|
214
|
-
var PACKAGE_VERSION = "0.6.
|
|
214
|
+
var PACKAGE_VERSION = "0.6.2";
|
|
215
215
|
|
|
216
216
|
// src/tools/quote.ts
|
|
217
217
|
import { z } from "zod";
|
|
@@ -663,13 +663,12 @@ var Q402NodeClient = class _Q402NodeClient {
|
|
|
663
663
|
* delegation), after which the remaining transfers are surfaced in
|
|
664
664
|
* the result array even if individual ones fail.
|
|
665
665
|
*
|
|
666
|
-
* Signature shape: `{ token, recipients }`. The
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
* second token. Same chain + same token across one batch, full stop.
|
|
666
|
+
* Signature shape: `{ token, recipients }`. The request body only ships
|
|
667
|
+
* one token field, so a per-row token would be silently ignored. The
|
|
668
|
+
* shape surfaces the constraint in the type so consumers can't
|
|
669
|
+
* accidentally build a "mixed-token batch" that would quietly drop
|
|
670
|
+
* the second token. Same chain + same token across one batch, full
|
|
671
|
+
* stop.
|
|
673
672
|
*/
|
|
674
673
|
async batchPay(input) {
|
|
675
674
|
const { token, recipients: rows } = input;
|
|
@@ -1272,9 +1271,10 @@ async function runBatchPay(input) {
|
|
|
1272
1271
|
guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
|
|
1273
1272
|
}
|
|
1274
1273
|
let senderWallet;
|
|
1275
|
-
|
|
1274
|
+
const echoPk = CONFIG.agenticPrivateKey && isValidPrivateKey(CONFIG.agenticPrivateKey) ? CONFIG.agenticPrivateKey : CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey) ? CONFIG.privateKey : null;
|
|
1275
|
+
if (echoPk) {
|
|
1276
1276
|
try {
|
|
1277
|
-
const addr = new Wallet3(
|
|
1277
|
+
const addr = new Wallet3(echoPk).address;
|
|
1278
1278
|
senderWallet = {
|
|
1279
1279
|
address: addr,
|
|
1280
1280
|
addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
|
|
@@ -1325,9 +1325,25 @@ async function runBatchPay(input) {
|
|
|
1325
1325
|
setupHint: reason
|
|
1326
1326
|
};
|
|
1327
1327
|
}
|
|
1328
|
+
const signingPk = CONFIG.agenticPrivateKey ?? CONFIG.privateKey;
|
|
1329
|
+
if (!signingPk) {
|
|
1330
|
+
guardsApplied.push("mode=sandbox");
|
|
1331
|
+
const sandboxResults = input.recipients.map(
|
|
1332
|
+
(r) => sandboxPay(chain, { to: r.to, amount: r.amount, token: input.token })
|
|
1333
|
+
);
|
|
1334
|
+
const reason = "No local signing key configured. Set Q402_AGENTIC_PRIVATE_KEY (Mode B) or Q402_PRIVATE_KEY (Mode A) to sign batch payments locally.";
|
|
1335
|
+
return {
|
|
1336
|
+
mode: "sandbox",
|
|
1337
|
+
status: "sandbox",
|
|
1338
|
+
result: { sandbox: sandboxResults, reason },
|
|
1339
|
+
senderWallet,
|
|
1340
|
+
guardsApplied,
|
|
1341
|
+
setupHint: reason
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1328
1344
|
const client = new Q402NodeClient({
|
|
1329
1345
|
apiKey: resolved.apiKey,
|
|
1330
|
-
privateKey:
|
|
1346
|
+
privateKey: signingPk,
|
|
1331
1347
|
chain,
|
|
1332
1348
|
relayBaseUrl: CONFIG.relayBaseUrl
|
|
1333
1349
|
});
|
|
@@ -1928,7 +1944,8 @@ Q402_MULTICHAIN_API_KEY=
|
|
|
1928
1944
|
# Pros: simplest mental model \u2014 same wallet you already use
|
|
1929
1945
|
# Cons: after first payment, MetaMask will show this account as
|
|
1930
1946
|
# "Smart account" (EIP-7702 delegation, reversible via
|
|
1931
|
-
# q402_clear_delegation
|
|
1947
|
+
# q402_clear_delegation \u2014 the visual change can be surprising
|
|
1948
|
+
# the first time you see it)
|
|
1932
1949
|
#
|
|
1933
1950
|
# B. Agent Wallet \u2014 local signing (recommended for AI agents)
|
|
1934
1951
|
# Set: Q402_AGENTIC_PRIVATE_KEY = 0x... (Agent Wallet pk from dashboard export)
|
|
@@ -2160,7 +2177,15 @@ async function runDoctor() {
|
|
|
2160
2177
|
),
|
|
2161
2178
|
Q402_PRIVATE_KEY: envSlot(
|
|
2162
2179
|
"Q402_PRIVATE_KEY",
|
|
2163
|
-
"
|
|
2180
|
+
"Mode A signing key \u2014 your real EOA's private key (e.g. MetaMask). EIP-7702 delegates your wallet to Q402 impl. Signs LOCALLY, never leaves your device."
|
|
2181
|
+
),
|
|
2182
|
+
Q402_AGENTIC_PRIVATE_KEY: envSlot(
|
|
2183
|
+
"Q402_AGENTIC_PRIVATE_KEY",
|
|
2184
|
+
"Mode B signing key \u2014 your Agent Wallet's exported private key from the dashboard. Signs LOCALLY, never leaves your device. Your MetaMask stays untouched."
|
|
2185
|
+
),
|
|
2186
|
+
Q402_AGENT_WALLET_ADDRESS: envSlot(
|
|
2187
|
+
"Q402_AGENT_WALLET_ADDRESS",
|
|
2188
|
+
"Mode C target \u2014 lowercased Agent Wallet address when you hold multiple. Omit to use the server-default wallet. No PK needed; the server signs."
|
|
2164
2189
|
),
|
|
2165
2190
|
Q402_ENABLE_REAL_PAYMENTS: envSlot(
|
|
2166
2191
|
"Q402_ENABLE_REAL_PAYMENTS",
|
|
@@ -2235,6 +2260,34 @@ async function runDoctor() {
|
|
|
2235
2260
|
"You have a legacy Q402_API_KEY set, and Q402_ENABLE_REAL_PAYMENTS wasn't explicitly set by you \u2014 so it's defaulting to 1 (real payments) since v0.5.11. To stay in sandbox while you check this, add `Q402_ENABLE_REAL_PAYMENTS=0` to ~/.q402/mcp.env (or your shell) and restart the MCP client."
|
|
2236
2261
|
);
|
|
2237
2262
|
}
|
|
2263
|
+
const walletModePicker = {
|
|
2264
|
+
question: "How would you like Q402 to sign your payments?",
|
|
2265
|
+
helpText: "Pick once \u2014 you can change later by editing ~/.q402/mcp.env. Most users want C (simplest).",
|
|
2266
|
+
recommendedPick: modes.modeC && !modes.modeA && !modes.modeB ? "C" : modes.primary ?? "C",
|
|
2267
|
+
options: [
|
|
2268
|
+
{
|
|
2269
|
+
pick: "C",
|
|
2270
|
+
title: "Q402 server signs for me (recommended, simplest)",
|
|
2271
|
+
description: "Q402 holds an encrypted Agent Wallet for you. You only set an API key \u2014 no private key in your env, no MetaMask popup, no Smart-account marker on your wallet. Best for AI agents, automations, and anyone who just wants payments to work.",
|
|
2272
|
+
env: ["Q402_MULTICHAIN_API_KEY (paid)", "or Q402_TRIAL_API_KEY (free BNB)"],
|
|
2273
|
+
privateKeyRequired: false
|
|
2274
|
+
},
|
|
2275
|
+
{
|
|
2276
|
+
pick: "B",
|
|
2277
|
+
title: "I'll export the Agent Wallet's private key and sign locally",
|
|
2278
|
+
description: "Same Agent Wallet as Mode C, but YOU hold the private key. Export it from the dashboard once. MCP signs locally so the key never leaves your machine. Your MetaMask is never touched.",
|
|
2279
|
+
env: ["Q402_AGENTIC_PRIVATE_KEY (export from dashboard)", "+ Q402_MULTICHAIN_API_KEY or Q402_TRIAL_API_KEY"],
|
|
2280
|
+
privateKeyRequired: true
|
|
2281
|
+
},
|
|
2282
|
+
{
|
|
2283
|
+
pick: "A",
|
|
2284
|
+
title: "Use my own EOA (MetaMask) as the signer",
|
|
2285
|
+
description: "Your existing wallet signs directly. EIP-7702 delegates it to Q402 for the call \u2014 your wallet shows a 'Smart account' marker after first use (normal, reversible). Best for power users who want their MetaMask address to be the on-chain payer. Use a FRESH wallet \u2014 not the one with your main funds.",
|
|
2286
|
+
env: ["Q402_PRIVATE_KEY (your EOA's private key)", "+ Q402_MULTICHAIN_API_KEY or Q402_TRIAL_API_KEY"],
|
|
2287
|
+
privateKeyRequired: true
|
|
2288
|
+
}
|
|
2289
|
+
]
|
|
2290
|
+
};
|
|
2238
2291
|
if (phase !== "live-check") {
|
|
2239
2292
|
return {
|
|
2240
2293
|
package: PACKAGE_NAME,
|
|
@@ -2243,6 +2296,7 @@ async function runDoctor() {
|
|
|
2243
2296
|
ready: false,
|
|
2244
2297
|
envFile,
|
|
2245
2298
|
envState,
|
|
2299
|
+
walletModePicker,
|
|
2246
2300
|
missing,
|
|
2247
2301
|
warnings,
|
|
2248
2302
|
recommendedActions,
|
|
@@ -2275,13 +2329,15 @@ async function runDoctor() {
|
|
|
2275
2329
|
}
|
|
2276
2330
|
let walletAddress;
|
|
2277
2331
|
let walletError;
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2332
|
+
if (CONFIG.privateKey) {
|
|
2333
|
+
try {
|
|
2334
|
+
walletAddress = new Wallet6(CONFIG.privateKey).address;
|
|
2335
|
+
} catch (e) {
|
|
2336
|
+
walletError = e instanceof Error ? e.message : String(e);
|
|
2337
|
+
warnings.push(
|
|
2338
|
+
`Q402_PRIVATE_KEY is set but does not parse as a 32-byte hex key: ${walletError}. Open ~/.q402/mcp.env in your editor and paste a real key (0x + 64 hex chars). Live calls will fail until this is fixed.`
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2285
2341
|
}
|
|
2286
2342
|
const verifyTargets = [];
|
|
2287
2343
|
if (CONFIG.trialApiKey) verifyTargets.push({ scope: "trial", envVar: "Q402_TRIAL_API_KEY", key: CONFIG.trialApiKey });
|
|
@@ -2328,6 +2384,38 @@ async function runDoctor() {
|
|
|
2328
2384
|
);
|
|
2329
2385
|
}
|
|
2330
2386
|
const ready = warnings.length === 0 && keys.some((k) => k.valid);
|
|
2387
|
+
const activeModes = [];
|
|
2388
|
+
if (modes.modeA) activeModes.push({
|
|
2389
|
+
mode: "A",
|
|
2390
|
+
label: "Real EOA (Q402_PRIVATE_KEY)",
|
|
2391
|
+
rationale: "Your MetaMask wallet signs directly. EIP-7702 delegates it to Q402 for the call."
|
|
2392
|
+
});
|
|
2393
|
+
if (modes.modeB) activeModes.push({
|
|
2394
|
+
mode: "B",
|
|
2395
|
+
label: "Agent Wallet local (Q402_AGENTIC_PRIVATE_KEY)",
|
|
2396
|
+
rationale: "Your exported Agent Wallet PK signs locally. Your MetaMask stays untouched."
|
|
2397
|
+
});
|
|
2398
|
+
if (modes.modeC) activeModes.push({
|
|
2399
|
+
mode: "C",
|
|
2400
|
+
label: "Server-mediated Agent Wallet (apiKey only)",
|
|
2401
|
+
rationale: "No PK in your env. Q402's server holds the encrypted Agent Wallet key and signs for you."
|
|
2402
|
+
});
|
|
2403
|
+
const recommendedMode = modes.primary ?? (CONFIG.apiKey?.startsWith("q402_live_") ? "C" : "none");
|
|
2404
|
+
const walletModes = {
|
|
2405
|
+
available: activeModes,
|
|
2406
|
+
count: modes.count,
|
|
2407
|
+
primary: modes.primary,
|
|
2408
|
+
/** Plain-English picker that the AI should echo when the user asks
|
|
2409
|
+
* "which mode do I use?" or "do I need a private key?". */
|
|
2410
|
+
recommendation: recommendedMode === "C" ? "You're configured for Mode C \u2014 Q402's server signs with your Agent Wallet. No private key needed. Simplest path; recommended for most users." : recommendedMode === "B" ? "You're configured for Mode B \u2014 your exported Agent Wallet PK signs locally. Your MetaMask is never touched." : recommendedMode === "A" ? "You're configured for Mode A \u2014 your MetaMask EOA signs directly. EIP-7702 delegates it to Q402 for the call. (If the Smart-account banner in MetaMask is a concern, switch to Mode B or C.)" : "No signing path configured yet. Easiest: set Q402_MULTICHAIN_API_KEY (or Q402_TRIAL_API_KEY for free BNB) and let the server sign \u2014 that's Mode C, no PK needed.",
|
|
2411
|
+
/** All three modes documented so the AI can answer "what are my
|
|
2412
|
+
* options?" without re-deriving from envState. */
|
|
2413
|
+
catalog: [
|
|
2414
|
+
{ mode: "A", label: "Real EOA", envVar: "Q402_PRIVATE_KEY", needsPk: true, bestFor: "Power users who want their MetaMask to be the on-chain signer." },
|
|
2415
|
+
{ mode: "B", label: "Agent Wallet local", envVar: "Q402_AGENTIC_PRIVATE_KEY", needsPk: true, bestFor: "Users who want Agent Wallet automation but keep custody of the PK." },
|
|
2416
|
+
{ mode: "C", label: "Server-mediated", envVar: "(none)", needsPk: false, bestFor: "Most users. No PK in env; Q402 holds the Agent Wallet key encrypted server-side." }
|
|
2417
|
+
]
|
|
2418
|
+
};
|
|
2331
2419
|
return {
|
|
2332
2420
|
package: PACKAGE_NAME,
|
|
2333
2421
|
version: PACKAGE_VERSION,
|
|
@@ -2335,6 +2423,7 @@ async function runDoctor() {
|
|
|
2335
2423
|
ready,
|
|
2336
2424
|
envFile,
|
|
2337
2425
|
envState,
|
|
2426
|
+
walletModes,
|
|
2338
2427
|
missing,
|
|
2339
2428
|
wallet: walletAddress ? { address: walletAddress } : void 0,
|
|
2340
2429
|
keys,
|
|
@@ -2480,6 +2569,298 @@ async function runAgenticInfo(input = {}) {
|
|
|
2480
2569
|
};
|
|
2481
2570
|
}
|
|
2482
2571
|
|
|
2572
|
+
// src/tools/recurring-list.ts
|
|
2573
|
+
import { z as z10 } from "zod";
|
|
2574
|
+
var RecurringListInputSchema = z10.object({
|
|
2575
|
+
walletId: z10.string().optional().describe(
|
|
2576
|
+
"Optional lowercased Agent Wallet address to list rules for when the user holds multiple wallets. Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
2577
|
+
)
|
|
2578
|
+
});
|
|
2579
|
+
var RECURRING_LIST_TOOL = {
|
|
2580
|
+
name: "q402_recurring_list",
|
|
2581
|
+
description: "Read the user's active recurring-payment rules on their Agent Wallet. Returns each rule's ruleId, label, frequency (hourly:N / daily / weekly:{day} / monthly:N / monthly:last), recipient + amount, chain, token, status (active / paused / paused-by-archive / fired-cap-exceeded / cancelled), when the next fire is scheduled, how many fires have completed, and the most recent error (if any). Use this when the user asks 'what scheduled payouts do I have?' or before authoring a new rule with q402_recurring_create. Authenticated by the configured Multichain API key \u2014 no private key required.",
|
|
2582
|
+
inputSchema: {
|
|
2583
|
+
type: "object",
|
|
2584
|
+
properties: {
|
|
2585
|
+
walletId: {
|
|
2586
|
+
type: "string",
|
|
2587
|
+
description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
2588
|
+
}
|
|
2589
|
+
},
|
|
2590
|
+
additionalProperties: false
|
|
2591
|
+
}
|
|
2592
|
+
};
|
|
2593
|
+
async function runRecurringList(input = {}) {
|
|
2594
|
+
const base = CONFIG.relayBaseUrl;
|
|
2595
|
+
const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
|
|
2596
|
+
if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
|
|
2597
|
+
return {
|
|
2598
|
+
configured: false,
|
|
2599
|
+
walletId: null,
|
|
2600
|
+
rules: [],
|
|
2601
|
+
count: 0,
|
|
2602
|
+
dashboardUrl,
|
|
2603
|
+
setupHint: "No live Q402 API key configured. Run q402_doctor to set one up, or open the dashboard to create rules from the UI."
|
|
2604
|
+
};
|
|
2605
|
+
}
|
|
2606
|
+
const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
|
|
2607
|
+
try {
|
|
2608
|
+
const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
|
|
2609
|
+
method: "POST",
|
|
2610
|
+
headers: { "Content-Type": "application/json" },
|
|
2611
|
+
body: JSON.stringify({
|
|
2612
|
+
apiKey: CONFIG.apiKey,
|
|
2613
|
+
action: "list",
|
|
2614
|
+
...explicitWalletId ? { walletId: explicitWalletId } : {}
|
|
2615
|
+
})
|
|
2616
|
+
});
|
|
2617
|
+
if (!res.ok) {
|
|
2618
|
+
const errBody = await res.json().catch(() => ({}));
|
|
2619
|
+
return {
|
|
2620
|
+
configured: true,
|
|
2621
|
+
walletId: explicitWalletId,
|
|
2622
|
+
rules: [],
|
|
2623
|
+
count: 0,
|
|
2624
|
+
dashboardUrl,
|
|
2625
|
+
setupHint: `recurring-list failed: ${errBody.error ?? `HTTP ${res.status}`}` + (errBody.message ? ` \u2014 ${errBody.message}` : "")
|
|
2626
|
+
};
|
|
2627
|
+
}
|
|
2628
|
+
const data = await res.json();
|
|
2629
|
+
return {
|
|
2630
|
+
configured: true,
|
|
2631
|
+
walletId: data.walletId,
|
|
2632
|
+
rules: data.rules,
|
|
2633
|
+
count: data.count,
|
|
2634
|
+
dashboardUrl
|
|
2635
|
+
};
|
|
2636
|
+
} catch (e) {
|
|
2637
|
+
return {
|
|
2638
|
+
configured: true,
|
|
2639
|
+
walletId: explicitWalletId,
|
|
2640
|
+
rules: [],
|
|
2641
|
+
count: 0,
|
|
2642
|
+
dashboardUrl,
|
|
2643
|
+
setupHint: `recurring-list network error: ${e instanceof Error ? e.message : String(e)}`
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
}
|
|
2647
|
+
|
|
2648
|
+
// src/tools/recurring-create.ts
|
|
2649
|
+
import { z as z11 } from "zod";
|
|
2650
|
+
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
2651
|
+
var AMOUNT_RE = /^\d+(\.\d{1,18})?$/;
|
|
2652
|
+
var RecurringCreateInputSchema = z11.object({
|
|
2653
|
+
frequency: z11.string().min(1).describe(
|
|
2654
|
+
'Cadence string. One of: "hourly:N" (N=1..23), "daily", "weekly:{mon|tue|wed|thu|fri|sat|sun}", "monthly:N" (N=1..31), "monthly:last". Examples: "hourly:1" fires every hour, "weekly:fri" fires every Friday, "monthly:1" fires on the 1st of each month.'
|
|
2655
|
+
),
|
|
2656
|
+
recipient: z11.string().regex(ADDRESS_RE).describe("0x-prefixed 20-byte recipient address. Required."),
|
|
2657
|
+
amount: z11.string().regex(AMOUNT_RE).describe(
|
|
2658
|
+
'Amount per fire, as a decimal string (e.g. "1.5", "0.0001"). Counted in the same unit as `token` (USDC or USDT, both 1:1 USD).'
|
|
2659
|
+
),
|
|
2660
|
+
chain: z11.enum(["bnb", "eth", "avax", "xlayer", "mantle", "injective", "monad", "scroll", "stable"]).default("bnb").describe(
|
|
2661
|
+
"Chain to fire the recurring TX on. Defaults to bnb (the only chain supported on Trial). Non-bnb requires the paid Multichain subscription."
|
|
2662
|
+
),
|
|
2663
|
+
token: z11.enum(["USDC", "USDT"]).default("USDT").describe("Stablecoin to send. USDC or USDT. Both peg to USD-1."),
|
|
2664
|
+
label: z11.string().max(64).optional().describe("Optional human-readable label (\u226464 chars). Shows up in q402_recurring_list and the dashboard."),
|
|
2665
|
+
cancelWindowHours: z11.number().min(0).optional().describe(
|
|
2666
|
+
"Hours of advance notice before each fire during which the rule can be cancelled. 0 = fire immediately at the next slot, no alert. Capped at the cadence interval (e.g. \u2264 N-0.5h for hourly:N, \u226424 for daily). Defaults to 0."
|
|
2667
|
+
),
|
|
2668
|
+
walletId: z11.string().optional().describe(
|
|
2669
|
+
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
2670
|
+
)
|
|
2671
|
+
});
|
|
2672
|
+
var RECURRING_CREATE_TOOL = {
|
|
2673
|
+
name: "q402_recurring_create",
|
|
2674
|
+
description: "Author a new recurring-payment rule on the user's Agent Wallet. Single-recipient (use the dashboard for multi-recipient payroll). Pick a cadence \u2014 hourly:N, daily, weekly:{day}, monthly:N, or monthly:last \u2014 and a recipient + amount + chain + token. Authenticated by the configured Multichain API key; no private key required. Non-bnb chains need the paid Multichain subscription. Each fire is bounded by the wallet's perTxMax (configured on the dashboard) \u2014 the dashboard's dailyLimit cap currently applies to manual sends only, NOT recurring fires, so an attacker with the apiKey could schedule N rules at perTxMax and drain the wallet's USDC balance over time. The user can stop a rule any time via q402_recurring_cancel.",
|
|
2675
|
+
inputSchema: {
|
|
2676
|
+
type: "object",
|
|
2677
|
+
properties: {
|
|
2678
|
+
frequency: {
|
|
2679
|
+
type: "string",
|
|
2680
|
+
description: 'Required. "hourly:N" (N=1..23), "daily", "weekly:{day}", "monthly:N", or "monthly:last".'
|
|
2681
|
+
},
|
|
2682
|
+
recipient: {
|
|
2683
|
+
type: "string",
|
|
2684
|
+
pattern: "^0x[0-9a-fA-F]{40}$",
|
|
2685
|
+
description: "Required. 0x-prefixed 20-byte recipient address."
|
|
2686
|
+
},
|
|
2687
|
+
amount: {
|
|
2688
|
+
type: "string",
|
|
2689
|
+
pattern: "^\\d+(\\.\\d{1,18})?$",
|
|
2690
|
+
description: 'Required. Per-fire amount as decimal string (e.g. "1.5").'
|
|
2691
|
+
},
|
|
2692
|
+
chain: {
|
|
2693
|
+
type: "string",
|
|
2694
|
+
enum: ["bnb", "eth", "avax", "xlayer", "mantle", "injective", "monad", "scroll", "stable"],
|
|
2695
|
+
description: "Default 'bnb'. Non-bnb requires paid Multichain."
|
|
2696
|
+
},
|
|
2697
|
+
token: {
|
|
2698
|
+
type: "string",
|
|
2699
|
+
enum: ["USDC", "USDT"],
|
|
2700
|
+
description: "Default 'USDT'. Both peg USD-1."
|
|
2701
|
+
},
|
|
2702
|
+
label: {
|
|
2703
|
+
type: "string",
|
|
2704
|
+
maxLength: 64,
|
|
2705
|
+
description: "Optional human label (\u226464 chars)."
|
|
2706
|
+
},
|
|
2707
|
+
cancelWindowHours: {
|
|
2708
|
+
type: "number",
|
|
2709
|
+
minimum: 0,
|
|
2710
|
+
description: "Optional advance-notice window in hours. 0 = no alert, fires at the next slot. Defaults to 0."
|
|
2711
|
+
},
|
|
2712
|
+
walletId: {
|
|
2713
|
+
type: "string",
|
|
2714
|
+
description: "Optional. Defaults to default wallet on server."
|
|
2715
|
+
}
|
|
2716
|
+
},
|
|
2717
|
+
required: ["frequency", "recipient", "amount"],
|
|
2718
|
+
additionalProperties: false
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2721
|
+
async function runRecurringCreate(input) {
|
|
2722
|
+
const base = CONFIG.relayBaseUrl;
|
|
2723
|
+
const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
|
|
2724
|
+
if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
|
|
2725
|
+
return {
|
|
2726
|
+
ok: false,
|
|
2727
|
+
walletId: null,
|
|
2728
|
+
rule: null,
|
|
2729
|
+
error: "API_KEY_REQUIRED",
|
|
2730
|
+
message: "No live Q402 API key configured. Run q402_doctor to set one up.",
|
|
2731
|
+
dashboardUrl
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
|
|
2735
|
+
try {
|
|
2736
|
+
const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
|
|
2737
|
+
method: "POST",
|
|
2738
|
+
headers: { "Content-Type": "application/json" },
|
|
2739
|
+
body: JSON.stringify({
|
|
2740
|
+
apiKey: CONFIG.apiKey,
|
|
2741
|
+
action: "create",
|
|
2742
|
+
frequency: input.frequency,
|
|
2743
|
+
recipient: input.recipient.toLowerCase(),
|
|
2744
|
+
amount: input.amount,
|
|
2745
|
+
chain: input.chain ?? "bnb",
|
|
2746
|
+
token: input.token ?? "USDT",
|
|
2747
|
+
...input.label !== void 0 ? { label: input.label } : {},
|
|
2748
|
+
...input.cancelWindowHours !== void 0 ? { cancelWindowHours: input.cancelWindowHours } : {},
|
|
2749
|
+
...explicitWalletId ? { walletId: explicitWalletId } : {}
|
|
2750
|
+
})
|
|
2751
|
+
});
|
|
2752
|
+
const data = await res.json().catch(() => ({}));
|
|
2753
|
+
if (!res.ok) {
|
|
2754
|
+
return {
|
|
2755
|
+
ok: false,
|
|
2756
|
+
walletId: explicitWalletId,
|
|
2757
|
+
rule: null,
|
|
2758
|
+
error: data.error ?? `HTTP_${res.status}`,
|
|
2759
|
+
message: data.message ?? `Create failed with HTTP ${res.status}.`,
|
|
2760
|
+
dashboardUrl
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
return {
|
|
2764
|
+
ok: true,
|
|
2765
|
+
walletId: data.walletId ?? explicitWalletId,
|
|
2766
|
+
rule: data.rule ?? null,
|
|
2767
|
+
dashboardUrl
|
|
2768
|
+
};
|
|
2769
|
+
} catch (e) {
|
|
2770
|
+
return {
|
|
2771
|
+
ok: false,
|
|
2772
|
+
walletId: explicitWalletId,
|
|
2773
|
+
rule: null,
|
|
2774
|
+
error: "NETWORK_ERROR",
|
|
2775
|
+
message: e instanceof Error ? e.message : String(e),
|
|
2776
|
+
dashboardUrl
|
|
2777
|
+
};
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// src/tools/recurring-cancel.ts
|
|
2782
|
+
import { z as z12 } from "zod";
|
|
2783
|
+
var RecurringCancelInputSchema = z12.object({
|
|
2784
|
+
ruleId: z12.string().min(1).describe(
|
|
2785
|
+
"Rule id to cancel. Obtain from q402_recurring_list \u2014 each entry's `ruleId` field. Cancelling is immediate."
|
|
2786
|
+
),
|
|
2787
|
+
walletId: z12.string().optional().describe(
|
|
2788
|
+
"Optional lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
2789
|
+
)
|
|
2790
|
+
});
|
|
2791
|
+
var RECURRING_CANCEL_TOOL = {
|
|
2792
|
+
name: "q402_recurring_cancel",
|
|
2793
|
+
description: "Cancel an active recurring-payment rule on the Agent Wallet. Takes a ruleId (from q402_recurring_list). Cancel is immediate \u2014 the rule will not fire again. Authenticated by the configured Multichain API key. Idempotent: cancelling an already-cancelled rule returns 409 with a clear message. Use this whenever the user says 'stop my recurring payment to X' \u2014 call q402_recurring_list first to find the matching ruleId, then call this with that id.",
|
|
2794
|
+
inputSchema: {
|
|
2795
|
+
type: "object",
|
|
2796
|
+
properties: {
|
|
2797
|
+
ruleId: {
|
|
2798
|
+
type: "string",
|
|
2799
|
+
description: "Rule id from q402_recurring_list. Required."
|
|
2800
|
+
},
|
|
2801
|
+
walletId: {
|
|
2802
|
+
type: "string",
|
|
2803
|
+
description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet on the server."
|
|
2804
|
+
}
|
|
2805
|
+
},
|
|
2806
|
+
required: ["ruleId"],
|
|
2807
|
+
additionalProperties: false
|
|
2808
|
+
}
|
|
2809
|
+
};
|
|
2810
|
+
async function runRecurringCancel(input) {
|
|
2811
|
+
const base = CONFIG.relayBaseUrl;
|
|
2812
|
+
const dashboardUrl = base.replace(/\/api$/, "") + "/dashboard?tab=agent";
|
|
2813
|
+
if (!CONFIG.apiKey || !CONFIG.apiKey.startsWith("q402_live_")) {
|
|
2814
|
+
return {
|
|
2815
|
+
ok: false,
|
|
2816
|
+
walletId: null,
|
|
2817
|
+
rule: null,
|
|
2818
|
+
error: "API_KEY_REQUIRED",
|
|
2819
|
+
message: "No live Q402 API key configured. Run q402_doctor to set one up.",
|
|
2820
|
+
dashboardUrl
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
const explicitWalletId = typeof input.walletId === "string" && input.walletId.length > 0 ? input.walletId.toLowerCase() : CONFIG.walletId;
|
|
2824
|
+
try {
|
|
2825
|
+
const res = await fetch(`${base}/wallet/agentic/recurring-by-key`, {
|
|
2826
|
+
method: "POST",
|
|
2827
|
+
headers: { "Content-Type": "application/json" },
|
|
2828
|
+
body: JSON.stringify({
|
|
2829
|
+
apiKey: CONFIG.apiKey,
|
|
2830
|
+
action: "cancel",
|
|
2831
|
+
ruleId: input.ruleId,
|
|
2832
|
+
...explicitWalletId ? { walletId: explicitWalletId } : {}
|
|
2833
|
+
})
|
|
2834
|
+
});
|
|
2835
|
+
const data = await res.json().catch(() => ({}));
|
|
2836
|
+
if (!res.ok) {
|
|
2837
|
+
return {
|
|
2838
|
+
ok: false,
|
|
2839
|
+
walletId: explicitWalletId,
|
|
2840
|
+
rule: null,
|
|
2841
|
+
error: data.error ?? `HTTP_${res.status}`,
|
|
2842
|
+
message: data.message ?? `Cancel failed with HTTP ${res.status}.`,
|
|
2843
|
+
dashboardUrl
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
return {
|
|
2847
|
+
ok: true,
|
|
2848
|
+
walletId: data.walletId ?? explicitWalletId,
|
|
2849
|
+
rule: data.rule ?? null,
|
|
2850
|
+
dashboardUrl
|
|
2851
|
+
};
|
|
2852
|
+
} catch (e) {
|
|
2853
|
+
return {
|
|
2854
|
+
ok: false,
|
|
2855
|
+
walletId: explicitWalletId,
|
|
2856
|
+
rule: null,
|
|
2857
|
+
error: "NETWORK_ERROR",
|
|
2858
|
+
message: e instanceof Error ? e.message : String(e),
|
|
2859
|
+
dashboardUrl
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2483
2864
|
// src/index.ts
|
|
2484
2865
|
function jsonText(value) {
|
|
2485
2866
|
return { type: "text", text: JSON.stringify(value, null, 2) };
|
|
@@ -2501,6 +2882,9 @@ async function main() {
|
|
|
2501
2882
|
RECEIPT_TOOL,
|
|
2502
2883
|
WALLET_STATUS_TOOL,
|
|
2503
2884
|
AGENTIC_INFO_TOOL,
|
|
2885
|
+
RECURRING_LIST_TOOL,
|
|
2886
|
+
RECURRING_CREATE_TOOL,
|
|
2887
|
+
RECURRING_CANCEL_TOOL,
|
|
2504
2888
|
CLEAR_DELEGATION_TOOL
|
|
2505
2889
|
]
|
|
2506
2890
|
}));
|
|
@@ -2544,6 +2928,18 @@ async function main() {
|
|
|
2544
2928
|
AgenticInfoInputSchema.parse(args ?? {});
|
|
2545
2929
|
return { content: [jsonText(await runAgenticInfo())] };
|
|
2546
2930
|
}
|
|
2931
|
+
case "q402_recurring_list": {
|
|
2932
|
+
const parsed = RecurringListInputSchema.parse(args ?? {});
|
|
2933
|
+
return { content: [jsonText(await runRecurringList(parsed))] };
|
|
2934
|
+
}
|
|
2935
|
+
case "q402_recurring_create": {
|
|
2936
|
+
const parsed = RecurringCreateInputSchema.parse(args ?? {});
|
|
2937
|
+
return { content: [jsonText(await runRecurringCreate(parsed))] };
|
|
2938
|
+
}
|
|
2939
|
+
case "q402_recurring_cancel": {
|
|
2940
|
+
const parsed = RecurringCancelInputSchema.parse(args ?? {});
|
|
2941
|
+
return { content: [jsonText(await runRecurringCancel(parsed))] };
|
|
2942
|
+
}
|
|
2547
2943
|
default:
|
|
2548
2944
|
return {
|
|
2549
2945
|
isError: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "MCP server for Q402 — gasless USDC, USDT, and RLUSD payments across 9 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": [
|