@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
package/README.md CHANGED
@@ -45,32 +45,53 @@ This detects your OS and writes to the correct Claude Desktop config file:
45
45
 
46
46
  Restart Claude Desktop after installing.
47
47
 
48
- ### Testnet Mode
48
+ ### Other MCP Clients
49
49
 
50
- Add `--testnet` to either command:
50
+ This is a standard stdio MCP server, so it works with **any** MCP-compatible client. Claude Code is the default `--install` target; select another client with a flag:
51
51
 
52
52
  ```bash
53
- # Claude Code on testnet
54
- npx @aibtc/mcp-server@latest --install --testnet
53
+ npx @aibtc/mcp-server@latest --install --cursor # Cursor
54
+ npx @aibtc/mcp-server@latest --install --windsurf # Windsurf
55
+ npx @aibtc/mcp-server@latest --install --gemini # Gemini CLI
56
+ npx @aibtc/mcp-server@latest --install --codex # OpenAI Codex CLI
57
+ npx @aibtc/mcp-server@latest --install --vscode # VS Code (writes ./.vscode/mcp.json)
58
+ ```
59
+
60
+ | Flag | Client | Config written |
61
+ |------|--------|----------------|
62
+ | _(none)_ | Claude Code | `~/.claude.json` |
63
+ | `--desktop` | Claude Desktop | `claude_desktop_config.json` (see path table above) |
64
+ | `--cursor` | Cursor | `~/.cursor/mcp.json` |
65
+ | `--windsurf` | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
66
+ | `--gemini` | Gemini CLI | `~/.gemini/settings.json` |
67
+ | `--codex` | OpenAI Codex CLI | `~/.codex/config.toml` |
68
+ | `--vscode` | VS Code (Copilot agent) | `./.vscode/mcp.json` (project-scoped) |
69
+
70
+ Each installer merges into the existing config — it won't clobber other servers or settings. Restart the client afterward.
55
71
 
56
- # Claude Desktop on testnet
57
- npx @aibtc/mcp-server@latest --install --desktop --testnet
72
+ ### Testnet Mode
73
+
74
+ Add `--testnet` to any install command:
75
+
76
+ ```bash
77
+ npx @aibtc/mcp-server@latest --install --testnet # Claude Code, testnet
78
+ npx @aibtc/mcp-server@latest --install --cursor --testnet # Cursor, testnet
58
79
  ```
59
80
 
60
81
  > **Why npx?** Using `npx @aibtc/mcp-server@latest` ensures you always get the newest version automatically. Global installs (`npm install -g`) won't auto-update.
61
82
 
62
83
  ### Manual Configuration
63
84
 
64
- If you prefer to configure manually, add the following to your config file.
85
+ If you prefer to configure manually, add the following to your client's config file. The `-y` flag stops npx from prompting for confirmation.
65
86
 
66
- **Claude Code** (`~/.claude.json`):
87
+ **Claude Code / Claude Desktop / Cursor / Windsurf / Gemini CLI** — `mcpServers` JSON:
67
88
 
68
89
  ```json
69
90
  {
70
91
  "mcpServers": {
71
92
  "aibtc": {
72
93
  "command": "npx",
73
- "args": ["@aibtc/mcp-server@latest"],
94
+ "args": ["-y", "@aibtc/mcp-server@latest"],
74
95
  "env": {
75
96
  "NETWORK": "mainnet"
76
97
  }
@@ -79,23 +100,50 @@ If you prefer to configure manually, add the following to your config file.
79
100
  }
80
101
  ```
81
102
 
82
- **Claude Desktop** (`claude_desktop_config.json` -- see path table above):
103
+ **VS Code** (`.vscode/mcp.json`) — uses a `servers` key and a typed entry:
83
104
 
84
105
  ```json
85
106
  {
86
- "mcpServers": {
107
+ "servers": {
87
108
  "aibtc": {
109
+ "type": "stdio",
88
110
  "command": "npx",
89
111
  "args": ["-y", "@aibtc/mcp-server@latest"],
90
- "env": {
91
- "NETWORK": "mainnet"
92
- }
112
+ "env": { "NETWORK": "mainnet" }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ **OpenAI Codex CLI** (`~/.codex/config.toml`) — TOML, not JSON:
119
+
120
+ ```toml
121
+ [mcp_servers.aibtc]
122
+ command = "npx"
123
+ args = ["-y", "@aibtc/mcp-server@latest"]
124
+
125
+ [mcp_servers.aibtc.env]
126
+ NETWORK = "mainnet"
127
+ ```
128
+
129
+ **Zed** (`settings.json`) — uses a `context_servers` key:
130
+
131
+ ```json
132
+ {
133
+ "context_servers": {
134
+ "aibtc": {
135
+ "source": "custom",
136
+ "command": "npx",
137
+ "args": ["-y", "@aibtc/mcp-server@latest"],
138
+ "env": { "NETWORK": "mainnet" }
93
139
  }
94
140
  }
95
141
  }
96
142
  ```
97
143
 
98
- > **Note:** Claude Desktop requires the `-y` flag in args so npx doesn't prompt for confirmation.
144
+ **Cline / Roo Code** (VS Code extension) — add the same `mcpServers` JSON block above via the extension's MCP settings panel (the exact `cline_mcp_settings.json` path varies by OS and VS Code build).
145
+
146
+ > Any other MCP client works too — point it at `npx -y @aibtc/mcp-server@latest` over stdio with `NETWORK` in the env.
99
147
 
100
148
  ## Giving Claude a Wallet
101
149
 
package/dist/index.js CHANGED
@@ -13,8 +13,13 @@ import { initializeStorage } from "./utils/storage.js";
13
13
  const require = createRequire(import.meta.url);
14
14
  const packageJson = require("../package.json");
15
15
  // =============================================================================
16
- // AUTO-INSTALL FOR CLAUDE CODE AND CLAUDE DESKTOP
16
+ // AUTO-INSTALL FOR MCP CLIENTS
17
+ //
18
+ // The server is a standard stdio MCP server, so it works with any MCP client.
19
+ // `--install` writes the correct config for a target client. Claude Code is the
20
+ // default; other clients are selected with a flag (e.g. --cursor, --codex).
17
21
  // =============================================================================
22
+ const SERVER_NPM = "@aibtc/mcp-server@latest";
18
23
  function getClaudeDesktopConfigPath() {
19
24
  const platform = process.platform;
20
25
  const home = os.homedir();
@@ -31,13 +36,24 @@ function getClaudeDesktopConfigPath() {
31
36
  }
32
37
  }
33
38
  async function readJsonConfig(filePath) {
39
+ let content;
40
+ try {
41
+ content = await fs.readFile(filePath, "utf8");
42
+ }
43
+ catch (error) {
44
+ if (error.code === "ENOENT") {
45
+ return {}; // No config yet - fresh install
46
+ }
47
+ throw error;
48
+ }
34
49
  try {
35
- const content = await fs.readFile(filePath, "utf8");
36
50
  return JSON.parse(content);
37
51
  }
38
52
  catch {
39
- // File doesn't exist or isn't valid JSON, start fresh
40
- return {};
53
+ // Never silently replace an unparseable config: it may hold the user's
54
+ // other MCP servers, and "merging" into {} would discard them on write.
55
+ throw new Error(`Existing config at ${filePath} is not valid JSON. ` +
56
+ `Fix or remove it, then re-run the install.`);
41
57
  }
42
58
  }
43
59
  async function writeJsonConfig(filePath, config) {
@@ -46,56 +62,145 @@ async function writeJsonConfig(filePath, config) {
46
62
  await fs.mkdir(dir, { recursive: true });
47
63
  await fs.writeFile(filePath, JSON.stringify(config, null, 2));
48
64
  }
49
- async function installToClaudeCode() {
50
- const claudeConfigPath = path.join(os.homedir(), ".claude.json");
51
- const network = process.argv.includes("--testnet") ? "testnet" : "mainnet";
52
- console.log("🔧 Installing @aibtc/mcp-server to Claude Code...\n");
53
- const config = await readJsonConfig(claudeConfigPath);
54
- if (!config.mcpServers) {
55
- config.mcpServers = {};
56
- }
57
- config.mcpServers["aibtc"] = {
65
+ // Standard MCP server entry shared by every JSON-based client config.
66
+ function serverEntry(network) {
67
+ return {
58
68
  command: "npx",
59
- args: ["@aibtc/mcp-server@latest"],
60
- env: {
61
- NETWORK: network,
62
- },
69
+ args: ["-y", SERVER_NPM],
70
+ env: { NETWORK: network },
63
71
  };
64
- await writeJsonConfig(claudeConfigPath, config);
65
- console.log("✅ Successfully installed!\n");
66
- console.log(` Config: ${claudeConfigPath}`);
67
- console.log(` Network: ${network}`);
68
- console.log("\n📋 Next steps:");
69
- console.log(" 1. Restart Claude Code (close and reopen terminal)");
70
- console.log(" 2. Ask Claude: \"What's your wallet address?\"");
71
- console.log(" 3. Claude will guide you through wallet setup\n");
72
- if (network === "testnet") {
73
- console.log("💡 Tip: Get testnet STX at https://explorer.hiro.so/sandbox/faucet?chain=testnet\n");
74
- }
75
72
  }
76
- async function installToClaudeDesktop() {
77
- const configPath = getClaudeDesktopConfigPath();
78
- const network = process.argv.includes("--testnet") ? "testnet" : "mainnet";
79
- console.log("🔧 Installing @aibtc/mcp-server to Claude Desktop...\n");
73
+ // Most clients (Claude Code, Claude Desktop, Cursor, Windsurf, Gemini CLI) use
74
+ // the same `{ "mcpServers": { "aibtc": {...} } }` JSON shape.
75
+ async function writeMcpServersJson(configPath, network) {
80
76
  const config = await readJsonConfig(configPath);
81
- if (!config.mcpServers) {
82
- config.mcpServers = {};
83
- }
84
- config.mcpServers["aibtc"] = {
77
+ const servers = (config.mcpServers ??= {});
78
+ servers["aibtc"] = serverEntry(network);
79
+ await writeJsonConfig(configPath, config);
80
+ }
81
+ // VS Code (.vscode/mcp.json) uses a `servers` key and a typed stdio entry.
82
+ async function writeVsCodeJson(configPath, network) {
83
+ const config = await readJsonConfig(configPath);
84
+ const servers = (config.servers ??= {});
85
+ servers["aibtc"] = {
86
+ type: "stdio",
85
87
  command: "npx",
86
- args: ["-y", "@aibtc/mcp-server@latest"],
87
- env: {
88
- NETWORK: network,
89
- },
88
+ args: ["-y", SERVER_NPM],
89
+ env: { NETWORK: network },
90
90
  };
91
91
  await writeJsonConfig(configPath, config);
92
+ }
93
+ // Codex uses TOML, not JSON. Rewrite only the `[mcp_servers.aibtc]` section so
94
+ // the rest of the user's config.toml (including comments) is preserved.
95
+ function stripCodexSection(content) {
96
+ const out = [];
97
+ let skipping = false;
98
+ for (const line of content.split("\n")) {
99
+ const header = line.trim();
100
+ if (header.startsWith("[")) {
101
+ skipping = header === "[mcp_servers.aibtc]" || header.startsWith("[mcp_servers.aibtc.");
102
+ }
103
+ if (!skipping)
104
+ out.push(line);
105
+ }
106
+ return out.join("\n");
107
+ }
108
+ async function writeCodexToml(configPath, network) {
109
+ let existing = "";
110
+ try {
111
+ existing = await fs.readFile(configPath, "utf8");
112
+ }
113
+ catch (error) {
114
+ if (error.code !== "ENOENT") {
115
+ throw error; // Unreadable existing config must not be clobbered
116
+ }
117
+ }
118
+ const preserved = stripCodexSection(existing).replace(/\s+$/, "");
119
+ const block = [
120
+ "[mcp_servers.aibtc]",
121
+ 'command = "npx"',
122
+ `args = ["-y", "${SERVER_NPM}"]`,
123
+ "",
124
+ "[mcp_servers.aibtc.env]",
125
+ `NETWORK = "${network}"`,
126
+ ].join("\n");
127
+ const content = (preserved ? `${preserved}\n\n` : "") + block + "\n";
128
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
129
+ await fs.writeFile(configPath, content);
130
+ }
131
+ const INSTALL_TARGETS = [
132
+ {
133
+ flag: "--desktop",
134
+ label: "Claude Desktop",
135
+ configPath: getClaudeDesktopConfigPath,
136
+ write: writeMcpServersJson,
137
+ restart: "Restart Claude Desktop (quit and reopen the app)",
138
+ },
139
+ {
140
+ flag: "--cursor",
141
+ label: "Cursor",
142
+ configPath: () => path.join(os.homedir(), ".cursor", "mcp.json"),
143
+ write: writeMcpServersJson,
144
+ restart: "Restart Cursor",
145
+ },
146
+ {
147
+ flag: "--windsurf",
148
+ label: "Windsurf",
149
+ configPath: () => path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
150
+ write: writeMcpServersJson,
151
+ restart: "Restart Windsurf",
152
+ },
153
+ {
154
+ flag: "--gemini",
155
+ label: "Gemini CLI",
156
+ configPath: () => path.join(os.homedir(), ".gemini", "settings.json"),
157
+ write: writeMcpServersJson,
158
+ restart: "Restart the Gemini CLI",
159
+ },
160
+ {
161
+ flag: "--vscode",
162
+ label: "VS Code",
163
+ configPath: () => path.join(process.cwd(), ".vscode", "mcp.json"),
164
+ write: writeVsCodeJson,
165
+ restart: "Reload VS Code, then start the MCP server from the .vscode/mcp.json gutter",
166
+ },
167
+ {
168
+ flag: "--codex",
169
+ label: "Codex CLI",
170
+ configPath: () => path.join(os.homedir(), ".codex", "config.toml"),
171
+ write: writeCodexToml,
172
+ restart: "Restart the Codex CLI",
173
+ },
174
+ {
175
+ flag: null,
176
+ label: "Claude Code",
177
+ configPath: () => path.join(os.homedir(), ".claude.json"),
178
+ write: writeMcpServersJson,
179
+ restart: "Restart Claude Code (close and reopen terminal)",
180
+ },
181
+ ];
182
+ function resolveInstallTarget() {
183
+ const matched = INSTALL_TARGETS.filter((t) => t.flag && process.argv.includes(t.flag));
184
+ if (matched.length > 1) {
185
+ console.warn(`⚠️ Multiple client flags passed (${matched.map((t) => t.flag).join(", ")}); using ${matched[0].label}.`);
186
+ }
187
+ // Default to the entry explicitly marked as default (flag === null) — Claude Code.
188
+ return matched[0] ?? INSTALL_TARGETS.find((t) => t.flag === null);
189
+ }
190
+ async function runInstall() {
191
+ const network = process.argv.includes("--testnet") ? "testnet" : "mainnet";
192
+ const target = resolveInstallTarget();
193
+ const configPath = target.configPath();
194
+ console.log(`🔧 Installing @aibtc/mcp-server to ${target.label}...\n`);
195
+ await target.write(configPath, network);
92
196
  console.log("✅ Successfully installed!\n");
93
- console.log(` Config: ${configPath}`);
197
+ console.log(` Client: ${target.label}`);
198
+ console.log(` Config: ${configPath}`);
94
199
  console.log(` Network: ${network}`);
95
200
  console.log("\n📋 Next steps:");
96
- console.log(" 1. Restart Claude Desktop (quit and reopen the app)");
97
- console.log(" 2. Ask Claude: \"What's your wallet address?\"");
98
- console.log(" 3. Claude will guide you through wallet setup\n");
201
+ console.log(` 1. ${target.restart}`);
202
+ console.log(" 2. Ask the agent: \"What's your wallet address?\"");
203
+ console.log(" 3. The agent will guide you through wallet setup\n");
99
204
  if (network === "testnet") {
100
205
  console.log("💡 Tip: Get testnet STX at https://explorer.hiro.so/sandbox/faucet?chain=testnet\n");
101
206
  }
@@ -126,9 +231,7 @@ if (process.argv[2] === "yield-hunter") {
126
231
  }
127
232
  // Check for --install flag
128
233
  else if (process.argv.includes("--install") || process.argv.includes("install")) {
129
- const isDesktop = process.argv.includes("--desktop");
130
- const installFn = isDesktop ? installToClaudeDesktop : installToClaudeCode;
131
- installFn()
234
+ runInstall()
132
235
  .then(() => process.exit(0))
133
236
  .catch((error) => {
134
237
  console.error("❌ Installation failed:", redactSensitive(error.message));
@@ -39,6 +39,7 @@ async function encrypt(data, password) {
39
39
  const iv = crypto.randomBytes(12);
40
40
  const key = await deriveKey(password, salt);
41
41
  const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
42
+ key.fill(0); // cipher holds its own copy
42
43
  const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
43
44
  const authTag = cipher.getAuthTag();
44
45
  return {
@@ -53,6 +54,7 @@ async function encrypt(data, password) {
53
54
  async function decrypt(encrypted, password) {
54
55
  const key = await deriveKey(password, Buffer.from(encrypted.salt, "base64"));
55
56
  const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(encrypted.iv, "base64"));
57
+ key.fill(0); // decipher holds its own copy
56
58
  decipher.setAuthTag(Buffer.from(encrypted.authTag, "base64"));
57
59
  const decrypted = Buffer.concat([
58
60
  decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
@@ -67,12 +69,15 @@ function emptyStore() {
67
69
  async function save() {
68
70
  if (!_store || !_password)
69
71
  throw new Error("Store not unlocked");
70
- await fs.mkdir(getStoreDir(), { recursive: true });
72
+ await fs.mkdir(getStoreDir(), { recursive: true, mode: 0o700 });
71
73
  const file = {
72
74
  version: VERSION,
73
75
  encrypted: await encrypt(JSON.stringify(_store), _password),
74
76
  };
75
- await fs.writeFile(getStoreFile(), JSON.stringify(file, null, 2));
77
+ // Owner-only perms + atomic rename, same pattern as the wallet keystores
78
+ const tempFile = getStoreFile() + ".tmp";
79
+ await fs.writeFile(tempFile, JSON.stringify(file, null, 2), { mode: 0o600 });
80
+ await fs.rename(tempFile, getStoreFile());
76
81
  }
77
82
  async function load(password) {
78
83
  const raw = await fs.readFile(getStoreFile(), "utf-8");
@@ -1,6 +1,6 @@
1
1
  import { uintCV, contractPrincipalCV, cvToJSON, hexToCV, PostConditionMode, Pc, principalCV, noneCV, someCV, listCV, bufferCV, } from "@stacks/transactions";
2
2
  import { AlexSDK, Currency } from "alex-sdk";
3
- import { getHiroApi } from "./hiro-api.js";
3
+ import { getHiroApi, isNotFoundError } from "./hiro-api.js";
4
4
  import { getAlexContracts, getZestContracts, parseContractId, ZEST_ASSETS, ZEST_V2_MARKET, ZEST_V2_MARKET_VAULT, } from "../config/index.js";
5
5
  import { callContract } from "../transactions/builder.js";
6
6
  // ============================================================================
@@ -129,8 +129,10 @@ export class AlexDexService {
129
129
  }
130
130
  return null;
131
131
  }
132
- catch {
133
- return null;
132
+ catch (error) {
133
+ if (isNotFoundError(error))
134
+ return null;
135
+ throw error;
134
136
  }
135
137
  }
136
138
  /**
@@ -360,8 +362,10 @@ export class ZestProtocolService {
360
362
  healthFactor: position["health-factor"]?.value,
361
363
  };
362
364
  }
363
- catch {
364
- return null;
365
+ catch (error) {
366
+ if (isNotFoundError(error))
367
+ return null;
368
+ throw error;
365
369
  }
366
370
  }
367
371
  /**
@@ -377,8 +381,10 @@ export class ZestProtocolService {
377
381
  }
378
382
  return cvToJSON(hexToCV(result.result));
379
383
  }
380
- catch {
381
- return null;
384
+ catch (error) {
385
+ if (isNotFoundError(error))
386
+ return null;
387
+ throw error;
382
388
  }
383
389
  }
384
390
  /**
@@ -411,8 +417,12 @@ export class ZestProtocolService {
411
417
  }
412
418
  }
413
419
  }
414
- catch {
415
- // Fall back to shares as lower bound estimate
420
+ catch (error) {
421
+ // Only fall back to the shares lower-bound estimate when the vault
422
+ // function is missing — network/server failures must surface, or the
423
+ // post-conditions built from this value would be silently wrong.
424
+ if (!isNotFoundError(error))
425
+ throw error;
416
426
  }
417
427
  return shares;
418
428
  }
@@ -431,8 +441,10 @@ export class ZestProtocolService {
431
441
  }
432
442
  }
433
443
  }
434
- catch {
435
- // Fall back to amount as upper bound
444
+ catch (error) {
445
+ // Same rationale as getUnderlyingFromShares: estimate only on 404.
446
+ if (!isNotFoundError(error))
447
+ throw error;
436
448
  }
437
449
  return amount;
438
450
  }
@@ -7,6 +7,16 @@ export declare class HiroApiRateLimitError extends Error {
7
7
  readonly retryAfterSeconds: number;
8
8
  constructor(message: string, retryAfterSeconds: number);
9
9
  }
10
+ /**
11
+ * HTTP error from the Hiro / BNS V2 APIs, carrying the status code so
12
+ * callers can treat 404 as "not found" without swallowing other failures.
13
+ */
14
+ export declare class HiroApiError extends Error {
15
+ readonly status: number;
16
+ constructor(message: string, status: number);
17
+ }
18
+ /** True when the error is an expected "resource does not exist" response. */
19
+ export declare function isNotFoundError(error: unknown): boolean;
10
20
  export interface AccountInfo {
11
21
  balance: string;
12
22
  locked: string;
@@ -16,6 +16,22 @@ export class HiroApiRateLimitError extends Error {
16
16
  this.name = "HiroApiRateLimitError";
17
17
  }
18
18
  }
19
+ /**
20
+ * HTTP error from the Hiro / BNS V2 APIs, carrying the status code so
21
+ * callers can treat 404 as "not found" without swallowing other failures.
22
+ */
23
+ export class HiroApiError extends Error {
24
+ status;
25
+ constructor(message, status) {
26
+ super(message);
27
+ this.status = status;
28
+ this.name = "HiroApiError";
29
+ }
30
+ }
31
+ /** True when the error is an expected "resource does not exist" response. */
32
+ export function isNotFoundError(error) {
33
+ return error instanceof HiroApiError && error.status === 404;
34
+ }
19
35
  // ============================================================================
20
36
  // Hiro API Service
21
37
  // ============================================================================
@@ -51,7 +67,7 @@ export class HiroApiService {
51
67
  throw new HiroApiRateLimitError(`Hiro API rate limit exceeded. Retry after ${retryAfterSeconds}s`, retryAfterSeconds);
52
68
  }
53
69
  const errorText = await response.text();
54
- throw new Error(`Hiro API error (${response.status}): ${errorText}`);
70
+ throw new HiroApiError(`Hiro API error (${response.status}): ${errorText}`, response.status);
55
71
  }
56
72
  return response.json();
57
73
  }
@@ -123,8 +139,10 @@ export class HiroApiService {
123
139
  try {
124
140
  return await this.fetch(`/metadata/v1/ft/${contractId}`);
125
141
  }
126
- catch {
127
- return null;
142
+ catch (error) {
143
+ if (isNotFoundError(error))
144
+ return null;
145
+ throw error;
128
146
  }
129
147
  }
130
148
  async getTokenHolders(contractId, options) {
@@ -329,8 +347,10 @@ export class HiroApiService {
329
347
  const info = await this.getBnsNameInfo(name);
330
348
  return info.address;
331
349
  }
332
- catch {
333
- return null;
350
+ catch (error) {
351
+ if (isNotFoundError(error))
352
+ return null;
353
+ throw error;
334
354
  }
335
355
  }
336
356
  // ==========================================================================
@@ -385,7 +405,7 @@ export class BnsV2ApiService {
385
405
  });
386
406
  if (!response.ok) {
387
407
  const errorText = await response.text();
388
- throw new Error(`BNS V2 API error (${response.status}): ${errorText}`);
408
+ throw new HiroApiError(`BNS V2 API error (${response.status}): ${errorText}`, response.status);
389
409
  }
390
410
  return response.json();
391
411
  }
@@ -431,8 +451,10 @@ export class BnsV2ApiService {
431
451
  }
432
452
  return null;
433
453
  }
434
- catch {
435
- return null;
454
+ catch (error) {
455
+ if (isNotFoundError(error))
456
+ return null;
457
+ throw error;
436
458
  }
437
459
  }
438
460
  }
@@ -1,5 +1,5 @@
1
1
  import { uintCV, principalCV } from "@stacks/transactions";
2
- import { getHiroApi } from "./hiro-api.js";
2
+ import { getHiroApi, HiroApiError } from "./hiro-api.js";
3
3
  import { parseContractId } from "../config/index.js";
4
4
  import { callContract } from "../transactions/builder.js";
5
5
  import { createNftSendPostCondition } from "../transactions/post-conditions.js";
@@ -51,8 +51,13 @@ export class NftService {
51
51
  }
52
52
  return null;
53
53
  }
54
- catch {
55
- return null;
54
+ catch (error) {
55
+ // 4xx means the contract doesn't implement get-owner (or rejects the
56
+ // call) — that is "no owner". Network/server failures must surface.
57
+ if (error instanceof HiroApiError && error.status < 500) {
58
+ return null;
59
+ }
60
+ throw error;
56
61
  }
57
62
  }
58
63
  /**
@@ -75,8 +80,12 @@ export class NftService {
75
80
  }
76
81
  }
77
82
  }
78
- catch {
79
- // Function may not exist or may fail
83
+ catch (error) {
84
+ // 4xx means the contract doesn't implement get-last-token-id —
85
+ // supply is simply unknown. Network/server failures must surface.
86
+ if (!(error instanceof HiroApiError && error.status < 500)) {
87
+ throw error;
88
+ }
80
89
  }
81
90
  return {
82
91
  contractId,
@@ -1,3 +1,11 @@
1
+ /**
2
+ * HTTP error from the Pillar API, carrying the status code so callers can
3
+ * treat 4xx ("no such wallet / no position") differently from infra failures.
4
+ */
5
+ export declare class PillarApiError extends Error {
6
+ readonly status: number;
7
+ constructor(message: string, status: number);
8
+ }
1
9
  declare class PillarApiService {
2
10
  private baseUrl;
3
11
  private apiKey;
@@ -1,4 +1,16 @@
1
1
  import { PILLAR_API_URL, PILLAR_API_KEY } from "../config/pillar.js";
2
+ /**
3
+ * HTTP error from the Pillar API, carrying the status code so callers can
4
+ * treat 4xx ("no such wallet / no position") differently from infra failures.
5
+ */
6
+ export class PillarApiError extends Error {
7
+ status;
8
+ constructor(message, status) {
9
+ super(message);
10
+ this.status = status;
11
+ this.name = "PillarApiError";
12
+ }
13
+ }
2
14
  class PillarApiService {
3
15
  baseUrl;
4
16
  apiKey;
@@ -32,7 +44,7 @@ class PillarApiService {
32
44
  });
33
45
  if (!response.ok) {
34
46
  const errorText = await response.text();
35
- throw new Error(`Pillar API error (${response.status}): ${errorText}`);
47
+ throw new PillarApiError(`Pillar API error (${response.status}): ${errorText}`, response.status);
36
48
  }
37
49
  return response.json();
38
50
  }