@aibtc/mcp-server 1.58.0 → 1.59.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/index.js +18 -5
- package/dist/services/credentials.js +7 -2
- package/dist/services/defi.service.js +23 -11
- package/dist/services/hiro-api.d.ts +10 -0
- package/dist/services/hiro-api.js +30 -8
- package/dist/services/nft.service.js +14 -5
- package/dist/services/pillar-api.service.d.ts +8 -0
- package/dist/services/pillar-api.service.js +13 -1
- package/dist/services/x402.service.js +45 -13
- package/dist/tools/bitcoin.tools.js +14 -0
- package/dist/tools/endpoint.tools.js +3 -1
- package/dist/tools/inbox.tools.js +9 -12
- package/dist/tools/news.tools.js +9 -12
- package/dist/tools/pillar-direct.tools.js +29 -22
- package/dist/tools/sbtc.tools.js +2 -9
- package/dist/tools/scaffold.tools.js +9 -14
- package/dist/tools/stacking-lottery.tools.js +8 -5
- package/dist/tools/yield-dashboard.tools.js +10 -14
- package/dist/utils/encryption.js +2 -0
- package/package.json +10 -5
- package/skill/SKILL.md +1 -1
- package/skill/references/genesis-lifecycle.md +105 -92
- package/dist/config/bitcoin-constants.d.ts.map +0 -1
- package/dist/config/bitcoin-constants.js.map +0 -1
- package/dist/config/caip.d.ts.map +0 -1
- package/dist/config/caip.js.map +0 -1
- package/dist/config/competition.d.ts.map +0 -1
- package/dist/config/competition.js.map +0 -1
- package/dist/config/contracts.d.ts.map +0 -1
- package/dist/config/contracts.js.map +0 -1
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js.map +0 -1
- package/dist/config/networks.d.ts.map +0 -1
- package/dist/config/networks.js.map +0 -1
- package/dist/config/pillar.d.ts.map +0 -1
- package/dist/config/pillar.js.map +0 -1
- package/dist/config/sponsor.d.ts.map +0 -1
- package/dist/config/sponsor.js.map +0 -1
- package/dist/endpoints/index.d.ts.map +0 -1
- package/dist/endpoints/index.js.map +0 -1
- package/dist/endpoints/registry.d.ts.map +0 -1
- package/dist/endpoints/registry.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/services/bitflow.service.d.ts.map +0 -1
- package/dist/services/bitflow.service.js.map +0 -1
- package/dist/services/bns.service.d.ts.map +0 -1
- package/dist/services/bns.service.js.map +0 -1
- package/dist/services/credentials.d.ts.map +0 -1
- package/dist/services/credentials.js.map +0 -1
- package/dist/services/defi.service.d.ts.map +0 -1
- package/dist/services/defi.service.js.map +0 -1
- package/dist/services/erc8004.service.d.ts.map +0 -1
- package/dist/services/erc8004.service.js.map +0 -1
- package/dist/services/hiro-api.d.ts.map +0 -1
- package/dist/services/hiro-api.js.map +0 -1
- package/dist/services/inscription-parser.d.ts.map +0 -1
- package/dist/services/inscription-parser.js.map +0 -1
- package/dist/services/lightning/provider.d.ts.map +0 -1
- package/dist/services/lightning/provider.js.map +0 -1
- package/dist/services/lightning/spark-provider.d.ts.map +0 -1
- package/dist/services/lightning/spark-provider.js.map +0 -1
- package/dist/services/lightning-manager.d.ts.map +0 -1
- package/dist/services/lightning-manager.js.map +0 -1
- package/dist/services/mempool-api.d.ts.map +0 -1
- package/dist/services/mempool-api.js.map +0 -1
- package/dist/services/nft.service.d.ts.map +0 -1
- package/dist/services/nft.service.js.map +0 -1
- package/dist/services/nonce-tracker.d.ts.map +0 -1
- package/dist/services/nonce-tracker.js.map +0 -1
- package/dist/services/pillar-api.service.d.ts.map +0 -1
- package/dist/services/pillar-api.service.js.map +0 -1
- package/dist/services/sbtc-deposit.service.d.ts.map +0 -1
- package/dist/services/sbtc-deposit.service.js.map +0 -1
- package/dist/services/sbtc.service.d.ts.map +0 -1
- package/dist/services/sbtc.service.js.map +0 -1
- package/dist/services/scaffold.service.d.ts.map +0 -1
- package/dist/services/scaffold.service.js.map +0 -1
- package/dist/services/signing-key.service.d.ts.map +0 -1
- package/dist/services/signing-key.service.js.map +0 -1
- package/dist/services/stacking.service.d.ts.map +0 -1
- package/dist/services/stacking.service.js.map +0 -1
- package/dist/services/tenero-api.d.ts.map +0 -1
- package/dist/services/tenero-api.js.map +0 -1
- package/dist/services/tokens.service.d.ts.map +0 -1
- package/dist/services/tokens.service.js.map +0 -1
- package/dist/services/unisat-indexer.d.ts.map +0 -1
- package/dist/services/unisat-indexer.js.map +0 -1
- package/dist/services/wallet-manager.d.ts.map +0 -1
- package/dist/services/wallet-manager.js.map +0 -1
- package/dist/services/x402.service.d.ts.map +0 -1
- package/dist/services/x402.service.js.map +0 -1
- package/dist/tools/arxiv-research.tools.d.ts.map +0 -1
- package/dist/tools/arxiv-research.tools.js.map +0 -1
- package/dist/tools/bitcoin.tools.d.ts.map +0 -1
- package/dist/tools/bitcoin.tools.js.map +0 -1
- package/dist/tools/bitflow.tools.d.ts.map +0 -1
- package/dist/tools/bitflow.tools.js.map +0 -1
- package/dist/tools/bns.tools.d.ts.map +0 -1
- package/dist/tools/bns.tools.js.map +0 -1
- package/dist/tools/bounty-scanner.tools.d.ts.map +0 -1
- package/dist/tools/bounty-scanner.tools.js.map +0 -1
- package/dist/tools/child-inscription.tools.d.ts.map +0 -1
- package/dist/tools/child-inscription.tools.js.map +0 -1
- package/dist/tools/competition.tools.d.ts.map +0 -1
- package/dist/tools/competition.tools.js.map +0 -1
- package/dist/tools/contract.tools.d.ts.map +0 -1
- package/dist/tools/contract.tools.js.map +0 -1
- package/dist/tools/credentials.tools.d.ts.map +0 -1
- package/dist/tools/credentials.tools.js.map +0 -1
- package/dist/tools/defi.tools.d.ts.map +0 -1
- package/dist/tools/defi.tools.js.map +0 -1
- package/dist/tools/dual-stacking.tools.d.ts.map +0 -1
- package/dist/tools/dual-stacking.tools.js.map +0 -1
- package/dist/tools/earning.tools.d.ts.map +0 -1
- package/dist/tools/earning.tools.js.map +0 -1
- package/dist/tools/endpoint.tools.d.ts.map +0 -1
- package/dist/tools/endpoint.tools.js.map +0 -1
- package/dist/tools/erc8004.tools.d.ts.map +0 -1
- package/dist/tools/erc8004.tools.js.map +0 -1
- package/dist/tools/identity.tools.d.ts.map +0 -1
- package/dist/tools/identity.tools.js.map +0 -1
- package/dist/tools/inbox-x402.tools.d.ts.map +0 -1
- package/dist/tools/inbox-x402.tools.js.map +0 -1
- package/dist/tools/inbox.tools.d.ts.map +0 -1
- package/dist/tools/inbox.tools.js.map +0 -1
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/jingswap.tools.d.ts.map +0 -1
- package/dist/tools/jingswap.tools.js.map +0 -1
- package/dist/tools/lightning.tools.d.ts.map +0 -1
- package/dist/tools/lightning.tools.js.map +0 -1
- package/dist/tools/mempool.tools.d.ts.map +0 -1
- package/dist/tools/mempool.tools.js.map +0 -1
- package/dist/tools/news.tools.d.ts.map +0 -1
- package/dist/tools/news.tools.js.map +0 -1
- package/dist/tools/nft.tools.d.ts.map +0 -1
- package/dist/tools/nft.tools.js.map +0 -1
- package/dist/tools/nonce.tools.d.ts.map +0 -1
- package/dist/tools/nonce.tools.js.map +0 -1
- package/dist/tools/nostr.tools.d.ts.map +0 -1
- package/dist/tools/nostr.tools.js.map +0 -1
- package/dist/tools/openrouter.tools.d.ts.map +0 -1
- package/dist/tools/openrouter.tools.js.map +0 -1
- package/dist/tools/ordinals-marketplace.tools.d.ts.map +0 -1
- package/dist/tools/ordinals-marketplace.tools.js.map +0 -1
- package/dist/tools/ordinals-p2p.tools.d.ts.map +0 -1
- package/dist/tools/ordinals-p2p.tools.js.map +0 -1
- package/dist/tools/ordinals.tools.d.ts.map +0 -1
- package/dist/tools/ordinals.tools.js.map +0 -1
- package/dist/tools/pillar-direct.tools.d.ts.map +0 -1
- package/dist/tools/pillar-direct.tools.js.map +0 -1
- package/dist/tools/pillar.tools.d.ts.map +0 -1
- package/dist/tools/pillar.tools.js.map +0 -1
- package/dist/tools/psbt.helpers.d.ts.map +0 -1
- package/dist/tools/psbt.helpers.js.map +0 -1
- package/dist/tools/psbt.tools.d.ts.map +0 -1
- package/dist/tools/psbt.tools.js.map +0 -1
- package/dist/tools/query.tools.d.ts.map +0 -1
- package/dist/tools/query.tools.js.map +0 -1
- package/dist/tools/relay-diagnostic.tools.d.ts.map +0 -1
- package/dist/tools/relay-diagnostic.tools.js.map +0 -1
- package/dist/tools/reputation.tools.d.ts.map +0 -1
- package/dist/tools/reputation.tools.js.map +0 -1
- package/dist/tools/runes.tools.d.ts.map +0 -1
- package/dist/tools/runes.tools.js.map +0 -1
- package/dist/tools/sbtc.tools.d.ts.map +0 -1
- package/dist/tools/sbtc.tools.js.map +0 -1
- package/dist/tools/scaffold.tools.d.ts.map +0 -1
- package/dist/tools/scaffold.tools.js.map +0 -1
- package/dist/tools/schemas.d.ts.map +0 -1
- package/dist/tools/schemas.js.map +0 -1
- package/dist/tools/settings.tools.d.ts.map +0 -1
- package/dist/tools/settings.tools.js.map +0 -1
- package/dist/tools/signing.tools.d.ts.map +0 -1
- package/dist/tools/signing.tools.js.map +0 -1
- package/dist/tools/skill-mappings.d.ts.map +0 -1
- package/dist/tools/skill-mappings.js.map +0 -1
- package/dist/tools/souldinals.tools.d.ts.map +0 -1
- package/dist/tools/souldinals.tools.js.map +0 -1
- package/dist/tools/stacking-lottery.tools.d.ts.map +0 -1
- package/dist/tools/stacking-lottery.tools.js.map +0 -1
- package/dist/tools/stacking.tools.d.ts.map +0 -1
- package/dist/tools/stacking.tools.js.map +0 -1
- package/dist/tools/stacks-market.tools.d.ts.map +0 -1
- package/dist/tools/stacks-market.tools.js.map +0 -1
- package/dist/tools/styx.tools.d.ts.map +0 -1
- package/dist/tools/styx.tools.js.map +0 -1
- package/dist/tools/taproot-multisig.tools.d.ts.map +0 -1
- package/dist/tools/taproot-multisig.tools.js.map +0 -1
- package/dist/tools/tenero.tools.d.ts.map +0 -1
- package/dist/tools/tenero.tools.js.map +0 -1
- package/dist/tools/tokens.tools.d.ts.map +0 -1
- package/dist/tools/tokens.tools.js.map +0 -1
- package/dist/tools/transfer.tools.d.ts.map +0 -1
- package/dist/tools/transfer.tools.js.map +0 -1
- package/dist/tools/wallet-management.tools.d.ts.map +0 -1
- package/dist/tools/wallet-management.tools.js.map +0 -1
- package/dist/tools/wallet.tools.d.ts.map +0 -1
- package/dist/tools/wallet.tools.js.map +0 -1
- package/dist/tools/yield-dashboard.tools.d.ts.map +0 -1
- package/dist/tools/yield-dashboard.tools.js.map +0 -1
- package/dist/tools/yield-hunter.tools.d.ts.map +0 -1
- package/dist/tools/yield-hunter.tools.js.map +0 -1
- package/dist/transactions/bitcoin-builder.d.ts.map +0 -1
- package/dist/transactions/bitcoin-builder.js.map +0 -1
- package/dist/transactions/builder.d.ts.map +0 -1
- package/dist/transactions/builder.js.map +0 -1
- package/dist/transactions/child-inscription-builder.d.ts.map +0 -1
- package/dist/transactions/child-inscription-builder.js.map +0 -1
- package/dist/transactions/clarity-values.d.ts.map +0 -1
- package/dist/transactions/clarity-values.js.map +0 -1
- package/dist/transactions/inscription-builder.d.ts.map +0 -1
- package/dist/transactions/inscription-builder.js.map +0 -1
- package/dist/transactions/post-conditions.d.ts.map +0 -1
- package/dist/transactions/post-conditions.js.map +0 -1
- package/dist/transactions/rune-transfer-builder.d.ts.map +0 -1
- package/dist/transactions/rune-transfer-builder.js.map +0 -1
- package/dist/transactions/runestone-builder.d.ts.map +0 -1
- package/dist/transactions/runestone-builder.js.map +0 -1
- package/dist/transactions/sponsor-builder.d.ts.map +0 -1
- package/dist/transactions/sponsor-builder.js.map +0 -1
- package/dist/utils/bip322.d.ts.map +0 -1
- package/dist/utils/bip322.js.map +0 -1
- package/dist/utils/bitcoin.d.ts.map +0 -1
- package/dist/utils/bitcoin.js.map +0 -1
- package/dist/utils/encryption.d.ts.map +0 -1
- package/dist/utils/encryption.js.map +0 -1
- package/dist/utils/erc8004-helpers.d.ts.map +0 -1
- package/dist/utils/erc8004-helpers.js.map +0 -1
- package/dist/utils/errors.d.ts.map +0 -1
- package/dist/utils/errors.js.map +0 -1
- package/dist/utils/fee.d.ts.map +0 -1
- package/dist/utils/fee.js.map +0 -1
- package/dist/utils/formatting.d.ts.map +0 -1
- package/dist/utils/formatting.js.map +0 -1
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/l402-protocol.d.ts.map +0 -1
- package/dist/utils/l402-protocol.js.map +0 -1
- package/dist/utils/redact.d.ts.map +0 -1
- package/dist/utils/redact.js.map +0 -1
- package/dist/utils/relay-health.d.ts.map +0 -1
- package/dist/utils/relay-health.js.map +0 -1
- package/dist/utils/storage.d.ts.map +0 -1
- package/dist/utils/storage.js.map +0 -1
- package/dist/utils/tokens.d.ts.map +0 -1
- package/dist/utils/tokens.js.map +0 -1
- package/dist/utils/x402-payment-logging.d.ts.map +0 -1
- package/dist/utils/x402-payment-logging.js.map +0 -1
- package/dist/utils/x402-payment-state.d.ts.map +0 -1
- package/dist/utils/x402-payment-state.js.map +0 -1
- package/dist/utils/x402-protocol.d.ts.map +0 -1
- package/dist/utils/x402-protocol.js.map +0 -1
- package/dist/utils/x402-recovery.d.ts.map +0 -1
- package/dist/utils/x402-recovery.js.map +0 -1
- package/dist/yield-hunter/index.d.ts.map +0 -1
- package/dist/yield-hunter/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -36,13 +36,24 @@ function getClaudeDesktopConfigPath() {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
async function readJsonConfig(filePath) {
|
|
39
|
+
let content;
|
|
40
|
+
try {
|
|
41
|
+
content = await fs.readFile(filePath, "utf8");
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error.code === "ENOENT") {
|
|
45
|
+
return {}; // No config yet - fresh install
|
|
46
|
+
}
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
39
49
|
try {
|
|
40
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
41
50
|
return JSON.parse(content);
|
|
42
51
|
}
|
|
43
52
|
catch {
|
|
44
|
-
//
|
|
45
|
-
|
|
53
|
+
// Never silently replace an unparseable config: it may hold the user's
|
|
54
|
+
// other MCP servers, and "merging" into {} would discard them on write.
|
|
55
|
+
throw new Error(`Existing config at ${filePath} is not valid JSON. ` +
|
|
56
|
+
`Fix or remove it, then re-run the install.`);
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
async function writeJsonConfig(filePath, config) {
|
|
@@ -99,8 +110,10 @@ async function writeCodexToml(configPath, network) {
|
|
|
99
110
|
try {
|
|
100
111
|
existing = await fs.readFile(configPath, "utf8");
|
|
101
112
|
}
|
|
102
|
-
catch {
|
|
103
|
-
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error.code !== "ENOENT") {
|
|
115
|
+
throw error; // Unreadable existing config must not be clobbered
|
|
116
|
+
}
|
|
104
117
|
}
|
|
105
118
|
const preserved = stripCodexSection(existing).replace(/\s+$/, "");
|
|
106
119
|
const block = [
|
|
@@ -39,6 +39,7 @@ async function encrypt(data, password) {
|
|
|
39
39
|
const iv = crypto.randomBytes(12);
|
|
40
40
|
const key = await deriveKey(password, salt);
|
|
41
41
|
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
42
|
+
key.fill(0); // cipher holds its own copy
|
|
42
43
|
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
43
44
|
const authTag = cipher.getAuthTag();
|
|
44
45
|
return {
|
|
@@ -53,6 +54,7 @@ async function encrypt(data, password) {
|
|
|
53
54
|
async function decrypt(encrypted, password) {
|
|
54
55
|
const key = await deriveKey(password, Buffer.from(encrypted.salt, "base64"));
|
|
55
56
|
const decipher = crypto.createDecipheriv("aes-256-gcm", key, Buffer.from(encrypted.iv, "base64"));
|
|
57
|
+
key.fill(0); // decipher holds its own copy
|
|
56
58
|
decipher.setAuthTag(Buffer.from(encrypted.authTag, "base64"));
|
|
57
59
|
const decrypted = Buffer.concat([
|
|
58
60
|
decipher.update(Buffer.from(encrypted.ciphertext, "base64")),
|
|
@@ -67,12 +69,15 @@ function emptyStore() {
|
|
|
67
69
|
async function save() {
|
|
68
70
|
if (!_store || !_password)
|
|
69
71
|
throw new Error("Store not unlocked");
|
|
70
|
-
await fs.mkdir(getStoreDir(), { recursive: true });
|
|
72
|
+
await fs.mkdir(getStoreDir(), { recursive: true, mode: 0o700 });
|
|
71
73
|
const file = {
|
|
72
74
|
version: VERSION,
|
|
73
75
|
encrypted: await encrypt(JSON.stringify(_store), _password),
|
|
74
76
|
};
|
|
75
|
-
|
|
77
|
+
// Owner-only perms + atomic rename, same pattern as the wallet keystores
|
|
78
|
+
const tempFile = getStoreFile() + ".tmp";
|
|
79
|
+
await fs.writeFile(tempFile, JSON.stringify(file, null, 2), { mode: 0o600 });
|
|
80
|
+
await fs.rename(tempFile, getStoreFile());
|
|
76
81
|
}
|
|
77
82
|
async function load(password) {
|
|
78
83
|
const raw = await fs.readFile(getStoreFile(), "utf-8");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { uintCV, contractPrincipalCV, cvToJSON, hexToCV, PostConditionMode, Pc, principalCV, noneCV, someCV, listCV, bufferCV, } from "@stacks/transactions";
|
|
2
2
|
import { AlexSDK, Currency } from "alex-sdk";
|
|
3
|
-
import { getHiroApi } from "./hiro-api.js";
|
|
3
|
+
import { getHiroApi, isNotFoundError } from "./hiro-api.js";
|
|
4
4
|
import { getAlexContracts, getZestContracts, parseContractId, ZEST_ASSETS, ZEST_V2_MARKET, ZEST_V2_MARKET_VAULT, } from "../config/index.js";
|
|
5
5
|
import { callContract } from "../transactions/builder.js";
|
|
6
6
|
// ============================================================================
|
|
@@ -129,8 +129,10 @@ export class AlexDexService {
|
|
|
129
129
|
}
|
|
130
130
|
return null;
|
|
131
131
|
}
|
|
132
|
-
catch {
|
|
133
|
-
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (isNotFoundError(error))
|
|
134
|
+
return null;
|
|
135
|
+
throw error;
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
/**
|
|
@@ -360,8 +362,10 @@ export class ZestProtocolService {
|
|
|
360
362
|
healthFactor: position["health-factor"]?.value,
|
|
361
363
|
};
|
|
362
364
|
}
|
|
363
|
-
catch {
|
|
364
|
-
|
|
365
|
+
catch (error) {
|
|
366
|
+
if (isNotFoundError(error))
|
|
367
|
+
return null;
|
|
368
|
+
throw error;
|
|
365
369
|
}
|
|
366
370
|
}
|
|
367
371
|
/**
|
|
@@ -377,8 +381,10 @@ export class ZestProtocolService {
|
|
|
377
381
|
}
|
|
378
382
|
return cvToJSON(hexToCV(result.result));
|
|
379
383
|
}
|
|
380
|
-
catch {
|
|
381
|
-
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (isNotFoundError(error))
|
|
386
|
+
return null;
|
|
387
|
+
throw error;
|
|
382
388
|
}
|
|
383
389
|
}
|
|
384
390
|
/**
|
|
@@ -411,8 +417,12 @@ export class ZestProtocolService {
|
|
|
411
417
|
}
|
|
412
418
|
}
|
|
413
419
|
}
|
|
414
|
-
catch {
|
|
415
|
-
//
|
|
420
|
+
catch (error) {
|
|
421
|
+
// Only fall back to the shares lower-bound estimate when the vault
|
|
422
|
+
// function is missing — network/server failures must surface, or the
|
|
423
|
+
// post-conditions built from this value would be silently wrong.
|
|
424
|
+
if (!isNotFoundError(error))
|
|
425
|
+
throw error;
|
|
416
426
|
}
|
|
417
427
|
return shares;
|
|
418
428
|
}
|
|
@@ -431,8 +441,10 @@ export class ZestProtocolService {
|
|
|
431
441
|
}
|
|
432
442
|
}
|
|
433
443
|
}
|
|
434
|
-
catch {
|
|
435
|
-
//
|
|
444
|
+
catch (error) {
|
|
445
|
+
// Same rationale as getUnderlyingFromShares: estimate only on 404.
|
|
446
|
+
if (!isNotFoundError(error))
|
|
447
|
+
throw error;
|
|
436
448
|
}
|
|
437
449
|
return amount;
|
|
438
450
|
}
|
|
@@ -7,6 +7,16 @@ export declare class HiroApiRateLimitError extends Error {
|
|
|
7
7
|
readonly retryAfterSeconds: number;
|
|
8
8
|
constructor(message: string, retryAfterSeconds: number);
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* HTTP error from the Hiro / BNS V2 APIs, carrying the status code so
|
|
12
|
+
* callers can treat 404 as "not found" without swallowing other failures.
|
|
13
|
+
*/
|
|
14
|
+
export declare class HiroApiError extends Error {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
constructor(message: string, status: number);
|
|
17
|
+
}
|
|
18
|
+
/** True when the error is an expected "resource does not exist" response. */
|
|
19
|
+
export declare function isNotFoundError(error: unknown): boolean;
|
|
10
20
|
export interface AccountInfo {
|
|
11
21
|
balance: string;
|
|
12
22
|
locked: string;
|
|
@@ -16,6 +16,22 @@ export class HiroApiRateLimitError extends Error {
|
|
|
16
16
|
this.name = "HiroApiRateLimitError";
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* HTTP error from the Hiro / BNS V2 APIs, carrying the status code so
|
|
21
|
+
* callers can treat 404 as "not found" without swallowing other failures.
|
|
22
|
+
*/
|
|
23
|
+
export class HiroApiError extends Error {
|
|
24
|
+
status;
|
|
25
|
+
constructor(message, status) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.status = status;
|
|
28
|
+
this.name = "HiroApiError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** True when the error is an expected "resource does not exist" response. */
|
|
32
|
+
export function isNotFoundError(error) {
|
|
33
|
+
return error instanceof HiroApiError && error.status === 404;
|
|
34
|
+
}
|
|
19
35
|
// ============================================================================
|
|
20
36
|
// Hiro API Service
|
|
21
37
|
// ============================================================================
|
|
@@ -51,7 +67,7 @@ export class HiroApiService {
|
|
|
51
67
|
throw new HiroApiRateLimitError(`Hiro API rate limit exceeded. Retry after ${retryAfterSeconds}s`, retryAfterSeconds);
|
|
52
68
|
}
|
|
53
69
|
const errorText = await response.text();
|
|
54
|
-
throw new
|
|
70
|
+
throw new HiroApiError(`Hiro API error (${response.status}): ${errorText}`, response.status);
|
|
55
71
|
}
|
|
56
72
|
return response.json();
|
|
57
73
|
}
|
|
@@ -123,8 +139,10 @@ export class HiroApiService {
|
|
|
123
139
|
try {
|
|
124
140
|
return await this.fetch(`/metadata/v1/ft/${contractId}`);
|
|
125
141
|
}
|
|
126
|
-
catch {
|
|
127
|
-
|
|
142
|
+
catch (error) {
|
|
143
|
+
if (isNotFoundError(error))
|
|
144
|
+
return null;
|
|
145
|
+
throw error;
|
|
128
146
|
}
|
|
129
147
|
}
|
|
130
148
|
async getTokenHolders(contractId, options) {
|
|
@@ -329,8 +347,10 @@ export class HiroApiService {
|
|
|
329
347
|
const info = await this.getBnsNameInfo(name);
|
|
330
348
|
return info.address;
|
|
331
349
|
}
|
|
332
|
-
catch {
|
|
333
|
-
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (isNotFoundError(error))
|
|
352
|
+
return null;
|
|
353
|
+
throw error;
|
|
334
354
|
}
|
|
335
355
|
}
|
|
336
356
|
// ==========================================================================
|
|
@@ -385,7 +405,7 @@ export class BnsV2ApiService {
|
|
|
385
405
|
});
|
|
386
406
|
if (!response.ok) {
|
|
387
407
|
const errorText = await response.text();
|
|
388
|
-
throw new
|
|
408
|
+
throw new HiroApiError(`BNS V2 API error (${response.status}): ${errorText}`, response.status);
|
|
389
409
|
}
|
|
390
410
|
return response.json();
|
|
391
411
|
}
|
|
@@ -431,8 +451,10 @@ export class BnsV2ApiService {
|
|
|
431
451
|
}
|
|
432
452
|
return null;
|
|
433
453
|
}
|
|
434
|
-
catch {
|
|
435
|
-
|
|
454
|
+
catch (error) {
|
|
455
|
+
if (isNotFoundError(error))
|
|
456
|
+
return null;
|
|
457
|
+
throw error;
|
|
436
458
|
}
|
|
437
459
|
}
|
|
438
460
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { uintCV, principalCV } from "@stacks/transactions";
|
|
2
|
-
import { getHiroApi } from "./hiro-api.js";
|
|
2
|
+
import { getHiroApi, HiroApiError } from "./hiro-api.js";
|
|
3
3
|
import { parseContractId } from "../config/index.js";
|
|
4
4
|
import { callContract } from "../transactions/builder.js";
|
|
5
5
|
import { createNftSendPostCondition } from "../transactions/post-conditions.js";
|
|
@@ -51,8 +51,13 @@ export class NftService {
|
|
|
51
51
|
}
|
|
52
52
|
return null;
|
|
53
53
|
}
|
|
54
|
-
catch {
|
|
55
|
-
|
|
54
|
+
catch (error) {
|
|
55
|
+
// 4xx means the contract doesn't implement get-owner (or rejects the
|
|
56
|
+
// call) — that is "no owner". Network/server failures must surface.
|
|
57
|
+
if (error instanceof HiroApiError && error.status < 500) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
@@ -75,8 +80,12 @@ export class NftService {
|
|
|
75
80
|
}
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
|
-
catch {
|
|
79
|
-
//
|
|
83
|
+
catch (error) {
|
|
84
|
+
// 4xx means the contract doesn't implement get-last-token-id —
|
|
85
|
+
// supply is simply unknown. Network/server failures must surface.
|
|
86
|
+
if (!(error instanceof HiroApiError && error.status < 500)) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
80
89
|
}
|
|
81
90
|
return {
|
|
82
91
|
contractId,
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP error from the Pillar API, carrying the status code so callers can
|
|
3
|
+
* treat 4xx ("no such wallet / no position") differently from infra failures.
|
|
4
|
+
*/
|
|
5
|
+
export declare class PillarApiError extends Error {
|
|
6
|
+
readonly status: number;
|
|
7
|
+
constructor(message: string, status: number);
|
|
8
|
+
}
|
|
1
9
|
declare class PillarApiService {
|
|
2
10
|
private baseUrl;
|
|
3
11
|
private apiKey;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import { PILLAR_API_URL, PILLAR_API_KEY } from "../config/pillar.js";
|
|
2
|
+
/**
|
|
3
|
+
* HTTP error from the Pillar API, carrying the status code so callers can
|
|
4
|
+
* treat 4xx ("no such wallet / no position") differently from infra failures.
|
|
5
|
+
*/
|
|
6
|
+
export class PillarApiError extends Error {
|
|
7
|
+
status;
|
|
8
|
+
constructor(message, status) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = "PillarApiError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
2
14
|
class PillarApiService {
|
|
3
15
|
baseUrl;
|
|
4
16
|
apiKey;
|
|
@@ -32,7 +44,7 @@ class PillarApiService {
|
|
|
32
44
|
});
|
|
33
45
|
if (!response.ok) {
|
|
34
46
|
const errorText = await response.text();
|
|
35
|
-
throw new
|
|
47
|
+
throw new PillarApiError(`Pillar API error (${response.status}): ${errorText}`, response.status);
|
|
36
48
|
}
|
|
37
49
|
return response.json();
|
|
38
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, PostConditionMode, } from "@stacks/transactions";
|
|
2
|
+
import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, Pc, PostConditionMode, } from "@stacks/transactions";
|
|
3
3
|
import { decodePaymentRequired, encodePaymentPayload, generatePaymentId, buildPaymentIdentifierExtension, X402_HEADERS, } from "../utils/x402-protocol.js";
|
|
4
4
|
import { generateWallet, getStxAddress } from "@stacks/wallet-sdk";
|
|
5
5
|
import { NETWORK, API_URL, getStacksNetwork } from "../config/networks.js";
|
|
@@ -11,6 +11,7 @@ import { getHiroApi } from "./hiro-api.js";
|
|
|
11
11
|
import { createHash } from "crypto";
|
|
12
12
|
import { InsufficientBalanceError } from "../utils/errors.js";
|
|
13
13
|
import { getContracts, parseContractId } from "../config/contracts.js";
|
|
14
|
+
import { resolveDefaultFee } from "../utils/fee.js";
|
|
14
15
|
import { classifyCanonicalPaymentStatus, formatCanonicalPaymentStatus, resolveCanonicalPaymentStatus, } from "../utils/x402-payment-state.js";
|
|
15
16
|
import { extractPaymentIdFromPaymentSignature } from "../utils/x402-recovery.js";
|
|
16
17
|
import { emitCanonicalPaymentDecisionLogs, emitCanonicalPaymentPollLogs, emitPaymentLog, } from "../utils/x402-payment-logging.js";
|
|
@@ -42,6 +43,16 @@ function parseSatsCap(envName, fallback) {
|
|
|
42
43
|
return parsed;
|
|
43
44
|
}
|
|
44
45
|
const L402_MAX_SATS_PER_INVOICE = parseSatsCap("L402_MAX_SATS_PER_INVOICE", DEFAULT_L402_MAX_SATS);
|
|
46
|
+
/**
|
|
47
|
+
* Per-payment caps for the Stacks x402 interceptor, mirroring the L402 cap
|
|
48
|
+
* above: a malicious endpoint can demand an arbitrary amount in its 402
|
|
49
|
+
* response and an autoApprove'd call would pay it, bounded only by wallet
|
|
50
|
+
* balance. Known registry endpoints cost at most 0.02 STX / 100 sats, so the
|
|
51
|
+
* defaults leave generous headroom. Overridable via env vars.
|
|
52
|
+
*/
|
|
53
|
+
const X402_MAX_USTX_PER_PAYMENT = parseSatsCap("X402_MAX_USTX_PER_PAYMENT", 1_000_000 // 1 STX
|
|
54
|
+
);
|
|
55
|
+
const X402_MAX_SATS_PER_PAYMENT = parseSatsCap("X402_MAX_SATS_PER_PAYMENT", 10_000);
|
|
45
56
|
// Track payment attempts per client instance (auto-cleanup via WeakMap)
|
|
46
57
|
const paymentAttempts = new WeakMap();
|
|
47
58
|
// Track 429 retry counts per client instance
|
|
@@ -457,6 +468,16 @@ export async function createApiClient(baseUrl, options) {
|
|
|
457
468
|
const networks = paymentRequired.accepts.map((a) => a.network).join(", ");
|
|
458
469
|
return Promise.reject(new Error(`No compatible Stacks payment option found. Available networks: ${networks}`));
|
|
459
470
|
}
|
|
471
|
+
const tokenType = detectTokenType(selectedOption.asset);
|
|
472
|
+
const amount = BigInt(selectedOption.amount);
|
|
473
|
+
const isSbtc = tokenType === "sBTC";
|
|
474
|
+
const cap = isSbtc ? X402_MAX_SATS_PER_PAYMENT : X402_MAX_USTX_PER_PAYMENT;
|
|
475
|
+
const unit = isSbtc ? "sats" : "uSTX";
|
|
476
|
+
if (amount > BigInt(Math.floor(cap))) {
|
|
477
|
+
return Promise.reject(new Error(`x402 payment of ${amount} ${unit} exceeds the per-payment cap of ${cap} ${unit}. ` +
|
|
478
|
+
`If this cost is expected, raise the cap via the ` +
|
|
479
|
+
`${isSbtc ? "X402_MAX_SATS_PER_PAYMENT" : "X402_MAX_USTX_PER_PAYMENT"} env var.`));
|
|
480
|
+
}
|
|
460
481
|
// Lazy-load account on first 402 — free endpoints never reach here
|
|
461
482
|
const acct = await ensureAccount();
|
|
462
483
|
// Verify the payment network matches our configured network
|
|
@@ -476,9 +497,11 @@ export async function createApiClient(baseUrl, options) {
|
|
|
476
497
|
account: acct,
|
|
477
498
|
});
|
|
478
499
|
}
|
|
479
|
-
// Build a sponsored signed transaction
|
|
480
|
-
|
|
481
|
-
|
|
500
|
+
// Build a non-sponsored signed transaction: the sender pays its own
|
|
501
|
+
// STX gas (fee + nonce auto-estimated by @stacks), and the x402
|
|
502
|
+
// facilitator simply verifies and broadcasts it. No relay sponsorship
|
|
503
|
+
// — this is the same tx shape the x402-stacks lib produces, and the
|
|
504
|
+
// balance check in onBeforePayment already accounts for the gas.
|
|
482
505
|
const networkName = getStacksNetwork(acct.network);
|
|
483
506
|
let transaction;
|
|
484
507
|
if (tokenType === "sBTC") {
|
|
@@ -496,9 +519,18 @@ export async function createApiClient(baseUrl, options) {
|
|
|
496
519
|
],
|
|
497
520
|
senderKey: acct.privateKey,
|
|
498
521
|
network: networkName,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
522
|
+
// The payment amount is known at build time, so pin it: a
|
|
523
|
+
// compromised token contract must not be able to pull more.
|
|
524
|
+
postConditionMode: PostConditionMode.Deny,
|
|
525
|
+
postConditions: [
|
|
526
|
+
Pc.principal(acct.address)
|
|
527
|
+
.willSendEq(amount)
|
|
528
|
+
.ft(contracts.SBTC_TOKEN, "sbtc-token"),
|
|
529
|
+
],
|
|
530
|
+
// Clamped medium fee — do NOT let @stacks auto-estimate, since
|
|
531
|
+
// Hiro's contract_call high_priority tier is polluted by outliers
|
|
532
|
+
// (observed >2000 STX). resolveDefaultFee caps it at 0.05 STX.
|
|
533
|
+
fee: await resolveDefaultFee(acct.network, "contract_call"),
|
|
502
534
|
});
|
|
503
535
|
}
|
|
504
536
|
else {
|
|
@@ -508,8 +540,7 @@ export async function createApiClient(baseUrl, options) {
|
|
|
508
540
|
senderKey: acct.privateKey,
|
|
509
541
|
network: networkName,
|
|
510
542
|
memo: "",
|
|
511
|
-
|
|
512
|
-
fee: 0n,
|
|
543
|
+
fee: await resolveDefaultFee(acct.network, "token_transfer"),
|
|
513
544
|
});
|
|
514
545
|
}
|
|
515
546
|
const txHex = "0x" + transaction.serialize();
|
|
@@ -716,8 +747,9 @@ export async function checkSufficientBalance(account, amount, asset, sponsored =
|
|
|
716
747
|
const hiroApiForSbtc = getHiroApi(account.network);
|
|
717
748
|
const stxInfoForSbtc = await hiroApiForSbtc.getStxBalance(account.address);
|
|
718
749
|
const stxBalanceForSbtc = BigInt(stxInfoForSbtc.balance);
|
|
719
|
-
|
|
720
|
-
|
|
750
|
+
// Same clamped fee the interceptor actually sets on the sBTC contract
|
|
751
|
+
// call — so this check is exact, not a high-priority over-estimate.
|
|
752
|
+
const estimatedSbtcFee = await resolveDefaultFee(account.network, "contract_call");
|
|
721
753
|
if (stxBalanceForSbtc < estimatedSbtcFee) {
|
|
722
754
|
const stxShortfall = estimatedSbtcFee - stxBalanceForSbtc;
|
|
723
755
|
throw new InsufficientBalanceError(`Insufficient STX balance to cover sBTC transfer fee: need ${formatStx(estimatedSbtcFee.toString())} estimated fee, ` +
|
|
@@ -733,8 +765,8 @@ export async function checkSufficientBalance(account, amount, asset, sponsored =
|
|
|
733
765
|
const balance = BigInt(balanceInfo.balance);
|
|
734
766
|
let totalRequired = requiredAmount;
|
|
735
767
|
if (!sponsored) {
|
|
736
|
-
|
|
737
|
-
const estimatedFee =
|
|
768
|
+
// Same clamped fee the interceptor sets on the STX transfer.
|
|
769
|
+
const estimatedFee = await resolveDefaultFee(account.network, "token_transfer");
|
|
738
770
|
totalRequired = requiredAmount + estimatedFee;
|
|
739
771
|
}
|
|
740
772
|
if (balance >= totalRequired)
|
|
@@ -33,6 +33,19 @@ async function getBtcAddress(providedAddress) {
|
|
|
33
33
|
throw new Error("No Bitcoin address provided and wallet is not unlocked. " +
|
|
34
34
|
"Either provide an address or unlock your wallet first.");
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Reject recipient addresses that belong to the other Bitcoin network before
|
|
38
|
+
* any UTXO fetching. The tx builder (@scure/btc-signer) rejects these too, but
|
|
39
|
+
* late and with a cryptic bech32/version-byte error.
|
|
40
|
+
*/
|
|
41
|
+
function validateRecipientNetwork(address) {
|
|
42
|
+
const prefixes = NETWORK === "mainnet" ? ["bc1", "1", "3"] : ["tb1", "m", "n", "2"];
|
|
43
|
+
if (!prefixes.some((p) => address.startsWith(p))) {
|
|
44
|
+
throw new Error(`Recipient "${address}" is not a ${NETWORK} address. ` +
|
|
45
|
+
`Expected a prefix of ${prefixes.join(", ")} — check that the address ` +
|
|
46
|
+
`matches the configured NETWORK.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
/**
|
|
37
50
|
* Format satoshis as BTC string
|
|
38
51
|
*/
|
|
@@ -231,6 +244,7 @@ export function registerBitcoinTools(server) {
|
|
|
231
244
|
},
|
|
232
245
|
}, async ({ recipient, amount, feeRate, includeOrdinals }) => {
|
|
233
246
|
try {
|
|
247
|
+
validateRecipientNetwork(recipient);
|
|
234
248
|
// Get wallet account (requires unlocked wallet)
|
|
235
249
|
const walletManager = getWalletManager();
|
|
236
250
|
const account = walletManager.getActiveAccount();
|
|
@@ -276,7 +276,9 @@ For aibtc.com inbox messages, use send_inbox_message_direct instead — it signs
|
|
|
276
276
|
const api = await createApiClient(parsed.baseUrl, {
|
|
277
277
|
toolName: "execute_x402_endpoint",
|
|
278
278
|
onBeforePayment: async (requirements) => {
|
|
279
|
-
|
|
279
|
+
// Non-sponsored: sender pays its own gas, so validate STX for the
|
|
280
|
+
// fee too (sponsored=false is the default, passed explicitly here).
|
|
281
|
+
await checkSufficientBalance(requirements.account, requirements.amount, requirements.asset, false);
|
|
280
282
|
},
|
|
281
283
|
});
|
|
282
284
|
const response = await api.request({ method, url: parsed.requestPath, params, data });
|
|
@@ -40,21 +40,18 @@ async function getNextNonce(address) {
|
|
|
40
40
|
const hiroApi = getHiroApi(NETWORK);
|
|
41
41
|
const accountInfo = await hiroApi.getAccountInfo(address);
|
|
42
42
|
const confirmedNonce = accountInfo.nonce;
|
|
43
|
+
// The mempool scan is what makes the nonce safe against pending txs —
|
|
44
|
+
// silently skipping it on failure risks a nonce collision, so let it fail.
|
|
43
45
|
let highestMempoolNonce = -1;
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
highestMempoolNonce = tx.nonce;
|
|
52
|
-
}
|
|
46
|
+
const mempool = await hiroApi.getMempoolTransactions({
|
|
47
|
+
sender_address: address,
|
|
48
|
+
limit: 50,
|
|
49
|
+
});
|
|
50
|
+
for (const tx of mempool.results) {
|
|
51
|
+
if (tx.nonce > highestMempoolNonce) {
|
|
52
|
+
highestMempoolNonce = tx.nonce;
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
catch {
|
|
56
|
-
// Non-fatal: fall back to confirmed nonce only
|
|
57
|
-
}
|
|
58
55
|
const chainNext = Math.max(confirmedNonce, highestMempoolNonce + 1);
|
|
59
56
|
// 3. Reconcile tracker with chain state
|
|
60
57
|
await reconcileWithChain(address, chainNext);
|
package/dist/tools/news.tools.js
CHANGED
|
@@ -56,21 +56,18 @@ async function getNextNonce(address) {
|
|
|
56
56
|
const hiroApi = getHiroApi(NETWORK);
|
|
57
57
|
const accountInfo = await hiroApi.getAccountInfo(address);
|
|
58
58
|
const confirmedNonce = accountInfo.nonce;
|
|
59
|
+
// The mempool scan is what makes the nonce safe against pending txs —
|
|
60
|
+
// silently skipping it on failure risks a nonce collision, so let it fail.
|
|
59
61
|
let highestMempoolNonce = -1;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
highestMempoolNonce = tx.nonce;
|
|
68
|
-
}
|
|
62
|
+
const mempool = await hiroApi.getMempoolTransactions({
|
|
63
|
+
sender_address: address,
|
|
64
|
+
limit: 50,
|
|
65
|
+
});
|
|
66
|
+
for (const tx of mempool.results) {
|
|
67
|
+
if (tx.nonce > highestMempoolNonce) {
|
|
68
|
+
highestMempoolNonce = tx.nonce;
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
-
catch {
|
|
72
|
-
// Non-fatal
|
|
73
|
-
}
|
|
74
71
|
const chainNext = Math.max(confirmedNonce, highestMempoolNonce + 1);
|
|
75
72
|
await reconcileWithChain(address, chainNext);
|
|
76
73
|
return Math.max(chainNext, localNext ?? 0);
|
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import crypto from "crypto";
|
|
3
3
|
import { tupleCV, stringAsciiCV, uintCV, principalCV, noneCV, trueCV, falseCV, } from "@stacks/transactions";
|
|
4
4
|
import { getSigningKeyService, generateAuthId, } from "../services/signing-key.service.js";
|
|
5
|
-
import { getPillarApi } from "../services/pillar-api.service.js";
|
|
5
|
+
import { getPillarApi, PillarApiError } from "../services/pillar-api.service.js";
|
|
6
6
|
import { getHiroApi } from "../services/hiro-api.js";
|
|
7
7
|
import { NETWORK } from "../config/networks.js";
|
|
8
8
|
import { PILLAR_API_KEY } from "../config/pillar.js";
|
|
@@ -497,8 +497,12 @@ export function registerPillarDirectTools(server) {
|
|
|
497
497
|
const walletInfo = await api.get(`/api/smart-wallet/${walletName}`);
|
|
498
498
|
walletStatus = walletInfo.data?.status || null;
|
|
499
499
|
}
|
|
500
|
-
catch {
|
|
501
|
-
//
|
|
500
|
+
catch (error) {
|
|
501
|
+
// 404 = wallet not registered in backend. Anything else (network,
|
|
502
|
+
// 5xx) must surface, or an initialized wallet gets misreported.
|
|
503
|
+
if (!(error instanceof PillarApiError && error.status === 404)) {
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
502
506
|
}
|
|
503
507
|
if (!walletStatus || walletStatus === "pending_init") {
|
|
504
508
|
return createJsonResponse({
|
|
@@ -554,8 +558,11 @@ export function registerPillarDirectTools(server) {
|
|
|
554
558
|
});
|
|
555
559
|
position = unwindQuote.data;
|
|
556
560
|
}
|
|
557
|
-
catch {
|
|
558
|
-
//
|
|
561
|
+
catch (error) {
|
|
562
|
+
// 4xx = fresh wallet with no position. Infra failures must surface.
|
|
563
|
+
if (!(error instanceof PillarApiError && error.status < 500)) {
|
|
564
|
+
throw error;
|
|
565
|
+
}
|
|
559
566
|
}
|
|
560
567
|
return createJsonResponse({
|
|
561
568
|
success: true,
|
|
@@ -1146,21 +1153,16 @@ export function registerPillarDirectTools(server) {
|
|
|
1146
1153
|
const frac = (micro % BigInt(1_000_000)).toString().padStart(6, "0");
|
|
1147
1154
|
return `${whole}.${frac} STX`;
|
|
1148
1155
|
};
|
|
1149
|
-
// Fetch PoX cycle info
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
catch {
|
|
1162
|
-
// PoX info fetch failed, continue without it
|
|
1163
|
-
}
|
|
1156
|
+
// Fetch PoX cycle info — a global endpoint with no "not found" case,
|
|
1157
|
+
// so any failure is infrastructure and should surface.
|
|
1158
|
+
const pox = await hiro.getPoxInfo();
|
|
1159
|
+
const poxInfo = {
|
|
1160
|
+
currentCycleId: pox.current_cycle.id,
|
|
1161
|
+
nextCycleId: pox.next_cycle.id,
|
|
1162
|
+
blocksUntilNextCycle: pox.next_cycle.blocks_until_reward_phase,
|
|
1163
|
+
minAmountUstx: pox.min_amount_ustx,
|
|
1164
|
+
isPoxActive: pox.current_cycle.is_pox_active,
|
|
1165
|
+
};
|
|
1164
1166
|
// Check enrollment status via backend
|
|
1165
1167
|
const api = getPillarApi();
|
|
1166
1168
|
const contractParts = session.smartWallet.split(".");
|
|
@@ -1175,8 +1177,13 @@ export function registerPillarDirectTools(server) {
|
|
|
1175
1177
|
};
|
|
1176
1178
|
}
|
|
1177
1179
|
}
|
|
1178
|
-
catch {
|
|
1179
|
-
//
|
|
1180
|
+
catch (error) {
|
|
1181
|
+
// 404 = wallet not registered, so not enrolled. Other failures
|
|
1182
|
+
// must surface — defaulting to enrolled:false on a backend outage
|
|
1183
|
+
// misreports an enrolled wallet and could prompt re-enrollment.
|
|
1184
|
+
if (!(error instanceof PillarApiError && error.status === 404)) {
|
|
1185
|
+
throw error;
|
|
1186
|
+
}
|
|
1180
1187
|
}
|
|
1181
1188
|
const isStacking = lockedMicro > BigInt(0);
|
|
1182
1189
|
return createJsonResponse({
|
package/dist/tools/sbtc.tools.js
CHANGED
|
@@ -283,16 +283,9 @@ Signers later process the request and send BTC on L1.`,
|
|
|
283
283
|
description: "Get information about how to deposit BTC to receive sBTC.",
|
|
284
284
|
}, async () => {
|
|
285
285
|
try {
|
|
286
|
-
//
|
|
286
|
+
// getActiveAccount returns null when locked - generic instructions then
|
|
287
287
|
const walletManager = getWalletManager();
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
account = walletManager.getActiveAccount();
|
|
291
|
-
}
|
|
292
|
-
catch {
|
|
293
|
-
// Wallet not unlocked - return generic instructions
|
|
294
|
-
account = null;
|
|
295
|
-
}
|
|
288
|
+
const account = walletManager.getActiveAccount();
|
|
296
289
|
// If wallet is unlocked and has Taproot keys, generate real deposit address
|
|
297
290
|
if (account?.taprootPublicKey) {
|
|
298
291
|
const depositService = getSbtcDepositService(NETWORK);
|