bsv-mcp 0.0.36 → 0.2.7
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 +124 -0
- package/README.md +233 -4
- package/dist/index.js +120082 -54669
- package/index.ts +1196 -88
- package/next-env.d.ts +6 -0
- package/package.json +58 -15
- package/prompts/bsvSdk/index.ts +5 -5
- package/prompts/bsvSdk/transaction.ts +2 -2
- package/prompts/bsvSdk/wallet.ts +1 -1
- package/prompts/index.ts +1 -1
- package/resources/bitcom.ts +132 -0
- package/resources/resources.ts +7 -3
- package/smithery.yaml +3 -0
- package/test-mcp-server.ts +157 -0
- package/tools/a2b/discover.ts +13 -13
- package/tools/bap/friend.ts +266 -0
- package/tools/bap/generate.test.ts +350 -0
- package/tools/bap/generate.ts +431 -0
- package/tools/bap/getCurrentAddress.ts +69 -0
- package/tools/bap/getId.test.ts +121 -0
- package/tools/bap/getId.ts +169 -0
- package/tools/bap/index.ts +111 -0
- package/tools/bap/types.ts +66 -0
- package/tools/bap/utils.ts +33 -0
- package/tools/bsocial/bmapFollow.ts +121 -0
- package/tools/bsocial/bmapLikes.ts +111 -0
- package/tools/bsocial/bmapReadPosts.ts +148 -0
- package/tools/bsocial/createPost.ts +136 -0
- package/tools/bsocial/index.ts +45 -0
- package/tools/bsocial/readPosts.ts +255 -0
- package/tools/bsv/decodeTransaction.ts +7 -6
- package/tools/bsv/explore.ts +2 -3
- package/tools/bsv/getPrice.ts +1 -9
- package/tools/bsv/token.ts +14 -26
- package/tools/constants.ts +21 -0
- package/tools/index.ts +91 -18
- package/tools/mnee/getBalance.ts +2 -4
- package/tools/mnee/index.ts +8 -0
- package/tools/mnee/parseTx.ts +4 -4
- package/tools/mnee/sendMnee.ts +6 -6
- package/tools/ordinals/getInscription.ts +2 -3
- package/tools/ordinals/getTokenByIdOrTicker.ts +6 -3
- package/tools/ordinals/marketListings.ts +2 -18
- package/tools/ordinals/marketSales.ts +2 -4
- package/tools/ordinals/searchInscriptions.ts +2 -4
- package/tools/utils/aip.ts +46 -0
- package/tools/utils/conversion.ts +1 -0
- package/tools/utils/errorHandler.ts +76 -0
- package/tools/utils/index.ts +56 -14
- package/tools/utils/installAgentMaster.ts +84 -0
- package/tools/utils/logger.ts +20 -0
- package/tools/utils/toolConfig.ts +80 -0
- package/tools/utils/toolRegistration.ts +66 -0
- package/tools/utils/transactionBuilder.ts +196 -0
- package/tools/wallet/a2bPublishAgent.ts +25 -30
- package/tools/wallet/a2bPublishMcp.ts +43 -44
- package/tools/wallet/createOrdinals.ts +13 -20
- package/tools/wallet/fetchPaymentUtxos.ts +48 -39
- package/tools/wallet/gatherCollectionInfo.ts +370 -0
- package/tools/wallet/getAddress.ts +14 -30
- package/tools/wallet/getBalance.ts +54 -0
- package/tools/wallet/getBalanceDroplet.ts +48 -0
- package/tools/wallet/getPublicKey.ts +3 -16
- package/tools/wallet/integratedWallet.ts +120 -0
- package/tools/wallet/mintCollection.ts +373 -0
- package/tools/wallet/purchaseListing.ts +21 -31
- package/tools/wallet/refreshUtxos.ts +2 -14
- package/tools/wallet/schemas.ts +18 -249
- package/tools/wallet/sendOrdinals.ts +14 -21
- package/tools/wallet/sendToAddress.ts +2 -18
- package/tools/wallet/setupDroplet.ts +161 -0
- package/tools/wallet/tools.ts +55 -210
- package/tools/wallet/transactionHelper.ts +121 -0
- package/tools/wallet/transferOrdToken.ts +19 -24
- package/tools/wallet/utxo.ts +62 -0
- package/tools/wallet/wallet.ts +237 -234
- package/vite.config.ts +15 -0
- package/tools/a2b/call.ts +0 -32
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { Utils } from "@bsv/sdk";
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
6
|
+
import type {
|
|
7
|
+
ChangeResult,
|
|
8
|
+
CollectionItemSubTypeData,
|
|
9
|
+
CollectionSubTypeData,
|
|
10
|
+
CollectionTraits,
|
|
11
|
+
CreateOrdinalsCollectionItemMetadata,
|
|
12
|
+
CreateOrdinalsCollectionMetadata,
|
|
13
|
+
CreateOrdinalsConfig,
|
|
14
|
+
LocalSigner,
|
|
15
|
+
RarityLabels,
|
|
16
|
+
} from "js-1sat-ord";
|
|
17
|
+
import { createOrdinals } from "js-1sat-ord";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { V5Broadcaster } from "../../utils/broadcaster";
|
|
20
|
+
import type { Wallet } from "./wallet";
|
|
21
|
+
|
|
22
|
+
const mintCollectionArgsSchema = z.object({
|
|
23
|
+
folderPath: z
|
|
24
|
+
.string()
|
|
25
|
+
.describe("Path to folder containing images to mint as a collection"),
|
|
26
|
+
collectionName: z.string().describe("Name of the collection"),
|
|
27
|
+
description: z.string().describe("Description of the collection"),
|
|
28
|
+
rarityLabels: z
|
|
29
|
+
.array(
|
|
30
|
+
z.object({
|
|
31
|
+
label: z.string(),
|
|
32
|
+
percentage: z.number().min(0).max(100),
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Rarity labels and their percentages"),
|
|
37
|
+
traits: z
|
|
38
|
+
.record(z.array(z.string()))
|
|
39
|
+
.optional()
|
|
40
|
+
.describe(
|
|
41
|
+
"Collection traits as key-value pairs where values are arrays of possible trait values",
|
|
42
|
+
),
|
|
43
|
+
skipBroadcast: z
|
|
44
|
+
.boolean()
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Skip broadcasting transactions (for testing)"),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
type MintCollectionArgs = z.infer<typeof mintCollectionArgsSchema>;
|
|
50
|
+
|
|
51
|
+
interface ImageFile {
|
|
52
|
+
path: string;
|
|
53
|
+
name: string;
|
|
54
|
+
data: Uint8Array;
|
|
55
|
+
contentType: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function getImageFiles(folderPath: string): Promise<ImageFile[]> {
|
|
59
|
+
const files = await fs.readdir(folderPath);
|
|
60
|
+
const imageFiles: ImageFile[] = [];
|
|
61
|
+
|
|
62
|
+
const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"];
|
|
63
|
+
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const filePath = path.join(folderPath, file);
|
|
66
|
+
const stat = await fs.stat(filePath);
|
|
67
|
+
|
|
68
|
+
if (stat.isFile()) {
|
|
69
|
+
const ext = path.extname(file).toLowerCase();
|
|
70
|
+
if (imageExtensions.includes(ext)) {
|
|
71
|
+
const data = await fs.readFile(filePath);
|
|
72
|
+
const contentType =
|
|
73
|
+
{
|
|
74
|
+
".jpg": "image/jpeg",
|
|
75
|
+
".jpeg": "image/jpeg",
|
|
76
|
+
".png": "image/png",
|
|
77
|
+
".gif": "image/gif",
|
|
78
|
+
".webp": "image/webp",
|
|
79
|
+
".svg": "image/svg+xml",
|
|
80
|
+
}[ext] || "application/octet-stream";
|
|
81
|
+
|
|
82
|
+
imageFiles.push({
|
|
83
|
+
path: filePath,
|
|
84
|
+
name: path.basename(file, ext),
|
|
85
|
+
data,
|
|
86
|
+
contentType,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return imageFiles.sort((a, b) => a.name.localeCompare(b.name));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function generateItemTraits(
|
|
96
|
+
itemIndex: number,
|
|
97
|
+
collectionTraits?: Record<string, string[]>,
|
|
98
|
+
): Array<{ name: string; value: string }> {
|
|
99
|
+
const traits: Array<{ name: string; value: string }> = [];
|
|
100
|
+
|
|
101
|
+
if (collectionTraits) {
|
|
102
|
+
// Distribute traits across items deterministically
|
|
103
|
+
for (const [traitName, possibleValues] of Object.entries(
|
|
104
|
+
collectionTraits,
|
|
105
|
+
)) {
|
|
106
|
+
if (possibleValues.length > 0) {
|
|
107
|
+
const valueIndex = itemIndex % possibleValues.length;
|
|
108
|
+
traits.push({
|
|
109
|
+
name: traitName as string, // Object.entries always produces string keys
|
|
110
|
+
value: possibleValues[valueIndex],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return traits;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function registerMintCollectionTool(server: McpServer, wallet: Wallet) {
|
|
120
|
+
server.tool(
|
|
121
|
+
"wallet_mintCollection",
|
|
122
|
+
"Mint a collection of ordinals from a folder of images with proper metadata. This tool creates a collection inscription first, then mints each image as a collection item with the appropriate metadata linking it to the collection.",
|
|
123
|
+
{ ...mintCollectionArgsSchema.shape },
|
|
124
|
+
async ({ folderPath, collectionName, description, rarityLabels, traits, skipBroadcast }): Promise<CallToolResult> => {
|
|
125
|
+
try {
|
|
126
|
+
// Get keys from wallet
|
|
127
|
+
const paymentPk = wallet.getPaymentKey();
|
|
128
|
+
if (!paymentPk) {
|
|
129
|
+
throw new Error("No payment key available in wallet");
|
|
130
|
+
}
|
|
131
|
+
const identityPk = wallet.getIdentityKey();
|
|
132
|
+
|
|
133
|
+
// Get image files
|
|
134
|
+
const imageFiles = await getImageFiles(folderPath);
|
|
135
|
+
if (imageFiles.length === 0) {
|
|
136
|
+
throw new Error("No image files found in the specified folder");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.error(`Found ${imageFiles.length} images to mint`);
|
|
140
|
+
|
|
141
|
+
// Prepare collection metadata
|
|
142
|
+
const collectionSubTypeData: CollectionSubTypeData = {
|
|
143
|
+
description: description,
|
|
144
|
+
quantity: imageFiles.length,
|
|
145
|
+
// Transform rarity labels to correct format
|
|
146
|
+
rarityLabels:
|
|
147
|
+
rarityLabels?.map((r) => ({ label: r.label })) || [],
|
|
148
|
+
// Transform traits - need to match the CollectionTraits type
|
|
149
|
+
traits: traits
|
|
150
|
+
? Object.entries(traits).reduce((acc, [key, values]) => {
|
|
151
|
+
acc[key] = {
|
|
152
|
+
values,
|
|
153
|
+
occurancePercentages: values.map(() =>
|
|
154
|
+
String(100 / values.length),
|
|
155
|
+
),
|
|
156
|
+
};
|
|
157
|
+
return acc;
|
|
158
|
+
}, {} as CollectionTraits)
|
|
159
|
+
: {},
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const collectionMetadata: CreateOrdinalsCollectionMetadata = {
|
|
163
|
+
app: "ord",
|
|
164
|
+
type: "ord",
|
|
165
|
+
subType: "collection",
|
|
166
|
+
name: collectionName,
|
|
167
|
+
subTypeData: collectionSubTypeData,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Create collection icon
|
|
171
|
+
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
|
172
|
+
<rect width="100" height="100" fill="#f0f0f0"/>
|
|
173
|
+
<text x="50" y="55" text-anchor="middle" font-family="Arial" font-size="16">${collectionName}</text>
|
|
174
|
+
</svg>`;
|
|
175
|
+
const collectionIconData = Utils.toBase64(
|
|
176
|
+
Utils.toArray(svgString, "utf8"),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const walletAddress = paymentPk.toAddress().toString();
|
|
180
|
+
|
|
181
|
+
// Get UTXOs for collection
|
|
182
|
+
const { paymentUtxos: collectionUtxos } = await wallet.getUtxos();
|
|
183
|
+
if (!collectionUtxos || collectionUtxos.length === 0) {
|
|
184
|
+
throw new Error("No payment UTXOs available");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Create collection config
|
|
188
|
+
const collectionConfig: CreateOrdinalsConfig = {
|
|
189
|
+
utxos: collectionUtxos,
|
|
190
|
+
destinations: [
|
|
191
|
+
{
|
|
192
|
+
address: walletAddress,
|
|
193
|
+
inscription: {
|
|
194
|
+
dataB64: collectionIconData,
|
|
195
|
+
contentType: "image/svg+xml",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
paymentPk,
|
|
200
|
+
changeAddress: walletAddress,
|
|
201
|
+
metaData: collectionMetadata,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (identityPk) {
|
|
205
|
+
collectionConfig.signer = { idKey: identityPk } as LocalSigner;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.error("Creating collection inscription...");
|
|
209
|
+
const collectionResult = (await createOrdinals(
|
|
210
|
+
collectionConfig,
|
|
211
|
+
)) as ChangeResult;
|
|
212
|
+
|
|
213
|
+
let collectionTxid = "";
|
|
214
|
+
const disableBroadcasting =
|
|
215
|
+
skipBroadcast || process.env.DISABLE_BROADCASTING === "true";
|
|
216
|
+
|
|
217
|
+
if (!disableBroadcasting) {
|
|
218
|
+
const broadcaster = new V5Broadcaster();
|
|
219
|
+
await collectionResult.tx.broadcast(broadcaster);
|
|
220
|
+
collectionTxid = collectionResult.tx.id("hex");
|
|
221
|
+
console.error(`✅ Collection created: ${collectionTxid}`);
|
|
222
|
+
|
|
223
|
+
// Refresh UTXOs after spending
|
|
224
|
+
await wallet.refreshUtxos();
|
|
225
|
+
} else {
|
|
226
|
+
collectionTxid = collectionResult.tx.id("hex");
|
|
227
|
+
console.error(
|
|
228
|
+
`🔸 Collection created (not broadcast): ${collectionTxid}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Mint collection items
|
|
233
|
+
const results = {
|
|
234
|
+
collectionTxid,
|
|
235
|
+
itemTxids: [] as string[],
|
|
236
|
+
errors: [] as Array<{ file: string; error: string }>,
|
|
237
|
+
totalCost: collectionResult.tx.getFee(),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
for (let i = 0; i < imageFiles.length; i++) {
|
|
241
|
+
const imageFile = imageFiles[i];
|
|
242
|
+
if (!imageFile) continue;
|
|
243
|
+
|
|
244
|
+
console.error(
|
|
245
|
+
`Minting item ${i + 1}/${imageFiles.length}: ${imageFile.name}`,
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const itemTraits = generateItemTraits(i, traits);
|
|
250
|
+
|
|
251
|
+
const itemSubTypeData: CollectionItemSubTypeData = {
|
|
252
|
+
collectionId: collectionTxid,
|
|
253
|
+
mintNumber: i + 1,
|
|
254
|
+
traits: itemTraits,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// Assign rarity if labels provided
|
|
258
|
+
if (rarityLabels && rarityLabels.length > 0) {
|
|
259
|
+
const rarityIndex = Math.floor(
|
|
260
|
+
(i / imageFiles.length) * rarityLabels.length,
|
|
261
|
+
);
|
|
262
|
+
const rarityItem =
|
|
263
|
+
rarityLabels[
|
|
264
|
+
Math.min(rarityIndex, rarityLabels.length - 1)
|
|
265
|
+
];
|
|
266
|
+
if (rarityItem) {
|
|
267
|
+
itemSubTypeData.rarityLabel = [
|
|
268
|
+
{ [rarityItem.label]: `${rarityItem.percentage}%` },
|
|
269
|
+
];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const itemMetadata: CreateOrdinalsCollectionItemMetadata = {
|
|
274
|
+
app: "ord",
|
|
275
|
+
type: "ord",
|
|
276
|
+
subType: "collectionItem",
|
|
277
|
+
name: imageFile.name,
|
|
278
|
+
subTypeData: itemSubTypeData,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// Get fresh UTXOs for each item
|
|
282
|
+
const { paymentUtxos: itemUtxos } = await wallet.getUtxos();
|
|
283
|
+
if (!itemUtxos || itemUtxos.length === 0) {
|
|
284
|
+
throw new Error("No payment UTXOs available for item");
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const itemConfig: CreateOrdinalsConfig = {
|
|
288
|
+
utxos: itemUtxos,
|
|
289
|
+
destinations: [
|
|
290
|
+
{
|
|
291
|
+
address: walletAddress,
|
|
292
|
+
inscription: {
|
|
293
|
+
dataB64: Utils.toBase64(Array.from(imageFile.data)),
|
|
294
|
+
contentType: imageFile.contentType,
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
paymentPk,
|
|
299
|
+
changeAddress: walletAddress,
|
|
300
|
+
metaData: itemMetadata,
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (identityPk) {
|
|
304
|
+
itemConfig.signer = { idKey: identityPk } as LocalSigner;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const itemResult = (await createOrdinals(
|
|
308
|
+
itemConfig,
|
|
309
|
+
)) as ChangeResult;
|
|
310
|
+
|
|
311
|
+
if (!disableBroadcasting) {
|
|
312
|
+
const broadcaster = new V5Broadcaster();
|
|
313
|
+
await itemResult.tx.broadcast(broadcaster);
|
|
314
|
+
const itemTxid = itemResult.tx.id("hex");
|
|
315
|
+
results.itemTxids.push(itemTxid);
|
|
316
|
+
console.error(` ✅ Item minted: ${itemTxid}`);
|
|
317
|
+
|
|
318
|
+
// Refresh UTXOs after each item
|
|
319
|
+
await wallet.refreshUtxos();
|
|
320
|
+
} else {
|
|
321
|
+
const itemTxid = itemResult.tx.id("hex");
|
|
322
|
+
results.itemTxids.push(itemTxid);
|
|
323
|
+
console.error(` 🔸 Item minted (not broadcast): ${itemTxid}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
results.totalCost += itemResult.tx.getFee();
|
|
327
|
+
} catch (error) {
|
|
328
|
+
const errorMessage =
|
|
329
|
+
error instanceof Error ? error.message : String(error);
|
|
330
|
+
results.errors.push({ file: imageFile.name, error: errorMessage });
|
|
331
|
+
console.error(
|
|
332
|
+
` ❌ Failed to mint ${imageFile.name}: ${errorMessage}`,
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Return results
|
|
338
|
+
return {
|
|
339
|
+
content: [
|
|
340
|
+
{
|
|
341
|
+
type: "text",
|
|
342
|
+
text: JSON.stringify(
|
|
343
|
+
{
|
|
344
|
+
success: true,
|
|
345
|
+
collectionTxid: results.collectionTxid,
|
|
346
|
+
itemsMinted: results.itemTxids.length,
|
|
347
|
+
totalItems: imageFiles.length,
|
|
348
|
+
errors: results.errors,
|
|
349
|
+
totalCost: results.totalCost,
|
|
350
|
+
summary: `Collection "${collectionName}" created with ${results.itemTxids.length}/${imageFiles.length} items successfully minted.${results.errors.length > 0 ? ` ${results.errors.length} items failed.` : ""}`,
|
|
351
|
+
},
|
|
352
|
+
null,
|
|
353
|
+
2,
|
|
354
|
+
),
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
};
|
|
358
|
+
} catch (error) {
|
|
359
|
+
const errorMessage =
|
|
360
|
+
error instanceof Error ? error.message : String(error);
|
|
361
|
+
return {
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
type: "text",
|
|
365
|
+
text: `Error minting collection: ${errorMessage}`,
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
isError: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
);
|
|
373
|
+
}
|
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import { PrivateKey } from "@bsv/sdk";
|
|
2
2
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
|
4
|
-
import type {
|
|
5
|
-
ServerNotification,
|
|
6
|
-
ServerRequest,
|
|
7
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
8
3
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
9
4
|
import {
|
|
10
5
|
type ChangeResult,
|
|
11
6
|
type ExistingListing,
|
|
12
7
|
type LocalSigner,
|
|
8
|
+
oneSatBroadcaster,
|
|
13
9
|
type Payment,
|
|
14
10
|
type PurchaseOrdListingConfig,
|
|
11
|
+
purchaseOrdListing,
|
|
12
|
+
purchaseOrdTokenListing,
|
|
15
13
|
type Royalty,
|
|
16
14
|
TokenType,
|
|
17
15
|
type TokenUtxo,
|
|
18
16
|
type Utxo,
|
|
19
|
-
oneSatBroadcaster,
|
|
20
|
-
purchaseOrdListing,
|
|
21
|
-
purchaseOrdTokenListing,
|
|
22
17
|
} from "js-1sat-ord";
|
|
23
|
-
import { Sigma } from "sigma-protocol";
|
|
24
|
-
import type { z } from "zod";
|
|
25
18
|
import {
|
|
26
19
|
MARKET_FEE_PERCENTAGE,
|
|
27
20
|
MARKET_WALLET_ADDRESS,
|
|
@@ -86,11 +79,8 @@ export function registerPurchaseListingTool(server: McpServer, wallet: Wallet) {
|
|
|
86
79
|
server.tool(
|
|
87
80
|
"wallet_purchaseListing",
|
|
88
81
|
"Purchases a listing from the Bitcoin SV ordinals marketplace. Supports both NFT purchases (with royalty payments to original creators) and BSV-20/BSV-21 token purchases. The tool handles all aspects of the transaction - from fetching listing details, calculating fees, creating and broadcasting the transaction.",
|
|
89
|
-
{
|
|
90
|
-
async (
|
|
91
|
-
{ args }: { args: z.infer<typeof purchaseListingArgsSchema> },
|
|
92
|
-
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
93
|
-
): Promise<CallToolResult> => {
|
|
82
|
+
{ ...purchaseListingArgsSchema.shape },
|
|
83
|
+
async ({ listingOutpoint, ordAddress, listingType, tokenProtocol, tokenID, description }): Promise<CallToolResult> => {
|
|
94
84
|
try {
|
|
95
85
|
// Load optional identity key for sigma signing
|
|
96
86
|
const identityKeyWif = process.env.IDENTITY_KEY_WIF;
|
|
@@ -108,7 +98,7 @@ export function registerPurchaseListingTool(server: McpServer, wallet: Wallet) {
|
|
|
108
98
|
|
|
109
99
|
// Fetch the listing info directly from the API
|
|
110
100
|
const response = await fetch(
|
|
111
|
-
`https://ordinals.gorillapool.io/api/txos/${
|
|
101
|
+
`https://ordinals.gorillapool.io/api/txos/${listingOutpoint}?script=true`,
|
|
112
102
|
);
|
|
113
103
|
if (!response.ok) {
|
|
114
104
|
throw new Error(
|
|
@@ -138,7 +128,7 @@ export function registerPurchaseListingTool(server: McpServer, wallet: Wallet) {
|
|
|
138
128
|
}
|
|
139
129
|
|
|
140
130
|
// Parse the listing outpoint to get txid and vout
|
|
141
|
-
const [txid, voutStr] =
|
|
131
|
+
const [txid, voutStr] = listingOutpoint.split("_");
|
|
142
132
|
if (!txid) {
|
|
143
133
|
throw new Error("Invalid outpoint format. Expected txid_vout");
|
|
144
134
|
}
|
|
@@ -182,12 +172,12 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
182
172
|
// Create the purchase transaction based on listing type
|
|
183
173
|
let transaction: ChangeResult;
|
|
184
174
|
|
|
185
|
-
if (
|
|
186
|
-
if (!
|
|
175
|
+
if (listingType === "token") {
|
|
176
|
+
if (!tokenProtocol) {
|
|
187
177
|
throw new Error("tokenProtocol is required for token listings");
|
|
188
178
|
}
|
|
189
179
|
|
|
190
|
-
if (!
|
|
180
|
+
if (!tokenID) {
|
|
191
181
|
throw new Error("tokenID is required for token listings");
|
|
192
182
|
}
|
|
193
183
|
|
|
@@ -203,7 +193,7 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
203
193
|
|
|
204
194
|
// Convert the token protocol to the enum type expected by js-1sat-ord
|
|
205
195
|
const protocol =
|
|
206
|
-
|
|
196
|
+
tokenProtocol === "bsv-20" ? TokenType.BSV20 : TokenType.BSV21;
|
|
207
197
|
|
|
208
198
|
// Create a TokenUtxo with the required fields
|
|
209
199
|
const listingUtxo: TokenUtxo = {
|
|
@@ -212,17 +202,17 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
212
202
|
script: listingData.script,
|
|
213
203
|
satoshis: 1, // TokenUtxo's satoshis must be exactly 1
|
|
214
204
|
amt: listingData.data.bsv20.amt,
|
|
215
|
-
id:
|
|
205
|
+
id: tokenID,
|
|
216
206
|
payout: listingData.data.list.payout,
|
|
217
207
|
};
|
|
218
208
|
|
|
219
209
|
transaction = await purchaseOrdTokenListing({
|
|
220
210
|
protocol,
|
|
221
|
-
tokenID:
|
|
211
|
+
tokenID: tokenID,
|
|
222
212
|
utxos: paymentUtxos,
|
|
223
213
|
paymentPk,
|
|
224
214
|
listingUtxo,
|
|
225
|
-
ordAddress:
|
|
215
|
+
ordAddress: ordAddress,
|
|
226
216
|
additionalPayments,
|
|
227
217
|
metaData,
|
|
228
218
|
});
|
|
@@ -257,7 +247,7 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
257
247
|
const purchaseOrdListingConfig: PurchaseOrdListingConfig = {
|
|
258
248
|
utxos: paymentUtxos,
|
|
259
249
|
paymentPk,
|
|
260
|
-
ordAddress:
|
|
250
|
+
ordAddress: ordAddress,
|
|
261
251
|
listing,
|
|
262
252
|
additionalPayments,
|
|
263
253
|
metaData,
|
|
@@ -301,16 +291,16 @@ Please fund this wallet address with enough BSV to cover the purchase price
|
|
|
301
291
|
status: resultStatus,
|
|
302
292
|
message: resultMessage,
|
|
303
293
|
txid: transaction.tx.id("hex"),
|
|
304
|
-
listingOutpoint:
|
|
305
|
-
destinationAddress:
|
|
306
|
-
listingType:
|
|
307
|
-
tokenProtocol:
|
|
308
|
-
tokenID:
|
|
294
|
+
listingOutpoint: listingOutpoint,
|
|
295
|
+
destinationAddress: ordAddress,
|
|
296
|
+
listingType: listingType,
|
|
297
|
+
tokenProtocol: tokenID ? tokenProtocol : undefined,
|
|
298
|
+
tokenID: tokenID,
|
|
309
299
|
price: listingData.data.list.price,
|
|
310
300
|
marketFee,
|
|
311
301
|
marketFeeAddress: MARKET_WALLET_ADDRESS,
|
|
312
302
|
royaltiesPaid:
|
|
313
|
-
|
|
303
|
+
listingType === "nft" &&
|
|
314
304
|
listingData.origin?.data?.map?.royalties
|
|
315
305
|
? JSON.parse(listingData.origin.data.map.royalties)
|
|
316
306
|
: undefined,
|
|
@@ -1,16 +1,7 @@
|
|
|
1
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
2
|
import { toBitcoin } from "satoshi-token";
|
|
8
|
-
import { z } from "zod";
|
|
9
3
|
import type { Wallet } from "./wallet";
|
|
10
4
|
|
|
11
|
-
const refreshUtxosArgsSchema = z.object({});
|
|
12
|
-
type RefreshUtxosArgs = z.infer<typeof refreshUtxosArgsSchema>;
|
|
13
|
-
|
|
14
5
|
/**
|
|
15
6
|
* Registers the wallet_refreshUtxos tool that refreshes and returns the UTXOs for the wallet
|
|
16
7
|
*/
|
|
@@ -18,11 +9,8 @@ export function registerRefreshUtxosTool(server: McpServer, wallet: Wallet) {
|
|
|
18
9
|
server.tool(
|
|
19
10
|
"wallet_refreshUtxos",
|
|
20
11
|
"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
|
-
{
|
|
22
|
-
async (
|
|
23
|
-
{ args }: { args: RefreshUtxosArgs },
|
|
24
|
-
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
25
|
-
) => {
|
|
12
|
+
{},
|
|
13
|
+
async () => {
|
|
26
14
|
try {
|
|
27
15
|
// Force refresh the UTXOs
|
|
28
16
|
await wallet.refreshUtxos();
|