@quackai/q402-mcp 0.6.0 → 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 +542 -38
- 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
|
@@ -121,7 +121,12 @@ function loadConfig() {
|
|
|
121
121
|
const apiKeyKind = classifyApiKey(apiKey);
|
|
122
122
|
const privateKey = ENV.Q402_PRIVATE_KEY ?? null;
|
|
123
123
|
const agenticPrivateKey = ENV.Q402_AGENTIC_PRIVATE_KEY ?? null;
|
|
124
|
-
const walletIdRaw = ENV.Q402_WALLET_ID;
|
|
124
|
+
const walletIdRaw = ENV.Q402_AGENT_WALLET_ADDRESS ?? ENV.Q402_WALLET_ID;
|
|
125
|
+
if (!ENV.Q402_AGENT_WALLET_ADDRESS && ENV.Q402_WALLET_ID) {
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
"[q402-mcp] Q402_WALLET_ID is deprecated \u2014 rename to Q402_AGENT_WALLET_ADDRESS. Old name will be removed in v0.7.0.\n"
|
|
128
|
+
);
|
|
129
|
+
}
|
|
125
130
|
const walletId = typeof walletIdRaw === "string" && walletIdRaw.length > 0 ? walletIdRaw.toLowerCase() : null;
|
|
126
131
|
const realPaymentsRequested = ENV.Q402_ENABLE_REAL_PAYMENTS === "1";
|
|
127
132
|
const anyLiveKey = classifyApiKey(trialApiKey) === "live" || classifyApiKey(multichainApiKey) === "live" || classifyApiKey(legacyApiKey) === "live";
|
|
@@ -206,7 +211,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
|
|
|
206
211
|
|
|
207
212
|
// src/version.ts
|
|
208
213
|
var PACKAGE_NAME = "@quackai/q402-mcp";
|
|
209
|
-
var PACKAGE_VERSION = "0.6.
|
|
214
|
+
var PACKAGE_VERSION = "0.6.2";
|
|
210
215
|
|
|
211
216
|
// src/tools/quote.ts
|
|
212
217
|
import { z } from "zod";
|
|
@@ -658,13 +663,12 @@ var Q402NodeClient = class _Q402NodeClient {
|
|
|
658
663
|
* delegation), after which the remaining transfers are surfaced in
|
|
659
664
|
* the result array even if individual ones fail.
|
|
660
665
|
*
|
|
661
|
-
* Signature shape: `{ token, recipients }`. The
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
* 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.
|
|
668
672
|
*/
|
|
669
673
|
async batchPay(input) {
|
|
670
674
|
const { token, recipients: rows } = input;
|
|
@@ -1047,6 +1051,32 @@ async function runPay(input) {
|
|
|
1047
1051
|
}
|
|
1048
1052
|
const data = await resp.json().catch(() => ({}));
|
|
1049
1053
|
const txHash = data.txHash ?? "";
|
|
1054
|
+
const isPending = resp.status === 202 || data.pending === true || data.status === "processing";
|
|
1055
|
+
if (isPending) {
|
|
1056
|
+
const retryAfter = typeof data.retryAfterSec === "number" ? data.retryAfterSec : 5;
|
|
1057
|
+
return {
|
|
1058
|
+
result: {
|
|
1059
|
+
success: false,
|
|
1060
|
+
sandbox: false,
|
|
1061
|
+
txHash: "",
|
|
1062
|
+
tokenAmount: input.amount,
|
|
1063
|
+
token: input.token,
|
|
1064
|
+
chain: chain.key,
|
|
1065
|
+
method: "eip7702",
|
|
1066
|
+
pending: true,
|
|
1067
|
+
retryAfterSec: retryAfter
|
|
1068
|
+
},
|
|
1069
|
+
guardsApplied: [
|
|
1070
|
+
...guardsApplied,
|
|
1071
|
+
"wallet=agentic-server",
|
|
1072
|
+
"mode=live",
|
|
1073
|
+
"status=pending",
|
|
1074
|
+
`retry_after=${retryAfter}s`
|
|
1075
|
+
],
|
|
1076
|
+
senderWallet,
|
|
1077
|
+
setupHint: `An identical send for this wallet is still in flight on the server. Wait ${retryAfter}s and retry \u2014 the cached result will come back, no double-spend.`
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1050
1080
|
const success = resp.ok && txHash.length > 0;
|
|
1051
1081
|
const message = "message" in data ? data.message : "error" in data ? data.error : void 0;
|
|
1052
1082
|
return {
|
|
@@ -1241,9 +1271,10 @@ async function runBatchPay(input) {
|
|
|
1241
1271
|
guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
|
|
1242
1272
|
}
|
|
1243
1273
|
let senderWallet;
|
|
1244
|
-
|
|
1274
|
+
const echoPk = CONFIG.agenticPrivateKey && isValidPrivateKey(CONFIG.agenticPrivateKey) ? CONFIG.agenticPrivateKey : CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey) ? CONFIG.privateKey : null;
|
|
1275
|
+
if (echoPk) {
|
|
1245
1276
|
try {
|
|
1246
|
-
const addr = new Wallet3(
|
|
1277
|
+
const addr = new Wallet3(echoPk).address;
|
|
1247
1278
|
senderWallet = {
|
|
1248
1279
|
address: addr,
|
|
1249
1280
|
addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
|
|
@@ -1294,9 +1325,25 @@ async function runBatchPay(input) {
|
|
|
1294
1325
|
setupHint: reason
|
|
1295
1326
|
};
|
|
1296
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
|
+
}
|
|
1297
1344
|
const client = new Q402NodeClient({
|
|
1298
1345
|
apiKey: resolved.apiKey,
|
|
1299
|
-
privateKey:
|
|
1346
|
+
privateKey: signingPk,
|
|
1300
1347
|
chain,
|
|
1301
1348
|
relayBaseUrl: CONFIG.relayBaseUrl
|
|
1302
1349
|
});
|
|
@@ -1884,13 +1931,77 @@ Q402_MULTICHAIN_API_KEY=
|
|
|
1884
1931
|
|
|
1885
1932
|
|
|
1886
1933
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1887
|
-
#
|
|
1934
|
+
# SIGNING MODE \u2014 pick ONE of A / B / C below
|
|
1935
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1936
|
+
# Q402 pays in three ways, depending on which keys you set. Pick the
|
|
1937
|
+
# row that matches your situation and fill ONLY that row's variable(s).
|
|
1938
|
+
# Setting multiple rows is allowed \u2014 q402_pay will ask you which one to
|
|
1939
|
+
# use per call (the AI surfaces the question; you don't pre-pick a
|
|
1940
|
+
# default here).
|
|
1941
|
+
#
|
|
1942
|
+
# A. Real EOA (your MetaMask wallet)
|
|
1943
|
+
# Set: Q402_PRIVATE_KEY = 0x... (your MetaMask account's private key)
|
|
1944
|
+
# Pros: simplest mental model \u2014 same wallet you already use
|
|
1945
|
+
# Cons: after first payment, MetaMask will show this account as
|
|
1946
|
+
# "Smart account" (EIP-7702 delegation, reversible via
|
|
1947
|
+
# q402_clear_delegation \u2014 the visual change can be surprising
|
|
1948
|
+
# the first time you see it)
|
|
1949
|
+
#
|
|
1950
|
+
# B. Agent Wallet \u2014 local signing (recommended for AI agents)
|
|
1951
|
+
# Set: Q402_AGENTIC_PRIVATE_KEY = 0x... (Agent Wallet pk from dashboard export)
|
|
1952
|
+
# Pros: your MetaMask stays untouched; agent has its own purse with
|
|
1953
|
+
# per-tx + daily caps you set on the dashboard
|
|
1954
|
+
# Cons: requires creating an Agent Wallet on the dashboard first
|
|
1955
|
+
# (https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Create)
|
|
1956
|
+
# then exporting its private key
|
|
1957
|
+
#
|
|
1958
|
+
# C. Agent Wallet \u2014 server-managed (no private key on your machine)
|
|
1959
|
+
# Set: (just the api key + optionally Q402_AGENT_WALLET_ADDRESS below)
|
|
1960
|
+
# Pros: zero private-key handling locally; the server holds the
|
|
1961
|
+
# encrypted Agent Wallet pk and signs on your behalf
|
|
1962
|
+
# Cons: requires a paid Multichain API key (Mode C is not available
|
|
1963
|
+
# on the free Trial). The server-side keystore is AES-256-GCM
|
|
1964
|
+
# encrypted but is a custodial path \u2014 pick A or B if that
|
|
1965
|
+
# posture doesn't fit your threat model.
|
|
1966
|
+
#
|
|
1967
|
+
# A user can have multiple wallets configured simultaneously (e.g. PK
|
|
1968
|
+
# for personal pays + apiKey-only for agent pays). When more than one
|
|
1969
|
+
# mode is set, q402_pay asks the user which to use rather than picking
|
|
1970
|
+
# silently.
|
|
1971
|
+
|
|
1972
|
+
|
|
1973
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1974
|
+
# WALLET \u2014 Mode A: real EOA private key
|
|
1888
1975
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1889
1976
|
# Hex EVM private key (0x + 64 hex chars). Signs payments LOCALLY on
|
|
1890
1977
|
# your machine \u2014 never leaves your device, never sent to any server.
|
|
1978
|
+
# Leave blank if you're using Mode B or Mode C.
|
|
1891
1979
|
Q402_PRIVATE_KEY=
|
|
1892
1980
|
|
|
1893
1981
|
|
|
1982
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1983
|
+
# WALLET \u2014 Mode B: exported Agent Wallet private key
|
|
1984
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1985
|
+
# Hex EVM private key (0x + 64 hex chars) exported from your Agent
|
|
1986
|
+
# Wallet on the dashboard. Signs payments LOCALLY just like Mode A,
|
|
1987
|
+
# but the wallet is your dedicated Agent Wallet \u2014 your MetaMask EOA
|
|
1988
|
+
# is never touched. Get the key at:
|
|
1989
|
+
# https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Export
|
|
1990
|
+
# Leave blank if you're using Mode A or Mode C.
|
|
1991
|
+
Q402_AGENTIC_PRIVATE_KEY=
|
|
1992
|
+
|
|
1993
|
+
|
|
1994
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1995
|
+
# WALLET \u2014 Mode C: server-managed Agent Wallet picker (optional)
|
|
1996
|
+
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1997
|
+
# Only set this when you're running Mode C (api key only, no private
|
|
1998
|
+
# keys above) AND you have more than one Agent Wallet on the account
|
|
1999
|
+
# (max 10). Pin which one Q402 should spend from. Format: lowercase
|
|
2000
|
+
# 0x... address of the Agent Wallet (visible on the dashboard's Agent
|
|
2001
|
+
# tab). Omit entirely to use your default Agent Wallet.
|
|
2002
|
+
# Q402_AGENT_WALLET_ADDRESS=0x...
|
|
2003
|
+
|
|
2004
|
+
|
|
1894
2005
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
1895
2006
|
# Live mode switch (safe even at 1 \u2014 see SAFE-BY-DEFAULT note at top)
|
|
1896
2007
|
# \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
@@ -1943,13 +2054,16 @@ function mask2(key) {
|
|
|
1943
2054
|
function detectPhase() {
|
|
1944
2055
|
const anyKey = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
|
|
1945
2056
|
const anyLiveKey = classifyApiKey(CONFIG.trialApiKey) === "live" || classifyApiKey(CONFIG.multichainApiKey) === "live" || classifyApiKey(CONFIG.legacyApiKey) === "live";
|
|
1946
|
-
const
|
|
1947
|
-
|
|
2057
|
+
const modes = detectAgenticModes();
|
|
2058
|
+
const hasAnyValidSigningPath = modes.count > 0;
|
|
2059
|
+
if (!Q402_ENV_FILE_PRESENT && !anyKey && !CONFIG.privateKey && !CONFIG.agenticPrivateKey) {
|
|
1948
2060
|
return "first-install";
|
|
1949
2061
|
}
|
|
1950
|
-
const allEssentials = anyKey &&
|
|
2062
|
+
const allEssentials = anyKey && hasAnyValidSigningPath && CONFIG.realPaymentsRequested && anyLiveKey;
|
|
1951
2063
|
if (allEssentials) return "live-check";
|
|
1952
|
-
if (anyKey || CONFIG.privateKey || Q402_ENV_FILE_PRESENT)
|
|
2064
|
+
if (anyKey || CONFIG.privateKey || CONFIG.agenticPrivateKey || Q402_ENV_FILE_PRESENT) {
|
|
2065
|
+
return "needs-completion";
|
|
2066
|
+
}
|
|
1953
2067
|
return "first-install";
|
|
1954
2068
|
}
|
|
1955
2069
|
async function verifyOneKey(scope, envVar, apiKey) {
|
|
@@ -2063,30 +2177,48 @@ async function runDoctor() {
|
|
|
2063
2177
|
),
|
|
2064
2178
|
Q402_PRIVATE_KEY: envSlot(
|
|
2065
2179
|
"Q402_PRIVATE_KEY",
|
|
2066
|
-
"
|
|
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."
|
|
2067
2189
|
),
|
|
2068
2190
|
Q402_ENABLE_REAL_PAYMENTS: envSlot(
|
|
2069
2191
|
"Q402_ENABLE_REAL_PAYMENTS",
|
|
2070
2192
|
"Must be '1' to allow real TX. Anything else = test response (fake hash)."
|
|
2071
2193
|
)
|
|
2072
2194
|
};
|
|
2195
|
+
const modes = detectAgenticModes();
|
|
2073
2196
|
const missing = [];
|
|
2074
2197
|
if (!CONFIG.trialApiKey && !CONFIG.multichainApiKey && !CONFIG.legacyApiKey) {
|
|
2075
2198
|
missing.push(
|
|
2076
2199
|
"An API key (Q402_TRIAL_API_KEY for free BNB OR Q402_MULTICHAIN_API_KEY for paid 9-chain)"
|
|
2077
2200
|
);
|
|
2078
2201
|
}
|
|
2079
|
-
if (
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2202
|
+
if (modes.count === 0) {
|
|
2203
|
+
const pkAPlaceholder = !!CONFIG.privateKey && !isValidPrivateKey(CONFIG.privateKey);
|
|
2204
|
+
const pkBPlaceholder = !!CONFIG.agenticPrivateKey && !isValidPrivateKey(CONFIG.agenticPrivateKey);
|
|
2205
|
+
if (pkAPlaceholder) {
|
|
2206
|
+
missing.push(
|
|
2207
|
+
"Q402_PRIVATE_KEY is set but malformed (expected 0x + 64 hex chars). Looks like the placeholder '0x...' is still in ~/.q402/mcp.env \u2014 paste a real key in your editor."
|
|
2208
|
+
);
|
|
2209
|
+
} else if (pkBPlaceholder) {
|
|
2210
|
+
missing.push(
|
|
2211
|
+
"Q402_AGENTIC_PRIVATE_KEY is set but malformed (expected 0x + 64 hex chars). Paste your exported Agent Wallet private key (https://q402.quackai.ai/dashboard \u2192 Agent tab \u2192 Export)."
|
|
2212
|
+
);
|
|
2213
|
+
} else {
|
|
2214
|
+
missing.push(
|
|
2215
|
+
"A signing path. Pick ONE: (A) Q402_PRIVATE_KEY = your MetaMask EOA's private key; (B) Q402_AGENTIC_PRIVATE_KEY = your exported Agent Wallet private key from the dashboard (Mode B keeps your MetaMask untouched); or (C) just use a live paid Q402_MULTICHAIN_API_KEY and let Q402 sign with your server-managed Agent Wallet (no PK on your machine)."
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2085
2218
|
}
|
|
2086
2219
|
if (!CONFIG.realPaymentsRequested) {
|
|
2087
2220
|
const haveAnyApi = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
|
|
2088
|
-
|
|
2089
|
-
if (haveAnyApi && havePk) {
|
|
2221
|
+
if (haveAnyApi && modes.count > 0) {
|
|
2090
2222
|
missing.push(
|
|
2091
2223
|
"Q402_ENABLE_REAL_PAYMENTS=1 \u2014 your other config looks fine, but your MCP client isn't passing the registry default through. Add the line Q402_ENABLE_REAL_PAYMENTS=1 to ~/.q402/mcp.env explicitly and restart."
|
|
2092
2224
|
);
|
|
@@ -2128,6 +2260,34 @@ async function runDoctor() {
|
|
|
2128
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."
|
|
2129
2261
|
);
|
|
2130
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
|
+
};
|
|
2131
2291
|
if (phase !== "live-check") {
|
|
2132
2292
|
return {
|
|
2133
2293
|
package: PACKAGE_NAME,
|
|
@@ -2136,17 +2296,19 @@ async function runDoctor() {
|
|
|
2136
2296
|
ready: false,
|
|
2137
2297
|
envFile,
|
|
2138
2298
|
envState,
|
|
2299
|
+
walletModePicker,
|
|
2139
2300
|
missing,
|
|
2140
2301
|
warnings,
|
|
2141
2302
|
recommendedActions,
|
|
2142
2303
|
greeting: phase === "first-install" ? `Q402 MCP is installed (v${PACKAGE_VERSION}).` : `Q402 MCP is installed (v${PACKAGE_VERSION}) \u2014 partially configured.`,
|
|
2143
2304
|
nextStep: phase === "first-install" ? "Show userInstructions verbatim to the user; do NOT show agentInstructions verbatim (it's prescription for you, the AI)." : "Tell the user which env vars are still missing (from the 'missing' list) and how to add them to ~/.q402/mcp.env. Show userInstructions for the human-readable steps.",
|
|
2144
|
-
agentInstructions: phase === "first-install" ? "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Multi-turn flow: (1) Briefly tell the user MCP is installed. (2) Ask one yes/no question \u2014 'Want me to create your Q402 settings file?'. (3) On yes, execute recommendedActions IN ORDER: first `ensure-q402-dir` shell action (bash on macOS/Linux, PowerShell on Windows via shellWindows variant), then `create-env-file` write_file action. (4) Open the file in the user's editor \u2014 `code` works for VS Code / Cursor / Cline (e.g. `code ~/.q402/mcp.env`); `open` on macOS, `start` on Windows, `xdg-open` on Linux as fallback. (5)
|
|
2305
|
+
agentInstructions: phase === "first-install" ? "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] Multi-turn flow: (1) Briefly tell the user MCP is installed. (2) Ask one yes/no question \u2014 'Want me to create your Q402 settings file?'. (3) On yes, execute recommendedActions IN ORDER: first `ensure-q402-dir` shell action (bash on macOS/Linux, PowerShell on Windows via shellWindows variant), then `create-env-file` write_file action. (4) Open the file in the user's editor \u2014 `code` works for VS Code / Cursor / Cline (e.g. `code ~/.q402/mcp.env`); `open` on macOS, `start` on Windows, `xdg-open` on Linux as fallback. (5) Help the user pick a wallet mode (A=Q402_PRIVATE_KEY real EOA, B=Q402_AGENTIC_PRIVATE_KEY exported Agent Wallet PK, C=API key only with server-managed Agent Wallet). If they're an AI agent / automation user, gently default to B or C; if they're a power user who wants their existing EOA to be the signer, A is fine. Walk through filling in the chosen mode's variable + the API key one at a time. (6) Do NOT accept key values via chat \u2014 direct the user to edit the file in their editor. BEFORE they paste any private key (Mode A OR Mode B), surface the `advisories` array: fresh wallet, Smart-account-in-MetaMask heads-up (Mode A only), hardware wallets unsupported, MetaMask key-export path (Mode A) or dashboard Export button (Mode B). (7) After they save, tell them to restart the MCP client \u2014 per-client restart verb: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window. (8) Have them re-invoke 'Set up Q402' to confirm. Keep the conversation tight: one decision per turn, plain language, never echo this paragraph." : "[AI-ONLY \u2014 do not show this paragraph to the user verbatim] User has SOME env set. List the missing items (from `missing`) in plain language. Tell them to edit ~/.q402/mcp.env and uncomment / fill the relevant line, then restart the MCP client. Restart verb per client: Claude Desktop \u2192 quit + relaunch; Codex \u2192 exit + relaunch; Cursor \u2192 Cmd/Ctrl+Shift+P \u2192 'Developer: Reload Window'; Cline \u2192 reload VS Code window.",
|
|
2145
2306
|
userInstructions: phase === "first-install" ? [
|
|
2146
|
-
"Q402 is installed. To start sending payments you need an API key and a wallet.",
|
|
2307
|
+
"Q402 is installed. To start sending payments you need (1) an API key and (2) a wallet to sign with.",
|
|
2147
2308
|
"I'll create a settings file for you \u2014 say yes and I'll set it up + open it in your editor.",
|
|
2148
2309
|
"Get a free API key at https://q402.quackai.ai/event (BNB Chain only, 2,000 sponsored transactions).",
|
|
2149
|
-
"
|
|
2310
|
+
"There are 3 wallet modes \u2014 pick one: (A) your MetaMask EOA's private key (simplest, but your account will be marked 'Smart account' after first payment); (B) export an Agent Wallet's private key from the dashboard (keeps your MetaMask untouched, recommended for AI agents); (C) on a paid plan, use the server-managed Agent Wallet \u2014 just set the API key, no private key needed.",
|
|
2311
|
+
"Use a FRESH wallet for Mode A \u2014 don't use the one holding your main funds. The 'Smart account' marker is normal (EIP-7702 delegation, reversible via q402_clear_delegation).",
|
|
2150
2312
|
"Paste your key + wallet private key INTO THE FILE (in your editor) \u2014 never paste a private key into this chat.",
|
|
2151
2313
|
"Save the file, restart your MCP client, then ask me 'Verify Q402' to confirm."
|
|
2152
2314
|
] : [
|
|
@@ -2167,13 +2329,15 @@ async function runDoctor() {
|
|
|
2167
2329
|
}
|
|
2168
2330
|
let walletAddress;
|
|
2169
2331
|
let walletError;
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
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
|
+
}
|
|
2177
2341
|
}
|
|
2178
2342
|
const verifyTargets = [];
|
|
2179
2343
|
if (CONFIG.trialApiKey) verifyTargets.push({ scope: "trial", envVar: "Q402_TRIAL_API_KEY", key: CONFIG.trialApiKey });
|
|
@@ -2220,6 +2384,38 @@ async function runDoctor() {
|
|
|
2220
2384
|
);
|
|
2221
2385
|
}
|
|
2222
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
|
+
};
|
|
2223
2419
|
return {
|
|
2224
2420
|
package: PACKAGE_NAME,
|
|
2225
2421
|
version: PACKAGE_VERSION,
|
|
@@ -2227,6 +2423,7 @@ async function runDoctor() {
|
|
|
2227
2423
|
ready,
|
|
2228
2424
|
envFile,
|
|
2229
2425
|
envState,
|
|
2426
|
+
walletModes,
|
|
2230
2427
|
missing,
|
|
2231
2428
|
wallet: walletAddress ? { address: walletAddress } : void 0,
|
|
2232
2429
|
keys,
|
|
@@ -2268,7 +2465,7 @@ var DOCTOR_TOOL = {
|
|
|
2268
2465
|
import { z as z9 } from "zod";
|
|
2269
2466
|
var AgenticInfoInputSchema = z9.object({
|
|
2270
2467
|
walletId: z9.string().optional().describe(
|
|
2271
|
-
"Optional lowercased Agent Wallet address to introspect when the user holds multiple (max 10 per owner). Omit to use
|
|
2468
|
+
"Optional lowercased Agent Wallet address to introspect when the user holds multiple (max 10 per owner). Omit to use Q402_AGENT_WALLET_ADDRESS env, then the owner's default wallet."
|
|
2272
2469
|
)
|
|
2273
2470
|
});
|
|
2274
2471
|
var AGENTIC_INFO_TOOL = {
|
|
@@ -2279,7 +2476,7 @@ var AGENTIC_INFO_TOOL = {
|
|
|
2279
2476
|
properties: {
|
|
2280
2477
|
walletId: {
|
|
2281
2478
|
type: "string",
|
|
2282
|
-
description: "Optional. Lowercased Agent Wallet address when the user holds multiple wallets. Defaults to
|
|
2479
|
+
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."
|
|
2283
2480
|
}
|
|
2284
2481
|
},
|
|
2285
2482
|
additionalProperties: false
|
|
@@ -2372,6 +2569,298 @@ async function runAgenticInfo(input = {}) {
|
|
|
2372
2569
|
};
|
|
2373
2570
|
}
|
|
2374
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
|
+
|
|
2375
2864
|
// src/index.ts
|
|
2376
2865
|
function jsonText(value) {
|
|
2377
2866
|
return { type: "text", text: JSON.stringify(value, null, 2) };
|
|
@@ -2393,6 +2882,9 @@ async function main() {
|
|
|
2393
2882
|
RECEIPT_TOOL,
|
|
2394
2883
|
WALLET_STATUS_TOOL,
|
|
2395
2884
|
AGENTIC_INFO_TOOL,
|
|
2885
|
+
RECURRING_LIST_TOOL,
|
|
2886
|
+
RECURRING_CREATE_TOOL,
|
|
2887
|
+
RECURRING_CANCEL_TOOL,
|
|
2396
2888
|
CLEAR_DELEGATION_TOOL
|
|
2397
2889
|
]
|
|
2398
2890
|
}));
|
|
@@ -2436,6 +2928,18 @@ async function main() {
|
|
|
2436
2928
|
AgenticInfoInputSchema.parse(args ?? {});
|
|
2437
2929
|
return { content: [jsonText(await runAgenticInfo())] };
|
|
2438
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
|
+
}
|
|
2439
2943
|
default:
|
|
2440
2944
|
return {
|
|
2441
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": [
|