@aibtc/mcp-server 1.65.0 → 1.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { createApiClient, API_URL, probeEndpoint, formatPaymentAmount, checkSufficientBalance, generateDedupKey, checkDedupCache, recordTransaction, NETWORK } from "../services/x402.service.js";
2
+ import { createApiClient, API_URL, probeEndpoint, formatPaymentAmount, checkSufficientBalance, generateDedupKey, checkDedupCache, recordTransaction, X402_DEDUP_TTL_MS, NETWORK } from "../services/x402.service.js";
3
3
  import { ALL_ENDPOINTS, searchEndpoints, formatEndpointsTable, getEndpointsBySource, getCategories, } from "../endpoints/registry.js";
4
4
  import { createJsonResponse, createErrorResponse } from "../utils/index.js";
5
5
  import { X402_HEADERS } from "../utils/x402-protocol.js";
@@ -270,6 +270,9 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
270
270
  },
271
271
  }, async ({ method, url, path, apiUrl, params, data, autoApprove, asset }) => {
272
272
  let fullUrl = "";
273
+ // Hoisted so the catch can record a broadcast payment for dedup — a
274
+ // failed settlement still spends real funds (#630).
275
+ let dedupKey = null;
273
276
  try {
274
277
  const parsed = parseEndpointUrl({ url, path, apiUrl, params });
275
278
  fullUrl = parsed.fullUrl;
@@ -281,14 +284,27 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
281
284
  // autoApprove=true: check dedup cache before any network request, then execute
282
285
  // with a single request. Balance validation happens inside the onBeforePayment
283
286
  // callback when the interceptor receives the 402, eliminating the separate probe.
284
- const dedupKey = generateDedupKey(method, fullUrl, params, data);
285
- const existingTxid = checkDedupCache(dedupKey);
287
+ dedupKey = generateDedupKey(method, fullUrl, params, data);
288
+ const existingTxid = await checkDedupCache(dedupKey);
286
289
  if (existingTxid) {
290
+ const windowSeconds = Math.round(X402_DEDUP_TTL_MS / 1000);
291
+ const pending = existingTxid.startsWith("pending:");
287
292
  return createJsonResponse({
288
293
  endpoint: `${method} ${fullUrl}`,
289
- message: 'Request already processed within the last 60 seconds. This prevents accidental duplicate payments.',
290
- txid: existingTxid,
291
- note: 'Wait 60s or use different endpoint/params to force a new transaction.',
294
+ // Deliberately says "broadcast", not "paid": the entry is written
295
+ // whenever a payment was signed and broadcast, including when
296
+ // settlement then reported an error. Claiming it succeeded would
297
+ // be wrong in exactly the cases the caller most needs to check.
298
+ message: `An identical request already broadcast a payment within the last ${windowSeconds} seconds. This prevents accidental duplicate payments.`,
299
+ // A prior attempt whose txid was never observable is recorded with a
300
+ // synthetic marker. Report it as null rather than leaking the marker,
301
+ // which is not a chain txid and must not be treated as one.
302
+ txid: pending ? null : existingTxid,
303
+ ...(pending && {
304
+ txidNote: "The earlier payment was broadcast but its txid was not observable in the response. " +
305
+ "Query get_account_transactions to find the settled txid before paying again.",
306
+ }),
307
+ note: `The earlier payment may have settled even if it reported an error — verify it before retrying. Wait ${windowSeconds}s or vary the endpoint/params to force a new transaction.`,
292
308
  });
293
309
  }
294
310
  const api = await createApiClient(parsed.baseUrl, {
@@ -321,7 +337,7 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
321
337
  // observable, using a synthetic pending marker that cannot be
322
338
  // confused for a real chain txid.
323
339
  if (paymentAttempted) {
324
- recordTransaction(dedupKey, txid ?? `pending:${dedupKey}`);
340
+ await recordTransaction(dedupKey, txid ?? `pending:${dedupKey}`);
325
341
  }
326
342
  // Build the txid response fields per the behavior matrix:
327
343
  // payment attempted + observable txid → { txid: "0x..." }
@@ -352,9 +368,20 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
352
368
  // interceptor in x402.service.ts) and return it so the agent can verify.
353
369
  const axiosError = error;
354
370
  const canonicalStatus = axiosError.x402PaymentStatus;
371
+ // The presence of this header means the interceptor signed and
372
+ // broadcast a payment. That spends real funds whether or not
373
+ // settlement succeeded, so record it for dedup before returning any
374
+ // error — otherwise an identical retry passes the dedup guard and pays
375
+ // a second time for an invoice already settled on chain (#630).
376
+ // Mirrors the success path, including the synthetic pending marker
377
+ // used when the txid is not yet observable.
378
+ const paymentSigHeader = axiosError.config?.headers?.[X402_HEADERS.PAYMENT_SIGNATURE];
379
+ if (paymentSigHeader && dedupKey) {
380
+ const broadcastTxid = extractTxidFromPaymentSignature(paymentSigHeader);
381
+ await recordTransaction(dedupKey, broadcastTxid ?? `pending:${dedupKey}`);
382
+ }
355
383
  if (canonicalStatus) {
356
384
  const baseError = formatEndpointError(error, label);
357
- const paymentSigHeader = axiosError.config?.headers?.[X402_HEADERS.PAYMENT_SIGNATURE];
358
385
  const fallbackTxid = paymentSigHeader
359
386
  ? extractTxidFromPaymentSignature(paymentSigHeader)
360
387
  : null;
@@ -382,7 +409,6 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
382
409
  content: [{ type: "text", text }],
383
410
  };
384
411
  }
385
- const paymentSigHeader = axiosError.config?.headers?.[X402_HEADERS.PAYMENT_SIGNATURE];
386
412
  if (paymentSigHeader) {
387
413
  const txid = extractTxidFromPaymentSignature(paymentSigHeader);
388
414
  const fallbackPaymentId = extractPaymentIdFromPaymentSignature(paymentSigHeader);
@@ -36,6 +36,7 @@ import { registerJingswapTools } from "./jingswap.tools.js";
36
36
  import { registerSigningTools } from "./signing.tools.js";
37
37
  import { registerInferenceMarketplaceTools } from "./inference-marketplace.tools.js";
38
38
  import { registerNewsTools } from "./news.tools.js";
39
+ import { registerLegionTools } from "./legion.tools.js";
39
40
  import { registerIdentityTools } from "./identity.tools.js";
40
41
  import { registerCredentialsTools } from "./credentials.tools.js";
41
42
  import { registerSouldinalsTools } from "./souldinals.tools.js";
@@ -167,6 +168,8 @@ export function registerAllTools(server) {
167
168
  registerInferenceMarketplaceTools(server);
168
169
  // AIBTC News (signal feed, leaderboard, file signals)
169
170
  registerNewsTools(server);
171
+ // AIBTC News Legion (news-gov-v5 governance — inscribe, propose, vote, veto, conclude)
172
+ registerLegionTools(server);
170
173
  // Identity (ERC-8004 on-chain agent identity management)
171
174
  registerIdentityTools(server);
172
175
  // Credentials (encrypted credential store — list, get, set, delete, unlock)
@@ -0,0 +1,16 @@
1
+ /**
2
+ * AIBTC News Legion tools (news-gov-v5).
3
+ *
4
+ * Lifecycle: inscribe → reveal → propose → vote → veto → conclude, plus
5
+ * contribute (buys weight) and sponsor (does not).
6
+ *
7
+ * These tools pin their own network: `getLegionAccount()` re-derives the
8
+ * unlocked wallet for the legion's chain, so a mainnet-configured server signs
9
+ * against testnet. Inscription is the exception — real BTC on whatever chain
10
+ * NETWORK names.
11
+ *
12
+ * Fund-moving calls sign in DENY mode with an exact post-condition.
13
+ */
14
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
+ export declare function registerLegionTools(server: McpServer): void;
16
+ //# sourceMappingURL=legion.tools.d.ts.map