@aibtc/mcp-server 1.70.1 → 1.71.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/README.md +9 -5
- package/dist/bridge/index.js +2 -2
- package/dist/config/contracts.d.ts +4 -4
- package/dist/config/contracts.js +2 -2
- package/dist/endpoints/registry.d.ts +8 -26
- package/dist/endpoints/registry.js +7 -932
- package/dist/services/stacking.service.d.ts +144 -45
- package/dist/services/stacking.service.js +416 -123
- package/dist/services/x402-discovery.service.d.ts +81 -0
- package/dist/services/x402-discovery.service.js +170 -0
- package/dist/tools/earning.tools.js +3 -3
- package/dist/tools/endpoint.tools.js +79 -37
- package/dist/tools/pillar-direct.tools.js +23 -2
- package/dist/tools/sbtc.tools.js +2 -21
- package/dist/tools/skill-mappings.js +4 -0
- package/dist/tools/stacking.tools.js +352 -44
- package/dist/tools/yield-dashboard.tools.js +13 -22
- package/dist/utils/bitcoin.d.ts +8 -0
- package/dist/utils/bitcoin.js +24 -0
- package/package.json +5 -5
- package/skill/SKILL.md +1 -1
- package/skill/references/stacks-defi.md +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live x402 endpoint discovery.
|
|
3
|
+
*
|
|
4
|
+
* x402.aibtc.com and stx402.com publish an OpenAPI spec, and stx402.com runs a
|
|
5
|
+
* directory of x402 endpoints registered by third parties. Reading those at call
|
|
6
|
+
* time keeps `list_x402_endpoints` in step with what the hosts actually serve,
|
|
7
|
+
* instead of a hand-maintained list that goes stale when routes move.
|
|
8
|
+
*
|
|
9
|
+
* Results are cached briefly. A failed fetch is never cached and never replaced
|
|
10
|
+
* with stale or invented entries: the source is reported as unavailable.
|
|
11
|
+
*/
|
|
12
|
+
import { NETWORK } from "../config/networks.js";
|
|
13
|
+
export const OPENAPI_SOURCES = [
|
|
14
|
+
{
|
|
15
|
+
source: "x402.aibtc.com",
|
|
16
|
+
baseUrl: { mainnet: "https://x402.aibtc.com", testnet: "https://x402.aibtc.dev" },
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
source: "stx402.com",
|
|
20
|
+
baseUrl: { mainnet: "https://stx402.com" },
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
const DIRECTORY_URL = "https://stx402.com/registry/list";
|
|
24
|
+
const DIRECTORY_PAGE_SIZE = 50;
|
|
25
|
+
/** Hard stop so a misbehaving `total` cannot turn one call into hundreds of requests. */
|
|
26
|
+
const DIRECTORY_MAX_PAGES = 20;
|
|
27
|
+
export const DISCOVERY_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
28
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
29
|
+
const cache = new Map();
|
|
30
|
+
async function cached(key, load) {
|
|
31
|
+
const hit = cache.get(key);
|
|
32
|
+
if (hit && Date.now() - hit.at < DISCOVERY_CACHE_TTL_MS) {
|
|
33
|
+
return hit.value;
|
|
34
|
+
}
|
|
35
|
+
const value = await load();
|
|
36
|
+
cache.set(key, { at: Date.now(), value });
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
/** Test seam. */
|
|
40
|
+
export function clearDiscoveryCache() {
|
|
41
|
+
cache.clear();
|
|
42
|
+
}
|
|
43
|
+
async function fetchJson(url) {
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
headers: { Accept: "application/json" },
|
|
46
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`GET ${url} returned HTTP ${response.status}`);
|
|
50
|
+
}
|
|
51
|
+
return response.json();
|
|
52
|
+
}
|
|
53
|
+
const METHODS = ["get", "post", "put", "delete"];
|
|
54
|
+
/**
|
|
55
|
+
* Summaries on these hosts lead with the pricing tier, e.g. "(paid, standard) Decode a
|
|
56
|
+
* transaction" or "(free) List models". Split that into a cost label and a description.
|
|
57
|
+
* The exact price is only known from the 402 challenge, so it is not guessed here.
|
|
58
|
+
*/
|
|
59
|
+
export function parseSummary(summary) {
|
|
60
|
+
const match = summary.match(/^\((free|paid)(?:,\s*([^)]+))?\)\s*(.*)$/i);
|
|
61
|
+
if (match) {
|
|
62
|
+
const [, kind, tier, rest] = match;
|
|
63
|
+
return {
|
|
64
|
+
cost: kind.toLowerCase() === "free" ? "FREE" : `paid${tier ? ` (${tier.trim()})` : ""}`,
|
|
65
|
+
description: rest,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (/\(free\)/i.test(summary)) {
|
|
69
|
+
return { cost: "FREE", description: summary.replace(/\s*\(free\)/i, "") };
|
|
70
|
+
}
|
|
71
|
+
return { cost: "see probe_x402_endpoint", description: summary };
|
|
72
|
+
}
|
|
73
|
+
/** Map an OpenAPI spec to endpoint entries. Admin-only operations are left out. */
|
|
74
|
+
export function endpointsFromOpenApi(spec, source) {
|
|
75
|
+
const endpoints = [];
|
|
76
|
+
for (const [path, operations] of Object.entries(spec.paths ?? {})) {
|
|
77
|
+
for (const method of METHODS) {
|
|
78
|
+
const op = operations[method];
|
|
79
|
+
if (!op)
|
|
80
|
+
continue;
|
|
81
|
+
const tags = op.tags ?? [];
|
|
82
|
+
if (tags.some((tag) => /admin/i.test(tag)))
|
|
83
|
+
continue;
|
|
84
|
+
const { cost, description } = parseSummary(op.summary ?? op.description ?? "");
|
|
85
|
+
// tokenType selects the payment asset; execute_x402_endpoint handles that itself.
|
|
86
|
+
const params = Object.fromEntries((op.parameters ?? [])
|
|
87
|
+
.filter((p) => p.name !== "tokenType")
|
|
88
|
+
.map((p) => [p.name, p.description ?? p.in]));
|
|
89
|
+
const schema = op.requestBody?.content?.["application/json"]?.schema;
|
|
90
|
+
const required = new Set(schema?.required ?? []);
|
|
91
|
+
const body = Object.fromEntries(Object.entries(schema?.properties ?? {}).map(([name, prop]) => [
|
|
92
|
+
name,
|
|
93
|
+
`${prop.description ?? name}${required.has(name) ? "" : " (optional)"}`,
|
|
94
|
+
]));
|
|
95
|
+
endpoints.push({
|
|
96
|
+
path,
|
|
97
|
+
method: method.toUpperCase(),
|
|
98
|
+
description,
|
|
99
|
+
cost,
|
|
100
|
+
category: tags[0] ?? "Other",
|
|
101
|
+
source,
|
|
102
|
+
...(Object.keys(params).length > 0 && { params }),
|
|
103
|
+
...(Object.keys(body).length > 0 && { body }),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return endpoints;
|
|
108
|
+
}
|
|
109
|
+
/** Fetch one host's endpoints from its OpenAPI spec. */
|
|
110
|
+
export function fetchOpenApiEndpoints(source, network = NETWORK) {
|
|
111
|
+
const baseUrl = source.baseUrl[network];
|
|
112
|
+
if (!baseUrl) {
|
|
113
|
+
return Promise.reject(new Error(`${source.source} has no ${network} deployment`));
|
|
114
|
+
}
|
|
115
|
+
return cached(`openapi:${baseUrl}`, async () => {
|
|
116
|
+
const spec = (await fetchJson(`${baseUrl}/openapi.json`));
|
|
117
|
+
if (!spec.paths) {
|
|
118
|
+
throw new Error(`${baseUrl}/openapi.json has no paths`);
|
|
119
|
+
}
|
|
120
|
+
return endpointsFromOpenApi(spec, source.source);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
/** Fetch every entry in the stx402.com endpoint directory (mainnet only). */
|
|
124
|
+
export function fetchDirectoryEntries() {
|
|
125
|
+
return cached("directory", async () => {
|
|
126
|
+
const entries = [];
|
|
127
|
+
let total = Infinity;
|
|
128
|
+
for (let page = 0; page < DIRECTORY_MAX_PAGES && entries.length < total; page++) {
|
|
129
|
+
const offset = page * DIRECTORY_PAGE_SIZE;
|
|
130
|
+
const data = (await fetchJson(`${DIRECTORY_URL}?limit=${DIRECTORY_PAGE_SIZE}&offset=${offset}`));
|
|
131
|
+
if (!Array.isArray(data.entries)) {
|
|
132
|
+
throw new Error(`${DIRECTORY_URL} response has no entries array`);
|
|
133
|
+
}
|
|
134
|
+
total = data.total ?? entries.length + data.entries.length;
|
|
135
|
+
for (const e of data.entries) {
|
|
136
|
+
if (!e.url)
|
|
137
|
+
continue;
|
|
138
|
+
entries.push({
|
|
139
|
+
name: e.name ?? e.url,
|
|
140
|
+
url: e.url,
|
|
141
|
+
category: e.category ?? "other",
|
|
142
|
+
status: e.status ?? "unknown",
|
|
143
|
+
owner: e.owner ?? "",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (data.entries.length < DIRECTORY_PAGE_SIZE)
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
return entries;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function errorMessage(error) {
|
|
153
|
+
return error instanceof Error ? error.message : String(error);
|
|
154
|
+
}
|
|
155
|
+
/** Fetch every OpenAPI source in parallel, reporting failures per source. */
|
|
156
|
+
export async function discoverOpenApiEndpoints(sources = OPENAPI_SOURCES, network = NETWORK) {
|
|
157
|
+
const results = await Promise.allSettled(sources.map((s) => fetchOpenApiEndpoints(s, network)));
|
|
158
|
+
const endpoints = [];
|
|
159
|
+
const unavailable = [];
|
|
160
|
+
results.forEach((result, i) => {
|
|
161
|
+
if (result.status === "fulfilled") {
|
|
162
|
+
endpoints.push(...result.value);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
unavailable.push({ source: sources[i].source, error: errorMessage(result.reason) });
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
return { endpoints, unavailable };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=x402-discovery.service.js.map
|
|
@@ -15,9 +15,9 @@ export const EARNING_STRATEGIES = [
|
|
|
15
15
|
id: "stacking",
|
|
16
16
|
name: "Bitcoin Stacking (PoX)",
|
|
17
17
|
assets: ["STX"],
|
|
18
|
-
returns: "
|
|
19
|
-
summary: "Lock STX to support Stacks consensus and earn
|
|
20
|
-
start_with: ["
|
|
18
|
+
returns: "sBTC rewards per PoX cycle",
|
|
19
|
+
summary: "Lock STX with a PoX-5 signer manager to support Stacks consensus and earn sBTC rewards (or BTC via the manager's payout address). Dual Stacking routes the yield as sBTC.",
|
|
20
|
+
start_with: ["list_stacking_signers", "stack_stx", "get_stacking_status", "dual_stacking_enroll"],
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
id: "defi_yield",
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { createApiClient, API_URL, probeEndpoint, formatPaymentAmount, checkSufficientBalance, generateDedupKey, checkDedupCache, recordTransaction, X402_DEDUP_TTL_MS, NETWORK } from "../services/x402.service.js";
|
|
3
|
-
import {
|
|
3
|
+
import { STATIC_ENDPOINTS, formatEndpointsTable } from "../endpoints/registry.js";
|
|
4
|
+
import { discoverOpenApiEndpoints, fetchDirectoryEntries, } from "../services/x402-discovery.service.js";
|
|
4
5
|
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
5
6
|
import { X402_HEADERS, decodePaymentResponse } from "../utils/x402-protocol.js";
|
|
6
7
|
import { extractPaymentIdFromPaymentSignature, extractTxidFromPaymentSignature, pollTransactionConfirmation, } from "../utils/x402-recovery.js";
|
|
7
8
|
import { formatCanonicalPaymentStatus } from "../utils/x402-payment-state.js";
|
|
8
9
|
import { emitPaymentLog } from "../utils/x402-payment-logging.js";
|
|
9
|
-
const ALL_SOURCES = "x402.biwas.xyz, x402.aibtc.com, stx402.com, aibtc.com";
|
|
10
10
|
/**
|
|
11
11
|
* Parse and validate endpoint URL from either a full URL or path+apiUrl combination.
|
|
12
12
|
* Merges any query parameters from the URL into the provided params.
|
|
@@ -130,27 +130,26 @@ function formatProbeResponse(result, method, fullUrl, callWithOptions, messagePr
|
|
|
130
130
|
export function registerEndpointTools(server) {
|
|
131
131
|
// List x402 endpoints
|
|
132
132
|
server.registerTool("list_x402_endpoints", {
|
|
133
|
-
description: `List
|
|
134
|
-
|
|
135
|
-
The agent can:
|
|
136
|
-
1. Execute x402 endpoints from these sources (paid API calls with automatic payment handling)
|
|
137
|
-
2. Execute direct Stacks transactions (transfer STX, call contracts, deploy contracts)
|
|
133
|
+
description: `List x402 API endpoints. x402.aibtc.com and stx402.com are read live from their OpenAPI specs; the stx402.com directory lists x402 endpoints registered by third parties.
|
|
138
134
|
|
|
139
135
|
Sources:
|
|
140
136
|
- x402.biwas.xyz: DeFi analytics, market data, wallet analysis, Zest/ALEX protocols
|
|
141
|
-
- x402.aibtc.com: AI inference,
|
|
142
|
-
- stx402.com:
|
|
143
|
-
- aibtc.com: Inbox messaging
|
|
137
|
+
- x402.aibtc.com: AI inference, Stacks utilities, hashing, storage (live)
|
|
138
|
+
- stx402.com: x402 endpoint registry, agent registry (ERC-8004), links (live)
|
|
139
|
+
- aibtc.com: Inbox messaging
|
|
140
|
+
- directory: third-party endpoints registered at stx402.com (live, mainnet). Unverified entries are hidden unless includeUnverified is true — probe before paying.
|
|
141
|
+
|
|
142
|
+
Costs from live specs are tiers ("paid (standard)", "paid (dynamic)"); use probe_x402_endpoint for the exact price.`,
|
|
144
143
|
inputSchema: {
|
|
145
144
|
source: z
|
|
146
|
-
.enum(["x402.biwas.xyz", "x402.aibtc.com", "stx402.com", "aibtc.com", "all"])
|
|
145
|
+
.enum(["x402.biwas.xyz", "x402.aibtc.com", "stx402.com", "aibtc.com", "directory", "all"])
|
|
147
146
|
.optional()
|
|
148
147
|
.default("all")
|
|
149
148
|
.describe("Filter by API source"),
|
|
150
149
|
category: z
|
|
151
150
|
.string()
|
|
152
151
|
.optional()
|
|
153
|
-
.describe("Filter by category (
|
|
152
|
+
.describe("Filter by category (case-insensitive)"),
|
|
154
153
|
search: z
|
|
155
154
|
.string()
|
|
156
155
|
.optional()
|
|
@@ -163,12 +162,42 @@ Sources:
|
|
|
163
162
|
.boolean()
|
|
164
163
|
.optional()
|
|
165
164
|
.describe("Only show paid endpoints (require x402 payment)"),
|
|
165
|
+
includeUnverified: z
|
|
166
|
+
.boolean()
|
|
167
|
+
.optional()
|
|
168
|
+
.default(false)
|
|
169
|
+
.describe("Include unverified third-party entries from the stx402.com directory"),
|
|
166
170
|
},
|
|
167
|
-
}, async ({ source, category, search, showFreeOnly, showPaidOnly }) => {
|
|
171
|
+
}, async ({ source, category, search, showFreeOnly, showPaidOnly, includeUnverified }) => {
|
|
168
172
|
try {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
173
|
+
const wanted = source ?? "all";
|
|
174
|
+
const unavailable = [];
|
|
175
|
+
let endpoints = STATIC_ENDPOINTS.filter((ep) => wanted === "all" || ep.source === wanted);
|
|
176
|
+
if (wanted === "all" || wanted === "x402.aibtc.com" || wanted === "stx402.com") {
|
|
177
|
+
const live = await discoverOpenApiEndpoints();
|
|
178
|
+
endpoints.push(...live.endpoints.filter((ep) => wanted === "all" || ep.source === wanted));
|
|
179
|
+
unavailable.push(...live.unavailable.filter((u) => wanted === "all" || u.source === wanted));
|
|
180
|
+
}
|
|
181
|
+
let directory = [];
|
|
182
|
+
// Directory entries carry no method or price, so they only answer
|
|
183
|
+
// unfiltered or keyword/category browsing, not free/paid filters.
|
|
184
|
+
if ((wanted === "all" || wanted === "directory") && !showFreeOnly && !showPaidOnly) {
|
|
185
|
+
if (NETWORK !== "mainnet") {
|
|
186
|
+
if (wanted === "directory") {
|
|
187
|
+
unavailable.push({ source: "directory", error: "the stx402.com directory lists mainnet endpoints only" });
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
try {
|
|
192
|
+
directory = (await fetchDirectoryEntries()).filter((e) => includeUnverified || e.status === "verified");
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
unavailable.push({
|
|
196
|
+
source: "directory",
|
|
197
|
+
error: error instanceof Error ? error.message : String(error),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
172
201
|
}
|
|
173
202
|
if (showFreeOnly) {
|
|
174
203
|
endpoints = endpoints.filter((ep) => ep.cost === "FREE");
|
|
@@ -177,38 +206,51 @@ Sources:
|
|
|
177
206
|
endpoints = endpoints.filter((ep) => ep.cost !== "FREE");
|
|
178
207
|
}
|
|
179
208
|
if (category) {
|
|
180
|
-
|
|
209
|
+
const c = category.toLowerCase();
|
|
210
|
+
endpoints = endpoints.filter((ep) => ep.category.toLowerCase() === c);
|
|
211
|
+
directory = directory.filter((e) => e.category.toLowerCase() === c);
|
|
181
212
|
}
|
|
182
213
|
if (search) {
|
|
183
|
-
const
|
|
184
|
-
endpoints = endpoints.filter((ep) =>
|
|
214
|
+
const q = search.toLowerCase();
|
|
215
|
+
endpoints = endpoints.filter((ep) => ep.path.toLowerCase().includes(q) ||
|
|
216
|
+
ep.description.toLowerCase().includes(q) ||
|
|
217
|
+
ep.category.toLowerCase().includes(q));
|
|
218
|
+
directory = directory.filter((e) => e.name.toLowerCase().includes(q) ||
|
|
219
|
+
e.url.toLowerCase().includes(q) ||
|
|
220
|
+
e.category.toLowerCase().includes(q));
|
|
185
221
|
}
|
|
186
|
-
|
|
187
|
-
|
|
222
|
+
const unavailableText = unavailable.length > 0
|
|
223
|
+
? `\n\n## Unavailable sources\n${unavailable.map((u) => `- ${u.source}: ${u.error}`).join("\n")}`
|
|
224
|
+
: "";
|
|
225
|
+
if (endpoints.length === 0 && directory.length === 0) {
|
|
188
226
|
return {
|
|
189
227
|
content: [
|
|
190
228
|
{
|
|
191
229
|
type: "text",
|
|
192
|
-
text: `No endpoints found matching your criteria
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Sources: ${ALL_SOURCES}
|
|
197
|
-
|
|
198
|
-
If you're looking to perform a direct blockchain action (transfer STX, call a contract), those are available via separate tools.`,
|
|
230
|
+
text: `No endpoints found matching your criteria (source: ${wanted}).` +
|
|
231
|
+
unavailableText +
|
|
232
|
+
`\n\nIf you're looking to perform a direct blockchain action (transfer STX, call a contract), those are available via separate tools.`,
|
|
199
233
|
},
|
|
200
234
|
],
|
|
201
235
|
};
|
|
202
236
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
const directoryText = directory.length > 0
|
|
238
|
+
? `\n\n## Directory (stx402.com, third-party)\n` +
|
|
239
|
+
directory
|
|
240
|
+
.map((e) => `- ${e.name} [${e.category}, ${e.status}]\n ${e.url}`)
|
|
241
|
+
.join("\n")
|
|
242
|
+
: "";
|
|
207
243
|
return {
|
|
208
244
|
content: [
|
|
209
245
|
{
|
|
210
246
|
type: "text",
|
|
211
|
-
text: `# Available x402 Endpoints (${endpoints.length} total)\n\n
|
|
247
|
+
text: `# Available x402 Endpoints (${endpoints.length + directory.length} total)\n\n` +
|
|
248
|
+
`Source: ${wanted}\nDefault API: ${API_URL}\n` +
|
|
249
|
+
(endpoints.length > 0 ? `\n${formatEndpointsTable(endpoints)}` : "") +
|
|
250
|
+
directoryText +
|
|
251
|
+
unavailableText +
|
|
252
|
+
`\n\n---\nUse execute_x402_endpoint to call an endpoint (full url for directory entries). ` +
|
|
253
|
+
`Use probe_x402_endpoint first to see the exact price.`,
|
|
212
254
|
},
|
|
213
255
|
],
|
|
214
256
|
};
|
|
@@ -224,7 +266,7 @@ If you're looking to perform a direct blockchain action (transfer STX, call a co
|
|
|
224
266
|
Supported sources:
|
|
225
267
|
- x402.biwas.xyz (default): Use path like "/api/pools/trending"
|
|
226
268
|
- x402.aibtc.com (mainnet) / x402.aibtc.dev (testnet): Use apiUrl="https://x402.aibtc.com" with path like "/inference/openrouter/chat"
|
|
227
|
-
- stx402.com: Use apiUrl="https://stx402.com" with path like "/
|
|
269
|
+
- stx402.com: Use apiUrl="https://stx402.com" with path like "/registry/list"
|
|
228
270
|
- aibtc.com (mainnet) / aibtc.dev (testnet): Use apiUrl="https://aibtc.com" with path like "/api/inbox/{address}"
|
|
229
271
|
- Any x402-compatible URL: Use url parameter with full endpoint URL
|
|
230
272
|
|
|
@@ -240,7 +282,7 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
|
|
|
240
282
|
.string()
|
|
241
283
|
.url()
|
|
242
284
|
.optional()
|
|
243
|
-
.describe("Full endpoint URL (e.g., 'https://
|
|
285
|
+
.describe("Full endpoint URL (e.g., 'https://x402.aibtc.com/hashing/sha256'). Takes precedence over path+apiUrl."),
|
|
244
286
|
path: z
|
|
245
287
|
.string()
|
|
246
288
|
.optional()
|
|
@@ -464,7 +506,7 @@ After probing a paid endpoint, use execute_x402_endpoint to actually execute and
|
|
|
464
506
|
Supported sources:
|
|
465
507
|
- x402.biwas.xyz (default): Use path like "/api/pools/trending"
|
|
466
508
|
- x402.aibtc.com (mainnet) / x402.aibtc.dev (testnet): Use apiUrl="https://x402.aibtc.com" with path like "/inference/openrouter/chat"
|
|
467
|
-
- stx402.com: Use apiUrl="https://stx402.com" with path like "/
|
|
509
|
+
- stx402.com: Use apiUrl="https://stx402.com" with path like "/registry/list"
|
|
468
510
|
- aibtc.com (mainnet) / aibtc.dev (testnet): Use apiUrl="https://aibtc.com" with path like "/api/inbox/{address}"
|
|
469
511
|
- Any x402-compatible URL: Use url parameter with full endpoint URL`,
|
|
470
512
|
inputSchema: {
|
|
@@ -476,7 +518,7 @@ Supported sources:
|
|
|
476
518
|
.string()
|
|
477
519
|
.url()
|
|
478
520
|
.optional()
|
|
479
|
-
.describe("Full endpoint URL (e.g., 'https://
|
|
521
|
+
.describe("Full endpoint URL (e.g., 'https://x402.aibtc.com/hashing/sha256'). Takes precedence over path+apiUrl."),
|
|
480
522
|
path: z
|
|
481
523
|
.string()
|
|
482
524
|
.optional()
|
|
@@ -88,6 +88,21 @@ function formatSigAuthForApi(sigAuth) {
|
|
|
88
88
|
: "0x" + sigAuth.pubkey,
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The Pillar smart wallet's Fast Pool functions are hardcoded to pox-4
|
|
93
|
+
* (`pox-4.allow-contract-caller` + `pox4-fast-pool-v3.delegate-stx`, and
|
|
94
|
+
* `pox-4.revoke-delegate-stx`). Once PoX has moved past pox-4 those calls abort
|
|
95
|
+
* on chain, so refuse before signing instead of burning gas on a sure failure.
|
|
96
|
+
*/
|
|
97
|
+
async function assertFastPoolPoxActive(action) {
|
|
98
|
+
const pox = await getHiroApi(NETWORK).getPoxInfo();
|
|
99
|
+
if (!pox.contract_id.endsWith(".pox-4")) {
|
|
100
|
+
throw new Error(`${action} is unavailable: the Pillar smart wallet's Fast Pool path calls pox-4, but the ` +
|
|
101
|
+
`active PoX contract is ${pox.contract_id}, so the transaction would abort on chain. ` +
|
|
102
|
+
`This needs a Pillar wallet contract update. For STX staking on PoX-5, use stack_stx ` +
|
|
103
|
+
`with a signer manager (see list_stacking_signers).`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
91
106
|
// ============================================================================
|
|
92
107
|
// Tool Registration
|
|
93
108
|
// ============================================================================
|
|
@@ -1041,7 +1056,8 @@ export function registerPillarDirectTools(server) {
|
|
|
1041
1056
|
server.registerTool("pillar_direct_stack_stx", {
|
|
1042
1057
|
description: "Stack STX from your Pillar smart wallet via Fast Pool or Stacking DAO. " +
|
|
1043
1058
|
"Agent-signed, no browser needed. Backend sponsors gas. " +
|
|
1044
|
-
"Fast Pool delegates STX to the pox4-fast-pool-v3 contract
|
|
1059
|
+
"Fast Pool delegates STX to the pox4-fast-pool-v3 contract through pox-4, and is refused while " +
|
|
1060
|
+
"pox-4 is not the active PoX contract (PoX-5 is live; use stack_stx for PoX-5 staking). " +
|
|
1045
1061
|
"Stacking DAO deposits STX into Stacking DAO core for stSTX yield. " +
|
|
1046
1062
|
"Your wallet must be enrolled in dual stacking first (automatic for v2 wallets with sBTC).",
|
|
1047
1063
|
inputSchema: {
|
|
@@ -1056,6 +1072,9 @@ export function registerPillarDirectTools(server) {
|
|
|
1056
1072
|
},
|
|
1057
1073
|
}, async ({ stxAmount, pool }) => {
|
|
1058
1074
|
try {
|
|
1075
|
+
if (pool === "fast-pool") {
|
|
1076
|
+
await assertFastPoolPoxActive("Fast Pool stacking");
|
|
1077
|
+
}
|
|
1059
1078
|
const { keyService, session } = await requireActiveKey();
|
|
1060
1079
|
const authId = generateAuthId();
|
|
1061
1080
|
// Build SIP-018 structured data matching the contract's hash builder.
|
|
@@ -1099,12 +1118,14 @@ export function registerPillarDirectTools(server) {
|
|
|
1099
1118
|
});
|
|
1100
1119
|
// --- pillar_direct_revoke_fast_pool ---
|
|
1101
1120
|
server.registerTool("pillar_direct_revoke_fast_pool", {
|
|
1102
|
-
description: "Revoke Fast Pool STX delegation from your Pillar smart wallet
|
|
1121
|
+
description: "Revoke Fast Pool STX delegation from your Pillar smart wallet (pox-4 revoke-delegate-stx; " +
|
|
1122
|
+
"refused while pox-4 is not the active PoX contract). " +
|
|
1103
1123
|
"Agent-signed, no browser needed. Backend sponsors gas. " +
|
|
1104
1124
|
"After revoking, STX stays locked until the current PoX cycle ends, then returns to liquid.",
|
|
1105
1125
|
inputSchema: {},
|
|
1106
1126
|
}, async () => {
|
|
1107
1127
|
try {
|
|
1128
|
+
await assertFastPoolPoxActive("Revoking Fast Pool");
|
|
1108
1129
|
const { keyService, session } = await requireActiveKey();
|
|
1109
1130
|
const authId = generateAuthId();
|
|
1110
1131
|
const structuredData = tupleCV({
|
package/dist/tools/sbtc.tools.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as btc from "@scure/btc-signer";
|
|
2
1
|
import { z } from "zod";
|
|
3
2
|
import { getAccount, getWalletAddress, NETWORK } from "../services/x402.service.js";
|
|
4
3
|
import { getSbtcService } from "../services/sbtc.service.js";
|
|
@@ -8,26 +7,8 @@ import { getContracts, parseContractId } from "../config/contracts.js";
|
|
|
8
7
|
import { createJsonResponse, createErrorResponse, resolveFee } from "../utils/index.js";
|
|
9
8
|
import { getWalletManager } from "../services/wallet-manager.js";
|
|
10
9
|
import { MempoolApi, getMempoolTxUrl } from "../services/mempool-api.js";
|
|
11
|
-
import { getBtcNetwork } from "../transactions/bitcoin-builder.js";
|
|
12
10
|
import { sponsoredSchema } from "./schemas.js";
|
|
13
|
-
|
|
14
|
-
const decoded = btc.Address(getBtcNetwork(NETWORK)).decode(btcRecipientAddress);
|
|
15
|
-
if (decoded.type === "tr" && decoded.pubkey) {
|
|
16
|
-
return { version: 0x06, hashbytesHex: Buffer.from(decoded.pubkey).toString("hex") };
|
|
17
|
-
}
|
|
18
|
-
switch (decoded.type) {
|
|
19
|
-
case "pkh":
|
|
20
|
-
return { version: 0x00, hashbytesHex: Buffer.from(decoded.hash).toString("hex") };
|
|
21
|
-
case "sh":
|
|
22
|
-
return { version: 0x01, hashbytesHex: Buffer.from(decoded.hash).toString("hex") };
|
|
23
|
-
case "wpkh":
|
|
24
|
-
return { version: 0x04, hashbytesHex: Buffer.from(decoded.hash).toString("hex") };
|
|
25
|
-
case "wsh":
|
|
26
|
-
return { version: 0x05, hashbytesHex: Buffer.from(decoded.hash).toString("hex") };
|
|
27
|
-
default:
|
|
28
|
-
throw new Error("Unsupported BTC recipient address type. Supported: P2PKH, P2SH, P2WPKH, P2WSH, P2TR.");
|
|
29
|
-
}
|
|
30
|
-
}
|
|
11
|
+
import { btcAddressToPoxAddr } from "../utils/bitcoin.js";
|
|
31
12
|
function getReadonlySenderAddress() {
|
|
32
13
|
const contracts = getContracts(NETWORK);
|
|
33
14
|
return parseContractId(contracts.SBTC_REGISTRY).address;
|
|
@@ -107,7 +88,7 @@ Example: To send 0.001 sBTC, use amount "100000" (satoshis).`,
|
|
|
107
88
|
const sbtcService = getSbtcService(NETWORK);
|
|
108
89
|
const account = await getAccount();
|
|
109
90
|
const resolvedFee = await resolveFee(fee, NETWORK, "contract_call");
|
|
110
|
-
const recipientTuple =
|
|
91
|
+
const recipientTuple = btcAddressToPoxAddr(btcRecipientAddress, NETWORK);
|
|
111
92
|
const result = await sbtcService.initiateWithdrawal(account, BigInt(amount), BigInt(maxFee), recipientTuple, resolvedFee, sponsored);
|
|
112
93
|
let requestId = null;
|
|
113
94
|
try {
|
|
@@ -68,6 +68,10 @@ export const TOOL_SKILL_MAP = {
|
|
|
68
68
|
get_stacking_status: "stacking",
|
|
69
69
|
stack_stx: "stacking",
|
|
70
70
|
extend_stacking: "stacking",
|
|
71
|
+
list_stacking_signers: "stacking",
|
|
72
|
+
unstake_stx: "stacking",
|
|
73
|
+
get_stacking_rewards: "stacking",
|
|
74
|
+
claim_stacking_rewards: "stacking",
|
|
71
75
|
// dual-stacking skill — sBTC Dual Stacking yield protocol
|
|
72
76
|
dual_stacking_status: "dual-stacking",
|
|
73
77
|
dual_stacking_get_rewards: "dual-stacking",
|