From bdfbb6994650b42d4d76847eb4fce43cea234c55 Mon Sep 17 00:00:00 2001 From: kwingram25 Date: Thu, 6 Jun 2019 13:57:07 +0200 Subject: [PATCH] Add contracts to ui-keyring (#137) * add contracts to keyring * genesisHash * requested changes + allPlus option type * remove duplicate fn * add contracts to keyring * genesisHash * requested changes + allPlus option type * remove duplicate fn * changes * contract key * prefixes --- packages/ui-keyring/src/Base.ts | 16 +++- packages/ui-keyring/src/Keyring.ts | 82 ++++++++++++++++--- packages/ui-keyring/src/defaults.ts | 12 ++- .../ui-keyring/src/observable/contracts.ts | 9 ++ packages/ui-keyring/src/observable/index.ts | 9 +- packages/ui-keyring/src/options/index.ts | 23 ++++++ packages/ui-keyring/src/options/types.ts | 2 + packages/ui-keyring/src/types.ts | 15 ++++ 8 files changed, 151 insertions(+), 17 deletions(-) create mode 100644 packages/ui-keyring/src/observable/contracts.ts diff --git a/packages/ui-keyring/src/Base.ts b/packages/ui-keyring/src/Base.ts index 855e9e8a..0e2a49b5 100644 --- a/packages/ui-keyring/src/Base.ts +++ b/packages/ui-keyring/src/Base.ts @@ -12,6 +12,7 @@ import { isBoolean, isString } from '@polkadot/util'; import accounts from './observable/accounts'; import addresses from './observable/addresses'; +import contracts from './observable/contracts'; import env from './observable/development'; import LocalStorageStore from './stores/LocalStorage'; import { MAX_PASS_LEN } from './defaults'; @@ -19,13 +20,16 @@ import { MAX_PASS_LEN } from './defaults'; export default class Base { private _accounts: AddressSubject; private _addresses: AddressSubject; + private _contracts: AddressSubject; private _keyring?: KeyringInstance; private _prefix?: Prefix; + protected _genesisHash?: string; protected _store: KeyringStore; constructor () { this._accounts = accounts; this._addresses = addresses; + this._contracts = contracts; this._keyring = undefined; this._store = null as any; } @@ -38,6 +42,10 @@ export default class Base { return this._addresses; } + get contracts () { + return this._contracts; + } + get keyring (): KeyringInstance { if (this._keyring) { return this._keyring; @@ -46,6 +54,10 @@ export default class Base { throw new Error(`Keyring should be initialised via 'loadAll' before use`); } + get genesisHash () { + return this._genesisHash; + } + decodeAddress (key: string | Uint8Array, ignoreChecksum?: boolean): Uint8Array { return this.keyring.decodeAddress(key, ignoreChecksum); } @@ -67,11 +79,12 @@ export default class Base { isAvailable (_address: Uint8Array | string): boolean { const accountsValue = this.accounts.subject.getValue(); const addressesValue = this.addresses.subject.getValue(); + const contractsValue = this.contracts.subject.getValue(); const address = isString(_address) ? _address : this.encodeAddress(_address); - return !accountsValue[address] && !addressesValue[address]; + return !accountsValue[address] && !addressesValue[address] && !contractsValue[address]; } isPassValid (password: string): boolean { @@ -94,6 +107,7 @@ export default class Base { } this._keyring = keyring; + this._genesisHash = options.genesisHash && options.genesisHash.toHex(); this._store = options.store || new LocalStorageStore(); this.addAccountPairs(); diff --git a/packages/ui-keyring/src/Keyring.ts b/packages/ui-keyring/src/Keyring.ts index 6e539725..bcd65cc7 100644 --- a/packages/ui-keyring/src/Keyring.ts +++ b/packages/ui-keyring/src/Keyring.ts @@ -12,7 +12,7 @@ import { hexToU8a, isHex, isString, u8aToHex } from '@polkadot/util'; import env from './observable/development'; import Base from './Base'; -import { accountKey, addressKey, accountRegex, addressRegex } from './defaults'; +import { accountKey, addressKey, accountRegex, addressRegex, contractKey, contractRegex } from './defaults'; import keyringOption from './options'; const RECENT_EXPIRY = 24 * 60 * 60; @@ -101,6 +101,10 @@ export class Keyring extends Base implements KeyringStruct { this.addresses.remove(this._store, address); } + forgetContract (address: string): void { + this.contracts.remove(this._store, address); + } + getAccount (address: string | Uint8Array): KeyringAddress { return this.getAddress(address, 'account'); } @@ -114,14 +118,21 @@ export class Keyring extends Base implements KeyringStruct { .filter((account) => env.isDevelopment() || account.getMeta().isTesting !== true); } - getAddress (_address: string | Uint8Array, type: 'account' | 'address' = 'address'): KeyringAddress { + getAddress (_address: string | Uint8Array, type: 'account' | 'address' | 'contract' = 'address'): KeyringAddress { const address = isString(_address) ? _address : this.encodeAddress(_address); const publicKey = this.decodeAddress(address); - const subject = type === 'account' - ? this.accounts.subject - : this.addresses.subject; + const subject = (() => { + switch (type) { + case 'account': + return this.accounts.subject; + case 'address': + return this.addresses.subject; + case 'contract': + return this.contracts.subject; + } + })(); return { address: (): string => @@ -143,6 +154,22 @@ export class Keyring extends Base implements KeyringStruct { .map((address) => this.getAddress(address)); } + getContract (address: string | Uint8Array): KeyringAddress { + return this.getAddress(address, 'contract'); + } + + getContracts (): Array { + const available = this.contracts.subject.getValue(); + + return Object + .entries(available) + .filter(([, data]) => { + const { json: { meta: { contract } } } = data; + return contract && contract.genesisHash === this.genesisHash; + }) + .map(([address]) => this.getContract(address)); + } + private rewriteKey (json: KeyringJson, key: string, hexAddr: string, creator: (addr: string) => string) { if (hexAddr.substr(0, 2) === '0x') { return; @@ -185,6 +212,16 @@ export class Keyring extends Base implements KeyringStruct { this.rewriteKey(json, key, hexAddr, addressKey); } + private loadContract (json: KeyringJson, key: string) { + const address = this.encodeAddress( + this.decodeAddress(json.address) + ); + const [, hexAddr] = key.split(':'); + + this.contracts.add(this._store, address, json); + this.rewriteKey(json, key, hexAddr, contractKey); + } + private loadInjected (address: string, meta: KeyringJson$Meta) { const json = { address, @@ -202,10 +239,20 @@ export class Keyring extends Base implements KeyringStruct { super.initKeyring(options); this._store.all((key: string, json: KeyringJson) => { - if (accountRegex.test(key)) { - this.loadAccount(json, key); - } else if (addressRegex.test(key)) { - this.loadAddress(json, key); + if (options.filter ? options.filter(json) : true) { + if (accountRegex.test(key)) { + this.loadAccount(json, key); + } else if (addressRegex.test(key)) { + this.loadAddress(json, key); + } else if (contractRegex.test(key)) { + if ( + json.meta && json.meta.contract && + this.genesisHash && + this.genesisHash === json.meta.contract.genesisHash + ) { + this.loadContract(json, key); + } + } } }); @@ -258,7 +305,7 @@ export class Keyring extends Base implements KeyringStruct { }); } - saveAddress (address: string, meta: KeyringPair$Meta): KeyringPair$Json { + saveAddress (address: string, meta: KeyringPair$Meta, type: 'address' | 'contract' = 'address'): KeyringPair$Json { const available = this.addresses.subject.getValue(); const json = (available[address] && available[address].json) || { @@ -275,11 +322,24 @@ export class Keyring extends Base implements KeyringStruct { delete json.meta.isRecent; - this.addresses.add(this._store, address, json); + const key = (() => { + switch (type) { + case 'contract': + return 'contracts'; + default: + return 'addresses'; + } + })(); + + this[key].add(this._store, address, json); return json as KeyringPair$Json; } + saveContract (address: string, meta: KeyringPair$Meta): KeyringPair$Json { + return this.saveAddress(address, meta, 'contract'); + } + saveRecent (address: string): SingleAddress { const available = this.addresses.subject.getValue(); diff --git a/packages/ui-keyring/src/defaults.ts b/packages/ui-keyring/src/defaults.ts index 09433e98..02aed915 100644 --- a/packages/ui-keyring/src/defaults.ts +++ b/packages/ui-keyring/src/defaults.ts @@ -7,6 +7,7 @@ import { decodeAddress } from '@polkadot/keyring'; const ACCOUNT_PREFIX = 'account:'; const ADDRESS_PREFIX = 'address:'; +const CONTRACT_PREFIX = 'contract:'; const MAX_PASS_LEN = 32; function toHex (address: string): string { @@ -22,14 +23,21 @@ const accountKey = (address: string): string => const addressKey = (address: string): string => `${ADDRESS_PREFIX}${toHex(address)}`; -const accountRegex = new RegExp(`^${ACCOUNT_PREFIX}`, ''); +const contractKey = (address: string): string => + `${CONTRACT_PREFIX}${toHex(address)}`; -const addressRegex = new RegExp(`^${ADDRESS_PREFIX}`, ''); +const accountRegex = new RegExp(`^${ACCOUNT_PREFIX}0x[0-9a-f]*`, ''); + +const addressRegex = new RegExp(`^${ADDRESS_PREFIX}0x[0-9a-f]*`, ''); + +const contractRegex = new RegExp(`^${CONTRACT_PREFIX}0x[0-9a-f]*`, ''); export { accountKey, accountRegex, addressKey, addressRegex, + contractKey, + contractRegex, MAX_PASS_LEN }; diff --git a/packages/ui-keyring/src/observable/contracts.ts b/packages/ui-keyring/src/observable/contracts.ts new file mode 100644 index 00000000..b126a748 --- /dev/null +++ b/packages/ui-keyring/src/observable/contracts.ts @@ -0,0 +1,9 @@ +// Copyright 2017-2019 @polkadot/ui-keyring authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { contractKey } from '../defaults'; + +import genericSubject from './genericSubject'; + +export default genericSubject(contractKey); diff --git a/packages/ui-keyring/src/observable/index.ts b/packages/ui-keyring/src/observable/index.ts index 75eb284b..10139485 100644 --- a/packages/ui-keyring/src/observable/index.ts +++ b/packages/ui-keyring/src/observable/index.ts @@ -7,13 +7,16 @@ import { map } from 'rxjs/operators'; import accounts from './accounts'; import addresses from './addresses'; +import contracts from './contracts'; export default combineLatest( accounts.subject, - addresses.subject + addresses.subject, + contracts.subject ).pipe( - map(([accounts, addresses]) => ({ + map(([accounts, addresses, contracts]) => ({ accounts, - addresses + addresses, + contracts })) ); diff --git a/packages/ui-keyring/src/options/index.ts b/packages/ui-keyring/src/options/index.ts index 74e9758a..13c17147 100644 --- a/packages/ui-keyring/src/options/index.ts +++ b/packages/ui-keyring/src/options/index.ts @@ -56,6 +56,7 @@ class KeyringOption implements KeyringOptionInstance { this.addAccounts(keyring, options); this.addAddresses(keyring, options); + this.addContracts(keyring, options); options.address = this.linkItems({ 'Addresses': options.address, @@ -65,12 +66,21 @@ class KeyringOption implements KeyringOptionInstance { 'Accounts': options.account, 'Development': options.testing }); + options.contract = this.linkItems({ + 'Contracts': options.contract + }); options.all = ([] as KeyringSectionOptions).concat( options.account, options.address ); + options.allPlus = ([] as KeyringSectionOptions).concat( + options.account, + options.address, + options.contract + ); + this.optionsSubject.next(options); }); @@ -129,11 +139,24 @@ class KeyringOption implements KeyringOptionInstance { }); } + private addContracts (keyring: KeyringStruct, options: KeyringOptions): void { + const available = keyring.contracts.subject.getValue(); + + Object + .values(available) + .sort(sortByName) + .forEach(({ option }: SingleAddress) => { + options.contract.push(option); + }); + } + private emptyOptions (): KeyringOptions { return { account: [], address: [], + contract: [], all: [], + allPlus: [], recent: [], testing: [] }; diff --git a/packages/ui-keyring/src/options/types.ts b/packages/ui-keyring/src/options/types.ts index 5ca7d97e..0f9e73c8 100644 --- a/packages/ui-keyring/src/options/types.ts +++ b/packages/ui-keyring/src/options/types.ts @@ -20,6 +20,8 @@ export type KeyringOptions = { account: KeyringSectionOptions, address: KeyringSectionOptions, all: KeyringSectionOptions, + allPlus: KeyringSectionOptions, + contract: KeyringSectionOptions, recent: KeyringSectionOptions, testing: KeyringSectionOptions }; diff --git a/packages/ui-keyring/src/types.ts b/packages/ui-keyring/src/types.ts index 63f0385e..1ee1ad0c 100644 --- a/packages/ui-keyring/src/types.ts +++ b/packages/ui-keyring/src/types.ts @@ -2,10 +2,16 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. +import { Hash } from '@polkadot/types'; import { KeyringInstance as BaseKeyringInstance, KeyringPair, KeyringPair$Meta, KeyringPair$Json, KeyringOptions as KeyringOptionsBase } from '@polkadot/keyring/types'; import { KeypairType } from '@polkadot/util-crypto/types'; import { AddressSubject, SingleAddress } from './observable/types'; +export type ContractMeta = { + abi: string, + genesisHash?: string +}; + export interface KeyringStore { all: (cb: (key: string, value: any) => void) => void; get: (key: string, cb: (value: any) => void) => void; @@ -14,11 +20,14 @@ export interface KeyringStore { } export type KeyringOptions = KeyringOptionsBase & { + filter?: (json: KeyringJson) => boolean, + genesisHash?: Hash, isDevelopment?: boolean, store?: KeyringStore }; export type KeyringJson$Meta = { + contract?: ContractMeta, isInjected?: boolean, isRecent?: boolean, isTesting?: boolean, @@ -49,7 +58,9 @@ export type CreateResult = { export interface KeyringStruct { readonly accounts: AddressSubject; readonly addresses: AddressSubject; + readonly contracts: AddressSubject; readonly keyring: BaseKeyringInstance | undefined; + readonly genesisHash?: string; addExternal: (publicKey: Uint8Array, meta?: KeyringPair$Meta) => CreateResult; addPair: (pair: KeyringPair, password: string) => CreateResult; @@ -64,10 +75,13 @@ export interface KeyringStruct { encryptAccount: (pair: KeyringPair, password: string) => void; forgetAccount: (address: string) => void; forgetAddress: (address: string) => void; + forgetContract: (address: string) => void; getAccount: (address: string | Uint8Array) => KeyringAddress; getAccounts: () => Array; getAddress: (address: string | Uint8Array) => KeyringAddress; getAddresses: () => Array; + getContract: (address: string | Uint8Array) => KeyringAddress; + getContracts: (genesisHash?: string) => Array; getPair: (address: string | Uint8Array) => KeyringPair; getPairs: () => Array; isAvailable: (address: string | Uint8Array) => boolean; @@ -77,6 +91,7 @@ export interface KeyringStruct { saveAccount: (pair: KeyringPair, password?: string) => KeyringPair$Json; saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta) => void; saveAddress: (address: string, meta: KeyringPair$Meta) => KeyringPair$Json; + saveContract: (address: string, meta: KeyringPair$Meta) => KeyringPair$Json; saveRecent: (address: string) => SingleAddress; setDevMode: (isDevelopment: boolean) => void; }