mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-25 13:57:54 +00:00
7a4bbeac25
- Update @pezkuwi/extension-inject to ^0.62.13 with proper /types exports - Update @pezkuwi/extension-dapp to ^0.62.13 - Update @pezkuwi/extension-compat-metamask to ^0.62.13 - Fix IconTheme type to include 'bizinikiwi' and 'pezkuwi' themes - Fix endpoint array issues (getTeleports -> direct array references) - Add type assertions for external package compatibility (acala, moonbeam, parallel) - Fix subspace.ts dynamic class typing - Fix conviction type in page-referenda - Update Pallet type names to Pezpallet prefix across codebase - Define InjectedExtension types locally for module resolution - Add styled-components DefaultTheme augmentation - Add react-copy-to-clipboard type declaration for React 18 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
172 lines
6.1 KiB
TypeScript
172 lines
6.1 KiB
TypeScript
// Copyright 2017-2026 @pezkuwi/app-teyrchains authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { Option, StorageKey } from '@pezkuwi/types';
|
|
import type { BlockNumber, WinningData } from '@pezkuwi/types/interfaces';
|
|
import type { AuctionInfo, WinnerData, Winning } from './types.js';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
|
|
import { createNamedHook, useApi, useBestNumber, useCall, useEventTrigger, useIsMountedRef } from '@pezkuwi/react-hooks';
|
|
import { BN, BN_ONE, BN_ZERO, u8aEq } from '@pezkuwi/util';
|
|
|
|
import { CROWD_PREFIX } from './constants.js';
|
|
import { useLeaseRanges } from './useLeaseRanges.js';
|
|
|
|
const FIRST_PARAM = [0];
|
|
|
|
function isNewWinners (a: WinnerData[], b: WinnerData[]): boolean {
|
|
return JSON.stringify({ w: a }) !== JSON.stringify({ w: b });
|
|
}
|
|
|
|
function isNewOrdering (a: WinnerData[], b: WinnerData[]): boolean {
|
|
return a.length !== b.length ||
|
|
a.some(({ firstSlot, lastSlot, paraId }, index) =>
|
|
!paraId.eq(b[index].paraId) ||
|
|
!firstSlot.eq(b[index].firstSlot) ||
|
|
!lastSlot.eq(b[index].lastSlot)
|
|
);
|
|
}
|
|
|
|
function extractWinners (ranges: [number, number][], auctionInfo: AuctionInfo, optData: Option<WinningData>): WinnerData[] {
|
|
return optData.isNone
|
|
? []
|
|
: optData.unwrap().reduce<WinnerData[]>((winners, optEntry, index): WinnerData[] => {
|
|
if (optEntry.isSome) {
|
|
const [accountId, paraId, value] = optEntry.unwrap();
|
|
const period = auctionInfo.leasePeriod || BN_ZERO;
|
|
const [first, last] = ranges[index];
|
|
|
|
winners.push({
|
|
accountId: accountId.toString(),
|
|
firstSlot: period.addn(first),
|
|
isCrowdloan: u8aEq(CROWD_PREFIX, accountId.subarray(0, CROWD_PREFIX.length)),
|
|
key: paraId.toString(),
|
|
lastSlot: period.addn(last),
|
|
paraId,
|
|
value
|
|
});
|
|
}
|
|
|
|
return winners;
|
|
}, []);
|
|
}
|
|
|
|
function createWinning ({ endBlock }: AuctionInfo, blockOffset: BN | null | undefined, winners: WinnerData[]): Winning {
|
|
return {
|
|
blockNumber: endBlock && blockOffset
|
|
? blockOffset.add(endBlock)
|
|
: blockOffset || BN_ZERO,
|
|
blockOffset: blockOffset || BN_ZERO,
|
|
total: winners.reduce((total, { value }) => total.iadd(value), new BN(0)),
|
|
winners
|
|
};
|
|
}
|
|
|
|
function extractData (ranges: [number, number][], auctionInfo: AuctionInfo, values: [StorageKey<[BlockNumber]>, Option<WinningData>][]): Winning[] {
|
|
return values
|
|
.sort(([{ args: [a] }], [{ args: [b] }]) => a.cmp(b))
|
|
.reduce((all: Winning[], [{ args: [blockOffset] }, optData]): Winning[] => {
|
|
const winners = extractWinners(ranges, auctionInfo, optData);
|
|
|
|
winners.length && (
|
|
all.length === 0 ||
|
|
isNewWinners(winners, all[all.length - 1].winners)
|
|
) && all.push(createWinning(auctionInfo, blockOffset, winners));
|
|
|
|
return all;
|
|
}, [])
|
|
.reverse();
|
|
}
|
|
|
|
function mergeCurrent (ranges: [number, number][], auctionInfo: AuctionInfo, prev: Winning[] | undefined, optCurrent: Option<WinningData>, blockOffset: BN): Winning[] | undefined {
|
|
const current = createWinning(auctionInfo, blockOffset, extractWinners(ranges, auctionInfo, optCurrent));
|
|
|
|
if (current.winners.length) {
|
|
if (!prev?.length) {
|
|
return [current];
|
|
}
|
|
|
|
if (isNewWinners(current.winners, prev[0].winners)) {
|
|
if (isNewOrdering(current.winners, prev[0].winners)) {
|
|
return [current, ...prev];
|
|
}
|
|
|
|
prev[0] = current;
|
|
|
|
return [...prev];
|
|
}
|
|
}
|
|
|
|
return prev;
|
|
}
|
|
|
|
function mergeFirst (ranges: [number, number][], auctionInfo: AuctionInfo, prev: Winning[] | undefined, optFirstData: Option<WinningData>): Winning[] | undefined {
|
|
if (prev && prev.length <= 1) {
|
|
const updated: Winning[] = prev || [];
|
|
const firstEntry = createWinning(auctionInfo, null, extractWinners(ranges, auctionInfo, optFirstData));
|
|
|
|
if (!firstEntry.winners.length) {
|
|
return updated;
|
|
} else if (!updated.length) {
|
|
return [firstEntry];
|
|
}
|
|
|
|
updated[updated.length - 1] = firstEntry;
|
|
|
|
return updated.slice();
|
|
}
|
|
|
|
return prev;
|
|
}
|
|
|
|
function useWinningDataImpl (auctionInfo?: AuctionInfo): Winning[] | undefined {
|
|
const { api } = useApi();
|
|
const mountedRef = useIsMountedRef();
|
|
const ranges = useLeaseRanges();
|
|
const [result, setResult] = useState<Winning[] | undefined>();
|
|
const bestNumber = useBestNumber();
|
|
const trigger = useEventTrigger([api.events.auctions?.BidAccepted]);
|
|
const triggerRef = useRef(trigger);
|
|
const initialEntries = useCall<[StorageKey<[BlockNumber]>, Option<WinningData>][]>(api.query.auctions?.winning.entries);
|
|
const optFirstData = useCall<Option<WinningData>>(api.query.auctions?.winning, FIRST_PARAM);
|
|
|
|
// should be fired once, all entries as an initial round
|
|
useEffect((): void => {
|
|
mountedRef.current && auctionInfo && initialEntries && setResult(
|
|
extractData(ranges, auctionInfo, initialEntries)
|
|
);
|
|
}, [auctionInfo, initialEntries, mountedRef, ranges]);
|
|
|
|
// when block 0 changes, update (typically in non-ending-period, static otherwise)
|
|
useEffect((): void => {
|
|
mountedRef.current && auctionInfo && optFirstData && setResult((prev) =>
|
|
mergeFirst(ranges, auctionInfo, prev, optFirstData)
|
|
);
|
|
}, [auctionInfo, optFirstData, mountedRef, ranges]);
|
|
|
|
// on a bid event, get the new entry (assuming the event really triggered, i.e. not just a block)
|
|
// and add it to the list when not duplicated. Additionally we cleanup after ourselves when endBlock
|
|
// gets cleared
|
|
useEffect((): void => {
|
|
if (auctionInfo?.endBlock && bestNumber && bestNumber.gt(auctionInfo.endBlock) && triggerRef.current !== trigger) {
|
|
const blockOffset = bestNumber.sub(auctionInfo.endBlock).iadd(BN_ONE);
|
|
|
|
triggerRef.current = trigger;
|
|
|
|
api.query.auctions
|
|
?.winning<Option<WinningData>>(blockOffset)
|
|
.then((optCurrent) =>
|
|
mountedRef.current && setResult((prev) =>
|
|
mergeCurrent(ranges, auctionInfo, prev, optCurrent, blockOffset)
|
|
)
|
|
)
|
|
.catch(console.error);
|
|
}
|
|
}, [api, bestNumber, auctionInfo, mountedRef, ranges, trigger, triggerRef]);
|
|
|
|
return result;
|
|
}
|
|
|
|
export default createNamedHook('useWinningData', useWinningDataImpl);
|