mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-26 02:37:56 +00:00
d21bfb1320
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
152 lines
4.5 KiB
TypeScript
152 lines
4.5 KiB
TypeScript
// Copyright 2017-2025 @pezkuwi/react-hooks authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { SubjectInfo } from '@pezkuwi/ui-keyring/observable/types';
|
|
import type { Accounts, Addresses } from './types.js';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import { combineLatest, map } from 'rxjs';
|
|
|
|
import { keyring } from '@pezkuwi/ui-keyring';
|
|
import { u8aToHex } from '@pezkuwi/util';
|
|
import { decodeAddress } from '@pezkuwi/util-crypto';
|
|
|
|
import { useApi } from '../useApi.js';
|
|
|
|
interface Props {
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
interface State {
|
|
accounts: Accounts;
|
|
addresses: Addresses;
|
|
}
|
|
|
|
const EMPTY_IS = () => false;
|
|
|
|
const EMPTY: State = {
|
|
accounts: { allAccounts: [], allAccountsHex: [], areAccountsLoaded: false, hasAccounts: false, isAccount: EMPTY_IS },
|
|
addresses: { allAddresses: [], allAddressesHex: [], areAddressesLoaded: false, hasAddresses: false, isAddress: EMPTY_IS }
|
|
};
|
|
|
|
export const KeyringCtx = React.createContext<State>(EMPTY);
|
|
|
|
/**
|
|
* @internal Helper function to dedupe a list of items, only adding it if
|
|
*
|
|
* 1. It is not already present in our list of results
|
|
* 2. It does not exist against a secondary list to check
|
|
*
|
|
* The first check ensures that we never have dupes in the original. The second
|
|
* ensures that e.g. an address is not also available as an account
|
|
**/
|
|
function filter (isEthereum: boolean, items: string[], others: string[] = []): string[] {
|
|
const allowedLength = isEthereum
|
|
? 20
|
|
: 32;
|
|
|
|
return items.reduce<string[]>((result, a) => {
|
|
if (!result.includes(a) && !others.includes(a)) {
|
|
try {
|
|
if (decodeAddress(a).length >= allowedLength) {
|
|
result.push(a);
|
|
} else {
|
|
console.warn(`Not adding address ${a}, not in correct format for chain (requires publickey from address)`);
|
|
}
|
|
} catch {
|
|
console.error(a, allowedLength);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}, []);
|
|
}
|
|
|
|
/**
|
|
* @internal Helper function to convert a list of ss58 addresses into hex
|
|
**/
|
|
function toHex (items: string[]): string[] {
|
|
return items
|
|
.map((a): string | null => {
|
|
try {
|
|
return u8aToHex(decodeAddress(a));
|
|
} catch (error) {
|
|
// This is actually just a failsafe - the keyring really should
|
|
// not be passing through invalid ss58 values, but never say never
|
|
console.error(`Unable to convert address ${a} to hex`, (error as Error).message);
|
|
|
|
return null;
|
|
}
|
|
})
|
|
.filter((a): a is string => !!a);
|
|
}
|
|
|
|
/**
|
|
* @internal Helper to create an is{Account, Address} check
|
|
**/
|
|
function createCheck (items: string[]): Accounts['isAccount'] {
|
|
return (a?: string | null | { toString: () => string }): boolean =>
|
|
!!a && items.includes(a.toString());
|
|
}
|
|
|
|
function extractAccounts (isEthereum: boolean, accounts: SubjectInfo = {}): Accounts {
|
|
const allAccounts = filter(isEthereum, Object.keys(accounts));
|
|
|
|
return {
|
|
allAccounts,
|
|
allAccountsHex: toHex(allAccounts),
|
|
areAccountsLoaded: true,
|
|
hasAccounts: allAccounts.length !== 0,
|
|
isAccount: createCheck(allAccounts)
|
|
};
|
|
}
|
|
|
|
function extractAddresses (isEthereum: boolean, addresses: SubjectInfo = {}, accounts: string[]): Addresses {
|
|
const allAddresses = filter(isEthereum, Object.keys(addresses), accounts);
|
|
|
|
return {
|
|
allAddresses,
|
|
allAddressesHex: toHex(allAddresses),
|
|
areAddressesLoaded: true,
|
|
hasAddresses: allAddresses.length !== 0,
|
|
isAddress: createCheck(allAddresses)
|
|
};
|
|
}
|
|
|
|
export function KeyringCtxRoot ({ children }: Props): React.ReactElement<Props> {
|
|
const { isApiReady, isEthereum } = useApi();
|
|
const [state, setState] = useState(EMPTY);
|
|
|
|
useEffect((): () => void => {
|
|
let sub: null | { unsubscribe: () => void } = null;
|
|
|
|
// Defer keyring injection until the API is ready - we need to have the chain
|
|
// info to determine which type of addresses we can use (before subscribing)
|
|
if (isApiReady) {
|
|
sub = combineLatest([
|
|
keyring.accounts.subject.pipe(
|
|
map((accInfo) => extractAccounts(isEthereum, accInfo))
|
|
),
|
|
keyring.addresses.subject
|
|
])
|
|
.pipe(
|
|
map(([accounts, addrInfo]): State => ({
|
|
accounts,
|
|
addresses: extractAddresses(isEthereum, addrInfo, accounts.allAccounts)
|
|
}))
|
|
)
|
|
.subscribe((state) => setState(state));
|
|
}
|
|
|
|
return (): void => {
|
|
sub && sub.unsubscribe();
|
|
};
|
|
}, [isApiReady, isEthereum]);
|
|
|
|
return (
|
|
<KeyringCtx.Provider value={state}>
|
|
{children}
|
|
</KeyringCtx.Provider>
|
|
);
|
|
}
|