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:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
+185
View File
@@ -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}`;
};
+292
View File
@@ -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
};
};