bsv-mcp 0.0.34 → 0.0.36
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 +20 -0
- package/dist/index.js +62893 -0
- package/index.ts +33 -36
- package/package.json +11 -7
- package/resources/junglebus.ts +242 -0
- package/resources/resources.ts +4 -0
- package/tools/a2b/discover.ts +184 -44
- package/tools/wallet/a2bPublishMcp.ts +163 -15
- package/tools/wallet/fetchPaymentUtxos.ts +130 -0
- package/tools/wallet/refreshUtxos.ts +76 -0
- package/tools/wallet/tools.ts +5 -0
- package/tools/wallet/wallet.ts +22 -17
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import { PrivateKey, Utils } from "@bsv/sdk";
|
|
2
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
2
4
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
5
|
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
6
|
import type {
|
|
7
|
+
ClientNotification,
|
|
8
|
+
ClientRequest,
|
|
5
9
|
ServerNotification,
|
|
6
10
|
ServerRequest,
|
|
7
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
12
|
import { createOrdinals } from "js-1sat-ord";
|
|
9
13
|
import type {
|
|
10
14
|
ChangeResult,
|
|
15
|
+
CreateOrdinalsConfig,
|
|
11
16
|
Destination,
|
|
12
17
|
Inscription,
|
|
18
|
+
LocalSigner,
|
|
13
19
|
PreMAP,
|
|
14
20
|
} from "js-1sat-ord";
|
|
15
21
|
import { Sigma } from "sigma-protocol";
|
|
@@ -17,10 +23,16 @@ import { z } from "zod";
|
|
|
17
23
|
import type { Wallet } from "./wallet";
|
|
18
24
|
const { toArray, toBase64 } = Utils;
|
|
19
25
|
|
|
26
|
+
// API endpoint for the A2B Overlay service
|
|
27
|
+
const OVERLAY_API_URL = "https://a2b-overlay-production.up.railway.app/v1";
|
|
28
|
+
|
|
20
29
|
// Schema for the MCP tool configuration
|
|
21
30
|
export const McpConfigSchema = z.object({
|
|
22
31
|
command: z.string().describe("The command to execute the tool"),
|
|
23
32
|
args: z.array(z.string()).describe("Arguments to pass to the command"),
|
|
33
|
+
tools: z.array(z.string()).optional().describe("Available tool names"),
|
|
34
|
+
prompts: z.array(z.string()).optional().describe("Available prompt names"),
|
|
35
|
+
resources: z.array(z.string()).optional().describe("Available resource URIs"),
|
|
24
36
|
env: z.record(z.string()).optional().describe("Environment variables"),
|
|
25
37
|
});
|
|
26
38
|
|
|
@@ -31,6 +43,10 @@ export const a2bPublishMcpArgsSchema = z.object({
|
|
|
31
43
|
toolName: z.string().describe("Human-friendly tool name"),
|
|
32
44
|
command: z.string().describe("The command to execute the tool"),
|
|
33
45
|
args: z.array(z.string()).describe("Arguments to pass to the command"),
|
|
46
|
+
keywords: z
|
|
47
|
+
.array(z.string())
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Optional keywords to improve tool discoverability"),
|
|
34
50
|
env: z
|
|
35
51
|
.array(
|
|
36
52
|
z.object({
|
|
@@ -51,6 +67,119 @@ export const a2bPublishMcpArgsSchema = z.object({
|
|
|
51
67
|
|
|
52
68
|
export type A2bPublishMcpArgs = z.infer<typeof a2bPublishMcpArgsSchema>;
|
|
53
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Call the ingest endpoint to process a transaction
|
|
72
|
+
*/
|
|
73
|
+
async function callIngestEndpoint(txid: string): Promise<boolean> {
|
|
74
|
+
try {
|
|
75
|
+
const response = await fetch(`${OVERLAY_API_URL}/ingest`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
"Content-Type": "application/json",
|
|
79
|
+
},
|
|
80
|
+
body: JSON.stringify({ txid }),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
console.warn(
|
|
85
|
+
`Ingest API returned status ${response.status}: ${response.statusText}`,
|
|
86
|
+
);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const result = await response.json();
|
|
91
|
+
// console.log('Ingest result:', result);
|
|
92
|
+
return true;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn("Error calling ingest endpoint:", error);
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Fetches MCP metadata (tools, prompts, resources) by running the command
|
|
101
|
+
* and connecting to it via the MCP client
|
|
102
|
+
*/
|
|
103
|
+
async function fetchMcpMetadata(
|
|
104
|
+
command: string,
|
|
105
|
+
args: string[],
|
|
106
|
+
): Promise<{
|
|
107
|
+
tools: string[];
|
|
108
|
+
prompts: string[];
|
|
109
|
+
resources: string[];
|
|
110
|
+
}> {
|
|
111
|
+
// console.log(`Fetching MCP metadata by running: ${command} ${args.join(' ')}`);
|
|
112
|
+
|
|
113
|
+
let transport: StdioClientTransport | undefined;
|
|
114
|
+
let client:
|
|
115
|
+
| Client<
|
|
116
|
+
ClientRequest,
|
|
117
|
+
ServerRequest,
|
|
118
|
+
ClientNotification | ServerNotification
|
|
119
|
+
>
|
|
120
|
+
| undefined;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
// Create a transport to the MCP server
|
|
124
|
+
// Pass through all current environment variables to ensure the same configuration
|
|
125
|
+
transport = new StdioClientTransport({
|
|
126
|
+
command,
|
|
127
|
+
args,
|
|
128
|
+
env: {
|
|
129
|
+
...process.env, // Pass through all current environment variables
|
|
130
|
+
DISABLE_BROADCASTING: "true", // Prevent actual broadcasting during tool discovery
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Create and connect a client
|
|
135
|
+
client = new Client({
|
|
136
|
+
name: "metadata-fetcher",
|
|
137
|
+
version: "1.0.0",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await client.connect(transport);
|
|
141
|
+
|
|
142
|
+
// Fetch available tools, prompts, and resources
|
|
143
|
+
const toolsResponse = await client.listTools();
|
|
144
|
+
const promptsResponse = await client.listPrompts();
|
|
145
|
+
const resourcesResponse = await client.listResources();
|
|
146
|
+
|
|
147
|
+
// Extract the names/URIs
|
|
148
|
+
const tools = toolsResponse.tools.map((tool) => tool.name);
|
|
149
|
+
const prompts = promptsResponse.prompts.map((prompt) => prompt.name);
|
|
150
|
+
const resources = resourcesResponse.resources.map(
|
|
151
|
+
(resource) => resource.uri,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
// Add known tools that might be missing, avoid duplicates
|
|
155
|
+
const allTools = [...new Set([...tools])];
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
tools: allTools,
|
|
159
|
+
prompts,
|
|
160
|
+
resources,
|
|
161
|
+
};
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("Error fetching MCP metadata:", error);
|
|
164
|
+
// Return hardcoded list of known tools if fetching fails
|
|
165
|
+
return {
|
|
166
|
+
tools: [],
|
|
167
|
+
prompts: [],
|
|
168
|
+
resources: [],
|
|
169
|
+
};
|
|
170
|
+
} finally {
|
|
171
|
+
// Clean up resources
|
|
172
|
+
if (transport) {
|
|
173
|
+
try {
|
|
174
|
+
// Close the transport to shut down the child process
|
|
175
|
+
await transport.close();
|
|
176
|
+
} catch (e) {
|
|
177
|
+
console.error("Error closing transport:", e);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
54
183
|
/**
|
|
55
184
|
* Registers the wallet_a2bPublishMcp for publishing an MCP tool configuration on-chain
|
|
56
185
|
*/
|
|
@@ -61,7 +190,7 @@ export function registerA2bPublishMcpTool(
|
|
|
61
190
|
) {
|
|
62
191
|
server.tool(
|
|
63
192
|
"wallet_a2bPublishMcp",
|
|
64
|
-
"Publish an MCP tool configuration record on-chain via Ordinal inscription",
|
|
193
|
+
"Publish an MCP tool configuration record on-chain via Ordinal inscription. This creates a permanent, immutable, and discoverable tool definition that can be accessed by other MCP servers. The tool is published as a JSON inscription with metadata and optional digital signatures for authenticity verification.",
|
|
65
194
|
{ args: a2bPublishMcpArgsSchema },
|
|
66
195
|
async (
|
|
67
196
|
{ args }: { args: A2bPublishMcpArgs },
|
|
@@ -91,10 +220,17 @@ export function registerA2bPublishMcpTool(
|
|
|
91
220
|
|
|
92
221
|
const walletAddress = paymentPk.toAddress().toString();
|
|
93
222
|
|
|
223
|
+
// Fetch MCP metadata (tools, prompts, resources)
|
|
224
|
+
const metadata = await fetchMcpMetadata(args.command, args.args);
|
|
225
|
+
// console.log(`Discovered ${metadata.tools.length} tools, ${metadata.prompts.length} prompts, and ${metadata.resources.length} resources`);
|
|
226
|
+
|
|
94
227
|
// Assemble tool configuration
|
|
95
228
|
const toolConfig: McpConfig = {
|
|
96
229
|
command: args.command,
|
|
97
230
|
args: args.args,
|
|
231
|
+
tools: metadata.tools,
|
|
232
|
+
prompts: metadata.prompts,
|
|
233
|
+
resources: metadata.resources,
|
|
98
234
|
env: args.env
|
|
99
235
|
? args.env.reduce(
|
|
100
236
|
(acc, { key, description }) => {
|
|
@@ -114,7 +250,10 @@ export function registerA2bPublishMcpTool(
|
|
|
114
250
|
mcpServers: {
|
|
115
251
|
[args.toolName]: {
|
|
116
252
|
description: args.description || "",
|
|
117
|
-
|
|
253
|
+
keywords: args.keywords || [],
|
|
254
|
+
tools: metadata.tools || [],
|
|
255
|
+
prompts: metadata.prompts || [],
|
|
256
|
+
resources: metadata.resources || [],
|
|
118
257
|
...toolConfig,
|
|
119
258
|
},
|
|
120
259
|
},
|
|
@@ -138,26 +277,32 @@ export function registerA2bPublishMcpTool(
|
|
|
138
277
|
// Default MAP metadata: file path, content type, encoding
|
|
139
278
|
const metaData: PreMAP = { app: "bsv-mcp", type: "a2b-mcp" };
|
|
140
279
|
|
|
141
|
-
|
|
142
|
-
const result = await createOrdinals({
|
|
280
|
+
const createOrdinalsConfig = {
|
|
143
281
|
utxos: paymentUtxos,
|
|
144
282
|
destinations,
|
|
145
283
|
paymentPk,
|
|
146
284
|
changeAddress: walletAddress,
|
|
147
285
|
metaData,
|
|
148
|
-
}
|
|
286
|
+
} as CreateOrdinalsConfig;
|
|
287
|
+
if (identityPk) {
|
|
288
|
+
createOrdinalsConfig.signer = {
|
|
289
|
+
idKey: identityPk,
|
|
290
|
+
} as LocalSigner;
|
|
291
|
+
}
|
|
292
|
+
// Inscribe the ordinal on-chain via js-1sat-ord
|
|
293
|
+
const result = await createOrdinals(createOrdinalsConfig);
|
|
149
294
|
|
|
150
295
|
const changeResult = result as ChangeResult;
|
|
151
296
|
|
|
152
|
-
let finalTx = changeResult.tx;
|
|
153
|
-
if (identityPk) {
|
|
154
|
-
const sigma = new Sigma(result.tx);
|
|
155
|
-
const signResponse = sigma.sign(identityPk);
|
|
156
|
-
finalTx = signResponse.signedTx;
|
|
157
|
-
}
|
|
158
297
|
// Broadcast the transaction
|
|
159
298
|
if (!config.disableBroadcasting) {
|
|
160
|
-
await
|
|
299
|
+
await changeResult.tx.broadcast();
|
|
300
|
+
const txid = changeResult.tx.id("hex");
|
|
301
|
+
|
|
302
|
+
setTimeout(async () => {
|
|
303
|
+
// Call the ingest endpoint to process the transaction
|
|
304
|
+
await callIngestEndpoint(txid);
|
|
305
|
+
}, 1000);
|
|
161
306
|
|
|
162
307
|
// Refresh UTXOs after spending
|
|
163
308
|
try {
|
|
@@ -171,7 +316,7 @@ export function registerA2bPublishMcpTool(
|
|
|
171
316
|
|
|
172
317
|
// Build a nicely formatted result
|
|
173
318
|
const outpointIndex = 0; // First output with the inscription
|
|
174
|
-
const outpoint = `${
|
|
319
|
+
const outpoint = `${txid}_${outpointIndex}`;
|
|
175
320
|
|
|
176
321
|
// Tool URL for discovery is the outpoint
|
|
177
322
|
const onchainUrl = `ord://${outpoint}`;
|
|
@@ -183,10 +328,13 @@ export function registerA2bPublishMcpTool(
|
|
|
183
328
|
text: JSON.stringify(
|
|
184
329
|
{
|
|
185
330
|
status: "success",
|
|
186
|
-
txid
|
|
331
|
+
txid,
|
|
187
332
|
outpoint,
|
|
188
333
|
onchainUrl,
|
|
189
334
|
toolName: args.toolName,
|
|
335
|
+
toolCount: metadata.tools.length,
|
|
336
|
+
promptCount: metadata.prompts.length,
|
|
337
|
+
resourceCount: metadata.resources.length,
|
|
190
338
|
description:
|
|
191
339
|
args.description || `MCP Tool: ${args.toolName}`,
|
|
192
340
|
address: targetAddress,
|
|
@@ -202,7 +350,7 @@ export function registerA2bPublishMcpTool(
|
|
|
202
350
|
content: [
|
|
203
351
|
{
|
|
204
352
|
type: "text",
|
|
205
|
-
text:
|
|
353
|
+
text: changeResult.tx.toHex(),
|
|
206
354
|
},
|
|
207
355
|
],
|
|
208
356
|
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { P2PKH, Transaction, Utils } from "@bsv/sdk";
|
|
2
|
+
import type { Utxo } from "js-1sat-ord";
|
|
3
|
+
const { toBase64 } = Utils;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Type definition for WhatsOnChain UTXO response
|
|
7
|
+
*/
|
|
8
|
+
interface WhatsOnChainUtxo {
|
|
9
|
+
tx_hash: string;
|
|
10
|
+
tx_pos: number;
|
|
11
|
+
value: number;
|
|
12
|
+
height: number;
|
|
13
|
+
address?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Fetches unspent transaction outputs (UTXOs) for a given address.
|
|
18
|
+
* Only returns confirmed unspent outputs.
|
|
19
|
+
*
|
|
20
|
+
* @param address - The address to fetch UTXOs for
|
|
21
|
+
* @returns Array of UTXOs or undefined if an error occurs
|
|
22
|
+
*/
|
|
23
|
+
export async function fetchPaymentUtxos(
|
|
24
|
+
address: string,
|
|
25
|
+
): Promise<Utxo[] | undefined> {
|
|
26
|
+
if (!address) {
|
|
27
|
+
console.error("fetchPaymentUtxos: No address provided");
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
// Fetch UTXOs from WhatsOnChain API
|
|
33
|
+
const response = await fetch(
|
|
34
|
+
`https://api.whatsonchain.com/v1/bsv/main/address/${address}/unspent`,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
console.error(
|
|
39
|
+
`WhatsOnChain API error: ${response.status} ${response.statusText}`,
|
|
40
|
+
);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const data = (await response.json()) as WhatsOnChainUtxo[];
|
|
45
|
+
|
|
46
|
+
// Validate response format
|
|
47
|
+
if (!Array.isArray(data)) {
|
|
48
|
+
console.error("Invalid response format from WhatsOnChain API");
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// For testing purposes (FOR TESTING ONLY - REMOVE IN PRODUCTION)
|
|
53
|
+
// const limitUTXOs = data.slice(0, 2);
|
|
54
|
+
|
|
55
|
+
// Process each UTXO
|
|
56
|
+
const utxos: (Utxo | null)[] = await Promise.all(
|
|
57
|
+
data.map(async (utxo: WhatsOnChainUtxo) => {
|
|
58
|
+
// Get the transaction hex to extract the correct script
|
|
59
|
+
const script = await getScriptFromTransaction(
|
|
60
|
+
utxo.tx_hash,
|
|
61
|
+
utxo.tx_pos,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (!script) {
|
|
65
|
+
console.warn(
|
|
66
|
+
`Could not get script for UTXO: ${utxo.tx_hash}:${utxo.tx_pos}`,
|
|
67
|
+
);
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
txid: utxo.tx_hash,
|
|
73
|
+
vout: utxo.tx_pos,
|
|
74
|
+
satoshis: utxo.value,
|
|
75
|
+
script: script,
|
|
76
|
+
};
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Filter out any null entries from failed processing
|
|
81
|
+
const validUtxos = utxos.filter((utxo) => utxo !== null) as Utxo[];
|
|
82
|
+
|
|
83
|
+
return validUtxos;
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error("Error fetching payment UTXOs:", error);
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Gets the script from a transaction for a specific output index
|
|
92
|
+
*
|
|
93
|
+
* @param txid - The transaction ID
|
|
94
|
+
* @param vout - The output index
|
|
95
|
+
* @returns The script as hex string or undefined if an error occurs
|
|
96
|
+
*/
|
|
97
|
+
async function getScriptFromTransaction(
|
|
98
|
+
txid: string,
|
|
99
|
+
vout: number,
|
|
100
|
+
): Promise<string | undefined> {
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(
|
|
103
|
+
`https://api.whatsonchain.com/v1/bsv/main/tx/${txid}/hex`,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
console.error(
|
|
108
|
+
`WhatsOnChain API error fetching tx hex: ${response.status} ${response.statusText}`,
|
|
109
|
+
);
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const txHex = await response.text();
|
|
114
|
+
const tx = Transaction.fromHex(txHex);
|
|
115
|
+
const output = tx.outputs[vout];
|
|
116
|
+
|
|
117
|
+
if (!output) {
|
|
118
|
+
console.error(`Output index ${vout} not found in transaction ${txid}`);
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return toBase64(output.lockingScript.toBinary());
|
|
123
|
+
} catch (error) {
|
|
124
|
+
console.error(
|
|
125
|
+
`Error getting script for transaction ${txid}:${vout}:`,
|
|
126
|
+
error,
|
|
127
|
+
);
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
3
|
+
import type {
|
|
4
|
+
ServerNotification,
|
|
5
|
+
ServerRequest,
|
|
6
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
import { toBitcoin } from "satoshi-token";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import type { Wallet } from "./wallet";
|
|
10
|
+
|
|
11
|
+
const refreshUtxosArgsSchema = z.object({});
|
|
12
|
+
type RefreshUtxosArgs = z.infer<typeof refreshUtxosArgsSchema>;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registers the wallet_refreshUtxos tool that refreshes and returns the UTXOs for the wallet
|
|
16
|
+
*/
|
|
17
|
+
export function registerRefreshUtxosTool(server: McpServer, wallet: Wallet) {
|
|
18
|
+
server.tool(
|
|
19
|
+
"wallet_refreshUtxos",
|
|
20
|
+
"Refreshes and returns the wallet's UTXOs. This is useful for debugging UTXO issues, ensuring the wallet has the latest transaction outputs, and for verifying available funds before making transactions.",
|
|
21
|
+
{ args: refreshUtxosArgsSchema },
|
|
22
|
+
async (
|
|
23
|
+
{ args }: { args: RefreshUtxosArgs },
|
|
24
|
+
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
25
|
+
) => {
|
|
26
|
+
try {
|
|
27
|
+
// Force refresh the UTXOs
|
|
28
|
+
await wallet.refreshUtxos();
|
|
29
|
+
|
|
30
|
+
// Get the refreshed UTXOs
|
|
31
|
+
const { paymentUtxos, nftUtxos } = await wallet.getUtxos();
|
|
32
|
+
|
|
33
|
+
// Calculate total satoshis in payment UTXOs
|
|
34
|
+
const totalSatoshis = paymentUtxos.reduce(
|
|
35
|
+
(sum, utxo) => sum + utxo.satoshis,
|
|
36
|
+
0,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Format the response
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: JSON.stringify(
|
|
45
|
+
{
|
|
46
|
+
status: "success",
|
|
47
|
+
paymentUtxos: paymentUtxos.map((utxo) => ({
|
|
48
|
+
txid: utxo.txid,
|
|
49
|
+
vout: utxo.vout,
|
|
50
|
+
satoshis: utxo.satoshis,
|
|
51
|
+
outpoint: `${utxo.txid}_${utxo.vout}`,
|
|
52
|
+
})),
|
|
53
|
+
nftUtxos: nftUtxos.map((utxo) => ({
|
|
54
|
+
txid: utxo.txid,
|
|
55
|
+
vout: utxo.vout,
|
|
56
|
+
origin: utxo.origin,
|
|
57
|
+
outpoint: `${utxo.txid}_${utxo.vout}`,
|
|
58
|
+
})),
|
|
59
|
+
totalPaymentUtxos: paymentUtxos.length,
|
|
60
|
+
totalNftUtxos: nftUtxos.length,
|
|
61
|
+
totalSatoshis: totalSatoshis,
|
|
62
|
+
totalBsv: toBitcoin(totalSatoshis),
|
|
63
|
+
},
|
|
64
|
+
null,
|
|
65
|
+
2,
|
|
66
|
+
),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
} catch (err: unknown) {
|
|
71
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
72
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
}
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -44,6 +44,7 @@ import type { createOrdinalsArgsSchema } from "./createOrdinals";
|
|
|
44
44
|
import { registerGetAddressTool } from "./getAddress";
|
|
45
45
|
import { registerGetPublicKeyTool } from "./getPublicKey";
|
|
46
46
|
import { registerPurchaseListingTool } from "./purchaseListing";
|
|
47
|
+
import { registerRefreshUtxosTool } from "./refreshUtxos";
|
|
47
48
|
import { registerSendToAddressTool } from "./sendToAddress";
|
|
48
49
|
import { registerTransferOrdTokenTool } from "./transferOrdToken";
|
|
49
50
|
|
|
@@ -83,6 +84,7 @@ type ToolArgSchemas = {
|
|
|
83
84
|
wallet_transferOrdToken: typeof transferOrdTokenArgsSchema;
|
|
84
85
|
wallet_a2bPublish: typeof a2bPublishArgsSchema;
|
|
85
86
|
wallet_createOrdinals: typeof createOrdinalsArgsSchema;
|
|
87
|
+
wallet_refreshUtxos: typeof emptyArgsSchema;
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
// Define a type for the handler function with proper argument types
|
|
@@ -133,6 +135,9 @@ export function registerWalletTools(
|
|
|
133
135
|
// Register the wallet_transferOrdToken tool
|
|
134
136
|
registerTransferOrdTokenTool(server, wallet);
|
|
135
137
|
|
|
138
|
+
// Register the wallet_refreshUtxos tool
|
|
139
|
+
registerRefreshUtxosTool(server, wallet);
|
|
140
|
+
|
|
136
141
|
// A2B tools have to be explicitly enabled
|
|
137
142
|
if (config.enableA2bTools) {
|
|
138
143
|
// Register the wallet_a2bPublishAgent tool
|
package/tools/wallet/wallet.ts
CHANGED
|
@@ -47,12 +47,8 @@ import type {
|
|
|
47
47
|
WalletCertificate,
|
|
48
48
|
WalletInterface,
|
|
49
49
|
} from "@bsv/sdk";
|
|
50
|
-
import {
|
|
51
|
-
|
|
52
|
-
type Utxo,
|
|
53
|
-
fetchNftUtxos,
|
|
54
|
-
fetchPayUtxos,
|
|
55
|
-
} from "js-1sat-ord";
|
|
50
|
+
import { type NftUtxo, type Utxo, fetchNftUtxos } from "js-1sat-ord";
|
|
51
|
+
import { fetchPaymentUtxos } from "./fetchPaymentUtxos";
|
|
56
52
|
|
|
57
53
|
export class Wallet extends ProtoWallet implements WalletInterface {
|
|
58
54
|
private paymentUtxos: Utxo[] = [];
|
|
@@ -77,28 +73,37 @@ export class Wallet extends ProtoWallet implements WalletInterface {
|
|
|
77
73
|
try {
|
|
78
74
|
const privateKey = this.getPrivateKey();
|
|
79
75
|
if (!privateKey) {
|
|
80
|
-
|
|
81
|
-
return;
|
|
76
|
+
return; // Silent fail if no private key, keep existing UTXOs
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
const address = privateKey.toAddress();
|
|
79
|
+
const address = privateKey.toAddress().toString();
|
|
85
80
|
this.lastUtxoFetch = Date.now();
|
|
86
81
|
|
|
82
|
+
// Payment UTXOs
|
|
83
|
+
let newPaymentUtxos: Utxo[] | undefined = undefined;
|
|
87
84
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
newPaymentUtxos = await fetchPaymentUtxos(address);
|
|
86
|
+
// Only update if we successfully got UTXOs
|
|
87
|
+
if (Array.isArray(newPaymentUtxos)) {
|
|
88
|
+
this.paymentUtxos = newPaymentUtxos;
|
|
89
|
+
}
|
|
90
90
|
} catch (error) {
|
|
91
|
-
|
|
91
|
+
// Keep existing UTXOs, don't clear them on error
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
// NFT UTXOs - keep existing if fetch fails
|
|
95
|
+
let newNftUtxos: NftUtxo[] = [];
|
|
93
96
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
newNftUtxos = await fetchNftUtxos(address);
|
|
98
|
+
// Only update if we successfully got UTXOs
|
|
99
|
+
if (Array.isArray(newNftUtxos)) {
|
|
100
|
+
this.nftUtxos = newNftUtxos;
|
|
101
|
+
}
|
|
96
102
|
} catch (error) {
|
|
97
|
-
|
|
103
|
+
// Keep existing UTXOs, don't clear them on error
|
|
98
104
|
}
|
|
99
105
|
} catch (error) {
|
|
100
|
-
|
|
101
|
-
throw error;
|
|
106
|
+
// Silent global error, preserve existing UTXOs
|
|
102
107
|
}
|
|
103
108
|
}
|
|
104
109
|
|