@polkadot/util#assert TypeScript Examples
The following examples show how to use
@polkadot/util#assert.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: util.ts From crust-apps with Apache License 2.0 | 7 votes |
// split is 65-byte signature into the r, s (combined) and recovery number (derived from v)
export function sigToParts (_signature: string): SignatureParts {
const signature = hexHasPrefix(_signature) ? hexToU8a(_signature) : hexToU8a(hexAddPrefix(_signature));
assert(signature.length === 65, `Invalid signature length, expected 65 found ${signature.length}`);
let v = signature[64];
if (v < 27) {
v += 27;
}
const recovery = v - 27;
assert(recovery === 0 || recovery === 1, 'Invalid signature v value');
return {
recovery,
signature: u8aToBuffer(signature.slice(0, 64))
};
}
Example #2
Source File: TxSigned.tsx From crust-apps with Apache License 2.0 | 6 votes |
async function extractParams (api: ApiPromise, address: string, options: Partial<SignerOptions>, getLedger: () => Ledger, setQrState: (state: QrState) => void): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
const pair = keyring.getPair(address);
const { meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isProxied, source } } = pair;
if (isHardware) {
return ['signing', address, { ...options, signer: new LedgerSigner(api.registry, getLedger, accountOffset as number || 0, addressOffset as number || 0) }];
} else if (isExternal && !isProxied) {
return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }];
} else if (isInjected) {
const injected = await web3FromSource(source as string);
assert(injected, `Unable to find a signer for ${address}`);
return ['signing', address, { ...options, signer: injected.signer }];
}
assert(addressEq(address, pair.address), `Unable to retrieve keypair for ${address}`);
return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }];
}
Example #3
Source File: index.tsx From subscan-multisig-react with Apache License 2.0 | 6 votes |
async function submitRpc(
api: ApiPromise,
{ method, section }: DefinitionRpcExt,
values: any[]
): Promise<QueueTxResult> {
try {
const rpc = api.rpc as Record<string, Record<string, (...params: unknown[]) => Promise<unknown>>>;
assert(isFunction(rpc[section] && rpc[section][method]), `api.rpc.${section}.${method} does not exist`);
const result = await rpc[section][method](...values);
return {
result,
status: 'sent',
};
} catch (error) {
console.error(error);
return {
error: error as Error,
status: 'error',
};
}
}
Example #4
Source File: KeyValueArray.tsx From subscan-multisig-react with Apache License 2.0 | 6 votes |
function parseFile(raw: Uint8Array): Parsed {
const json = JSON.parse(u8aToString(raw)) as Record<string, string>;
const keys = Object.keys(json);
let isValid = keys.length !== 0;
const value = keys.map((key): [Uint8Array, Uint8Array] => {
const val = json[key];
assert(isHex(key) && isHex(val), `Non-hex key/value pair found in ${key.toString()} => ${val.toString()}`);
const encKey = createParam(key);
const encValue = createParam(val);
isValid = isValid && encKey.isValid && encValue.isValid;
return [encKey.u8a, encValue.u8a];
});
return {
isValid,
value,
};
}
Example #5
Source File: useLedger.ts From subscan-multisig-react with Apache License 2.0 | 6 votes |
function retrieveLedger(api: ApiPromise): Ledger {
const currType = uiSettings.ledgerConn as LedgerTypes;
if (!ledger || ledgerType !== currType) {
const genesisHex = api.genesisHash.toHex();
const def = ledgerChains.find(({ genesisHash }) => genesisHash[0] === genesisHex);
assert(def, `Unable to find supported chain for ${genesisHex}`);
ledger = new Ledger(currType, def.network);
ledgerType = currType;
}
return ledger;
}
Example #6
Source File: toAddress.ts From subscan-multisig-react with Apache License 2.0 | 6 votes |
// eslint-disable-next-line complexity
export default function toAddress(value?: string | Uint8Array | null, allowIndices = false): string | undefined {
if (value) {
try {
const u8a = isHex(value) ? hexToU8a(value) : keyring.decodeAddress(value);
assert(allowIndices || u8a.length === 32 || u8a.length === 20, 'AccountIndex values not allowed');
if (u8a.length === 20) {
return ethereumEncode(u8a);
} else {
return keyring.encodeAddress(u8a);
}
} catch (error) {
// noop, undefined return indicates invalid/transient
}
}
return undefined;
}
Example #7
Source File: urlTypes.ts From subscan-multisig-react with Apache License 2.0 | 6 votes |
export function decodeUrlTypes(): Record<string, any> | null {
const urlOptions = queryString.parse(location.href.split('?')[1]);
if (urlOptions.types) {
try {
assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');
const parts = urlOptions.types.split('#');
const compressed = base64Decode(decodeURIComponent(parts[0]));
const uncompressed = unzlibSync(compressed);
return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
} catch (error) {
console.error(error);
}
}
return null;
}
Example #8
Source File: api.tsx From subscan-multisig-react with Apache License 2.0 | 6 votes |
export default function withApi<P extends ApiProps>( Inner: React.ComponentType<P>, defaultProps: DefaultProps = {} // eslint-disable-next-line @typescript-eslint/no-explicit-any ): React.ComponentType<any> { class WithApi extends React.PureComponent<SubtractProps<P, ApiProps>> { // eslint-disable-next-line @typescript-eslint/no-explicit-any private component: any = React.createRef(); public render(): React.ReactNode { return ( <ApiConsumer> {(apiProps?: ApiProps): React.ReactNode => { assert( apiProps && apiProps.api, `Application root must be wrapped inside 'react-api/Api' to provide API context` ); return ( <Inner {...defaultProps} // eslint-disable-next-line @typescript-eslint/no-explicit-any {...(apiProps as any)} {...this.props} // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment ref={this.component} /> ); }} </ApiConsumer> ); } } return WithApi; }
Example #9
Source File: index.tsx From crust-apps with Apache License 2.0 | 6 votes |
async function submitRpc (api: ApiPromise, { method, section }: DefinitionRpcExt, values: any[]): Promise<QueueTxResult> {
try {
const rpc = api.rpc as Record<string, Record<string, (...params: unknown[]) => Promise<unknown>>>;
assert(isFunction(rpc[section] && rpc[section][method]), `api.rpc.${section}.${method} does not exist`);
const result = await rpc[section][method](...values);
console.log('submitRpc: result ::', loggerFormat(result));
return {
result,
status: 'sent'
};
} catch (error) {
console.error(error);
return {
error: error as Error,
status: 'error'
};
}
}
Example #10
Source File: KeyValueArray.tsx From crust-apps with Apache License 2.0 | 6 votes |
function parseFile (raw: Uint8Array): Parsed {
const json = JSON.parse(u8aToString(raw)) as Record<string, string>;
const keys = Object.keys(json);
let isValid = keys.length !== 0;
const value = keys.map((key): [Uint8Array, Uint8Array] => {
const value = json[key];
assert(isHex(key) && isHex(value), `Non-hex key/value pair found in ${key.toString()} => ${value.toString()}`);
const encKey = createParam(key);
const encValue = createParam(value);
isValid = isValid && encKey.isValid && encValue.isValid;
return [encKey.u8a, encValue.u8a];
});
return {
isValid,
value
};
}
Example #11
Source File: useLedger.ts From crust-apps with Apache License 2.0 | 6 votes |
function retrieveLedger (api: ApiPromise): Ledger {
if (!ledger) {
const genesisHex = api.genesisHash.toHex();
const def = ledgerChains.find(({ genesisHash }) => genesisHash[0] === genesisHex);
assert(def, `Unable to find supported chain for ${genesisHex}`);
ledger = new Ledger(uiSettings.ledgerConn as LedgerTypes, def.network);
}
return ledger;
}
Example #12
Source File: toAddress.ts From crust-apps with Apache License 2.0 | 6 votes |
export default function toAddress (value?: string | Uint8Array | null, allowIndices = false): string | undefined {
if (value) {
try {
const u8a = isHex(value)
? hexToU8a(value)
: keyring.decodeAddress(value);
assert(allowIndices || u8a.length === 32 || u8a.length === 20, 'AccountIndex values not allowed');
if (u8a.length === 20) {
return ethereumEncode(u8a);
} else {
return keyring.encodeAddress(u8a);
}
} catch (error) {
// noop, undefined return indicates invalid/transient
}
}
return undefined;
}
Example #13
Source File: urlTypes.ts From crust-apps with Apache License 2.0 | 6 votes |
export function decodeUrlTypes (): Record<string, any> | null {
const urlOptions = queryString.parse(location.href.split('?')[1]);
if (urlOptions.types) {
try {
assert(!Array.isArray(urlOptions.types), 'Expected a single type specification');
const parts = urlOptions.types.split('#');
const compressed = base64Decode(decodeURIComponent(parts[0]));
const uncompressed = unzlibSync(compressed);
return JSON.parse(u8aToString(uncompressed)) as Record<string, any>;
} catch (error) {
console.error(error);
}
}
return null;
}
Example #14
Source File: api.tsx From crust-apps with Apache License 2.0 | 6 votes |
export default function withApi <P extends ApiProps> (Inner: React.ComponentType<P>, defaultProps: DefaultProps = {}): React.ComponentType<any> {
class WithApi extends React.PureComponent<SubtractProps<P, ApiProps>> {
private component: any = React.createRef();
public render (): React.ReactNode {
return (
<ApiConsumer>
{(apiProps?: ApiProps): React.ReactNode => {
assert(apiProps && apiProps.api, 'Application root must be wrapped inside \'react-api/Api\' to provide API context');
return (
<Inner
{...defaultProps}
{...(apiProps as any)}
{...this.props}
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
ref={this.component}
/>
);
}}
</ApiConsumer>
);
}
}
return WithApi;
}
Example #15
Source File: initSettings.ts From crust-apps with Apache License 2.0 | 5 votes |
function getApiUrl (): string {
// we split here so that both these forms are allowed
// - http://localhost:3000/?rpc=wss://substrate-rpc.parity.io/#/explorer
// - http://localhost:3000/#/explorer?rpc=wss://substrate-rpc.parity.io
const urlOptions = queryString.parse(location.href.split('?')[1]);
// const randomIndex = new Date().getTime() % endPoints.length;
// return endPoints[randomIndex];
// if specified, this takes priority
if (urlOptions.rpc) {
assert(!Array.isArray(urlOptions.rpc), 'Invalid WS endpoint specified');
// https://polkadot.js.org/apps/?rpc=ws://127.0.0.1:9944#/explorer;
const url = decodeURIComponent(urlOptions.rpc.split('#')[0]);
assert(url.startsWith('ws://') || url.startsWith('wss://'), 'Non-prefixed ws/wss url');
return url;
}
const endpoints = createWsEndpoints(<T = string>(): T => ('' as unknown as T));
const { ipnsChain } = extractIpfsDetails();
// check against ipns domains (could be expanded to others)
if (ipnsChain) {
const option = endpoints.find(({ dnslink }) => dnslink === ipnsChain);
if (option) {
return option.value as string;
}
}
const stored = store.get('settings') as Record<string, unknown> || {};
const fallbackUrl = endpoints.find(({ value }) => !!value);
// via settings, or the default chain
return [stored.apiUrl, process.env.WS_URL].includes(settings.apiUrl)
? settings.apiUrl // keep as-is
: fallbackUrl
? fallbackUrl.value as string // grab the fallback
: 'ws://127.0.0.1:9944'; // nothing found, go local
}
Example #16
Source File: networkSpect.ts From sdk with Apache License 2.0 | 5 votes |
function getGenesis(name: string): string {
const network = allNetworks.find(({ network }) => network === name);
assert(network && network.genesisHash[0], `Unable to find genesisHash for ${name}`);
return network.genesisHash[0];
}
Example #17
Source File: TxSigned.tsx From subscan-multisig-react with Apache License 2.0 | 5 votes |
async function extractParams(
api: ApiPromise,
address: string,
options: Partial<SignerOptions>,
getLedger: () => Ledger,
setQrState: (state: QrState) => void
): Promise<['qr' | 'signing', string, Partial<SignerOptions>]> {
const pair = keyring.getPair(address);
const {
meta: { accountOffset, addressOffset, isExternal, isHardware, isInjected, isProxied, source },
} = pair;
if (isHardware) {
return [
'signing',
address,
{
...options,
signer: new LedgerSigner(
api.registry,
getLedger,
(accountOffset as number) || 0,
(addressOffset as number) || 0
),
},
];
} else if (isExternal && !isProxied) {
return ['qr', address, { ...options, signer: new QrSigner(api.registry, setQrState) }];
} else if (isInjected) {
const injected = await web3FromSource(source as string);
assert(injected, `Unable to find a signer for ${address}`);
return ['signing', address, { ...options, signer: injected.signer }];
}
assert(addressEq(address, pair.address), `Unable to retrieve keypair for ${address}`);
return ['signing', address, { ...options, signer: new AccountSigner(api.registry, pair) }];
}
Example #18
Source File: constants.ts From crust-apps with Apache License 2.0 | 5 votes |
function getGenesis (name: string): string {
const network = networks.find(({ network }) => network === name);
assert(network && network.genesisHash[0], `Unable to find genesisHash for ${name}`);
return network.genesisHash[0];
}
Example #19
Source File: TxButton.tsx From crust-apps with Apache License 2.0 | 4 votes |
function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isBusy, isDisabled, isIcon, isToplevel, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner, withoutLink }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const mountedRef = useIsMountedRef();
const { queueExtrinsic } = useContext(StatusContext);
const [isSending, setIsSending] = useState(false);
const [isStarted, setIsStarted] = useState(false);
useEffect((): void => {
(isStarted && onStart) && onStart();
}, [isStarted, onStart]);
const _onFailed = useCallback(
(result: SubmittableResult | null): void => {
mountedRef.current && setIsSending(false);
onFailed && onFailed(result);
},
[onFailed, setIsSending, mountedRef]
);
const _onSuccess = useCallback(
(result: SubmittableResult): void => {
mountedRef.current && setIsSending(false);
onSuccess && onSuccess(result);
},
[onSuccess, setIsSending, mountedRef]
);
const _onStart = useCallback(
(): void => {
mountedRef.current && setIsStarted(true);
},
[setIsStarted, mountedRef]
);
const _onSend = useCallback(
(): void => {
let extrinsics: SubmittableExtrinsic<'promise'>[] | undefined;
if (propsExtrinsic) {
extrinsics = Array.isArray(propsExtrinsic)
? propsExtrinsic
: [propsExtrinsic];
} else if (tx) {
extrinsics = [
tx(...(
isFunction(params)
? params()
: (params || [])
))
];
}
assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton');
mountedRef.current && withSpinner && setIsSending(true);
extrinsics.forEach((extrinsic): void => {
queueExtrinsic({
accountId: accountId && accountId.toString(),
extrinsic,
isUnsigned,
txFailedCb: withSpinner ? _onFailed : onFailed,
txStartCb: _onStart,
txSuccessCb: withSpinner ? _onSuccess : onSuccess,
txUpdateCb: onUpdate
});
});
onClick && onClick();
},
[_onFailed, _onStart, _onSuccess, accountId, isUnsigned, onClick, onFailed, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner, mountedRef]
);
if (onSendRef) {
onSendRef.current = _onSend;
}
return (
<Button
className={className}
icon={icon || 'check'}
isBasic={isBasic}
isBusy={isBusy}
isDisabled={isSending || isDisabled || (!isUnsigned && !accountId) || (
tx
? false
: Array.isArray(propsExtrinsic)
? propsExtrinsic.length === 0
: !propsExtrinsic
)}
isIcon={isIcon}
isToplevel={isToplevel}
label={label || (isIcon ? '' : t<string>('Submit'))}
onClick={_onSend}
tooltip={tooltip}
withoutLink={withoutLink}
/>
);
}
Example #20
Source File: call.tsx From subscan-multisig-react with Apache License 2.0 | 4 votes |
export default function withCall<P extends ApiProps>(
endpoint: string,
{
at,
atProp,
callOnResult,
fallbacks,
isMulti = false,
params = [],
paramName,
paramPick,
paramValid = false,
propName,
skipIf = NO_SKIP,
transform = echoTransform,
withIndicator = false,
}: Options = {}
): (Inner: React.ComponentType<ApiProps>) => React.ComponentType<any> {
return (Inner: React.ComponentType<ApiProps>): React.ComponentType<SubtractProps<P, ApiProps>> => {
class WithPromise extends React.Component<P, State> {
public state: State = {
callResult: undefined,
callUpdated: false,
callUpdatedAt: 0,
};
private destroy?: () => void;
private isActive = false;
private propName: string;
private timerId = -1;
constructor(props: P) {
super(props);
const [, section, method] = endpoint.split('.');
this.propName = `${section}_${method}`;
}
public componentDidUpdate(prevProps: any): void {
const oldParams = this.getParams(prevProps);
const newParams = this.getParams(this.props);
if (this.isActive && !isEqual(newParams, oldParams)) {
this.subscribe(newParams).then(NOOP).catch(NOOP);
}
}
public componentDidMount(): void {
this.isActive = true;
if (withIndicator) {
this.timerId = window.setInterval((): void => {
const elapsed = Date.now() - (this.state.callUpdatedAt || 0);
const callUpdated = elapsed <= 1500;
if (callUpdated !== this.state.callUpdated) {
this.nextState({ callUpdated });
}
}, 500);
}
// The attachment takes time when a lot is available, set a timeout
// to first handle the current queue before subscribing
setTimeout((): void => {
this.subscribe(this.getParams(this.props)).then(NOOP).catch(NOOP);
}, 0);
}
public componentWillUnmount(): void {
this.isActive = false;
this.unsubscribe().then(NOOP).catch(NOOP);
if (this.timerId !== -1) {
clearInterval(this.timerId);
}
}
private nextState(state: Partial<State>): void {
if (this.isActive) {
this.setState(state as State);
}
}
private getParams(props: any): [boolean, any[]] {
const paramValue = paramPick ? paramPick(props) : paramName ? props[paramName] : undefined;
if (atProp) {
at = props[atProp];
}
// When we are specifying a param and have an invalid, don't use it. For 'params',
// we default to the original types, i.e. no validation (query app uses this)
if (!paramValid && paramName && (isUndefined(paramValue) || isNull(paramValue))) {
return [false, []];
}
const values = isUndefined(paramValue)
? params
: params.concat(Array.isArray(paramValue) && !(paramValue as any).toU8a ? paramValue : [paramValue]);
return [true, values];
}
// eslint-disable-next-line @typescript-eslint/no-shadow
private constructApiSection = (endpoint: string): [Record<string, Method>, string, string, string] => {
// eslint-disable-next-line no-invalid-this
const { api } = this.props;
const [area, section, method, ...others] = endpoint.split('.');
assert(
area.length && section.length && method.length && others.length === 0,
`Invalid API format, expected <area>.<section>.<method>, found ${endpoint}`
);
assert(
['consts', 'rpc', 'query', 'derive'].includes(area),
`Unknown api.${area}, expected consts, rpc, query or derive`
);
assert(!at || area === 'query', `Only able to do an 'at' query on the api.query interface`);
const apiSection = (api as any)[area][section];
return [apiSection, area, section, method];
};
private getApiMethod(newParams: any[]): ApiMethodInfo {
if (endpoint === 'subscribe') {
// eslint-disable-next-line @typescript-eslint/no-shadow
const [fn, ...params] = newParams;
return [fn, params, 'subscribe'];
}
const endpoints: string[] = [endpoint].concat(fallbacks || []);
const expanded = endpoints.map(this.constructApiSection);
// eslint-disable-next-line @typescript-eslint/no-shadow
const [apiSection, area, section, method] = expanded.find(([apiSection]): boolean => !!apiSection) || [
{},
expanded[0][1],
expanded[0][2],
expanded[0][3],
];
assert(apiSection && apiSection[method], `Unable to find api.${area}.${section}.${method}`);
const meta = apiSection[method].meta;
if (area === 'query' && meta?.type.isMap) {
const arg = newParams[0];
assert(
(!isUndefined(arg) && !isNull(arg)) || meta.type.asMap.kind.isLinkedMap,
`${meta.name} expects one argument`
);
}
return [apiSection[method], newParams, method.startsWith('subscribe') ? 'subscribe' : area];
}
private async subscribe([isValid, newParams]: [boolean, any[]]): Promise<void> {
if (!isValid || skipIf(this.props)) {
return;
}
const { api } = this.props;
let info: ApiMethodInfo | undefined;
await api.isReady;
try {
assert(at || !atProp, 'Unable to perform query on non-existent at hash');
info = this.getApiMethod(newParams);
} catch (error) {
// don't flood the console with the same errors each time, just do it once, then
// ignore it going forward
if (!errorred[(error as Error).message]) {
console.warn(endpoint, '::', error);
errorred[(error as Error).message] = true;
}
}
if (!info) {
return;
}
// eslint-disable-next-line @typescript-eslint/no-shadow
const [apiMethod, params, area] = info;
const updateCb = (value?: any): void => this.triggerUpdate(this.props, value);
await this.unsubscribe();
try {
if (['derive', 'subscribe'].includes(area) || (area === 'query' && !at && !atProp)) {
this.destroy = isMulti ? await apiMethod.multi(params, updateCb) : await apiMethod(...params, updateCb);
} else if (area === 'consts') {
updateCb(apiMethod);
} else {
updateCb(at ? await apiMethod.at(at, ...params) : await apiMethod(...params));
}
} catch (error) {
// console.warn(endpoint, '::', error);
}
}
// eslint-disable-next-line @typescript-eslint/require-await
private async unsubscribe(): Promise<void> {
if (this.destroy) {
this.destroy();
this.destroy = undefined;
}
}
private triggerUpdate(props: any, value?: any): void {
try {
const callResult = (props.transform || transform)(value);
if (!this.isActive || isEqual(callResult, this.state.callResult)) {
return;
}
triggerChange(callResult, callOnResult, props.callOnResult);
this.nextState({
callResult,
callUpdated: true,
callUpdatedAt: Date.now(),
});
} catch (error) {
// console.warn(endpoint, '::', (error as Error).message);
}
}
public render(): React.ReactNode {
const { callResult, callUpdated, callUpdatedAt } = this.state;
const _props = {
...this.props,
callUpdated,
callUpdatedAt,
};
if (!isUndefined(callResult)) {
(_props as any)[propName || this.propName] = callResult;
}
return <Inner {..._props} />;
}
}
return withApi(WithPromise);
};
}
Example #21
Source File: call.tsx From crust-apps with Apache License 2.0 | 4 votes |
export default function withCall<P extends ApiProps> (endpoint: string, { at, atProp, callOnResult, fallbacks, isMulti = false, params = [], paramName, paramPick, paramValid = false, propName, skipIf = NO_SKIP, transform = echoTransform, withIndicator = false }: Options = {}): (Inner: React.ComponentType<ApiProps>) => React.ComponentType<any> {
return (Inner: React.ComponentType<ApiProps>): React.ComponentType<SubtractProps<P, ApiProps>> => {
class WithPromise extends React.Component<P, State> {
public state: State = {
callResult: undefined,
callUpdated: false,
callUpdatedAt: 0
};
private destroy?: () => void;
private isActive = false;
private propName: string;
private timerId = -1;
constructor (props: P) {
super(props);
const [, section, method] = endpoint.split('.');
this.propName = `${section}_${method}`;
}
public componentDidUpdate (prevProps: any): void {
const oldParams = this.getParams(prevProps);
const newParams = this.getParams(this.props);
if (this.isActive && !isEqual(newParams, oldParams)) {
this
.subscribe(newParams)
.then(NOOP)
.catch(NOOP);
}
}
public componentDidMount (): void {
this.isActive = true;
if (withIndicator) {
this.timerId = window.setInterval((): void => {
const elapsed = Date.now() - (this.state.callUpdatedAt || 0);
const callUpdated = elapsed <= 1500;
if (callUpdated !== this.state.callUpdated) {
this.nextState({ callUpdated });
}
}, 500);
}
// The attachment takes time when a lot is available, set a timeout
// to first handle the current queue before subscribing
setTimeout((): void => {
this
.subscribe(this.getParams(this.props))
.then(NOOP)
.catch(NOOP);
}, 0);
}
public componentWillUnmount (): void {
this.isActive = false;
this.unsubscribe()
.then(NOOP)
.catch(NOOP);
if (this.timerId !== -1) {
clearInterval(this.timerId);
}
}
private nextState (state: Partial<State>): void {
if (this.isActive) {
this.setState(state as State);
}
}
private getParams (props: any): [boolean, any[]] {
const paramValue = paramPick
? paramPick(props)
: paramName
? props[paramName]
: undefined;
if (atProp) {
at = props[atProp];
}
// When we are specifying a param and have an invalid, don't use it. For 'params',
// we default to the original types, i.e. no validation (query app uses this)
if (!paramValid && paramName && (isUndefined(paramValue) || isNull(paramValue))) {
return [false, []];
}
const values = isUndefined(paramValue)
? params
: params.concat(
(Array.isArray(paramValue) && !(paramValue as any).toU8a)
? paramValue
: [paramValue]
);
return [true, values];
}
private constructApiSection = (endpoint: string): [Record<string, Method>, string, string, string] => {
const { api } = this.props;
const [area, section, method, ...others] = endpoint.split('.');
assert(area.length && section.length && method.length && others.length === 0, `Invalid API format, expected <area>.<section>.<method>, found ${endpoint}`);
assert(['consts', 'rpc', 'query', 'derive'].includes(area), `Unknown api.${area}, expected consts, rpc, query or derive`);
assert(!at || area === 'query', 'Only able to do an \'at\' query on the api.query interface');
const apiSection = (api as any)[area][section];
return [
apiSection,
area,
section,
method
];
}
private getApiMethod (newParams: any[]): ApiMethodInfo {
if (endpoint === 'subscribe') {
const [fn, ...params] = newParams;
return [
fn,
params,
'subscribe'
];
}
const endpoints: string[] = [endpoint].concat(fallbacks || []);
const expanded = endpoints.map(this.constructApiSection);
const [apiSection, area, section, method] = expanded.find(([apiSection]): boolean =>
!!apiSection
) || [{}, expanded[0][1], expanded[0][2], expanded[0][3]];
assert(apiSection && apiSection[method], `Unable to find api.${area}.${section}.${method}`);
const meta = apiSection[method].meta;
if (area === 'query' && meta?.type.isMap) {
const arg = newParams[0];
assert((!isUndefined(arg) && !isNull(arg)) || meta.type.asMap.kind.isLinkedMap, `${meta.name} expects one argument`);
}
return [
apiSection[method],
newParams,
method.startsWith('subscribe') ? 'subscribe' : area
];
}
private async subscribe ([isValid, newParams]: [boolean, any[]]): Promise<void> {
if (!isValid || skipIf(this.props)) {
return;
}
const { api } = this.props;
let info: ApiMethodInfo | undefined;
await api.isReady;
try {
assert(at || !atProp, 'Unable to perform query on non-existent at hash');
info = this.getApiMethod(newParams);
} catch (error) {
// don't flood the console with the same errors each time, just do it once, then
// ignore it going forward
if (!errorred[(error as Error).message]) {
console.warn(endpoint, '::', error);
errorred[(error as Error).message] = true;
}
}
if (!info) {
return;
}
const [apiMethod, params, area] = info;
const updateCb = (value?: any): void =>
this.triggerUpdate(this.props, value);
await this.unsubscribe();
try {
if (['derive', 'subscribe'].includes(area) || (area === 'query' && (!at && !atProp))) {
this.destroy = isMulti
? await apiMethod.multi(params, updateCb)
: await apiMethod(...params, updateCb);
} else if (area === 'consts') {
updateCb(apiMethod);
} else {
updateCb(
at
? await apiMethod.at(at, ...params)
: await apiMethod(...params)
);
}
} catch (error) {
// console.warn(endpoint, '::', error);
}
}
// eslint-disable-next-line @typescript-eslint/require-await
private async unsubscribe (): Promise<void> {
if (this.destroy) {
this.destroy();
this.destroy = undefined;
}
}
private triggerUpdate (props: any, value?: any): void {
try {
const callResult = (props.transform || transform)(value);
if (!this.isActive || isEqual(callResult, this.state.callResult)) {
return;
}
triggerChange(callResult, callOnResult, props.callOnResult);
this.nextState({
callResult,
callUpdated: true,
callUpdatedAt: Date.now()
});
} catch (error) {
// console.warn(endpoint, '::', (error as Error).message);
}
}
public render (): React.ReactNode {
const { callResult, callUpdated, callUpdatedAt } = this.state;
const _props = {
...this.props,
callUpdated,
callUpdatedAt
};
if (!isUndefined(callResult)) {
(_props as any)[propName || this.propName] = callResult;
}
return (
<Inner {..._props} />
);
}
}
return withApi(WithPromise);
};
}
Example #22
Source File: TxButton.tsx From subscan-multisig-react with Apache License 2.0 | 4 votes |
function TxButton({
accountId,
className = '',
extrinsic: propsExtrinsic,
icon,
isBasic,
isBusy,
isDisabled,
isIcon,
isToplevel,
isUnsigned,
label,
onClick,
onFailed,
onSendRef,
onStart,
onSuccess,
onUpdate,
params,
tooltip,
tx,
withSpinner,
withoutLink,
}: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const mountedRef = useIsMountedRef();
const { queueExtrinsic } = useContext(StatusContext);
const [isSending, setIsSending] = useState(false);
const [isStarted, setIsStarted] = useState(false);
useEffect((): void => {
// eslint-disable-next-line
isStarted && onStart && onStart();
}, [isStarted, onStart]);
const _onFailed = useCallback(
(result: SubmittableResult | null): void => {
// eslint-disable-next-line
mountedRef.current && setIsSending(false);
// eslint-disable-next-line
onFailed && onFailed(result);
},
[onFailed, setIsSending, mountedRef]
);
const _onSuccess = useCallback(
(result: SubmittableResult): void => {
// eslint-disable-next-line
mountedRef.current && setIsSending(false);
// eslint-disable-next-line
onSuccess && onSuccess(result);
},
[onSuccess, setIsSending, mountedRef]
);
const _onStart = useCallback((): void => {
// eslint-disable-next-line
mountedRef.current && setIsStarted(true);
}, [setIsStarted, mountedRef]);
const _onSend = useCallback((): void => {
let extrinsics: SubmittableExtrinsic<'promise'>[] | undefined;
if (propsExtrinsic) {
extrinsics = Array.isArray(propsExtrinsic) ? propsExtrinsic : [propsExtrinsic];
} else if (tx) {
extrinsics = [tx(...(isFunction(params) ? params() : params || []))];
}
assert(extrinsics?.length, 'Expected generated extrinsic passed to TxButton');
// eslint-disable-next-line
mountedRef.current && withSpinner && setIsSending(true);
extrinsics.forEach((extrinsic): void => {
queueExtrinsic({
accountId: accountId && accountId.toString(),
extrinsic,
isUnsigned,
txFailedCb: withSpinner ? _onFailed : onFailed,
txStartCb: _onStart,
txSuccessCb: withSpinner ? _onSuccess : onSuccess,
txUpdateCb: onUpdate,
});
});
// eslint-disable-next-line
onClick && onClick();
}, [
_onFailed,
_onStart,
_onSuccess,
accountId,
isUnsigned,
onClick,
onFailed,
onSuccess,
onUpdate,
params,
propsExtrinsic,
queueExtrinsic,
setIsSending,
tx,
withSpinner,
mountedRef,
]);
if (onSendRef) {
onSendRef.current = _onSend;
}
return (
<Button
className={className}
icon={icon || 'check'}
isBasic={isBasic}
isBusy={isBusy}
isDisabled={
isSending ||
isDisabled ||
(!isUnsigned && !accountId) ||
(tx ? false : Array.isArray(propsExtrinsic) ? propsExtrinsic.length === 0 : !propsExtrinsic)
}
isIcon={isIcon}
isToplevel={isToplevel}
label={label || (isIcon ? '' : t<string>('Submit'))}
onClick={_onSend}
tooltip={tooltip}
withoutLink={withoutLink}
/>
);
}
Example #23
Source File: InjectKeys.tsx From crust-apps with Apache License 2.0 | 4 votes |
function InjectKeys ({ onClose }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { queueRpc } = useContext(StatusContext);
// this needs to align with what is set as the first value in `type`
const [crypto, setCrypto] = useState<KeypairType>('sr25519');
const [publicKey, setPublicKey] = useState(EMPTY_KEY);
const [suri, setSuri] = useState('');
const [keyType, setKeyType] = useState('babe');
const keyTypeOptRef = useRef([
{ text: t<string>('Aura'), value: 'aura' },
{ text: t<string>('Babe'), value: 'babe' },
{ text: t<string>('Grandpa'), value: 'gran' },
{ text: t<string>('I\'m Online'), value: 'imon' },
{ text: t<string>('Parachains'), value: 'para' }
]);
useEffect((): void => {
setCrypto(CRYPTO_MAP[keyType][0]);
}, [keyType]);
useEffect((): void => {
try {
const { phrase } = keyExtractSuri(suri);
assert(mnemonicValidate(phrase), 'Invalid mnemonic phrase');
setPublicKey(u8aToHex(keyring.createFromUri(suri, {}, crypto).publicKey));
} catch (error) {
setPublicKey(EMPTY_KEY);
}
}, [crypto, suri]);
const _onSubmit = useCallback(
(): void => queueRpc({
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
rpc: { method: 'insertKey', section: 'author' } as any,
values: [keyType, suri, publicKey]
}),
[keyType, publicKey, queueRpc, suri]
);
const _cryptoOptions = useMemo(
() => CRYPTO_MAP[keyType].map((value): { text: string; value: KeypairType } => ({
text: value === 'ed25519'
? t<string>('ed25519, Edwards')
: t<string>('sr15519, Schnorrkel'),
value
})),
[keyType, t]
);
return (
<Modal
header={t<string>('Inject Keys')}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('The seed and derivation path will be submitted to the validator node. this is an advanced operation, only to be performed when you are sure of the security and connection risks.')}>
<Input
autoFocus
isError={publicKey.length !== 66}
label={t<string>('suri (seed & derivation)')}
onChange={setSuri}
value={suri}
/>
<MarkWarning content={t<string>('This operation will submit the seed via an RPC call. Do not perform this operation on a public RPC node, but ensure that the node is local, connected to your validator and secure.')} />
</Modal.Columns>
<Modal.Columns hint={t<string>('The key type and crypto type to use for this key. Be aware that different keys have different crypto requirements. You should be familiar with the type requirements for the different keys.')}>
<Dropdown
label={t<string>('key type to set')}
onChange={setKeyType}
options={keyTypeOptRef.current}
value={keyType}
/>
<Dropdown
isDisabled={_cryptoOptions.length === 1}
label={t<string>('crypto type to use')}
onChange={setCrypto}
options={_cryptoOptions}
value={crypto}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('This pubic key is what will be visible in your queued keys list. It is generated based on the seed and the crypto used.')}>
<Input
isDisabled
label={t<string>('generated public key')}
value={publicKey}
/>
</Modal.Columns>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<Button
icon='sign-in-alt'
label={t<string>('Submit key')}
onClick={_onSubmit}
/>
</Modal.Actions>
</Modal>
);
}
Example #24
Source File: MultisigApprove.tsx From crust-apps with Apache License 2.0 | 4 votes |
function MultisigApprove ({ className = '', onClose, ongoing, threshold, who }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const [callData, setCallData] = useState<Call | null>(null);
const [callWeight] = useWeight(callData);
const [hash, setHash] = useState<string | null>(ongoing[0][0].toHex());
const [{ isMultiCall, multisig }, setMultisig] = useState<MultiInfo>({ isMultiCall: false, multisig: null });
const [isCallOverride, setCallOverride] = useState(true);
const [others, setOthers] = useState<AccountId[]>([]);
const [signatory, setSignatory] = useState<string | null>(null);
const [whoFilter, setWhoFilter] = useState<string[]>([]);
const [type, setType] = useState<string | null>('aye');
const [tx, setTx] = useState<SubmittableExtrinsic<'promise'> | null>(null);
const calltypes = useMemo<Option[]>(
() => [
{ text: t<string>('Approve this call hash'), value: 'aye' },
{ text: t<string>('Cancel this call hash'), value: 'nay' }
],
[t]
);
const hashes = useMemo<Option[]>(
() => ongoing.map(([h]) => ({ text: h.toHex(), value: h.toHex() })),
[ongoing]
);
// filter the current multisig by supplied hash
useEffect((): void => {
const [, multisig] = ongoing.find(([h]) => h.eq(hash)) || [null, null];
setMultisig({
isMultiCall: !!multisig && (multisig.approvals.length + 1) >= threshold,
multisig
});
setCallData(null);
}, [hash, ongoing, threshold]);
// the others are all the who elements, without the current signatory (re-encoded)
useEffect((): void => {
setOthers(
who
.map((w) => api.createType('AccountId', w))
.filter((w) => !w.eq(signatory))
);
}, [api, signatory, who]);
// Filter the who by those not approved yet that is an actual account we own. In the case of
// rejections, we defer to the the first approver, since he is the only one to send the cancel
// On reaching threshold, we include all possible signatories in the list
useEffect((): void => {
const hasThreshold = multisig && (multisig.approvals.length >= threshold);
setWhoFilter(
who
.map((w) => api.createType('AccountId', w).toString())
.filter((w) => allAccounts.some((a) => a === w) && multisig && (
type === 'nay'
? multisig.approvals[0].eq(w)
: hasThreshold || !multisig.approvals.some((a) => a.eq(w))
))
);
}, [api, allAccounts, multisig, threshold, type, who]);
// based on the type, multisig, others create the tx. This can be either an approval or final call
useEffect((): void => {
const multiMod = api.tx.multisig || api.tx.utility;
setTx(() =>
hash && multisig
? type === 'aye'
? isMultiCall && isCallOverride
? callData
? multiMod.asMulti.meta.args.length === 6
? multiMod.asMulti(threshold, others, multisig.when, callData.toHex(), false, callWeight)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore (We are doing toHex here since we have a Vec<u8> input)
: multiMod.asMulti(threshold, others, multisig.when, callData)
: null
: multiMod.approveAsMulti.meta.args.length === 5
? multiMod.approveAsMulti(threshold, others, multisig.when, hash, callWeight)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
: multiMod.approveAsMulti(threshold, others, multisig.when, hash)
: multiMod.cancelAsMulti(threshold, others, multisig.when, hash)
: null
);
}, [api, callData, callWeight, hash, isCallOverride, isMultiCall, others, multisig, threshold, type]);
// when the actual call input changes, create a call and set it
const _setCallData = useCallback(
(callHex: string): void => {
try {
assert(isHex(callHex), 'Hex call data required');
const callData = api.createType('Call', callHex);
setCallData(
callData.hash.eq(hash)
? callData
: null
);
} catch (error) {
setCallData(null);
}
},
[api, hash]
);
const isAye = type === 'aye';
return (
<Modal
className={className}
header={t<string>('Pending call hashes')}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t('The call hash from the list of available and unapproved calls.')}>
<Dropdown
help={t<string>('The call hashes that have not been executed as of yet.')}
label={t<string>('pending hashes')}
onChange={setHash}
options={hashes}
value={hash}
/>
</Modal.Columns>
<Modal.Columns hint={t('The operation type to apply. For approvals both non-final and final approvals are supported.')}>
<Dropdown
help={t<string>('Either approve or reject this call.')}
label={t<string>('approval type')}
onChange={setType}
options={calltypes}
value={type}
/>
</Modal.Columns>
{whoFilter.length !== 0 && (
<>
<Modal.Columns hint={t('For approvals outstanding approvers will be shown, for hashes that should be cancelled the first approver is required.')}>
<InputAddress
filter={whoFilter}
help={t<string>('The signatory to send the approval/cancel from')}
label={t<string>('signatory')}
onChange={setSignatory}
/>
</Modal.Columns>
{type === 'aye' && isMultiCall && (
<>
{isCallOverride && (
<Modal.Columns hint={t('The call data for this transaction matching the hash. Once sent, the multisig will be executed against this.')}>
<Input
autoFocus
help={t('For final approvals, the actual full call data is required to execute the transaction')}
isError={!callData}
label={t('call data for final approval')}
onChange={_setCallData}
/>
</Modal.Columns>
)}
<Modal.Columns hint={t('Swap to a non-executing approval type, with subsequent calls providing the actual call data.')}>
<Toggle
className='tipToggle'
label={
isMultiCall
? t<string>('Multisig message with call (for final approval)')
: t<string>('Multisig approval with hash (non-final approval)')
}
onChange={setCallOverride}
value={isCallOverride}
/>
</Modal.Columns>
</>
)}
</>
)}
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={signatory}
extrinsic={tx}
icon={isAye ? 'check' : 'times'}
isDisabled={!tx || (isAye && !whoFilter.length)}
label={isAye ? 'Approve' : 'Reject'}
onStart={onClose}
/>
</Modal.Actions>
</Modal>
);
}