From c6b31e2c183158a804289614c05ac44cccd50390 Mon Sep 17 00:00:00 2001 From: janniks Date: Wed, 5 Jul 2023 22:37:57 +0200 Subject: [PATCH] fix: add getSecondsUntilStackingDeadline in addition to getSecondsUntilNextCycle --- packages/stacking/README.md | 6 +-- packages/stacking/src/index.ts | 49 ++++++++++++------------ packages/stacking/tests/stacking.test.ts | 34 +++++++++++++--- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/packages/stacking/README.md b/packages/stacking/README.md index 9780e2f1d..112d912fe 100644 --- a/packages/stacking/README.md +++ b/packages/stacking/README.md @@ -19,7 +19,7 @@ npm install @stacks/stacking - [Client helpers](#client-helpers) - [Will Stacking be executed in the next cycle?](#will-stacking-be-executed-in-the-next-cycle) - [How long (in seconds) is a Stacking cycle?](#how-long-in-seconds-is-a-stacking-cycle) - - [How much time is left (in seconds) until the next cycle begins?](#how-much-time-is-left-in-seconds-until-the-next-cycle-begins) + - [How much estimated time is left (in seconds) to submit a stacking transaction for the upcoming reward cycle?](#how-much-estimated-time-is-left-in-seconds-to-submit-a-stacking-transaction-for-the-upcoming-reward-cycle) - [Does account have sufficient STX to meet minimum threshold?](#does-account-have-sufficient-stx-to-meet-minimum-threshold) - [Get PoX info](#get-pox-info) - [Get Stacks node info](#get-stacks-node-info) @@ -169,10 +169,10 @@ const cycleDuration = await client.getCycleDuration(); // 120 ``` -### How much time is left (in seconds) until the next cycle begins? +### How much estimated time is left (in seconds) to submit a stacking transaction for the upcoming reward cycle? ```typescript -const secondsUntilNextCycle = await client.getSecondsUntilNextCycle(); +const seconds = await client.getSecondsUntilStackingDeadline(); // 600000 ``` diff --git a/packages/stacking/src/index.ts b/packages/stacking/src/index.ts index 04a5079e6..93282afcc 100644 --- a/packages/stacking/src/index.ts +++ b/packages/stacking/src/index.ts @@ -380,8 +380,7 @@ export class StackingClient { /** * Get account balance - * - * @returns promise resolves to a bigint if the operation succeeds + * @returns {Promise} resolves to a bigint if the operation succeeds */ async getAccountBalance(): Promise { return this.getAccountStatus().then(res => { @@ -391,8 +390,7 @@ export class StackingClient { /** * Get extended account balances - * - * @returns promise resolves to a bigint if the operation succeeds + * @returns {Promise} resolves to an AccountExtendedBalances response if the operation succeeds */ async getAccountExtendedBalances(): Promise { const url = this.network.getAccountExtendedBalancesApiUrl(this.address); @@ -401,8 +399,7 @@ export class StackingClient { /** * Get account balance of locked tokens - * - * @returns promise resolves to a bigint if the operation succeeds + * @returns {Promise} resolves to a bigint if the operation succeeds */ async getAccountBalanceLocked(): Promise { return this.getAccountStatus().then(res => BigInt(res.locked)); @@ -410,7 +407,6 @@ export class StackingClient { /** * Get reward cycle duration in seconds - * * @returns {Promise} that resolves to a number if the operation succeeds */ async getCycleDuration(): Promise { @@ -426,7 +422,6 @@ export class StackingClient { /** * Get the total burnchain rewards total for the set address - * * @returns {Promise} that resolves to TotalRewardsResponse or RewardsError */ async getRewardsTotalForBtcAddress(): Promise { @@ -436,7 +431,6 @@ export class StackingClient { /** * Get burnchain rewards for the set address - * * @returns {Promise} that resolves to RewardsResponse or RewardsError */ async getRewardsForBtcAddress( @@ -448,7 +442,6 @@ export class StackingClient { /** * Get burnchain rewards holders for the set address - * * @returns {Promise} that resolves to RewardHoldersResponse or RewardsError */ async getRewardHoldersForBtcAddress( @@ -460,7 +453,6 @@ export class StackingClient { /** * Get PoX address from reward set by index - * * @returns {Promise} that resolves to RewardSetInfo if the entry exists */ async getRewardSet(options: RewardSetOptions): Promise { @@ -485,8 +477,10 @@ export class StackingClient { /** * Get number of seconds until next reward cycle - * * @returns {Promise} that resolves to a number if the operation succeeds + * + * See also: + * - {@link getSecondsUntilStackingDeadline} */ async getSecondsUntilNextCycle(): Promise { const poxInfoPromise = this.getPoxInfo(); @@ -504,6 +498,25 @@ export class StackingClient { ); } + /** + * Get number of seconds until the end of the stacking deadline. + * This is the estimated time stackers have to submit their stacking + * transactions to be included in the upcoming reward cycle. + * @returns {Promise} that resolves to a number if the operation succeeds + * + * See also: + * - {@link getSecondsUntilNextCycle} + */ + async getSecondsUntilStackingDeadline(): Promise { + const poxInfoPromise = this.getPoxInfo(); + const targetBlockTimePromise = this.getTargetBlockTime(); + + return Promise.all([poxInfoPromise, targetBlockTimePromise]).then( + ([poxInfo, targetBlockTime]) => + poxInfo.next_cycle.blocks_until_prepare_phase * targetBlockTime + ); + } + /** * Get information on current PoX operation * @@ -592,7 +605,6 @@ export class StackingClient { /** * Check if account has minimum require amount of Stacks for stacking - * * @returns {Promise} that resolves to a bool if the operation succeeds */ async hasMinimumStx(): Promise { @@ -603,9 +615,7 @@ export class StackingClient { /** * Check if account can lock stx - * * @param {CanLockStxOptions} options - a required lock STX options object - * * @returns {Promise} that resolves to a StackingEligibility object if the operation succeeds */ async canStack({ poxAddress, cycles }: CanLockStxOptions): Promise { @@ -648,9 +658,7 @@ export class StackingClient { /** * Generate and broadcast a stacking transaction to lock STX - * * @param {LockStxOptions} options - a required lock STX options object - * * @returns {Promise} that resolves to a broadcasted txid if the operation succeeds */ async stack({ @@ -737,9 +745,7 @@ export class StackingClient { /** * As a delegatee, generate and broadcast a transaction to create a delegation relationship - * * @param {DelegateStxOptions} options - a required delegate STX options object - * * @returns {Promise} that resolves to a broadcasted txid if the operation succeeds */ async delegateStx({ @@ -774,9 +780,7 @@ export class StackingClient { /** * As a delegator, generate and broadcast transactions to stack for multiple delegatees. This will lock up tokens owned by the delegatees. - * * @param {DelegateStackStxOptions} options - a required delegate stack STX options object - * * @returns {Promise} that resolves to a broadcasted txid if the operation succeeds */ async delegateStackStx({ @@ -870,9 +874,7 @@ export class StackingClient { /** * As a delegator, generate and broadcast a transaction to commit partially committed delegatee tokens - * * @param {StackAggregationCommitOptions} options - a required stack aggregation commit options object - * * @returns {Promise} that resolves to a broadcasted txid if the operation succeeds */ async stackAggregationCommit({ @@ -938,7 +940,6 @@ export class StackingClient { /** * As a delegator, generate and broadcast a transaction to increase partial commitment committed delegatee tokens - * * @param {StackAggregationIncreaseOptions} options - a required stack aggregation increase options object * @category PoX-2 * @returns {Promise} that resolves to a broadcasted txid if the operation succeeds diff --git a/packages/stacking/tests/stacking.test.ts b/packages/stacking/tests/stacking.test.ts index cb06b96dd..59241ad59 100644 --- a/packages/stacking/tests/stacking.test.ts +++ b/packages/stacking/tests/stacking.test.ts @@ -1,28 +1,29 @@ import { bigIntToBytes, bytesToHex, hexToBytes } from '@stacks/common'; import { base58CheckDecode } from '@stacks/encryption'; -import { StacksTestnet } from '@stacks/network'; +import { StacksMainnet, StacksTestnet } from '@stacks/network'; import { AnchorMode, - bufferCV, ClarityType, + ReadOnlyFunctionOptions, + SignedContractCallOptions, + TupleCV, + bufferCV, intCV, noneCV, - ReadOnlyFunctionOptions, responseErrorCV, responseOkCV, - SignedContractCallOptions, someCV, standardPrincipalCV, trueCV, tupleCV, - TupleCV, uintCV, validateContractCall, } from '@stacks/transactions'; import fetchMock from 'jest-fetch-mock'; +import { StackingClient } from '../src'; import { PoXAddressVersion, StackingErrors } from '../src/constants'; import { decodeBtcAddress, poxAddressToBtcAddress } from '../src/utils'; -import { V2_POX_REGTEST_POX_3 } from './apiMockingHelpers'; +import { V2_POX_REGTEST_POX_3, setApiMocks } from './apiMockingHelpers'; const poxInfo = { contract_id: 'ST000000000000000000002AMW42H.pox', @@ -1084,3 +1085,24 @@ test('client operations with contract principal stacker', () => { ); expect(async () => await client.getStatus()).not.toThrow(); }); + +test('getSecondsUntilStackingDeadline', async () => { + const network = new StacksMainnet({ url: 'http://localhost:3999' }); + const client = new StackingClient('', network); + + setApiMocks({ + '/extended/v1/info/network_block_times': `{"testnet":{"target_block_time":120},"mainnet":{"target_block_time":600}}`, + '/v2/pox': `{"contract_id":"ST000000000000000000002AMW42H.pox-3","pox_activation_threshold_ustx":600058115845055,"first_burnchain_block_height":0,"current_burnchain_block_height":275,"prepare_phase_block_length":1,"reward_phase_block_length":4,"reward_slots":8,"rejection_fraction":3333333333333333,"total_liquid_supply_ustx":60005811584505576,"current_cycle":{"id":54,"min_threshold_ustx":1875190000000000,"stacked_ustx":0,"is_pox_active":false},"next_cycle":{"id":55,"min_threshold_ustx":1875190000000000,"min_increment_ustx":7500726448063,"stacked_ustx":0,"prepare_phase_start_block_height":279,"blocks_until_prepare_phase":4,"reward_phase_start_block_height":280,"blocks_until_reward_phase":5,"ustx_until_pox_rejection":14656114351294034000},"min_amount_ustx":1875190000000000,"prepare_cycle_length":1,"reward_cycle_id":54,"reward_cycle_length":5,"rejection_votes_left_required":14656114351294034000,"next_reward_cycle_in":5,"contract_versions":[{"contract_id":"ST000000000000000000002AMW42H.pox","activation_burnchain_block_height":0,"first_reward_cycle_id":0},{"contract_id":"ST000000000000000000002AMW42H.pox-2","activation_burnchain_block_height":107,"first_reward_cycle_id":22},{"contract_id":"ST000000000000000000002AMW42H.pox-3","activation_burnchain_block_height":111,"first_reward_cycle_id":23}]}`, + }); + + let seconds = await client.getSecondsUntilStackingDeadline(); + expect(seconds).toBe(4 * 10 * 60); // four blocks until prepare phase + + setApiMocks({ + '/extended/v1/info/network_block_times': `{"testnet":{"target_block_time":120},"mainnet":{"target_block_time":600}}`, + '/v2/pox': `{"contract_id":"ST000000000000000000002AMW42H.pox-3","pox_activation_threshold_ustx":600058812952055,"first_burnchain_block_height":0,"current_burnchain_block_height":344,"prepare_phase_block_length":1,"reward_phase_block_length":4,"reward_slots":8,"rejection_fraction":3333333333333333,"total_liquid_supply_ustx":60005881295205576,"current_cycle":{"id":68,"min_threshold_ustx":1875190000000000,"stacked_ustx":0,"is_pox_active":false},"next_cycle":{"id":69,"min_threshold_ustx":1875190000000000,"min_increment_ustx":7500735161900,"stacked_ustx":0,"prepare_phase_start_block_height":344,"blocks_until_prepare_phase":0,"reward_phase_start_block_height":345,"blocks_until_reward_phase":1,"ustx_until_pox_rejection":5198637306263702000},"min_amount_ustx":1875190000000000,"prepare_cycle_length":1,"reward_cycle_id":68,"reward_cycle_length":5,"rejection_votes_left_required":5198637306263702000,"next_reward_cycle_in":1,"contract_versions":[{"contract_id":"ST000000000000000000002AMW42H.pox","activation_burnchain_block_height":0,"first_reward_cycle_id":0},{"contract_id":"ST000000000000000000002AMW42H.pox-2","activation_burnchain_block_height":107,"first_reward_cycle_id":22},{"contract_id":"ST000000000000000000002AMW42H.pox-3","activation_burnchain_block_height":111,"first_reward_cycle_id":23}]}`, + }); + + seconds = await client.getSecondsUntilStackingDeadline(); + expect(seconds).toBe(0); // this time we are in the prepare phase +});