@aibtc/mcp-server 1.39.0 → 1.41.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/dist/services/credentials.d.ts +31 -0
- package/dist/services/credentials.d.ts.map +1 -0
- package/dist/services/credentials.js +162 -0
- package/dist/services/credentials.js.map +1 -0
- package/dist/services/erc8004.service.d.ts +28 -0
- package/dist/services/erc8004.service.d.ts.map +1 -1
- package/dist/services/erc8004.service.js +115 -0
- package/dist/services/erc8004.service.js.map +1 -1
- package/dist/services/unisat-indexer.d.ts +97 -0
- package/dist/services/unisat-indexer.d.ts.map +1 -0
- package/dist/services/unisat-indexer.js +178 -0
- package/dist/services/unisat-indexer.js.map +1 -0
- package/dist/tools/bounty-scanner.tools.d.ts +29 -0
- package/dist/tools/bounty-scanner.tools.d.ts.map +1 -0
- package/dist/tools/bounty-scanner.tools.js +405 -0
- package/dist/tools/bounty-scanner.tools.js.map +1 -0
- package/dist/tools/credentials.tools.d.ts +9 -0
- package/dist/tools/credentials.tools.d.ts.map +1 -0
- package/dist/tools/credentials.tools.js +151 -0
- package/dist/tools/credentials.tools.js.map +1 -0
- package/dist/tools/identity.tools.d.ts +22 -0
- package/dist/tools/identity.tools.d.ts.map +1 -0
- package/dist/tools/identity.tools.js +404 -0
- package/dist/tools/identity.tools.js.map +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +21 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/news.tools.d.ts +26 -0
- package/dist/tools/news.tools.d.ts.map +1 -0
- package/dist/tools/news.tools.js +348 -0
- package/dist/tools/news.tools.js.map +1 -0
- package/dist/tools/runes.tools.d.ts +25 -0
- package/dist/tools/runes.tools.d.ts.map +1 -0
- package/dist/tools/runes.tools.js +464 -0
- package/dist/tools/runes.tools.js.map +1 -0
- package/dist/tools/signing.tools.d.ts.map +1 -1
- package/dist/tools/signing.tools.js +3 -88
- package/dist/tools/signing.tools.js.map +1 -1
- package/dist/tools/skill-mappings.d.ts.map +1 -1
- package/dist/tools/skill-mappings.js +40 -0
- package/dist/tools/skill-mappings.js.map +1 -1
- package/dist/tools/souldinals.tools.d.ts +18 -0
- package/dist/tools/souldinals.tools.d.ts.map +1 -0
- package/dist/tools/souldinals.tools.js +551 -0
- package/dist/tools/souldinals.tools.js.map +1 -0
- package/dist/tools/stacking-lottery.tools.d.ts.map +1 -1
- package/dist/tools/stacking-lottery.tools.js +7 -4
- package/dist/tools/stacking-lottery.tools.js.map +1 -1
- package/dist/transactions/rune-transfer-builder.d.ts +46 -0
- package/dist/transactions/rune-transfer-builder.d.ts.map +1 -0
- package/dist/transactions/rune-transfer-builder.js +130 -0
- package/dist/transactions/rune-transfer-builder.js.map +1 -0
- package/dist/transactions/runestone-builder.d.ts +40 -0
- package/dist/transactions/runestone-builder.d.ts.map +1 -0
- package/dist/transactions/runestone-builder.js +76 -0
- package/dist/transactions/runestone-builder.js.map +1 -0
- package/dist/utils/bip322.d.ts +48 -0
- package/dist/utils/bip322.d.ts.map +1 -0
- package/dist/utils/bip322.js +110 -0
- package/dist/utils/bip322.js.map +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runes tools
|
|
3
|
+
*
|
|
4
|
+
* MCP tools for the Bitcoin Runes protocol — a Bitcoin-native fungible token
|
|
5
|
+
* standard introduced by Casey Rodarmor.
|
|
6
|
+
*
|
|
7
|
+
* Read-only tools (Hiro Runes API):
|
|
8
|
+
* - runes_list_etchings: List all rune etchings with pagination
|
|
9
|
+
* - runes_get_etching: Get details for a specific rune by name or numeric ID
|
|
10
|
+
* - runes_get_holders: Get holder list for a rune
|
|
11
|
+
* - runes_get_activity: Get recent mint/transfer/burn activity for a rune
|
|
12
|
+
* - runes_get_address_balances: Get all rune balances for a Bitcoin address
|
|
13
|
+
* - runes_get_address_activity: Get rune activity for a Bitcoin address
|
|
14
|
+
*
|
|
15
|
+
* Wallet tools (Unisat API — requires UNISAT_API_KEY):
|
|
16
|
+
* - get_rune_balances: Fetch rune token balances at a Bitcoin address
|
|
17
|
+
* - get_rune_utxos: List UTXOs containing a specific rune (block:tx format)
|
|
18
|
+
* - transfer_rune: Transfer runes via Runestone OP_RETURN encoding
|
|
19
|
+
*
|
|
20
|
+
* Set HIRO_API_KEY to increase Hiro rate limits.
|
|
21
|
+
* Set UNISAT_API_KEY for Unisat indexer access (5 req/s free tier).
|
|
22
|
+
*/
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { NETWORK, getApiBaseUrl } from "../config/networks.js";
|
|
25
|
+
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
26
|
+
import { getHiroApiKey } from "../utils/storage.js";
|
|
27
|
+
import { getWalletManager } from "../services/wallet-manager.js";
|
|
28
|
+
import { MempoolApi, getMempoolAddressUrl, getMempoolTxUrl } from "../services/mempool-api.js";
|
|
29
|
+
import { UnisatIndexer } from "../services/unisat-indexer.js";
|
|
30
|
+
import { buildRuneTransfer, signRuneTransfer } from "../transactions/rune-transfer-builder.js";
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Helpers
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
function formatRuneAmount(amount, divisibility, symbol) {
|
|
35
|
+
if (divisibility === 0)
|
|
36
|
+
return `${amount} ${symbol}`;
|
|
37
|
+
const num = BigInt(amount);
|
|
38
|
+
const divisor = 10n ** BigInt(divisibility);
|
|
39
|
+
const whole = num / divisor;
|
|
40
|
+
const frac = num % divisor;
|
|
41
|
+
const fracStr = frac.toString().padStart(divisibility, "0").replace(/0+$/, "");
|
|
42
|
+
return fracStr ? `${whole}.${fracStr} ${symbol}` : `${whole} ${symbol}`;
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// API helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
async function runesApiFetch(path) {
|
|
48
|
+
const apiKey = (await getHiroApiKey()) || process.env.HIRO_API_KEY || "";
|
|
49
|
+
const baseUrl = getApiBaseUrl(NETWORK);
|
|
50
|
+
const url = `${baseUrl}${path}`;
|
|
51
|
+
const headers = {
|
|
52
|
+
"Content-Type": "application/json",
|
|
53
|
+
...(apiKey ? { "x-hiro-api-key": apiKey } : {}),
|
|
54
|
+
};
|
|
55
|
+
const response = await fetch(url, { headers });
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const errorText = await response.text();
|
|
58
|
+
throw new Error(`Hiro Runes API error (${response.status}): ${errorText}`);
|
|
59
|
+
}
|
|
60
|
+
return response.json();
|
|
61
|
+
}
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Tool registration
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
export function registerRunesTools(server) {
|
|
66
|
+
// --------------------------------------------------------------------------
|
|
67
|
+
// runes_list_etchings — List all rune etchings
|
|
68
|
+
// --------------------------------------------------------------------------
|
|
69
|
+
server.registerTool("runes_list_etchings", {
|
|
70
|
+
description: "List all Bitcoin Rune etchings (token deployments) with pagination.\n\n" +
|
|
71
|
+
"Returns rune names, IDs, supply, divisibility, symbol, etching transaction, " +
|
|
72
|
+
"and other metadata for each rune.\n\n" +
|
|
73
|
+
"Use runes_get_etching to get full details on a specific rune.",
|
|
74
|
+
inputSchema: {
|
|
75
|
+
limit: z
|
|
76
|
+
.number()
|
|
77
|
+
.int()
|
|
78
|
+
.min(1)
|
|
79
|
+
.max(60)
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Number of results to return (1-60, default: 20)"),
|
|
82
|
+
offset: z
|
|
83
|
+
.number()
|
|
84
|
+
.int()
|
|
85
|
+
.min(0)
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("Number of results to skip for pagination (default: 0)"),
|
|
88
|
+
},
|
|
89
|
+
}, async ({ limit = 20, offset = 0 }) => {
|
|
90
|
+
try {
|
|
91
|
+
const data = await runesApiFetch(`/runes/v1/etchings?limit=${limit}&offset=${offset}`);
|
|
92
|
+
return createJsonResponse(data);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return createErrorResponse(error);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
// --------------------------------------------------------------------------
|
|
99
|
+
// runes_get_etching — Get a specific rune by name or ID
|
|
100
|
+
// --------------------------------------------------------------------------
|
|
101
|
+
server.registerTool("runes_get_etching", {
|
|
102
|
+
description: "Get details for a specific Bitcoin Rune by its name or numeric ID.\n\n" +
|
|
103
|
+
"Returns name, ID, supply info, divisibility, symbol, etching transaction, " +
|
|
104
|
+
"cenotaph status, terms (mint conditions), and turbo flag.\n\n" +
|
|
105
|
+
"Rune names use spacers (e.g., 'UNCOMMONGOODS' or 'UNCOMMON•GOODS').",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
rune: z
|
|
108
|
+
.string()
|
|
109
|
+
.describe("Rune name (e.g., 'UNCOMMONGOODS' or 'UNCOMMON•GOODS') or numeric rune ID"),
|
|
110
|
+
},
|
|
111
|
+
}, async ({ rune }) => {
|
|
112
|
+
try {
|
|
113
|
+
const encoded = encodeURIComponent(rune);
|
|
114
|
+
const data = await runesApiFetch(`/runes/v1/etchings/${encoded}`);
|
|
115
|
+
return createJsonResponse(data);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return createErrorResponse(error);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
// --------------------------------------------------------------------------
|
|
122
|
+
// runes_get_holders — Get holders of a rune
|
|
123
|
+
// --------------------------------------------------------------------------
|
|
124
|
+
server.registerTool("runes_get_holders", {
|
|
125
|
+
description: "Get the list of holders for a specific Bitcoin Rune.\n\n" +
|
|
126
|
+
"Returns Bitcoin addresses and their rune balances, sorted by balance descending.\n\n" +
|
|
127
|
+
"Supports pagination for runes with many holders.",
|
|
128
|
+
inputSchema: {
|
|
129
|
+
rune: z
|
|
130
|
+
.string()
|
|
131
|
+
.describe("Rune name (e.g., 'UNCOMMONGOODS' or 'UNCOMMON•GOODS') or numeric rune ID"),
|
|
132
|
+
limit: z
|
|
133
|
+
.number()
|
|
134
|
+
.int()
|
|
135
|
+
.min(1)
|
|
136
|
+
.max(60)
|
|
137
|
+
.optional()
|
|
138
|
+
.describe("Number of results to return (1-60, default: 20)"),
|
|
139
|
+
offset: z
|
|
140
|
+
.number()
|
|
141
|
+
.int()
|
|
142
|
+
.min(0)
|
|
143
|
+
.optional()
|
|
144
|
+
.describe("Number of results to skip for pagination (default: 0)"),
|
|
145
|
+
},
|
|
146
|
+
}, async ({ rune, limit = 20, offset = 0 }) => {
|
|
147
|
+
try {
|
|
148
|
+
const encoded = encodeURIComponent(rune);
|
|
149
|
+
const data = await runesApiFetch(`/runes/v1/etchings/${encoded}/holders?limit=${limit}&offset=${offset}`);
|
|
150
|
+
return createJsonResponse(data);
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
return createErrorResponse(error);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// --------------------------------------------------------------------------
|
|
157
|
+
// runes_get_activity — Get recent activity for a rune
|
|
158
|
+
// --------------------------------------------------------------------------
|
|
159
|
+
server.registerTool("runes_get_activity", {
|
|
160
|
+
description: "Get recent on-chain activity (mints, transfers, burns) for a specific Bitcoin Rune.\n\n" +
|
|
161
|
+
"Returns transaction events with amounts, addresses, block heights, and timestamps.\n\n" +
|
|
162
|
+
"Useful for monitoring rune distribution and trading activity.",
|
|
163
|
+
inputSchema: {
|
|
164
|
+
rune: z
|
|
165
|
+
.string()
|
|
166
|
+
.describe("Rune name (e.g., 'UNCOMMONGOODS' or 'UNCOMMON•GOODS') or numeric rune ID"),
|
|
167
|
+
limit: z
|
|
168
|
+
.number()
|
|
169
|
+
.int()
|
|
170
|
+
.min(1)
|
|
171
|
+
.max(60)
|
|
172
|
+
.optional()
|
|
173
|
+
.describe("Number of results to return (1-60, default: 20)"),
|
|
174
|
+
offset: z
|
|
175
|
+
.number()
|
|
176
|
+
.int()
|
|
177
|
+
.min(0)
|
|
178
|
+
.optional()
|
|
179
|
+
.describe("Number of results to skip for pagination (default: 0)"),
|
|
180
|
+
},
|
|
181
|
+
}, async ({ rune, limit = 20, offset = 0 }) => {
|
|
182
|
+
try {
|
|
183
|
+
const encoded = encodeURIComponent(rune);
|
|
184
|
+
const data = await runesApiFetch(`/runes/v1/etchings/${encoded}/activity?limit=${limit}&offset=${offset}`);
|
|
185
|
+
return createJsonResponse(data);
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return createErrorResponse(error);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// --------------------------------------------------------------------------
|
|
192
|
+
// runes_get_address_balances — Get rune balances for a Bitcoin address
|
|
193
|
+
// --------------------------------------------------------------------------
|
|
194
|
+
server.registerTool("runes_get_address_balances", {
|
|
195
|
+
description: "Get all Bitcoin Rune balances for a Bitcoin address.\n\n" +
|
|
196
|
+
"Returns each rune the address holds along with its balance, divisibility, " +
|
|
197
|
+
"and symbol. Useful for checking which runes a wallet owns.\n\n" +
|
|
198
|
+
"Address can be any Bitcoin address format (P2WPKH bc1q..., P2TR bc1p..., legacy 1..., etc.)",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
address: z
|
|
201
|
+
.string()
|
|
202
|
+
.describe("Bitcoin address to check rune balances for"),
|
|
203
|
+
},
|
|
204
|
+
}, async ({ address }) => {
|
|
205
|
+
try {
|
|
206
|
+
const encoded = encodeURIComponent(address);
|
|
207
|
+
const data = await runesApiFetch(`/runes/v1/addresses/${encoded}/balances`);
|
|
208
|
+
return createJsonResponse(data);
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
return createErrorResponse(error);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
// --------------------------------------------------------------------------
|
|
215
|
+
// runes_get_address_activity — Get rune activity for a Bitcoin address
|
|
216
|
+
// --------------------------------------------------------------------------
|
|
217
|
+
server.registerTool("runes_get_address_activity", {
|
|
218
|
+
description: "Get Bitcoin Rune transaction activity for a specific Bitcoin address.\n\n" +
|
|
219
|
+
"Returns mints received, transfers sent/received, and burns associated with " +
|
|
220
|
+
"this address across all runes.\n\n" +
|
|
221
|
+
"Address can be any Bitcoin address format (P2WPKH bc1q..., P2TR bc1p..., legacy 1..., etc.)",
|
|
222
|
+
inputSchema: {
|
|
223
|
+
address: z
|
|
224
|
+
.string()
|
|
225
|
+
.describe("Bitcoin address to query rune activity for"),
|
|
226
|
+
limit: z
|
|
227
|
+
.number()
|
|
228
|
+
.int()
|
|
229
|
+
.min(1)
|
|
230
|
+
.max(60)
|
|
231
|
+
.optional()
|
|
232
|
+
.describe("Number of results to return (1-60, default: 20)"),
|
|
233
|
+
offset: z
|
|
234
|
+
.number()
|
|
235
|
+
.int()
|
|
236
|
+
.min(0)
|
|
237
|
+
.optional()
|
|
238
|
+
.describe("Number of results to skip for pagination (default: 0)"),
|
|
239
|
+
},
|
|
240
|
+
}, async ({ address, limit = 20, offset = 0 }) => {
|
|
241
|
+
try {
|
|
242
|
+
const encoded = encodeURIComponent(address);
|
|
243
|
+
const data = await runesApiFetch(`/runes/v1/addresses/${encoded}/activity?limit=${limit}&offset=${offset}`);
|
|
244
|
+
return createJsonResponse(data);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
return createErrorResponse(error);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
// --------------------------------------------------------------------------
|
|
251
|
+
// get_rune_balances — Unisat: on-chain rune balances at a Bitcoin address
|
|
252
|
+
// --------------------------------------------------------------------------
|
|
253
|
+
server.registerTool("get_rune_balances", {
|
|
254
|
+
description: "Fetch all rune token balances held at a Bitcoin address via the Unisat indexer.\n\n" +
|
|
255
|
+
"Returns rune IDs, amounts, symbols, and divisibility for all runes at the address.\n\n" +
|
|
256
|
+
"If no address is provided, uses the active wallet's Taproot address.\n" +
|
|
257
|
+
"Set UNISAT_API_KEY for higher rate limits (5 req/s on free tier).",
|
|
258
|
+
inputSchema: {
|
|
259
|
+
address: z
|
|
260
|
+
.string()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe("Bitcoin address to check (uses active wallet's Taproot address if omitted)"),
|
|
263
|
+
},
|
|
264
|
+
}, async ({ address }) => {
|
|
265
|
+
try {
|
|
266
|
+
let resolvedAddress = address;
|
|
267
|
+
if (!resolvedAddress) {
|
|
268
|
+
const walletManager = getWalletManager();
|
|
269
|
+
const sessionInfo = walletManager.getSessionInfo();
|
|
270
|
+
if (!sessionInfo?.taprootAddress) {
|
|
271
|
+
return createErrorResponse(new Error("No address provided and wallet is not unlocked. " +
|
|
272
|
+
"Either provide an address or unlock your wallet first."));
|
|
273
|
+
}
|
|
274
|
+
resolvedAddress = sessionInfo.taprootAddress;
|
|
275
|
+
}
|
|
276
|
+
const indexer = new UnisatIndexer(NETWORK);
|
|
277
|
+
const balances = await indexer.getRuneBalances(resolvedAddress);
|
|
278
|
+
const formattedBalances = balances.map((b) => ({
|
|
279
|
+
rune: b.rune,
|
|
280
|
+
runeId: b.runeid,
|
|
281
|
+
spacedRune: b.spacedRune,
|
|
282
|
+
amount: b.amount,
|
|
283
|
+
formatted: formatRuneAmount(b.amount, b.divisibility, b.symbol),
|
|
284
|
+
symbol: b.symbol,
|
|
285
|
+
divisibility: b.divisibility,
|
|
286
|
+
}));
|
|
287
|
+
return createJsonResponse({
|
|
288
|
+
address: resolvedAddress,
|
|
289
|
+
network: NETWORK,
|
|
290
|
+
balances: formattedBalances,
|
|
291
|
+
summary: { runeCount: balances.length },
|
|
292
|
+
explorerUrl: getMempoolAddressUrl(resolvedAddress, NETWORK),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
return createErrorResponse(error);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
// --------------------------------------------------------------------------
|
|
300
|
+
// get_rune_utxos — Unisat: list UTXOs containing a specific rune
|
|
301
|
+
// --------------------------------------------------------------------------
|
|
302
|
+
server.registerTool("get_rune_utxos", {
|
|
303
|
+
description: "List UTXOs containing a specific rune at a Bitcoin address via the Unisat indexer.\n\n" +
|
|
304
|
+
"Rune ID format: 'block:tx' (e.g., '840000:1' for UNCOMMONGOODS).\n\n" +
|
|
305
|
+
"If no address is provided, uses the active wallet's Taproot address.",
|
|
306
|
+
inputSchema: {
|
|
307
|
+
runeId: z
|
|
308
|
+
.string()
|
|
309
|
+
.describe("Rune ID in 'block:tx' format (e.g., '840000:1')"),
|
|
310
|
+
address: z
|
|
311
|
+
.string()
|
|
312
|
+
.optional()
|
|
313
|
+
.describe("Bitcoin address to check (uses active wallet's Taproot address if omitted)"),
|
|
314
|
+
},
|
|
315
|
+
}, async ({ runeId, address }) => {
|
|
316
|
+
try {
|
|
317
|
+
let resolvedAddress = address;
|
|
318
|
+
if (!resolvedAddress) {
|
|
319
|
+
const walletManager = getWalletManager();
|
|
320
|
+
const sessionInfo = walletManager.getSessionInfo();
|
|
321
|
+
if (!sessionInfo?.taprootAddress) {
|
|
322
|
+
return createErrorResponse(new Error("No address provided and wallet is not unlocked. " +
|
|
323
|
+
"Either provide an address or unlock your wallet first."));
|
|
324
|
+
}
|
|
325
|
+
resolvedAddress = sessionInfo.taprootAddress;
|
|
326
|
+
}
|
|
327
|
+
const indexer = new UnisatIndexer(NETWORK);
|
|
328
|
+
const utxos = await indexer.getRuneUtxos(resolvedAddress, runeId);
|
|
329
|
+
const formattedUtxos = utxos.map((u) => ({
|
|
330
|
+
txid: u.txid,
|
|
331
|
+
vout: u.vout,
|
|
332
|
+
satoshis: u.satoshi,
|
|
333
|
+
address: u.address,
|
|
334
|
+
height: u.height,
|
|
335
|
+
runes: u.runes.map((r) => ({
|
|
336
|
+
runeId: r.runeid,
|
|
337
|
+
spacedRune: r.spacedRune,
|
|
338
|
+
amount: r.amount,
|
|
339
|
+
formatted: formatRuneAmount(r.amount, r.divisibility, r.symbol),
|
|
340
|
+
symbol: r.symbol,
|
|
341
|
+
})),
|
|
342
|
+
}));
|
|
343
|
+
return createJsonResponse({
|
|
344
|
+
address: resolvedAddress,
|
|
345
|
+
network: NETWORK,
|
|
346
|
+
runeId,
|
|
347
|
+
utxos: formattedUtxos,
|
|
348
|
+
summary: {
|
|
349
|
+
utxoCount: utxos.length,
|
|
350
|
+
totalSatoshis: utxos.reduce((sum, u) => sum + u.satoshi, 0),
|
|
351
|
+
},
|
|
352
|
+
explorerUrl: getMempoolAddressUrl(resolvedAddress, NETWORK),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
return createErrorResponse(error);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
// --------------------------------------------------------------------------
|
|
360
|
+
// transfer_rune — build and broadcast a rune transfer via Runestone OP_RETURN
|
|
361
|
+
// --------------------------------------------------------------------------
|
|
362
|
+
server.registerTool("transfer_rune", {
|
|
363
|
+
description: "Transfer runes to a recipient address using Runestone OP_RETURN encoding.\n\n" +
|
|
364
|
+
"Builds a Bitcoin transaction with a Runestone, sends runes to the recipient, " +
|
|
365
|
+
"and returns remaining runes to the sender Taproot address.\n\n" +
|
|
366
|
+
"Requires wallet to be unlocked. Amount is in smallest rune units (raw integer).\n" +
|
|
367
|
+
"Uses Unisat indexer to fetch rune UTXOs (UNISAT_API_KEY recommended).",
|
|
368
|
+
inputSchema: {
|
|
369
|
+
runeId: z
|
|
370
|
+
.string()
|
|
371
|
+
.describe("Rune ID in 'block:tx' format (e.g., '840000:1')"),
|
|
372
|
+
amount: z
|
|
373
|
+
.string()
|
|
374
|
+
.describe("Amount of runes to transfer in smallest unit (integer string, e.g., '1000')"),
|
|
375
|
+
toAddress: z
|
|
376
|
+
.string()
|
|
377
|
+
.describe("Recipient Bitcoin address"),
|
|
378
|
+
feeRate: z
|
|
379
|
+
.union([z.enum(["fast", "medium", "slow"]), z.number().positive()])
|
|
380
|
+
.optional()
|
|
381
|
+
.describe("Fee rate: 'fast' (~10 min), 'medium' (~30 min), 'slow' (~1 hr), or sat/vB number (default: medium)"),
|
|
382
|
+
},
|
|
383
|
+
}, async ({ runeId, amount, toAddress, feeRate }) => {
|
|
384
|
+
try {
|
|
385
|
+
const walletManager = getWalletManager();
|
|
386
|
+
const account = walletManager.getAccount();
|
|
387
|
+
if (!account) {
|
|
388
|
+
return createErrorResponse(new Error("Wallet is not unlocked. Use wallet_unlock first."));
|
|
389
|
+
}
|
|
390
|
+
if (!account.btcAddress ||
|
|
391
|
+
!account.btcPrivateKey ||
|
|
392
|
+
!account.btcPublicKey ||
|
|
393
|
+
!account.taprootPrivateKey ||
|
|
394
|
+
!account.taprootPublicKey ||
|
|
395
|
+
!account.taprootAddress) {
|
|
396
|
+
return createErrorResponse(new Error("Bitcoin and Taproot keys not available. Please unlock your wallet again."));
|
|
397
|
+
}
|
|
398
|
+
const transferAmount = BigInt(amount);
|
|
399
|
+
if (transferAmount <= 0n) {
|
|
400
|
+
return createErrorResponse(new Error("amount must be a positive integer"));
|
|
401
|
+
}
|
|
402
|
+
const indexer = new UnisatIndexer(NETWORK);
|
|
403
|
+
const mempoolApi = new MempoolApi(NETWORK);
|
|
404
|
+
// Resolve fee rate
|
|
405
|
+
let actualFeeRate;
|
|
406
|
+
if (typeof feeRate === "string" || feeRate === undefined) {
|
|
407
|
+
const fees = await mempoolApi.getFeeEstimates();
|
|
408
|
+
if (!feeRate || feeRate === "medium")
|
|
409
|
+
actualFeeRate = fees.halfHourFee;
|
|
410
|
+
else if (feeRate === "fast")
|
|
411
|
+
actualFeeRate = fees.fastestFee;
|
|
412
|
+
else
|
|
413
|
+
actualFeeRate = fees.hourFee;
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
actualFeeRate = feeRate;
|
|
417
|
+
}
|
|
418
|
+
const runeUtxos = await indexer.getRuneUtxos(account.taprootAddress, runeId);
|
|
419
|
+
if (runeUtxos.length === 0) {
|
|
420
|
+
return createErrorResponse(new Error(`No UTXOs found for rune ${runeId} at address ${account.taprootAddress}`));
|
|
421
|
+
}
|
|
422
|
+
const runeUtxosFormatted = runeUtxos.map((u) => ({
|
|
423
|
+
txid: u.txid,
|
|
424
|
+
vout: u.vout,
|
|
425
|
+
value: u.satoshi,
|
|
426
|
+
status: { confirmed: true, block_height: u.height, block_hash: "", block_time: 0 },
|
|
427
|
+
}));
|
|
428
|
+
const cardinalUtxos = await indexer.getCardinalUtxos(account.btcAddress);
|
|
429
|
+
if (cardinalUtxos.length === 0) {
|
|
430
|
+
return createErrorResponse(new Error(`No cardinal UTXOs available at ${account.btcAddress} to pay fees. ` +
|
|
431
|
+
`Send some BTC to your SegWit address first.`));
|
|
432
|
+
}
|
|
433
|
+
const transferResult = buildRuneTransfer({
|
|
434
|
+
runeId,
|
|
435
|
+
amount: transferAmount,
|
|
436
|
+
runeUtxos: runeUtxosFormatted,
|
|
437
|
+
feeUtxos: cardinalUtxos,
|
|
438
|
+
recipientAddress: toAddress,
|
|
439
|
+
feeRate: actualFeeRate,
|
|
440
|
+
senderPubKey: account.btcPublicKey,
|
|
441
|
+
senderTaprootPubKey: account.taprootPublicKey,
|
|
442
|
+
senderAddress: account.btcAddress,
|
|
443
|
+
senderTaprootAddress: account.taprootAddress,
|
|
444
|
+
network: NETWORK,
|
|
445
|
+
});
|
|
446
|
+
const signed = signRuneTransfer(transferResult.tx, account.taprootPrivateKey, account.btcPrivateKey, transferResult.taprootInputIndices, transferResult.feeInputIndices);
|
|
447
|
+
const txid = await mempoolApi.broadcastTransaction(signed.txHex);
|
|
448
|
+
return createJsonResponse({
|
|
449
|
+
success: true,
|
|
450
|
+
txid,
|
|
451
|
+
explorerUrl: getMempoolTxUrl(txid, NETWORK),
|
|
452
|
+
rune: { runeId, amount },
|
|
453
|
+
recipient: toAddress,
|
|
454
|
+
fee: { satoshis: transferResult.fee, rateUsed: `${actualFeeRate} sat/vB` },
|
|
455
|
+
btcChange: { satoshis: transferResult.btcChange },
|
|
456
|
+
network: NETWORK,
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
catch (error) {
|
|
460
|
+
return createErrorResponse(error);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
//# sourceMappingURL=runes.tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runes.tools.js","sourceRoot":"","sources":["../../src/tools/runes.tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signing.tools.d.ts","sourceRoot":"","sources":["../../src/tools/signing.tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;
|
|
1
|
+
{"version":3,"file":"signing.tools.d.ts","sourceRoot":"","sources":["../../src/tools/signing.tools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAwnBpE,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAkkC5D"}
|
|
@@ -30,10 +30,11 @@ import { hashMessage, verifyMessageSignatureRsv, hashSha256Sync } from "@stacks/
|
|
|
30
30
|
import { bytesToHex } from "@stacks/common";
|
|
31
31
|
import { secp256k1, schnorr } from "@noble/curves/secp256k1.js";
|
|
32
32
|
import { hex, bech32 } from "@scure/base";
|
|
33
|
-
import { Transaction, p2wpkh, p2pkh, p2sh, p2tr, Script, SigHash, RawWitness,
|
|
33
|
+
import { Transaction, p2wpkh, p2pkh, p2sh, p2tr, Script, SigHash, RawWitness, Address, NETWORK as BTC_MAINNET, TEST_NETWORK as BTC_TESTNET, } from "@scure/btc-signer";
|
|
34
34
|
import { NETWORK } from "../config/networks.js";
|
|
35
35
|
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
36
36
|
import { getWalletManager } from "../services/wallet-manager.js";
|
|
37
|
+
import { bip322BuildToSpendTxId, bip322Sign, } from "../utils/bip322.js";
|
|
37
38
|
/**
|
|
38
39
|
* Chain IDs for SIP-018 domain (from SIP-005)
|
|
39
40
|
*/
|
|
@@ -187,93 +188,7 @@ function parseDERSignature(der) {
|
|
|
187
188
|
compact.set(sBytes.slice(-32), 32); // s (last 32 bytes)
|
|
188
189
|
return compact;
|
|
189
190
|
}
|
|
190
|
-
//
|
|
191
|
-
// BIP-322 helper functions
|
|
192
|
-
// ---------------------------------------------------------------------------
|
|
193
|
-
/**
|
|
194
|
-
* BIP-322 tagged hash: SHA256(SHA256(tag) || SHA256(tag) || msg)
|
|
195
|
-
* where tag = "BIP0322-signed-message"
|
|
196
|
-
*
|
|
197
|
-
* Per BIP-322 spec, message bytes are passed directly — no varint length prefix.
|
|
198
|
-
* Ref: https://github.com/aibtcdev/x402-sponsor-relay/issues/135
|
|
199
|
-
*/
|
|
200
|
-
function bip322TaggedHash(message) {
|
|
201
|
-
const tagBytes = new TextEncoder().encode("BIP0322-signed-message");
|
|
202
|
-
const tagHash = hashSha256Sync(tagBytes);
|
|
203
|
-
const msgBytes = new TextEncoder().encode(message);
|
|
204
|
-
return hashSha256Sync(concatBytes(tagHash, tagHash, msgBytes));
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Build the BIP-322 to_spend virtual transaction and return its txid (32 bytes, LE).
|
|
208
|
-
*
|
|
209
|
-
* The to_spend tx is a virtual legacy transaction:
|
|
210
|
-
* - Input: txid=zero32, vout=0xFFFFFFFF, sequence=0, scriptSig = OP_0 push32 <msgHash>
|
|
211
|
-
* - Output: amount=0, script=scriptPubKey of the signing address
|
|
212
|
-
*
|
|
213
|
-
* The txid is computed as doubleSha256 of the legacy (non-segwit) serialization.
|
|
214
|
-
* The returned txid is already in the byte order used by transaction inputs (reversed).
|
|
215
|
-
*/
|
|
216
|
-
function bip322BuildToSpendTxId(message, scriptPubKey) {
|
|
217
|
-
const msgHash = bip322TaggedHash(message);
|
|
218
|
-
// scriptSig: OP_0 (0x00) push32 (0x20) <32-byte hash>
|
|
219
|
-
const scriptSig = concatBytes(new Uint8Array([0x00, 0x20]), msgHash);
|
|
220
|
-
const rawTx = RawTx.encode({
|
|
221
|
-
version: 0,
|
|
222
|
-
inputs: [
|
|
223
|
-
{
|
|
224
|
-
txid: new Uint8Array(32),
|
|
225
|
-
index: 0xffffffff,
|
|
226
|
-
finalScriptSig: scriptSig,
|
|
227
|
-
sequence: 0,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
outputs: [
|
|
231
|
-
{
|
|
232
|
-
amount: 0n,
|
|
233
|
-
script: scriptPubKey,
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
lockTime: 0,
|
|
237
|
-
});
|
|
238
|
-
// txid is double-SHA256 of the serialized tx, returned in little-endian byte order
|
|
239
|
-
return doubleSha256(rawTx).reverse();
|
|
240
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* BIP-322 "simple" signing.
|
|
243
|
-
*
|
|
244
|
-
* Builds and signs the to_sign virtual transaction. The private key is used directly —
|
|
245
|
-
* @scure/btc-signer's Transaction.signIdx() auto-detects the address type from witnessUtxo.script
|
|
246
|
-
* and computes the correct sighash (BIP143 for P2WPKH, BIP341 for P2TR).
|
|
247
|
-
*
|
|
248
|
-
* @param message - Plain text message to sign
|
|
249
|
-
* @param privateKey - 32-byte private key (P2WPKH key for bc1q, Taproot key for bc1p)
|
|
250
|
-
* @param scriptPubKey - scriptPubKey of the signing address
|
|
251
|
-
* @param tapInternalKey - For P2TR: the UNTWEAKED x-only pubkey (32 bytes). Required for Taproot
|
|
252
|
-
* signing. Must be the internal key BEFORE TapTweak, NOT the tweaked key in the scriptPubKey.
|
|
253
|
-
* @returns Base64-encoded BIP-322 "simple" signature (serialized witness)
|
|
254
|
-
*/
|
|
255
|
-
function bip322Sign(message, privateKey, scriptPubKey, tapInternalKey) {
|
|
256
|
-
const toSpendTxid = bip322BuildToSpendTxId(message, scriptPubKey);
|
|
257
|
-
// allowUnknownOutputs: true is required for the OP_RETURN output in BIP-322 virtual transactions.
|
|
258
|
-
const toSignTx = new Transaction({ version: 0, lockTime: 0, allowUnknownOutputs: true });
|
|
259
|
-
toSignTx.addInput({
|
|
260
|
-
txid: toSpendTxid,
|
|
261
|
-
index: 0,
|
|
262
|
-
sequence: 0,
|
|
263
|
-
witnessUtxo: { amount: 0n, script: scriptPubKey },
|
|
264
|
-
...(tapInternalKey && { tapInternalKey }),
|
|
265
|
-
});
|
|
266
|
-
toSignTx.addOutput({ script: Script.encode(["RETURN"]), amount: 0n });
|
|
267
|
-
// signIdx auto-detects P2WPKH vs P2TR from witnessUtxo.script and applies correct sighash
|
|
268
|
-
toSignTx.signIdx(privateKey, 0);
|
|
269
|
-
toSignTx.finalizeIdx(0);
|
|
270
|
-
const input = toSignTx.getInput(0);
|
|
271
|
-
if (!input.finalScriptWitness) {
|
|
272
|
-
throw new Error("BIP-322 signing failed: no witness produced");
|
|
273
|
-
}
|
|
274
|
-
const encodedWitness = RawWitness.encode(input.finalScriptWitness);
|
|
275
|
-
return Buffer.from(encodedWitness).toString("base64");
|
|
276
|
-
}
|
|
191
|
+
// bip322BuildToSpendTxId and bip322Sign are imported from ../utils/bip322.js
|
|
277
192
|
/**
|
|
278
193
|
* BIP-322 "simple" verification for P2WPKH (bc1q/tb1q) addresses.
|
|
279
194
|
*
|