mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-24 16:57:59 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChainBlockConstants, ChainConstants, CoretimeInformation } from '@pezkuwi/react-hooks/types';
|
||||
import type { GetResponse, RegionInfo, RelayName } from '../types.js';
|
||||
|
||||
import { CoreTimeTypes } from '@pezkuwi/react-hooks/constants';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
type FirstCycleStartType = Record<
|
||||
'block' | 'timeslice',
|
||||
Record<
|
||||
'coretime',
|
||||
Record<RelayName, number>
|
||||
>
|
||||
>;
|
||||
|
||||
// Blocks on the Coretime Chain
|
||||
export const FirstCycleStart: FirstCycleStartType = {
|
||||
block: {
|
||||
coretime: {
|
||||
dicle: 53793,
|
||||
'paseo testnet': 22316,
|
||||
pezkuwi: 100988,
|
||||
zagros: 7363
|
||||
}
|
||||
},
|
||||
timeslice: {
|
||||
coretime: {
|
||||
dicle: 284920,
|
||||
'paseo testnet': 38469,
|
||||
pezkuwi: 282525,
|
||||
zagros: 245402
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const coretimeTypeColours: Record<string, string> = {
|
||||
[CoreTimeTypes.Reservation]: 'orange',
|
||||
[CoreTimeTypes.Lease]: 'blue',
|
||||
[CoreTimeTypes['Bulk Coretime']]: 'pink'
|
||||
};
|
||||
|
||||
export function formatDate (date: Date, time = false) {
|
||||
const day = date.getDate();
|
||||
const month = date.toLocaleString('default', { month: 'short' });
|
||||
const year = date.getFullYear();
|
||||
|
||||
if (time) {
|
||||
const hours = date.getHours().toString().padStart(2, '0');
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
return `${day} ${month} ${year}, ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
return `${day} ${month} ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives you a date for the target timeslice
|
||||
*
|
||||
* Relay chain info:
|
||||
* blockTime = 6000 ms
|
||||
* BlocksPerTimeslice = 80
|
||||
* Default Regoin = 5040 timeslices
|
||||
*
|
||||
* Calculation:
|
||||
* TargetBlock = TargetTimeslice * BlocksPerTimeslice
|
||||
* Block Time Difference = |TargetBlock - latest Block| * blockTime
|
||||
*
|
||||
* Estimate timestamp =
|
||||
* if targetBlock is before the latestBlock
|
||||
* now minus block time difference
|
||||
* else
|
||||
* now plus block time difference
|
||||
*/
|
||||
export const estimateTime = (
|
||||
targetTimeslice: string | number,
|
||||
latestBlock: number,
|
||||
{ blocksPerTimeslice: blocksPerTs, blocktimeMs }: ChainBlockConstants
|
||||
): { timestamp: number, formattedDate: string } | null => {
|
||||
if (!latestBlock || !targetTimeslice) {
|
||||
console.error('Invalid input: one or more inputs are missing');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const now = new BN(Date.now());
|
||||
const blockTime = new BN(blocktimeMs); // Average block time in milliseconds (6 seconds)
|
||||
const blocksPerTimeslice = new BN(blocksPerTs);
|
||||
const targetBlock = new BN(Number(targetTimeslice)).mul(blocksPerTimeslice);
|
||||
const latestBlockBN = new BN(latestBlock);
|
||||
const blockDifference = targetBlock.sub(latestBlockBN);
|
||||
const timeDifference = blockDifference.mul(blockTime);
|
||||
const estTimestamp = now.add(timeDifference);
|
||||
|
||||
return {
|
||||
formattedDate: formatDate(new Date(estTimestamp.toNumber())),
|
||||
timestamp: estTimestamp.toNumber()
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in calculation:', error);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Factory function to create helper functions for converting timeslices to blocks and vice versa.
|
||||
*
|
||||
* @returns An object containing blocks and timeslices conversion functions.
|
||||
*/
|
||||
export const createGet = (constants: ChainConstants): GetResponse => ({
|
||||
blocks: {
|
||||
/**
|
||||
* Convert timeslices to Coretime blocks.
|
||||
*
|
||||
* @param ts - Number of timeslices.
|
||||
* @returns Number of Coretime blocks.
|
||||
*/
|
||||
coretime: (ts: number) => {
|
||||
return ts * constants.coretime.blocksPerTimeslice;
|
||||
},
|
||||
/**
|
||||
* Convert timeslices to Relay blocks.
|
||||
*
|
||||
* @param ts - Number of timeslices.
|
||||
* @returns Number of Relay blocks.
|
||||
*/
|
||||
relay: (ts: number) => {
|
||||
return ts * constants.relay.blocksPerTimeslice;
|
||||
}
|
||||
},
|
||||
timeslices: {
|
||||
/**
|
||||
* Convert Coretime blocks to timeslices.
|
||||
*
|
||||
* @param blocks - Number of Coretime blocks.
|
||||
* @returns Number of timeslices.
|
||||
*/
|
||||
coretime: (blocks: number) => {
|
||||
return blocks / constants.coretime.blocksPerTimeslice;
|
||||
},
|
||||
/**
|
||||
* Convert Relay blocks to timeslices.
|
||||
*
|
||||
* @param blocks - Number of Relay blocks.
|
||||
* @returns Number of timeslices.
|
||||
*/
|
||||
relay: (blocks: number) => {
|
||||
return blocks / constants.relay.blocksPerTimeslice;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the start and end of the current region
|
||||
* broker.saleInfo call returns the start/end of the next region always
|
||||
*
|
||||
* The end of the current region is the start of the next region, which is returned by broker.saleInfo call
|
||||
*
|
||||
* @param saleInfo - The sale information
|
||||
* @param config - The broker configuration
|
||||
*
|
||||
* @returns The start and end of the current region
|
||||
*/
|
||||
export const getCurrentRegionStartEndTs = (saleInfo: RegionInfo, regionLength: number) => {
|
||||
return {
|
||||
currentRegionEndTs: saleInfo.regionBegin,
|
||||
currentRegionStartTs: saleInfo.regionBegin - regionLength
|
||||
};
|
||||
};
|
||||
|
||||
export const getAvailableNumberOfCores = (coretimeInfo: CoretimeInformation) =>
|
||||
Number(coretimeInfo?.salesInfo?.coresOffered) - Number(coretimeInfo?.salesInfo.coresSold);
|
||||
|
||||
export const constructSubscanQuery = (dateStart: string, dateEnd: string, chainName: string, module = 'broker', call = 'purchase') => {
|
||||
const page = 1;
|
||||
const pageSize = 25;
|
||||
const signed = 'all';
|
||||
const baseURL = `https://coretime-${chainName}.subscan.io/extrinsic`;
|
||||
|
||||
return `${baseURL}?page=${page}&time_dimension=date&page_size=${pageSize}&module=${module}&signed=${signed}&call=${call}&date_start=${dateStart}&date_end=${dateEnd}`;
|
||||
};
|
||||
@@ -0,0 +1,292 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChainConstants, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord } from '@pezkuwi/react-hooks/types';
|
||||
import type { GetResponse, PhaseConfig, RegionInfo, RelayName, SaleParameters } from '../types.js';
|
||||
|
||||
import { type ProgressBarSection } from '@pezkuwi/react-components/types';
|
||||
import { BN, formatBalance } from '@pezkuwi/util';
|
||||
|
||||
import { PhaseName } from '../constants.js';
|
||||
import { createGet, estimateTime, FirstCycleStart, getCurrentRegionStartEndTs } from './index.js';
|
||||
|
||||
// We are scaling everything to avoid floating point precision issues.
|
||||
const SCALE = new BN(10000);
|
||||
|
||||
/**
|
||||
* Formats a BN value to a human-readable balance string with proper units
|
||||
*
|
||||
* @param num - The BN value to format
|
||||
* @returns A formatted string with the balance value and unit
|
||||
*/
|
||||
export const formatBNToBalance = (num: BN) => formatBalance(num, { forceUnit: formatBalance.getDefaults().unit, withAll: true, withUnit: true });
|
||||
|
||||
export const leadinFactorAt = (scaledWhen: BN): BN => {
|
||||
const scaledHalf = SCALE.div(new BN(2)); // 0.5 scaled to 10000
|
||||
|
||||
if (scaledWhen.lte(scaledHalf)) {
|
||||
// First half of the graph, steeper slope
|
||||
return SCALE.mul(new BN(100)).sub(scaledWhen.mul(new BN(180)));
|
||||
} else {
|
||||
// Second half of the graph, flatter slope
|
||||
return SCALE.mul(new BN(19)).sub(scaledWhen.mul(new BN(18)));
|
||||
}
|
||||
};
|
||||
|
||||
export const getCorePriceAt = (blockNow: number | null, saleInfo: PalletBrokerSaleInfoRecord | undefined): BN => {
|
||||
if (!saleInfo || !blockNow) {
|
||||
return new BN(0);
|
||||
}
|
||||
|
||||
const { endPrice, leadinLength, saleStart } = saleInfo;
|
||||
|
||||
// Explicit conversion to BN
|
||||
const blockNowBn = new BN(blockNow);
|
||||
const saleStartBn = new BN(saleStart);
|
||||
const leadinLengthBn = new BN(leadinLength);
|
||||
|
||||
// Elapsed time since the start of the sale, constrained to not exceed the total lead-in period
|
||||
const elapsedTimeSinceSaleStart = blockNowBn.sub(saleStartBn);
|
||||
const cappedElapsedTime = elapsedTimeSinceSaleStart.lt(leadinLengthBn)
|
||||
? elapsedTimeSinceSaleStart
|
||||
: leadinLengthBn;
|
||||
|
||||
const scaledProgress = cappedElapsedTime.mul(new BN(10000)).div(leadinLengthBn);
|
||||
/**
|
||||
* Progress is a normalized value between 0 and 1, where:
|
||||
*
|
||||
* 0 means the sale just started.
|
||||
* 1 means the sale is at the end of the lead-in period.
|
||||
*
|
||||
* We are scaling it to avoid floating point precision issues.
|
||||
*/
|
||||
const leadinFactor = leadinFactorAt(scaledProgress);
|
||||
const scaledPrice = leadinFactor.mul(endPrice).div(SCALE);
|
||||
|
||||
return scaledPrice;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current sale number
|
||||
*
|
||||
* @param currentRegionEnd - The end of the current region
|
||||
* @param chainName - The name of the chain
|
||||
* @param config - broker.configuration call response
|
||||
*
|
||||
* @returns The current sale number
|
||||
*/
|
||||
export const getCurrentSaleNumber = (
|
||||
currentRegionEnd: number,
|
||||
relayName: RelayName,
|
||||
config: Pick<PalletBrokerConfigRecord, 'interludeLength' | 'leadinLength' | 'regionLength'>
|
||||
): number => {
|
||||
if (!relayName || !currentRegionEnd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return Math.ceil((currentRegionEnd - FirstCycleStart.timeslice.coretime[relayName]) / config.regionLength);
|
||||
};
|
||||
|
||||
export const determinePhaseName = (
|
||||
lastCommittedTimeslice: number,
|
||||
currentRegionStart: number,
|
||||
interludeLengthTs: number,
|
||||
leadInLengthTs: number): typeof PhaseName[keyof typeof PhaseName] => {
|
||||
const progress = lastCommittedTimeslice - currentRegionStart;
|
||||
|
||||
if (progress < interludeLengthTs) {
|
||||
return PhaseName.Renewals;
|
||||
}
|
||||
|
||||
if (progress < interludeLengthTs + leadInLengthTs) {
|
||||
return PhaseName.PriceDiscovery;
|
||||
}
|
||||
|
||||
return PhaseName.FixedPrice;
|
||||
};
|
||||
|
||||
export const getSaleProgress = (
|
||||
lastCommittedTimeslice: number | undefined,
|
||||
currentRegionStartTs: number,
|
||||
interludeLengthTs: number,
|
||||
leadInLengthTs: number,
|
||||
regionBegin: number): ProgressBarSection[] => {
|
||||
if (!lastCommittedTimeslice || !currentRegionStartTs || !interludeLengthTs || !leadInLengthTs || !regionBegin) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const progress = lastCommittedTimeslice - currentRegionStartTs;
|
||||
|
||||
return [
|
||||
{
|
||||
label: PhaseName.Renewals,
|
||||
total: interludeLengthTs,
|
||||
value: Math.min(progress, interludeLengthTs)
|
||||
},
|
||||
{
|
||||
label: PhaseName.PriceDiscovery,
|
||||
total: leadInLengthTs,
|
||||
value: Math.min(Math.max(progress - interludeLengthTs, 0), leadInLengthTs)
|
||||
},
|
||||
{
|
||||
label: PhaseName.FixedPrice,
|
||||
total: regionBegin - currentRegionStartTs - interludeLengthTs - leadInLengthTs,
|
||||
value: Math.max(progress - interludeLengthTs - leadInLengthTs, 0)
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const makeConfig = (startTs: number, endTs: number, get: GetResponse, getDate: (ts: number) => string | null, phaseName?: typeof PhaseName[keyof typeof PhaseName]) => {
|
||||
return {
|
||||
end: {
|
||||
blocks: {
|
||||
coretime: get.blocks.coretime(endTs),
|
||||
relay: get.blocks.relay(endTs)
|
||||
},
|
||||
date: getDate(endTs),
|
||||
ts: endTs
|
||||
},
|
||||
name: phaseName ?? '',
|
||||
start: {
|
||||
blocks: {
|
||||
coretime: get.blocks.coretime(startTs),
|
||||
relay: get.blocks.relay(startTs)
|
||||
},
|
||||
date: getDate(startTs),
|
||||
ts: startTs
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
const getPhaseConfiguration = (
|
||||
currentRegionStart: number,
|
||||
regionLength: number,
|
||||
interludeLengthTs: number,
|
||||
leadInLengthTs: number,
|
||||
lastCommittedTimeslice: number,
|
||||
constants: ChainConstants
|
||||
): PhaseConfig => {
|
||||
const renewalsEndTs = currentRegionStart + interludeLengthTs;
|
||||
const priceDiscoveryEndTs = renewalsEndTs + leadInLengthTs;
|
||||
const fixedPriceLengthTs = regionLength - interludeLengthTs - leadInLengthTs;
|
||||
const fixedPriceEndTs = priceDiscoveryEndTs + fixedPriceLengthTs;
|
||||
const get = createGet(constants);
|
||||
const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null;
|
||||
|
||||
return {
|
||||
config: {
|
||||
[PhaseName.FixedPrice]: makeConfig(priceDiscoveryEndTs, fixedPriceEndTs, get, getDate, PhaseName.FixedPrice),
|
||||
[PhaseName.PriceDiscovery]: makeConfig(renewalsEndTs, priceDiscoveryEndTs, get, getDate, PhaseName.PriceDiscovery),
|
||||
[PhaseName.Renewals]: makeConfig(currentRegionStart, renewalsEndTs, get, getDate, PhaseName.Renewals)
|
||||
},
|
||||
currentPhaseName: determinePhaseName(lastCommittedTimeslice, currentRegionStart, interludeLengthTs, leadInLengthTs)
|
||||
};
|
||||
};
|
||||
|
||||
export const getSaleParameters = (
|
||||
{ config, constants, salesInfo }: {salesInfo: RegionInfo, config: Pick<PalletBrokerConfigRecord, 'interludeLength' | 'leadinLength' | 'regionLength'>, constants: ChainConstants},
|
||||
relayName: RelayName,
|
||||
lastCommittedTimeslice: number,
|
||||
chosenSaleNumber = -1
|
||||
): SaleParameters => {
|
||||
const get = createGet(constants);
|
||||
const interludeLengthTs = get.timeslices.coretime(config.interludeLength);
|
||||
const leadInLengthTs = get.timeslices.coretime(config.leadinLength);
|
||||
let { currentRegionEndTs, currentRegionStartTs } = getCurrentRegionStartEndTs(salesInfo, config.regionLength);
|
||||
const getDate = (ts: number) => estimateTime(ts, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null;
|
||||
const saleNumber = getCurrentSaleNumber(currentRegionEndTs, relayName, config);
|
||||
|
||||
let currentRegionInfo: SaleParameters['currentRegion'];
|
||||
|
||||
if (chosenSaleNumber !== -1) {
|
||||
// A hack for Dicle as one of the sales had an unusual length
|
||||
// checked against Subscan historical sales
|
||||
if (relayName === 'dicle') {
|
||||
const irregularRegionLength = 848;
|
||||
|
||||
if (chosenSaleNumber === 0) {
|
||||
currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName];
|
||||
currentRegionEndTs = currentRegionStartTs + config.regionLength;
|
||||
} else if (chosenSaleNumber === 1) {
|
||||
currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength;
|
||||
// that particular sale #2 was only 848 blocks long
|
||||
currentRegionEndTs = currentRegionStartTs + irregularRegionLength;
|
||||
} else {
|
||||
currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength * (chosenSaleNumber - 1) + irregularRegionLength;
|
||||
currentRegionEndTs = currentRegionStartTs + config.regionLength;
|
||||
}
|
||||
} else {
|
||||
currentRegionStartTs = FirstCycleStart.timeslice.coretime[relayName] + config.regionLength * chosenSaleNumber;
|
||||
currentRegionEndTs = currentRegionStartTs + config.regionLength;
|
||||
}
|
||||
|
||||
currentRegionInfo = {
|
||||
end: {
|
||||
blocks: {
|
||||
// the coretime blocks cannot be calculated as historically the regions are not 201,600 blocks long, they deviate from 2,212 to 1,417 blocks
|
||||
coretime: 0,
|
||||
relay: get.blocks.relay(currentRegionEndTs)
|
||||
},
|
||||
date: getDate(currentRegionEndTs) ?? '',
|
||||
ts: currentRegionEndTs
|
||||
},
|
||||
start: {
|
||||
blocks: {
|
||||
|
||||
coretime: 0,
|
||||
relay: get.blocks.relay(currentRegionStartTs)
|
||||
},
|
||||
date: getDate(currentRegionStartTs) ?? '',
|
||||
ts: currentRegionStartTs
|
||||
}
|
||||
};
|
||||
} else {
|
||||
currentRegionInfo = makeConfig(currentRegionStartTs, currentRegionEndTs, get, getDate) as SaleParameters['currentRegion'];
|
||||
}
|
||||
|
||||
let phaseConfig: PhaseConfig | null = null;
|
||||
|
||||
if (currentRegionEndTs - currentRegionStartTs === config.regionLength) {
|
||||
phaseConfig = getPhaseConfiguration(
|
||||
currentRegionStartTs,
|
||||
config.regionLength,
|
||||
interludeLengthTs,
|
||||
leadInLengthTs,
|
||||
lastCommittedTimeslice,
|
||||
constants
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
currentRegion: currentRegionInfo,
|
||||
interlude: {
|
||||
blocks: config.interludeLength,
|
||||
ts: interludeLengthTs
|
||||
},
|
||||
leadin: {
|
||||
blocks: config.leadinLength,
|
||||
ts: leadInLengthTs
|
||||
},
|
||||
phaseConfig,
|
||||
regionForSale: {
|
||||
end: {
|
||||
blocks: {
|
||||
coretime: 0,
|
||||
relay: currentRegionInfo.end.blocks.relay + get.blocks.relay(config.regionLength)
|
||||
},
|
||||
date: estimateTime(currentRegionInfo.end.ts + config.regionLength, get.blocks.relay(lastCommittedTimeslice), constants.relay)?.formattedDate ?? null,
|
||||
ts: currentRegionInfo.end.ts + config.regionLength
|
||||
},
|
||||
start: {
|
||||
blocks: {
|
||||
coretime: 0,
|
||||
relay: currentRegionInfo.end.blocks.relay
|
||||
},
|
||||
date: currentRegionInfo.end.date,
|
||||
ts: currentRegionInfo.end.ts
|
||||
}
|
||||
},
|
||||
saleNumber
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user