Update domain references to pezkuwichain.app and rebrand from polkadot

This commit is contained in:
2026-01-06 12:21:11 +03:00
commit c2913a65d9
401 changed files with 23179 additions and 0 deletions
@@ -0,0 +1,111 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
/* eslint-disable jest/expect-expect */
import type * as _ from '@pezkuwi/dev-test/globals.d.ts';
import type { AccountJson, AccountWithChildren } from '@pezkuwi/extension-base/background/types';
import { buildHierarchy } from './buildHierarchy.js';
const genesisExample = {
KUSAMA: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe',
POLKADOT: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
} as const;
const testHierarchy = (accounts: AccountJson[], expected: AccountWithChildren[]): void => {
expect(buildHierarchy(accounts)).toEqual(expected);
};
describe('Use Account Hierarchy', () => {
const acc = (address: string, parentAddress?: string, whenCreated?: number, name?: string, suri?: string): {
address: string;
name?: string;
parentAddress?: string;
suri?: string;
whenCreated?: number;
} => ({ address, name, parentAddress, suri, whenCreated });
it('for empty account list, returns empty list', () => {
testHierarchy([], []);
});
it('returns one account', () => {
testHierarchy([acc('a')], [acc('a')]);
});
it('puts child account into children field of parent: single child', () => {
testHierarchy([acc('a'), acc('b', 'a')], [
{ address: 'a', children: [acc('b', 'a')], name: undefined, parentAddress: undefined, suri: undefined, whenCreated: undefined }
]);
});
it('puts child account into children field of parent: more children', () => {
testHierarchy([acc('a'), acc('b', 'a'), acc('c', 'a')], [
{ address: 'a', children: [acc('b', 'a'), acc('c', 'a')], name: undefined, parentAddress: undefined, suri: undefined, whenCreated: undefined }
]);
});
it('puts child account into children field of parent: 2 roots', () => {
testHierarchy([acc('a'), acc('b', 'a'), acc('c', 'a'), acc('d')], [
{ address: 'a', children: [acc('b', 'a'), acc('c', 'a')], name: undefined, parentAddress: undefined, suri: undefined, whenCreated: undefined },
acc('d')
]);
});
it('handles grandchildren', () => {
testHierarchy([acc('a'), acc('b', 'a'), acc('c', 'b')], [{
address: 'a',
children: [{
...acc('b', 'a'),
children: [acc('c', 'b')]
}],
name: undefined,
parentAddress: undefined,
suri: undefined,
whenCreated: undefined
}]);
});
it('sorts accounts by network', () => {
testHierarchy(
[{ address: 'b', genesisHash: genesisExample.KUSAMA }, { address: 'a', genesisHash: genesisExample.POLKADOT }, { address: 'c', genesisHash: genesisExample.KUSAMA }],
[{ address: 'b', genesisHash: genesisExample.KUSAMA }, { address: 'c', genesisHash: genesisExample.KUSAMA }, { address: 'a', genesisHash: genesisExample.POLKADOT }]
);
});
it('sorts accounts by network and name', () => {
testHierarchy(
[{ address: 'b', genesisHash: genesisExample.KUSAMA, name: 'b-last-kusama' }, { address: 'a', genesisHash: genesisExample.POLKADOT }, { address: 'c', genesisHash: genesisExample.KUSAMA, name: 'a-first-kusama' }],
[{ address: 'c', genesisHash: genesisExample.KUSAMA, name: 'a-first-kusama' }, { address: 'b', genesisHash: genesisExample.KUSAMA, name: 'b-last-kusama' }, { address: 'a', genesisHash: genesisExample.POLKADOT }]
);
});
it('sorts accounts by name and creation date', () => {
testHierarchy(
[acc('b', undefined, 2, 'b'), acc('z', undefined, 1, 'b'), acc('a', undefined, 4, 'a')],
[{ address: 'a', name: 'a', parentAddress: undefined, suri: undefined, whenCreated: 4 }, { address: 'z', name: 'b', parentAddress: undefined, suri: undefined, whenCreated: 1 }, { address: 'b', name: 'b', parentAddress: undefined, suri: undefined, whenCreated: 2 }]
);
});
it('sorts account children by name and path', () => {
testHierarchy(
[acc('a', undefined, 1, 'a'), acc('b', 'a', 1, 'b', '/2'), acc('b', 'a', 1, 'b', '/0')],
[{ address: 'a', children: [acc('b', 'a', 1, 'b', '/0'), acc('b', 'a', 1, 'b', '/2')], name: 'a', parentAddress: undefined, suri: undefined, whenCreated: 1 }]
);
});
it('sorts accounts with children by name and creation date', () => {
testHierarchy(
[acc('b', undefined, 2, 'b'), acc('z', undefined, 1, 'b'), acc('d', 'b', 2, 'd'), acc('c', 'b', 3, 'c'), acc('a', undefined, 4, 'a')],
[{ address: 'a', name: 'a', parentAddress: undefined, suri: undefined, whenCreated: 4 }, { address: 'z', name: 'b', parentAddress: undefined, suri: undefined, whenCreated: 1 }, { address: 'b', children: [acc('c', 'b', 3, 'c'), acc('d', 'b', 2, 'd')], name: 'b', parentAddress: undefined, suri: undefined, whenCreated: 2 }]
);
});
it('if creation time is missing, puts account at the back of a list', () => {
testHierarchy(
[acc('a'), acc('b', undefined, 2), acc('c', undefined, 1)],
[acc('c', undefined, 1), acc('b', undefined, 2), acc('a')]
);
});
});
@@ -0,0 +1,76 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountJson, AccountWithChildren } from '@pezkuwi/extension-base/background/types';
import getNetworkMap from './getNetworkMap.js';
type ChildFilter = (account: AccountJson) => AccountWithChildren;
function compareByCreation (a: AccountJson, b: AccountJson): number {
return (a.whenCreated || Infinity) - (b.whenCreated || Infinity);
}
function compareByName (a: AccountJson, b: AccountJson): number {
const nameA = a.name?.toUpperCase() || '';
const nameB = b.name?.toUpperCase() || '';
return nameA.localeCompare(nameB);
}
function compareByPath (a: AccountJson, b: AccountJson): number {
const suriA = a.suri?.toUpperCase() || '';
const suriB = b.suri?.toUpperCase() || '';
return suriA.localeCompare(suriB);
}
function compareByNetwork (a: AccountJson, b: AccountJson): number {
const networkMap = getNetworkMap();
const networkA = networkMap.get(a?.genesisHash || '') || '';
const networkB = networkMap.get(b?.genesisHash || '') || '';
return networkA.localeCompare(networkB);
}
function compareByPathThenCreation (a: AccountJson, b: AccountJson): number {
// if the paths are equal, compare by creation time
return compareByPath(a, b) || compareByCreation(a, b);
}
function compareByNameThenPathThenCreation (a: AccountJson, b: AccountJson): number {
// This comparison happens after an initial sorting by network.
// if the 2 accounts are from different networks, don't touch their order
if (a.genesisHash !== b.genesisHash) {
return 0;
}
// if the names are equal, compare by path then creation time
return compareByName(a, b) || compareByPathThenCreation(a, b);
}
export function accountWithChildren (accounts: AccountJson[]): ChildFilter {
return (account: AccountJson): AccountWithChildren => {
const children = accounts
.filter(({ parentAddress }) => account.address === parentAddress)
.map(accountWithChildren(accounts))
.sort(compareByNameThenPathThenCreation);
return children.length === 0
? account
: { children, ...account };
};
}
export function buildHierarchy (accounts: AccountJson[]): AccountWithChildren[] {
return accounts
.filter(({ parentAddress }) =>
// it is a parent
!parentAddress ||
// we don't have a parent for this one
!accounts.some(({ address }) => parentAddress === address)
)
.map(accountWithChildren(accounts))
.sort(compareByNetwork)
.sort(compareByNameThenPathThenCreation);
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { MetadataDefBase } from '@pezkuwi/extension-inject/types';
import { selectableNetworks } from '@pezkuwi/networks';
const hashes: MetadataDefBase[] = selectableNetworks
.filter(({ genesisHash }) => !!genesisHash.length)
.map((network) => ({
chain: network.displayName,
genesisHash: network.genesisHash[0],
icon: network.icon,
ss58Format: network.prefix
}));
export default hashes;
@@ -0,0 +1,6 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeypairType } from '@pezkuwi/util-crypto/types';
export const DEFAULT_TYPE: KeypairType = 'sr25519';
@@ -0,0 +1,46 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type React from 'react';
interface Option {
info?: string;
isDisabled?: boolean;
isHeader?: boolean;
text: React.ReactNode;
value: string | number;
}
export default function getLanguageOptions (): Option[] {
return [
// default/native
{
text: 'English',
value: 'en'
},
{
text: '汉语',
value: 'zh'
},
{
text: 'Français',
value: 'fr'
},
{
text: 'Türkce',
value: 'tr'
},
{
text: 'Polski',
value: 'pl'
},
{
text: 'ภาษาไทย',
value: 'th'
},
{
text: 'اردو',
value: 'ur'
}
];
}
@@ -0,0 +1,14 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import chains from './chains.js';
export default function getNetworkMap (): Map<string, string> {
const res = new Map<string, string>();
chains.forEach((chain) => {
res.set(chain.genesisHash, chain.chain);
});
return res;
}
@@ -0,0 +1,6 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
export default function (parentName?: string | null, suri?: string): string {
return `${parentName || ''} ${suri || ''}`;
}
@@ -0,0 +1,6 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { selectableNetworks } from '@pezkuwi/networks';
export default selectableNetworks.filter((network) => network.hasLedgerSupport);
@@ -0,0 +1,32 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type * as _ from '@pezkuwi/dev-test/globals.d.ts';
import { nextDerivationPath } from './nextDerivationPath.js';
describe('Generate Derivation Path', () => {
const acc = (address: string, parentAddress?: string): {
address: string;
parentAddress?: string;
} => ({
address,
parentAddress
});
it('generates path for first masters child', () => {
expect(nextDerivationPath([acc('a')], 'a')).toEqual('//0');
});
it('generates path for third masters child', () => {
expect(nextDerivationPath([acc('a'), acc('b', 'a'), acc('c', 'a')], 'a')).toEqual('//2');
});
it('generates path for masters child when another root exists', () => {
expect(nextDerivationPath([acc('a'), acc('b', 'a'), acc('c', 'a'), acc('d')], 'a')).toEqual('//2');
});
it('generates path for masters grandchild', () => {
expect(nextDerivationPath([acc('a'), acc('b', 'a'), acc('c', 'b'), acc('d', 'b')], 'b')).toEqual('//2');
});
});
@@ -0,0 +1,10 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { AccountJson } from '@pezkuwi/extension-base/background/types';
export function nextDerivationPath (accounts: AccountJson[], parentAddress: string): string {
const siblingsCount = accounts.filter((account) => account.parentAddress === parentAddress).length;
return `//${siblingsCount}`;
}
@@ -0,0 +1,54 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TFunction } from '../hooks/useTranslation.js';
import { zxcvbn, zxcvbnOptions } from '@zxcvbn-ts/core';
import * as zxcvbnCommonPackage from '@zxcvbn-ts/language-common';
import * as zxcvbnEnPackage from '@zxcvbn-ts/language-en';
// Configure zxcvbn with English dictionary and common patterns
zxcvbnOptions.setOptions({
dictionary: {
...zxcvbnCommonPackage.dictionary, // common words across languages
...zxcvbnEnPackage.dictionary // english-specific dictionary
},
graphs: zxcvbnCommonPackage.adjacencyGraphs, // keyboard patterns
translations: zxcvbnEnPackage.translations // language translations for feedback messages
});
export interface PasswordStrength {
feedback: {
warning: string;
suggestions: string[];
};
score: number; // 0-4 (0: very weak, 4: very strong)
}
const MIN_LENGTH = 6; // Minimum password length requirement
export function validatePasswordStrength (password: string, t: TFunction): PasswordStrength {
const result = zxcvbn(password);
// First check: Minimum length requirement
if (password.length < MIN_LENGTH) {
return {
feedback: {
suggestions: [t('Password must be at least {{length}} characters long', { replace: { length: MIN_LENGTH } })],
warning: t('Password is too short')
},
score: 0
};
}
// Combine zxcvbn suggestions with our custom ones
const suggestions = (result.feedback.suggestions || []).map((suggestion) => t(suggestion));
return {
feedback: {
suggestions,
warning: result.feedback.warning ? t(result.feedback.warning) : ''
},
score: result.score
};
}
@@ -0,0 +1,64 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
const sanitizeHtml = (input: string, options?: {
allowLineBreaks?: boolean;
maxLength?: number;
}): string => {
if (!input || typeof input !== 'string') {
return '';
}
let sanitized = input;
// Remove all HTML tags
sanitized = sanitized.replace(/<[^>]*>/g, '');
// Handle line breaks if specified
if (options?.allowLineBreaks) {
// Convert <br> to actual line breaks before sanitization
sanitized = input
.replace(/<br\s*\/?>/gi, '\n')
.replace(/<[^>]*>/g, ''); // Remove other HTML tags
}
// Normalize whitespace
sanitized = sanitized
.trim()
.replace(/\s+/g, ' ');
// Apply length limit if specified
if (options?.maxLength && sanitized.length > options.maxLength) {
sanitized = sanitized.substring(0, options.maxLength) + '...';
}
return sanitized;
};
export const sanitizeOrigin = (origin: string): string => {
return sanitizeHtml(origin, {
allowLineBreaks: false,
maxLength: 50
});
};
// Validate that the origin looks legitimate
export const validateOrigin = (origin: string): boolean => {
const sanitized = sanitizeOrigin(origin);
// Special case: always allow localhost URLs
if (/^https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|::1)(?::\d+)?(?:\/|$)/.test(sanitized)) {
return true;
}
// Check for suspicious patterns
const suspiciousPatterns = [
/[<>]/, // HTML characters
/^\s*$/, // Empty or whitespace only
/.{50,}/, // Extremely long names
/^(?!https?:\/\/).*:\/\//, // Any protocol other than http/https
/^https?:\/\/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(?::\d+)?(?:\/|$)/ // Raw IP addresses (except localhost)
];
return !suspiciousPatterns.some((pattern) => pattern.test(sanitized));
};
@@ -0,0 +1,9 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { KeyringPair$Json } from '@pezkuwi/keyring/types';
import type { KeyringPairs$Json } from '@pezkuwi/ui-keyring/types';
export function isKeyringPairs$Json (json: KeyringPair$Json | KeyringPairs$Json): json is KeyringPairs$Json {
return (json.encoding.content).includes('batch-pkcs8');
}
@@ -0,0 +1,51 @@
// Copyright 2019-2025 @pezkuwi/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
interface InputError {
errorDescription: string;
}
export type ResultType<T> = { error: InputError } | { ok: T };
export declare type Validator<T> = (value: T) => ResultType<T> | Promise<ResultType<T>>;
export const Result = {
error: <T>(errorDescription: string): ResultType<T> => ({ error: { errorDescription } }),
isError<T> (value: ResultType<T>): value is ({ error: InputError }) {
return Object.hasOwnProperty.call(value, 'error');
},
isOk<T> (value: ResultType<T>): value is ({ ok: T }) {
return Object.hasOwnProperty.call(value, 'ok');
},
ok: <T>(ok: T): ResultType<T> => ({ ok })
};
export function allOf <T> (...validators: Validator<T>[]): Validator<T> {
return async (value: T): Promise<ResultType<T>> => {
for (const validator of validators) {
const validationResult = await validator(value);
if (Result.isError(validationResult)) {
return validationResult;
}
}
return Result.ok(value);
};
}
export function isNotShorterThan (minLength: number, errorText: string): Validator<string> {
return (value: string): ResultType<string> => {
return value.length < minLength
? Result.error(errorText)
: Result.ok(value);
};
}
export function isSameAs <T> (expectedValue: T, errorText: string): Validator<T> {
return (value: T): ResultType<T> => {
return value !== expectedValue
? Result.error(errorText)
: Result.ok(value);
};
}