@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.
@@ -1,155 +1,448 @@
1
- import { uintCV, tupleCV, bufferCV, noneCV, someCV } from "@stacks/transactions";
1
+ /**
2
+ * PoX-5 stacking.
3
+ *
4
+ * PoX-5 replaced pox-4 at reward cycle 141 on mainnet, and it is not a rename:
5
+ *
6
+ * - Every stake names a SIGNER MANAGER, a contract implementing pox-5's
7
+ * `signer-manager-trait`. There is no pool delegation (`delegate-stx`) and no
8
+ * PoX reward address on the stake itself; the manager's `validate-stake!`
9
+ * decides who may join and reads any payout preferences out of
10
+ * `signer-calldata`.
11
+ * - Rewards are paid in sBTC to the signer manager, not the staker. Getting a
12
+ * staker paid takes the manager pulling its share out of pox-5
13
+ * (`manager.claim-rewards`) and then paying the staker
14
+ * (`manager.claim-staker-rewards`). Each manager implements that second step
15
+ * its own way, and some pay out off-chain, so the claim path is read from the
16
+ * manager's interface rather than assumed.
17
+ * - `stake`, `stake-update` and `unstake` are refused during the prepare phase
18
+ * (the last `prepare-cycle-length` blocks of a cycle).
19
+ *
20
+ * Locking STX is not a transfer, so these calls carry pox-5's own post-condition
21
+ * types (a staking lock amount, or "performs PoX") rather than STX transfer
22
+ * conditions, and they are not metered against the spend limit.
23
+ */
24
+ import { Pc, PostConditionMode, bufferCV, contractPrincipalCV, cvToJSON, hexToCV, listCV, noneCV, principalCV, serializeCV, someCV, tupleCV, uintCV, } from "@stacks/transactions";
2
25
  import { getHiroApi } from "./hiro-api.js";
3
26
  import { getContracts, parseContractId } from "../config/index.js";
4
27
  import { callContract } from "../transactions/builder.js";
5
- import { createStxPostCondition } from "../transactions/post-conditions.js";
6
28
  // ============================================================================
7
- // Stacking Service
29
+ // Constants mirrored from pox-5
30
+ // ============================================================================
31
+ /** pox-5 `MAX_NUM_CYCLES`. */
32
+ export const MAX_STAKE_CYCLES = 96;
33
+ /** Hard stop when walking the signer-set linked list. */
34
+ const MAX_SIGNERS_WALKED = 200;
35
+ // ============================================================================
36
+ // Helpers
37
+ // ============================================================================
38
+ /**
39
+ * Decode a Clarity value to plain JS: tuples to objects, lists to arrays,
40
+ * optionals to value-or-null, responses to their inner value, uints to strings.
41
+ * (`cvToValue` only unwraps the outermost layer.)
42
+ */
43
+ function plain(cv) {
44
+ const unwrap = (node) => {
45
+ if (node === null || typeof node !== "object")
46
+ return node;
47
+ if (Array.isArray(node))
48
+ return node.map(unwrap);
49
+ const n = node;
50
+ if (typeof n.type === "string" && "value" in n) {
51
+ if (n.type.startsWith("(optional") && n.value === null)
52
+ return null;
53
+ return unwrap(n.value);
54
+ }
55
+ return Object.fromEntries(Object.entries(node).map(([k, v]) => [k, unwrap(v)]));
56
+ };
57
+ return unwrap(cvToJSON(cv));
58
+ }
59
+ function toNumber(v) {
60
+ return Number(v);
61
+ }
62
+ function toBigInt(v) {
63
+ return BigInt(v);
64
+ }
65
+ function assertContractId(id, label) {
66
+ if (!/^S[PMTN][0-9A-Z]{28,40}\.[a-zA-Z][a-zA-Z0-9-_]{0,39}$/.test(id)) {
67
+ throw new Error(`${label} must be a contract id like SP....name, got "${id}"`);
68
+ }
69
+ }
70
+ function contractCV(id) {
71
+ const { address, name } = parseContractId(id);
72
+ return contractPrincipalCV(address, name);
73
+ }
74
+ function calldataCV(calldata) {
75
+ if (!calldata)
76
+ return noneCV();
77
+ if (calldata.length > 500) {
78
+ throw new Error(`signer calldata is ${calldata.length} bytes; pox-5 accepts at most 500`);
79
+ }
80
+ return someCV(bufferCV(calldata));
81
+ }
82
+ /**
83
+ * Payout calldata in the shape reference signer managers decode:
84
+ * `{ pox-addr: { version, hashbytes }, max-fee }`. Managers derived from the
85
+ * pox-5 reference implementation use it to pay rewards to a Bitcoin address via
86
+ * an sBTC withdrawal, capped at `max-fee` sats of withdrawal fee. Whether a given
87
+ * manager accepts it is up to that manager's `validate-stake!`.
88
+ */
89
+ export function buildPayoutCalldata(poxAddr, maxFeeSats) {
90
+ const serialized = serializeCV(tupleCV({
91
+ "pox-addr": tupleCV({
92
+ version: bufferCV(Uint8Array.from([poxAddr.version])),
93
+ hashbytes: bufferCV(Buffer.from(poxAddr.hashbytesHex, "hex")),
94
+ }),
95
+ "max-fee": uintCV(maxFeeSats),
96
+ }));
97
+ return typeof serialized === "string"
98
+ ? Uint8Array.from(Buffer.from(serialized, "hex"))
99
+ : serialized;
100
+ }
101
+ // ============================================================================
102
+ // Service
8
103
  // ============================================================================
9
104
  export class StackingService {
10
105
  network;
11
106
  hiro;
12
- contracts;
107
+ poxContract;
13
108
  constructor(network) {
14
109
  this.network = network;
15
110
  this.hiro = getHiroApi(network);
16
- this.contracts = getContracts(network);
111
+ this.poxContract = getContracts(network).POX_5;
17
112
  }
18
- /**
19
- * Get current PoX (Proof of Transfer) info
20
- */
21
- async getPoxInfo() {
22
- return this.hiro.getPoxInfo();
113
+ get contractId() {
114
+ return this.poxContract;
23
115
  }
24
- /**
25
- * Get stacking status for an address
26
- * Note: Returns whether the address is stacking, but detailed amounts require proper CV parsing
27
- */
28
- async getStackingStatus(address) {
29
- try {
30
- const result = await this.hiro.callReadOnlyFunction(this.contracts.POX_4, "get-stacker-info", [{ type: "principal", value: address }], address);
31
- if (result.okay && result.result) {
32
- const isStacked = result.result.includes("some");
33
- return {
34
- stacked: isStacked,
35
- amountMicroStx: "0", // Requires CV parsing
36
- amountStx: "0",
37
- firstRewardCycle: 0,
38
- lockPeriod: 0,
39
- unlockHeight: 0,
40
- };
41
- }
42
- }
43
- catch {
44
- // Stacker info not found
116
+ async read(contractId, functionName, args = []) {
117
+ const sender = parseContractId(this.poxContract).address;
118
+ const result = await this.hiro.callReadOnlyFunction(contractId, functionName, args, sender);
119
+ if (!result.okay || !result.result) {
120
+ throw new Error(`${contractId}::${functionName} failed: ${result.cause ?? "no result"}`);
45
121
  }
122
+ return plain(hexToCV(result.result));
123
+ }
124
+ readPox(functionName, args = []) {
125
+ return this.read(this.poxContract, functionName, args);
126
+ }
127
+ // --------------------------------------------------------------------------
128
+ // Reads
129
+ // --------------------------------------------------------------------------
130
+ async getPoxState() {
131
+ const [info, core] = await Promise.all([
132
+ this.readPox("get-pox-info"),
133
+ this.hiro.getCoreApiInfo(),
134
+ ]);
135
+ const firstBurnHeight = toNumber(info["first-burnchain-block-height"]);
136
+ const rewardCycleLength = toNumber(info["reward-cycle-length"]);
137
+ const prepareCycleLength = toNumber(info["prepare-cycle-length"]);
138
+ const burnHeight = core.burn_block_height;
139
+ // Mirrors pox-5 burn-height-to-reward-cycle / reward-cycle-to-burn-height.
140
+ const rewardCycle = Math.floor((burnHeight - firstBurnHeight) / rewardCycleLength);
141
+ const nextCycleStartHeight = firstBurnHeight + (rewardCycle + 1) * rewardCycleLength;
142
+ const preparePhaseStartHeight = nextCycleStartHeight - prepareCycleLength;
46
143
  return {
47
- stacked: false,
48
- amountMicroStx: "0",
49
- amountStx: "0",
50
- firstRewardCycle: 0,
51
- lockPeriod: 0,
52
- unlockHeight: 0,
144
+ contractId: this.poxContract,
145
+ burnHeight,
146
+ rewardCycle,
147
+ firstBurnHeight,
148
+ rewardCycleLength,
149
+ prepareCycleLength,
150
+ nextCycleStartHeight,
151
+ preparePhaseStartHeight,
152
+ inPreparePhase: burnHeight >= preparePhaseStartHeight,
153
+ signerSetMinUstx: toBigInt(info["min-amount-ustx"]),
154
+ totalLiquidSupplyUstx: toBigInt(info["total-liquid-supply-ustx"]),
53
155
  };
54
156
  }
55
- /**
56
- * Stack STX tokens
57
- */
58
- async stack(account, amount, poxAddress, startBurnHeight, lockPeriod) {
59
- const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4);
60
- const functionArgs = [
61
- uintCV(amount),
62
- tupleCV({
63
- version: bufferCV(Buffer.from([poxAddress.version])),
64
- hashbytes: bufferCV(Buffer.from(poxAddress.hashbytes, "hex")),
65
- }),
66
- uintCV(startBurnHeight),
67
- uintCV(lockPeriod),
68
- ];
69
- // Add post condition: sender must lock exactly `amount` of STX
70
- const postCondition = createStxPostCondition(account.address, "eq", amount);
71
- return callContract(account, {
72
- contractAddress,
73
- contractName,
74
- functionName: "stack-stx",
75
- functionArgs,
76
- postConditions: [postCondition],
157
+ rewardCycleStartHeight(pox, cycle) {
158
+ return pox.firstBurnHeight + cycle * pox.rewardCycleLength;
159
+ }
160
+ async getStakerInfo(address, pox) {
161
+ const raw = (await this.readPox("get-staker-info", [principalCV(address)]));
162
+ if (!raw)
163
+ return null;
164
+ const state = pox ?? (await this.getPoxState());
165
+ const firstRewardCycle = toNumber(raw["first-reward-cycle"]);
166
+ const numCycles = toNumber(raw["num-cycles"]);
167
+ const unlockCycle = firstRewardCycle + numCycles;
168
+ return {
169
+ amountUstx: toBigInt(raw["amount-ustx"]),
170
+ firstRewardCycle,
171
+ numCycles,
172
+ unlockCycle,
173
+ unlockBurnHeight: this.rewardCycleStartHeight(state, unlockCycle),
174
+ signerManager: String(raw["signer"]),
175
+ };
176
+ }
177
+ async getBondMembership(address) {
178
+ const raw = (await this.readPox("get-bond-membership", [principalCV(address)]));
179
+ if (!raw)
180
+ return null;
181
+ return {
182
+ bondIndex: toNumber(raw["bond-index"]),
183
+ amountUstx: toBigInt(raw["amount-ustx"]),
184
+ amountSats: toBigInt(raw["amount-sats"]),
185
+ isL1Lock: Boolean(raw["is-l1-lock"]),
186
+ signerManager: String(raw["signer"]),
187
+ };
188
+ }
189
+ async getStakingStatus(address) {
190
+ const pox = await this.getPoxState();
191
+ const [staking, bond, balance] = await Promise.all([
192
+ this.getStakerInfo(address, pox),
193
+ this.getBondMembership(address),
194
+ this.hiro.getStxBalance(address),
195
+ ]);
196
+ const balanceUstx = BigInt(balance.balance);
197
+ const lockedUstx = BigInt(balance.locked);
198
+ return {
199
+ address,
200
+ pox,
201
+ staking,
202
+ bond,
203
+ account: {
204
+ balanceUstx,
205
+ lockedUstx,
206
+ unlockedUstx: balanceUstx - lockedUstx,
207
+ burnchainUnlockHeight: balance.burnchain_unlock_height,
208
+ },
209
+ };
210
+ }
211
+ /** Whether pox-5 has a signer key registered for this manager. */
212
+ async isRegisteredSigner(signerManager) {
213
+ const key = await this.readPox("get-signer-info", [contractCV(signerManager)]);
214
+ return key !== null;
215
+ }
216
+ /** Walk pox-5's signer-set linked list for a reward cycle. */
217
+ async getSignerSet(cycle) {
218
+ const entries = [];
219
+ let next = (await this.readPox("get-signer-set-first-item-for-cycle", [
220
+ uintCV(cycle),
221
+ ]));
222
+ while (next !== null && entries.length < MAX_SIGNERS_WALKED) {
223
+ const signer = next;
224
+ const delegated = await this.readPox("get-amount-delegated-for-signer", [
225
+ principalCV(signer),
226
+ uintCV(cycle),
227
+ ]);
228
+ entries.push({ signerManager: signer, delegatedUstx: toBigInt(delegated) });
229
+ next = (await this.readPox("get-signer-set-next-item-for-cycle", [
230
+ principalCV(signer),
231
+ uintCV(cycle),
232
+ ]));
233
+ }
234
+ return entries;
235
+ }
236
+ async getClaimStyle(signerManager) {
237
+ const iface = await this.hiro.getContractInterface(signerManager);
238
+ const fn = iface.functions.find((f) => f.name === "claim-staker-rewards" && f.access === "public");
239
+ if (!fn)
240
+ return "none";
241
+ const names = fn.args.map((a) => a.name);
242
+ if (names.length === 3 && names[0] === "staker" && names[1] === "reward-cycle") {
243
+ return "staker-arg";
244
+ }
245
+ if (names.length === 2 && names[0] === "reward-cycle") {
246
+ return "caller";
247
+ }
248
+ return "none";
249
+ }
250
+ /** sBTC the staker has earned from this signer for a cycle and not yet claimed (before manager fees). */
251
+ async getStakerUnclaimedRewards(signerManager, rewardCycle, staker) {
252
+ return toBigInt(await this.readPox("get-earned-staker-rewards", [
253
+ principalCV(signerManager),
254
+ uintCV(rewardCycle),
255
+ noneCV(),
256
+ principalCV(staker),
257
+ ]));
258
+ }
259
+ /** sBTC pox-5 still holds for the signer manager for a cycle (not yet pulled by the manager). */
260
+ async getSignerUnpulledRewards(signerManager, rewardCycle) {
261
+ return toBigInt(await this.readPox("get-earned", [principalCV(signerManager), uintCV(rewardCycle), noneCV()]));
262
+ }
263
+ // --------------------------------------------------------------------------
264
+ // Writes
265
+ // --------------------------------------------------------------------------
266
+ assertNotPreparePhase(pox, action) {
267
+ if (pox.inPreparePhase) {
268
+ throw new Error(`pox-5 refuses ${action} during the prepare phase. Burn height ${pox.burnHeight} is inside ` +
269
+ `the prepare phase that started at ${pox.preparePhaseStartHeight}; try again at or after ` +
270
+ `burn height ${pox.nextCycleStartHeight} (reward cycle ${pox.rewardCycle + 1}).`);
271
+ }
272
+ }
273
+ async stake(account, options) {
274
+ const { signerManager, amountUstx, numCycles, signerCalldata } = options;
275
+ assertContractId(signerManager, "signerManager");
276
+ if (amountUstx <= 0n)
277
+ throw new Error("amount must be greater than zero");
278
+ if (!Number.isInteger(numCycles) || numCycles < 1 || numCycles > MAX_STAKE_CYCLES) {
279
+ throw new Error(`numCycles must be an integer between 1 and ${MAX_STAKE_CYCLES}`);
280
+ }
281
+ const status = await this.getStakingStatus(account.address);
282
+ const { pox } = status;
283
+ this.assertNotPreparePhase(pox, "stake");
284
+ if (status.staking) {
285
+ throw new Error(`${account.address} is already staking ${status.staking.amountUstx} uSTX with ` +
286
+ `${status.staking.signerManager}. Use extend_stacking to extend, increase or switch signer.`);
287
+ }
288
+ // pox-5 counts locked + unlocked STX (a bond rolling over into a stake is still locked).
289
+ if (status.account.balanceUstx < amountUstx) {
290
+ throw new Error(`Insufficient STX: ${status.account.balanceUstx} uSTX total, ${amountUstx} uSTX requested.`);
291
+ }
292
+ if (!(await this.isRegisteredSigner(signerManager))) {
293
+ throw new Error(`${signerManager} is not a registered pox-5 signer manager. Use list_stacking_signers to pick one.`);
294
+ }
295
+ const { address, name } = parseContractId(this.poxContract);
296
+ const result = await callContract(account, {
297
+ contractAddress: address,
298
+ contractName: name,
299
+ functionName: "stake",
300
+ functionArgs: [
301
+ contractCV(signerManager),
302
+ uintCV(amountUstx),
303
+ uintCV(numCycles),
304
+ // Any height in the current cycle makes the next cycle the first reward cycle.
305
+ uintCV(pox.burnHeight),
306
+ calldataCV(signerCalldata),
307
+ ],
308
+ postConditionMode: PostConditionMode.Deny,
309
+ postConditions: [Pc.principal(account.address).willSendEq(amountUstx).ustxToLock()],
77
310
  });
311
+ const unlockCycle = pox.rewardCycle + 1 + numCycles;
312
+ return {
313
+ ...result,
314
+ pox,
315
+ unlockCycle,
316
+ unlockBurnHeight: this.rewardCycleStartHeight(pox, unlockCycle),
317
+ };
78
318
  }
79
- /**
80
- * Extend stacking period
81
- */
82
- async extendStacking(account, extendCount, poxAddress) {
83
- const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4);
84
- const functionArgs = [
85
- uintCV(extendCount),
86
- tupleCV({
87
- version: bufferCV(Buffer.from([poxAddress.version])),
88
- hashbytes: bufferCV(Buffer.from(poxAddress.hashbytes, "hex")),
89
- }),
90
- ];
91
- // No assets moved from sender (extends existing lock period)
92
- return callContract(account, {
93
- contractAddress,
94
- contractName,
95
- functionName: "stack-extend",
96
- functionArgs,
97
- postConditions: [],
319
+ async updateStake(account, options) {
320
+ const { cyclesToExtend, amountIncreaseUstx, signerCalldata } = options;
321
+ if (!Number.isInteger(cyclesToExtend) || cyclesToExtend < 0) {
322
+ throw new Error("cyclesToExtend must be a non-negative integer");
323
+ }
324
+ if (amountIncreaseUstx < 0n)
325
+ throw new Error("amountIncrease must not be negative");
326
+ const status = await this.getStakingStatus(account.address);
327
+ const { pox } = status;
328
+ const current = status.staking;
329
+ if (!current) {
330
+ throw new Error(`${account.address} is not staking. Use stack_stx to start.`);
331
+ }
332
+ this.assertNotPreparePhase(pox, "stake-update");
333
+ const signerManager = options.signerManager ?? current.signerManager;
334
+ assertContractId(signerManager, "signerManager");
335
+ if (cyclesToExtend === 0 &&
336
+ amountIncreaseUstx === 0n &&
337
+ signerManager === current.signerManager &&
338
+ !signerCalldata) {
339
+ throw new Error("Nothing to update: pass cyclesToExtend, amountIncrease, a new signerManager or payout calldata.");
340
+ }
341
+ const unlockCycle = current.unlockCycle + cyclesToExtend;
342
+ // pox-5 recomputes num-cycles from the next cycle to the unlock cycle.
343
+ const numCycles = unlockCycle - pox.rewardCycle - 1;
344
+ if (numCycles < 1 || numCycles > MAX_STAKE_CYCLES) {
345
+ throw new Error(`The lock would run ${numCycles} cycles from the next cycle; pox-5 allows 1 to ${MAX_STAKE_CYCLES}.`);
346
+ }
347
+ if (status.account.unlockedUstx < amountIncreaseUstx) {
348
+ throw new Error(`Insufficient unlocked STX: ${status.account.unlockedUstx} uSTX unlocked, ${amountIncreaseUstx} uSTX requested.`);
349
+ }
350
+ if (signerManager !== current.signerManager && !(await this.isRegisteredSigner(signerManager))) {
351
+ throw new Error(`${signerManager} is not a registered pox-5 signer manager. Use list_stacking_signers to pick one.`);
352
+ }
353
+ const newAmountUstx = current.amountUstx + amountIncreaseUstx;
354
+ const { address, name } = parseContractId(this.poxContract);
355
+ const result = await callContract(account, {
356
+ contractAddress: address,
357
+ contractName: name,
358
+ functionName: "stake-update",
359
+ functionArgs: [
360
+ contractCV(signerManager),
361
+ contractCV(current.signerManager),
362
+ uintCV(cyclesToExtend),
363
+ uintCV(amountIncreaseUstx),
364
+ calldataCV(signerCalldata),
365
+ ],
366
+ postConditionMode: PostConditionMode.Deny,
367
+ // The staking condition is on the resulting total lock, not the increment.
368
+ postConditions: [Pc.principal(account.address).willSendEq(newAmountUstx).ustxToLock()],
98
369
  });
370
+ return {
371
+ ...result,
372
+ previous: current,
373
+ newAmountUstx,
374
+ signerManager,
375
+ unlockCycle,
376
+ unlockBurnHeight: this.rewardCycleStartHeight(pox, unlockCycle),
377
+ };
99
378
  }
100
- /**
101
- * Increase stacking amount
102
- */
103
- async increaseStacking(account, increaseAmount) {
104
- const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4);
105
- const functionArgs = [uintCV(increaseAmount)];
106
- // Add post condition: sender must lock exactly `increaseAmount` of additional STX
107
- const postCondition = createStxPostCondition(account.address, "eq", increaseAmount);
108
- return callContract(account, {
109
- contractAddress,
110
- contractName,
111
- functionName: "stack-increase",
112
- functionArgs,
113
- postConditions: [postCondition],
379
+ async unstake(account) {
380
+ const status = await this.getStakingStatus(account.address);
381
+ const { pox } = status;
382
+ const current = status.staking;
383
+ if (!current) {
384
+ throw new Error(`${account.address} is not staking.`);
385
+ }
386
+ this.assertNotPreparePhase(pox, "unstake");
387
+ if (current.unlockCycle <= pox.rewardCycle + 1) {
388
+ throw new Error(`This stake already unlocks at the start of cycle ${current.unlockCycle} ` +
389
+ `(burn height ${current.unlockBurnHeight}); unstaking would not unlock it sooner.`);
390
+ }
391
+ const { address, name } = parseContractId(this.poxContract);
392
+ const result = await callContract(account, {
393
+ contractAddress: address,
394
+ contractName: name,
395
+ functionName: "unstake",
396
+ functionArgs: [contractCV(current.signerManager)],
397
+ postConditionMode: PostConditionMode.Deny,
398
+ postConditions: [Pc.origin().willPerformPox()],
114
399
  });
400
+ const unlockCycle = pox.rewardCycle + 1;
401
+ return {
402
+ ...result,
403
+ previous: current,
404
+ unlockCycle,
405
+ unlockBurnHeight: this.rewardCycleStartHeight(pox, unlockCycle),
406
+ };
115
407
  }
116
408
  /**
117
- * Delegate STX to a stacking pool
409
+ * Have the signer manager pull its STX-staking rewards for a cycle out of pox-5.
410
+ * Permissionless on reference managers. An empty bond-period list claims the
411
+ * STX-staker bucket only, which is all an STX staker's payout draws on.
118
412
  */
119
- async delegateStx(account, amount, delegateTo, untilBurnHeight, poxAddress) {
120
- const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4);
121
- const functionArgs = [
122
- uintCV(amount),
123
- { type: "principal", value: delegateTo },
124
- untilBurnHeight ? someCV(uintCV(untilBurnHeight)) : noneCV(),
125
- poxAddress
126
- ? someCV(tupleCV({
127
- version: bufferCV(Buffer.from([poxAddress.version])),
128
- hashbytes: bufferCV(Buffer.from(poxAddress.hashbytes, "hex")),
129
- }))
130
- : noneCV(),
131
- ];
132
- // No assets moved from sender (delegation is permission, not transfer)
413
+ async pullSignerRewards(account, signerManager, rewardCycle, unpulledSats) {
414
+ const { address, name } = parseContractId(signerManager);
415
+ const sbtc = getContracts(this.network).SBTC_TOKEN;
133
416
  return callContract(account, {
134
- contractAddress,
135
- contractName,
136
- functionName: "delegate-stx",
137
- functionArgs,
138
- postConditions: [],
417
+ contractAddress: address,
418
+ contractName: name,
419
+ functionName: "claim-rewards",
420
+ functionArgs: [
421
+ listCV([]),
422
+ uintCV(rewardCycle),
423
+ ],
424
+ postConditionMode: PostConditionMode.Deny,
425
+ // pox-5 pays the manager; more may have accrued by the time this mines.
426
+ postConditions: [Pc.principal(this.poxContract).willSendGte(unpulledSats).ft(sbtc, "sbtc-token")],
139
427
  });
140
428
  }
141
- /**
142
- * Revoke delegation
143
- */
144
- async revokeDelegation(account) {
145
- const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4);
146
- // No assets moved from sender (revokes delegation permission)
429
+ /** Pay this staker their share of a cycle's rewards through the signer manager. */
430
+ async claimStakerRewards(account, signerManager, rewardCycle, style) {
431
+ const { address, name } = parseContractId(signerManager);
432
+ const sbtc = getContracts(this.network).SBTC_TOKEN;
433
+ const args = style === "staker-arg"
434
+ ? [principalCV(account.address), uintCV(rewardCycle), noneCV()]
435
+ : [uintCV(rewardCycle), noneCV()];
147
436
  return callContract(account, {
148
- contractAddress,
149
- contractName,
150
- functionName: "revoke-delegate-stx",
151
- functionArgs: [],
152
- postConditions: [],
437
+ contractAddress: address,
438
+ contractName: name,
439
+ functionName: "claim-staker-rewards",
440
+ functionArgs: args,
441
+ postConditionMode: PostConditionMode.Deny,
442
+ // The manager sends sBTC to the staker (or into an sBTC withdrawal to their
443
+ // BTC address). Fees and earlier settled balances make the exact amount
444
+ // manager-specific; nothing may leave the caller.
445
+ postConditions: [Pc.principal(signerManager).willSendGte(1).ft(sbtc, "sbtc-token")],
153
446
  });
154
447
  }
155
448
  }
@@ -0,0 +1,81 @@
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 { type Network } from "../config/networks.js";
13
+ import type { X402Endpoint } from "../endpoints/registry.js";
14
+ export interface OpenApiSource {
15
+ /** Label shown to the agent. */
16
+ source: string;
17
+ /** Base URL per network; a network without one is not served by this host. */
18
+ baseUrl: Partial<Record<Network, string>>;
19
+ }
20
+ export declare const OPENAPI_SOURCES: OpenApiSource[];
21
+ export declare const DISCOVERY_CACHE_TTL_MS: number;
22
+ /** An endpoint registered in the stx402.com directory by its owner. */
23
+ export interface DirectoryEntry {
24
+ name: string;
25
+ url: string;
26
+ category: string;
27
+ status: string;
28
+ owner: string;
29
+ }
30
+ export interface UnavailableSource {
31
+ source: string;
32
+ error: string;
33
+ }
34
+ /** Test seam. */
35
+ export declare function clearDiscoveryCache(): void;
36
+ interface OpenApiParameter {
37
+ name: string;
38
+ in: string;
39
+ description?: string;
40
+ }
41
+ interface OpenApiOperation {
42
+ summary?: string;
43
+ description?: string;
44
+ tags?: string[];
45
+ parameters?: OpenApiParameter[];
46
+ requestBody?: {
47
+ content?: Record<string, {
48
+ schema?: {
49
+ properties?: Record<string, {
50
+ description?: string;
51
+ }>;
52
+ required?: string[];
53
+ };
54
+ }>;
55
+ };
56
+ }
57
+ interface OpenApiSpec {
58
+ paths?: Record<string, Record<string, OpenApiOperation>>;
59
+ }
60
+ /**
61
+ * Summaries on these hosts lead with the pricing tier, e.g. "(paid, standard) Decode a
62
+ * transaction" or "(free) List models". Split that into a cost label and a description.
63
+ * The exact price is only known from the 402 challenge, so it is not guessed here.
64
+ */
65
+ export declare function parseSummary(summary: string): {
66
+ cost: string;
67
+ description: string;
68
+ };
69
+ /** Map an OpenAPI spec to endpoint entries. Admin-only operations are left out. */
70
+ export declare function endpointsFromOpenApi(spec: OpenApiSpec, source: string): X402Endpoint[];
71
+ /** Fetch one host's endpoints from its OpenAPI spec. */
72
+ export declare function fetchOpenApiEndpoints(source: OpenApiSource, network?: Network): Promise<X402Endpoint[]>;
73
+ /** Fetch every entry in the stx402.com endpoint directory (mainnet only). */
74
+ export declare function fetchDirectoryEntries(): Promise<DirectoryEntry[]>;
75
+ /** Fetch every OpenAPI source in parallel, reporting failures per source. */
76
+ export declare function discoverOpenApiEndpoints(sources?: OpenApiSource[], network?: Network): Promise<{
77
+ endpoints: X402Endpoint[];
78
+ unavailable: UnavailableSource[];
79
+ }>;
80
+ export {};
81
+ //# sourceMappingURL=x402-discovery.service.d.ts.map