Initial commit: Pezkuwi SDK UI

Comprehensive web interface for interacting with Pezkuwi blockchain.

Features:
- Blockchain explorer
- Wallet management
- Staking interface
- Governance participation
- Developer tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 13:55:36 +03:00
commit d949863789
5831 changed files with 327739 additions and 0 deletions
@@ -0,0 +1,139 @@
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveBalancesAll, DeriveStakingAccount } from '@pezkuwi/api-derive/types';
import type { Accounts } from '@pezkuwi/react-hooks/ctx/types';
import type { UseAccountInfo } from '@pezkuwi/react-hooks/types';
import type { KeyringJson$Meta } from '@pezkuwi/ui-keyring/types';
import { BN } from '@pezkuwi/util';
import { balanceOf } from '../creation/balance.js';
import { makeStakingLedger } from '../creation/staking.js';
export interface Account {
balance: DeriveBalancesAll,
info: UseAccountInfo,
staking: DeriveStakingAccount
}
export type AccountsMap = Record<string, Account>;
export type Override<T> = {
[P in keyof T]?: T[P];
}
/**
* Test inputs structure
*/
export interface AccountOverrides {
meta?: Override<KeyringJson$Meta>;
balance?: Override<DeriveBalancesAll>;
staking?: Override<DeriveStakingAccount>;
info?: Override<UseAccountInfo>;
}
export const emptyAccounts: Accounts = {
allAccounts: [],
allAccountsHex: [],
areAccountsLoaded: true,
hasAccounts: false,
isAccount: () => true
};
// here it's extremely hard to reconstruct the entire DeriveBalancesAll upfront, so we incrementally add properties
// instead along the way; thus the need to tell the tsc we know what we are doing here
export const defaultBalanceAccount = {
accountNonce: new BN(1),
additional: [],
availableBalance: balanceOf(0),
freeBalance: balanceOf(0),
lockedBalance: balanceOf(0),
lockedBreakdown: [],
namedReserves: [],
reservedBalance: balanceOf(0)
} as unknown as DeriveBalancesAll;
// here it's extremely hard to reconstruct the entire DeriveStakingAccount upfront,
// so we set just the properties that we use in page-accounts
export const defaultStakingAccount = {
nextSessionIds: [],
nominators: [],
redeemable: balanceOf(0),
sessionIds: [],
stakingLedger: makeStakingLedger(0),
unlocking: [
{
remainingEras: new BN('1000000000'),
value: balanceOf(0)
},
{
remainingEras: new BN('2000000000'),
value: balanceOf(0)
},
{
remainingEras: new BN('3000000000'),
value: balanceOf(0)
}
]
} as unknown as DeriveStakingAccount;
export const defaultMeta: KeyringJson$Meta = {};
export const defaultAccountInfo = {
flags: {},
identity: { email: 'user@email.com', isExistent: true, judgements: [] },
tags: []
} as unknown as UseAccountInfo;
class MockAccountHooks {
public useAccounts: Accounts = emptyAccounts;
public accountsMap: AccountsMap = {};
public nonce: BN = new BN(1);
public setAccounts (accounts: [string, AccountOverrides][]): void {
this.useAccounts = {
allAccounts: accounts.map(([address]) => address),
allAccountsHex: [],
areAccountsLoaded: true,
hasAccounts: accounts && accounts.length !== 0,
isAccount: () => true
};
for (const [address, props] of accounts) {
const staking = { ...defaultStakingAccount };
const meta = { ...defaultMeta };
const balance = { ...defaultBalanceAccount };
const info = { ...defaultAccountInfo };
Object
.entries(props.meta || meta)
.forEach(([key, value]) => {
(meta as Record<string, unknown>)[key] = value;
});
Object
.entries(props.balance || balance)
.forEach(([key, value]) => {
(balance as Record<string, unknown>)[key] = value;
});
Object
.entries(props.staking || staking)
.forEach(([key, value]) => {
(staking as Record<string, unknown>)[key] = value;
});
Object
.entries(props.info || info)
.forEach(([key, value]) => {
(info as Record<string, unknown>)[key] = value;
});
this.accountsMap[address] = {
balance,
info,
staking
};
}
}
}
export const mockAccountHooks = new MockAccountHooks();
@@ -0,0 +1,29 @@
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Balance } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
import { formatBalance } from '@pezkuwi/util';
import { balanceOf } from '../creation/balance.js';
/**
* Creates a balance instance for testing purposes which most often do not need to specify/use decimal part.
* @param amountInt Integer part of the balance number
* @param decimalsString Decimals part of the balance number. Note! This is a string sequence just after '.' separator
* that is the point that separates integers from decimals. E.g. (100, 4567) => 100.45670000...00
*/
export function balance (amountInt: number, decimalsString?: string): Balance {
const decimalsPadded = (decimalsString || '').padEnd(12, '0');
return balanceOf(amountInt.toString() + decimalsPadded);
}
export function showBalance (amount: number): string {
return format(balance(amount));
}
export function format (amount: Balance | BN): string {
return formatBalance(amount, { decimals: 12, forceUnit: '-', withUnit: true });
}
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
// SPDX-License-Identifier: Apache-2.0
export * from './accountDefaults.js';
export * from './balance.js';
export * from './mockApiHooks.js';
export * from './renderedScreenUtils.js';
export * from './waitFor.js';
@@ -0,0 +1,41 @@
// Copyright 2017-2026 @pezkuwi/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registrar } from '@pezkuwi/react-hooks/types';
import type { H256, Multisig, ProxyDefinition, RegistrationJudgement, Voting } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
class MockApiHooks {
public multisigApprovals: [H256, Multisig][] | undefined = [];
public delegations: Voting[] | undefined;
public proxies: [ProxyDefinition[], BN][] | undefined = [];
public subs: string[] | undefined = [];
public judgements: RegistrationJudgement[] | undefined = [];
public registrars: Registrar[] = [];
public setDelegations (delegations: Voting[]) {
this.delegations = delegations;
}
public setMultisigApprovals (multisigApprovals: [H256, Multisig][]) {
this.multisigApprovals = multisigApprovals;
}
public setProxies (proxies: [ProxyDefinition[], BN][]) {
this.proxies = proxies;
}
public setSubs (subs: string[] | undefined) {
this.subs = subs;
}
public setJudgements (judgements: RegistrationJudgement[] | undefined) {
this.judgements = judgements;
}
public setRegistrars (registrars: Registrar[]) {
this.registrars = registrars;
}
}
export const mockApiHooks = new MockApiHooks();
@@ -0,0 +1,28 @@
// Copyright 2017-2026 @pezkuwi/test-supports authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* global expect */
import { fireEvent, screen } from '@testing-library/react';
export const clickButton = async (buttonName: string): Promise<void> => {
const button = await screen.findByRole('button', { name: buttonName });
fireEvent.click(button);
};
export const assertText = async (text: string): Promise<HTMLElement> => {
return screen.findByText(text);
};
export const fillInput = (inputTestId: string, value: string): void => {
const nameInput = screen.getByTestId(inputTestId);
fireEvent.change(nameInput, { target: { value } });
};
export const assertButtonDisabled = (buttonName: string): void => {
const button = screen.getByRole('button', { name: buttonName });
expect(button).toHaveClass('isDisabled');
};
@@ -0,0 +1,25 @@
// Copyright 2017-2026 @pezkuwi/test-support authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { WaitOptions } from '../types.js';
export async function waitFor (predicate: () => Promise<boolean> | boolean, { interval = 500, timeout = 10000 }: WaitOptions = {}): Promise<boolean> {
const asyncPredicate = () => Promise.resolve(predicate());
let elapsed = 0;
while (!(await asyncPredicate())) {
if (elapsed > timeout) {
throw Error('Timeout');
}
await sleep(interval);
elapsed += interval;
}
return true;
}
export function sleep (ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}