bsv-mcp 0.2.9 → 0.2.11
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 +19 -0
- package/README.md +26 -2
- package/dist/index.js +6777 -126216
- package/index.ts +287 -265
- package/package.json +4 -3
- package/tools/bap/generate.test.ts +17 -16
- package/tools/index.ts +8 -3
- package/tools/ordinals/getInscription.ts +15 -65
- package/tools/ordinals/getTokenByIdOrTicker.ts +14 -80
- package/tools/ordinals/index.ts +8 -7
- package/tools/ordinals/marketListings.ts +21 -188
- package/tools/ordinals/marketSales.ts +21 -127
- package/tools/ordinals/searchInscriptions.ts +21 -108
- package/tools/wallet/brc100.ts +572 -0
- package/tools/wallet/cancelListing.ts +80 -0
- package/tools/wallet/createOrdinals.ts +62 -143
- package/tools/wallet/getAddress.ts +19 -13
- package/tools/wallet/getBalance.ts +29 -25
- package/tools/wallet/getBsv21Balances.ts +28 -0
- package/tools/wallet/getLockData.ts +25 -0
- package/tools/wallet/getOrdinals.ts +35 -0
- package/tools/wallet/listOrdinal.ts +86 -0
- package/tools/wallet/listTokens.ts +28 -0
- package/tools/wallet/lockBsv.ts +69 -0
- package/tools/wallet/opnsDeregister.ts +80 -0
- package/tools/wallet/opnsRegister.ts +80 -0
- package/tools/wallet/purchaseListing.ts +89 -290
- package/tools/wallet/refreshUtxos.ts +21 -32
- package/tools/wallet/schemas.ts +7 -11
- package/tools/wallet/sendAllBsv.ts +64 -0
- package/tools/wallet/sendToAddress.ts +46 -47
- package/tools/wallet/signBsm.ts +29 -0
- package/tools/wallet/sweepBsv.ts +70 -0
- package/tools/wallet/sweepBsv21.ts +71 -0
- package/tools/wallet/sweepOrdinals.ts +69 -0
- package/tools/wallet/tools.ts +50 -88
- package/tools/wallet/transferOrdToken.ts +137 -129
- package/tools/wallet/unlockBsv.ts +57 -0
- package/tools/wallet/a2bPublishAgent.ts +0 -307
- package/tools/wallet/sendOrdinals.ts +0 -160
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import type { OneSatContext } from '@1sat/actions'
|
|
2
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
3
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
const noCtx: CallToolResult = {
|
|
7
|
+
content: [{ type: 'text', text: 'Wallet not initialized.' }],
|
|
8
|
+
isError: true,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function result(data: unknown): CallToolResult {
|
|
12
|
+
return { content: [{ type: 'text', text: JSON.stringify(data) }] }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function error(err: unknown): CallToolResult {
|
|
16
|
+
return {
|
|
17
|
+
content: [{ type: 'text', text: err instanceof Error ? err.message : String(err) }],
|
|
18
|
+
isError: true,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseJSON(s: string | undefined): any {
|
|
23
|
+
if (!s) return undefined
|
|
24
|
+
try { return JSON.parse(s) } catch { return s }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function registerBrc100Tools(
|
|
28
|
+
server: McpServer,
|
|
29
|
+
ctx: OneSatContext | undefined,
|
|
30
|
+
) {
|
|
31
|
+
// ── Transaction lifecycle ──────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
server.tool(
|
|
34
|
+
'wallet_createAction',
|
|
35
|
+
'Creates a new Bitcoin transaction. Handles funding, signing, and broadcasting based on options.',
|
|
36
|
+
{
|
|
37
|
+
description: z.string().describe('5-50 char description of the action'),
|
|
38
|
+
inputsJSON: z.string().optional().describe('JSON array of transaction inputs'),
|
|
39
|
+
outputsJSON: z.string().optional().describe('JSON array of transaction outputs [{lockingScript, satoshis, outputDescription, basket, tags, customInstructions}]'),
|
|
40
|
+
labelsJSON: z.string().optional().describe('JSON array of label strings'),
|
|
41
|
+
lockTime: z.number().optional(),
|
|
42
|
+
version: z.number().optional(),
|
|
43
|
+
optionsJSON: z.string().optional().describe('JSON CreateActionOptions: {noSend, sendWith, acceptDelayedBroadcast, signAndProcess, randomizeOutputs, noSendChange, knownTxids, trustSelf}'),
|
|
44
|
+
},
|
|
45
|
+
async ({ description, inputsJSON, outputsJSON, labelsJSON, lockTime, version, optionsJSON }) => {
|
|
46
|
+
if (!ctx) return noCtx
|
|
47
|
+
try {
|
|
48
|
+
return result(await ctx.wallet.createAction({
|
|
49
|
+
description,
|
|
50
|
+
inputs: parseJSON(inputsJSON),
|
|
51
|
+
outputs: parseJSON(outputsJSON),
|
|
52
|
+
labels: parseJSON(labelsJSON),
|
|
53
|
+
lockTime,
|
|
54
|
+
version,
|
|
55
|
+
options: parseJSON(optionsJSON),
|
|
56
|
+
} as any))
|
|
57
|
+
} catch (e) { return error(e) }
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
server.tool(
|
|
62
|
+
'wallet_signAction',
|
|
63
|
+
'Signs a transaction previously created with createAction (when signAndProcess was false).',
|
|
64
|
+
{
|
|
65
|
+
spendsJSON: z.string().describe('JSON map of input index to {unlockingScript, sequenceNumber}'),
|
|
66
|
+
reference: z.string().describe('Base64 reference from createAction result'),
|
|
67
|
+
optionsJSON: z.string().optional().describe('JSON SignActionOptions: {acceptDelayedBroadcast, sendWith}'),
|
|
68
|
+
},
|
|
69
|
+
async ({ spendsJSON, reference, optionsJSON }) => {
|
|
70
|
+
if (!ctx) return noCtx
|
|
71
|
+
try {
|
|
72
|
+
return result(await ctx.wallet.signAction({
|
|
73
|
+
spends: parseJSON(spendsJSON),
|
|
74
|
+
reference,
|
|
75
|
+
options: parseJSON(optionsJSON),
|
|
76
|
+
} as any))
|
|
77
|
+
} catch (e) { return error(e) }
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
server.tool(
|
|
82
|
+
'wallet_abortAction',
|
|
83
|
+
'Aborts a pending (nosend or unsigned) transaction, releasing consumed inputs back to spendable state.',
|
|
84
|
+
{
|
|
85
|
+
reference: z.string().describe('Base64 reference of the transaction to abort'),
|
|
86
|
+
},
|
|
87
|
+
async ({ reference }) => {
|
|
88
|
+
if (!ctx) return noCtx
|
|
89
|
+
try {
|
|
90
|
+
return result(await ctx.wallet.abortAction({ reference }))
|
|
91
|
+
} catch (e) { return error(e) }
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
server.tool(
|
|
96
|
+
'wallet_internalizeAction',
|
|
97
|
+
'Internalizes an external transaction, adding its outputs to wallet baskets.',
|
|
98
|
+
{
|
|
99
|
+
txJSON: z.string().describe('JSON array of AtomicBEEF bytes'),
|
|
100
|
+
outputsJSON: z.string().describe('JSON array of outputs to internalize'),
|
|
101
|
+
description: z.string().describe('5-50 char description'),
|
|
102
|
+
labelsJSON: z.string().optional().describe('JSON array of label strings'),
|
|
103
|
+
},
|
|
104
|
+
async ({ txJSON, outputsJSON, description, labelsJSON }) => {
|
|
105
|
+
if (!ctx) return noCtx
|
|
106
|
+
try {
|
|
107
|
+
return result(await ctx.wallet.internalizeAction({
|
|
108
|
+
tx: parseJSON(txJSON),
|
|
109
|
+
outputs: parseJSON(outputsJSON),
|
|
110
|
+
description,
|
|
111
|
+
labels: parseJSON(labelsJSON),
|
|
112
|
+
} as any))
|
|
113
|
+
} catch (e) { return error(e) }
|
|
114
|
+
},
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// ── Queries ────────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
server.tool(
|
|
120
|
+
'wallet_listActions',
|
|
121
|
+
'Lists wallet transactions filtered by labels, with optional input/output details.',
|
|
122
|
+
{
|
|
123
|
+
labelsJSON: z.string().default('[]').describe('JSON array of label strings'),
|
|
124
|
+
labelQueryMode: z.enum(['any', 'all']).default('any'),
|
|
125
|
+
includeLabels: z.boolean().default(true),
|
|
126
|
+
includeInputs: z.boolean().default(false),
|
|
127
|
+
includeInputSourceLockingScripts: z.boolean().default(false),
|
|
128
|
+
includeInputUnlockingScripts: z.boolean().default(false),
|
|
129
|
+
includeOutputs: z.boolean().default(false),
|
|
130
|
+
includeOutputLockingScripts: z.boolean().default(false),
|
|
131
|
+
limit: z.number().default(25),
|
|
132
|
+
offset: z.number().default(0),
|
|
133
|
+
},
|
|
134
|
+
async ({ labelsJSON, ...rest }) => {
|
|
135
|
+
if (!ctx) return noCtx
|
|
136
|
+
try {
|
|
137
|
+
return result(await ctx.wallet.listActions({
|
|
138
|
+
labels: parseJSON(labelsJSON),
|
|
139
|
+
...rest,
|
|
140
|
+
} as any))
|
|
141
|
+
} catch (e) { return error(e) }
|
|
142
|
+
},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
server.tool(
|
|
146
|
+
'wallet_listOutputs',
|
|
147
|
+
'Lists spendable outputs in a basket, optionally filtered by tags.',
|
|
148
|
+
{
|
|
149
|
+
basket: z.string().describe('Basket name (e.g. "default")'),
|
|
150
|
+
tagsJSON: z.string().optional().describe('JSON array of tag strings'),
|
|
151
|
+
tagQueryMode: z.enum(['all', 'any']).optional(),
|
|
152
|
+
include: z.enum(['locking scripts', 'entire transactions']).optional(),
|
|
153
|
+
includeCustomInstructions: z.boolean().default(false),
|
|
154
|
+
includeTags: z.boolean().default(false),
|
|
155
|
+
includeLabels: z.boolean().default(false),
|
|
156
|
+
limit: z.number().default(25),
|
|
157
|
+
offset: z.number().default(0),
|
|
158
|
+
},
|
|
159
|
+
async ({ tagsJSON, ...rest }) => {
|
|
160
|
+
if (!ctx) return noCtx
|
|
161
|
+
try {
|
|
162
|
+
return result(await ctx.wallet.listOutputs({
|
|
163
|
+
tags: parseJSON(tagsJSON),
|
|
164
|
+
...rest,
|
|
165
|
+
} as any))
|
|
166
|
+
} catch (e) { return error(e) }
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
server.tool(
|
|
171
|
+
'wallet_relinquishOutput',
|
|
172
|
+
'Removes an output from a basket without spending it.',
|
|
173
|
+
{
|
|
174
|
+
basket: z.string().describe('Basket name'),
|
|
175
|
+
output: z.string().describe('Outpoint string (txid.vout)'),
|
|
176
|
+
},
|
|
177
|
+
async (args) => {
|
|
178
|
+
if (!ctx) return noCtx
|
|
179
|
+
try {
|
|
180
|
+
return result(await ctx.wallet.relinquishOutput(args as any))
|
|
181
|
+
} catch (e) { return error(e) }
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
// ── Keys & Crypto ──────────────────────────────────────────────────
|
|
186
|
+
|
|
187
|
+
server.tool(
|
|
188
|
+
'wallet_getPublicKey',
|
|
189
|
+
'Retrieves a public key by protocol/key derivation. Use identityKey:true for the root identity key. protocolID is a JSON array like [2,"1sat"].',
|
|
190
|
+
{
|
|
191
|
+
identityKey: z.boolean().optional().describe('If true, return the identity key (ignores other args)'),
|
|
192
|
+
protocolIDJSON: z.string().optional().describe('JSON array [securityLevel, protocolString] e.g. [2,"1sat"]'),
|
|
193
|
+
keyID: z.string().optional(),
|
|
194
|
+
counterparty: z.string().optional(),
|
|
195
|
+
forSelf: z.boolean().optional(),
|
|
196
|
+
privileged: z.boolean().optional(),
|
|
197
|
+
privilegedReason: z.string().optional(),
|
|
198
|
+
},
|
|
199
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
200
|
+
if (!ctx) return noCtx
|
|
201
|
+
try {
|
|
202
|
+
return result(await ctx.wallet.getPublicKey({
|
|
203
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
204
|
+
...rest,
|
|
205
|
+
} as any))
|
|
206
|
+
} catch (e) { return error(e) }
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
server.tool(
|
|
211
|
+
'wallet_encrypt',
|
|
212
|
+
'Encrypts data using wallet keys. protocolID is a JSON array like [2,"protocolName"].',
|
|
213
|
+
{
|
|
214
|
+
plaintext: z.array(z.number()).describe('Data bytes to encrypt'),
|
|
215
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
216
|
+
keyID: z.string(),
|
|
217
|
+
counterparty: z.string().optional(),
|
|
218
|
+
privileged: z.boolean().optional(),
|
|
219
|
+
},
|
|
220
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
221
|
+
if (!ctx) return noCtx
|
|
222
|
+
try {
|
|
223
|
+
return result(await ctx.wallet.encrypt({
|
|
224
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
225
|
+
...rest,
|
|
226
|
+
} as any))
|
|
227
|
+
} catch (e) { return error(e) }
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
server.tool(
|
|
232
|
+
'wallet_decrypt',
|
|
233
|
+
'Decrypts data using wallet keys.',
|
|
234
|
+
{
|
|
235
|
+
ciphertext: z.array(z.number()).describe('Encrypted data bytes'),
|
|
236
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
237
|
+
keyID: z.string(),
|
|
238
|
+
counterparty: z.string().optional(),
|
|
239
|
+
privileged: z.boolean().optional(),
|
|
240
|
+
},
|
|
241
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
242
|
+
if (!ctx) return noCtx
|
|
243
|
+
try {
|
|
244
|
+
return result(await ctx.wallet.decrypt({
|
|
245
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
246
|
+
...rest,
|
|
247
|
+
} as any))
|
|
248
|
+
} catch (e) { return error(e) }
|
|
249
|
+
},
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
server.tool(
|
|
253
|
+
'wallet_createHmac',
|
|
254
|
+
'Creates an HMAC using wallet keys.',
|
|
255
|
+
{
|
|
256
|
+
data: z.array(z.number()).describe('Data bytes'),
|
|
257
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
258
|
+
keyID: z.string(),
|
|
259
|
+
counterparty: z.string().optional(),
|
|
260
|
+
privileged: z.boolean().optional(),
|
|
261
|
+
},
|
|
262
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
263
|
+
if (!ctx) return noCtx
|
|
264
|
+
try {
|
|
265
|
+
return result(await ctx.wallet.createHmac({
|
|
266
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
267
|
+
...rest,
|
|
268
|
+
} as any))
|
|
269
|
+
} catch (e) { return error(e) }
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
server.tool(
|
|
274
|
+
'wallet_verifyHmac',
|
|
275
|
+
'Verifies an HMAC using wallet keys.',
|
|
276
|
+
{
|
|
277
|
+
data: z.array(z.number()).describe('Data bytes'),
|
|
278
|
+
hmac: z.array(z.number()).describe('HMAC bytes to verify'),
|
|
279
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
280
|
+
keyID: z.string(),
|
|
281
|
+
counterparty: z.string().optional(),
|
|
282
|
+
privileged: z.boolean().optional(),
|
|
283
|
+
},
|
|
284
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
285
|
+
if (!ctx) return noCtx
|
|
286
|
+
try {
|
|
287
|
+
return result(await ctx.wallet.verifyHmac({
|
|
288
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
289
|
+
...rest,
|
|
290
|
+
} as any))
|
|
291
|
+
} catch (e) { return error(e) }
|
|
292
|
+
},
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
server.tool(
|
|
296
|
+
'wallet_createSignature',
|
|
297
|
+
'Creates a digital signature using wallet keys.',
|
|
298
|
+
{
|
|
299
|
+
data: z.array(z.number()).describe('Data bytes to sign'),
|
|
300
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
301
|
+
keyID: z.string(),
|
|
302
|
+
counterparty: z.string().optional(),
|
|
303
|
+
privileged: z.boolean().optional(),
|
|
304
|
+
},
|
|
305
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
306
|
+
if (!ctx) return noCtx
|
|
307
|
+
try {
|
|
308
|
+
return result(await ctx.wallet.createSignature({
|
|
309
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
310
|
+
...rest,
|
|
311
|
+
} as any))
|
|
312
|
+
} catch (e) { return error(e) }
|
|
313
|
+
},
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
server.tool(
|
|
317
|
+
'wallet_verifySignature',
|
|
318
|
+
'Verifies a digital signature using wallet keys.',
|
|
319
|
+
{
|
|
320
|
+
data: z.array(z.number()).describe('Data bytes that were signed'),
|
|
321
|
+
signature: z.array(z.number()).describe('Signature bytes'),
|
|
322
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
323
|
+
keyID: z.string(),
|
|
324
|
+
counterparty: z.string().optional(),
|
|
325
|
+
forSelf: z.boolean().optional(),
|
|
326
|
+
privileged: z.boolean().optional(),
|
|
327
|
+
},
|
|
328
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
329
|
+
if (!ctx) return noCtx
|
|
330
|
+
try {
|
|
331
|
+
return result(await ctx.wallet.verifySignature({
|
|
332
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
333
|
+
...rest,
|
|
334
|
+
} as any))
|
|
335
|
+
} catch (e) { return error(e) }
|
|
336
|
+
},
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
// ── Key Linkage ────────────────────────────────────────────────────
|
|
340
|
+
|
|
341
|
+
server.tool(
|
|
342
|
+
'wallet_revealCounterpartyKeyLinkage',
|
|
343
|
+
'Reveals the linkage between the wallet identity and a counterparty to a verifier.',
|
|
344
|
+
{
|
|
345
|
+
counterparty: z.string().describe('Counterparty public key hex'),
|
|
346
|
+
verifier: z.string().describe('Verifier public key hex'),
|
|
347
|
+
privileged: z.boolean().optional(),
|
|
348
|
+
privilegedReason: z.string().optional(),
|
|
349
|
+
},
|
|
350
|
+
async (args) => {
|
|
351
|
+
if (!ctx) return noCtx
|
|
352
|
+
try {
|
|
353
|
+
return result(await ctx.wallet.revealCounterpartyKeyLinkage(args as any))
|
|
354
|
+
} catch (e) { return error(e) }
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
server.tool(
|
|
359
|
+
'wallet_revealSpecificKeyLinkage',
|
|
360
|
+
'Reveals linkage for a specific protocol/key combination to a verifier.',
|
|
361
|
+
{
|
|
362
|
+
counterparty: z.string().describe('Counterparty public key hex'),
|
|
363
|
+
verifier: z.string().describe('Verifier public key hex'),
|
|
364
|
+
protocolIDJSON: z.string().describe('JSON array [securityLevel, protocolString]'),
|
|
365
|
+
keyID: z.string(),
|
|
366
|
+
privileged: z.boolean().optional(),
|
|
367
|
+
privilegedReason: z.string().optional(),
|
|
368
|
+
},
|
|
369
|
+
async ({ protocolIDJSON, ...rest }) => {
|
|
370
|
+
if (!ctx) return noCtx
|
|
371
|
+
try {
|
|
372
|
+
return result(await ctx.wallet.revealSpecificKeyLinkage({
|
|
373
|
+
protocolID: parseJSON(protocolIDJSON),
|
|
374
|
+
...rest,
|
|
375
|
+
} as any))
|
|
376
|
+
} catch (e) { return error(e) }
|
|
377
|
+
},
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
// ── Certificates ───────────────────────────────────────────────────
|
|
381
|
+
|
|
382
|
+
server.tool(
|
|
383
|
+
'wallet_acquireCertificate',
|
|
384
|
+
'Acquires an identity certificate from a certifier.',
|
|
385
|
+
{
|
|
386
|
+
type: z.string().describe('Certificate type (base64)'),
|
|
387
|
+
certifier: z.string().describe('Certifier public key hex'),
|
|
388
|
+
acquisitionProtocol: z.enum(['direct', 'issuance']),
|
|
389
|
+
fieldsJSON: z.string().describe('JSON object of certificate fields'),
|
|
390
|
+
serialNumber: z.string().optional(),
|
|
391
|
+
revocationOutpoint: z.string().optional(),
|
|
392
|
+
signature: z.string().optional(),
|
|
393
|
+
certifierUrl: z.string().optional(),
|
|
394
|
+
keyringRevealer: z.string().optional(),
|
|
395
|
+
keyringForSubjectJSON: z.string().optional().describe('JSON object of keyring for subject'),
|
|
396
|
+
privileged: z.boolean().optional(),
|
|
397
|
+
privilegedReason: z.string().optional(),
|
|
398
|
+
},
|
|
399
|
+
async ({ fieldsJSON, keyringForSubjectJSON, ...rest }) => {
|
|
400
|
+
if (!ctx) return noCtx
|
|
401
|
+
try {
|
|
402
|
+
return result(await ctx.wallet.acquireCertificate({
|
|
403
|
+
fields: parseJSON(fieldsJSON),
|
|
404
|
+
keyringForSubject: parseJSON(keyringForSubjectJSON),
|
|
405
|
+
...rest,
|
|
406
|
+
} as any))
|
|
407
|
+
} catch (e) { return error(e) }
|
|
408
|
+
},
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
server.tool(
|
|
412
|
+
'wallet_listCertificates',
|
|
413
|
+
'Lists identity certificates filtered by certifiers and types.',
|
|
414
|
+
{
|
|
415
|
+
certifiersJSON: z.string().describe('JSON array of certifier public key hexes'),
|
|
416
|
+
typesJSON: z.string().describe('JSON array of certificate types (base64)'),
|
|
417
|
+
limit: z.number().default(25),
|
|
418
|
+
offset: z.number().default(0),
|
|
419
|
+
privileged: z.boolean().optional(),
|
|
420
|
+
privilegedReason: z.string().optional(),
|
|
421
|
+
},
|
|
422
|
+
async ({ certifiersJSON, typesJSON, ...rest }) => {
|
|
423
|
+
if (!ctx) return noCtx
|
|
424
|
+
try {
|
|
425
|
+
return result(await ctx.wallet.listCertificates({
|
|
426
|
+
certifiers: parseJSON(certifiersJSON),
|
|
427
|
+
types: parseJSON(typesJSON),
|
|
428
|
+
...rest,
|
|
429
|
+
} as any))
|
|
430
|
+
} catch (e) { return error(e) }
|
|
431
|
+
},
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
server.tool(
|
|
435
|
+
'wallet_proveCertificate',
|
|
436
|
+
'Proves select fields of a certificate to a verifier.',
|
|
437
|
+
{
|
|
438
|
+
certificateJSON: z.string().describe('JSON object of the certificate to prove'),
|
|
439
|
+
fieldsToRevealJSON: z.string().describe('JSON array of field names to reveal'),
|
|
440
|
+
verifier: z.string().describe('Verifier public key hex'),
|
|
441
|
+
privileged: z.boolean().optional(),
|
|
442
|
+
privilegedReason: z.string().optional(),
|
|
443
|
+
},
|
|
444
|
+
async ({ certificateJSON, fieldsToRevealJSON, ...rest }) => {
|
|
445
|
+
if (!ctx) return noCtx
|
|
446
|
+
try {
|
|
447
|
+
return result(await ctx.wallet.proveCertificate({
|
|
448
|
+
certificate: parseJSON(certificateJSON),
|
|
449
|
+
fieldsToReveal: parseJSON(fieldsToRevealJSON),
|
|
450
|
+
...rest,
|
|
451
|
+
} as any))
|
|
452
|
+
} catch (e) { return error(e) }
|
|
453
|
+
},
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
server.tool(
|
|
457
|
+
'wallet_relinquishCertificate',
|
|
458
|
+
'Removes a certificate from the wallet.',
|
|
459
|
+
{
|
|
460
|
+
type: z.string().describe('Certificate type'),
|
|
461
|
+
serialNumber: z.string().describe('Certificate serial number'),
|
|
462
|
+
certifier: z.string().describe('Certifier public key hex'),
|
|
463
|
+
},
|
|
464
|
+
async (args) => {
|
|
465
|
+
if (!ctx) return noCtx
|
|
466
|
+
try {
|
|
467
|
+
return result(await ctx.wallet.relinquishCertificate(args as any))
|
|
468
|
+
} catch (e) { return error(e) }
|
|
469
|
+
},
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
// ── Discovery ──────────────────────────────────────────────────────
|
|
473
|
+
|
|
474
|
+
server.tool(
|
|
475
|
+
'wallet_discoverByIdentityKey',
|
|
476
|
+
'Discovers certificates issued to a given identity key.',
|
|
477
|
+
{
|
|
478
|
+
identityKey: z.string().describe('Identity public key hex'),
|
|
479
|
+
limit: z.number().default(25),
|
|
480
|
+
offset: z.number().default(0),
|
|
481
|
+
},
|
|
482
|
+
async (args) => {
|
|
483
|
+
if (!ctx) return noCtx
|
|
484
|
+
try {
|
|
485
|
+
return result(await ctx.wallet.discoverByIdentityKey(args as any))
|
|
486
|
+
} catch (e) { return error(e) }
|
|
487
|
+
},
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
server.tool(
|
|
491
|
+
'wallet_discoverByAttributes',
|
|
492
|
+
'Discovers certificates matching specific attributes.',
|
|
493
|
+
{
|
|
494
|
+
attributesJSON: z.string().describe('JSON object of attribute key/value pairs to match'),
|
|
495
|
+
limit: z.number().default(25),
|
|
496
|
+
offset: z.number().default(0),
|
|
497
|
+
},
|
|
498
|
+
async ({ attributesJSON, ...rest }) => {
|
|
499
|
+
if (!ctx) return noCtx
|
|
500
|
+
try {
|
|
501
|
+
return result(await ctx.wallet.discoverByAttributes({
|
|
502
|
+
attributes: parseJSON(attributesJSON),
|
|
503
|
+
...rest,
|
|
504
|
+
} as any))
|
|
505
|
+
} catch (e) { return error(e) }
|
|
506
|
+
},
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
// ── Info ────────────────────────────────────────────────────────────
|
|
510
|
+
|
|
511
|
+
server.tool(
|
|
512
|
+
'wallet_isAuthenticated',
|
|
513
|
+
'Checks if the wallet user is authenticated.',
|
|
514
|
+
{},
|
|
515
|
+
async () => {
|
|
516
|
+
if (!ctx) return noCtx
|
|
517
|
+
try {
|
|
518
|
+
return result(await ctx.wallet.isAuthenticated({}))
|
|
519
|
+
} catch (e) { return error(e) }
|
|
520
|
+
},
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
server.tool(
|
|
524
|
+
'wallet_getHeight',
|
|
525
|
+
'Gets the current blockchain height.',
|
|
526
|
+
{},
|
|
527
|
+
async () => {
|
|
528
|
+
if (!ctx) return noCtx
|
|
529
|
+
try {
|
|
530
|
+
return result(await ctx.wallet.getHeight({}))
|
|
531
|
+
} catch (e) { return error(e) }
|
|
532
|
+
},
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
server.tool(
|
|
536
|
+
'wallet_getHeaderForHeight',
|
|
537
|
+
'Gets the 80-byte block header at a given height.',
|
|
538
|
+
{
|
|
539
|
+
height: z.number().describe('Block height'),
|
|
540
|
+
},
|
|
541
|
+
async ({ height }) => {
|
|
542
|
+
if (!ctx) return noCtx
|
|
543
|
+
try {
|
|
544
|
+
return result(await ctx.wallet.getHeaderForHeight({ height }))
|
|
545
|
+
} catch (e) { return error(e) }
|
|
546
|
+
},
|
|
547
|
+
)
|
|
548
|
+
|
|
549
|
+
server.tool(
|
|
550
|
+
'wallet_getNetwork',
|
|
551
|
+
'Gets the network the wallet is connected to (mainnet or testnet).',
|
|
552
|
+
{},
|
|
553
|
+
async () => {
|
|
554
|
+
if (!ctx) return noCtx
|
|
555
|
+
try {
|
|
556
|
+
return result(await ctx.wallet.getNetwork({}))
|
|
557
|
+
} catch (e) { return error(e) }
|
|
558
|
+
},
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
server.tool(
|
|
562
|
+
'wallet_getVersion',
|
|
563
|
+
'Gets the wallet implementation version.',
|
|
564
|
+
{},
|
|
565
|
+
async () => {
|
|
566
|
+
if (!ctx) return noCtx
|
|
567
|
+
try {
|
|
568
|
+
return result(await ctx.wallet.getVersion({}))
|
|
569
|
+
} catch (e) { return error(e) }
|
|
570
|
+
},
|
|
571
|
+
)
|
|
572
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { OneSatContext } from '@1sat/actions'
|
|
2
|
+
import { cancelListing } from '@1sat/actions'
|
|
3
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
4
|
+
import { z } from 'zod'
|
|
5
|
+
|
|
6
|
+
const walletOutputSchema = z.object({
|
|
7
|
+
outpoint: z.string(),
|
|
8
|
+
satoshis: z.number().optional(),
|
|
9
|
+
tags: z.array(z.string()).optional(),
|
|
10
|
+
customInstructions: z.string().optional(),
|
|
11
|
+
lockingScript: z.string().optional(),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const cancelListingArgsSchema = z.object({
|
|
15
|
+
listing: walletOutputSchema.describe(
|
|
16
|
+
'WalletOutput of the listing to cancel (must include lockingScript)',
|
|
17
|
+
),
|
|
18
|
+
inputBEEF: z
|
|
19
|
+
.array(z.number())
|
|
20
|
+
.describe(
|
|
21
|
+
"BEEF bytes from listOutputs (include: 'entire transactions')",
|
|
22
|
+
),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export function registerCancelListingTool(
|
|
26
|
+
server: McpServer,
|
|
27
|
+
ctx: OneSatContext | undefined,
|
|
28
|
+
) {
|
|
29
|
+
server.tool(
|
|
30
|
+
'wallet_cancelListing',
|
|
31
|
+
'Cancel an ordinal marketplace listing',
|
|
32
|
+
{ ...cancelListingArgsSchema.shape },
|
|
33
|
+
async ({ listing, inputBEEF }) => {
|
|
34
|
+
if (!ctx) {
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: 'text',
|
|
39
|
+
text: 'Wallet not initialized. Please configure a wallet before cancelling.',
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
isError: true,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await cancelListing.execute(ctx, {
|
|
48
|
+
listing: listing as Parameters<typeof cancelListing.execute>[1]['listing'],
|
|
49
|
+
inputBEEF,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (result.error) {
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: 'text', text: result.error }],
|
|
55
|
+
isError: true,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text',
|
|
63
|
+
text: JSON.stringify(result, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2),
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
} catch (err: unknown) {
|
|
68
|
+
return {
|
|
69
|
+
content: [
|
|
70
|
+
{
|
|
71
|
+
type: 'text',
|
|
72
|
+
text: err instanceof Error ? err.message : String(err),
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
isError: true,
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
}
|