Tie account to genesis (#355)
* Tie account to genesis * Allow (faster) partial retrieval * Edit toggles menu
This commit is contained in:
parent
d521d5780b
commit
f773342343
@ -4,7 +4,7 @@
|
||||
|
||||
import { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountShow, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore } from '../types';
|
||||
import { AccountJson, AuthorizeRequest, MessageTypes, MetadataRequest, RequestAccountCreateExternal, RequestAccountCreateSuri, RequestAccountEdit, RequestAccountExport, RequestAccountShow, RequestAccountTie, RequestAccountValidate, RequestAuthorizeApprove, RequestAuthorizeReject, RequestDeriveCreate, ResponseDeriveValidate, RequestMetadataApprove, RequestMetadataReject, RequestSigningApprovePassword, RequestSigningApproveSignature, RequestSigningCancel, RequestSeedCreate, RequestTypes, ResponseAccountExport, RequestAccountForget, ResponseSeedCreate, RequestSeedValidate, RequestDeriveValidate, ResponseSeedValidate, ResponseType, SigningRequest, RequestJsonRestore, ResponseJsonRestore } from '../types';
|
||||
|
||||
import chrome from '@polkadot/extension-inject/chrome';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
@ -79,6 +79,16 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private accountsTie ({ address, genesisHash }: RequestAccountTie): boolean {
|
||||
const pair = keyring.getPair(address);
|
||||
|
||||
assert(pair, 'Unable to find pair');
|
||||
|
||||
keyring.saveAccountMeta(pair, { ...pair.meta, genesisHash });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private accountsValidate ({ address, password }: RequestAccountValidate): boolean {
|
||||
try {
|
||||
keyring.backupAccount(keyring.getPair(address), password);
|
||||
@ -157,6 +167,10 @@ export default class Extension {
|
||||
return true;
|
||||
}
|
||||
|
||||
private metadataGet (genesisHash: string | null): MetadataDef | null {
|
||||
return this.#state.knownMetadata.find((result) => result.genesisHash === genesisHash) || null;
|
||||
}
|
||||
|
||||
private metadataList (): MetadataDef[] {
|
||||
return this.#state.knownMetadata;
|
||||
}
|
||||
@ -394,15 +408,21 @@ export default class Extension {
|
||||
case 'pri(accounts.show)':
|
||||
return this.accountsShow(request as RequestAccountShow);
|
||||
|
||||
case 'pri(accounts.validate)':
|
||||
return this.accountsValidate(request as RequestAccountValidate);
|
||||
|
||||
case 'pri(accounts.subscribe)':
|
||||
return this.accountsSubscribe(id, port);
|
||||
|
||||
case 'pri(accounts.tie)':
|
||||
return this.accountsTie(request as RequestAccountTie);
|
||||
|
||||
case 'pri(accounts.validate)':
|
||||
return this.accountsValidate(request as RequestAccountValidate);
|
||||
|
||||
case 'pri(metadata.approve)':
|
||||
return this.metadataApprove(request as RequestMetadataApprove);
|
||||
|
||||
case 'pri(metadata.get)':
|
||||
return this.metadataGet(request as string);
|
||||
|
||||
case 'pri(metadata.list)':
|
||||
return this.metadataList();
|
||||
|
||||
|
||||
@ -72,6 +72,7 @@ export interface RequestSignatures {
|
||||
'pri(accounts.export)': [RequestAccountExport, ResponseAccountExport];
|
||||
'pri(accounts.forget)': [RequestAccountForget, boolean];
|
||||
'pri(accounts.show)': [RequestAccountShow, boolean];
|
||||
'pri(accounts.tie)': [RequestAccountTie, boolean];
|
||||
'pri(accounts.subscribe)': [RequestAccountSubscribe, boolean, AccountJson[]];
|
||||
'pri(accounts.validate)': [RequestAccountValidate, boolean];
|
||||
'pri(authorize.approve)': [RequestAuthorizeApprove, boolean];
|
||||
@ -83,6 +84,7 @@ export interface RequestSignatures {
|
||||
'pri(json.verify.file)': [RequestJsonRestore, boolean];
|
||||
'pri(json.verify.password)': [string, boolean];
|
||||
'pri(metadata.approve)': [RequestMetadataApprove, boolean];
|
||||
'pri(metadata.get)': [string | null, MetadataDef | null];
|
||||
'pri(metadata.reject)': [RequestMetadataReject, boolean];
|
||||
'pri(metadata.requests)': [RequestMetadataSubscribe, boolean, MetadataRequest[]];
|
||||
'pri(metadata.list)': [null, MetadataDef[]];
|
||||
@ -180,6 +182,11 @@ export interface RequestAccountShow {
|
||||
isShowing: boolean;
|
||||
}
|
||||
|
||||
export interface RequestAccountTie {
|
||||
address: string;
|
||||
genesisHash: string | null;
|
||||
}
|
||||
|
||||
export interface RequestAccountValidate {
|
||||
address: string;
|
||||
password: string;
|
||||
|
||||
29
packages/extension-chains/src/genesisHashes.ts
Normal file
29
packages/extension-chains/src/genesisHashes.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// Copyright 2019-2020 @polkadot/extension-chains authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
interface Option {
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const hashes: Option[] = [
|
||||
{
|
||||
text: 'Allow use on any chain',
|
||||
value: ''
|
||||
},
|
||||
{
|
||||
text: 'Polkadot CC1 (live)',
|
||||
value: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
|
||||
},
|
||||
{
|
||||
text: 'Kusama (canary)',
|
||||
value: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe'
|
||||
},
|
||||
{
|
||||
text: 'Edgeware (live)',
|
||||
value: '0x742a2ca70c2fda6cee4f8df98d64c4c670a052d9568058982dad9d5a7a135c5b'
|
||||
}
|
||||
];
|
||||
|
||||
export default hashes;
|
||||
@ -11,20 +11,27 @@ import { Metadata, TypeRegistry } from '@polkadot/types';
|
||||
// inside the api, run `yarn chain:info --ws <url>`
|
||||
import kusama from './kusama';
|
||||
|
||||
const definitions: Map<string, MetadataDef> = new Map(
|
||||
const definitions = new Map<string, MetadataDef>(
|
||||
[kusama].map((def) => [def.genesisHash, def])
|
||||
);
|
||||
|
||||
function metadataExpand (definition: MetadataDef): Chain {
|
||||
const expanded = new Map<string, Chain>();
|
||||
|
||||
export function metadataExpand (definition: MetadataDef, isPartial = false): Chain {
|
||||
const cached = expanded.get(definition.genesisHash);
|
||||
|
||||
if (cached && cached.specVersion === definition.specVersion) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const { chain, genesisHash, icon, metaCalls, specVersion, ss58Format, tokenDecimals, tokenSymbol, types } = definition;
|
||||
const registry = new TypeRegistry();
|
||||
|
||||
registry.register(types);
|
||||
if (!isPartial) {
|
||||
registry.register(types);
|
||||
}
|
||||
|
||||
const isUnknown = genesisHash === '0x';
|
||||
const metadata = metaCalls
|
||||
? new Metadata(registry, Buffer.from(metaCalls, 'base64'))
|
||||
: null;
|
||||
|
||||
registry.setChainProperties(registry.createType('ChainProperties', {
|
||||
ss58Format,
|
||||
@ -32,7 +39,11 @@ function metadataExpand (definition: MetadataDef): Chain {
|
||||
tokenSymbol
|
||||
}));
|
||||
|
||||
return {
|
||||
const metadata = metaCalls && !isPartial
|
||||
? new Metadata(registry, Buffer.from(metaCalls, 'base64'))
|
||||
: null;
|
||||
|
||||
const result = {
|
||||
definition,
|
||||
genesisHash: isUnknown
|
||||
? undefined
|
||||
@ -47,6 +58,12 @@ function metadataExpand (definition: MetadataDef): Chain {
|
||||
tokenDecimals,
|
||||
tokenSymbol
|
||||
};
|
||||
|
||||
if (result.genesisHash && !isPartial) {
|
||||
expanded.set(result.genesisHash, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function findChain (definitions: MetadataDef[], genesisHash?: string | null): Chain | null {
|
||||
|
||||
@ -7,9 +7,10 @@ import { ThemeProps } from '../../types';
|
||||
|
||||
import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import genesisOptions from '@polkadot/extension-chains/genesisHashes';
|
||||
|
||||
import { ActionContext, Address, Checkbox, Link } from '../../components';
|
||||
import { editAccount, showAccount } from '../../messaging';
|
||||
import { ActionContext, Address, Checkbox, Dropdown, Link } from '../../components';
|
||||
import { editAccount, showAccount, tieAccount } from '../../messaging';
|
||||
import { Name } from '../../partials';
|
||||
|
||||
interface Props extends AccountJson {
|
||||
@ -17,13 +18,26 @@ interface Props extends AccountJson {
|
||||
parentName?: string;
|
||||
}
|
||||
|
||||
function Account ({ address, className, isExternal, isHidden, parentName, suri }: Props): React.ReactElement<Props> {
|
||||
interface EditState {
|
||||
isEditing: boolean;
|
||||
toggleActions: number;
|
||||
}
|
||||
|
||||
function Account ({ address, className, genesisHash, isExternal, isHidden, parentName, suri }: Props): React.ReactElement<Props> {
|
||||
const onAction = useContext(ActionContext);
|
||||
const [isEditing, setEditing] = useState(false);
|
||||
const [{ isEditing, toggleActions }, setEditing] = useState<EditState>({ isEditing: false, toggleActions: 0 });
|
||||
const [editedName, setName] = useState<string | null>(null);
|
||||
|
||||
const _onChangeGenesis = useCallback(
|
||||
(genesisHash?: string | null): void => {
|
||||
tieAccount(address, genesisHash || null)
|
||||
.catch(console.error);
|
||||
},
|
||||
[address]
|
||||
);
|
||||
|
||||
const _toggleEdit = useCallback(
|
||||
(): void => setEditing(!isEditing),
|
||||
(): void => setEditing(({ toggleActions }) => ({ isEditing: !isEditing, toggleActions: ++toggleActions })),
|
||||
[isEditing]
|
||||
);
|
||||
|
||||
@ -39,6 +53,7 @@ function Account ({ address, className, isExternal, isHidden, parentName, suri }
|
||||
},
|
||||
[editedName, address, _toggleEdit, onAction]
|
||||
);
|
||||
|
||||
const _toggleVisibility = useCallback(
|
||||
(): void => {
|
||||
showAccount(address, isHidden || false)
|
||||
@ -87,8 +102,15 @@ function Account ({ address, className, isExternal, isHidden, parentName, suri }
|
||||
label='Visible (always inject)'
|
||||
onClick={_toggleVisibility}
|
||||
/>
|
||||
<Dropdown
|
||||
className='menuItem'
|
||||
label=''
|
||||
onChange={_onChangeGenesis}
|
||||
options={genesisOptions}
|
||||
value={genesisHash || ''}
|
||||
/>
|
||||
</>
|
||||
), [_toggleEdit, _toggleVisibility, address, isExternal, isHidden]);
|
||||
), [_onChangeGenesis, _toggleEdit, _toggleVisibility, address, genesisHash, isExternal, isHidden]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -96,9 +118,11 @@ function Account ({ address, className, isExternal, isHidden, parentName, suri }
|
||||
actions={_actions}
|
||||
address={address}
|
||||
className='address'
|
||||
genesisHash={genesisHash}
|
||||
name={editedName}
|
||||
parentName={parentName}
|
||||
suri={suri}
|
||||
toggleActions={toggleActions}
|
||||
>
|
||||
{isEditing && (
|
||||
<Name
|
||||
@ -141,6 +165,7 @@ export default styled(Account)(({ theme }: ThemeProps) => `
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
margin: 0;
|
||||
min-width: 13rem;
|
||||
padding: 4px 16px;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -33,6 +33,7 @@ interface Props {
|
||||
actions?: React.ReactNode;
|
||||
parentName?: string | null;
|
||||
suri?: string;
|
||||
toggleActions?: number;
|
||||
}
|
||||
|
||||
interface Recoded {
|
||||
@ -67,12 +68,12 @@ function recodeAddress (address: string, accounts: AccountWithChildren[], chain:
|
||||
};
|
||||
}
|
||||
|
||||
const ACCOUNTS_SCREEN_HEIGHT = 500;
|
||||
const ACCOUNTS_SCREEN_HEIGHT = 550;
|
||||
|
||||
function Address ({ actions, address, children, className, genesisHash, name, parentName, suri }: Props): React.ReactElement<Props> {
|
||||
function Address ({ actions, address, children, className, genesisHash, name, parentName, suri, toggleActions }: Props): React.ReactElement<Props> {
|
||||
const { accounts } = useContext(AccountContext);
|
||||
const settings = useContext(SettingsContext);
|
||||
const chain = useMetadata(genesisHash);
|
||||
const chain = useMetadata(genesisHash, true);
|
||||
const [{ account, formatted, prefix }, setRecoded] = useState<Recoded>({ account: null, formatted: null, prefix: 42 });
|
||||
const [showActionsMenu, setShowActionsMenu] = useState(false);
|
||||
const [moveMenuUp, setIsMovedMenu] = useState(false);
|
||||
@ -99,6 +100,10 @@ function Address ({ actions, address, children, className, genesisHash, name, pa
|
||||
}
|
||||
}, [showActionsMenu]);
|
||||
|
||||
useEffect((): void => {
|
||||
setShowActionsMenu(false);
|
||||
}, [toggleActions]);
|
||||
|
||||
const theme = ((chain && chain.icon) || 'polkadot') as 'polkadot';
|
||||
const _onClick = useCallback((): void => setShowActionsMenu(!showActionsMenu), [showActionsMenu]);
|
||||
const _onCopy = useCallback((): void => show('Copied'), [show]);
|
||||
@ -125,7 +130,7 @@ function Address ({ actions, address, children, className, genesisHash, name, pa
|
||||
className='parentName'
|
||||
data-field='parent'
|
||||
>
|
||||
{parentName}{suri || ''}
|
||||
{parentName} {suri || ''}
|
||||
</div>
|
||||
</div>
|
||||
<div className='name displaced'>{displayedName}</div>
|
||||
@ -182,18 +187,18 @@ function Address ({ actions, address, children, className, genesisHash, name, pa
|
||||
);
|
||||
}
|
||||
|
||||
const FullAddress = styled.div`
|
||||
const FullAddress = styled.div(({ theme }: ThemeProps) => `
|
||||
width: 270px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
color: ${({ theme }: ThemeProps): string => theme.labelColor};
|
||||
color: ${theme.labelColor};
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
`;
|
||||
`);
|
||||
|
||||
FullAddress.displayName = 'FullAddress';
|
||||
|
||||
const Settings = styled.div`
|
||||
const Settings = styled.div(({ theme }: ThemeProps) => `
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -213,14 +218,14 @@ const Settings = styled.div`
|
||||
top: 25%;
|
||||
bottom: 25%;
|
||||
width: 1px;
|
||||
background: ${({ theme }: ThemeProps): string => theme.boxBorderColor};
|
||||
background: ${theme.boxBorderColor};
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: ${({ theme }: ThemeProps): string => theme.readonlyInputBackground};
|
||||
background: ${theme.readonlyInputBackground};
|
||||
}
|
||||
`;
|
||||
`);
|
||||
|
||||
Settings.displayName = 'Details';
|
||||
|
||||
|
||||
@ -8,14 +8,18 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { getMetadata } from '../messaging';
|
||||
|
||||
export default function useMetadata (genesisHash?: string | null): Chain | null {
|
||||
export default function useMetadata (genesisHash?: string | null, isPartial?: boolean): Chain | null {
|
||||
const [chain, setChain] = useState<Chain | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
getMetadata(genesisHash)
|
||||
.then(setChain)
|
||||
.catch(console.error);
|
||||
}, [genesisHash]);
|
||||
if (genesisHash) {
|
||||
getMetadata(genesisHash, isPartial)
|
||||
.then(setChain)
|
||||
.catch(console.error);
|
||||
} else {
|
||||
setChain(null);
|
||||
}
|
||||
}, [genesisHash, isPartial]);
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
@ -5,11 +5,12 @@
|
||||
import { Message } from '@polkadot/extension-base/types';
|
||||
import { AccountJson, AuthorizeRequest, SigningRequest, RequestTypes, MessageTypes, ResponseTypes, SeedLengths, SubscriptionMessageTypes, MetadataRequest, MessageTypesWithNullRequest, MessageTypesWithNoSubscriptions, MessageTypesWithSubscriptions, ResponseDeriveValidate, ResponseJsonRestore } from '@polkadot/extension-base/background/types';
|
||||
import { Chain } from '@polkadot/extension-chains/types';
|
||||
import { MetadataDef } from '@polkadot/extension-inject/types';
|
||||
import { KeypairType } from '@polkadot/util-crypto/types';
|
||||
|
||||
import { PORT_EXTENSION } from '@polkadot/extension-base/defaults';
|
||||
import chrome from '@polkadot/extension-inject/chrome';
|
||||
import { findChain } from '@polkadot/extension-chains';
|
||||
import { metadataExpand } from '@polkadot/extension-chains';
|
||||
import { KeyringPair$Json } from '@polkadot/keyring/types';
|
||||
|
||||
interface Handler {
|
||||
@ -22,6 +23,7 @@ interface Handler {
|
||||
|
||||
type Handlers = Record<string, Handler>;
|
||||
|
||||
const metadataGets = new Map<string, Promise<MetadataDef | null>>();
|
||||
const port = chrome.runtime.connect({ name: PORT_EXTENSION });
|
||||
const handlers: Handlers = {};
|
||||
let idCounter = 0;
|
||||
@ -71,6 +73,10 @@ export async function showAccount (address: string, isShowing: boolean): Promise
|
||||
return sendMessage('pri(accounts.show)', { address, isShowing });
|
||||
}
|
||||
|
||||
export async function tieAccount (address: string, genesisHash: string | null): Promise<boolean> {
|
||||
return sendMessage('pri(accounts.tie)', { address, genesisHash });
|
||||
}
|
||||
|
||||
export async function exportAccount (address: string, password: string): Promise<{ exportedJson: string }> {
|
||||
return sendMessage('pri(accounts.export)', { address, password });
|
||||
}
|
||||
@ -115,10 +121,23 @@ export async function createSeed (length?: SeedLengths, type?: KeypairType): Pro
|
||||
return sendMessage('pri(seed.create)', { length, type });
|
||||
}
|
||||
|
||||
export async function getMetadata (genesisHash?: string | null): Promise<Chain | null> {
|
||||
const definitions = await sendMessage('pri(metadata.list)');
|
||||
export async function getMetadata (genesisHash?: string | null, isPartial = false): Promise<Chain | null> {
|
||||
if (!genesisHash) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return findChain(definitions || [], genesisHash);
|
||||
let request = metadataGets.get(genesisHash);
|
||||
|
||||
if (!request) {
|
||||
request = sendMessage('pri(metadata.get)', genesisHash || null);
|
||||
metadataGets.set(genesisHash, request);
|
||||
}
|
||||
|
||||
const def = await request;
|
||||
|
||||
return def
|
||||
? metadataExpand(def, isPartial)
|
||||
: null;
|
||||
}
|
||||
|
||||
export async function rejectAuthRequest (id: string): Promise<boolean> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user