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.
Files changed (83) hide show
  1. package/CHANGELOG.md +90 -10
  2. package/README.md +13 -13
  3. package/dist/app.html +483 -106
  4. package/dist/index.js +265787 -155292
  5. package/index.ts +505 -70
  6. package/package.json +24 -15
  7. package/prompts/bsvSdk/auth.ts +1 -1
  8. package/prompts/bsvSdk/cryptography.ts +1 -1
  9. package/prompts/bsvSdk/overview.ts +1 -1
  10. package/prompts/bsvSdk/primitives.ts +1 -1
  11. package/prompts/bsvSdk/script.ts +1 -1
  12. package/prompts/bsvSdk/transaction.ts +1 -1
  13. package/prompts/bsvSdk/wallet.ts +1 -1
  14. package/prompts/ordinals.ts +1 -1
  15. package/test-mcp-server.ts +26 -4
  16. package/tools/a2b/discover.ts +2 -2
  17. package/tools/bap/friend.ts +17 -24
  18. package/tools/bap/generate.test.ts +34 -32
  19. package/tools/bap/generate.ts +48 -43
  20. package/tools/bap/getCurrentAddress.ts +1 -3
  21. package/tools/bap/getId.test.ts +18 -10
  22. package/tools/bap/getId.ts +8 -6
  23. package/tools/bap/utils.ts +1 -1
  24. package/tools/bsocial/bmapReadPosts.ts +16 -2
  25. package/tools/bsocial/createPost.ts +21 -10
  26. package/tools/bsocial/readPosts.ts +10 -4
  27. package/tools/bsv/decodeTransaction.ts +1 -1
  28. package/tools/bsv/explore.ts +0 -4
  29. package/tools/bsv/getPrice.ts +1 -1
  30. package/tools/bsv/token.ts +14 -6
  31. package/tools/constants.ts +6 -2
  32. package/tools/index.ts +10 -10
  33. package/tools/mnee/getBalance.ts +1 -1
  34. package/tools/mnee/parseTx.ts +1 -1
  35. package/tools/mnee/sendMnee.ts +30 -8
  36. package/tools/ordinals/getInscription.ts +1 -3
  37. package/tools/ordinals/getTokenByIdOrTicker.ts +1 -3
  38. package/tools/ordinals/index.ts +4 -1
  39. package/tools/ordinals/marketListings.ts +1 -1
  40. package/tools/ordinals/marketSales.ts +1 -1
  41. package/tools/ordinals/searchInscriptions.ts +1 -1
  42. package/tools/utils/aip.ts +31 -22
  43. package/tools/utils/installAgentMaster.ts +8 -9
  44. package/tools/utils/toolRegistration.ts +21 -22
  45. package/tools/utils/transactionBuilder.ts +21 -14
  46. package/tools/wallet/a2bPublishMcp.ts +15 -5
  47. package/tools/wallet/brc100.ts +541 -310
  48. package/tools/wallet/cancelListing.ts +29 -37
  49. package/tools/wallet/createOrdinals.ts +43 -27
  50. package/tools/wallet/fetchPaymentUtxos.ts +7 -5
  51. package/tools/wallet/gatherCollectionInfo.ts +3 -5
  52. package/tools/wallet/getAddress.ts +1 -4
  53. package/tools/wallet/getBalance.ts +15 -15
  54. package/tools/wallet/{getBalanceDroplet.ts → getBalanceDroplit.ts} +6 -6
  55. package/tools/wallet/getBsv21Balances.ts +20 -14
  56. package/tools/wallet/getLockData.ts +11 -11
  57. package/tools/wallet/getOrdinals.ts +26 -20
  58. package/tools/wallet/integratedWallet.ts +55 -53
  59. package/tools/wallet/listOrdinal.ts +40 -39
  60. package/tools/wallet/listTokens.ts +18 -17
  61. package/tools/wallet/lockBsv.ts +36 -22
  62. package/tools/wallet/mintCollection.ts +15 -10
  63. package/tools/wallet/opnsDeregister.ts +29 -39
  64. package/tools/wallet/opnsRegister.ts +29 -39
  65. package/tools/wallet/purchaseListing.test.ts +27 -0
  66. package/tools/wallet/purchaseListing.ts +59 -40
  67. package/tools/wallet/refreshUtxos.ts +1 -1
  68. package/tools/wallet/schemas.ts +0 -13
  69. package/tools/wallet/sendAllBsv.ts +26 -20
  70. package/tools/wallet/sendBsv.ts +107 -0
  71. package/tools/wallet/setupDroplit.ts +157 -0
  72. package/tools/wallet/signBsm.ts +18 -18
  73. package/tools/wallet/sweepBsv.ts +48 -27
  74. package/tools/wallet/sweepBsv21.ts +42 -28
  75. package/tools/wallet/sweepOrdinals.ts +38 -26
  76. package/tools/wallet/tools.ts +11 -13
  77. package/tools/wallet/transactionHelper.ts +13 -5
  78. package/tools/wallet/transferOrdToken.ts +72 -84
  79. package/tools/wallet/unlockBsv.ts +23 -17
  80. package/tools/wallet/utxo.ts +1 -0
  81. package/tools/wallet/wallet.ts +1 -13
  82. package/tools/wallet/sendToAddress.ts +0 -74
  83. package/tools/wallet/setupDroplet.ts +0 -161
@@ -1,80 +1,70 @@
1
- import type { OneSatContext } from '@1sat/actions'
2
- import { opnsRegister } 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
- })
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
- ordinal: walletOutputSchema.describe(
16
- 'WalletOutput of the OpNS ordinal to register (from listOutputs)',
17
- ),
18
- inputBEEF: z
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
- 'wallet_opnsRegister',
31
- 'Register an OpNS name',
18
+ "wallet_opnsRegister",
19
+ "Register an OpNS name",
32
20
  { ...opnsRegisterArgsSchema.shape },
33
- async ({ ordinal, inputBEEF }) => {
21
+ async ({ id }) => {
34
22
  if (!ctx) {
35
23
  return {
36
24
  content: [
37
25
  {
38
- type: 'text',
39
- text: 'Wallet not initialized. Please configure a wallet before registering.',
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
- const result = await opnsRegister.execute(ctx, {
48
- ordinal: ordinal as Parameters<typeof opnsRegister.execute>[1]['ordinal'],
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: 'text', text: result.error }],
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: 'text',
63
- text: JSON.stringify(result, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2),
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: 'text',
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 '@1sat/actions'
2
- import { purchaseBsv21, purchaseOrdinal } from '@1sat/actions'
3
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4
- import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
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 '../constants'
10
- import { purchaseListingArgsSchema } from './schemas'
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
- 'wallet_purchaseListing',
18
- 'Purchases a listing from the Bitcoin SV ordinals marketplace. Supports both NFT purchases and BSV21 token purchases.',
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 ({ listingOutpoint, listingType, tokenID, tokenAmount, description }): Promise<CallToolResult> => {
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: 'text',
26
- text: 'Wallet not initialized. Please configure a wallet before purchasing.',
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
- let marketplaceRate = MARKET_FEE_PERCENTAGE
49
+ assertBroadcastAllowed("wallet_purchaseListing");
50
+ let marketplaceRate = MARKET_FEE_PERCENTAGE;
35
51
 
36
- if (listingType === 'token') {
52
+ if (listingType === "token") {
37
53
  if (!tokenID) {
38
- throw new Error('tokenID is required for token listings')
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(`Failed to fetch listing data: ${response.statusText}`)
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('Token listing does not have an amount')
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
- const fee = Math.round(listingData.data.list.price * MARKET_FEE_PERCENTAGE)
58
- marketplaceRate = Math.max(fee, MINIMUM_MARKET_FEE_SATOSHIS) / listingData.data.list.price
75
+ marketplaceRate = computeMarketplaceRate(
76
+ listingData.data.list.price,
77
+ );
59
78
  }
60
79
  }
61
80
 
62
- const result = await purchaseBsv21.execute(ctx, {
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: 'text', text: result.error }],
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: 'text',
99
+ type: "text",
81
100
  text: JSON.stringify({
82
- status: 'success',
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 purchaseOrdinal.execute(ctx, {
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: 'text', text: result.error }],
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: 'text',
128
+ type: "text",
110
129
  text: JSON.stringify({
111
- status: 'success',
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: 'text', text: msg }], isError: true }
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
  }
@@ -1,4 +1,4 @@
1
- import { syncAddresses, type OneSatContext } from "@1sat/actions";
1
+ import { type OneSatContext, syncAddresses } from "@1sat/actions";
2
2
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
 
4
4
  const MCP_ADDRESS_PREFIX = "mcp";
@@ -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 '@1sat/actions'
2
- import { sendAllBsv } from '@1sat/actions'
3
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
4
- import { z } from 'zod'
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('Destination P2PKH address to send all funds to'),
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
- 'wallet_sendAllBsv',
18
- 'Send all BSV in the wallet to a single recipient',
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: 'text',
26
- text: 'Wallet not initialized. Please configure a wallet before sending.',
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
- const result = await sendAllBsv.execute(ctx, { destination })
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: 'text', text: result.error }],
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: 'text',
47
- text: JSON.stringify(result, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2),
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: 'text',
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
+ }