diff --git a/src/lib/config/contracts.ts b/src/lib/config/contracts.ts index dee5669..c0e136a 100644 --- a/src/lib/config/contracts.ts +++ b/src/lib/config/contracts.ts @@ -16,7 +16,13 @@ export const MAINNET_CONTRACTS = { BNS: "SP000000000000000000002Q6VF78.bns", // Stacking + // NOTE: pox-4 is superseded by pox-5 at Epoch 4.0 activation (stacks-core 4.0.0, + // Bitcoin height ~960230, ~2026-07-29). Existing pox-4 locks are unaffected, but new + // stack/delegate/extend/increase calls must target the currently active PoX contract. + // stacking.service.ts resolves this dynamically via /v2/pox `contract_id`; POX_4/POX_5 + // here are fallbacks only — do not hardcode either into new call sites. POX_4: "SP000000000000000000002Q6VF78.pox-4", + POX_5: "SP000000000000000000002Q6VF78.pox-5", // ALEX DEX (SDK handles most operations, but we need pool contract for queries) ALEX_AMM_POOL: "SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.amm-swap-pool-v1-1", @@ -165,8 +171,9 @@ export const TESTNET_CONTRACTS = { // BNS BNS: "ST000000000000000000002AMW42H.bns", - // Stacking + // Stacking (see mainnet note above re: pox-4 -> pox-5) POX_4: "ST000000000000000000002AMW42H.pox-4", + POX_5: "ST000000000000000000002AMW42H.pox-5", // ERC-8004 Identity & Reputation IDENTITY_REGISTRY: "ST3YT0XW92E6T2FE59B2G5N2WNNFSBZ6MZKQS5D18.identity-registry-v2", diff --git a/src/lib/services/stacking.service.ts b/src/lib/services/stacking.service.ts index beadf8b..5a49ec8 100644 --- a/src/lib/services/stacking.service.ts +++ b/src/lib/services/stacking.service.ts @@ -38,14 +38,33 @@ export class StackingService { return this.hiro.getPoxInfo(); } + /** + * Resolve the currently active PoX contract from the node (/v2/pox `contract_id`) + * rather than a hardcoded version. This is what makes stack/delegate/extend/increase + * calls survive PoX version transitions (e.g. pox-4 -> pox-5 at Epoch 4.0) without a + * code change. Falls back to the last-known POX_4 constant if the node call fails. + */ + private async getActivePoxContract(): Promise { + try { + const poxInfo = await this.hiro.getPoxInfo(); + if (poxInfo.contract_id) { + return poxInfo.contract_id; + } + } catch { + // fall through to static fallback + } + return this.contracts.POX_4; + } + /** * Get stacking status for an address * Note: Returns whether the address is stacking, but detailed amounts require proper CV parsing */ async getStackingStatus(address: string): Promise { try { + const poxContract = await this.getActivePoxContract(); const result = await this.hiro.callReadOnlyFunction( - this.contracts.POX_4, + poxContract, "get-stacker-info", [{ type: "principal", value: address } as unknown as ClarityValue], address @@ -87,7 +106,7 @@ export class StackingService { startBurnHeight: number, lockPeriod: number ): Promise { - const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4); + const { address: contractAddress, name: contractName } = parseContractId(await this.getActivePoxContract()); const functionArgs: ClarityValue[] = [ uintCV(amount), @@ -123,7 +142,7 @@ export class StackingService { extendCount: number, poxAddress: { version: number; hashbytes: string } ): Promise { - const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4); + const { address: contractAddress, name: contractName } = parseContractId(await this.getActivePoxContract()); const functionArgs: ClarityValue[] = [ uintCV(extendCount), @@ -150,7 +169,7 @@ export class StackingService { account: Account, increaseAmount: bigint ): Promise { - const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4); + const { address: contractAddress, name: contractName } = parseContractId(await this.getActivePoxContract()); const functionArgs: ClarityValue[] = [uintCV(increaseAmount)]; @@ -180,7 +199,7 @@ export class StackingService { untilBurnHeight?: number, poxAddress?: { version: number; hashbytes: string } ): Promise { - const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4); + const { address: contractAddress, name: contractName } = parseContractId(await this.getActivePoxContract()); const functionArgs: ClarityValue[] = [ uintCV(amount), @@ -208,7 +227,7 @@ export class StackingService { * Revoke delegation */ async revokeDelegation(account: Account): Promise { - const { address: contractAddress, name: contractName } = parseContractId(this.contracts.POX_4); + const { address: contractAddress, name: contractName } = parseContractId(await this.getActivePoxContract()); // No assets moved from sender (revokes delegation permission) return callContract(account, {