bsv-mcp 0.0.7 → 0.0.9

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/Dockerfile ADDED
@@ -0,0 +1,20 @@
1
+ # Use the official Bun image
2
+ FROM oven/bun:1
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy all application code first
8
+ COPY . .
9
+
10
+ # Install dependencies
11
+ RUN bun install --frozen-lockfile
12
+
13
+ # Set user for security
14
+ USER bun
15
+
16
+ # Expose port (if needed)
17
+ EXPOSE 3000
18
+
19
+ # Run the application
20
+ CMD ["bun", "run", "index.ts"]
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # Bitcoin SV MCP Server
2
2
 
3
+ [![smithery badge](https://smithery.ai/badge/@b-open-io/bsv-mcp)](https://smithery.ai/server/@b-open-io/bsv-mcp)
4
+
3
5
  > **⚠️ NOTICE: Experimental Work in Progress**
4
6
  > This project is in an early experimental stage. Features may change, and the API is not yet stable.
5
7
  > Contributions, feedback, and bug reports are welcome! Feel free to open issues or submit pull requests.
@@ -136,46 +138,46 @@ The toolkit is organized into several categories:
136
138
 
137
139
  Wallet tools provide core BSV wallet functionality:
138
140
 
139
- | Tool Name | Description |
140
- |-----------|-------------|
141
- | `wallet_getPublicKey` | Retrieves a public key for a specified protocol and key ID |
142
- | `wallet_createSignature` | Creates a cryptographic signature for the provided data |
143
- | `wallet_verifySignature` | Verifies a cryptographic signature against the provided data |
144
- | `wallet_encrypt` | Encrypts data using a specified protocol and key |
145
- | `wallet_decrypt` | Decrypts data using a specified protocol and key |
146
- | `wallet_getAddress` | Returns a BSV address for the current wallet or a derived path |
147
- | `wallet_sendToAddress` | Sends BSV to a specified address (supports BSV or USD amounts) |
148
- | `wallet_purchaseListing` | Purchases NFTs or BSV-20/BSV-21 tokens from marketplace listings |
149
- | `wallet_createOrdinals` | Creates and inscribes ordinals on the BSV blockchain |
141
+ | Tool Name | Description | Example Output |
142
+ |-----------|-------------|----------------|
143
+ | `wallet_getPublicKey` | Retrieves a public key for a specified protocol and key ID | `{"publicKey":"032d0c73eb9270e9e009fd1f9dd77e19cf764fbad5f799560c4e8fd414e40d6fc2"}` |
144
+ | `wallet_createSignature` | Creates a cryptographic signature for the provided data | `{"signature":[144,124,85,193,226,45,140,249,9,177,11,167,33,215,209,38,...]}` |
145
+ | `wallet_verifySignature` | Verifies a cryptographic signature against the provided data | `{"isValid":true}` |
146
+ | `wallet_encryption` | Combined tool for encrypting and decrypting data using the wallet's cryptographic keys (replaces separate encrypt/decrypt tools) | Encrypt: `{"ciphertext":[89,32,155,38,125,22,49,226,26,...]}` <br> Decrypt: `{"plaintext":[104,101,108,108,111,32,119,111,114,108,100]}` |
147
+ | `wallet_getAddress` | Returns a BSV address for the current wallet or a derived path | `{"address":"1ExampleBsvAddressXXXXXXXXXXXXXXXXX","status":"ok"}` |
148
+ | `wallet_sendToAddress` | Sends BSV to a specified address (supports BSV or USD amounts) | `{"status":"success","txid":"a1b2c3d4e5f6...","satoshis":1000000}` |
149
+ | `wallet_purchaseListing` | Purchases NFTs or BSV-20/BSV-21 tokens from marketplace listings | `{"status":"success","txid":"a1b2c3d4e5f6...","type":"nft","origin":"abcdef123456..."}` |
150
+ | `wallet_createOrdinals` | Creates and inscribes ordinals on the BSV blockchain | `{"txid":"a1b2c3d4e5f6...","inscriptionAddress":"1ExampleAddress...","contentType":"image/png"}` |
150
151
 
151
152
  ### BSV Tools
152
153
 
153
154
  Tools for interacting with the BSV blockchain and network:
154
155
 
155
- | Tool Name | Description |
156
- |-----------|-------------|
157
- | `bsv_getPrice` | Gets the current BSV price from an exchange API |
158
- | `bsv_decodeTransaction` | Decodes a BSV transaction and returns detailed information |
156
+ | Tool Name | Description | Example Output |
157
+ |-----------|-------------|----------------|
158
+ | `bsv_getPrice` | Gets the current BSV price from an exchange API | `Current BSV price: $38.75 USD` |
159
+ | `bsv_decodeTransaction` | Decodes a BSV transaction and returns detailed information | `{"txid":"a1b2c3d4e5f6...","version":1,"locktime":0,"size":225,"inputs":[...],"outputs":[...]}` |
160
+ | `bsv_explore` | Comprehensive blockchain explorer tool accessing WhatsOnChain API endpoints | `{"chain_info":{"chain":"main","blocks":826458,"headers":826458,"bestblockhash":"0000000000..."}}` |
159
161
 
160
162
  ### Ordinals Tools
161
163
 
162
164
  Tools for working with ordinals (NFTs) on BSV:
163
165
 
164
- | Tool Name | Description |
165
- |-----------|-------------|
166
- | `ordinals_getInscription` | Retrieves detailed information about a specific inscription |
167
- | `ordinals_searchInscriptions` | Searches for inscriptions based on various criteria |
168
- | `ordinals_marketListings` | Retrieves market listings for NFTs, BSV-20, and BSV-21 tokens with unified interface |
169
- | `ordinals_marketSales` | Gets information about BSV-20 and BSV-21 token market sales |
170
- | `ordinals_getBsv20ById` | Retrieves details about a specific BSV20 token by ID |
166
+ | Tool Name | Description | Example Output |
167
+ |-----------|-------------|----------------|
168
+ | `ordinals_getInscription` | Retrieves detailed information about a specific inscription | `{"id":"a1b2c3d4e5f6...","origin":"a1b2c3d4e5f6...","contentType":"image/png","content":"iVBORw0KGgoAAA..."}` |
169
+ | `ordinals_searchInscriptions` | Searches for inscriptions based on various criteria | `{"results":[{"id":"a1b2c3...","contentType":"image/png","owner":"1Example..."},...]}` |
170
+ | `ordinals_marketListings` | Retrieves market listings for NFTs, BSV-20, and BSV-21 tokens with unified interface | `{"results":[{"txid":"a1b2c3...","price":9990000,"tick":"PEPE","listing":true},...]}` |
171
+ | `ordinals_marketSales` | Gets information about BSV-20 and BSV-21 token market sales | `{"results":[{"txid":"a1b2c3...","price":34710050,"tick":"$BTC","sale":true},...]}` |
172
+ | `ordinals_getTokenByIdOrTicker` | Retrieves details about a specific BSV20 token by ID | `{"tick":"PEPE","max":"21000000","lim":"1000","dec":"2"}` |
171
173
 
172
174
  ### Utility Tools
173
175
 
174
176
  General-purpose utility functions:
175
177
 
176
- | Tool Name | Description |
177
- |-----------|-------------|
178
- | `utils_convertData` | Converts data between different encodings (utf8, hex, base64, binary) |
178
+ | Tool Name | Description | Example Output |
179
+ |-----------|-------------|----------------|
180
+ | `utils_convertData` | Converts data between different encodings (utf8, hex, base64, binary) | `68656c6c6f20776f726c64` (UTF-8 "hello world" converted to hex) |
179
181
 
180
182
  ## Using the Tools with MCP
181
183
 
@@ -185,6 +187,8 @@ Once connected, you can use natural language to interact with Bitcoin SV through
185
187
  - "Get my Bitcoin SV address"
186
188
  - "Send 0.01 BSV to 1ExampleBsvAddressXXXXXXXXXXXXXXXXX"
187
189
  - "Send $5 USD worth of BSV to 1ExampleBsvAddressXXXXXXXXXXXXXXXXX"
190
+ - "Encrypt this message using my wallet's keys"
191
+ - "Decrypt this data that was previously encrypted for me"
188
192
  - "Purchase this NFT listing: txid_vout"
189
193
  - "Purchase this BSV-20 token listing: txid_vout"
190
194
 
@@ -198,6 +202,11 @@ Once connected, you can use natural language to interact with Bitcoin SV through
198
202
  ### Blockchain Operations
199
203
  - "What is the current BSV price?"
200
204
  - "Decode this BSV transaction: (transaction hex or ID)"
205
+ - "Get the latest Bitcoin SV chain information"
206
+ - "Show me block details for height 800000"
207
+ - "Explore transaction history for address 1ExampleBsvAddressXXXX"
208
+ - "Check unspent outputs (UTXOs) for my wallet address"
209
+ - "Get details for transaction with hash a1b2c3d4e5f6..."
201
210
 
202
211
  ### Data Conversion
203
212
  - "Convert 'Hello World' from UTF-8 to hex format"
@@ -230,14 +239,81 @@ For Cursor, check the Cursor MCP logs in Settings → Extensions → Model Conte
230
239
 
231
240
  ## Recent Updates
232
241
 
233
- ### Enhanced Marketplace Tools
234
- - **Unified Market Listings**: The `ordinals_marketListings` tool now supports NFTs, BSV-20, and BSV-21 tokens through a single interface with appropriate filtering.
235
- - **Improved Market Sales**: The `ordinals_marketSales` tool (renamed from `ordinals_bsv20MarketSales`) now supports both BSV-20 and BSV-21 token sales.
236
- - **Token Purchases**: The `wallet_purchaseListing` tool has been enhanced to support purchasing both NFTs and BSV-20/BSV-21 tokens.
242
+ - **Blockchain Explorer**: Added `bsv_explore` tool for WhatsOnChain API access with mainnet/testnet support
243
+ - **Unified Tools**: Merged `wallet_encrypt`/`wallet_decrypt` into single `wallet_encryption` tool
244
+ - **Enhanced Marketplace**: Support for NFTs, BSV-20/21 tokens in listings, sales and purchases
245
+ - **Performance**: Added price caching and optimized API endpoint structure
246
+ - **Improved Validation**: Better error handling for private keys and parameters
247
+
248
+ ## Bitcoin SV Blockchain Explorer
249
+
250
+ The `bsv_explore` tool provides comprehensive access to the Bitcoin SV blockchain through the WhatsOnChain API. This powerful explorer tool allows you to query various aspects of the blockchain, including chain data, blocks, transactions, and address information.
251
+
252
+ ### Available Endpoints
253
+
254
+ The tool supports the following endpoint categories and specific endpoints:
255
+
256
+ #### Chain Data
257
+ | Endpoint | Description | Required Parameters | Example Response |
258
+ |----------|-------------|---------------------|------------------|
259
+ | `chain_info` | Network statistics, difficulty, and chain work | None | `{"chain":"main","blocks":826458,"headers":826458,"bestblockhash":"000000000000..."}` |
260
+ | `chain_tips` | Current chain tips including heights and states | None | `[{"height":826458,"hash":"000000000000...","branchlen":0,"status":"active"}]` |
261
+ | `circulating_supply` | Current BSV circulating supply | None | `{"bsv":21000000}` |
262
+ | `peer_info` | Connected peer statistics | None | `[{"addr":"1.2.3.4:8333","services":"000000000000...","lastsend":1621234567}]` |
263
+
264
+ #### Block Data
265
+ | Endpoint | Description | Required Parameters | Example Response |
266
+ |----------|-------------|---------------------|------------------|
267
+ | `block_by_hash` | Complete block data via hash | `blockHash` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
268
+ | `block_by_height` | Complete block data via height | `blockHeight` | `{"hash":"000000000000...","confirmations":1000,"size":1000000,...}` |
269
+
270
+ #### Transaction Data
271
+ | Endpoint | Description | Required Parameters | Example Response |
272
+ |----------|-------------|---------------------|------------------|
273
+ | `tx_by_hash` | Detailed transaction data | `txHash` | `{"txid":"a1b2c3d4e5f6...","version":1,"locktime":0,"size":225,...}` |
274
+ | `tx_raw` | Raw transaction hex data | `txHash` | `"01000000012345abcdef..."` |
275
+ | `tx_receipt` | Transaction receipt | `txHash` | `{"blockHash":"000000000000...","blockHeight":800000,"confirmations":26458}` |
276
+
277
+ #### Address Data
278
+ | Endpoint | Description | Required Parameters | Example Response |
279
+ |----------|-------------|---------------------|------------------|
280
+ | `address_history` | Transaction history for address | `address`, optional: `limit` | `[{"tx_hash":"a1b2c3d4e5f6...","height":800000},...]` |
281
+ | `address_utxos` | Unspent outputs for address | `address` | `[{"tx_hash":"a1b2c3d4e5f6...","tx_pos":0,"value":100000},...]` |
282
+
283
+ #### Network
284
+ | Endpoint | Description | Required Parameters | Example Response |
285
+ |----------|-------------|---------------------|------------------|
286
+ | `health` | API health check | None | `{"status":"synced"}` |
287
+
288
+ ### Usage Examples
289
+
290
+ The `bsv_explore` tool can be used with natural language prompts like:
291
+
292
+ ```
293
+ "Get the current Bitcoin SV blockchain information"
294
+ "Show me block #800000 details"
295
+ "Fetch transaction history for address 1ExampleBsvAddressXXXXXXXX"
296
+ "Get unspent outputs for my wallet address"
297
+ "Check transaction details for txid a1b2c3d4e5f6..."
298
+ "What is the current BSV circulating supply?"
299
+ ```
300
+
301
+ Under the hood, the tool accepts parameters to specify which data to retrieve:
302
+
303
+ - `endpoint`: The specific WhatsOnChain endpoint to query (e.g., `chain_info`, `tx_by_hash`)
304
+ - `network`: The BSV network to use (`main` or `test`)
305
+ - Additional parameters as required by the specific endpoint:
306
+ - `blockHash`: For block_by_hash endpoint
307
+ - `blockHeight`: For block_by_height endpoint
308
+ - `txHash`: For transaction-related endpoints
309
+ - `address`: For address-related endpoints
310
+ - `limit`: Optional pagination limit for address_history
311
+
312
+ ### Network Options
237
313
 
238
- ### API Refinements
239
- - Standardized endpoint structure for interactions with the GorillaPool API.
240
- - Improved parameter handling for different token types and marketplace actions.
314
+ The tool supports both mainnet and testnet:
315
+ - `main`: Bitcoin SV mainnet (default)
316
+ - `test`: Bitcoin SV testnet
241
317
 
242
318
  ## Development
243
319
 
@@ -255,4 +331,4 @@ bun test
255
331
 
256
332
  ## License
257
333
 
258
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
334
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/index.ts CHANGED
@@ -6,16 +6,43 @@ import { registerAllTools } from "./tools";
6
6
  import { registerWalletTools } from "./tools/wallet/tools";
7
7
  import { Wallet } from "./tools/wallet/wallet";
8
8
 
9
+ /**
10
+ * Validate the private key from environment variables
11
+ * Exits the process with an error message if validation fails
12
+ */
13
+ function validatePrivateKey(): PrivateKey {
14
+ const privateKeyWif = process.env.PRIVATE_KEY_WIF;
15
+
16
+ // Check if private key is set
17
+ if (!privateKeyWif) {
18
+ console.error("\x1b[31mError: PRIVATE_KEY_WIF environment variable is not set\x1b[0m");
19
+ console.error("Please set this variable with a valid Bitcoin SV private key in WIF format");
20
+ console.error("Example: PRIVATE_KEY_WIF=your_private_key_wif bun run index.ts");
21
+ process.exit(1);
22
+ }
23
+
24
+ // Validate the private key format
25
+ try {
26
+ return PrivateKey.fromWif(privateKeyWif);
27
+ } catch (error) {
28
+ console.error("\x1b[31mError: Invalid private key format\x1b[0m");
29
+ console.error("The PRIVATE_KEY_WIF provided is not a valid Bitcoin SV private key in WIF format");
30
+ console.error("Please check your key and try again");
31
+ process.exit(1);
32
+ }
33
+ }
34
+
35
+ // Validate private key early before starting the server
36
+ const privKey = validatePrivateKey();
37
+ console.log("\x1b[32mPrivate key validated successfully\x1b[0m");
38
+
9
39
  const server = new McpServer({
10
40
  name: "Bitcoin SV MCP",
11
41
  version: "0.0.1",
12
42
  });
13
43
 
14
- // Singleton wallet instance (for demo, could be replaced with real key management)
15
- // If PRIVATE_KEY_WIF is set in the environment, use it to instantiate the Wallet
16
- const privateKeyWif = process.env.PRIVATE_KEY_WIF;
17
- const privKey = privateKeyWif ? PrivateKey.fromWif(privateKeyWif) : undefined;
18
- const wallet = privKey ? new Wallet(privKey) : new Wallet();
44
+ // Initialize wallet with the validated private key
45
+ const wallet = new Wallet(privKey);
19
46
 
20
47
  // Register wallet tools separately (needs wallet instance)
21
48
  registerWalletTools(server, wallet);
@@ -25,7 +52,7 @@ registerAllTools(server);
25
52
 
26
53
  // Debug: Log all registered tools
27
54
  console.log("Registered tools:", Object.keys(server));
28
- console.log("MCP Server:", server);
55
+ console.log("MCP Server ready 🚀");
29
56
 
30
57
  // Connect to the transport
31
58
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "bsv-mcp",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.0.7",
5
+ "version": "0.0.9",
6
6
  "bin": {
7
7
  "bsv-mcp": "./index.ts"
8
8
  },
package/smithery.yaml CHANGED
@@ -1,3 +1,6 @@
1
+ build:
2
+ dockerBuildPath: ./
3
+
1
4
  startCommand:
2
5
  type: stdio
3
6
  configSchema:
@@ -7,15 +10,12 @@ startCommand:
7
10
  properties:
8
11
  privateKeyWif:
9
12
  type: string
10
- description: "Your private key WIF (Wallet Import Format) for Bitcoin SV transactions. This key is used to sign transactions and is required for wallet operations."
13
+ description: "The private key WIF (Wallet Import Format) for Bitcoin SV transactions. This key is used to sign transactions and is required for wallet operations."
11
14
  commandFunction: |
12
- (config) => ({
13
- "command": "bun",
14
- "args": [
15
- "run",
16
- "index.ts"
17
- ],
18
- "env": {
19
- "PRIVATE_KEY_WIF": config.privateKeyWif
20
- }
15
+ (config) => ({
16
+ command: 'bun',
17
+ args: ['run', 'index.ts'],
18
+ env: {
19
+ PRIVATE_KEY_WIF: config.privateKeyWif
20
+ }
21
21
  })
@@ -0,0 +1,177 @@
1
+ import { z } from "zod";
2
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+
4
+ // Base URL for WhatsOnChain API
5
+ const WOC_API_BASE_URL = "https://api.whatsonchain.com/v1/bsv";
6
+
7
+ /**
8
+ * WhatsOnChain API endpoints available for exploration
9
+ */
10
+ enum ExploreEndpoint {
11
+ // Chain endpoints
12
+ CHAIN_INFO = "chain_info",
13
+ CHAIN_TIPS = "chain_tips",
14
+ CIRCULATING_SUPPLY = "circulating_supply",
15
+ PEER_INFO = "peer_info",
16
+
17
+ // Block endpoints
18
+ BLOCK_BY_HASH = "block_by_hash",
19
+ BLOCK_BY_HEIGHT = "block_by_height",
20
+
21
+ // Transaction endpoints
22
+ TX_BY_HASH = "tx_by_hash",
23
+ TX_RAW = "tx_raw",
24
+ TX_RECEIPT = "tx_receipt",
25
+ ADDRESS_HISTORY = "address_history",
26
+ ADDRESS_UTXOS = "address_utxos",
27
+
28
+ // Health endpoint
29
+ HEALTH = "health",
30
+ }
31
+
32
+ enum Network {
33
+ MAIN = "main",
34
+ TEST = "test",
35
+ }
36
+
37
+ // Schema for the bsv_explore tool arguments
38
+ const exploreArgsSchema = z.object({
39
+ endpoint: z.nativeEnum(ExploreEndpoint).describe("WhatsOnChain API endpoint to call"),
40
+ network: z.nativeEnum(Network).default(Network.MAIN).describe("Network to use (main or test)"),
41
+
42
+ // Parameters for specific endpoints
43
+ blockHash: z.string().optional().describe("Block hash (required for block_by_hash endpoint)"),
44
+ blockHeight: z.number().optional().describe("Block height (required for block_by_height endpoint)"),
45
+ txHash: z.string().optional().describe("Transaction hash (required for tx_by_hash, tx_raw, and tx_receipt endpoints)"),
46
+ address: z.string().optional().describe("Bitcoin address (required for address_history and address_utxos endpoints)"),
47
+ limit: z.number().optional().describe("Limit for paginated results (optional for address_history)"),
48
+ });
49
+
50
+ // Type for the tool arguments
51
+ type ExploreArgs = z.infer<typeof exploreArgsSchema>;
52
+
53
+ /**
54
+ * Register the bsv_explore tool with the MCP server
55
+ * @param server The MCP server instance
56
+ */
57
+ export function registerExploreTool(server: McpServer): void {
58
+ server.tool(
59
+ "bsv_explore",
60
+ "Explore Bitcoin SV blockchain data using the WhatsOnChain API. Access multiple data types:\n\n" +
61
+ "CHAIN DATA:\n" +
62
+ "- chain_info: Network stats, difficulty, and chain work\n" +
63
+ "- chain_tips: Current chain tips including heights and states\n" +
64
+ "- circulating_supply: Current BSV circulating supply\n" +
65
+ "- peer_info: Connected peer statistics\n\n" +
66
+ "BLOCK DATA:\n" +
67
+ "- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
68
+ "- block_by_height: Complete block data via height (requires blockHeight parameter)\n\n" +
69
+ "TRANSACTION DATA:\n" +
70
+ "- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
71
+ "- tx_raw: Raw transaction hex data (requires txHash parameter)\n" +
72
+ "- tx_receipt: Transaction receipt (requires txHash parameter)\n\n" +
73
+ "ADDRESS DATA:\n" +
74
+ "- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
75
+ "- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
76
+ "NETWORK:\n" +
77
+ "- health: API health check\n\n" +
78
+ "Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
79
+ { args: exploreArgsSchema },
80
+ async ({ args }) => {
81
+ try {
82
+ const params = exploreArgsSchema.parse(args);
83
+
84
+ // Validate required parameters for specific endpoints
85
+ if (params.endpoint === ExploreEndpoint.BLOCK_BY_HASH && !params.blockHash) {
86
+ throw new Error("blockHash is required for block_by_hash endpoint");
87
+ }
88
+
89
+ if (params.endpoint === ExploreEndpoint.BLOCK_BY_HEIGHT && params.blockHeight === undefined) {
90
+ throw new Error("blockHeight is required for block_by_height endpoint");
91
+ }
92
+
93
+ if ([ExploreEndpoint.TX_BY_HASH, ExploreEndpoint.TX_RAW, ExploreEndpoint.TX_RECEIPT].includes(params.endpoint) && !params.txHash) {
94
+ throw new Error("txHash is required for transaction endpoints");
95
+ }
96
+
97
+ if ([ExploreEndpoint.ADDRESS_HISTORY, ExploreEndpoint.ADDRESS_UTXOS].includes(params.endpoint) && !params.address) {
98
+ throw new Error("address is required for address endpoints");
99
+ }
100
+
101
+ // Build API URL based on the selected endpoint
102
+ let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
103
+
104
+ switch (params.endpoint) {
105
+ case ExploreEndpoint.CHAIN_INFO:
106
+ apiUrl += "/chain/info";
107
+ break;
108
+ case ExploreEndpoint.CHAIN_TIPS:
109
+ apiUrl += "/chain/tips";
110
+ break;
111
+ case ExploreEndpoint.CIRCULATING_SUPPLY:
112
+ apiUrl += "/circulatingsupply";
113
+ break;
114
+ case ExploreEndpoint.PEER_INFO:
115
+ apiUrl += "/peer/info";
116
+ break;
117
+ case ExploreEndpoint.BLOCK_BY_HASH:
118
+ apiUrl += `/block/hash/${params.blockHash}`;
119
+ break;
120
+ case ExploreEndpoint.BLOCK_BY_HEIGHT:
121
+ apiUrl += `/block/height/${params.blockHeight}`;
122
+ break;
123
+ case ExploreEndpoint.TX_BY_HASH:
124
+ apiUrl += `/tx/hash/${params.txHash}`;
125
+ break;
126
+ case ExploreEndpoint.TX_RAW:
127
+ apiUrl += `/tx/${params.txHash}/hex`;
128
+ break;
129
+ case ExploreEndpoint.TX_RECEIPT:
130
+ apiUrl += `/tx/${params.txHash}/receipt`;
131
+ break;
132
+ case ExploreEndpoint.ADDRESS_HISTORY:
133
+ apiUrl += `/address/${params.address}/history`;
134
+ if (params.limit !== undefined) {
135
+ apiUrl += `?limit=${params.limit}`;
136
+ }
137
+ break;
138
+ case ExploreEndpoint.ADDRESS_UTXOS:
139
+ apiUrl += `/address/${params.address}/unspent`;
140
+ break;
141
+ case ExploreEndpoint.HEALTH:
142
+ apiUrl += "/woc";
143
+ break;
144
+ default:
145
+ throw new Error(`Unsupported endpoint: ${params.endpoint}`);
146
+ }
147
+
148
+ // Make the API request
149
+ const response = await fetch(apiUrl);
150
+ if (!response.ok) {
151
+ throw new Error(`API error: ${response.status} ${response.statusText}`);
152
+ }
153
+
154
+ const result = await response.json();
155
+
156
+ return {
157
+ content: [
158
+ {
159
+ type: "text",
160
+ text: JSON.stringify(result, null, 2),
161
+ },
162
+ ],
163
+ };
164
+ } catch (error) {
165
+ return {
166
+ content: [
167
+ {
168
+ type: "text",
169
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`
170
+ }
171
+ ],
172
+ isError: true,
173
+ };
174
+ }
175
+ }
176
+ );
177
+ }
@@ -1,6 +1,48 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
 
4
+ // Define cache duration (5 minutes in milliseconds)
5
+ const PRICE_CACHE_DURATION = 5 * 60 * 1000;
6
+
7
+ // Cache object to store price data
8
+ let cachedPrice: { value: number; timestamp: number } | null = null;
9
+
10
+ /**
11
+ * Get the BSV price with caching mechanism
12
+ * @returns The current BSV price in USD
13
+ */
14
+ async function getBsvPriceWithCache(): Promise<number> {
15
+ // Return cached price if it's still valid
16
+ if (cachedPrice && (Date.now() - cachedPrice.timestamp) < PRICE_CACHE_DURATION) {
17
+ console.log("Using cached BSV price");
18
+ return cachedPrice.value;
19
+ }
20
+
21
+ console.log("Fetching fresh BSV price");
22
+ // If no valid cache, fetch new price
23
+ const res = await fetch(
24
+ "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
25
+ );
26
+ if (!res.ok) throw new Error("Failed to fetch price");
27
+
28
+ const data = (await res.json()) as {
29
+ currency: string;
30
+ rate: string;
31
+ time: number;
32
+ };
33
+
34
+ const price = Number(data.rate);
35
+ if (Number.isNaN(price) || price <= 0) throw new Error("Invalid price received");
36
+
37
+ // Update cache
38
+ cachedPrice = {
39
+ value: price,
40
+ timestamp: Date.now()
41
+ };
42
+
43
+ return price;
44
+ }
45
+
4
46
  /**
5
47
  * Register the BSV price lookup tool
6
48
  * @param server The MCP server instance
@@ -14,23 +56,12 @@ export function registerGetPriceTool(server: McpServer): void {
14
56
  },
15
57
  async () => {
16
58
  try {
17
- const res = await fetch(
18
- "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
19
- );
20
- if (!res.ok) throw new Error("Failed to fetch price");
21
- const data = (await res.json()) as {
22
- currency: string;
23
- rate: string;
24
- time: number;
25
- };
26
- const price = data.rate;
27
- if (typeof price !== "string" && typeof price !== "number")
28
- throw new Error("Price not found");
59
+ const price = await getBsvPriceWithCache();
29
60
  return {
30
61
  content: [
31
62
  {
32
63
  type: "text",
33
- text: `Current BSV price: $${Number(price).toFixed(2)} USD`,
64
+ text: `Current BSV price: $${price.toFixed(2)} USD`,
34
65
  },
35
66
  ],
36
67
  };
@@ -43,3 +74,6 @@ export function registerGetPriceTool(server: McpServer): void {
43
74
  },
44
75
  );
45
76
  }
77
+
78
+ // Export the cached price getter for use in other modules
79
+ export { getBsvPriceWithCache };
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { registerDecodeTransactionTool } from "./decodeTransaction";
3
3
  import { registerGetPriceTool } from "./getPrice";
4
+ import { registerExploreTool } from "./explore";
4
5
 
5
6
  /**
6
7
  * Register all BSV tools with the MCP server
@@ -10,4 +11,5 @@ export function registerBsvTools(server: McpServer): void {
10
11
  // Register BSV-related tools
11
12
  registerGetPriceTool(server);
12
13
  registerDecodeTransactionTool(server);
14
+ registerExploreTool(server);
13
15
  }
@@ -72,6 +72,19 @@ export const walletDecryptArgsSchema = z.object({
72
72
  privileged: z.boolean().optional(),
73
73
  });
74
74
 
75
+ // Combined wallet encryption/decryption args
76
+ export const walletEncryptionArgsSchema = z.object({
77
+ mode: z.enum(["encrypt", "decrypt"]).describe("Operation mode: 'encrypt' to encrypt data or 'decrypt' to decrypt data"),
78
+ data: z.array(z.number()).describe("Data to process: plaintext for encryption or ciphertext for decryption"),
79
+ protocolID: walletProtocolSchema,
80
+ keyID: z.string(),
81
+ privilegedReason: z.string().optional(),
82
+ counterparty: z
83
+ .union([z.string(), z.literal("self"), z.literal("anyone")])
84
+ .optional(),
85
+ privileged: z.boolean().optional(),
86
+ }).describe("Combined schema for encryption and decryption operations, with a mode parameter to switch between functions");
87
+
75
88
  // Create HMAC arguments
76
89
  export const createHmacArgsSchema = z.object({
77
90
  message: z.string(),
@@ -296,3 +309,4 @@ export const purchaseListingArgsSchema = z.object({
296
309
  // Export types
297
310
  export type SendToAddressArgs = z.infer<typeof sendToAddressArgsSchema>;
298
311
  export type PurchaseListingArgs = z.infer<typeof purchaseListingArgsSchema>;
312
+ export type WalletEncryptionArgs = z.infer<typeof walletEncryptionArgsSchema>;
@@ -5,33 +5,7 @@ import { toSatoshi } from "satoshi-token";
5
5
  import type { z } from "zod";
6
6
  import { sendToAddressArgsSchema } from "./schemas";
7
7
  import type { Wallet } from "./wallet";
8
-
9
- /**
10
- * Fetch the current BSV price from whatsonchain API
11
- * @returns The BSV price in USD
12
- */
13
- async function getBsvPrice(): Promise<number> {
14
- try {
15
- const res = await fetch(
16
- "https://api.whatsonchain.com/v1/bsv/main/exchangerate",
17
- );
18
- if (!res.ok) throw new Error("Failed to fetch BSV price");
19
-
20
- // Parse the response with proper type casting
21
- const data = (await res.json()) as {
22
- rate: string;
23
- currency: string;
24
- time: number;
25
- };
26
- const price = Number(data.rate);
27
-
28
- if (Number.isNaN(price) || price <= 0) throw new Error("Invalid BSV price");
29
- return price;
30
- } catch (error) {
31
- console.error("BSV price fetch error:", error);
32
- throw error;
33
- }
34
- }
8
+ import { getBsvPriceWithCache } from "../bsv/getPrice";
35
9
 
36
10
  // Use the schema imported from schemas.ts
37
11
  export type SendToAddressArgs = z.infer<typeof sendToAddressArgsSchema>;
@@ -62,7 +36,7 @@ export function registerSendToAddressTool(server: McpServer, wallet: Wallet) {
62
36
  let satoshis: number;
63
37
  if (currency === "USD") {
64
38
  // Get current BSV price
65
- const bsvPriceUsd = await getBsvPrice();
39
+ const bsvPriceUsd = await getBsvPriceWithCache();
66
40
 
67
41
  // Convert USD to BSV
68
42
  const bsvAmount = amount / bsvPriceUsd;
@@ -11,15 +11,13 @@ type WalletToolName =
11
11
  | "wallet_getPublicKey"
12
12
  | "wallet_createSignature"
13
13
  | "wallet_verifySignature"
14
- | "wallet_encrypt"
15
- | "wallet_decrypt";
14
+ | "wallet_encryption";
16
15
 
17
16
  const toolNames: WalletToolName[] = [
18
17
  "wallet_getPublicKey",
19
18
  "wallet_createSignature",
20
19
  "wallet_verifySignature",
21
- "wallet_encrypt",
22
- "wallet_decrypt",
20
+ "wallet_encryption",
23
21
  ];
24
22
 
25
23
  // Helper function to get dummy arguments for each tool
@@ -36,10 +34,12 @@ function getDummyArgs(tool: WalletToolName): Record<string, unknown> {
36
34
  signature: "sig",
37
35
  publicKey: "pubkey",
38
36
  };
39
- case "wallet_encrypt":
40
- return { data: "test", publicKey: "pubkey" };
41
- case "wallet_decrypt":
42
- return { encryptedData: "data" };
37
+ // TODO we merged encrypt and decrypt into encryption we need to update the test file
38
+ // case "wallet_encryption":
39
+ // if
40
+ // return { data: "test", publicKey: "pubkey" };
41
+ // case "wallet_decryption":
42
+ // return { encryptedData: "data" };
43
43
  default:
44
44
  return {};
45
45
  }
@@ -31,8 +31,7 @@ import {
31
31
  type purchaseListingArgsSchema,
32
32
  type sendToAddressArgsSchema,
33
33
  verifySignatureArgsSchema,
34
- walletDecryptArgsSchema,
35
- walletEncryptArgsSchema,
34
+ walletEncryptionArgsSchema,
36
35
  } from "./schemas";
37
36
 
38
37
  import { registerCreateOrdinalsTool } from "./createOrdinals";
@@ -46,8 +45,7 @@ type ToolArgSchemas = {
46
45
  wallet_getPublicKey: typeof getPublicKeyArgsSchema;
47
46
  wallet_createSignature: typeof createSignatureArgsSchema;
48
47
  wallet_verifySignature: typeof verifySignatureArgsSchema;
49
- wallet_encrypt: typeof walletEncryptArgsSchema;
50
- wallet_decrypt: typeof walletDecryptArgsSchema;
48
+ wallet_encryption: typeof walletEncryptionArgsSchema;
51
49
  wallet_listActions: typeof listActionsArgsSchema;
52
50
  wallet_listOutputs: typeof listOutputsArgsSchema;
53
51
  wallet_getNetwork: typeof emptyArgsSchema;
@@ -94,11 +92,12 @@ export function registerWalletTools(
94
92
  // Handle tools registration with properly typed parameters
95
93
  function registerTool<T extends z.ZodType>(
96
94
  name: keyof ToolArgSchemas,
95
+ description: string,
97
96
  schema: { args: T },
98
97
  handler: ToolCallback<{ args: T }>,
99
98
  ): void {
100
99
  // Register all tools normally
101
- server.tool(name, schema, handler);
100
+ server.tool(name, description, schema, handler);
102
101
  handlers[name] = handler as ToolHandler;
103
102
  }
104
103
 
@@ -117,6 +116,7 @@ export function registerWalletTools(
117
116
  // Register wallet_getPublicKey
118
117
  registerTool(
119
118
  "wallet_getPublicKey",
119
+ "Retrieves the current wallet's public key. This public key can be used for cryptographic operations like signature verification or encryption.",
120
120
  { args: getPublicKeyArgsSchema },
121
121
  async (
122
122
  { args }: { args: z.infer<typeof getPublicKeyArgsSchema> },
@@ -135,6 +135,7 @@ export function registerWalletTools(
135
135
  // Register wallet_createSignature
136
136
  registerTool(
137
137
  "wallet_createSignature",
138
+ "Creates a cryptographic signature using the wallet's private key. This tool enables secure message signing and transaction authorization, supporting various signature protocols.",
138
139
  { args: createSignatureArgsSchema },
139
140
  async (
140
141
  { args }: { args: z.infer<typeof createSignatureArgsSchema> },
@@ -153,6 +154,7 @@ export function registerWalletTools(
153
154
  // Register wallet_verifySignature
154
155
  registerTool(
155
156
  "wallet_verifySignature",
157
+ "Verifies a cryptographic signature against a message or data. This tool supports various verification protocols and can validate signatures from both the wallet's own keys and external public keys.",
156
158
  { args: verifySignatureArgsSchema },
157
159
  async (
158
160
  { args }: { args: z.infer<typeof verifySignatureArgsSchema> },
@@ -168,16 +170,38 @@ export function registerWalletTools(
168
170
  },
169
171
  );
170
172
 
171
- // Register wallet_encrypt
173
+ // Register combined wallet_encryption tool
172
174
  registerTool(
173
- "wallet_encrypt",
174
- { args: walletEncryptArgsSchema },
175
+ "wallet_encryption",
176
+ "Combined tool for encrypting and decrypting data using the wallet's cryptographic keys. Supports both encryption of plaintext data and decryption of previously encrypted content. Use the 'mode' parameter to switch between operations.",
177
+ { args: walletEncryptionArgsSchema },
175
178
  async (
176
- { args }: { args: z.infer<typeof walletEncryptArgsSchema> },
179
+ { args }: { args: z.infer<typeof walletEncryptionArgsSchema> },
177
180
  extra: RequestHandlerExtra,
178
181
  ) => {
179
182
  try {
180
- const result = await wallet.encrypt(args);
183
+ let result: { ciphertext?: number[]; plaintext?: number[] };
184
+ if (args.mode === "encrypt") {
185
+ // For encryption, the data is treated as plaintext
186
+ result = await wallet.encrypt({
187
+ plaintext: args.data,
188
+ protocolID: args.protocolID,
189
+ keyID: args.keyID,
190
+ privilegedReason: args.privilegedReason,
191
+ counterparty: args.counterparty,
192
+ privileged: args.privileged,
193
+ });
194
+ } else {
195
+ // For decryption, the data is treated as ciphertext
196
+ result = await wallet.decrypt({
197
+ ciphertext: args.data,
198
+ protocolID: args.protocolID,
199
+ keyID: args.keyID,
200
+ privilegedReason: args.privilegedReason,
201
+ counterparty: args.counterparty,
202
+ privileged: args.privileged,
203
+ });
204
+ }
181
205
  return { content: [{ type: "text", text: JSON.stringify(result) }] };
182
206
  } catch (err: unknown) {
183
207
  const msg = err instanceof Error ? err.message : String(err);
@@ -186,27 +210,8 @@ export function registerWalletTools(
186
210
  },
187
211
  );
188
212
 
189
- // Register wallet_decrypt
190
- registerTool(
191
- "wallet_decrypt",
192
- { args: walletDecryptArgsSchema },
193
- async (
194
- { args }: { args: z.infer<typeof walletDecryptArgsSchema> },
195
- extra: RequestHandlerExtra,
196
- ) => {
197
- try {
198
- const result = await wallet.decrypt(args);
199
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
200
- } catch (err: unknown) {
201
- const msg = err instanceof Error ? err.message : String(err);
202
- return { content: [{ type: "text", text: msg }], isError: true };
203
- }
204
- },
205
- );
206
-
207
- // Register ordinals extension tools
208
- // Register the wallet_createOrdinals tool
213
+ // Register createOrdinals tool
209
214
  registerCreateOrdinalsTool(server, wallet);
210
-
215
+
211
216
  return handlers;
212
217
  }
@@ -38,6 +38,7 @@ import type {
38
38
  ListOutputsResult,
39
39
  ProveCertificateArgs,
40
40
  ProveCertificateResult,
41
+ PubKeyHex,
41
42
  RelinquishCertificateArgs,
42
43
  RelinquishCertificateResult,
43
44
  RelinquishOutputArgs,
@@ -127,7 +128,15 @@ export class Wallet extends ProtoWallet implements WalletInterface {
127
128
  }
128
129
 
129
130
  async getPublicKey(args: GetPublicKeyArgs): Promise<GetPublicKeyResult> {
130
- return Promise.reject(new Error("Not implemented"));
131
+ const privateKey = this.getPrivateKey();
132
+ if (!privateKey) {
133
+ throw new Error("No private key available");
134
+ }
135
+
136
+ const publicKey = privateKey.toPublicKey();
137
+ return {
138
+ publicKey: publicKey.toDER("hex") as PubKeyHex,
139
+ };
131
140
  }
132
141
  async revealCounterpartyKeyLinkage(
133
142
  args: RevealCounterpartyKeyLinkageArgs,