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
|
@@ -1,80 +1,70 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import {
|
|
3
|
-
import type { McpServer } from
|
|
4
|
-
import { z } from
|
|
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
|
-
})
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { registerOpns } from "@1sat/actions";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
13
6
|
|
|
14
7
|
const opnsRegisterArgsSchema = z.object({
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.array(z.number())
|
|
20
|
-
.describe(
|
|
21
|
-
"BEEF bytes from listOutputs (include: 'entire transactions')",
|
|
22
|
-
),
|
|
23
|
-
})
|
|
8
|
+
id: z
|
|
9
|
+
.string()
|
|
10
|
+
.describe("Tracking id of the OpNS name in the wallet's OPNS basket"),
|
|
11
|
+
});
|
|
24
12
|
|
|
25
13
|
export function registerOpnsRegisterTool(
|
|
26
14
|
server: McpServer,
|
|
27
15
|
ctx: OneSatContext | undefined,
|
|
28
16
|
) {
|
|
29
17
|
server.tool(
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
"wallet_opnsRegister",
|
|
19
|
+
"Register an OpNS name",
|
|
32
20
|
{ ...opnsRegisterArgsSchema.shape },
|
|
33
|
-
async ({
|
|
21
|
+
async ({ id }) => {
|
|
34
22
|
if (!ctx) {
|
|
35
23
|
return {
|
|
36
24
|
content: [
|
|
37
25
|
{
|
|
38
|
-
type:
|
|
39
|
-
text:
|
|
26
|
+
type: "text",
|
|
27
|
+
text: "Wallet not initialized. Please configure a wallet before registering.",
|
|
40
28
|
},
|
|
41
29
|
],
|
|
42
30
|
isError: true,
|
|
43
|
-
}
|
|
31
|
+
};
|
|
44
32
|
}
|
|
45
33
|
|
|
46
34
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
inputBEEF,
|
|
50
|
-
})
|
|
35
|
+
assertBroadcastAllowed("wallet_opnsRegister");
|
|
36
|
+
const result = await registerOpns.execute(ctx, { id });
|
|
51
37
|
|
|
52
38
|
if (result.error) {
|
|
53
39
|
return {
|
|
54
|
-
content: [{ type:
|
|
40
|
+
content: [{ type: "text", text: result.error }],
|
|
55
41
|
isError: true,
|
|
56
|
-
}
|
|
42
|
+
};
|
|
57
43
|
}
|
|
58
44
|
|
|
59
45
|
return {
|
|
60
46
|
content: [
|
|
61
47
|
{
|
|
62
|
-
type:
|
|
63
|
-
text: JSON.stringify(
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify(
|
|
50
|
+
result,
|
|
51
|
+
(_, v) => (typeof v === "bigint" ? v.toString() : v),
|
|
52
|
+
2,
|
|
53
|
+
),
|
|
64
54
|
},
|
|
65
55
|
],
|
|
66
|
-
}
|
|
56
|
+
};
|
|
67
57
|
} catch (err: unknown) {
|
|
68
58
|
return {
|
|
69
59
|
content: [
|
|
70
60
|
{
|
|
71
|
-
type:
|
|
61
|
+
type: "text",
|
|
72
62
|
text: err instanceof Error ? err.message : String(err),
|
|
73
63
|
},
|
|
74
64
|
],
|
|
75
65
|
isError: true,
|
|
76
|
-
}
|
|
66
|
+
};
|
|
77
67
|
}
|
|
78
68
|
},
|
|
79
|
-
)
|
|
69
|
+
);
|
|
80
70
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
MARKET_FEE_PERCENTAGE,
|
|
4
|
+
MINIMUM_MARKET_FEE_SATOSHIS,
|
|
5
|
+
} from "../constants";
|
|
6
|
+
import { computeMarketplaceRate } from "./purchaseListing";
|
|
7
|
+
|
|
8
|
+
describe("computeMarketplaceRate", () => {
|
|
9
|
+
it("applies the minimum fee floor below the threshold", () => {
|
|
10
|
+
const price = 100_000;
|
|
11
|
+
|
|
12
|
+
expect(computeMarketplaceRate(price)).toBe(
|
|
13
|
+
MINIMUM_MARKET_FEE_SATOSHIS / price,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("applies the percentage above the minimum-fee threshold", () => {
|
|
18
|
+
expect(computeMarketplaceRate(1_000_000)).toBe(MARKET_FEE_PERCENTAGE);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("returns a finite rate for a zero price", () => {
|
|
22
|
+
const rate = computeMarketplaceRate(0);
|
|
23
|
+
|
|
24
|
+
expect(rate).toBe(MARKET_FEE_PERCENTAGE);
|
|
25
|
+
expect(Number.isFinite(rate)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -1,85 +1,104 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import {
|
|
3
|
-
import type { McpServer } from
|
|
4
|
-
import type { CallToolResult } from
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { buyBsv21, buyOrdinal } from "@1sat/actions";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
5
6
|
import {
|
|
6
7
|
MARKET_FEE_PERCENTAGE,
|
|
7
8
|
MARKET_WALLET_ADDRESS,
|
|
8
9
|
MINIMUM_MARKET_FEE_SATOSHIS,
|
|
9
|
-
} from
|
|
10
|
-
import { purchaseListingArgsSchema } from
|
|
10
|
+
} from "../constants";
|
|
11
|
+
import { purchaseListingArgsSchema } from "./schemas";
|
|
12
|
+
|
|
13
|
+
export function computeMarketplaceRate(listingPriceSatoshis: number): number {
|
|
14
|
+
if (listingPriceSatoshis === 0) {
|
|
15
|
+
return MARKET_FEE_PERCENTAGE;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const fee = Math.round(listingPriceSatoshis * MARKET_FEE_PERCENTAGE);
|
|
19
|
+
return Math.max(fee, MINIMUM_MARKET_FEE_SATOSHIS) / listingPriceSatoshis;
|
|
20
|
+
}
|
|
11
21
|
|
|
12
22
|
export function registerPurchaseListingTool(
|
|
13
23
|
server: McpServer,
|
|
14
24
|
ctx: OneSatContext | undefined,
|
|
15
25
|
) {
|
|
16
26
|
server.tool(
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
"wallet_purchaseListing",
|
|
28
|
+
"Purchases a listing from the Bitcoin SV ordinals marketplace. Supports both NFT purchases and BSV21 token purchases.",
|
|
19
29
|
{ ...purchaseListingArgsSchema.shape },
|
|
20
|
-
async ({
|
|
30
|
+
async ({
|
|
31
|
+
listingOutpoint,
|
|
32
|
+
listingType,
|
|
33
|
+
tokenID,
|
|
34
|
+
tokenAmount,
|
|
35
|
+
}): Promise<CallToolResult> => {
|
|
21
36
|
if (!ctx) {
|
|
22
37
|
return {
|
|
23
38
|
content: [
|
|
24
39
|
{
|
|
25
|
-
type:
|
|
26
|
-
text:
|
|
40
|
+
type: "text",
|
|
41
|
+
text: "Wallet not initialized. Please configure a wallet before purchasing.",
|
|
27
42
|
},
|
|
28
43
|
],
|
|
29
44
|
isError: true,
|
|
30
|
-
}
|
|
45
|
+
};
|
|
31
46
|
}
|
|
32
47
|
|
|
33
48
|
try {
|
|
34
|
-
|
|
49
|
+
assertBroadcastAllowed("wallet_purchaseListing");
|
|
50
|
+
let marketplaceRate = MARKET_FEE_PERCENTAGE;
|
|
35
51
|
|
|
36
|
-
if (listingType ===
|
|
52
|
+
if (listingType === "token") {
|
|
37
53
|
if (!tokenID) {
|
|
38
|
-
throw new Error(
|
|
54
|
+
throw new Error("tokenID is required for token listings");
|
|
39
55
|
}
|
|
40
56
|
|
|
41
57
|
if (!tokenAmount) {
|
|
42
58
|
const response = await fetch(
|
|
43
59
|
`https://ordinals.gorillapool.io/api/txos/${listingOutpoint}?script=true`,
|
|
44
|
-
)
|
|
60
|
+
);
|
|
45
61
|
if (!response.ok) {
|
|
46
|
-
throw new Error(
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Failed to fetch listing data: ${response.statusText}`,
|
|
64
|
+
);
|
|
47
65
|
}
|
|
48
66
|
const listingData = (await response.json()) as {
|
|
49
|
-
data?: { list?: { price: number }; bsv20?: { amt?: string } }
|
|
50
|
-
}
|
|
67
|
+
data?: { list?: { price: number }; bsv20?: { amt?: string } };
|
|
68
|
+
};
|
|
51
69
|
if (!listingData.data?.bsv20?.amt) {
|
|
52
|
-
throw new Error(
|
|
70
|
+
throw new Error("Token listing does not have an amount");
|
|
53
71
|
}
|
|
54
|
-
tokenAmount = listingData.data.bsv20.amt
|
|
72
|
+
tokenAmount = listingData.data.bsv20.amt;
|
|
55
73
|
|
|
56
74
|
if (listingData.data?.list?.price) {
|
|
57
|
-
|
|
58
|
-
|
|
75
|
+
marketplaceRate = computeMarketplaceRate(
|
|
76
|
+
listingData.data.list.price,
|
|
77
|
+
);
|
|
59
78
|
}
|
|
60
79
|
}
|
|
61
80
|
|
|
62
|
-
const result = await
|
|
81
|
+
const result = await buyBsv21.execute(ctx, {
|
|
63
82
|
tokenId: tokenID,
|
|
64
83
|
outpoint: listingOutpoint,
|
|
65
84
|
amount: tokenAmount,
|
|
66
85
|
marketplaceAddress: MARKET_WALLET_ADDRESS,
|
|
67
86
|
marketplaceRate,
|
|
68
|
-
})
|
|
87
|
+
});
|
|
69
88
|
|
|
70
89
|
if (result.error) {
|
|
71
90
|
return {
|
|
72
|
-
content: [{ type:
|
|
91
|
+
content: [{ type: "text", text: result.error }],
|
|
73
92
|
isError: true,
|
|
74
|
-
}
|
|
93
|
+
};
|
|
75
94
|
}
|
|
76
95
|
|
|
77
96
|
return {
|
|
78
97
|
content: [
|
|
79
98
|
{
|
|
80
|
-
type:
|
|
99
|
+
type: "text",
|
|
81
100
|
text: JSON.stringify({
|
|
82
|
-
status:
|
|
101
|
+
status: "success",
|
|
83
102
|
txid: result.txid,
|
|
84
103
|
listingOutpoint,
|
|
85
104
|
listingType,
|
|
@@ -87,39 +106,39 @@ export function registerPurchaseListingTool(
|
|
|
87
106
|
}),
|
|
88
107
|
},
|
|
89
108
|
],
|
|
90
|
-
}
|
|
109
|
+
};
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
const result = await
|
|
112
|
+
const result = await buyOrdinal.execute(ctx, {
|
|
94
113
|
outpoint: listingOutpoint,
|
|
95
114
|
marketplaceAddress: MARKET_WALLET_ADDRESS,
|
|
96
115
|
marketplaceRate,
|
|
97
|
-
})
|
|
116
|
+
});
|
|
98
117
|
|
|
99
118
|
if (result.error) {
|
|
100
119
|
return {
|
|
101
|
-
content: [{ type:
|
|
120
|
+
content: [{ type: "text", text: result.error }],
|
|
102
121
|
isError: true,
|
|
103
|
-
}
|
|
122
|
+
};
|
|
104
123
|
}
|
|
105
124
|
|
|
106
125
|
return {
|
|
107
126
|
content: [
|
|
108
127
|
{
|
|
109
|
-
type:
|
|
128
|
+
type: "text",
|
|
110
129
|
text: JSON.stringify({
|
|
111
|
-
status:
|
|
130
|
+
status: "success",
|
|
112
131
|
txid: result.txid,
|
|
113
132
|
listingOutpoint,
|
|
114
133
|
listingType,
|
|
115
134
|
}),
|
|
116
135
|
},
|
|
117
136
|
],
|
|
118
|
-
}
|
|
137
|
+
};
|
|
119
138
|
} catch (err: unknown) {
|
|
120
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
121
|
-
return { content: [{ type:
|
|
139
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
140
|
+
return { content: [{ type: "text", text: msg }], isError: true };
|
|
122
141
|
}
|
|
123
142
|
},
|
|
124
|
-
)
|
|
143
|
+
);
|
|
125
144
|
}
|
package/tools/wallet/schemas.ts
CHANGED
|
@@ -48,14 +48,6 @@ export const walletEncryptionArgsSchema = z
|
|
|
48
48
|
// Get address args
|
|
49
49
|
export const getAddressArgsSchema = z.object({});
|
|
50
50
|
|
|
51
|
-
// Send to address args
|
|
52
|
-
export const sendToAddressArgsSchema = z.object({
|
|
53
|
-
address: z.string(),
|
|
54
|
-
amount: z.number(),
|
|
55
|
-
currency: z.enum(["BSV", "USD"]).optional().default("BSV"),
|
|
56
|
-
description: z.string().optional(),
|
|
57
|
-
});
|
|
58
|
-
|
|
59
51
|
/**
|
|
60
52
|
* Schema for purchase listing arguments
|
|
61
53
|
*/
|
|
@@ -82,16 +74,11 @@ export const purchaseListingArgsSchema = z
|
|
|
82
74
|
.describe(
|
|
83
75
|
"Amount of tokens in the listing (as string) — required when listingType is 'token'",
|
|
84
76
|
),
|
|
85
|
-
description: z
|
|
86
|
-
.string()
|
|
87
|
-
.optional()
|
|
88
|
-
.describe("Optional description for the transaction"),
|
|
89
77
|
})
|
|
90
78
|
.describe(
|
|
91
79
|
"Schema for the wallet_purchaseListing tool arguments (purchase NFTs or BSV21 tokens).",
|
|
92
80
|
);
|
|
93
81
|
|
|
94
82
|
// Export types
|
|
95
|
-
export type SendToAddressArgs = z.infer<typeof sendToAddressArgsSchema>;
|
|
96
83
|
export type PurchaseListingArgs = z.infer<typeof purchaseListingArgsSchema>;
|
|
97
84
|
export type WalletEncryptionArgs = z.infer<typeof walletEncryptionArgsSchema>;
|
|
@@ -1,64 +1,70 @@
|
|
|
1
|
-
import type { OneSatContext } from
|
|
2
|
-
import { sendAllBsv } from
|
|
3
|
-
import type { McpServer } from
|
|
4
|
-
import { z } from
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { sendAllBsv } from "@1sat/actions";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
5
6
|
|
|
6
7
|
const sendAllBsvArgsSchema = z.object({
|
|
7
8
|
destination: z
|
|
8
9
|
.string()
|
|
9
|
-
.describe(
|
|
10
|
-
})
|
|
10
|
+
.describe("Destination P2PKH address to send all funds to"),
|
|
11
|
+
});
|
|
11
12
|
|
|
12
13
|
export function registerSendAllBsvTool(
|
|
13
14
|
server: McpServer,
|
|
14
15
|
ctx: OneSatContext | undefined,
|
|
15
16
|
) {
|
|
16
17
|
server.tool(
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
"wallet_sendAllBsv",
|
|
19
|
+
"Send all BSV in the wallet to a single recipient",
|
|
19
20
|
{ ...sendAllBsvArgsSchema.shape },
|
|
20
21
|
async ({ destination }) => {
|
|
21
22
|
if (!ctx) {
|
|
22
23
|
return {
|
|
23
24
|
content: [
|
|
24
25
|
{
|
|
25
|
-
type:
|
|
26
|
-
text:
|
|
26
|
+
type: "text",
|
|
27
|
+
text: "Wallet not initialized. Please configure a wallet before sending.",
|
|
27
28
|
},
|
|
28
29
|
],
|
|
29
30
|
isError: true,
|
|
30
|
-
}
|
|
31
|
+
};
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
+
assertBroadcastAllowed("wallet_sendAllBsv");
|
|
36
|
+
const result = await sendAllBsv.execute(ctx, { destination });
|
|
35
37
|
|
|
36
38
|
if (result.error) {
|
|
37
39
|
return {
|
|
38
|
-
content: [{ type:
|
|
40
|
+
content: [{ type: "text", text: result.error }],
|
|
39
41
|
isError: true,
|
|
40
|
-
}
|
|
42
|
+
};
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
return {
|
|
44
46
|
content: [
|
|
45
47
|
{
|
|
46
|
-
type:
|
|
47
|
-
text: JSON.stringify(
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify(
|
|
50
|
+
result,
|
|
51
|
+
(_, v) => (typeof v === "bigint" ? v.toString() : v),
|
|
52
|
+
2,
|
|
53
|
+
),
|
|
48
54
|
},
|
|
49
55
|
],
|
|
50
|
-
}
|
|
56
|
+
};
|
|
51
57
|
} catch (err: unknown) {
|
|
52
58
|
return {
|
|
53
59
|
content: [
|
|
54
60
|
{
|
|
55
|
-
type:
|
|
61
|
+
type: "text",
|
|
56
62
|
text: err instanceof Error ? err.message : String(err),
|
|
57
63
|
},
|
|
58
64
|
],
|
|
59
65
|
isError: true,
|
|
60
|
-
}
|
|
66
|
+
};
|
|
61
67
|
}
|
|
62
68
|
},
|
|
63
|
-
)
|
|
69
|
+
);
|
|
64
70
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { OneSatContext } from "@1sat/actions";
|
|
2
|
+
import { sendBsv } from "@1sat/actions";
|
|
3
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { toSatoshi } from "satoshi-token";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { assertBroadcastAllowed } from "../../utils/broadcastGuard";
|
|
7
|
+
import { getBsvPriceWithCache } from "../bsv/getPrice";
|
|
8
|
+
|
|
9
|
+
const recipientSchema = z.object({
|
|
10
|
+
address: z.string().optional().describe("Destination P2PKH address"),
|
|
11
|
+
paymail: z.string().optional().describe("Destination paymail address"),
|
|
12
|
+
amount: z
|
|
13
|
+
.number()
|
|
14
|
+
.positive()
|
|
15
|
+
.finite()
|
|
16
|
+
.describe("Amount to send; must be greater than zero"),
|
|
17
|
+
currency: z
|
|
18
|
+
.enum(["BSV", "USD"])
|
|
19
|
+
.optional()
|
|
20
|
+
.default("BSV")
|
|
21
|
+
.describe("Currency of amount (default: BSV)"),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const sendBsvArgsSchema = z.object({
|
|
25
|
+
recipients: z.array(recipientSchema).describe("Array of payment recipients"),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export function registerSendBsvTool(
|
|
29
|
+
server: McpServer,
|
|
30
|
+
ctx: OneSatContext | undefined,
|
|
31
|
+
) {
|
|
32
|
+
server.tool(
|
|
33
|
+
"wallet_sendBsv",
|
|
34
|
+
"Send BSV to one or more recipients by address or paymail. Supports BSV and USD amounts with automatic conversion.",
|
|
35
|
+
{ ...sendBsvArgsSchema.shape },
|
|
36
|
+
async ({ recipients }) => {
|
|
37
|
+
if (!ctx) {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: "Wallet not initialized." }],
|
|
40
|
+
isError: true,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
assertBroadcastAllowed("wallet_sendBsv");
|
|
46
|
+
let bsvPrice: number | undefined;
|
|
47
|
+
|
|
48
|
+
const requests = await Promise.all(
|
|
49
|
+
recipients.map(async (r) => {
|
|
50
|
+
if (!r.address && !r.paymail) {
|
|
51
|
+
throw new Error("Each recipient requires an address or paymail");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let satoshis: number;
|
|
55
|
+
if (r.currency === "USD") {
|
|
56
|
+
if (!bsvPrice) bsvPrice = await getBsvPriceWithCache();
|
|
57
|
+
satoshis = toSatoshi(r.amount / bsvPrice);
|
|
58
|
+
} else {
|
|
59
|
+
satoshis = toSatoshi(r.amount);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
...(r.address && { address: r.address }),
|
|
64
|
+
...(r.paymail && { paymail: r.paymail }),
|
|
65
|
+
satoshis,
|
|
66
|
+
};
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = await sendBsv.execute(ctx, { requests });
|
|
71
|
+
|
|
72
|
+
if (result.error) {
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: result.error }],
|
|
75
|
+
isError: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "text",
|
|
83
|
+
text: JSON.stringify({
|
|
84
|
+
status: "success",
|
|
85
|
+
txid: result.txid,
|
|
86
|
+
recipients: requests.map((r) => ({
|
|
87
|
+
destination: r.address || r.paymail,
|
|
88
|
+
satoshis: r.satoshis,
|
|
89
|
+
})),
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: "text",
|
|
99
|
+
text: error instanceof Error ? error.message : String(error),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
);
|
|
107
|
+
}
|