bsv-mcp 0.2.14 → 0.3.0
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 +90 -10
- package/README.md +13 -13
- package/dist/app.html +483 -106
- package/dist/index.js +265787 -155292
- package/index.ts +505 -70
- package/package.json +24 -15
- package/prompts/bsvSdk/auth.ts +1 -1
- package/prompts/bsvSdk/cryptography.ts +1 -1
- package/prompts/bsvSdk/overview.ts +1 -1
- package/prompts/bsvSdk/primitives.ts +1 -1
- package/prompts/bsvSdk/script.ts +1 -1
- package/prompts/bsvSdk/transaction.ts +1 -1
- package/prompts/bsvSdk/wallet.ts +1 -1
- package/prompts/ordinals.ts +1 -1
- package/test-mcp-server.ts +26 -4
- package/tools/a2b/discover.ts +2 -2
- package/tools/bap/friend.ts +17 -24
- package/tools/bap/generate.test.ts +34 -32
- package/tools/bap/generate.ts +48 -43
- package/tools/bap/getCurrentAddress.ts +1 -3
- package/tools/bap/getId.test.ts +18 -10
- package/tools/bap/getId.ts +8 -6
- package/tools/bap/utils.ts +1 -1
- package/tools/bsocial/bmapReadPosts.ts +16 -2
- package/tools/bsocial/createPost.ts +21 -10
- package/tools/bsocial/readPosts.ts +10 -4
- package/tools/bsv/decodeTransaction.ts +1 -1
- package/tools/bsv/explore.ts +0 -4
- package/tools/bsv/getPrice.ts +1 -1
- package/tools/bsv/token.ts +14 -6
- package/tools/constants.ts +6 -2
- package/tools/index.ts +10 -10
- package/tools/mnee/getBalance.ts +1 -1
- package/tools/mnee/parseTx.ts +1 -1
- package/tools/mnee/sendMnee.ts +30 -8
- package/tools/ordinals/getInscription.ts +1 -3
- package/tools/ordinals/getTokenByIdOrTicker.ts +1 -3
- package/tools/ordinals/index.ts +4 -1
- package/tools/ordinals/marketListings.ts +1 -1
- package/tools/ordinals/marketSales.ts +1 -1
- package/tools/ordinals/searchInscriptions.ts +1 -1
- package/tools/utils/aip.ts +31 -22
- package/tools/utils/installAgentMaster.ts +8 -9
- package/tools/utils/toolRegistration.ts +21 -22
- package/tools/utils/transactionBuilder.ts +21 -14
- package/tools/wallet/a2bPublishMcp.ts +15 -5
- package/tools/wallet/brc100.ts +541 -310
- package/tools/wallet/cancelListing.ts +29 -37
- package/tools/wallet/createOrdinals.ts +43 -27
- package/tools/wallet/fetchPaymentUtxos.ts +7 -5
- package/tools/wallet/gatherCollectionInfo.ts +3 -5
- package/tools/wallet/getAddress.ts +1 -4
- package/tools/wallet/getBalance.ts +15 -15
- package/tools/wallet/{getBalanceDroplet.ts → getBalanceDroplit.ts} +6 -6
- package/tools/wallet/getBsv21Balances.ts +20 -14
- package/tools/wallet/getLockData.ts +11 -11
- package/tools/wallet/getOrdinals.ts +26 -20
- package/tools/wallet/integratedWallet.ts +55 -53
- package/tools/wallet/listOrdinal.ts +40 -39
- package/tools/wallet/listTokens.ts +18 -17
- package/tools/wallet/lockBsv.ts +36 -22
- package/tools/wallet/mintCollection.ts +15 -10
- package/tools/wallet/opnsDeregister.ts +29 -39
- package/tools/wallet/opnsRegister.ts +29 -39
- package/tools/wallet/purchaseListing.test.ts +27 -0
- package/tools/wallet/purchaseListing.ts +59 -40
- package/tools/wallet/refreshUtxos.ts +1 -1
- package/tools/wallet/schemas.ts +0 -13
- package/tools/wallet/sendAllBsv.ts +26 -20
- package/tools/wallet/sendBsv.ts +107 -0
- package/tools/wallet/setupDroplit.ts +157 -0
- package/tools/wallet/signBsm.ts +18 -18
- package/tools/wallet/sweepBsv.ts +48 -27
- package/tools/wallet/sweepBsv21.ts +42 -28
- package/tools/wallet/sweepOrdinals.ts +38 -26
- package/tools/wallet/tools.ts +11 -13
- package/tools/wallet/transactionHelper.ts +13 -5
- package/tools/wallet/transferOrdToken.ts +72 -84
- package/tools/wallet/unlockBsv.ts +23 -17
- package/tools/wallet/utxo.ts +1 -0
- package/tools/wallet/wallet.ts +1 -13
- package/tools/wallet/sendToAddress.ts +0 -74
- package/tools/wallet/setupDroplet.ts +0 -161
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "bsv-mcp",
|
|
3
3
|
"module": "dist/index.js",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "satchmo",
|
|
8
8
|
"description": "A collection of Bitcoin SV (BSV) tools for the Model Context Protocol (MCP) framework",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"bun": "^1.3.10",
|
|
63
63
|
"class-variance-authority": "^0.7.1",
|
|
64
64
|
"clsx": "^2.1.1",
|
|
65
|
+
"esbuild": "^0.28.1",
|
|
65
66
|
"lucide-react": "0.577.0",
|
|
66
67
|
"next": "^16.1.6",
|
|
67
68
|
"next-themes": "^0.4.6",
|
|
@@ -72,15 +73,28 @@
|
|
|
72
73
|
"tailwind-merge": "^3.5.0",
|
|
73
74
|
"tailwindcss": "^4.2.1",
|
|
74
75
|
"tailwindcss-animate": "^1.0.7",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
"vite": "^7.3.1",
|
|
77
|
+
"vite-plugin-singlefile": "^2.3.0"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"typescript": "^5.9.3"
|
|
81
|
+
},
|
|
82
|
+
"overrides": {
|
|
83
|
+
"zod": "4.4.3"
|
|
84
|
+
},
|
|
85
|
+
"dependencies": {
|
|
86
|
+
"@1sat/actions": "^0.0.192",
|
|
87
|
+
"@1sat/wallet-node": "^0.0.64",
|
|
88
|
+
"@1sat/wallet-remote": "^0.0.55",
|
|
89
|
+
"@bsv/sdk": "^2.2.0",
|
|
90
|
+
"@bsv/wallet-toolbox": "^2.4.4",
|
|
91
|
+
"@gorillapool/js-junglebus": "^0.4.4",
|
|
92
|
+
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
93
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
94
|
+
"bitcoin-auth": "^0.0.8",
|
|
95
|
+
"bitcoin-backup": "^0.0.13",
|
|
82
96
|
"bmap-api-types": "0.0.9",
|
|
83
|
-
"bsv-bap": "0.
|
|
97
|
+
"bsv-bap": "^0.3.4",
|
|
84
98
|
"jose": "^6.2.0",
|
|
85
99
|
"js-1sat-ord": "^0.1.91",
|
|
86
100
|
"mcp-handler": "^1.0.7",
|
|
@@ -88,13 +102,8 @@
|
|
|
88
102
|
"satoshi-token": "^0.0.7",
|
|
89
103
|
"schema-dts": "^1.1.5",
|
|
90
104
|
"sigma-protocol": "^0.1.9",
|
|
91
|
-
"
|
|
92
|
-
"vite-plugin-singlefile": "^2.3.0"
|
|
93
|
-
},
|
|
94
|
-
"peerDependencies": {
|
|
95
|
-
"typescript": "^5.9.3"
|
|
105
|
+
"zod": "4.4.3"
|
|
96
106
|
},
|
|
97
|
-
"dependencies": {},
|
|
98
107
|
"scripts": {
|
|
99
108
|
"build": "bun run ./scripts/build.ts",
|
|
100
109
|
"build:view": "vite build",
|
package/prompts/bsvSdk/auth.ts
CHANGED
|
@@ -44,7 +44,7 @@ export function registerAuthPrompt(server: McpServer): void {
|
|
|
44
44
|
server.prompt(
|
|
45
45
|
"bitcoin_sv_sdk_auth",
|
|
46
46
|
"Detailed information about the authentication functionality in the BSV SDK, including identity protocols, certificates, and session management.",
|
|
47
|
-
async (
|
|
47
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
48
48
|
return {
|
|
49
49
|
messages: [
|
|
50
50
|
{
|
|
@@ -45,7 +45,7 @@ export function registerCryptographyPrompt(server: McpServer): void {
|
|
|
45
45
|
server.prompt(
|
|
46
46
|
"bitcoin_sv_sdk_cryptography",
|
|
47
47
|
"Detailed information about the cryptographic functionality in the BSV SDK, including key generation, signing, encryption, and hashing.",
|
|
48
|
-
async (
|
|
48
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
49
49
|
return {
|
|
50
50
|
messages: [
|
|
51
51
|
{
|
|
@@ -88,7 +88,7 @@ export function registerOverviewPrompt(server: McpServer): void {
|
|
|
88
88
|
server.prompt(
|
|
89
89
|
"bitcoin_sv_sdk_overview",
|
|
90
90
|
"General overview of the Bitcoin SV SDK, including its purpose and main components.",
|
|
91
|
-
async (
|
|
91
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
92
92
|
return {
|
|
93
93
|
messages: [
|
|
94
94
|
{
|
|
@@ -52,7 +52,7 @@ export function registerPrimitivesPrompt(server: McpServer): void {
|
|
|
52
52
|
server.prompt(
|
|
53
53
|
"bitcoin_sv_sdk_primitives",
|
|
54
54
|
"Detailed information about the primitive data types and structures in the BSV SDK, including Binary, Hex, Points, and other fundamental types.",
|
|
55
|
-
async (
|
|
55
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
56
56
|
return {
|
|
57
57
|
messages: [
|
|
58
58
|
{
|
package/prompts/bsvSdk/script.ts
CHANGED
|
@@ -53,7 +53,7 @@ export function registerScriptPrompt(server: McpServer): void {
|
|
|
53
53
|
server.prompt(
|
|
54
54
|
"bitcoin_sv_sdk_script",
|
|
55
55
|
"Detailed information about the script functionality in the BSV SDK, including Bitcoin Script operations, locking and unlocking scripts, and OP_CODES.",
|
|
56
|
-
async (
|
|
56
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
57
57
|
return {
|
|
58
58
|
messages: [
|
|
59
59
|
{
|
|
@@ -232,7 +232,7 @@ export function registerTransactionPrompt(server: McpServer): void {
|
|
|
232
232
|
server.prompt(
|
|
233
233
|
"bitcoin_sv_sdk_transaction",
|
|
234
234
|
"Detailed information about transaction building and management in the BSV SDK, including input/output handling, script integration, and transaction signing.",
|
|
235
|
-
async (
|
|
235
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
236
236
|
return {
|
|
237
237
|
messages: [
|
|
238
238
|
{
|
package/prompts/bsvSdk/wallet.ts
CHANGED
|
@@ -171,7 +171,7 @@ export function registerWalletPrompt(server: McpServer): void {
|
|
|
171
171
|
server.prompt(
|
|
172
172
|
"bitcoin_sv_sdk_wallet",
|
|
173
173
|
"Detailed information about the wallet functionality in the BSV SDK, including key management, address handling, and UTXO management.",
|
|
174
|
-
async (
|
|
174
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
175
175
|
return {
|
|
176
176
|
messages: [
|
|
177
177
|
{
|
package/prompts/ordinals.ts
CHANGED
|
@@ -86,7 +86,7 @@ export function registerOrdinalsPrompt(server: McpServer): void {
|
|
|
86
86
|
server.prompt(
|
|
87
87
|
"bitcoin_sv_ordinals",
|
|
88
88
|
"Comprehensive information about Bitcoin SV ordinals, including what they are, how they work, and how to use them.",
|
|
89
|
-
async (
|
|
89
|
+
async (_extra: RequestHandlerExtra<ServerRequest, ServerNotification>) => {
|
|
90
90
|
return {
|
|
91
91
|
messages: [
|
|
92
92
|
{
|
package/test-mcp-server.ts
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Read the first text block out of a callTool result. The client return type
|
|
7
|
+
* is a union that also covers the legacy `toolResult` shape, so the content
|
|
8
|
+
* array has to be checked rather than assumed.
|
|
9
|
+
*/
|
|
10
|
+
function firstText(result: { [key: string]: unknown }): string {
|
|
11
|
+
const content = result.content;
|
|
12
|
+
if (!Array.isArray(content) || content.length === 0) {
|
|
13
|
+
throw new Error("Tool result carried no content blocks");
|
|
14
|
+
}
|
|
15
|
+
const block: unknown = content[0];
|
|
16
|
+
if (
|
|
17
|
+
typeof block !== "object" ||
|
|
18
|
+
block === null ||
|
|
19
|
+
!("text" in block) ||
|
|
20
|
+
typeof block.text !== "string"
|
|
21
|
+
) {
|
|
22
|
+
throw new Error("First content block was not text");
|
|
23
|
+
}
|
|
24
|
+
return block.text;
|
|
25
|
+
}
|
|
26
|
+
|
|
5
27
|
async function testMCPServer() {
|
|
6
28
|
console.log("🧪 Testing BSV MCP Server...\n");
|
|
7
29
|
|
|
@@ -67,7 +89,7 @@ async function testMCPServer() {
|
|
|
67
89
|
name: "bsv_getPrice",
|
|
68
90
|
arguments: {},
|
|
69
91
|
});
|
|
70
|
-
const content =
|
|
92
|
+
const content = { text: firstText(priceResult) };
|
|
71
93
|
console.log("✅", content.text);
|
|
72
94
|
} catch (error) {
|
|
73
95
|
console.log("❌ Error:", error);
|
|
@@ -86,7 +108,7 @@ async function testMCPServer() {
|
|
|
86
108
|
},
|
|
87
109
|
},
|
|
88
110
|
});
|
|
89
|
-
const content =
|
|
111
|
+
const content = { text: firstText(convertResult) };
|
|
90
112
|
console.log("✅ Converted:", content.text);
|
|
91
113
|
} catch (error) {
|
|
92
114
|
console.log("❌ Error:", error);
|
|
@@ -103,7 +125,7 @@ async function testMCPServer() {
|
|
|
103
125
|
},
|
|
104
126
|
},
|
|
105
127
|
});
|
|
106
|
-
const content =
|
|
128
|
+
const content = { text: firstText(postsResult) };
|
|
107
129
|
console.log("✅ Read posts (showing first 200 chars):");
|
|
108
130
|
console.log(`${content.text.substring(0, 200)}...`);
|
|
109
131
|
} catch (error) {
|
|
@@ -122,7 +144,7 @@ async function testMCPServer() {
|
|
|
122
144
|
},
|
|
123
145
|
},
|
|
124
146
|
});
|
|
125
|
-
const content =
|
|
147
|
+
const content = { text: firstText(ordinalsResult) };
|
|
126
148
|
const response = JSON.parse(content.text);
|
|
127
149
|
console.log(`✅ Found ${response.total || 0} inscriptions`);
|
|
128
150
|
} catch (error) {
|
package/tools/a2b/discover.ts
CHANGED
|
@@ -50,7 +50,7 @@ export type A2bDiscoverArgs = z.infer<typeof a2bDiscoverArgsSchema>;
|
|
|
50
50
|
/**
|
|
51
51
|
* Format the response in a user-friendly way
|
|
52
52
|
*/
|
|
53
|
-
function
|
|
53
|
+
function _formatSearchResults(data: unknown, queryType: string): string {
|
|
54
54
|
// console.log(`Received data: ${JSON.stringify(data).substring(0, 200)}...`); // Debug log
|
|
55
55
|
|
|
56
56
|
// Check if data is an object with items property
|
|
@@ -124,7 +124,7 @@ export function registerA2bDiscoverTool(server: McpServer) {
|
|
|
124
124
|
{ ...a2bDiscoverArgsSchema.shape },
|
|
125
125
|
async (
|
|
126
126
|
{ queryType, query, limit, offset, fromBlock, toBlock },
|
|
127
|
-
|
|
127
|
+
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
128
128
|
) => {
|
|
129
129
|
try {
|
|
130
130
|
const params = new URLSearchParams();
|
package/tools/bap/friend.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
isBroadcastFailure,
|
|
6
6
|
isBroadcastResponse,
|
|
7
7
|
P2PKH,
|
|
8
|
-
PrivateKey,
|
|
9
8
|
SatoshisPerKilobyte,
|
|
10
9
|
Script,
|
|
11
10
|
Transaction,
|
|
@@ -18,21 +17,17 @@ import type {
|
|
|
18
17
|
ServerNotification,
|
|
19
18
|
ServerRequest,
|
|
20
19
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
21
|
-
import { BAP
|
|
20
|
+
import { BAP } from "bsv-bap";
|
|
22
21
|
import { z } from "zod";
|
|
23
22
|
import { BsocialBroadcaster } from "../../utils/broadcaster";
|
|
24
|
-
import {
|
|
25
|
-
friendPrivateKeyFromMemberIdKey,
|
|
26
|
-
friendPublicKeyFromSeedString,
|
|
27
|
-
} from "../../utils/keys";
|
|
23
|
+
import { friendPrivateKeyFromMemberIdKey } from "../../utils/keys";
|
|
28
24
|
import { MAP_PREFIX } from "../constants";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
} from "../wallet/fetchPaymentUtxos";
|
|
25
|
+
import { signOpReturnWithAIP } from "../utils/aip";
|
|
26
|
+
import { buildOpReturnScript } from "../utils/transactionBuilder";
|
|
27
|
+
import { fetchPaymentUtxosFromV5 } from "../wallet/fetchPaymentUtxos";
|
|
33
28
|
import type { Wallet } from "../wallet/wallet";
|
|
34
29
|
|
|
35
|
-
const { toArray
|
|
30
|
+
const { toArray } = Utils;
|
|
36
31
|
|
|
37
32
|
const APP_DOMAIN = "bsv-mcp";
|
|
38
33
|
|
|
@@ -58,9 +53,9 @@ export function registerBapFriendTool(
|
|
|
58
53
|
{ ...bapFriendArgsSchema.shape },
|
|
59
54
|
async (
|
|
60
55
|
{ targetBapId },
|
|
61
|
-
|
|
56
|
+
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
62
57
|
): Promise<CallToolResult> => {
|
|
63
|
-
const
|
|
58
|
+
const _logFunc = console.error;
|
|
64
59
|
|
|
65
60
|
try {
|
|
66
61
|
// --- Preconditions ---
|
|
@@ -95,10 +90,7 @@ export function registerBapFriendTool(
|
|
|
95
90
|
|
|
96
91
|
// 3. Derive Initial Identity
|
|
97
92
|
const bap = new BAP(xprv);
|
|
98
|
-
const idpk =
|
|
99
|
-
bap.newId().exportMemberBackup().derivedPrivateKey,
|
|
100
|
-
);
|
|
101
|
-
const identityInstance = new MemberID(idpk);
|
|
93
|
+
const idpk = bap.newId().getAccountKey();
|
|
102
94
|
const paymentAddress = payPk.toAddress();
|
|
103
95
|
|
|
104
96
|
// --- Derive friend public key ---
|
|
@@ -123,15 +115,16 @@ export function registerBapFriendTool(
|
|
|
123
115
|
friendPubKey,
|
|
124
116
|
];
|
|
125
117
|
|
|
126
|
-
const payloadBuffers = payloadParts.map(
|
|
127
|
-
|
|
118
|
+
const payloadBuffers: number[][] = payloadParts.map((part) =>
|
|
119
|
+
toArray(part),
|
|
128
120
|
);
|
|
129
121
|
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
122
|
+
const { signedData } = await signOpReturnWithAIP(
|
|
123
|
+
payloadBuffers,
|
|
124
|
+
idpk,
|
|
125
|
+
idpk.toAddress(),
|
|
126
|
+
);
|
|
127
|
+
const opReturnScript = buildOpReturnScript(signedData);
|
|
135
128
|
|
|
136
129
|
// --- Build Transaction ---
|
|
137
130
|
const tx = new Transaction();
|
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
afterEach,
|
|
3
|
-
beforeEach,
|
|
4
|
-
describe,
|
|
5
|
-
expect,
|
|
6
|
-
it,
|
|
7
|
-
mock,
|
|
8
|
-
spyOn,
|
|
9
|
-
} from "bun:test";
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test";
|
|
10
2
|
import fs from "node:fs";
|
|
11
3
|
import os from "node:os";
|
|
12
4
|
import path from "node:path";
|
|
@@ -24,18 +16,29 @@ import { type BapGenerateArgs, registerBapGenerateTool } from "./generate";
|
|
|
24
16
|
|
|
25
17
|
type GenericToolHandler = (
|
|
26
18
|
params: BapGenerateArgs,
|
|
27
|
-
|
|
19
|
+
_extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
|
|
28
20
|
) => CallToolResult | Promise<CallToolResult>;
|
|
29
21
|
|
|
22
|
+
/** Narrow the first content block of a tool result to its text payload. */
|
|
23
|
+
function firstText(result: CallToolResult): string {
|
|
24
|
+
const block = result.content?.[0];
|
|
25
|
+
if (block?.type !== "text") {
|
|
26
|
+
throw new Error(
|
|
27
|
+
`Expected a text content block, got ${block?.type ?? "none"}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return block.text;
|
|
31
|
+
}
|
|
32
|
+
|
|
30
33
|
const mockMcpServerInstance = {
|
|
31
34
|
tool: (..._args: Parameters<McpServer["tool"]>): void => undefined,
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
const KEY_DIR = path.join(os.homedir(), ".bsv-mcp");
|
|
35
|
-
const
|
|
38
|
+
const _KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
|
|
36
39
|
|
|
37
40
|
const MOCK_PAY_PK_WIF = "L3tg5yk23vBrSGZT8pfrDFQBK8n46eeot2Sf91ezNt7rhyReCA53";
|
|
38
|
-
const
|
|
41
|
+
const _MOCK_PAY_ADDRESS = PrivateKey.fromWif(MOCK_PAY_PK_WIF).toAddress();
|
|
39
42
|
const MOCK_KEYS_WITH_PAYPK = { payPk: MOCK_PAY_PK_WIF };
|
|
40
43
|
const MOCK_UTXO_SCRIPT_HASH_PLACEHOLDER =
|
|
41
44
|
"1a2b3c4d5e6f7081920a1b2c3d4e5f607081920a";
|
|
@@ -45,6 +48,7 @@ const MOCK_UTXO = {
|
|
|
45
48
|
satoshis: 100000,
|
|
46
49
|
script: `76a914${MOCK_UTXO_SCRIPT_HASH_PLACEHOLDER}88ac`,
|
|
47
50
|
};
|
|
51
|
+
const MOCK_BROADCAST_TXID = "b1c2d3e4".repeat(8);
|
|
48
52
|
|
|
49
53
|
describe("BAP Generate Tool", () => {
|
|
50
54
|
let readFileSyncSpy: ReturnType<typeof spyOn<typeof fs, "readFileSync">>;
|
|
@@ -58,7 +62,7 @@ describe("BAP Generate Tool", () => {
|
|
|
58
62
|
typeof spyOn<typeof utxoUtils, "fetchPaymentUtxos">
|
|
59
63
|
>;
|
|
60
64
|
let transactionBroadcastSpy: ReturnType<
|
|
61
|
-
typeof spyOn<Transaction
|
|
65
|
+
typeof spyOn<Transaction, "broadcast">
|
|
62
66
|
>;
|
|
63
67
|
let passphrasePromptSpy: ReturnType<
|
|
64
68
|
typeof spyOn<
|
|
@@ -96,9 +100,11 @@ describe("BAP Generate Tool", () => {
|
|
|
96
100
|
mkdirSyncSpy.mockImplementation(() => undefined);
|
|
97
101
|
writeFileSyncSpy.mockImplementation(() => undefined);
|
|
98
102
|
fetchPaymentUtxosSpy.mockResolvedValue([MOCK_UTXO]);
|
|
99
|
-
transactionBroadcastSpy.mockResolvedValue(
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
transactionBroadcastSpy.mockResolvedValue({
|
|
104
|
+
status: "success",
|
|
105
|
+
txid: MOCK_BROADCAST_TXID,
|
|
106
|
+
message: "broadcast accepted",
|
|
107
|
+
});
|
|
102
108
|
});
|
|
103
109
|
|
|
104
110
|
afterEach(() => {
|
|
@@ -159,10 +165,10 @@ describe("BAP Generate Tool", () => {
|
|
|
159
165
|
expect(writtenData.xprv?.startsWith("xprv")).toBe(true);
|
|
160
166
|
expect(writtenData.identityPk).toBeString();
|
|
161
167
|
|
|
162
|
-
expect(result
|
|
168
|
+
expect(firstText(result)).toContain(
|
|
163
169
|
"BAP HD Key and Initial Identity Generated & Saved",
|
|
164
170
|
);
|
|
165
|
-
expect(result
|
|
171
|
+
expect(firstText(result)).toContain("Registration TXID:");
|
|
166
172
|
expect(transactionBroadcastSpy).toHaveBeenCalled();
|
|
167
173
|
});
|
|
168
174
|
|
|
@@ -178,7 +184,7 @@ describe("BAP Generate Tool", () => {
|
|
|
178
184
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
179
185
|
|
|
180
186
|
expect(result.isError).toBe(true);
|
|
181
|
-
expect(result
|
|
187
|
+
expect(firstText(result)).toContain(
|
|
182
188
|
"keys.json not found. Payment Private Key (payPk) is required.",
|
|
183
189
|
);
|
|
184
190
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -196,7 +202,7 @@ describe("BAP Generate Tool", () => {
|
|
|
196
202
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
197
203
|
|
|
198
204
|
expect(result.isError).toBe(true);
|
|
199
|
-
expect(result
|
|
205
|
+
expect(firstText(result)).toContain(
|
|
200
206
|
"Payment Private Key (payPk) does not exist in keys.json.",
|
|
201
207
|
);
|
|
202
208
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -218,7 +224,7 @@ describe("BAP Generate Tool", () => {
|
|
|
218
224
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
219
225
|
|
|
220
226
|
expect(result.isError).toBe(true);
|
|
221
|
-
expect(result
|
|
227
|
+
expect(firstText(result)).toContain(
|
|
222
228
|
"BAP Master Key (xprv) already exists in keys.json.",
|
|
223
229
|
);
|
|
224
230
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -240,7 +246,7 @@ describe("BAP Generate Tool", () => {
|
|
|
240
246
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
241
247
|
|
|
242
248
|
expect(result.isError).toBe(true);
|
|
243
|
-
expect(result
|
|
249
|
+
expect(firstText(result)).toContain(
|
|
244
250
|
"BAP Identity Key (identityPk) already exists in keys.json.",
|
|
245
251
|
);
|
|
246
252
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -264,7 +270,7 @@ describe("BAP Generate Tool", () => {
|
|
|
264
270
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
265
271
|
|
|
266
272
|
expect(result.isError).toBe(true);
|
|
267
|
-
expect(result
|
|
273
|
+
expect(firstText(result)).toContain(
|
|
268
274
|
"Failed during BAP key generation/saving or transaction construction: Test HD generation error",
|
|
269
275
|
);
|
|
270
276
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -287,7 +293,7 @@ describe("BAP Generate Tool", () => {
|
|
|
287
293
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
288
294
|
|
|
289
295
|
expect(result.isError).toBe(true);
|
|
290
|
-
expect(result
|
|
296
|
+
expect(firstText(result)).toContain(
|
|
291
297
|
"Could not read or parse keys.json: JSON Parse error",
|
|
292
298
|
);
|
|
293
299
|
expect(writeFileSyncSpy).not.toHaveBeenCalled();
|
|
@@ -306,12 +312,8 @@ describe("BAP Generate Tool", () => {
|
|
|
306
312
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
307
313
|
|
|
308
314
|
expect(result.isError).toBe(false);
|
|
309
|
-
expect(result
|
|
310
|
-
|
|
311
|
-
);
|
|
312
|
-
expect(result.content?.[0]?.text).toContain(
|
|
313
|
-
"Cannot fund BAP registration.",
|
|
314
|
-
);
|
|
315
|
+
expect(firstText(result)).toContain("No UTXOs found for payment address");
|
|
316
|
+
expect(firstText(result)).toContain("Cannot fund BAP registration.");
|
|
315
317
|
expect(writeFileSyncSpy).toHaveBeenCalledTimes(1);
|
|
316
318
|
const writtenData = JSON.parse(
|
|
317
319
|
writeFileSyncSpy.mock.calls[0][1] as string,
|
|
@@ -337,10 +339,10 @@ describe("BAP Generate Tool", () => {
|
|
|
337
339
|
const result = (await handler({}, mockExtra)) as CallToolResult;
|
|
338
340
|
|
|
339
341
|
expect(result.isError).toBe(false);
|
|
340
|
-
expect(result
|
|
342
|
+
expect(firstText(result)).toContain(
|
|
341
343
|
"Keys generated and saved, but failed to broadcast BAP registration transaction",
|
|
342
344
|
);
|
|
343
|
-
expect(result
|
|
345
|
+
expect(firstText(result)).toContain("Broadcast network error");
|
|
344
346
|
expect(writeFileSyncSpy).toHaveBeenCalledTimes(1);
|
|
345
347
|
const writtenData = JSON.parse(
|
|
346
348
|
writeFileSyncSpy.mock.calls[0][1] as string,
|
package/tools/bap/generate.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { BAP_KEY_ID, BAP_PROTOCOL_ID } from "@1sat/actions";
|
|
3
4
|
import {
|
|
4
5
|
Utils as BSVUtils,
|
|
5
6
|
fromUtxo,
|
|
6
7
|
HD,
|
|
7
8
|
isBroadcastFailure,
|
|
9
|
+
KeyDeriver,
|
|
8
10
|
P2PKH,
|
|
9
|
-
PrivateKey,
|
|
10
|
-
Script,
|
|
11
|
+
type PrivateKey,
|
|
11
12
|
Transaction,
|
|
12
13
|
} from "@bsv/sdk";
|
|
13
14
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -17,6 +18,8 @@ import { z } from "zod";
|
|
|
17
18
|
import { V5Broadcaster } from "../../utils/broadcaster";
|
|
18
19
|
import { SecureKeyManager } from "../../utils/keyManager";
|
|
19
20
|
import { BAP_PREFIX } from "../constants";
|
|
21
|
+
import { signOpReturnWithAIP } from "../utils/aip";
|
|
22
|
+
import { buildOpReturnScript } from "../utils/transactionBuilder";
|
|
20
23
|
import { fetchPaymentUtxos } from "../wallet/fetchPaymentUtxos";
|
|
21
24
|
|
|
22
25
|
// Get toArray from BSV SDK Utils
|
|
@@ -26,8 +29,14 @@ const KEY_DIR = path.join(os.homedir(), ".bsv-mcp");
|
|
|
26
29
|
const KEY_FILE_PATH = path.join(KEY_DIR, "keys.json");
|
|
27
30
|
|
|
28
31
|
const bapGenerateArgsSchema = z.object({
|
|
29
|
-
alternateName: z
|
|
30
|
-
|
|
32
|
+
alternateName: z
|
|
33
|
+
.string()
|
|
34
|
+
.optional()
|
|
35
|
+
.describe("Alternate name for the BAP identity profile"),
|
|
36
|
+
description: z
|
|
37
|
+
.string()
|
|
38
|
+
.optional()
|
|
39
|
+
.describe("Description for the BAP identity profile"),
|
|
31
40
|
});
|
|
32
41
|
|
|
33
42
|
export type BapGenerateArgs = z.infer<typeof bapGenerateArgsSchema>;
|
|
@@ -51,7 +60,7 @@ async function generateBapKeys(
|
|
|
51
60
|
}> {
|
|
52
61
|
const keyManager = new SecureKeyManager({ keyDir: KEY_DIR });
|
|
53
62
|
let tempXprv = "";
|
|
54
|
-
let
|
|
63
|
+
let _identityPk: string;
|
|
55
64
|
let payPkToPreserve: PrivateKey | undefined;
|
|
56
65
|
|
|
57
66
|
// 1. Load existing keys and check preconditions
|
|
@@ -150,29 +159,22 @@ async function generateBapKeys(
|
|
|
150
159
|
|
|
151
160
|
// 3. Create BAP instance and generate identity
|
|
152
161
|
const bapInstance = new BAP(tempXprv);
|
|
153
|
-
|
|
154
|
-
// Create identity attributes
|
|
155
|
-
const identityAttributes = {
|
|
156
|
-
name: {
|
|
157
|
-
value: args?.alternateName || "Anonymous User",
|
|
158
|
-
nonce: BSVUtils.toHex(BSVUtils.toArray(Math.random().toString())),
|
|
159
|
-
},
|
|
160
|
-
description: {
|
|
161
|
-
value: args?.description || "A BAP identity managed by BSV-MCP.",
|
|
162
|
-
nonce: BSVUtils.toHex(BSVUtils.toArray(Math.random().toString())),
|
|
163
|
-
},
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
// Create new ID with the BAP instance
|
|
167
|
-
const bapId = bapInstance.newId(undefined, identityAttributes);
|
|
162
|
+
const bapId = bapInstance.newId();
|
|
168
163
|
|
|
169
164
|
// Get the generated identity information
|
|
170
|
-
const generatedIdentityKey = bapId.
|
|
165
|
+
const generatedIdentityKey = bapId.bapId;
|
|
171
166
|
const generatedIdentityAddress = bapId.rootAddress;
|
|
172
167
|
|
|
173
|
-
//
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
// rootAddress is the identity-0 key derived from the account key at
|
|
169
|
+
// protocolID [1,"sigma"], keyID "identity-0", counterparty "self".
|
|
170
|
+
// That is the key the BAP ID record declares, so it must also be the
|
|
171
|
+
// key that signs it.
|
|
172
|
+
const identityKey = new KeyDeriver(bapId.getAccountKey()).derivePrivateKey(
|
|
173
|
+
BAP_PROTOCOL_ID,
|
|
174
|
+
`${BAP_KEY_ID}-0`,
|
|
175
|
+
"self",
|
|
176
|
+
);
|
|
177
|
+
_identityPk = identityKey.toWif();
|
|
176
178
|
|
|
177
179
|
console.error(`INFO: Generated BAP identity key: ${generatedIdentityKey}`);
|
|
178
180
|
console.error(
|
|
@@ -182,7 +184,7 @@ async function generateBapKeys(
|
|
|
182
184
|
// 5. Save Keys to secure storage
|
|
183
185
|
const updatedKeys = {
|
|
184
186
|
payPk: payPkToPreserve,
|
|
185
|
-
identityPk:
|
|
187
|
+
identityPk: identityKey,
|
|
186
188
|
xprv: tempXprv,
|
|
187
189
|
};
|
|
188
190
|
|
|
@@ -201,7 +203,7 @@ async function generateBapKeys(
|
|
|
201
203
|
|
|
202
204
|
// Create the ID registration transaction output
|
|
203
205
|
// BAP ID format: OP_0 OP_RETURN <BAP_PREFIX> "ID" <identity_key> <root_address> <current_address>
|
|
204
|
-
const idPayload = [
|
|
206
|
+
const idPayload: number[][] = [
|
|
205
207
|
toArray(BAP_PREFIX, "utf8"),
|
|
206
208
|
toArray("ID"),
|
|
207
209
|
toArray(generatedIdentityKey),
|
|
@@ -210,14 +212,12 @@ async function generateBapKeys(
|
|
|
210
212
|
];
|
|
211
213
|
|
|
212
214
|
// Sign the ID payload with AIP
|
|
213
|
-
const signedIdPayload =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
BSVUtils.toHex(bytes as number[]),
|
|
215
|
+
const { signedData: signedIdPayload } = await signOpReturnWithAIP(
|
|
216
|
+
idPayload,
|
|
217
|
+
identityKey,
|
|
218
|
+
bapId.rootAddress,
|
|
218
219
|
);
|
|
219
|
-
const
|
|
220
|
-
const idScript = Script.fromASM(idAsmString);
|
|
220
|
+
const idScript = buildOpReturnScript(signedIdPayload);
|
|
221
221
|
|
|
222
222
|
// Create the ALIAS transaction output
|
|
223
223
|
// First create the structured data object for the alias
|
|
@@ -229,7 +229,7 @@ async function generateBapKeys(
|
|
|
229
229
|
url: `bitcoin:${paymentAddress}`,
|
|
230
230
|
};
|
|
231
231
|
|
|
232
|
-
const aliasPayload = [
|
|
232
|
+
const aliasPayload: number[][] = [
|
|
233
233
|
toArray(BAP_PREFIX, "utf8"),
|
|
234
234
|
toArray("ALIAS"),
|
|
235
235
|
toArray(generatedIdentityKey),
|
|
@@ -237,14 +237,12 @@ async function generateBapKeys(
|
|
|
237
237
|
];
|
|
238
238
|
|
|
239
239
|
// Sign the ALIAS payload with AIP
|
|
240
|
-
const signedAliasPayload =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
BSVUtils.toHex(bytes as number[]),
|
|
240
|
+
const { signedData: signedAliasPayload } = await signOpReturnWithAIP(
|
|
241
|
+
aliasPayload,
|
|
242
|
+
identityKey,
|
|
243
|
+
bapId.rootAddress,
|
|
245
244
|
);
|
|
246
|
-
const
|
|
247
|
-
const aliasScript = Script.fromASM(aliasAsmString);
|
|
245
|
+
const aliasScript = buildOpReturnScript(signedAliasPayload);
|
|
248
246
|
|
|
249
247
|
// 6. Build Transaction
|
|
250
248
|
const tx = new Transaction();
|
|
@@ -409,14 +407,21 @@ async function generateBapKeys(
|
|
|
409
407
|
/**
|
|
410
408
|
* Registers the bap_generate tool
|
|
411
409
|
*/
|
|
412
|
-
export function registerBapGenerateTool(
|
|
410
|
+
export function registerBapGenerateTool(
|
|
411
|
+
server: McpServer,
|
|
412
|
+
config?: { disableBroadcasting?: boolean },
|
|
413
|
+
) {
|
|
413
414
|
server.tool(
|
|
414
415
|
"bap_generate",
|
|
415
416
|
"Generates a BAP HD master key AND derives the first identity key if no BAP keys (xprv or identityPk) exist. Saves keys to secure storage. Attempts on-chain registration using payPk (honors DISABLE_BROADCASTING). Optionally takes alternateName and description for the profile.",
|
|
416
417
|
{ ...bapGenerateArgsSchema.shape },
|
|
417
418
|
async ({ alternateName, description }): Promise<CallToolResult> => {
|
|
418
419
|
try {
|
|
419
|
-
const result = await generateBapKeys({
|
|
420
|
+
const result = await generateBapKeys({
|
|
421
|
+
alternateName,
|
|
422
|
+
description,
|
|
423
|
+
disableBroadcasting: config?.disableBroadcasting,
|
|
424
|
+
});
|
|
420
425
|
// Format result as JSON
|
|
421
426
|
return {
|
|
422
427
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|