@aibtc/mcp-server 1.70.1 → 1.72.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 +45 -8
- package/dist/bridge/index.js +4 -4
- package/dist/config/contracts.d.ts +4 -4
- package/dist/config/contracts.js +2 -2
- package/dist/config/networks.js +4 -1
- package/dist/endpoints/registry.d.ts +8 -26
- package/dist/endpoints/registry.js +7 -932
- package/dist/index.js +118 -42
- 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/arxiv-research.tools.d.ts +1 -1
- package/dist/tools/arxiv-research.tools.js +6 -6
- package/dist/tools/at-stake-legion.tools.d.ts +1 -1
- package/dist/tools/at-stake-legion.tools.js +14 -14
- package/dist/tools/at-stake.tools.d.ts +1 -1
- package/dist/tools/at-stake.tools.js +17 -17
- package/dist/tools/bitcoin.tools.d.ts +1 -1
- package/dist/tools/bitcoin.tools.js +12 -12
- package/dist/tools/bitflow.tools.d.ts +1 -1
- package/dist/tools/bitflow.tools.js +21 -21
- package/dist/tools/bns.tools.d.ts +1 -1
- package/dist/tools/bns.tools.js +18 -18
- package/dist/tools/bounty-hint.d.ts +1 -1
- package/dist/tools/bounty-scanner.tools.d.ts +1 -1
- package/dist/tools/bounty-scanner.tools.js +20 -20
- package/dist/tools/child-inscription.tools.d.ts +1 -1
- package/dist/tools/child-inscription.tools.js +6 -6
- package/dist/tools/competition.tools.d.ts +1 -1
- package/dist/tools/competition.tools.js +4 -4
- package/dist/tools/contract.tools.d.ts +1 -1
- package/dist/tools/contract.tools.js +8 -8
- package/dist/tools/credentials.tools.d.ts +1 -1
- package/dist/tools/credentials.tools.js +8 -8
- package/dist/tools/defi.tools.d.ts +1 -1
- package/dist/tools/defi.tools.js +21 -21
- package/dist/tools/dual-stacking.tools.d.ts +1 -1
- package/dist/tools/dual-stacking.tools.js +7 -7
- package/dist/tools/earning.tools.d.ts +1 -1
- package/dist/tools/earning.tools.js +5 -5
- package/dist/tools/endpoint.tools.d.ts +1 -1
- package/dist/tools/endpoint.tools.js +92 -49
- package/dist/tools/identity.tools.d.ts +1 -1
- package/dist/tools/identity.tools.js +19 -19
- package/dist/tools/inbox-x402.tools.d.ts +1 -1
- package/dist/tools/inbox-x402.tools.js +2 -2
- package/dist/tools/inbox.tools.d.ts +1 -1
- package/dist/tools/inbox.tools.js +2 -2
- package/dist/tools/index.d.ts +6 -3
- package/dist/tools/index.js +69 -58
- package/dist/tools/inference-marketplace.tools.d.ts +1 -1
- package/dist/tools/inference-marketplace.tools.js +10 -10
- package/dist/tools/jingswap.tools.d.ts +1 -1
- package/dist/tools/jingswap.tools.js +30 -30
- package/dist/tools/legion.tools.d.ts +1 -1
- package/dist/tools/legion.tools.js +20 -20
- package/dist/tools/lightning.tools.d.ts +1 -1
- package/dist/tools/lightning.tools.js +14 -14
- package/dist/tools/mempool.tools.d.ts +1 -1
- package/dist/tools/mempool.tools.js +5 -5
- package/dist/tools/news.tools.d.ts +1 -1
- package/dist/tools/news.tools.js +2 -1
- package/dist/tools/nft.tools.d.ts +1 -1
- package/dist/tools/nft.tools.js +12 -12
- package/dist/tools/nonce.tools.d.ts +1 -1
- package/dist/tools/nonce.tools.js +8 -8
- package/dist/tools/nostr.tools.d.ts +1 -1
- package/dist/tools/nostr.tools.js +12 -12
- package/dist/tools/openrouter.tools.d.ts +1 -1
- package/dist/tools/openrouter.tools.js +4 -4
- package/dist/tools/ordinals-marketplace.tools.d.ts +1 -1
- package/dist/tools/ordinals-marketplace.tools.js +10 -10
- package/dist/tools/ordinals-p2p.tools.d.ts +1 -1
- package/dist/tools/ordinals-p2p.tools.js +18 -18
- package/dist/tools/ordinals.tools.d.ts +1 -1
- package/dist/tools/ordinals.tools.js +8 -8
- package/dist/tools/pillar-direct.tools.d.ts +1 -1
- package/dist/tools/pillar-direct.tools.js +59 -38
- package/dist/tools/pillar.tools.d.ts +1 -1
- package/dist/tools/pillar.tools.js +28 -26
- package/dist/tools/profiles.d.ts +51 -0
- package/dist/tools/profiles.js +111 -0
- package/dist/tools/psbt.tools.d.ts +1 -1
- package/dist/tools/psbt.tools.js +8 -8
- package/dist/tools/query.tools.d.ts +1 -1
- package/dist/tools/query.tools.js +12 -12
- package/dist/tools/relay-diagnostic.tools.d.ts +1 -1
- package/dist/tools/relay-diagnostic.tools.js +3 -3
- package/dist/tools/reputation.tools.d.ts +1 -1
- package/dist/tools/reputation.tools.js +22 -22
- package/dist/tools/runes.tools.d.ts +1 -1
- package/dist/tools/runes.tools.js +18 -18
- package/dist/tools/sbtc.tools.d.ts +1 -1
- package/dist/tools/sbtc.tools.js +14 -33
- package/dist/tools/scaffold.tools.d.ts +1 -1
- package/dist/tools/scaffold.tools.js +4 -4
- package/dist/tools/settings.tools.d.ts +1 -1
- package/dist/tools/settings.tools.js +9 -9
- package/dist/tools/signing.tools.d.ts +1 -1
- package/dist/tools/signing.tools.js +20 -20
- package/dist/tools/skill-mappings.js +4 -0
- package/dist/tools/souldinals.tools.d.ts +1 -1
- package/dist/tools/souldinals.tools.js +10 -10
- package/dist/tools/stacking-lottery.tools.d.ts +1 -1
- package/dist/tools/stacking-lottery.tools.js +11 -11
- package/dist/tools/stacking.tools.d.ts +1 -1
- package/dist/tools/stacking.tools.js +358 -50
- package/dist/tools/stacks-market.tools.d.ts +1 -1
- package/dist/tools/stacks-market.tools.js +22 -22
- package/dist/tools/styx.tools.d.ts +1 -1
- package/dist/tools/styx.tools.js +11 -11
- package/dist/tools/taproot-multisig.tools.d.ts +1 -1
- package/dist/tools/taproot-multisig.tools.js +5 -5
- package/dist/tools/tenero.tools.d.ts +1 -1
- package/dist/tools/tenero.tools.js +22 -22
- package/dist/tools/tokens.tools.d.ts +1 -1
- package/dist/tools/tokens.tools.js +10 -10
- package/dist/tools/transfer.tools.d.ts +1 -1
- package/dist/tools/transfer.tools.js +4 -4
- package/dist/tools/wallet-management.tools.d.ts +1 -1
- package/dist/tools/wallet-management.tools.js +16 -16
- package/dist/tools/wallet.tools.d.ts +1 -1
- package/dist/tools/wallet.tools.js +2 -2
- package/dist/tools/yield-dashboard.tools.d.ts +1 -1
- package/dist/tools/yield-dashboard.tools.js +18 -27
- package/dist/tools/yield-hunter.tools.d.ts +1 -1
- package/dist/tools/yield-hunter.tools.js +6 -6
- package/dist/utils/bitcoin.d.ts +8 -0
- package/dist/utils/bitcoin.js +24 -0
- package/dist/utils/fee.js +1 -1
- package/package.json +8 -7
- package/skill/SKILL.md +1 -1
- package/skill/references/stacks-defi.md +1 -1
|
@@ -1,155 +1,448 @@
|
|
|
1
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
107
|
+
poxContract;
|
|
13
108
|
constructor(network) {
|
|
14
109
|
this.network = network;
|
|
15
110
|
this.hiro = getHiroApi(network);
|
|
16
|
-
this.
|
|
111
|
+
this.poxContract = getContracts(network).POX_5;
|
|
17
112
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
*/
|
|
21
|
-
async getPoxInfo() {
|
|
22
|
-
return this.hiro.getPoxInfo();
|
|
113
|
+
get contractId() {
|
|
114
|
+
return this.poxContract;
|
|
23
115
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
async
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
*
|
|
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
|
|
120
|
-
const { address
|
|
121
|
-
const
|
|
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: "
|
|
137
|
-
functionArgs
|
|
138
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
|
|
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: "
|
|
151
|
-
functionArgs:
|
|
152
|
-
|
|
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
|