@aibtc/mcp-server 1.57.0 → 1.59.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.
Files changed (260) hide show
  1. package/README.md +63 -15
  2. package/dist/index.js +150 -47
  3. package/dist/services/credentials.js +7 -2
  4. package/dist/services/defi.service.js +23 -11
  5. package/dist/services/hiro-api.d.ts +10 -0
  6. package/dist/services/hiro-api.js +30 -8
  7. package/dist/services/nft.service.js +14 -5
  8. package/dist/services/pillar-api.service.d.ts +8 -0
  9. package/dist/services/pillar-api.service.js +13 -1
  10. package/dist/services/x402.service.js +45 -13
  11. package/dist/tools/bitcoin.tools.js +14 -0
  12. package/dist/tools/endpoint.tools.js +3 -1
  13. package/dist/tools/inbox.tools.js +9 -12
  14. package/dist/tools/news.tools.js +9 -12
  15. package/dist/tools/pillar-direct.tools.js +29 -22
  16. package/dist/tools/sbtc.tools.js +2 -9
  17. package/dist/tools/scaffold.tools.js +9 -14
  18. package/dist/tools/settings.tools.js +2 -2
  19. package/dist/tools/stacking-lottery.tools.js +8 -5
  20. package/dist/tools/yield-dashboard.tools.js +10 -14
  21. package/dist/utils/encryption.js +2 -0
  22. package/package.json +10 -5
  23. package/skill/SKILL.md +14 -3
  24. package/skill/references/genesis-lifecycle.md +105 -92
  25. package/dist/config/bitcoin-constants.d.ts.map +0 -1
  26. package/dist/config/bitcoin-constants.js.map +0 -1
  27. package/dist/config/caip.d.ts.map +0 -1
  28. package/dist/config/caip.js.map +0 -1
  29. package/dist/config/competition.d.ts.map +0 -1
  30. package/dist/config/competition.js.map +0 -1
  31. package/dist/config/contracts.d.ts.map +0 -1
  32. package/dist/config/contracts.js.map +0 -1
  33. package/dist/config/index.d.ts.map +0 -1
  34. package/dist/config/index.js.map +0 -1
  35. package/dist/config/networks.d.ts.map +0 -1
  36. package/dist/config/networks.js.map +0 -1
  37. package/dist/config/pillar.d.ts.map +0 -1
  38. package/dist/config/pillar.js.map +0 -1
  39. package/dist/config/sponsor.d.ts.map +0 -1
  40. package/dist/config/sponsor.js.map +0 -1
  41. package/dist/endpoints/index.d.ts.map +0 -1
  42. package/dist/endpoints/index.js.map +0 -1
  43. package/dist/endpoints/registry.d.ts.map +0 -1
  44. package/dist/endpoints/registry.js.map +0 -1
  45. package/dist/index.d.ts.map +0 -1
  46. package/dist/index.js.map +0 -1
  47. package/dist/services/bitflow.service.d.ts.map +0 -1
  48. package/dist/services/bitflow.service.js.map +0 -1
  49. package/dist/services/bns.service.d.ts.map +0 -1
  50. package/dist/services/bns.service.js.map +0 -1
  51. package/dist/services/credentials.d.ts.map +0 -1
  52. package/dist/services/credentials.js.map +0 -1
  53. package/dist/services/defi.service.d.ts.map +0 -1
  54. package/dist/services/defi.service.js.map +0 -1
  55. package/dist/services/erc8004.service.d.ts.map +0 -1
  56. package/dist/services/erc8004.service.js.map +0 -1
  57. package/dist/services/hiro-api.d.ts.map +0 -1
  58. package/dist/services/hiro-api.js.map +0 -1
  59. package/dist/services/inscription-parser.d.ts.map +0 -1
  60. package/dist/services/inscription-parser.js.map +0 -1
  61. package/dist/services/lightning/provider.d.ts.map +0 -1
  62. package/dist/services/lightning/provider.js.map +0 -1
  63. package/dist/services/lightning/spark-provider.d.ts.map +0 -1
  64. package/dist/services/lightning/spark-provider.js.map +0 -1
  65. package/dist/services/lightning-manager.d.ts.map +0 -1
  66. package/dist/services/lightning-manager.js.map +0 -1
  67. package/dist/services/mempool-api.d.ts.map +0 -1
  68. package/dist/services/mempool-api.js.map +0 -1
  69. package/dist/services/nft.service.d.ts.map +0 -1
  70. package/dist/services/nft.service.js.map +0 -1
  71. package/dist/services/nonce-tracker.d.ts.map +0 -1
  72. package/dist/services/nonce-tracker.js.map +0 -1
  73. package/dist/services/pillar-api.service.d.ts.map +0 -1
  74. package/dist/services/pillar-api.service.js.map +0 -1
  75. package/dist/services/sbtc-deposit.service.d.ts.map +0 -1
  76. package/dist/services/sbtc-deposit.service.js.map +0 -1
  77. package/dist/services/sbtc.service.d.ts.map +0 -1
  78. package/dist/services/sbtc.service.js.map +0 -1
  79. package/dist/services/scaffold.service.d.ts.map +0 -1
  80. package/dist/services/scaffold.service.js.map +0 -1
  81. package/dist/services/signing-key.service.d.ts.map +0 -1
  82. package/dist/services/signing-key.service.js.map +0 -1
  83. package/dist/services/stacking.service.d.ts.map +0 -1
  84. package/dist/services/stacking.service.js.map +0 -1
  85. package/dist/services/tenero-api.d.ts.map +0 -1
  86. package/dist/services/tenero-api.js.map +0 -1
  87. package/dist/services/tokens.service.d.ts.map +0 -1
  88. package/dist/services/tokens.service.js.map +0 -1
  89. package/dist/services/unisat-indexer.d.ts.map +0 -1
  90. package/dist/services/unisat-indexer.js.map +0 -1
  91. package/dist/services/wallet-manager.d.ts.map +0 -1
  92. package/dist/services/wallet-manager.js.map +0 -1
  93. package/dist/services/x402.service.d.ts.map +0 -1
  94. package/dist/services/x402.service.js.map +0 -1
  95. package/dist/tools/arxiv-research.tools.d.ts.map +0 -1
  96. package/dist/tools/arxiv-research.tools.js.map +0 -1
  97. package/dist/tools/bitcoin.tools.d.ts.map +0 -1
  98. package/dist/tools/bitcoin.tools.js.map +0 -1
  99. package/dist/tools/bitflow.tools.d.ts.map +0 -1
  100. package/dist/tools/bitflow.tools.js.map +0 -1
  101. package/dist/tools/bns.tools.d.ts.map +0 -1
  102. package/dist/tools/bns.tools.js.map +0 -1
  103. package/dist/tools/bounty-scanner.tools.d.ts.map +0 -1
  104. package/dist/tools/bounty-scanner.tools.js.map +0 -1
  105. package/dist/tools/child-inscription.tools.d.ts.map +0 -1
  106. package/dist/tools/child-inscription.tools.js.map +0 -1
  107. package/dist/tools/competition.tools.d.ts.map +0 -1
  108. package/dist/tools/competition.tools.js.map +0 -1
  109. package/dist/tools/contract.tools.d.ts.map +0 -1
  110. package/dist/tools/contract.tools.js.map +0 -1
  111. package/dist/tools/credentials.tools.d.ts.map +0 -1
  112. package/dist/tools/credentials.tools.js.map +0 -1
  113. package/dist/tools/defi.tools.d.ts.map +0 -1
  114. package/dist/tools/defi.tools.js.map +0 -1
  115. package/dist/tools/dual-stacking.tools.d.ts.map +0 -1
  116. package/dist/tools/dual-stacking.tools.js.map +0 -1
  117. package/dist/tools/earning.tools.d.ts.map +0 -1
  118. package/dist/tools/earning.tools.js.map +0 -1
  119. package/dist/tools/endpoint.tools.d.ts.map +0 -1
  120. package/dist/tools/endpoint.tools.js.map +0 -1
  121. package/dist/tools/erc8004.tools.d.ts.map +0 -1
  122. package/dist/tools/erc8004.tools.js.map +0 -1
  123. package/dist/tools/identity.tools.d.ts.map +0 -1
  124. package/dist/tools/identity.tools.js.map +0 -1
  125. package/dist/tools/inbox-x402.tools.d.ts.map +0 -1
  126. package/dist/tools/inbox-x402.tools.js.map +0 -1
  127. package/dist/tools/inbox.tools.d.ts.map +0 -1
  128. package/dist/tools/inbox.tools.js.map +0 -1
  129. package/dist/tools/index.d.ts.map +0 -1
  130. package/dist/tools/index.js.map +0 -1
  131. package/dist/tools/jingswap.tools.d.ts.map +0 -1
  132. package/dist/tools/jingswap.tools.js.map +0 -1
  133. package/dist/tools/lightning.tools.d.ts.map +0 -1
  134. package/dist/tools/lightning.tools.js.map +0 -1
  135. package/dist/tools/mempool.tools.d.ts.map +0 -1
  136. package/dist/tools/mempool.tools.js.map +0 -1
  137. package/dist/tools/news.tools.d.ts.map +0 -1
  138. package/dist/tools/news.tools.js.map +0 -1
  139. package/dist/tools/nft.tools.d.ts.map +0 -1
  140. package/dist/tools/nft.tools.js.map +0 -1
  141. package/dist/tools/nonce.tools.d.ts.map +0 -1
  142. package/dist/tools/nonce.tools.js.map +0 -1
  143. package/dist/tools/nostr.tools.d.ts.map +0 -1
  144. package/dist/tools/nostr.tools.js.map +0 -1
  145. package/dist/tools/openrouter.tools.d.ts.map +0 -1
  146. package/dist/tools/openrouter.tools.js.map +0 -1
  147. package/dist/tools/ordinals-marketplace.tools.d.ts.map +0 -1
  148. package/dist/tools/ordinals-marketplace.tools.js.map +0 -1
  149. package/dist/tools/ordinals-p2p.tools.d.ts.map +0 -1
  150. package/dist/tools/ordinals-p2p.tools.js.map +0 -1
  151. package/dist/tools/ordinals.tools.d.ts.map +0 -1
  152. package/dist/tools/ordinals.tools.js.map +0 -1
  153. package/dist/tools/pillar-direct.tools.d.ts.map +0 -1
  154. package/dist/tools/pillar-direct.tools.js.map +0 -1
  155. package/dist/tools/pillar.tools.d.ts.map +0 -1
  156. package/dist/tools/pillar.tools.js.map +0 -1
  157. package/dist/tools/psbt.helpers.d.ts.map +0 -1
  158. package/dist/tools/psbt.helpers.js.map +0 -1
  159. package/dist/tools/psbt.tools.d.ts.map +0 -1
  160. package/dist/tools/psbt.tools.js.map +0 -1
  161. package/dist/tools/query.tools.d.ts.map +0 -1
  162. package/dist/tools/query.tools.js.map +0 -1
  163. package/dist/tools/relay-diagnostic.tools.d.ts.map +0 -1
  164. package/dist/tools/relay-diagnostic.tools.js.map +0 -1
  165. package/dist/tools/reputation.tools.d.ts.map +0 -1
  166. package/dist/tools/reputation.tools.js.map +0 -1
  167. package/dist/tools/runes.tools.d.ts.map +0 -1
  168. package/dist/tools/runes.tools.js.map +0 -1
  169. package/dist/tools/sbtc.tools.d.ts.map +0 -1
  170. package/dist/tools/sbtc.tools.js.map +0 -1
  171. package/dist/tools/scaffold.tools.d.ts.map +0 -1
  172. package/dist/tools/scaffold.tools.js.map +0 -1
  173. package/dist/tools/schemas.d.ts.map +0 -1
  174. package/dist/tools/schemas.js.map +0 -1
  175. package/dist/tools/settings.tools.d.ts.map +0 -1
  176. package/dist/tools/settings.tools.js.map +0 -1
  177. package/dist/tools/signing.tools.d.ts.map +0 -1
  178. package/dist/tools/signing.tools.js.map +0 -1
  179. package/dist/tools/skill-mappings.d.ts.map +0 -1
  180. package/dist/tools/skill-mappings.js.map +0 -1
  181. package/dist/tools/souldinals.tools.d.ts.map +0 -1
  182. package/dist/tools/souldinals.tools.js.map +0 -1
  183. package/dist/tools/stacking-lottery.tools.d.ts.map +0 -1
  184. package/dist/tools/stacking-lottery.tools.js.map +0 -1
  185. package/dist/tools/stacking.tools.d.ts.map +0 -1
  186. package/dist/tools/stacking.tools.js.map +0 -1
  187. package/dist/tools/stacks-market.tools.d.ts.map +0 -1
  188. package/dist/tools/stacks-market.tools.js.map +0 -1
  189. package/dist/tools/styx.tools.d.ts.map +0 -1
  190. package/dist/tools/styx.tools.js.map +0 -1
  191. package/dist/tools/taproot-multisig.tools.d.ts.map +0 -1
  192. package/dist/tools/taproot-multisig.tools.js.map +0 -1
  193. package/dist/tools/tenero.tools.d.ts.map +0 -1
  194. package/dist/tools/tenero.tools.js.map +0 -1
  195. package/dist/tools/tokens.tools.d.ts.map +0 -1
  196. package/dist/tools/tokens.tools.js.map +0 -1
  197. package/dist/tools/transfer.tools.d.ts.map +0 -1
  198. package/dist/tools/transfer.tools.js.map +0 -1
  199. package/dist/tools/wallet-management.tools.d.ts.map +0 -1
  200. package/dist/tools/wallet-management.tools.js.map +0 -1
  201. package/dist/tools/wallet.tools.d.ts.map +0 -1
  202. package/dist/tools/wallet.tools.js.map +0 -1
  203. package/dist/tools/yield-dashboard.tools.d.ts.map +0 -1
  204. package/dist/tools/yield-dashboard.tools.js.map +0 -1
  205. package/dist/tools/yield-hunter.tools.d.ts.map +0 -1
  206. package/dist/tools/yield-hunter.tools.js.map +0 -1
  207. package/dist/transactions/bitcoin-builder.d.ts.map +0 -1
  208. package/dist/transactions/bitcoin-builder.js.map +0 -1
  209. package/dist/transactions/builder.d.ts.map +0 -1
  210. package/dist/transactions/builder.js.map +0 -1
  211. package/dist/transactions/child-inscription-builder.d.ts.map +0 -1
  212. package/dist/transactions/child-inscription-builder.js.map +0 -1
  213. package/dist/transactions/clarity-values.d.ts.map +0 -1
  214. package/dist/transactions/clarity-values.js.map +0 -1
  215. package/dist/transactions/inscription-builder.d.ts.map +0 -1
  216. package/dist/transactions/inscription-builder.js.map +0 -1
  217. package/dist/transactions/post-conditions.d.ts.map +0 -1
  218. package/dist/transactions/post-conditions.js.map +0 -1
  219. package/dist/transactions/rune-transfer-builder.d.ts.map +0 -1
  220. package/dist/transactions/rune-transfer-builder.js.map +0 -1
  221. package/dist/transactions/runestone-builder.d.ts.map +0 -1
  222. package/dist/transactions/runestone-builder.js.map +0 -1
  223. package/dist/transactions/sponsor-builder.d.ts.map +0 -1
  224. package/dist/transactions/sponsor-builder.js.map +0 -1
  225. package/dist/utils/bip322.d.ts.map +0 -1
  226. package/dist/utils/bip322.js.map +0 -1
  227. package/dist/utils/bitcoin.d.ts.map +0 -1
  228. package/dist/utils/bitcoin.js.map +0 -1
  229. package/dist/utils/encryption.d.ts.map +0 -1
  230. package/dist/utils/encryption.js.map +0 -1
  231. package/dist/utils/erc8004-helpers.d.ts.map +0 -1
  232. package/dist/utils/erc8004-helpers.js.map +0 -1
  233. package/dist/utils/errors.d.ts.map +0 -1
  234. package/dist/utils/errors.js.map +0 -1
  235. package/dist/utils/fee.d.ts.map +0 -1
  236. package/dist/utils/fee.js.map +0 -1
  237. package/dist/utils/formatting.d.ts.map +0 -1
  238. package/dist/utils/formatting.js.map +0 -1
  239. package/dist/utils/index.d.ts.map +0 -1
  240. package/dist/utils/index.js.map +0 -1
  241. package/dist/utils/l402-protocol.d.ts.map +0 -1
  242. package/dist/utils/l402-protocol.js.map +0 -1
  243. package/dist/utils/redact.d.ts.map +0 -1
  244. package/dist/utils/redact.js.map +0 -1
  245. package/dist/utils/relay-health.d.ts.map +0 -1
  246. package/dist/utils/relay-health.js.map +0 -1
  247. package/dist/utils/storage.d.ts.map +0 -1
  248. package/dist/utils/storage.js.map +0 -1
  249. package/dist/utils/tokens.d.ts.map +0 -1
  250. package/dist/utils/tokens.js.map +0 -1
  251. package/dist/utils/x402-payment-logging.d.ts.map +0 -1
  252. package/dist/utils/x402-payment-logging.js.map +0 -1
  253. package/dist/utils/x402-payment-state.d.ts.map +0 -1
  254. package/dist/utils/x402-payment-state.js.map +0 -1
  255. package/dist/utils/x402-protocol.d.ts.map +0 -1
  256. package/dist/utils/x402-protocol.js.map +0 -1
  257. package/dist/utils/x402-recovery.d.ts.map +0 -1
  258. package/dist/utils/x402-recovery.js.map +0 -1
  259. package/dist/yield-hunter/index.d.ts.map +0 -1
  260. package/dist/yield-hunter/index.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import axios from "axios";
2
- import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, PostConditionMode, } from "@stacks/transactions";
2
+ import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, Pc, PostConditionMode, } from "@stacks/transactions";
3
3
  import { decodePaymentRequired, encodePaymentPayload, generatePaymentId, buildPaymentIdentifierExtension, X402_HEADERS, } from "../utils/x402-protocol.js";
4
4
  import { generateWallet, getStxAddress } from "@stacks/wallet-sdk";
5
5
  import { NETWORK, API_URL, getStacksNetwork } from "../config/networks.js";
@@ -11,6 +11,7 @@ import { getHiroApi } from "./hiro-api.js";
11
11
  import { createHash } from "crypto";
12
12
  import { InsufficientBalanceError } from "../utils/errors.js";
13
13
  import { getContracts, parseContractId } from "../config/contracts.js";
14
+ import { resolveDefaultFee } from "../utils/fee.js";
14
15
  import { classifyCanonicalPaymentStatus, formatCanonicalPaymentStatus, resolveCanonicalPaymentStatus, } from "../utils/x402-payment-state.js";
15
16
  import { extractPaymentIdFromPaymentSignature } from "../utils/x402-recovery.js";
16
17
  import { emitCanonicalPaymentDecisionLogs, emitCanonicalPaymentPollLogs, emitPaymentLog, } from "../utils/x402-payment-logging.js";
@@ -42,6 +43,16 @@ function parseSatsCap(envName, fallback) {
42
43
  return parsed;
43
44
  }
44
45
  const L402_MAX_SATS_PER_INVOICE = parseSatsCap("L402_MAX_SATS_PER_INVOICE", DEFAULT_L402_MAX_SATS);
46
+ /**
47
+ * Per-payment caps for the Stacks x402 interceptor, mirroring the L402 cap
48
+ * above: a malicious endpoint can demand an arbitrary amount in its 402
49
+ * response and an autoApprove'd call would pay it, bounded only by wallet
50
+ * balance. Known registry endpoints cost at most 0.02 STX / 100 sats, so the
51
+ * defaults leave generous headroom. Overridable via env vars.
52
+ */
53
+ const X402_MAX_USTX_PER_PAYMENT = parseSatsCap("X402_MAX_USTX_PER_PAYMENT", 1_000_000 // 1 STX
54
+ );
55
+ const X402_MAX_SATS_PER_PAYMENT = parseSatsCap("X402_MAX_SATS_PER_PAYMENT", 10_000);
45
56
  // Track payment attempts per client instance (auto-cleanup via WeakMap)
46
57
  const paymentAttempts = new WeakMap();
47
58
  // Track 429 retry counts per client instance
@@ -457,6 +468,16 @@ export async function createApiClient(baseUrl, options) {
457
468
  const networks = paymentRequired.accepts.map((a) => a.network).join(", ");
458
469
  return Promise.reject(new Error(`No compatible Stacks payment option found. Available networks: ${networks}`));
459
470
  }
471
+ const tokenType = detectTokenType(selectedOption.asset);
472
+ const amount = BigInt(selectedOption.amount);
473
+ const isSbtc = tokenType === "sBTC";
474
+ const cap = isSbtc ? X402_MAX_SATS_PER_PAYMENT : X402_MAX_USTX_PER_PAYMENT;
475
+ const unit = isSbtc ? "sats" : "uSTX";
476
+ if (amount > BigInt(Math.floor(cap))) {
477
+ return Promise.reject(new Error(`x402 payment of ${amount} ${unit} exceeds the per-payment cap of ${cap} ${unit}. ` +
478
+ `If this cost is expected, raise the cap via the ` +
479
+ `${isSbtc ? "X402_MAX_SATS_PER_PAYMENT" : "X402_MAX_USTX_PER_PAYMENT"} env var.`));
480
+ }
460
481
  // Lazy-load account on first 402 — free endpoints never reach here
461
482
  const acct = await ensureAccount();
462
483
  // Verify the payment network matches our configured network
@@ -476,9 +497,11 @@ export async function createApiClient(baseUrl, options) {
476
497
  account: acct,
477
498
  });
478
499
  }
479
- // Build a sponsored signed transaction (relay pays gas; fee: 0n)
480
- const tokenType = detectTokenType(selectedOption.asset);
481
- const amount = BigInt(selectedOption.amount);
500
+ // Build a non-sponsored signed transaction: the sender pays its own
501
+ // STX gas (fee + nonce auto-estimated by @stacks), and the x402
502
+ // facilitator simply verifies and broadcasts it. No relay sponsorship
503
+ // — this is the same tx shape the x402-stacks lib produces, and the
504
+ // balance check in onBeforePayment already accounts for the gas.
482
505
  const networkName = getStacksNetwork(acct.network);
483
506
  let transaction;
484
507
  if (tokenType === "sBTC") {
@@ -496,9 +519,18 @@ export async function createApiClient(baseUrl, options) {
496
519
  ],
497
520
  senderKey: acct.privateKey,
498
521
  network: networkName,
499
- postConditionMode: PostConditionMode.Allow,
500
- sponsored: true,
501
- fee: 0n,
522
+ // The payment amount is known at build time, so pin it: a
523
+ // compromised token contract must not be able to pull more.
524
+ postConditionMode: PostConditionMode.Deny,
525
+ postConditions: [
526
+ Pc.principal(acct.address)
527
+ .willSendEq(amount)
528
+ .ft(contracts.SBTC_TOKEN, "sbtc-token"),
529
+ ],
530
+ // Clamped medium fee — do NOT let @stacks auto-estimate, since
531
+ // Hiro's contract_call high_priority tier is polluted by outliers
532
+ // (observed >2000 STX). resolveDefaultFee caps it at 0.05 STX.
533
+ fee: await resolveDefaultFee(acct.network, "contract_call"),
502
534
  });
503
535
  }
504
536
  else {
@@ -508,8 +540,7 @@ export async function createApiClient(baseUrl, options) {
508
540
  senderKey: acct.privateKey,
509
541
  network: networkName,
510
542
  memo: "",
511
- sponsored: true,
512
- fee: 0n,
543
+ fee: await resolveDefaultFee(acct.network, "token_transfer"),
513
544
  });
514
545
  }
515
546
  const txHex = "0x" + transaction.serialize();
@@ -716,8 +747,9 @@ export async function checkSufficientBalance(account, amount, asset, sponsored =
716
747
  const hiroApiForSbtc = getHiroApi(account.network);
717
748
  const stxInfoForSbtc = await hiroApiForSbtc.getStxBalance(account.address);
718
749
  const stxBalanceForSbtc = BigInt(stxInfoForSbtc.balance);
719
- const sbtcFees = await hiroApiForSbtc.getMempoolFees();
720
- const estimatedSbtcFee = BigInt(sbtcFees.contract_call.high_priority);
750
+ // Same clamped fee the interceptor actually sets on the sBTC contract
751
+ // call — so this check is exact, not a high-priority over-estimate.
752
+ const estimatedSbtcFee = await resolveDefaultFee(account.network, "contract_call");
721
753
  if (stxBalanceForSbtc < estimatedSbtcFee) {
722
754
  const stxShortfall = estimatedSbtcFee - stxBalanceForSbtc;
723
755
  throw new InsufficientBalanceError(`Insufficient STX balance to cover sBTC transfer fee: need ${formatStx(estimatedSbtcFee.toString())} estimated fee, ` +
@@ -733,8 +765,8 @@ export async function checkSufficientBalance(account, amount, asset, sponsored =
733
765
  const balance = BigInt(balanceInfo.balance);
734
766
  let totalRequired = requiredAmount;
735
767
  if (!sponsored) {
736
- const mempoolFees = await hiroApi.getMempoolFees();
737
- const estimatedFee = BigInt(mempoolFees.contract_call.high_priority);
768
+ // Same clamped fee the interceptor sets on the STX transfer.
769
+ const estimatedFee = await resolveDefaultFee(account.network, "token_transfer");
738
770
  totalRequired = requiredAmount + estimatedFee;
739
771
  }
740
772
  if (balance >= totalRequired)
@@ -33,6 +33,19 @@ async function getBtcAddress(providedAddress) {
33
33
  throw new Error("No Bitcoin address provided and wallet is not unlocked. " +
34
34
  "Either provide an address or unlock your wallet first.");
35
35
  }
36
+ /**
37
+ * Reject recipient addresses that belong to the other Bitcoin network before
38
+ * any UTXO fetching. The tx builder (@scure/btc-signer) rejects these too, but
39
+ * late and with a cryptic bech32/version-byte error.
40
+ */
41
+ function validateRecipientNetwork(address) {
42
+ const prefixes = NETWORK === "mainnet" ? ["bc1", "1", "3"] : ["tb1", "m", "n", "2"];
43
+ if (!prefixes.some((p) => address.startsWith(p))) {
44
+ throw new Error(`Recipient "${address}" is not a ${NETWORK} address. ` +
45
+ `Expected a prefix of ${prefixes.join(", ")} — check that the address ` +
46
+ `matches the configured NETWORK.`);
47
+ }
48
+ }
36
49
  /**
37
50
  * Format satoshis as BTC string
38
51
  */
@@ -231,6 +244,7 @@ export function registerBitcoinTools(server) {
231
244
  },
232
245
  }, async ({ recipient, amount, feeRate, includeOrdinals }) => {
233
246
  try {
247
+ validateRecipientNetwork(recipient);
234
248
  // Get wallet account (requires unlocked wallet)
235
249
  const walletManager = getWalletManager();
236
250
  const account = walletManager.getActiveAccount();
@@ -276,7 +276,9 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
276
276
  const api = await createApiClient(parsed.baseUrl, {
277
277
  toolName: "execute_x402_endpoint",
278
278
  onBeforePayment: async (requirements) => {
279
- await checkSufficientBalance(requirements.account, requirements.amount, requirements.asset, true);
279
+ // Non-sponsored: sender pays its own gas, so validate STX for the
280
+ // fee too (sponsored=false is the default, passed explicitly here).
281
+ await checkSufficientBalance(requirements.account, requirements.amount, requirements.asset, false);
280
282
  },
281
283
  });
282
284
  const response = await api.request({ method, url: parsed.requestPath, params, data });
@@ -40,21 +40,18 @@ async function getNextNonce(address) {
40
40
  const hiroApi = getHiroApi(NETWORK);
41
41
  const accountInfo = await hiroApi.getAccountInfo(address);
42
42
  const confirmedNonce = accountInfo.nonce;
43
+ // The mempool scan is what makes the nonce safe against pending txs —
44
+ // silently skipping it on failure risks a nonce collision, so let it fail.
43
45
  let highestMempoolNonce = -1;
44
- try {
45
- const mempool = await hiroApi.getMempoolTransactions({
46
- sender_address: address,
47
- limit: 50,
48
- });
49
- for (const tx of mempool.results) {
50
- if (tx.nonce > highestMempoolNonce) {
51
- highestMempoolNonce = tx.nonce;
52
- }
46
+ const mempool = await hiroApi.getMempoolTransactions({
47
+ sender_address: address,
48
+ limit: 50,
49
+ });
50
+ for (const tx of mempool.results) {
51
+ if (tx.nonce > highestMempoolNonce) {
52
+ highestMempoolNonce = tx.nonce;
53
53
  }
54
54
  }
55
- catch {
56
- // Non-fatal: fall back to confirmed nonce only
57
- }
58
55
  const chainNext = Math.max(confirmedNonce, highestMempoolNonce + 1);
59
56
  // 3. Reconcile tracker with chain state
60
57
  await reconcileWithChain(address, chainNext);
@@ -56,21 +56,18 @@ async function getNextNonce(address) {
56
56
  const hiroApi = getHiroApi(NETWORK);
57
57
  const accountInfo = await hiroApi.getAccountInfo(address);
58
58
  const confirmedNonce = accountInfo.nonce;
59
+ // The mempool scan is what makes the nonce safe against pending txs —
60
+ // silently skipping it on failure risks a nonce collision, so let it fail.
59
61
  let highestMempoolNonce = -1;
60
- try {
61
- const mempool = await hiroApi.getMempoolTransactions({
62
- sender_address: address,
63
- limit: 50,
64
- });
65
- for (const tx of mempool.results) {
66
- if (tx.nonce > highestMempoolNonce) {
67
- highestMempoolNonce = tx.nonce;
68
- }
62
+ const mempool = await hiroApi.getMempoolTransactions({
63
+ sender_address: address,
64
+ limit: 50,
65
+ });
66
+ for (const tx of mempool.results) {
67
+ if (tx.nonce > highestMempoolNonce) {
68
+ highestMempoolNonce = tx.nonce;
69
69
  }
70
70
  }
71
- catch {
72
- // Non-fatal
73
- }
74
71
  const chainNext = Math.max(confirmedNonce, highestMempoolNonce + 1);
75
72
  await reconcileWithChain(address, chainNext);
76
73
  return Math.max(chainNext, localNext ?? 0);
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import crypto from "crypto";
3
3
  import { tupleCV, stringAsciiCV, uintCV, principalCV, noneCV, trueCV, falseCV, } from "@stacks/transactions";
4
4
  import { getSigningKeyService, generateAuthId, } from "../services/signing-key.service.js";
5
- import { getPillarApi } from "../services/pillar-api.service.js";
5
+ import { getPillarApi, PillarApiError } from "../services/pillar-api.service.js";
6
6
  import { getHiroApi } from "../services/hiro-api.js";
7
7
  import { NETWORK } from "../config/networks.js";
8
8
  import { PILLAR_API_KEY } from "../config/pillar.js";
@@ -497,8 +497,12 @@ export function registerPillarDirectTools(server) {
497
497
  const walletInfo = await api.get(`/api/smart-wallet/${walletName}`);
498
498
  walletStatus = walletInfo.data?.status || null;
499
499
  }
500
- catch {
501
- // Wallet not found in backend
500
+ catch (error) {
501
+ // 404 = wallet not registered in backend. Anything else (network,
502
+ // 5xx) must surface, or an initialized wallet gets misreported.
503
+ if (!(error instanceof PillarApiError && error.status === 404)) {
504
+ throw error;
505
+ }
502
506
  }
503
507
  if (!walletStatus || walletStatus === "pending_init") {
504
508
  return createJsonResponse({
@@ -554,8 +558,11 @@ export function registerPillarDirectTools(server) {
554
558
  });
555
559
  position = unwindQuote.data;
556
560
  }
557
- catch {
558
- // unwind-quote may fail for fresh wallets with no position
561
+ catch (error) {
562
+ // 4xx = fresh wallet with no position. Infra failures must surface.
563
+ if (!(error instanceof PillarApiError && error.status < 500)) {
564
+ throw error;
565
+ }
559
566
  }
560
567
  return createJsonResponse({
561
568
  success: true,
@@ -1146,21 +1153,16 @@ export function registerPillarDirectTools(server) {
1146
1153
  const frac = (micro % BigInt(1_000_000)).toString().padStart(6, "0");
1147
1154
  return `${whole}.${frac} STX`;
1148
1155
  };
1149
- // Fetch PoX cycle info
1150
- let poxInfo = null;
1151
- try {
1152
- const pox = await hiro.getPoxInfo();
1153
- poxInfo = {
1154
- currentCycleId: pox.current_cycle.id,
1155
- nextCycleId: pox.next_cycle.id,
1156
- blocksUntilNextCycle: pox.next_cycle.blocks_until_reward_phase,
1157
- minAmountUstx: pox.min_amount_ustx,
1158
- isPoxActive: pox.current_cycle.is_pox_active,
1159
- };
1160
- }
1161
- catch {
1162
- // PoX info fetch failed, continue without it
1163
- }
1156
+ // Fetch PoX cycle info — a global endpoint with no "not found" case,
1157
+ // so any failure is infrastructure and should surface.
1158
+ const pox = await hiro.getPoxInfo();
1159
+ const poxInfo = {
1160
+ currentCycleId: pox.current_cycle.id,
1161
+ nextCycleId: pox.next_cycle.id,
1162
+ blocksUntilNextCycle: pox.next_cycle.blocks_until_reward_phase,
1163
+ minAmountUstx: pox.min_amount_ustx,
1164
+ isPoxActive: pox.current_cycle.is_pox_active,
1165
+ };
1164
1166
  // Check enrollment status via backend
1165
1167
  const api = getPillarApi();
1166
1168
  const contractParts = session.smartWallet.split(".");
@@ -1175,8 +1177,13 @@ export function registerPillarDirectTools(server) {
1175
1177
  };
1176
1178
  }
1177
1179
  }
1178
- catch {
1179
- // Backend lookup failed, continue without enrollment info
1180
+ catch (error) {
1181
+ // 404 = wallet not registered, so not enrolled. Other failures
1182
+ // must surface — defaulting to enrolled:false on a backend outage
1183
+ // misreports an enrolled wallet and could prompt re-enrollment.
1184
+ if (!(error instanceof PillarApiError && error.status === 404)) {
1185
+ throw error;
1186
+ }
1180
1187
  }
1181
1188
  const isStacking = lockedMicro > BigInt(0);
1182
1189
  return createJsonResponse({
@@ -283,16 +283,9 @@ Signers later process the request and send BTC on L1.`,
283
283
  description: "Get information about how to deposit BTC to receive sBTC.",
284
284
  }, async () => {
285
285
  try {
286
- // Try to get wallet account (don't throw if not unlocked)
286
+ // getActiveAccount returns null when locked - generic instructions then
287
287
  const walletManager = getWalletManager();
288
- let account;
289
- try {
290
- account = walletManager.getActiveAccount();
291
- }
292
- catch {
293
- // Wallet not unlocked - return generic instructions
294
- account = null;
295
- }
288
+ const account = walletManager.getActiveAccount();
296
289
  // If wallet is unlocked and has Taproot keys, generate real deposit address
297
290
  if (account?.taprootPublicKey) {
298
291
  const depositService = getSbtcDepositService(NETWORK);
@@ -8,20 +8,15 @@ import { getWalletAddress } from "../services/x402.service.js";
8
8
  * Check if a directory contains an existing x402 project
9
9
  */
10
10
  async function isExistingX402Project(dir) {
11
- try {
12
- const indexPath = path.join(dir, "src", "index.ts");
13
- const middlewarePath = path.join(dir, "src", "x402-middleware.ts");
14
- const packagePath = path.join(dir, "package.json");
15
- const [hasIndex, hasMiddleware, hasPackage] = await Promise.all([
16
- fs.access(indexPath).then(() => true).catch(() => false),
17
- fs.access(middlewarePath).then(() => true).catch(() => false),
18
- fs.access(packagePath).then(() => true).catch(() => false),
19
- ]);
20
- return hasIndex && hasMiddleware && hasPackage;
21
- }
22
- catch {
23
- return false;
24
- }
11
+ const indexPath = path.join(dir, "src", "index.ts");
12
+ const middlewarePath = path.join(dir, "src", "x402-middleware.ts");
13
+ const packagePath = path.join(dir, "package.json");
14
+ const [hasIndex, hasMiddleware, hasPackage] = await Promise.all([
15
+ fs.access(indexPath).then(() => true).catch(() => false),
16
+ fs.access(middlewarePath).then(() => true).catch(() => false),
17
+ fs.access(packagePath).then(() => true).catch(() => false),
18
+ ]);
19
+ return hasIndex && hasMiddleware && hasPackage;
25
20
  }
26
21
  /**
27
22
  * Validate that a project directory does not already exist.
@@ -198,7 +198,7 @@ Example: http://localhost:3999`,
198
198
  server.registerTool("get_server_version", {
199
199
  description: `Check the currently running MCP server version and compare with the latest published version on npm.
200
200
  Use this to detect if you're running a stale cached version (common with npx).
201
- If your version is outdated, clear the npx cache and reinstall: npx clear-npx-cache && npx @aibtc/mcp-server@latest --install`,
201
+ If your version is outdated, clear the npx cache and restart your MCP client: npx clear-npx-cache (the client config already points at @latest, so the next launch pulls the newest version).`,
202
202
  inputSchema: {},
203
203
  }, async () => {
204
204
  try {
@@ -237,7 +237,7 @@ If your version is outdated, clear the npx cache and reinstall: npx clear-npx-ca
237
237
  updateAvailable,
238
238
  package: "@aibtc/mcp-server",
239
239
  hint: updateAvailable
240
- ? "⚠️ Update available! Clear npx cache and reinstall: npx clear-npx-cache && npx @aibtc/mcp-server@latest --install"
240
+ ? "⚠️ Update available! Clear the npx cache and restart your MCP client: npx clear-npx-cache (config already points at @latest, so the next launch pulls the newest version)."
241
241
  : fetched
242
242
  ? "✅ Running the latest version"
243
243
  : "Unable to verify latest version (network error)",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { uintCV, contractPrincipalCV, PostConditionMode, deserializeCV, cvToJSON, } from "@stacks/transactions";
3
3
  import { getAccount, NETWORK } from "../services/x402.service.js";
4
- import { getHiroApi } from "../services/hiro-api.js";
4
+ import { getHiroApi, isNotFoundError } from "../services/hiro-api.js";
5
5
  import { callContract } from "../transactions/builder.js";
6
6
  import { getExplorerTxUrl } from "../config/networks.js";
7
7
  import { createJsonResponse, createErrorResponse } from "../utils/index.js";
@@ -100,14 +100,17 @@ Note: Stackspot is only available on mainnet.`,
100
100
  try {
101
101
  currentValueUstx = await callPotReadOnly(pot.contractName, "get-pot-value", []);
102
102
  }
103
- catch {
104
- // pot may not be deployed or reachable — skip gracefully
103
+ catch (error) {
104
+ // 404 = pot contract not deployed; other failures surface
105
+ if (!isNotFoundError(error))
106
+ throw error;
105
107
  }
106
108
  try {
107
109
  isLocked = await callPotReadOnly(pot.contractName, "is-locked", []);
108
110
  }
109
- catch {
110
- // same
111
+ catch (error) {
112
+ if (!isNotFoundError(error))
113
+ throw error;
111
114
  }
112
115
  return {
113
116
  name: pot.name,
@@ -251,21 +251,17 @@ async function readStackingPosition(walletAddress) {
251
251
  return pos;
252
252
  }
253
253
  async function getWalletBalances(walletAddress) {
254
- try {
255
- const res = await fetch(`${MAINNET_HIRO_API}/extended/v1/address/${walletAddress}/balances`);
256
- if (!res.ok)
257
- return { stxMicroStx: 0, sbtcSats: 0 };
258
- const data = (await res.json());
259
- const stxMicroStx = parseInt(data.stx?.balance ?? "0", 10);
260
- const sbtcKey = Object.keys(data.fungible_tokens ?? {}).find((k) => k.toLowerCase().includes("sbtc"));
261
- const sbtcSats = sbtcKey
262
- ? parseInt(data.fungible_tokens?.[sbtcKey]?.balance ?? "0", 10)
263
- : 0;
264
- return { stxMicroStx, sbtcSats };
265
- }
266
- catch {
267
- return { stxMicroStx: 0, sbtcSats: 0 };
254
+ const res = await fetch(`${MAINNET_HIRO_API}/extended/v1/address/${walletAddress}/balances`);
255
+ if (!res.ok) {
256
+ throw new Error(`Failed to fetch wallet balances for ${walletAddress}: HTTP ${res.status}`);
268
257
  }
258
+ const data = (await res.json());
259
+ const stxMicroStx = parseInt(data.stx?.balance ?? "0", 10);
260
+ const sbtcKey = Object.keys(data.fungible_tokens ?? {}).find((k) => k.toLowerCase().includes("sbtc"));
261
+ const sbtcSats = sbtcKey
262
+ ? parseInt(data.fungible_tokens?.[sbtcKey]?.balance ?? "0", 10)
263
+ : 0;
264
+ return { stxMicroStx, sbtcSats };
269
265
  }
270
266
  async function checkZestV2() {
271
267
  try {
@@ -37,6 +37,7 @@ export async function encrypt(plaintext, password) {
37
37
  const key = await deriveKey(password, salt);
38
38
  // Encrypt with AES-256-GCM
39
39
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
40
+ key.fill(0); // cipher holds its own copy
40
41
  const encrypted = Buffer.concat([
41
42
  cipher.update(plaintext, "utf8"),
42
43
  cipher.final(),
@@ -64,6 +65,7 @@ export async function decrypt(encrypted, password) {
64
65
  const key = await deriveKeyWithParams(password, salt, encrypted.scryptParams);
65
66
  // Decrypt with AES-256-GCM
66
67
  const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
68
+ key.fill(0); // decipher holds its own copy
67
69
  decipher.setAuthTag(authTag);
68
70
  try {
69
71
  const decrypted = Buffer.concat([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibtc/mcp-server",
3
- "version": "1.57.0",
3
+ "version": "1.59.0",
4
4
  "description": "Bitcoin-native MCP server for AI agents: BTC/STX wallets, DeFi yield, sBTC peg, NFTs, and x402 payments.",
5
5
  "mcpName": "io.github.aibtcdev/mcp-server",
6
6
  "type": "module",
@@ -18,9 +18,13 @@
18
18
  },
19
19
  "files": [
20
20
  "dist",
21
+ "!dist/**/*.map",
21
22
  "README.md",
22
23
  "skill"
23
24
  ],
25
+ "engines": {
26
+ "node": ">=18.0.0"
27
+ },
24
28
  "keywords": [
25
29
  "claude",
26
30
  "claude-code",
@@ -60,7 +64,7 @@
60
64
  "@stacks/transactions": "^7.3.1",
61
65
  "@stacks/wallet-sdk": "^7.2.0",
62
66
  "alex-sdk": "^3.2.1",
63
- "axios": "^1.15.0",
67
+ "axios": "^1.17.0",
64
68
  "dotenv": "^17.3.1",
65
69
  "light-bolt11-decoder": "^3.2.0",
66
70
  "micro-ordinals": "^0.3.0",
@@ -68,7 +72,7 @@
68
72
  "path-to-regexp": "^8.4.0",
69
73
  "sbtc": "^0.3.2",
70
74
  "tslib": "^2.8.1",
71
- "ws": "^8.19.0",
75
+ "ws": "^8.21.0",
72
76
  "x402-stacks": "^2.0.3",
73
77
  "zod": "^4.3.6"
74
78
  },
@@ -77,9 +81,10 @@
77
81
  "@types/ws": "^8.18.1",
78
82
  "tsx": "^4.21.0",
79
83
  "typescript": "^5.9.3",
80
- "vitest": "^4.0.18"
84
+ "vitest": "^4.1.8"
81
85
  },
82
86
  "overrides": {
83
- "lodash": "^4.18.0"
87
+ "lodash": "^4.18.0",
88
+ "axios": "^1.17.0"
84
89
  }
85
90
  }
package/skill/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Bitcoin L1 wallet for agents - check balances, send BTC, manage UTX
4
4
  license: MIT
5
5
  metadata:
6
6
  author: aibtcdev
7
- version: 1.57.0 # x-release-please-version
7
+ version: 1.59.0 # x-release-please-version
8
8
  npm: "@aibtc/mcp-server"
9
9
  github: https://github.com/aibtcdev/aibtc-mcp-server
10
10
  ---
@@ -15,13 +15,24 @@ A skill for managing Bitcoin L1 wallets with optional Pillar smart wallet and St
15
15
 
16
16
  ## Install
17
17
 
18
- One-command installation:
18
+ One-command installation (Claude Code is the default):
19
19
 
20
20
  ```bash
21
21
  npx @aibtc/mcp-server@latest --install
22
22
  ```
23
23
 
24
- For testnet:
24
+ Other MCP clients are selected with a flag:
25
+
26
+ ```bash
27
+ npx @aibtc/mcp-server@latest --install --cursor # Cursor
28
+ npx @aibtc/mcp-server@latest --install --codex # OpenAI Codex CLI
29
+ npx @aibtc/mcp-server@latest --install --gemini # Gemini CLI
30
+ npx @aibtc/mcp-server@latest --install --windsurf # Windsurf
31
+ npx @aibtc/mcp-server@latest --install --vscode # VS Code
32
+ npx @aibtc/mcp-server@latest --install --desktop # Claude Desktop
33
+ ```
34
+
35
+ For testnet, add `--testnet` to any command:
25
36
 
26
37
  ```bash
27
38
  npx @aibtc/mcp-server@latest --install --testnet