@aibtc/mcp-server 1.70.0 → 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 +16 -10
- package/dist/bridge/index.js +2 -2
- package/dist/config/contracts.d.ts +8 -8
- package/dist/config/contracts.js +3 -3
- 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/defi.tools.js +1 -1
- package/dist/tools/earning.tools.js +3 -3
- package/dist/tools/endpoint.tools.js +85 -38
- package/dist/tools/index.js +7 -1
- package/dist/tools/news.tools.d.ts +5 -31
- package/dist/tools/news.tools.js +96 -1389
- package/dist/tools/pillar-direct.tools.js +23 -2
- package/dist/tools/sbtc.tools.js +2 -21
- package/dist/tools/skill-mappings.js +4 -7
- package/dist/tools/stacking.tools.js +352 -44
- package/dist/tools/yield-dashboard.tools.js +13 -22
- package/dist/utils/bip322.d.ts +2 -3
- package/dist/utils/bip322.js +2 -3
- package/dist/utils/bitcoin.d.ts +8 -0
- package/dist/utils/bitcoin.js +24 -0
- package/dist/utils/erc8004-helpers.d.ts +1 -1
- package/dist/utils/erc8004-helpers.js +1 -1
- package/package.json +6 -6
- package/skill/SKILL.md +3 -3
- package/skill/references/genesis-lifecycle.md +17 -32
- package/skill/references/pillar-wallet.md +1 -1
- package/skill/references/stacks-defi.md +1 -1
- package/dist/tools/erc8004.tools.d.ts +0 -21
- package/dist/tools/erc8004.tools.js +0 -329
|
@@ -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",
|
|
@@ -225,8 +229,6 @@ export const TOOL_SKILL_MAP = {
|
|
|
225
229
|
taproot_verify_cosig: "ordinals-p2p",
|
|
226
230
|
taproot_multisig_guide: "ordinals-p2p",
|
|
227
231
|
// identity skill — ERC-8004 on-chain agent identity
|
|
228
|
-
register_identity: "identity",
|
|
229
|
-
get_identity: "identity",
|
|
230
232
|
identity_get_last_id: "identity",
|
|
231
233
|
identity_get: "identity",
|
|
232
234
|
identity_get_metadata: "identity",
|
|
@@ -238,12 +240,7 @@ export const TOOL_SKILL_MAP = {
|
|
|
238
240
|
identity_unset_wallet: "identity",
|
|
239
241
|
identity_transfer: "identity",
|
|
240
242
|
// reputation skill — ERC-8004 on-chain agent reputation
|
|
241
|
-
give_feedback: "reputation",
|
|
242
|
-
get_reputation: "reputation",
|
|
243
243
|
// validation skill — ERC-8004 on-chain agent validation
|
|
244
|
-
request_validation: "validation",
|
|
245
|
-
get_validation_status: "validation",
|
|
246
|
-
get_validation_summary: "validation",
|
|
247
244
|
// stackspot skill — stacking lottery pots
|
|
248
245
|
stackspot_list_pots: "stackspot",
|
|
249
246
|
stackspot_get_pot_state: "stackspot",
|
|
@@ -1,107 +1,415 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { getAccount, getWalletAddress, NETWORK } from "../services/x402.service.js";
|
|
3
|
-
import { getStackingService } from "../services/stacking.service.js";
|
|
3
|
+
import { MAX_STAKE_CYCLES, buildPayoutCalldata, getStackingService, } from "../services/stacking.service.js";
|
|
4
4
|
import { getExplorerTxUrl } from "../config/networks.js";
|
|
5
5
|
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
6
|
+
import { btcAddressToPoxAddr } from "../utils/bitcoin.js";
|
|
7
|
+
const POX5_NOTE = "PoX-5: every stake names a signer manager contract; rewards are paid in sBTC through that manager.";
|
|
8
|
+
function stx(ustx) {
|
|
9
|
+
const whole = ustx / 1000000n;
|
|
10
|
+
const frac = (ustx % 1000000n).toString().padStart(6, "0");
|
|
11
|
+
return `${whole}.${frac} STX`;
|
|
12
|
+
}
|
|
13
|
+
function poxSummary(pox) {
|
|
14
|
+
return {
|
|
15
|
+
contract: pox.contractId,
|
|
16
|
+
burnHeight: pox.burnHeight,
|
|
17
|
+
rewardCycle: pox.rewardCycle,
|
|
18
|
+
nextCycleStartHeight: pox.nextCycleStartHeight,
|
|
19
|
+
preparePhaseStartHeight: pox.preparePhaseStartHeight,
|
|
20
|
+
inPreparePhase: pox.inPreparePhase,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function stakerSummary(info) {
|
|
24
|
+
return {
|
|
25
|
+
signerManager: info.signerManager,
|
|
26
|
+
amountUstx: info.amountUstx.toString(),
|
|
27
|
+
amount: stx(info.amountUstx),
|
|
28
|
+
firstRewardCycle: info.firstRewardCycle,
|
|
29
|
+
numCycles: info.numCycles,
|
|
30
|
+
unlockCycle: info.unlockCycle,
|
|
31
|
+
unlockBurnHeight: info.unlockBurnHeight,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const payoutInputs = {
|
|
35
|
+
btcRewardAddress: z
|
|
36
|
+
.string()
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Optional Bitcoin address to receive rewards. Encoded as `{ pox-addr, max-fee }` signer calldata, " +
|
|
39
|
+
"the shape reference signer managers (e.g. Xverse, Fast Pool) use to pay rewards to BTC via an " +
|
|
40
|
+
"sBTC withdrawal. Some managers require it (PlanBetter), some ignore it (native-pool). " +
|
|
41
|
+
"Omit to receive sBTC where the manager supports that."),
|
|
42
|
+
maxWithdrawalFeeSats: z
|
|
43
|
+
.number()
|
|
44
|
+
.int()
|
|
45
|
+
.nonnegative()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("Max sBTC withdrawal fee (sats) per payout when btcRewardAddress is set. Default 3000."),
|
|
48
|
+
signerCalldataHex: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Advanced: raw signer calldata (hex, max 500 bytes) for managers with a custom format. " +
|
|
52
|
+
"Cannot be combined with btcRewardAddress."),
|
|
53
|
+
};
|
|
54
|
+
function resolveCalldata(input) {
|
|
55
|
+
if (input.btcRewardAddress && input.signerCalldataHex) {
|
|
56
|
+
throw new Error("Pass either btcRewardAddress or signerCalldataHex, not both.");
|
|
57
|
+
}
|
|
58
|
+
if (input.btcRewardAddress) {
|
|
59
|
+
return buildPayoutCalldata(btcAddressToPoxAddr(input.btcRewardAddress, NETWORK), BigInt(input.maxWithdrawalFeeSats ?? 3000));
|
|
60
|
+
}
|
|
61
|
+
if (input.signerCalldataHex) {
|
|
62
|
+
const hex = input.signerCalldataHex.replace(/^0x/, "");
|
|
63
|
+
if (!/^([0-9a-fA-F]{2})*$/.test(hex)) {
|
|
64
|
+
throw new Error("signerCalldataHex must be an even-length hex string.");
|
|
65
|
+
}
|
|
66
|
+
return Uint8Array.from(Buffer.from(hex, "hex"));
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
6
70
|
export function registerStackingTools(server) {
|
|
7
|
-
//
|
|
71
|
+
// --- get_pox_info ---
|
|
8
72
|
server.registerTool("get_pox_info", {
|
|
9
|
-
description: "
|
|
73
|
+
description: "Current PoX-5 state: reward cycle, burn height, next cycle start, and whether the prepare " +
|
|
74
|
+
"phase is active (stake, extend and unstake are refused during it).",
|
|
10
75
|
}, async () => {
|
|
11
76
|
try {
|
|
12
|
-
const
|
|
13
|
-
const poxInfo = await stackingService.getPoxInfo();
|
|
77
|
+
const pox = await getStackingService(NETWORK).getPoxState();
|
|
14
78
|
return createJsonResponse({
|
|
15
79
|
network: NETWORK,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
80
|
+
...poxSummary(pox),
|
|
81
|
+
firstBurnHeight: pox.firstBurnHeight,
|
|
82
|
+
rewardCycleLength: pox.rewardCycleLength,
|
|
83
|
+
prepareCycleLength: pox.prepareCycleLength,
|
|
84
|
+
signerSetMinUstx: pox.signerSetMinUstx.toString(),
|
|
85
|
+
totalLiquidSupplyUstx: pox.totalLiquidSupplyUstx.toString(),
|
|
86
|
+
note: POX5_NOTE,
|
|
23
87
|
});
|
|
24
88
|
}
|
|
25
89
|
catch (error) {
|
|
26
90
|
return createErrorResponse(error);
|
|
27
91
|
}
|
|
28
92
|
});
|
|
29
|
-
//
|
|
93
|
+
// --- get_stacking_status ---
|
|
30
94
|
server.registerTool("get_stacking_status", {
|
|
31
|
-
description: "
|
|
95
|
+
description: "PoX-5 staking status for an address: locked amount, signer manager, lock period, unlock " +
|
|
96
|
+
"cycle and burn height, any protocol bond membership, and locked/unlocked STX balance.",
|
|
32
97
|
inputSchema: {
|
|
33
98
|
address: z
|
|
34
99
|
.string()
|
|
35
100
|
.optional()
|
|
36
|
-
.describe("
|
|
101
|
+
.describe("Stacks address to check. Uses the active wallet if omitted."),
|
|
37
102
|
},
|
|
38
103
|
}, async ({ address }) => {
|
|
39
104
|
try {
|
|
40
|
-
const stackingService = getStackingService(NETWORK);
|
|
41
105
|
const walletAddress = address || (await getWalletAddress());
|
|
42
|
-
const status = await
|
|
106
|
+
const status = await getStackingService(NETWORK).getStakingStatus(walletAddress);
|
|
43
107
|
return createJsonResponse({
|
|
44
108
|
address: walletAddress,
|
|
45
109
|
network: NETWORK,
|
|
46
|
-
|
|
110
|
+
staking: status.staking !== null,
|
|
111
|
+
stake: status.staking ? stakerSummary(status.staking) : null,
|
|
112
|
+
bond: status.bond
|
|
113
|
+
? {
|
|
114
|
+
bondIndex: status.bond.bondIndex,
|
|
115
|
+
signerManager: status.bond.signerManager,
|
|
116
|
+
amountUstx: status.bond.amountUstx.toString(),
|
|
117
|
+
amountSats: status.bond.amountSats.toString(),
|
|
118
|
+
isL1Lock: status.bond.isL1Lock,
|
|
119
|
+
}
|
|
120
|
+
: null,
|
|
121
|
+
balance: {
|
|
122
|
+
totalUstx: status.account.balanceUstx.toString(),
|
|
123
|
+
lockedUstx: status.account.lockedUstx.toString(),
|
|
124
|
+
unlockedUstx: status.account.unlockedUstx.toString(),
|
|
125
|
+
burnchainUnlockHeight: status.account.burnchainUnlockHeight,
|
|
126
|
+
},
|
|
127
|
+
pox: poxSummary(status.pox),
|
|
47
128
|
});
|
|
48
129
|
}
|
|
49
130
|
catch (error) {
|
|
50
131
|
return createErrorResponse(error);
|
|
51
132
|
}
|
|
52
133
|
});
|
|
53
|
-
//
|
|
134
|
+
// --- list_stacking_signers ---
|
|
135
|
+
server.registerTool("list_stacking_signers", {
|
|
136
|
+
description: "List PoX-5 signer managers in the signer set for a reward cycle (default: next cycle), with " +
|
|
137
|
+
"the STX delegated to each. A signer manager is what stack_stx stakes with. Only signers with " +
|
|
138
|
+
"at least the signer-set minimum delegated appear here; other registered managers can still be " +
|
|
139
|
+
"staked with by contract id. Set withPayoutInfo to see how each manager pays stakers (slower).",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
rewardCycle: z
|
|
142
|
+
.number()
|
|
143
|
+
.int()
|
|
144
|
+
.positive()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe("Reward cycle to list. Defaults to the next cycle (the one a new stake joins)."),
|
|
147
|
+
withPayoutInfo: z
|
|
148
|
+
.boolean()
|
|
149
|
+
.optional()
|
|
150
|
+
.default(false)
|
|
151
|
+
.describe("Also read each manager's claim path (one extra request per signer)."),
|
|
152
|
+
},
|
|
153
|
+
}, async ({ rewardCycle, withPayoutInfo }) => {
|
|
154
|
+
try {
|
|
155
|
+
const service = getStackingService(NETWORK);
|
|
156
|
+
const pox = await service.getPoxState();
|
|
157
|
+
const cycle = rewardCycle ?? pox.rewardCycle + 1;
|
|
158
|
+
const signers = (await service.getSignerSet(cycle)).sort((a, b) => b.delegatedUstx > a.delegatedUstx ? 1 : b.delegatedUstx < a.delegatedUstx ? -1 : 0);
|
|
159
|
+
const rows = [];
|
|
160
|
+
for (const s of signers) {
|
|
161
|
+
rows.push({
|
|
162
|
+
signerManager: s.signerManager,
|
|
163
|
+
delegatedUstx: s.delegatedUstx.toString(),
|
|
164
|
+
delegated: stx(s.delegatedUstx),
|
|
165
|
+
...(withPayoutInfo && { stakerClaim: await service.getClaimStyle(s.signerManager) }),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
return createJsonResponse({
|
|
169
|
+
network: NETWORK,
|
|
170
|
+
rewardCycle: cycle,
|
|
171
|
+
count: rows.length,
|
|
172
|
+
signers: rows,
|
|
173
|
+
...(withPayoutInfo && {
|
|
174
|
+
stakerClaimKey: {
|
|
175
|
+
"staker-arg": "on-chain; claim_stacking_rewards works",
|
|
176
|
+
caller: "on-chain; claim_stacking_rewards works (paid to the caller)",
|
|
177
|
+
none: "no on-chain staker claim; the manager pays off-chain or not at all",
|
|
178
|
+
},
|
|
179
|
+
}),
|
|
180
|
+
note: "Managers can restrict who may stake (allowlists, minimums, required payout calldata). " +
|
|
181
|
+
"Check a manager's own terms before staking; a refused stake aborts on chain.",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return createErrorResponse(error);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
// --- stack_stx ---
|
|
54
189
|
server.registerTool("stack_stx", {
|
|
55
|
-
description: `Lock STX
|
|
190
|
+
description: `Lock STX in PoX-5 with a signer manager to earn sBTC rewards.
|
|
56
191
|
|
|
57
|
-
|
|
192
|
+
The lock starts next reward cycle and lasts numCycles cycles (1-${MAX_STAKE_CYCLES}). Pick a signer manager with list_stacking_signers. Refused during the prepare phase and if the address is already staking (use extend_stacking). Locking is not a transfer: the transaction carries a staking post-condition for exactly the locked amount.`,
|
|
58
193
|
inputSchema: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
194
|
+
signerManager: z
|
|
195
|
+
.string()
|
|
196
|
+
.describe("Signer manager contract id, e.g. SP8HK160YD5GHXP69VGA0TC7AQJ1X4CDW3XVERSE.xverse-signer-manager-2"),
|
|
197
|
+
amount: z.string().describe("Amount to lock, in micro-STX"),
|
|
198
|
+
numCycles: z
|
|
199
|
+
.number()
|
|
200
|
+
.int()
|
|
201
|
+
.min(1)
|
|
202
|
+
.max(MAX_STAKE_CYCLES)
|
|
203
|
+
.describe(`Reward cycles to lock (1-${MAX_STAKE_CYCLES}; one cycle is about two weeks)`),
|
|
204
|
+
...payoutInputs,
|
|
64
205
|
},
|
|
65
|
-
}, async ({
|
|
206
|
+
}, async ({ signerManager, amount, numCycles, ...payout }) => {
|
|
66
207
|
try {
|
|
67
|
-
const stackingService = getStackingService(NETWORK);
|
|
68
208
|
const account = await getAccount();
|
|
69
|
-
const result = await
|
|
209
|
+
const result = await getStackingService(NETWORK).stake(account, {
|
|
210
|
+
signerManager,
|
|
211
|
+
amountUstx: BigInt(amount),
|
|
212
|
+
numCycles,
|
|
213
|
+
signerCalldata: resolveCalldata(payout),
|
|
214
|
+
});
|
|
70
215
|
return createJsonResponse({
|
|
71
216
|
success: true,
|
|
72
217
|
txid: result.txid,
|
|
73
|
-
stacker: account.address,
|
|
74
|
-
amount,
|
|
75
|
-
lockPeriod,
|
|
76
|
-
startBurnHeight,
|
|
77
|
-
network: NETWORK,
|
|
78
218
|
explorerUrl: getExplorerTxUrl(result.txid, NETWORK),
|
|
219
|
+
staker: account.address,
|
|
220
|
+
signerManager,
|
|
221
|
+
amountUstx: amount,
|
|
222
|
+
amount: stx(BigInt(amount)),
|
|
223
|
+
firstRewardCycle: result.pox.rewardCycle + 1,
|
|
224
|
+
numCycles,
|
|
225
|
+
unlockCycle: result.unlockCycle,
|
|
226
|
+
unlockBurnHeight: result.unlockBurnHeight,
|
|
227
|
+
rewardPayout: payout.btcRewardAddress
|
|
228
|
+
? `BTC to ${payout.btcRewardAddress} (if the manager supports it)`
|
|
229
|
+
: payout.signerCalldataHex
|
|
230
|
+
? "custom signer calldata"
|
|
231
|
+
: "sBTC (if the manager supports it)",
|
|
232
|
+
network: NETWORK,
|
|
79
233
|
});
|
|
80
234
|
}
|
|
81
235
|
catch (error) {
|
|
82
236
|
return createErrorResponse(error);
|
|
83
237
|
}
|
|
84
238
|
});
|
|
85
|
-
//
|
|
239
|
+
// --- extend_stacking ---
|
|
86
240
|
server.registerTool("extend_stacking", {
|
|
87
|
-
description:
|
|
241
|
+
description: `Update an existing PoX-5 stake: extend the lock, add STX, switch signer manager, or change payout calldata (pox-5 stake-update).
|
|
242
|
+
|
|
243
|
+
Any combination in one call. Increasing locks more of the address's unlocked STX. Refused during the prepare phase. The lock may not run more than ${MAX_STAKE_CYCLES} cycles past the next cycle.`,
|
|
88
244
|
inputSchema: {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
245
|
+
cyclesToExtend: z
|
|
246
|
+
.number()
|
|
247
|
+
.int()
|
|
248
|
+
.nonnegative()
|
|
249
|
+
.optional()
|
|
250
|
+
.default(0)
|
|
251
|
+
.describe("Cycles to add to the current unlock cycle"),
|
|
252
|
+
amountIncrease: z
|
|
253
|
+
.string()
|
|
254
|
+
.optional()
|
|
255
|
+
.default("0")
|
|
256
|
+
.describe("Additional micro-STX to lock"),
|
|
257
|
+
signerManager: z
|
|
258
|
+
.string()
|
|
259
|
+
.optional()
|
|
260
|
+
.describe("New signer manager contract id. Defaults to the current one."),
|
|
261
|
+
...payoutInputs,
|
|
92
262
|
},
|
|
93
|
-
}, async ({
|
|
263
|
+
}, async ({ cyclesToExtend, amountIncrease, signerManager, ...payout }) => {
|
|
94
264
|
try {
|
|
95
|
-
const stackingService = getStackingService(NETWORK);
|
|
96
265
|
const account = await getAccount();
|
|
97
|
-
const result = await
|
|
266
|
+
const result = await getStackingService(NETWORK).updateStake(account, {
|
|
267
|
+
signerManager,
|
|
268
|
+
cyclesToExtend,
|
|
269
|
+
amountIncreaseUstx: BigInt(amountIncrease),
|
|
270
|
+
signerCalldata: resolveCalldata(payout),
|
|
271
|
+
});
|
|
98
272
|
return createJsonResponse({
|
|
99
273
|
success: true,
|
|
100
274
|
txid: result.txid,
|
|
101
|
-
|
|
102
|
-
|
|
275
|
+
explorerUrl: getExplorerTxUrl(result.txid, NETWORK),
|
|
276
|
+
staker: account.address,
|
|
277
|
+
previous: stakerSummary(result.previous),
|
|
278
|
+
signerManager: result.signerManager,
|
|
279
|
+
newAmountUstx: result.newAmountUstx.toString(),
|
|
280
|
+
newAmount: stx(result.newAmountUstx),
|
|
281
|
+
unlockCycle: result.unlockCycle,
|
|
282
|
+
unlockBurnHeight: result.unlockBurnHeight,
|
|
103
283
|
network: NETWORK,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
return createErrorResponse(error);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// --- unstake_stx ---
|
|
291
|
+
server.registerTool("unstake_stx", {
|
|
292
|
+
description: "Stop a PoX-5 stake early (pox-5 unstake). The STX stays locked through the current reward " +
|
|
293
|
+
"cycle and unlocks at the start of the next one. Refused during the prepare phase.",
|
|
294
|
+
inputSchema: {},
|
|
295
|
+
}, async () => {
|
|
296
|
+
try {
|
|
297
|
+
const account = await getAccount();
|
|
298
|
+
const result = await getStackingService(NETWORK).unstake(account);
|
|
299
|
+
return createJsonResponse({
|
|
300
|
+
success: true,
|
|
301
|
+
txid: result.txid,
|
|
104
302
|
explorerUrl: getExplorerTxUrl(result.txid, NETWORK),
|
|
303
|
+
staker: account.address,
|
|
304
|
+
previous: stakerSummary(result.previous),
|
|
305
|
+
unlockCycle: result.unlockCycle,
|
|
306
|
+
unlockBurnHeight: result.unlockBurnHeight,
|
|
307
|
+
network: NETWORK,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
return createErrorResponse(error);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
// --- get_stacking_rewards ---
|
|
315
|
+
server.registerTool("get_stacking_rewards", {
|
|
316
|
+
description: "PoX-5 sBTC rewards an address has earned from its signer manager for one reward cycle and not " +
|
|
317
|
+
"yet claimed (before the manager's fees), whether the manager still has to pull them from pox-5, " +
|
|
318
|
+
"and whether the manager supports an on-chain staker claim.",
|
|
319
|
+
inputSchema: {
|
|
320
|
+
rewardCycle: z.number().int().nonnegative().describe("Reward cycle to check"),
|
|
321
|
+
address: z
|
|
322
|
+
.string()
|
|
323
|
+
.optional()
|
|
324
|
+
.describe("Stacks address. Uses the active wallet if omitted."),
|
|
325
|
+
signerManager: z
|
|
326
|
+
.string()
|
|
327
|
+
.optional()
|
|
328
|
+
.describe("Signer manager to check. Defaults to the address's current signer manager."),
|
|
329
|
+
},
|
|
330
|
+
}, async ({ rewardCycle, address, signerManager }) => {
|
|
331
|
+
try {
|
|
332
|
+
const service = getStackingService(NETWORK);
|
|
333
|
+
const staker = address || (await getWalletAddress());
|
|
334
|
+
const manager = signerManager ?? (await service.getStakerInfo(staker))?.signerManager;
|
|
335
|
+
if (!manager) {
|
|
336
|
+
throw new Error(`${staker} is not currently staking; pass signerManager to check rewards from a past signer.`);
|
|
337
|
+
}
|
|
338
|
+
const [earnedSats, unpulledSats, claimStyle] = await Promise.all([
|
|
339
|
+
service.getStakerUnclaimedRewards(manager, rewardCycle, staker),
|
|
340
|
+
service.getSignerUnpulledRewards(manager, rewardCycle),
|
|
341
|
+
service.getClaimStyle(manager),
|
|
342
|
+
]);
|
|
343
|
+
return createJsonResponse({
|
|
344
|
+
address: staker,
|
|
345
|
+
network: NETWORK,
|
|
346
|
+
rewardCycle,
|
|
347
|
+
signerManager: manager,
|
|
348
|
+
unclaimedSatsBeforeFees: earnedSats.toString(),
|
|
349
|
+
managerUnpulledSats: unpulledSats.toString(),
|
|
350
|
+
stakerClaim: claimStyle,
|
|
351
|
+
next: earnedSats === 0n
|
|
352
|
+
? "Nothing to claim for this cycle."
|
|
353
|
+
: claimStyle === "none"
|
|
354
|
+
? "This manager has no on-chain staker claim; it pays stakers off-chain."
|
|
355
|
+
: "Call claim_stacking_rewards with this rewardCycle.",
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
return createErrorResponse(error);
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
// --- claim_stacking_rewards ---
|
|
363
|
+
server.registerTool("claim_stacking_rewards", {
|
|
364
|
+
description: "Claim PoX-5 sBTC rewards for one reward cycle through the staker's signer manager. If the " +
|
|
365
|
+
"manager has not yet pulled that cycle's rewards from pox-5, this first sends the manager's " +
|
|
366
|
+
"permissionless claim-rewards (a second transaction, paid by this wallet), then the staker " +
|
|
367
|
+
"claim. Rewards arrive as sBTC, or as a BTC withdrawal if a payout address was set when " +
|
|
368
|
+
"staking. Not available for managers that pay off-chain.",
|
|
369
|
+
inputSchema: {
|
|
370
|
+
rewardCycle: z.number().int().nonnegative().describe("Reward cycle to claim"),
|
|
371
|
+
signerManager: z
|
|
372
|
+
.string()
|
|
373
|
+
.optional()
|
|
374
|
+
.describe("Signer manager to claim from. Defaults to the wallet's current signer manager."),
|
|
375
|
+
},
|
|
376
|
+
}, async ({ rewardCycle, signerManager }) => {
|
|
377
|
+
try {
|
|
378
|
+
const service = getStackingService(NETWORK);
|
|
379
|
+
const account = await getAccount();
|
|
380
|
+
const manager = signerManager ?? (await service.getStakerInfo(account.address))?.signerManager;
|
|
381
|
+
if (!manager) {
|
|
382
|
+
throw new Error(`${account.address} is not currently staking; pass signerManager to claim from a past signer.`);
|
|
383
|
+
}
|
|
384
|
+
const [earnedSats, unpulledSats, claimStyle] = await Promise.all([
|
|
385
|
+
service.getStakerUnclaimedRewards(manager, rewardCycle, account.address),
|
|
386
|
+
service.getSignerUnpulledRewards(manager, rewardCycle),
|
|
387
|
+
service.getClaimStyle(manager),
|
|
388
|
+
]);
|
|
389
|
+
if (claimStyle === "none") {
|
|
390
|
+
throw new Error(`${manager} has no on-chain staker claim (claim-staker-rewards); it pays stakers off-chain.`);
|
|
391
|
+
}
|
|
392
|
+
if (earnedSats === 0n) {
|
|
393
|
+
throw new Error(`No unclaimed rewards for ${account.address} from ${manager} in cycle ${rewardCycle}.`);
|
|
394
|
+
}
|
|
395
|
+
let pull = null;
|
|
396
|
+
if (unpulledSats > 0n) {
|
|
397
|
+
const pulled = await service.pullSignerRewards(account, manager, rewardCycle, unpulledSats);
|
|
398
|
+
pull = { txid: pulled.txid, explorerUrl: getExplorerTxUrl(pulled.txid, NETWORK) };
|
|
399
|
+
}
|
|
400
|
+
const claim = await service.claimStakerRewards(account, manager, rewardCycle, claimStyle);
|
|
401
|
+
return createJsonResponse({
|
|
402
|
+
success: true,
|
|
403
|
+
network: NETWORK,
|
|
404
|
+
rewardCycle,
|
|
405
|
+
signerManager: manager,
|
|
406
|
+
unclaimedSatsBeforeFees: earnedSats.toString(),
|
|
407
|
+
managerPull: pull,
|
|
408
|
+
txid: claim.txid,
|
|
409
|
+
explorerUrl: getExplorerTxUrl(claim.txid, NETWORK),
|
|
410
|
+
note: pull
|
|
411
|
+
? "Two transactions were sent in nonce order: the manager's pull, then the staker claim."
|
|
412
|
+
: undefined,
|
|
105
413
|
});
|
|
106
414
|
}
|
|
107
415
|
catch (error) {
|
|
@@ -17,6 +17,7 @@ import { contractPrincipalCV, standardPrincipalCV, uintCV, hexToCV, cvToValue, }
|
|
|
17
17
|
import { NETWORK } from "../services/x402.service.js";
|
|
18
18
|
import { getWalletAddress } from "../services/x402.service.js";
|
|
19
19
|
import { getHiroApi } from "../services/hiro-api.js";
|
|
20
|
+
import { getStackingService } from "../services/stacking.service.js";
|
|
20
21
|
import { createJsonResponse, createErrorResponse } from "../utils/index.js";
|
|
21
22
|
// ============================================================================
|
|
22
23
|
// Constants
|
|
@@ -41,10 +42,6 @@ const ALEX_TOKEN_Y_NAME = "token-abtc";
|
|
|
41
42
|
const ALEX_FACTOR = 100_000_000;
|
|
42
43
|
// Bitflow public API
|
|
43
44
|
const BITFLOW_API = "https://app.bitflow.finance/api";
|
|
44
|
-
// Stacking (PoX-4)
|
|
45
|
-
const POX_CONTRACT = "SP000000000000000000002Q6VF78";
|
|
46
|
-
const POX_NAME = "pox-4";
|
|
47
|
-
const POX_CONTRACT_ID = `${POX_CONTRACT}.${POX_NAME}`;
|
|
48
45
|
// Mainnet Hiro API base URL (direct — not network-switched, this skill is mainnet-only)
|
|
49
46
|
const MAINNET_HIRO_API = "https://api.hiro.so";
|
|
50
47
|
// ============================================================================
|
|
@@ -225,24 +222,18 @@ async function readStackingPosition(walletAddress) {
|
|
|
225
222
|
details: {},
|
|
226
223
|
};
|
|
227
224
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
? lockAmount
|
|
241
|
-
: 0;
|
|
242
|
-
pos.apyPct = 8.0;
|
|
243
|
-
pos.details.apySource = "static estimate, not live";
|
|
244
|
-
pos.details.stackerInfo = val;
|
|
245
|
-
}
|
|
225
|
+
const info = await getStackingService("mainnet").getStakerInfo(walletAddress);
|
|
226
|
+
if (info) {
|
|
227
|
+
pos.valueSats = Number(info.amountUstx);
|
|
228
|
+
pos.apyPct = 8.0;
|
|
229
|
+
pos.details.apySource = "static estimate, not live";
|
|
230
|
+
pos.details.stakerInfo = {
|
|
231
|
+
signerManager: info.signerManager,
|
|
232
|
+
firstRewardCycle: info.firstRewardCycle,
|
|
233
|
+
numCycles: info.numCycles,
|
|
234
|
+
unlockCycle: info.unlockCycle,
|
|
235
|
+
unlockBurnHeight: info.unlockBurnHeight,
|
|
236
|
+
};
|
|
246
237
|
}
|
|
247
238
|
}
|
|
248
239
|
catch (e) {
|
package/dist/utils/bip322.d.ts
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Spec: https://github.com/bitcoin/bips/blob/master/bip-0322.mediawiki
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* - src/tools/news.tools.ts (news_file_signal auth headers)
|
|
10
|
+
* Used by src/tools/signing.tools.ts (btc_sign_message / btc_verify_message)
|
|
11
|
+
* and the BIP-322-authenticated aibtc.com tools.
|
|
13
12
|
*/
|
|
14
13
|
/**
|
|
15
14
|
* BIP-322 tagged hash: SHA256(SHA256(tag) || SHA256(tag) || msg)
|