@quackai/q402-mcp 0.5.11 → 0.5.12
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 +2 -2
- package/dist/index.js +87 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ The agent calls `q402_doctor`. On first install, the tool tells the agent to:
|
|
|
49
49
|
|
|
50
50
|
### Manual setup (no AI)
|
|
51
51
|
|
|
52
|
-
Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes — every secret line is commented out and `Q402_ENABLE_REAL_PAYMENTS` defaults to `
|
|
52
|
+
Create `~/.q402/mcp.env` yourself. The template below matches what `q402_doctor` writes — every secret line is commented out and `Q402_ENABLE_REAL_PAYMENTS` defaults to `1`. Uncomment the lines you need and paste real values; the server only flips into live mode once both a `q402_live_*` API key AND a valid 32-byte private key are configured, so saving the template as-is is safe (placeholders stay in sandbox). Change the flag to `0` if you want to force sandbox even with real keys (e.g. for chained testing).
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
55
|
# ~/.q402/mcp.env
|
|
@@ -149,7 +149,7 @@ Then export the values in `~/.zshrc` / `~/.bashrc`. See the [Codex config refere
|
|
|
149
149
|
|
|
150
150
|
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.
|
|
151
151
|
|
|
152
|
-
To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set to a valid 32-byte hex key, and `Q402_ENABLE_REAL_PAYMENTS=1`. The block below is the template `q402_doctor` writes to `~/.q402/mcp.env` — every secret line is commented out and the live flag defaults to `
|
|
152
|
+
To enable real on-chain transactions, the resolved API key must be live (`q402_live_*`), `Q402_PRIVATE_KEY` must be set to a valid 32-byte hex key, and `Q402_ENABLE_REAL_PAYMENTS=1`. The block below is the template `q402_doctor` writes to `~/.q402/mcp.env` — every secret line is commented out and the live flag defaults to `1`. Uncomment the lines you need and paste real values in your editor; the live-mode gate only flips once a real key + valid PK are present, so saving the template as-is stays in sandbox. Change the flag to `0` if you want to force sandbox even with real keys (e.g. for chained testing on a paid plan):
|
|
153
153
|
|
|
154
154
|
```bash
|
|
155
155
|
# Two-key model — uncomment ONE (or both for auto-routing).
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,7 @@ function loadQ402EnvFileFromPath(path) {
|
|
|
39
39
|
);
|
|
40
40
|
return {};
|
|
41
41
|
}
|
|
42
|
+
if (raw.charCodeAt(0) === 65279) raw = raw.slice(1);
|
|
42
43
|
for (const line of raw.split(/\r?\n/)) {
|
|
43
44
|
const t = line.trim();
|
|
44
45
|
if (!t || t.startsWith("#")) continue;
|
|
@@ -168,7 +169,7 @@ var isValidPrivateKey = (s) => typeof s === "string" && PRIVATE_KEY_RE.test(s);
|
|
|
168
169
|
|
|
169
170
|
// src/version.ts
|
|
170
171
|
var PACKAGE_NAME = "@quackai/q402-mcp";
|
|
171
|
-
var PACKAGE_VERSION = "0.5.
|
|
172
|
+
var PACKAGE_VERSION = "0.5.12";
|
|
172
173
|
|
|
173
174
|
// src/tools/quote.ts
|
|
174
175
|
import { z } from "zod";
|
|
@@ -421,7 +422,7 @@ var QUOTE_TOOL = {
|
|
|
421
422
|
};
|
|
422
423
|
|
|
423
424
|
// src/tools/pay.ts
|
|
424
|
-
import { isAddress as isAddress2 } from "ethers";
|
|
425
|
+
import { isAddress as isAddress2, Wallet as Wallet2 } from "ethers";
|
|
425
426
|
import { z as z2 } from "zod";
|
|
426
427
|
|
|
427
428
|
// src/client.ts
|
|
@@ -810,6 +811,17 @@ async function runPay(input) {
|
|
|
810
811
|
);
|
|
811
812
|
}
|
|
812
813
|
const guardsApplied = [];
|
|
814
|
+
let senderWallet;
|
|
815
|
+
if (CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
|
|
816
|
+
try {
|
|
817
|
+
const addr = new Wallet2(CONFIG.privateKey).address;
|
|
818
|
+
senderWallet = {
|
|
819
|
+
address: addr,
|
|
820
|
+
addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
|
|
821
|
+
};
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
}
|
|
813
825
|
maxAmountGuard(input.amount, CONFIG.maxAmountPerCallUsd);
|
|
814
826
|
guardsApplied.push(`max_amount<=${CONFIG.maxAmountPerCallUsd}`);
|
|
815
827
|
recipientGuard(input.to, CONFIG.allowedRecipients);
|
|
@@ -828,7 +840,7 @@ async function runPay(input) {
|
|
|
828
840
|
});
|
|
829
841
|
guardsApplied.push("mode=sandbox");
|
|
830
842
|
const setupHint = resolved.sandboxReason ?? describeSandboxReason(resolved.apiKey ?? "", resolved.scope);
|
|
831
|
-
return { result: result2, guardsApplied, setupHint };
|
|
843
|
+
return { result: result2, guardsApplied, setupHint, senderWallet };
|
|
832
844
|
}
|
|
833
845
|
const client = new Q402NodeClient({
|
|
834
846
|
apiKey: resolved.apiKey,
|
|
@@ -845,13 +857,20 @@ async function runPay(input) {
|
|
|
845
857
|
return {
|
|
846
858
|
result,
|
|
847
859
|
guardsApplied,
|
|
860
|
+
senderWallet,
|
|
848
861
|
postPaymentTip: result.success ? `After this payment your EOA is EIP-7702-delegated to Q402's impl on ${chain.name} \u2014 MetaMask / OKX will show it as a 'Smart account'. That's normal and reversible: q402_clear_delegation removes the delegation on a specific chain (Q402 sponsors the gas, so you pay $0). If you ever try to receive native gas tokens directly to this EOA and the transfer reverts, the delegation is the cause \u2014 clear it for that chain first.` : void 0
|
|
849
862
|
};
|
|
850
863
|
}
|
|
851
864
|
function describeSandboxReason(resolvedKey, scope) {
|
|
852
865
|
const missing = [];
|
|
853
866
|
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
854
|
-
if (!CONFIG.privateKey)
|
|
867
|
+
if (!CONFIG.privateKey) {
|
|
868
|
+
missing.push("Q402_PRIVATE_KEY");
|
|
869
|
+
} else if (!isValidPrivateKey(CONFIG.privateKey)) {
|
|
870
|
+
missing.push(
|
|
871
|
+
"Q402_PRIVATE_KEY (currently the placeholder '0x...' \u2014 paste a real 0x + 64-hex key into ~/.q402/mcp.env)"
|
|
872
|
+
);
|
|
873
|
+
}
|
|
855
874
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
856
875
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
857
876
|
const tier = scope === "trial" ? "Free Trial" : "Multichain";
|
|
@@ -860,7 +879,7 @@ function describeSandboxReason(resolvedKey, scope) {
|
|
|
860
879
|
}
|
|
861
880
|
var PAY_TOOL = {
|
|
862
881
|
name: "q402_pay",
|
|
863
|
-
description: "Send a gasless USDC, USDT, or RLUSD payment via Q402. Auto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 9-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
|
|
882
|
+
description: "Send a gasless USDC, USDT, or RLUSD payment via Q402. Auto-routing: chain='bnb' + Q402_TRIAL_API_KEY set \u2192 Trial (free sponsored); anything else \u2192 Multichain (paid 9-chain). Same rule for q402_batch_pay. Set keyScope='trial' or 'multichain' to force one explicitly. Trial keys reject any non-BNB chain server-side with TRIAL_BNB_ONLY. Multichain keys cover avax, bnb, eth, xlayer, stable, mantle, injective, monad, scroll \u2014 USDC/USDT on most chains, RLUSD on Ethereum only, Injective USDT-only. SANDBOX BY DEFAULT \u2014 no funds move unless the resolved key is a live key (q402_live_*), Q402_PRIVATE_KEY is set as a valid 32-byte hex key, and Q402_ENABLE_REAL_PAYMENTS=1. Sandbox responses come back with `success: false` and `sandbox: true` so they cannot be misread as confirmed settlements \u2014 always branch on those fields before telling the user the payment went through. The recipient receives the full amount; the sender pays $0 in gas. \n\nSENDER ECHO \u2014 every response includes a `senderWallet` field with the address derived from the configured `Q402_PRIVATE_KEY`. Show this alongside the recipient/amount when you confirm the payment with the user (e.g. 'Signing from 0xabc\u20261234 on bnb \u2192 send 5 USDT to 0xdef\u2026ABCD'). Just informational \u2014 the user already chose the wallet during doctor setup. \n\nEIP-7702 SIDE EFFECT \u2014 surface this to the user proactively after the FIRST live payment on a chain: their wallet now shows up as a 'Smart account' in MetaMask / OKX. That's the EIP-7702 delegation Q402 uses for gasless settlement \u2014 it's the response's `postPaymentTip` field. Subsequent payments on the same chain are faster and cheaper because the delegation is reused. \n\nIf the user EVER reports that native gas tokens (BNB / ETH / AVAX / etc.) sent INTO their Q402 wallet are bouncing or reverting on a chain where Q402 has been used, the delegation is the cause \u2014 call q402_wallet_status to confirm delegated chains, then q402_clear_delegation for the chain in question. Q402 sponsors the gas for the clear, so the user pays $0. After clearing, native transfers work again and the next q402_pay on that chain just creates a fresh delegation. \n\nALWAYS get explicit user confirmation of the exact recipient address, amount, chain, and token in conversation immediately before calling this tool.",
|
|
864
883
|
inputSchema: {
|
|
865
884
|
type: "object",
|
|
866
885
|
properties: {
|
|
@@ -899,7 +918,7 @@ var PAY_TOOL = {
|
|
|
899
918
|
};
|
|
900
919
|
|
|
901
920
|
// src/tools/batch-pay.ts
|
|
902
|
-
import { isAddress as isAddress3 } from "ethers";
|
|
921
|
+
import { isAddress as isAddress3, Wallet as Wallet3 } from "ethers";
|
|
903
922
|
import { z as z3 } from "zod";
|
|
904
923
|
var RECIPIENT_LIMIT_TRIAL = 5;
|
|
905
924
|
var RECIPIENT_LIMIT_PAID = 20;
|
|
@@ -964,6 +983,17 @@ async function runBatchPay(input) {
|
|
|
964
983
|
if (CONFIG.allowedRecipients.length > 0) {
|
|
965
984
|
guardsApplied.push(`recipient_allowlist[${CONFIG.allowedRecipients.length}]`);
|
|
966
985
|
}
|
|
986
|
+
let senderWallet;
|
|
987
|
+
if (CONFIG.privateKey && isValidPrivateKey(CONFIG.privateKey)) {
|
|
988
|
+
try {
|
|
989
|
+
const addr = new Wallet3(CONFIG.privateKey).address;
|
|
990
|
+
senderWallet = {
|
|
991
|
+
address: addr,
|
|
992
|
+
addressShort: `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`
|
|
993
|
+
};
|
|
994
|
+
} catch {
|
|
995
|
+
}
|
|
996
|
+
}
|
|
967
997
|
const scopeRequest = input.keyScope ?? "auto";
|
|
968
998
|
if (scopeRequest === "auto" && input.chain === "bnb" && CONFIG.trialApiKey && input.recipients.length > RECIPIENT_LIMIT_TRIAL) {
|
|
969
999
|
const overflow = input.recipients.length - RECIPIENT_LIMIT_TRIAL;
|
|
@@ -972,6 +1002,7 @@ async function runBatchPay(input) {
|
|
|
972
1002
|
mode: "none",
|
|
973
1003
|
status: "ambiguous",
|
|
974
1004
|
guardsApplied,
|
|
1005
|
+
senderWallet,
|
|
975
1006
|
setupHint: `Batch of ${input.recipients.length} on BNB exceeds the Trial cap of ${RECIPIENT_LIMIT_TRIAL}. Ask the user to pick one and re-invoke q402_batch_pay with explicit keyScope:
|
|
976
1007
|
\u2022 keyScope="trial" \u2014 keep only the first ${RECIPIENT_LIMIT_TRIAL} recipients (free, sponsored). Drop the remaining ${overflow}.
|
|
977
1008
|
\u2022 keyScope="multichain" \u2014 send all ${input.recipients.length} on the paid Multichain key (charges the paid pool + Gas Tank).
|
|
@@ -991,6 +1022,7 @@ async function runBatchPay(input) {
|
|
|
991
1022
|
mode: "sandbox",
|
|
992
1023
|
status: "sandbox",
|
|
993
1024
|
result: { sandbox: sandboxResults, reason },
|
|
1025
|
+
senderWallet,
|
|
994
1026
|
guardsApplied,
|
|
995
1027
|
setupHint: reason
|
|
996
1028
|
};
|
|
@@ -1009,7 +1041,7 @@ async function runBatchPay(input) {
|
|
|
1009
1041
|
guardsApplied.push("mode=live");
|
|
1010
1042
|
guardsApplied.push(`scope=${result.scope} (server enforced)`);
|
|
1011
1043
|
guardsApplied.push(`batch_size=${input.recipients.length}/${result.limit}`);
|
|
1012
|
-
return { mode: "live", status: "success", result, guardsApplied };
|
|
1044
|
+
return { mode: "live", status: "success", result, guardsApplied, senderWallet };
|
|
1013
1045
|
} catch (err) {
|
|
1014
1046
|
if (err instanceof BatchPayError) {
|
|
1015
1047
|
guardsApplied.push("mode=live");
|
|
@@ -1029,6 +1061,7 @@ async function runBatchPay(input) {
|
|
|
1029
1061
|
results: err.results
|
|
1030
1062
|
},
|
|
1031
1063
|
guardsApplied,
|
|
1064
|
+
senderWallet,
|
|
1032
1065
|
error: err.message
|
|
1033
1066
|
};
|
|
1034
1067
|
}
|
|
@@ -1038,7 +1071,13 @@ async function runBatchPay(input) {
|
|
|
1038
1071
|
function describeSandboxReason2(resolvedKey, scope) {
|
|
1039
1072
|
const missing = [];
|
|
1040
1073
|
if (!resolvedKey.startsWith("q402_live_")) missing.push("a live API key (must start with q402_live_)");
|
|
1041
|
-
if (!CONFIG.privateKey)
|
|
1074
|
+
if (!CONFIG.privateKey) {
|
|
1075
|
+
missing.push("Q402_PRIVATE_KEY");
|
|
1076
|
+
} else if (!isValidPrivateKey(CONFIG.privateKey)) {
|
|
1077
|
+
missing.push(
|
|
1078
|
+
"Q402_PRIVATE_KEY (currently the placeholder '0x...' \u2014 paste a real 0x + 64-hex key into ~/.q402/mcp.env)"
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1042
1081
|
if (!CONFIG.realPaymentsRequested) missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
1043
1082
|
if (missing.length === 0) return "Sandbox mode active (no env state change needed).";
|
|
1044
1083
|
const tier = scope === "trial" ? "Free Trial" : "Multichain";
|
|
@@ -1285,7 +1324,9 @@ async function runReceipt(input) {
|
|
|
1285
1324
|
notFound: true
|
|
1286
1325
|
};
|
|
1287
1326
|
}
|
|
1288
|
-
const resp = await fetch(`${apiBase}/receipt/${receiptId}
|
|
1327
|
+
const resp = await fetch(`${apiBase}/receipt/${receiptId}`, {
|
|
1328
|
+
signal: AbortSignal.timeout(1e4)
|
|
1329
|
+
});
|
|
1289
1330
|
if (resp.status === 404) {
|
|
1290
1331
|
return {
|
|
1291
1332
|
receiptId,
|
|
@@ -1338,7 +1379,7 @@ var RECEIPT_TOOL = {
|
|
|
1338
1379
|
|
|
1339
1380
|
// src/tools/wallet-status.ts
|
|
1340
1381
|
import { z as z6 } from "zod";
|
|
1341
|
-
import { Wallet as
|
|
1382
|
+
import { Wallet as Wallet4 } from "ethers";
|
|
1342
1383
|
var WalletStatusInputSchema = z6.object({});
|
|
1343
1384
|
async function runWalletStatus() {
|
|
1344
1385
|
if (!CONFIG.privateKey) {
|
|
@@ -1349,7 +1390,7 @@ async function runWalletStatus() {
|
|
|
1349
1390
|
}
|
|
1350
1391
|
let address;
|
|
1351
1392
|
try {
|
|
1352
|
-
address = new
|
|
1393
|
+
address = new Wallet4(CONFIG.privateKey).address;
|
|
1353
1394
|
} catch {
|
|
1354
1395
|
return {
|
|
1355
1396
|
error: "INVALID_PRIVATE_KEY",
|
|
@@ -1360,7 +1401,7 @@ async function runWalletStatus() {
|
|
|
1360
1401
|
let body;
|
|
1361
1402
|
let res;
|
|
1362
1403
|
try {
|
|
1363
|
-
res = await fetch(url);
|
|
1404
|
+
res = await fetch(url, { signal: AbortSignal.timeout(1e4) });
|
|
1364
1405
|
body = await res.json();
|
|
1365
1406
|
} catch (e) {
|
|
1366
1407
|
return {
|
|
@@ -1397,7 +1438,7 @@ var WALLET_STATUS_TOOL = {
|
|
|
1397
1438
|
|
|
1398
1439
|
// src/tools/clear-delegation.ts
|
|
1399
1440
|
import { z as z7 } from "zod";
|
|
1400
|
-
import { Wallet as
|
|
1441
|
+
import { Wallet as Wallet5, JsonRpcProvider as JsonRpcProvider2 } from "ethers";
|
|
1401
1442
|
var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
|
|
1402
1443
|
var DEFAULT_RPC2 = {
|
|
1403
1444
|
1: "https://ethereum.publicnode.com",
|
|
@@ -1428,7 +1469,7 @@ async function runClearDelegation(input) {
|
|
|
1428
1469
|
let wallet;
|
|
1429
1470
|
try {
|
|
1430
1471
|
const provider = new JsonRpcProvider2(DEFAULT_RPC2[cfg.chainId]);
|
|
1431
|
-
wallet = new
|
|
1472
|
+
wallet = new Wallet5(CONFIG.privateKey, provider);
|
|
1432
1473
|
} catch {
|
|
1433
1474
|
return {
|
|
1434
1475
|
ok: false,
|
|
@@ -1482,7 +1523,8 @@ async function runClearDelegation(input) {
|
|
|
1482
1523
|
res = await fetch(url, {
|
|
1483
1524
|
method: "POST",
|
|
1484
1525
|
headers: { "Content-Type": "application/json" },
|
|
1485
|
-
body: JSON.stringify(body)
|
|
1526
|
+
body: JSON.stringify(body),
|
|
1527
|
+
signal: AbortSignal.timeout(3e4)
|
|
1486
1528
|
});
|
|
1487
1529
|
json = await res.json();
|
|
1488
1530
|
} catch (e) {
|
|
@@ -1538,7 +1580,7 @@ var CLEAR_DELEGATION_TOOL = {
|
|
|
1538
1580
|
|
|
1539
1581
|
// src/tools/doctor.ts
|
|
1540
1582
|
import { z as z8 } from "zod";
|
|
1541
|
-
import { Wallet as
|
|
1583
|
+
import { Wallet as Wallet6 } from "ethers";
|
|
1542
1584
|
var DoctorInputSchema = z8.object({});
|
|
1543
1585
|
var ENV_FILE_TEMPLATE = `# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1544
1586
|
# Q402 MCP \u2014 secrets
|
|
@@ -1584,10 +1626,10 @@ Q402_RELAY_BASE_URL=https://q402.quackai.ai/api
|
|
|
1584
1626
|
`;
|
|
1585
1627
|
var SECURITY_NOTICE = "Q402 never asks you to paste your private key into chat. The MCP server signs payments LOCALLY on your machine \u2014 your key never leaves your device, never goes to a remote server. If a key was already pasted in chat by mistake, treat the wallet as exposed: move funds to a fresh wallet and use that new key in ~/.q402/mcp.env going forward.";
|
|
1586
1628
|
var FIRST_INSTALL_ADVISORY = [
|
|
1587
|
-
|
|
1588
|
-
"After your first payment, that wallet will show 'Smart account' in MetaMask / OKX. That's EIP-7702 delegation (Q402's gasless settlement mechanism), reversible anytime
|
|
1589
|
-
"Hardware wallets (Ledger / Trezor)
|
|
1590
|
-
"To
|
|
1629
|
+
`Tip: a separate MetaMask account dedicated to Q402 keeps your existing balances and history tidy \u2014 it's a quick "+ Add account" in MetaMask. Q402 works with any EOA you control, though.`,
|
|
1630
|
+
"After your first payment, that wallet will show 'Smart account' in MetaMask / OKX. That's EIP-7702 delegation (Q402's gasless settlement mechanism), reversible anytime via q402_clear_delegation.",
|
|
1631
|
+
"Hardware wallets (Ledger / Trezor) can't sign EIP-7702 type-4 authorizations yet, so they're not supported in Q402 today \u2014 a hot wallet works.",
|
|
1632
|
+
"To export the key in MetaMask: open the account menu \u2192 Account details \u2192 Show private key. Paste the 0x... string into ~/.q402/mcp.env in your editor (never into chat)."
|
|
1591
1633
|
];
|
|
1592
1634
|
function envSource(name) {
|
|
1593
1635
|
if (process.env[name] !== void 0) return "process";
|
|
@@ -1739,7 +1781,17 @@ async function runDoctor() {
|
|
|
1739
1781
|
"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."
|
|
1740
1782
|
);
|
|
1741
1783
|
}
|
|
1742
|
-
if (!CONFIG.realPaymentsRequested)
|
|
1784
|
+
if (!CONFIG.realPaymentsRequested) {
|
|
1785
|
+
const haveAnyApi = !!(CONFIG.trialApiKey || CONFIG.multichainApiKey || CONFIG.legacyApiKey);
|
|
1786
|
+
const havePk = isValidPrivateKey(CONFIG.privateKey);
|
|
1787
|
+
if (haveAnyApi && havePk) {
|
|
1788
|
+
missing.push(
|
|
1789
|
+
"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."
|
|
1790
|
+
);
|
|
1791
|
+
} else {
|
|
1792
|
+
missing.push("Q402_ENABLE_REAL_PAYMENTS=1");
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1743
1795
|
const recommendedActions = [];
|
|
1744
1796
|
if (!envFile.exists) {
|
|
1745
1797
|
recommendedActions.push({
|
|
@@ -1796,13 +1848,19 @@ async function runDoctor() {
|
|
|
1796
1848
|
"Then ask me 'Verify Q402' to re-check."
|
|
1797
1849
|
],
|
|
1798
1850
|
securityNotice: SECURITY_NOTICE,
|
|
1799
|
-
|
|
1851
|
+
// Advisories are useful in BOTH first-install and needs-completion:
|
|
1852
|
+
// a user who already pasted an API key but hasn't yet added their
|
|
1853
|
+
// private key is exactly the audience that needs the "MetaMask
|
|
1854
|
+
// export path" + "Smart-account-is-normal" heads-up. Suppressing
|
|
1855
|
+
// them once any env was set (the pre-0.5.12 behaviour) left a
|
|
1856
|
+
// gap right at the moment they were most useful.
|
|
1857
|
+
advisories: FIRST_INSTALL_ADVISORY
|
|
1800
1858
|
};
|
|
1801
1859
|
}
|
|
1802
1860
|
let walletAddress;
|
|
1803
1861
|
let walletError;
|
|
1804
1862
|
try {
|
|
1805
|
-
walletAddress = new
|
|
1863
|
+
walletAddress = new Wallet6(CONFIG.privateKey).address;
|
|
1806
1864
|
} catch (e) {
|
|
1807
1865
|
walletError = e instanceof Error ? e.message : String(e);
|
|
1808
1866
|
warnings.push(
|
|
@@ -1863,16 +1921,18 @@ async function runDoctor() {
|
|
|
1863
1921
|
userInstructions: ready ? [
|
|
1864
1922
|
`Your wallet: ${walletAddress ? walletAddress.slice(0, 6) + "\u2026" + walletAddress.slice(-4) : "(derive failed \u2014 check Q402_PRIVATE_KEY)"}`,
|
|
1865
1923
|
"Q402 is live. You can now ask me to quote, pay, batch-pay, or check Trust Receipts.",
|
|
1866
|
-
"Want me to run a quick gas comparison across all 9 chains as a smoke test?"
|
|
1924
|
+
"Want me to run a quick gas comparison across all 9 chains as a smoke test?",
|
|
1925
|
+
"Need to chain-test against sandbox without changing keys? Set Q402_ENABLE_REAL_PAYMENTS=0 in ~/.q402/mcp.env and restart \u2014 every q402_pay returns a fake hash until you flip it back to 1."
|
|
1867
1926
|
] : [
|
|
1868
1927
|
`Q402 has ${warnings.length} issue${warnings.length === 1 ? "" : "s"} to fix:`,
|
|
1869
1928
|
...warnings.map((w) => `\u2022 ${w}`),
|
|
1870
1929
|
"Open ~/.q402/mcp.env, fix the lines above, save, then restart your MCP client (Cursor/Cline: Cmd/Ctrl+Shift+P \u2192 Reload Window; Claude/Codex: quit + relaunch). Then ask me 'Verify Q402' to re-check."
|
|
1871
1930
|
],
|
|
1872
1931
|
securityNotice: SECURITY_NOTICE,
|
|
1873
|
-
// advisories
|
|
1874
|
-
//
|
|
1875
|
-
|
|
1932
|
+
// Carry advisories through live-check too — even a fully-configured
|
|
1933
|
+
// user benefits from the "Smart-account in MetaMask is normal" line
|
|
1934
|
+
// appearing alongside their first ready state.
|
|
1935
|
+
advisories: FIRST_INSTALL_ADVISORY
|
|
1876
1936
|
};
|
|
1877
1937
|
}
|
|
1878
1938
|
var DOCTOR_TOOL = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quackai/q402-mcp",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.12",
|
|
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": [
|