bsv-mcp 0.2.15 → 0.3.1
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 +94 -10
- package/README.md +95 -13
- package/dist/app.html +483 -106
- package/dist/index.js +227724 -117380
- package/index.ts +523 -63
- 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/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 +22 -13
- 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 +567 -312
- package/tools/wallet/cancelListing.ts +29 -37
- package/tools/wallet/createOrdinals.ts +43 -27
- package/tools/wallet/droplit.ts +124 -0
- 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 +53 -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 +151 -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 +17 -17
- 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/test-mcp-server.ts +0 -157
- package/tools/wallet/sendToAddress.ts +0 -74
- package/tools/wallet/setupDroplet.ts +0 -161
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import {
|
|
4
|
+
createErrorResponse,
|
|
5
|
+
createSuccessResponse,
|
|
6
|
+
} from "../utils/errorHandler";
|
|
7
|
+
import type { IntegratedWallet } from "./integratedWallet";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Droplit setup and faucet administration.
|
|
11
|
+
*
|
|
12
|
+
* These were one tool behind an `action` enum, which made every argument
|
|
13
|
+
* conditional on the action and left the caller to work out which combination
|
|
14
|
+
* was valid. They are three tools now, each declaring only what it needs.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
type DroplitClient = NonNullable<
|
|
18
|
+
ReturnType<IntegratedWallet["getDroplitClient"]>
|
|
19
|
+
>;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Every Droplit call needs a configured client and every write needs the
|
|
23
|
+
* signing key, so resolve both up front rather than failing part-way through a
|
|
24
|
+
* request with a different message depending on which tool was called.
|
|
25
|
+
*/
|
|
26
|
+
async function requireDroplit(integratedWallet: IntegratedWallet): Promise<{
|
|
27
|
+
apiUrl: string;
|
|
28
|
+
publicKeyHex: string;
|
|
29
|
+
client: DroplitClient;
|
|
30
|
+
}> {
|
|
31
|
+
const client = integratedWallet.getDroplitClient();
|
|
32
|
+
if (!client) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
"Droplit client is not configured. Set DROPLIT_API_URL and DROPLIT_FAUCET_NAME.",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const { apiUrl } = client.getConfig();
|
|
39
|
+
return { apiUrl, publicKeyHex: await client.getIdentityKey(), client };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function failIfNotOk(
|
|
43
|
+
response: Response,
|
|
44
|
+
attempted: string,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
if (response.ok) return;
|
|
47
|
+
throw new Error(
|
|
48
|
+
`${attempted} failed (${response.status}): ${await response.text()}`,
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function registerSetupDroplitTools(
|
|
53
|
+
server: McpServer,
|
|
54
|
+
integratedWallet: IntegratedWallet,
|
|
55
|
+
) {
|
|
56
|
+
server.tool(
|
|
57
|
+
"wallet_registerDroplitKey",
|
|
58
|
+
"Registers public authentication material. Registration does not grant sponsor access; a sponsor owner must approve the wallet separately.",
|
|
59
|
+
{},
|
|
60
|
+
async () => {
|
|
61
|
+
try {
|
|
62
|
+
const { apiUrl, publicKeyHex } = await requireDroplit(integratedWallet);
|
|
63
|
+
|
|
64
|
+
const response = await fetch(`${apiUrl}/auth/register`, {
|
|
65
|
+
method: "POST",
|
|
66
|
+
headers: { "Content-Type": "application/json" },
|
|
67
|
+
body: JSON.stringify({ publicKey: publicKeyHex }),
|
|
68
|
+
});
|
|
69
|
+
await failIfNotOk(response, "Registering the Droplit key");
|
|
70
|
+
|
|
71
|
+
return createSuccessResponse({
|
|
72
|
+
message: "Public key registered with Droplit",
|
|
73
|
+
publicKey: publicKeyHex,
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return createErrorResponse(error);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
server.tool(
|
|
82
|
+
"wallet_createDroplitFaucet",
|
|
83
|
+
"Creates a faucet owned by this wallet which must be funded before use. This does not provide free credit or approval for another sponsor.",
|
|
84
|
+
{
|
|
85
|
+
faucetName: z
|
|
86
|
+
.string()
|
|
87
|
+
.min(1)
|
|
88
|
+
.describe("Identifier for the new faucet; must be unique"),
|
|
89
|
+
fixedDropSats: z
|
|
90
|
+
.number()
|
|
91
|
+
.int()
|
|
92
|
+
.positive()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe(
|
|
95
|
+
"Satoshis paid out per tap. Server default applies if unset.",
|
|
96
|
+
),
|
|
97
|
+
maxConsolidationInputs: z
|
|
98
|
+
.number()
|
|
99
|
+
.int()
|
|
100
|
+
.positive()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Cap on inputs the faucet consolidates in one transaction"),
|
|
103
|
+
},
|
|
104
|
+
async ({ faucetName, fixedDropSats, maxConsolidationInputs }) => {
|
|
105
|
+
try {
|
|
106
|
+
const { client } = await requireDroplit(integratedWallet);
|
|
107
|
+
|
|
108
|
+
// Ownership comes from the BRC-103/104 identity established by the
|
|
109
|
+
// handshake, not from the body, so the key is never sent here.
|
|
110
|
+
const body = {
|
|
111
|
+
name: faucetName,
|
|
112
|
+
...(fixedDropSats !== undefined && {
|
|
113
|
+
fixed_drop_sats: fixedDropSats,
|
|
114
|
+
}),
|
|
115
|
+
...(maxConsolidationInputs !== undefined && {
|
|
116
|
+
max_consolidation_inputs: maxConsolidationInputs,
|
|
117
|
+
}),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const response = await client.authenticatedFetch("/faucets", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body,
|
|
123
|
+
});
|
|
124
|
+
await failIfNotOk(response, "Creating the Droplit faucet");
|
|
125
|
+
|
|
126
|
+
return createSuccessResponse({
|
|
127
|
+
message: "Faucet created",
|
|
128
|
+
faucet: await response.json(),
|
|
129
|
+
});
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return createErrorResponse(error);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
server.tool(
|
|
137
|
+
"wallet_checkDroplitFaucetStatus",
|
|
138
|
+
"Reads public faucet balance and payout settings. This is not proof of caller authorization; use droplit_getAccess for that.",
|
|
139
|
+
{},
|
|
140
|
+
async () => {
|
|
141
|
+
try {
|
|
142
|
+
const { client } = await requireDroplit(integratedWallet);
|
|
143
|
+
return createSuccessResponse({
|
|
144
|
+
faucetStatus: await client.getFaucetStatus(),
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
return createErrorResponse(error);
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
}
|
package/tools/wallet/signBsm.ts
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { McpServer } from
|
|
3
|
-
import { z } from
|
|
1
|
+
import { type OneSatContext, signBsm } from "@1sat/actions";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
4
|
|
|
5
|
-
export function registerSignBsmTool(
|
|
6
|
-
server: McpServer,
|
|
7
|
-
ctx?: OneSatContext,
|
|
8
|
-
) {
|
|
5
|
+
export function registerSignBsmTool(server: McpServer, ctx?: OneSatContext) {
|
|
9
6
|
server.tool(
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
"wallet_signBsm",
|
|
8
|
+
"Sign a message using BSM (Bitcoin Signed Message) format",
|
|
12
9
|
{
|
|
13
|
-
message: z.string().describe(
|
|
14
|
-
encoding: z
|
|
10
|
+
message: z.string().describe("The message to sign"),
|
|
11
|
+
encoding: z
|
|
12
|
+
.enum(["utf8", "hex", "base64"])
|
|
13
|
+
.optional()
|
|
14
|
+
.describe("Message encoding format"),
|
|
15
15
|
},
|
|
16
16
|
async (params) => {
|
|
17
17
|
try {
|
|
18
|
-
if (!ctx) throw new Error(
|
|
19
|
-
const result = await signBsm.execute(ctx, params)
|
|
18
|
+
if (!ctx) throw new Error("BRC-100 wallet context not available");
|
|
19
|
+
const result = await signBsm.execute(ctx, params);
|
|
20
20
|
return {
|
|
21
|
-
content: [{ type:
|
|
22
|
-
}
|
|
21
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
22
|
+
};
|
|
23
23
|
} catch (err: unknown) {
|
|
24
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
25
|
-
return { content: [{ type:
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
|
-
)
|
|
28
|
+
);
|
|
29
29
|
}
|
package/tools/wallet/sweepBsv.ts
CHANGED
|
@@ -1,70 +1,91 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import { sweepBsv } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { sweepBsv } from "@1sat/actions";
|
|
3
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
7
|
+
import { redactKeyMaterial, redactUnknown } from "../../utils/redact";
|
|
5
8
|
|
|
6
9
|
const sweepInputSchema = z.object({
|
|
7
|
-
outpoint: z.string().describe(
|
|
8
|
-
satoshis: z.number().int().describe(
|
|
9
|
-
lockingScript: z.string().describe(
|
|
10
|
-
})
|
|
10
|
+
outpoint: z.string().describe("Outpoint (txid_vout)"),
|
|
11
|
+
satoshis: z.number().int().describe("Satoshis in output"),
|
|
12
|
+
lockingScript: z.string().describe("Locking script hex"),
|
|
13
|
+
});
|
|
11
14
|
|
|
12
15
|
const sweepBsvSchema = z.object({
|
|
13
|
-
inputs: z
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
inputs: z
|
|
17
|
+
.array(sweepInputSchema)
|
|
18
|
+
.describe("UTXOs to sweep (use prepareSweepInputs to build these)"),
|
|
19
|
+
wif: z.string().describe("WIF private key controlling the inputs"),
|
|
20
|
+
amount: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe(
|
|
25
|
+
"Amount to sweep (satoshis). If omitted, sweeps all input value.",
|
|
26
|
+
),
|
|
27
|
+
});
|
|
17
28
|
|
|
18
29
|
export function registerSweepBsvTool(
|
|
19
30
|
server: McpServer,
|
|
20
31
|
ctx: OneSatContext | undefined,
|
|
21
32
|
) {
|
|
22
33
|
server.tool(
|
|
23
|
-
|
|
24
|
-
|
|
34
|
+
"wallet_sweepBsv",
|
|
35
|
+
"Sweep BSV from an external WIF private key into the wallet",
|
|
25
36
|
{ ...sweepBsvSchema.shape },
|
|
26
37
|
async ({ inputs, wif, amount }) => {
|
|
27
38
|
if (!ctx) {
|
|
28
39
|
return {
|
|
29
40
|
content: [
|
|
30
41
|
{
|
|
31
|
-
type:
|
|
32
|
-
text:
|
|
42
|
+
type: "text" as const,
|
|
43
|
+
text: "BRC-100 wallet context not available",
|
|
33
44
|
},
|
|
34
45
|
],
|
|
35
46
|
isError: true,
|
|
36
|
-
}
|
|
47
|
+
};
|
|
37
48
|
}
|
|
38
49
|
|
|
39
50
|
try {
|
|
40
|
-
|
|
51
|
+
assertBroadcastAllowed("wallet_sweepBsv");
|
|
52
|
+
const key = PrivateKey.fromWif(wif);
|
|
53
|
+
const result = await sweepBsv.execute(ctx, {
|
|
54
|
+
inputs,
|
|
55
|
+
keys: inputs.map(() => key),
|
|
56
|
+
amount,
|
|
57
|
+
});
|
|
41
58
|
|
|
42
59
|
if (result.error) {
|
|
43
60
|
return {
|
|
44
|
-
content: [
|
|
61
|
+
content: [
|
|
62
|
+
{ type: "text" as const, text: redactKeyMaterial(result.error) },
|
|
63
|
+
],
|
|
45
64
|
isError: true,
|
|
46
|
-
}
|
|
65
|
+
};
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
return {
|
|
50
69
|
content: [
|
|
51
70
|
{
|
|
52
|
-
type:
|
|
53
|
-
text:
|
|
71
|
+
type: "text" as const,
|
|
72
|
+
text: redactUnknown(result, 2),
|
|
54
73
|
},
|
|
55
74
|
],
|
|
56
|
-
}
|
|
75
|
+
};
|
|
57
76
|
} catch (error) {
|
|
58
77
|
return {
|
|
59
78
|
content: [
|
|
60
79
|
{
|
|
61
|
-
type:
|
|
62
|
-
text:
|
|
80
|
+
type: "text" as const,
|
|
81
|
+
text: redactKeyMaterial(
|
|
82
|
+
error instanceof Error ? error.message : String(error),
|
|
83
|
+
),
|
|
63
84
|
},
|
|
64
85
|
],
|
|
65
86
|
isError: true,
|
|
66
|
-
}
|
|
87
|
+
};
|
|
67
88
|
}
|
|
68
89
|
},
|
|
69
|
-
)
|
|
90
|
+
);
|
|
70
91
|
}
|
|
@@ -1,71 +1,85 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import { sweepBsv21 } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { sweepBsv21 } from "@1sat/actions";
|
|
3
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
7
|
+
import { redactKeyMaterial, redactUnknown } from "../../utils/redact";
|
|
5
8
|
|
|
6
9
|
const sweepBsv21InputSchema = z.object({
|
|
7
|
-
outpoint: z.string().describe(
|
|
8
|
-
satoshis: z.number().int().describe(
|
|
9
|
-
lockingScript: z.string().describe(
|
|
10
|
-
tokenId: z.string().describe(
|
|
11
|
-
amount: z.string().describe(
|
|
12
|
-
})
|
|
10
|
+
outpoint: z.string().describe("Outpoint (txid_vout)"),
|
|
11
|
+
satoshis: z.number().int().describe("Satoshis (should be 1)"),
|
|
12
|
+
lockingScript: z.string().describe("Locking script hex"),
|
|
13
|
+
tokenId: z.string().describe("Token ID (txid_vout format)"),
|
|
14
|
+
amount: z.string().describe("Token amount as string"),
|
|
15
|
+
});
|
|
13
16
|
|
|
14
17
|
const sweepBsv21Schema = z.object({
|
|
15
|
-
inputs: z
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
inputs: z
|
|
19
|
+
.array(sweepBsv21InputSchema)
|
|
20
|
+
.describe("Token UTXOs to sweep (must all be same tokenId)"),
|
|
21
|
+
wif: z.string().describe("WIF private key controlling the inputs"),
|
|
22
|
+
});
|
|
18
23
|
|
|
19
24
|
export function registerSweepBsv21Tool(
|
|
20
25
|
server: McpServer,
|
|
21
26
|
ctx: OneSatContext | undefined,
|
|
22
27
|
) {
|
|
23
28
|
server.tool(
|
|
24
|
-
|
|
25
|
-
|
|
29
|
+
"wallet_sweepBsv21",
|
|
30
|
+
"Sweep BSV21 tokens from an external WIF private key into the wallet",
|
|
26
31
|
{ ...sweepBsv21Schema.shape },
|
|
27
32
|
async ({ inputs, wif }) => {
|
|
28
33
|
if (!ctx) {
|
|
29
34
|
return {
|
|
30
35
|
content: [
|
|
31
36
|
{
|
|
32
|
-
type:
|
|
33
|
-
text:
|
|
37
|
+
type: "text" as const,
|
|
38
|
+
text: "BRC-100 wallet context not available",
|
|
34
39
|
},
|
|
35
40
|
],
|
|
36
41
|
isError: true,
|
|
37
|
-
}
|
|
42
|
+
};
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
try {
|
|
41
|
-
|
|
46
|
+
assertBroadcastAllowed("wallet_sweepBsv21");
|
|
47
|
+
const key = PrivateKey.fromWif(wif);
|
|
48
|
+
const result = await sweepBsv21.execute(ctx, {
|
|
49
|
+
inputs,
|
|
50
|
+
keys: inputs.map(() => key),
|
|
51
|
+
});
|
|
42
52
|
|
|
43
53
|
if (result.error) {
|
|
44
54
|
return {
|
|
45
|
-
content: [
|
|
55
|
+
content: [
|
|
56
|
+
{ type: "text" as const, text: redactKeyMaterial(result.error) },
|
|
57
|
+
],
|
|
46
58
|
isError: true,
|
|
47
|
-
}
|
|
59
|
+
};
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
return {
|
|
51
63
|
content: [
|
|
52
64
|
{
|
|
53
|
-
type:
|
|
54
|
-
text:
|
|
65
|
+
type: "text" as const,
|
|
66
|
+
text: redactUnknown(result, 2),
|
|
55
67
|
},
|
|
56
68
|
],
|
|
57
|
-
}
|
|
69
|
+
};
|
|
58
70
|
} catch (error) {
|
|
59
71
|
return {
|
|
60
72
|
content: [
|
|
61
73
|
{
|
|
62
|
-
type:
|
|
63
|
-
text:
|
|
74
|
+
type: "text" as const,
|
|
75
|
+
text: redactKeyMaterial(
|
|
76
|
+
error instanceof Error ? error.message : String(error),
|
|
77
|
+
),
|
|
64
78
|
},
|
|
65
79
|
],
|
|
66
80
|
isError: true,
|
|
67
|
-
}
|
|
81
|
+
};
|
|
68
82
|
}
|
|
69
83
|
},
|
|
70
|
-
)
|
|
84
|
+
);
|
|
71
85
|
}
|
|
@@ -1,69 +1,81 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import { sweepOrdinals } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { sweepOrdinals } from "@1sat/actions";
|
|
3
|
+
import { PrivateKey } from "@bsv/sdk";
|
|
4
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
7
|
+
import { redactKeyMaterial, redactUnknown } from "../../utils/redact";
|
|
5
8
|
|
|
6
9
|
const sweepInputSchema = z.object({
|
|
7
|
-
outpoint: z.string().describe(
|
|
8
|
-
satoshis: z.number().int().describe(
|
|
9
|
-
lockingScript: z.string().describe(
|
|
10
|
-
})
|
|
10
|
+
outpoint: z.string().describe("Outpoint (txid_vout)"),
|
|
11
|
+
satoshis: z.number().int().describe("Satoshis (should be 1)"),
|
|
12
|
+
lockingScript: z.string().describe("Locking script hex"),
|
|
13
|
+
});
|
|
11
14
|
|
|
12
15
|
const sweepOrdinalsSchema = z.object({
|
|
13
|
-
inputs: z.array(sweepInputSchema).describe(
|
|
14
|
-
wif: z.string().describe(
|
|
15
|
-
})
|
|
16
|
+
inputs: z.array(sweepInputSchema).describe("Ordinal UTXOs to sweep"),
|
|
17
|
+
wif: z.string().describe("WIF private key controlling the inputs"),
|
|
18
|
+
});
|
|
16
19
|
|
|
17
20
|
export function registerSweepOrdinalsTool(
|
|
18
21
|
server: McpServer,
|
|
19
22
|
ctx: OneSatContext | undefined,
|
|
20
23
|
) {
|
|
21
24
|
server.tool(
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
"wallet_sweepOrdinals",
|
|
26
|
+
"Sweep ordinals from an external WIF private key into the wallet",
|
|
24
27
|
{ ...sweepOrdinalsSchema.shape },
|
|
25
28
|
async ({ inputs, wif }) => {
|
|
26
29
|
if (!ctx) {
|
|
27
30
|
return {
|
|
28
31
|
content: [
|
|
29
32
|
{
|
|
30
|
-
type:
|
|
31
|
-
text:
|
|
33
|
+
type: "text" as const,
|
|
34
|
+
text: "BRC-100 wallet context not available",
|
|
32
35
|
},
|
|
33
36
|
],
|
|
34
37
|
isError: true,
|
|
35
|
-
}
|
|
38
|
+
};
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
try {
|
|
39
|
-
|
|
42
|
+
assertBroadcastAllowed("wallet_sweepOrdinals");
|
|
43
|
+
const key = PrivateKey.fromWif(wif);
|
|
44
|
+
const result = await sweepOrdinals.execute(ctx, {
|
|
45
|
+
inputs,
|
|
46
|
+
keys: inputs.map(() => key),
|
|
47
|
+
});
|
|
40
48
|
|
|
41
49
|
if (result.error) {
|
|
42
50
|
return {
|
|
43
|
-
content: [
|
|
51
|
+
content: [
|
|
52
|
+
{ type: "text" as const, text: redactKeyMaterial(result.error) },
|
|
53
|
+
],
|
|
44
54
|
isError: true,
|
|
45
|
-
}
|
|
55
|
+
};
|
|
46
56
|
}
|
|
47
57
|
|
|
48
58
|
return {
|
|
49
59
|
content: [
|
|
50
60
|
{
|
|
51
|
-
type:
|
|
52
|
-
text:
|
|
61
|
+
type: "text" as const,
|
|
62
|
+
text: redactUnknown(result, 2),
|
|
53
63
|
},
|
|
54
64
|
],
|
|
55
|
-
}
|
|
65
|
+
};
|
|
56
66
|
} catch (error) {
|
|
57
67
|
return {
|
|
58
68
|
content: [
|
|
59
69
|
{
|
|
60
|
-
type:
|
|
61
|
-
text:
|
|
70
|
+
type: "text" as const,
|
|
71
|
+
text: redactKeyMaterial(
|
|
72
|
+
error instanceof Error ? error.message : String(error),
|
|
73
|
+
),
|
|
62
74
|
},
|
|
63
75
|
],
|
|
64
76
|
isError: true,
|
|
65
|
-
}
|
|
77
|
+
};
|
|
66
78
|
}
|
|
67
79
|
},
|
|
68
|
-
)
|
|
80
|
+
);
|
|
69
81
|
}
|
package/tools/wallet/tools.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { PrivateKey } from "@bsv/sdk";
|
|
|
3
3
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
4
|
import { registerA2bPublishMcpTool } from "./a2bPublishMcp";
|
|
5
5
|
import { registerBrc100Tools } from "./brc100";
|
|
6
|
+
import { registerCancelListingTool } from "./cancelListing";
|
|
6
7
|
import { registerCreateOrdinalsTool } from "./createOrdinals";
|
|
7
8
|
import { registerGatherCollectionInfoTool } from "./gatherCollectionInfo";
|
|
8
9
|
import { registerGetAddressTool } from "./getAddress";
|
|
@@ -10,28 +11,27 @@ import { registerWalletGetBalanceTool } from "./getBalance";
|
|
|
10
11
|
import { registerGetBsv21BalancesTool } from "./getBsv21Balances";
|
|
11
12
|
import { registerGetLockDataTool } from "./getLockData";
|
|
12
13
|
import { registerGetOrdinalsTool } from "./getOrdinals";
|
|
14
|
+
import { registerListOrdinalTool } from "./listOrdinal";
|
|
13
15
|
import { registerListTokensTool } from "./listTokens";
|
|
16
|
+
import { registerLockBsvTool } from "./lockBsv";
|
|
14
17
|
import { registerMintCollectionTool } from "./mintCollection";
|
|
18
|
+
import { registerOpnsDeregisterTool } from "./opnsDeregister";
|
|
19
|
+
import { registerOpnsRegisterTool } from "./opnsRegister";
|
|
15
20
|
import { registerPurchaseListingTool } from "./purchaseListing";
|
|
16
21
|
import { registerRefreshUtxosTool } from "./refreshUtxos";
|
|
17
|
-
import { registerSendToAddressTool } from "./sendToAddress";
|
|
18
|
-
import { registerSignBsmTool } from "./signBsm";
|
|
19
|
-
import { registerTransferOrdTokenTool } from "./transferOrdToken";
|
|
20
|
-
import { registerListOrdinalTool } from "./listOrdinal";
|
|
21
|
-
import { registerCancelListingTool } from "./cancelListing";
|
|
22
22
|
import { registerSendAllBsvTool } from "./sendAllBsv";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { registerOpnsRegisterTool } from "./opnsRegister";
|
|
26
|
-
import { registerOpnsDeregisterTool } from "./opnsDeregister";
|
|
23
|
+
import { registerSendBsvTool } from "./sendBsv";
|
|
24
|
+
import { registerSignBsmTool } from "./signBsm";
|
|
27
25
|
import { registerSweepBsvTool } from "./sweepBsv";
|
|
28
|
-
import { registerSweepOrdinalsTool } from "./sweepOrdinals";
|
|
29
26
|
import { registerSweepBsv21Tool } from "./sweepBsv21";
|
|
27
|
+
import { registerSweepOrdinalsTool } from "./sweepOrdinals";
|
|
28
|
+
import { registerTransferOrdTokenTool } from "./transferOrdToken";
|
|
29
|
+
import { registerUnlockBsvTool } from "./unlockBsv";
|
|
30
30
|
import type { Wallet } from "./wallet";
|
|
31
31
|
|
|
32
32
|
export function registerWalletTools(
|
|
33
33
|
server: McpServer,
|
|
34
|
-
wallet: Wallet,
|
|
34
|
+
wallet: Wallet | undefined,
|
|
35
35
|
config: {
|
|
36
36
|
disableBroadcasting: boolean;
|
|
37
37
|
enableA2bTools: boolean;
|
|
@@ -39,8 +39,7 @@ export function registerWalletTools(
|
|
|
39
39
|
ctx?: OneSatContext;
|
|
40
40
|
},
|
|
41
41
|
): void {
|
|
42
|
-
|
|
43
|
-
registerSendToAddressTool(server, config.ctx);
|
|
42
|
+
registerSendBsvTool(server, config.ctx);
|
|
44
43
|
|
|
45
44
|
// Register the wallet_getAddress tool
|
|
46
45
|
registerGetAddressTool(server, config.ctx);
|
|
@@ -61,7 +60,7 @@ export function registerWalletTools(
|
|
|
61
60
|
registerBrc100Tools(server, config.ctx);
|
|
62
61
|
|
|
63
62
|
// A2B tools have to be explicitly enabled
|
|
64
|
-
if (config.enableA2bTools) {
|
|
63
|
+
if (config.enableA2bTools && wallet && config.identityPk) {
|
|
65
64
|
// Register the wallet_a2bPublishMcp tool
|
|
66
65
|
registerA2bPublishMcpTool(server, wallet, config.identityPk, {
|
|
67
66
|
disableBroadcasting: config.disableBroadcasting,
|
|
@@ -72,8 +71,10 @@ export function registerWalletTools(
|
|
|
72
71
|
registerCreateOrdinalsTool(server, config.ctx);
|
|
73
72
|
|
|
74
73
|
// Register collection tools
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
if (wallet) {
|
|
75
|
+
registerGatherCollectionInfoTool(server, wallet);
|
|
76
|
+
registerMintCollectionTool(server, wallet);
|
|
77
|
+
}
|
|
77
78
|
|
|
78
79
|
// Register read-only wallet tools
|
|
79
80
|
registerGetOrdinalsTool(server, config.ctx);
|
|
@@ -95,5 +96,4 @@ export function registerWalletTools(
|
|
|
95
96
|
registerSweepBsvTool(server, config.ctx);
|
|
96
97
|
registerSweepOrdinalsTool(server, config.ctx);
|
|
97
98
|
registerSweepBsv21Tool(server, config.ctx);
|
|
98
|
-
|
|
99
99
|
}
|
|
@@ -50,14 +50,16 @@ export async function executeTransaction<TArgs>(
|
|
|
50
50
|
throw new Error("Could not get wallet address");
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
//
|
|
53
|
+
// Absent means "do not use identity features". Present but unparseable
|
|
54
|
+
// means the caller meant to sign and the key is wrong, so proceeding
|
|
55
|
+
// would silently produce an unsigned transaction.
|
|
54
56
|
let identityKey: PrivateKey | undefined;
|
|
55
57
|
if (identityKeyWif) {
|
|
56
58
|
try {
|
|
57
59
|
identityKey = PrivateKey.fromWif(identityKeyWif);
|
|
58
60
|
} catch (e) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
throw new Error(
|
|
62
|
+
`An identity key was supplied but is not a valid WIF: ${formatError(e)}`,
|
|
61
63
|
);
|
|
62
64
|
}
|
|
63
65
|
}
|
|
@@ -96,8 +98,14 @@ export function getIdentityKeyFromEnv(): PrivateKey | undefined {
|
|
|
96
98
|
try {
|
|
97
99
|
return PrivateKey.fromWif(identityKeyWif);
|
|
98
100
|
} catch (e) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
+
// Unset means "do not sign" and is a valid configuration. Set but
|
|
102
|
+
// unparseable means the operator intended to sign and the key is wrong,
|
|
103
|
+
// so returning undefined here would silently publish unsigned data under
|
|
104
|
+
// no identity. Fail instead. The parse error carries no key material --
|
|
105
|
+
// @bsv/sdk reports "Invalid checksum" without echoing its input.
|
|
106
|
+
throw new Error(
|
|
107
|
+
`IDENTITY_KEY_WIF is set but is not a valid WIF: ${formatError(e)}`,
|
|
108
|
+
);
|
|
101
109
|
}
|
|
102
110
|
}
|
|
103
111
|
|