bsv-mcp 0.0.33 → 0.0.34
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/CHANGELOG.md +9 -1
- package/README.md +8 -0
- package/index.ts +9 -3
- package/package.json +1 -1
- package/tools/index.ts +2 -2
- package/tools/wallet/a2bPublishAgent.ts +45 -17
- package/tools/wallet/a2bPublishMcp.ts +65 -35
- package/tools/wallet/createOrdinals.ts +44 -27
- package/tools/wallet/purchaseListing.ts +47 -36
- package/tools/wallet/sendOrdinals.ts +45 -29
- package/tools/wallet/tools.ts +13 -4
- package/tools/wallet/transferOrdToken.ts +35 -12
- package/tools/wallet/wallet.ts +13 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# BSV MCP Server Changelog
|
|
2
2
|
|
|
3
|
+
## v0.0.34 - Transaction Broadcast Control
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
- Added `DISABLE_BROADCASTING` environment variable to control transaction broadcasting behavior
|
|
7
|
+
- When set to "true", transactions are created but not broadcast to the network
|
|
8
|
+
- Returns raw transaction hex instead of broadcasting, useful for testing and review
|
|
9
|
+
- Code cleanup and organization improvements
|
|
10
|
+
|
|
3
11
|
## v0.0.33 - Identity Key Sigma Signing
|
|
4
12
|
|
|
5
13
|
### Features
|
|
6
14
|
- Added optional `IDENTITY_KEY_WIF` environment variable for sigma-protocol signing.
|
|
7
|
-
-
|
|
15
|
+
- `wallet_createOrdinals`, and `wallet_purchaseListing` tools now support signing with an identity key.
|
|
8
16
|
- Updated `README.md` to document `IDENTITY_KEY_WIF` usage and JSON configuration examples.
|
|
9
17
|
|
|
10
18
|
## v0.0.32 - Reliability Improvements
|
package/README.md
CHANGED
|
@@ -365,6 +365,7 @@ The BSV MCP server can be customized using environment variables to enable or di
|
|
|
365
365
|
| `DISABLE_ORDINALS_TOOLS` | `false` | Set to `true` to disable Ordinals/NFT tools |
|
|
366
366
|
| `DISABLE_UTILS_TOOLS` | `false` | Set to `true` to disable utility tools |
|
|
367
367
|
| `IDENTITY_KEY_WIF` | `not set` | Optional WIF for identity key; if set, ordinals inscriptions will be signed with sigma-protocol for authentication, curation, and web-of-trust. |
|
|
368
|
+
| `DISABLE_BROADCASTING` | `false` | Set to `true` to disable transaction broadcasting; returns raw transaction hex instead - useful for testing and transaction review before broadcasting |
|
|
368
369
|
|
|
369
370
|
### Examples
|
|
370
371
|
|
|
@@ -386,6 +387,12 @@ Use all tools except wallet operations:
|
|
|
386
387
|
DISABLE_WALLET_TOOLS=true bunx bsv-mcp@latest
|
|
387
388
|
```
|
|
388
389
|
|
|
390
|
+
Create transactions without broadcasting them (dry-run mode):
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
DISABLE_BROADCASTING=true bunx bsv-mcp@latest
|
|
394
|
+
```
|
|
395
|
+
|
|
389
396
|
## Troubleshooting
|
|
390
397
|
|
|
391
398
|
If you're having issues with the BSV MCP server:
|
|
@@ -427,6 +434,7 @@ For Cursor, check the Cursor MCP logs in Settings → Extensions → Model Conte
|
|
|
427
434
|
|
|
428
435
|
## Recent Updates
|
|
429
436
|
|
|
437
|
+
- **Transaction Broadcast Control**: Added `DISABLE_BROADCASTING` environment variable to prevent transactions from being broadcast to the network
|
|
430
438
|
- **Blockchain Explorer**: Added `bsv_explore` tool for WhatsOnChain API access with mainnet/testnet support
|
|
431
439
|
- **Unified Tools**: Merged `wallet_encrypt`/`wallet_decrypt` into single `wallet_encryption` tool
|
|
432
440
|
- **Enhanced Marketplace**: Support for NFTs, BSV-20/21 tokens in listings, sales and purchases
|
package/index.ts
CHANGED
|
@@ -24,7 +24,10 @@ const CONFIG = {
|
|
|
24
24
|
loadBsvTools: process.env.DISABLE_BSV_TOOLS !== "true",
|
|
25
25
|
loadOrdinalsTools: process.env.DISABLE_ORDINALS_TOOLS !== "true",
|
|
26
26
|
loadUtilsTools: process.env.DISABLE_UTILS_TOOLS !== "true",
|
|
27
|
-
loadA2bTools: process.env.
|
|
27
|
+
loadA2bTools: process.env.ENABLE_A2B_TOOLS === "true",
|
|
28
|
+
|
|
29
|
+
// Transaction broadcasting control
|
|
30
|
+
disableBroadcasting: process.env.DISABLE_BROADCASTING === "true",
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
/**
|
|
@@ -70,7 +73,7 @@ function initializePrivateKey(): PrivateKey | undefined {
|
|
|
70
73
|
const privKey = initializePrivateKey();
|
|
71
74
|
|
|
72
75
|
const server = new McpServer(
|
|
73
|
-
{ name: "Bitcoin SV", version: "0.0.
|
|
76
|
+
{ name: "Bitcoin SV", version: "0.0.34" },
|
|
74
77
|
// {
|
|
75
78
|
// // Advertise only what you actually implement
|
|
76
79
|
// capabilities: {
|
|
@@ -102,7 +105,10 @@ if (CONFIG.loadTools) {
|
|
|
102
105
|
// Initialize wallet with the private key if wallet tools are enabled
|
|
103
106
|
if (CONFIG.loadWalletTools) {
|
|
104
107
|
wallet = new Wallet(privKey);
|
|
105
|
-
registerWalletTools(server, wallet
|
|
108
|
+
registerWalletTools(server, wallet, {
|
|
109
|
+
disableBroadcasting: CONFIG.disableBroadcasting,
|
|
110
|
+
enableA2bTools: CONFIG.loadA2bTools,
|
|
111
|
+
});
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
|
package/package.json
CHANGED
package/tools/index.ts
CHANGED
|
@@ -32,14 +32,14 @@ export function registerAllTools(
|
|
|
32
32
|
enableBsvTools: true,
|
|
33
33
|
enableOrdinalsTools: true,
|
|
34
34
|
enableUtilsTools: true,
|
|
35
|
-
enableA2bTools:
|
|
35
|
+
enableA2bTools: false,
|
|
36
36
|
},
|
|
37
37
|
): void {
|
|
38
38
|
const {
|
|
39
39
|
enableBsvTools = true,
|
|
40
40
|
enableOrdinalsTools = true,
|
|
41
41
|
enableUtilsTools = true,
|
|
42
|
-
enableA2bTools =
|
|
42
|
+
enableA2bTools = false,
|
|
43
43
|
} = config;
|
|
44
44
|
|
|
45
45
|
// Register BSV-related tools
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Utils } from "@bsv/sdk";
|
|
1
|
+
import { PrivateKey, Utils } from "@bsv/sdk";
|
|
2
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
4
|
import type {
|
|
@@ -8,10 +8,13 @@ import type {
|
|
|
8
8
|
import { createOrdinals } from "js-1sat-ord";
|
|
9
9
|
import type {
|
|
10
10
|
ChangeResult,
|
|
11
|
+
CreateOrdinalsConfig,
|
|
11
12
|
Destination,
|
|
12
13
|
Inscription,
|
|
14
|
+
LocalSigner,
|
|
13
15
|
PreMAP,
|
|
14
16
|
} from "js-1sat-ord";
|
|
17
|
+
import { Sigma } from "sigma-protocol";
|
|
15
18
|
import { z } from "zod";
|
|
16
19
|
import type { Wallet } from "./wallet";
|
|
17
20
|
const { toArray, toBase64 } = Utils;
|
|
@@ -246,32 +249,57 @@ export function registerA2bPublishAgentTool(server: McpServer, wallet: Wallet) {
|
|
|
246
249
|
// Default MAP metadata: file path, content type, encoding
|
|
247
250
|
const metaData: PreMAP = { app: "bsv-mcp", type: "a2b" };
|
|
248
251
|
|
|
249
|
-
|
|
250
|
-
const result = await createOrdinals({
|
|
252
|
+
const createOrdinalsConfig: CreateOrdinalsConfig = {
|
|
251
253
|
utxos: paymentUtxos,
|
|
252
254
|
destinations,
|
|
253
255
|
paymentPk,
|
|
254
256
|
changeAddress: walletAddress,
|
|
255
257
|
metaData,
|
|
256
|
-
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const identityPk = process.env.IDENTITY_KEY_WIF
|
|
261
|
+
? PrivateKey.fromWif(process.env.IDENTITY_KEY_WIF)
|
|
262
|
+
: undefined;
|
|
263
|
+
if (identityPk) {
|
|
264
|
+
createOrdinalsConfig.signer = {
|
|
265
|
+
idKey: identityPk,
|
|
266
|
+
} as LocalSigner;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Inscribe the ordinal on-chain via js-1sat-ord
|
|
270
|
+
const result = await createOrdinals(createOrdinalsConfig);
|
|
257
271
|
const changeResult = result as ChangeResult;
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
await
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
|
|
273
|
+
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
274
|
+
if (!disableBroadcasting) {
|
|
275
|
+
await changeResult.tx.broadcast();
|
|
276
|
+
|
|
277
|
+
// Refresh UTXOs
|
|
278
|
+
try {
|
|
279
|
+
await wallet.refreshUtxos();
|
|
280
|
+
} catch {}
|
|
281
|
+
// Return transaction details
|
|
282
|
+
return {
|
|
283
|
+
content: [
|
|
284
|
+
{
|
|
285
|
+
type: "text",
|
|
286
|
+
text: JSON.stringify({
|
|
287
|
+
txid: changeResult.tx.id("hex"),
|
|
288
|
+
spentOutpoints: changeResult.spentOutpoints,
|
|
289
|
+
payChange: changeResult.payChange,
|
|
290
|
+
inscriptionAddress: targetAddress,
|
|
291
|
+
agentCard,
|
|
292
|
+
}),
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
264
298
|
return {
|
|
265
299
|
content: [
|
|
266
300
|
{
|
|
267
301
|
type: "text",
|
|
268
|
-
text:
|
|
269
|
-
txid: changeResult.tx.id("hex"),
|
|
270
|
-
spentOutpoints: changeResult.spentOutpoints,
|
|
271
|
-
payChange: changeResult.payChange,
|
|
272
|
-
inscriptionAddress: targetAddress,
|
|
273
|
-
agentCard,
|
|
274
|
-
}),
|
|
302
|
+
text: changeResult.tx.toHex(),
|
|
275
303
|
},
|
|
276
304
|
],
|
|
277
305
|
};
|
|
@@ -32,9 +32,16 @@ export const a2bPublishMcpArgsSchema = z.object({
|
|
|
32
32
|
command: z.string().describe("The command to execute the tool"),
|
|
33
33
|
args: z.array(z.string()).describe("Arguments to pass to the command"),
|
|
34
34
|
env: z
|
|
35
|
-
.
|
|
35
|
+
.array(
|
|
36
|
+
z.object({
|
|
37
|
+
key: z.string().describe("Environment variable name"),
|
|
38
|
+
description: z
|
|
39
|
+
.string()
|
|
40
|
+
.describe("Description of the environment variable"),
|
|
41
|
+
}),
|
|
42
|
+
)
|
|
36
43
|
.optional()
|
|
37
|
-
.describe("Optional environment variables"),
|
|
44
|
+
.describe("Optional environment variables with descriptions"),
|
|
38
45
|
description: z.string().optional().describe("Optional tool description"),
|
|
39
46
|
destinationAddress: z
|
|
40
47
|
.string()
|
|
@@ -47,7 +54,11 @@ export type A2bPublishMcpArgs = z.infer<typeof a2bPublishMcpArgsSchema>;
|
|
|
47
54
|
/**
|
|
48
55
|
* Registers the wallet_a2bPublishMcp for publishing an MCP tool configuration on-chain
|
|
49
56
|
*/
|
|
50
|
-
export function registerA2bPublishMcpTool(
|
|
57
|
+
export function registerA2bPublishMcpTool(
|
|
58
|
+
server: McpServer,
|
|
59
|
+
wallet: Wallet,
|
|
60
|
+
config: { disableBroadcasting: boolean },
|
|
61
|
+
) {
|
|
51
62
|
server.tool(
|
|
52
63
|
"wallet_a2bPublishMcp",
|
|
53
64
|
"Publish an MCP tool configuration record on-chain via Ordinal inscription",
|
|
@@ -84,7 +95,15 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
|
|
|
84
95
|
const toolConfig: McpConfig = {
|
|
85
96
|
command: args.command,
|
|
86
97
|
args: args.args,
|
|
87
|
-
env: args.env
|
|
98
|
+
env: args.env
|
|
99
|
+
? args.env.reduce(
|
|
100
|
+
(acc, { key, description }) => {
|
|
101
|
+
acc[key] = description;
|
|
102
|
+
return acc;
|
|
103
|
+
},
|
|
104
|
+
{} as Record<string, string>,
|
|
105
|
+
)
|
|
106
|
+
: undefined,
|
|
88
107
|
};
|
|
89
108
|
|
|
90
109
|
// Validate compliance
|
|
@@ -94,7 +113,7 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
|
|
|
94
113
|
const fullConfig = {
|
|
95
114
|
mcpServers: {
|
|
96
115
|
[args.toolName]: {
|
|
97
|
-
description: args.description ||
|
|
116
|
+
description: args.description || "",
|
|
98
117
|
type: "mcp-tool",
|
|
99
118
|
...toolConfig,
|
|
100
119
|
},
|
|
@@ -137,42 +156,53 @@ export function registerA2bPublishMcpTool(server: McpServer, wallet: Wallet) {
|
|
|
137
156
|
finalTx = signResponse.signedTx;
|
|
138
157
|
}
|
|
139
158
|
// Broadcast the transaction
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Refresh UTXOs after spending
|
|
143
|
-
try {
|
|
144
|
-
await wallet.refreshUtxos();
|
|
145
|
-
} catch (refreshError) {
|
|
146
|
-
console.warn(
|
|
147
|
-
"Failed to refresh UTXOs after transaction:",
|
|
148
|
-
refreshError,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Build a nicely formatted result
|
|
153
|
-
const outpointIndex = 0; // First output with the inscription
|
|
154
|
-
const outpoint = `${changeResult.tx.id("hex")}_${outpointIndex}`;
|
|
159
|
+
if (!config.disableBroadcasting) {
|
|
160
|
+
await finalTx.broadcast();
|
|
155
161
|
|
|
156
|
-
|
|
157
|
-
|
|
162
|
+
// Refresh UTXOs after spending
|
|
163
|
+
try {
|
|
164
|
+
await wallet.refreshUtxos();
|
|
165
|
+
} catch (refreshError) {
|
|
166
|
+
console.warn(
|
|
167
|
+
"Failed to refresh UTXOs after transaction:",
|
|
168
|
+
refreshError,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
158
171
|
|
|
172
|
+
// Build a nicely formatted result
|
|
173
|
+
const outpointIndex = 0; // First output with the inscription
|
|
174
|
+
const outpoint = `${finalTx.id("hex")}_${outpointIndex}`;
|
|
175
|
+
|
|
176
|
+
// Tool URL for discovery is the outpoint
|
|
177
|
+
const onchainUrl = `ord://${outpoint}`;
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
content: [
|
|
181
|
+
{
|
|
182
|
+
type: "text",
|
|
183
|
+
text: JSON.stringify(
|
|
184
|
+
{
|
|
185
|
+
status: "success",
|
|
186
|
+
txid: finalTx.id("hex"),
|
|
187
|
+
outpoint,
|
|
188
|
+
onchainUrl,
|
|
189
|
+
toolName: args.toolName,
|
|
190
|
+
description:
|
|
191
|
+
args.description || `MCP Tool: ${args.toolName}`,
|
|
192
|
+
address: targetAddress,
|
|
193
|
+
},
|
|
194
|
+
null,
|
|
195
|
+
2,
|
|
196
|
+
),
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
159
201
|
return {
|
|
160
202
|
content: [
|
|
161
203
|
{
|
|
162
204
|
type: "text",
|
|
163
|
-
text:
|
|
164
|
-
{
|
|
165
|
-
status: "success",
|
|
166
|
-
txid: changeResult.tx.id("hex"),
|
|
167
|
-
outpoint,
|
|
168
|
-
onchainUrl,
|
|
169
|
-
toolName: args.toolName,
|
|
170
|
-
description: args.description || `MCP Tool: ${args.toolName}`,
|
|
171
|
-
address: targetAddress,
|
|
172
|
-
},
|
|
173
|
-
null,
|
|
174
|
-
2,
|
|
175
|
-
),
|
|
205
|
+
text: finalTx.toHex(),
|
|
176
206
|
},
|
|
177
207
|
],
|
|
178
208
|
};
|
|
@@ -11,8 +11,10 @@ import type {
|
|
|
11
11
|
ChangeResult,
|
|
12
12
|
CreateOrdinalsCollectionItemMetadata,
|
|
13
13
|
CreateOrdinalsCollectionMetadata,
|
|
14
|
+
CreateOrdinalsConfig,
|
|
14
15
|
Destination,
|
|
15
16
|
Inscription,
|
|
17
|
+
LocalSigner,
|
|
16
18
|
PreMAP,
|
|
17
19
|
} from "js-1sat-ord";
|
|
18
20
|
import { Sigma } from "sigma-protocol";
|
|
@@ -101,8 +103,7 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
|
|
|
101
103
|
},
|
|
102
104
|
];
|
|
103
105
|
|
|
104
|
-
|
|
105
|
-
const result = await createOrdinals({
|
|
106
|
+
const createOrdinalsConfig: CreateOrdinalsConfig = {
|
|
106
107
|
utxos: paymentUtxos,
|
|
107
108
|
destinations,
|
|
108
109
|
paymentPk,
|
|
@@ -111,41 +112,57 @@ export function registerCreateOrdinalsTool(server: McpServer, wallet: Wallet) {
|
|
|
111
112
|
| PreMAP
|
|
112
113
|
| CreateOrdinalsCollectionMetadata
|
|
113
114
|
| CreateOrdinalsCollectionItemMetadata,
|
|
114
|
-
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (identityPk) {
|
|
118
|
+
createOrdinalsConfig.signer = {
|
|
119
|
+
idKey: identityPk,
|
|
120
|
+
} as LocalSigner;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 6. Create and broadcast the transaction
|
|
124
|
+
const result = await createOrdinals(createOrdinalsConfig);
|
|
115
125
|
|
|
116
126
|
const changeResult = result as ChangeResult;
|
|
117
127
|
|
|
118
128
|
// 7. Optionally sign with identity key and broadcast the transaction
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
129
|
+
|
|
130
|
+
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
131
|
+
if (!disableBroadcasting) {
|
|
132
|
+
await changeResult.tx.broadcast();
|
|
133
|
+
|
|
134
|
+
// 8. Refresh the wallet's UTXOs after spending
|
|
135
|
+
try {
|
|
136
|
+
await wallet.refreshUtxos();
|
|
137
|
+
} catch (refreshError) {
|
|
138
|
+
console.warn(
|
|
139
|
+
"Failed to refresh UTXOs after transaction:",
|
|
140
|
+
refreshError,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 9. Return transaction details
|
|
145
|
+
return {
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: "text",
|
|
149
|
+
text: JSON.stringify({
|
|
150
|
+
txid: changeResult.tx.id("hex"),
|
|
151
|
+
spentOutpoints: changeResult.spentOutpoints,
|
|
152
|
+
payChange: changeResult.payChange,
|
|
153
|
+
inscriptionAddress: args.destinationAddress || walletAddress,
|
|
154
|
+
contentType: args.contentType,
|
|
155
|
+
}),
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
135
159
|
}
|
|
136
160
|
|
|
137
|
-
// 9. Return transaction details
|
|
138
161
|
return {
|
|
139
162
|
content: [
|
|
140
163
|
{
|
|
141
164
|
type: "text",
|
|
142
|
-
text:
|
|
143
|
-
txid: changeResult.tx.id("hex"),
|
|
144
|
-
spentOutpoints: changeResult.spentOutpoints,
|
|
145
|
-
payChange: changeResult.payChange,
|
|
146
|
-
inscriptionAddress: args.destinationAddress || walletAddress,
|
|
147
|
-
contentType: args.contentType,
|
|
148
|
-
}),
|
|
165
|
+
text: changeResult.tx.toHex(),
|
|
149
166
|
},
|
|
150
167
|
],
|
|
151
168
|
};
|
|
@@ -9,7 +9,9 @@ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
|
9
9
|
import {
|
|
10
10
|
type ChangeResult,
|
|
11
11
|
type ExistingListing,
|
|
12
|
+
type LocalSigner,
|
|
12
13
|
type Payment,
|
|
14
|
+
type PurchaseOrdListingConfig,
|
|
13
15
|
type Royalty,
|
|
14
16
|
TokenType,
|
|
15
17
|
type TokenUtxo,
|
|
@@ -252,7 +254,7 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
|
|
255
|
-
|
|
257
|
+
const purchaseOrdListingConfig: PurchaseOrdListingConfig = {
|
|
256
258
|
utxos: paymentUtxos,
|
|
257
259
|
paymentPk,
|
|
258
260
|
ordAddress: args.ordAddress,
|
|
@@ -260,7 +262,9 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
260
262
|
additionalPayments,
|
|
261
263
|
metaData,
|
|
262
264
|
royalties,
|
|
263
|
-
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
transaction = await purchaseOrdListing(purchaseOrdListingConfig);
|
|
264
268
|
}
|
|
265
269
|
|
|
266
270
|
// After successful transaction creation, refresh the wallet's UTXOs
|
|
@@ -272,47 +276,54 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
272
276
|
}
|
|
273
277
|
|
|
274
278
|
// Optionally sign with identity key then broadcast the transaction
|
|
275
|
-
let finalTx = transaction.tx;
|
|
276
|
-
if (identityPk) {
|
|
277
|
-
const sigma = new Sigma(finalTx);
|
|
278
|
-
const signResponse = sigma.sign(identityPk);
|
|
279
|
-
finalTx = signResponse.signedTx;
|
|
280
|
-
}
|
|
281
|
-
const broadcastResult = await finalTx.broadcast(oneSatBroadcaster());
|
|
282
279
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
280
|
+
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
281
|
+
if (!disableBroadcasting) {
|
|
282
|
+
const broadcastResult = await transaction.tx.broadcast(
|
|
283
|
+
oneSatBroadcaster(),
|
|
284
|
+
);
|
|
285
|
+
// Handle broadcast response
|
|
286
|
+
const resultStatus =
|
|
287
|
+
typeof broadcastResult === "object" && "status" in broadcastResult
|
|
288
|
+
? broadcastResult.status
|
|
289
|
+
: "unknown";
|
|
288
290
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
const resultMessage =
|
|
292
|
+
typeof broadcastResult === "object" && "error" in broadcastResult
|
|
293
|
+
? broadcastResult.error
|
|
294
|
+
: "Transaction broadcast successful";
|
|
293
295
|
|
|
296
|
+
return {
|
|
297
|
+
content: [
|
|
298
|
+
{
|
|
299
|
+
type: "text",
|
|
300
|
+
text: JSON.stringify({
|
|
301
|
+
status: resultStatus,
|
|
302
|
+
message: resultMessage,
|
|
303
|
+
txid: transaction.tx.id("hex"),
|
|
304
|
+
listingOutpoint: args.listingOutpoint,
|
|
305
|
+
destinationAddress: args.ordAddress,
|
|
306
|
+
listingType: args.listingType,
|
|
307
|
+
tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
|
|
308
|
+
tokenID: args.tokenID,
|
|
309
|
+
price: listingData.data.list.price,
|
|
310
|
+
marketFee,
|
|
311
|
+
marketFeeAddress: MARKET_WALLET_ADDRESS,
|
|
312
|
+
royaltiesPaid:
|
|
313
|
+
args.listingType === "nft" &&
|
|
314
|
+
listingData.origin?.data?.map?.royalties
|
|
315
|
+
? JSON.parse(listingData.origin.data.map.royalties)
|
|
316
|
+
: undefined,
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
};
|
|
321
|
+
}
|
|
294
322
|
return {
|
|
295
323
|
content: [
|
|
296
324
|
{
|
|
297
325
|
type: "text",
|
|
298
|
-
text:
|
|
299
|
-
status: resultStatus,
|
|
300
|
-
message: resultMessage,
|
|
301
|
-
txid: transaction.tx.id("hex"),
|
|
302
|
-
listingOutpoint: args.listingOutpoint,
|
|
303
|
-
destinationAddress: args.ordAddress,
|
|
304
|
-
listingType: args.listingType,
|
|
305
|
-
tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
|
|
306
|
-
tokenID: args.tokenID,
|
|
307
|
-
price: listingData.data.list.price,
|
|
308
|
-
marketFee,
|
|
309
|
-
marketFeeAddress: MARKET_WALLET_ADDRESS,
|
|
310
|
-
royaltiesPaid:
|
|
311
|
-
args.listingType === "nft" &&
|
|
312
|
-
listingData.origin?.data?.map?.royalties
|
|
313
|
-
? JSON.parse(listingData.origin.data.map.royalties)
|
|
314
|
-
: undefined,
|
|
315
|
-
}),
|
|
326
|
+
text: transaction.tx.toHex(),
|
|
316
327
|
},
|
|
317
328
|
],
|
|
318
329
|
};
|
|
@@ -7,7 +7,12 @@ import type {
|
|
|
7
7
|
ServerRequest,
|
|
8
8
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { sendOrdinals } from "js-1sat-ord";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ChangeResult,
|
|
12
|
+
LocalSigner,
|
|
13
|
+
SendOrdinalsConfig,
|
|
14
|
+
} from "js-1sat-ord";
|
|
15
|
+
import { Sigma } from "sigma-protocol";
|
|
11
16
|
import { z } from "zod";
|
|
12
17
|
import type { Wallet } from "./wallet";
|
|
13
18
|
|
|
@@ -92,17 +97,15 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
|
|
|
92
97
|
};
|
|
93
98
|
|
|
94
99
|
const identityKeyWif = process.env.IDENTITY_KEY_WIF;
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
e,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
100
|
+
const identityPk = identityKeyWif
|
|
101
|
+
? PrivateKey.fromWif(identityKeyWif)
|
|
102
|
+
: undefined;
|
|
103
|
+
if (identityPk) {
|
|
104
|
+
sendOrdinalsConfig.signer = {
|
|
105
|
+
idKey: identityPk,
|
|
106
|
+
} as LocalSigner;
|
|
105
107
|
}
|
|
108
|
+
|
|
106
109
|
// Add metadata if provided
|
|
107
110
|
if (args.metadata) {
|
|
108
111
|
sendOrdinalsConfig.metaData = args.metadata;
|
|
@@ -113,32 +116,45 @@ export function registerSendOrdinalsTool(server: McpServer, wallet: Wallet) {
|
|
|
113
116
|
|
|
114
117
|
const result = await sendOrdinals(sendOrdinalsConfig);
|
|
115
118
|
const changeResult = result as ChangeResult;
|
|
119
|
+
// no signing when you send since we don't emit an inscription
|
|
116
120
|
|
|
117
121
|
// 7. Broadcast the transaction
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
122
|
+
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
123
|
+
if (!disableBroadcasting) {
|
|
124
|
+
await changeResult.tx.broadcast();
|
|
125
|
+
|
|
126
|
+
// 8. Refresh the wallet's UTXOs after spending
|
|
127
|
+
try {
|
|
128
|
+
await wallet.refreshUtxos();
|
|
129
|
+
} catch (refreshError) {
|
|
130
|
+
console.warn(
|
|
131
|
+
"Failed to refresh UTXOs after transaction:",
|
|
132
|
+
refreshError,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 9. Return transaction details
|
|
137
|
+
return {
|
|
138
|
+
content: [
|
|
139
|
+
{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: JSON.stringify({
|
|
142
|
+
txid: changeResult.tx.id("hex"),
|
|
143
|
+
spentOutpoints: changeResult.spentOutpoints,
|
|
144
|
+
payChange: changeResult.payChange,
|
|
145
|
+
inscriptionOutpoint: args.inscriptionOutpoint,
|
|
146
|
+
destinationAddress: args.destinationAddress,
|
|
147
|
+
}),
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
};
|
|
128
151
|
}
|
|
129
152
|
|
|
130
|
-
// 9. Return transaction details
|
|
131
153
|
return {
|
|
132
154
|
content: [
|
|
133
155
|
{
|
|
134
156
|
type: "text",
|
|
135
|
-
text:
|
|
136
|
-
txid: changeResult.tx.id("hex"),
|
|
137
|
-
spentOutpoints: changeResult.spentOutpoints,
|
|
138
|
-
payChange: changeResult.payChange,
|
|
139
|
-
inscriptionOutpoint: args.inscriptionOutpoint,
|
|
140
|
-
destinationAddress: args.destinationAddress,
|
|
141
|
-
}),
|
|
157
|
+
text: changeResult.tx.toHex(),
|
|
142
158
|
},
|
|
143
159
|
],
|
|
144
160
|
};
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -99,6 +99,10 @@ type ToolHandlerMap = {
|
|
|
99
99
|
export function registerWalletTools(
|
|
100
100
|
server: McpServer,
|
|
101
101
|
wallet: Wallet,
|
|
102
|
+
config: {
|
|
103
|
+
disableBroadcasting: boolean;
|
|
104
|
+
enableA2bTools: boolean;
|
|
105
|
+
},
|
|
102
106
|
): ToolHandlerMap {
|
|
103
107
|
const handlers = {} as ToolHandlerMap;
|
|
104
108
|
|
|
@@ -129,11 +133,16 @@ export function registerWalletTools(
|
|
|
129
133
|
// Register the wallet_transferOrdToken tool
|
|
130
134
|
registerTransferOrdTokenTool(server, wallet);
|
|
131
135
|
|
|
132
|
-
//
|
|
133
|
-
|
|
136
|
+
// A2B tools have to be explicitly enabled
|
|
137
|
+
if (config.enableA2bTools) {
|
|
138
|
+
// Register the wallet_a2bPublishAgent tool
|
|
139
|
+
// registerA2bPublishAgentTool(server, wallet);
|
|
134
140
|
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
// Register the wallet_a2bPublishMcp tool
|
|
142
|
+
registerA2bPublishMcpTool(server, wallet, {
|
|
143
|
+
disableBroadcasting: config.disableBroadcasting,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
137
146
|
|
|
138
147
|
// Register only the minimal public-facing tools
|
|
139
148
|
// wallet_createAction, wallet_signAction and wallet_getHeight have been removed
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
1
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
3
|
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
4
|
import type {
|
|
@@ -6,6 +7,7 @@ import type {
|
|
|
6
7
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
8
|
import {
|
|
8
9
|
type Distribution,
|
|
10
|
+
type LocalSigner,
|
|
9
11
|
type Payment,
|
|
10
12
|
type TokenChangeResult,
|
|
11
13
|
TokenInputMode,
|
|
@@ -106,26 +108,47 @@ export function registerTransferOrdTokenTool(
|
|
|
106
108
|
},
|
|
107
109
|
};
|
|
108
110
|
|
|
111
|
+
const identityPk = process.env.IDENTITY_KEY_WIF
|
|
112
|
+
? PrivateKey.fromWif(process.env.IDENTITY_KEY_WIF)
|
|
113
|
+
: undefined;
|
|
114
|
+
|
|
115
|
+
if (identityPk) {
|
|
116
|
+
config.signer = {
|
|
117
|
+
idKey: identityPk,
|
|
118
|
+
} as LocalSigner;
|
|
119
|
+
}
|
|
120
|
+
|
|
109
121
|
// execute transfer
|
|
110
122
|
const result: TokenChangeResult = await transferOrdTokens(config);
|
|
111
|
-
|
|
123
|
+
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
|
|
124
|
+
if (!disableBroadcasting) {
|
|
125
|
+
await result.tx.broadcast();
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
// refresh UTXOs
|
|
128
|
+
try {
|
|
129
|
+
await wallet.refreshUtxos();
|
|
130
|
+
} catch {}
|
|
117
131
|
|
|
118
|
-
|
|
132
|
+
// respond
|
|
133
|
+
return {
|
|
134
|
+
content: [
|
|
135
|
+
{
|
|
136
|
+
type: "text",
|
|
137
|
+
text: JSON.stringify({
|
|
138
|
+
txid: result.tx.id("hex"),
|
|
139
|
+
spentOutpoints: result.spentOutpoints,
|
|
140
|
+
payChange: result.payChange,
|
|
141
|
+
tokenChange: result.tokenChange,
|
|
142
|
+
}),
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
};
|
|
146
|
+
}
|
|
119
147
|
return {
|
|
120
148
|
content: [
|
|
121
149
|
{
|
|
122
150
|
type: "text",
|
|
123
|
-
text:
|
|
124
|
-
txid: result.tx.id("hex"),
|
|
125
|
-
spentOutpoints: result.spentOutpoints,
|
|
126
|
-
payChange: result.payChange,
|
|
127
|
-
tokenChange: result.tokenChange,
|
|
128
|
-
}),
|
|
151
|
+
text: result.tx.toHex(),
|
|
129
152
|
},
|
|
130
153
|
],
|
|
131
154
|
};
|
package/tools/wallet/wallet.ts
CHANGED
|
@@ -82,12 +82,20 @@ export class Wallet extends ProtoWallet implements WalletInterface {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
const address = privateKey.toAddress();
|
|
85
|
-
|
|
86
|
-
const utxos = await fetchPayUtxos(address);
|
|
87
|
-
const nftUtxos = await fetchNftUtxos(address);
|
|
88
|
-
this.paymentUtxos = utxos;
|
|
89
|
-
this.nftUtxos = nftUtxos;
|
|
90
85
|
this.lastUtxoFetch = Date.now();
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const utxos = await fetchPayUtxos(address);
|
|
89
|
+
this.paymentUtxos = utxos;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("Error fetching payment UTXOs:", error);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const nftUtxos = await fetchNftUtxos(address);
|
|
95
|
+
this.nftUtxos = nftUtxos;
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error("Error fetching NFT UTXOs:", error);
|
|
98
|
+
}
|
|
91
99
|
} catch (error) {
|
|
92
100
|
console.error("Error refreshing UTXOs:", error);
|
|
93
101
|
throw error;
|