From b45d2fb9ae95232f882344a4ba1fefbe5259151e Mon Sep 17 00:00:00 2001 From: Thiendekaco Date: Mon, 26 Feb 2024 14:56:12 +0700 Subject: [PATCH] Add Polkadot Vault wallet package --- packages/core/src/utils.ts | 61 +++-- packages/core/src/views/connect/Index.svelte | 3 +- packages/core/src/views/shared/Modal.svelte | 2 + packages/demo/package.json | 1 + .../src/components/account/AccountList.tsx | 10 +- packages/demo/src/utils/api/substrateApi.ts | 47 +++- packages/demo/src/utils/network.ts | 2 +- packages/demo/src/web3-onboard.ts | 16 +- packages/hw-common/src/account-select.ts | 2 +- packages/hw-common/src/utils.ts | 28 ++- packages/polkadotVault/.eslintrc.cjs | 44 ++++ packages/polkadotVault/.prettierrc.cjs | 9 + packages/polkadotVault/package.json | 94 ++++++++ packages/polkadotVault/rollup.config.js | 49 ++++ packages/polkadotVault/src/constants.ts | 29 +++ packages/polkadotVault/src/icon/close.ts | 7 + .../polkadotVault/src/icon/imageSquare.ts | 10 + packages/polkadotVault/src/icon/index.ts | 4 + packages/polkadotVault/src/icon/logoWallet.ts | 7 + packages/polkadotVault/src/index.ts | 151 ++++++++++++ packages/polkadotVault/src/streams.ts | 8 + packages/polkadotVault/src/types.ts | 32 +++ packages/polkadotVault/src/utils.ts | 118 ++++++++++ .../src/views/CloseButton.svelte | 49 ++++ packages/polkadotVault/src/views/Modal.svelte | 218 ++++++++++++++++++ .../src/views/ModalConnect.svelte | 218 ++++++++++++++++++ .../src/views/QrCodeModal.svelte | 164 +++++++++++++ .../polkadotVault/src/views/ScanQRCode.svelte | 189 +++++++++++++++ .../src/views/ScanQrModal.svelte | 80 +++++++ packages/polkadotVault/src/views/global.d.ts | 1 + packages/polkadotVault/src/views/index.ts | 160 +++++++++++++ packages/polkadotVault/tsconfig.json | 16 ++ .../src/walletConnect.ts | 8 +- yarn.lock | 5 + 34 files changed, 1784 insertions(+), 58 deletions(-) create mode 100644 packages/polkadotVault/.eslintrc.cjs create mode 100644 packages/polkadotVault/.prettierrc.cjs create mode 100644 packages/polkadotVault/package.json create mode 100644 packages/polkadotVault/rollup.config.js create mode 100644 packages/polkadotVault/src/constants.ts create mode 100644 packages/polkadotVault/src/icon/close.ts create mode 100644 packages/polkadotVault/src/icon/imageSquare.ts create mode 100644 packages/polkadotVault/src/icon/index.ts create mode 100644 packages/polkadotVault/src/icon/logoWallet.ts create mode 100644 packages/polkadotVault/src/index.ts create mode 100644 packages/polkadotVault/src/streams.ts create mode 100644 packages/polkadotVault/src/types.ts create mode 100644 packages/polkadotVault/src/utils.ts create mode 100644 packages/polkadotVault/src/views/CloseButton.svelte create mode 100644 packages/polkadotVault/src/views/Modal.svelte create mode 100644 packages/polkadotVault/src/views/ModalConnect.svelte create mode 100644 packages/polkadotVault/src/views/QrCodeModal.svelte create mode 100644 packages/polkadotVault/src/views/ScanQRCode.svelte create mode 100644 packages/polkadotVault/src/views/ScanQrModal.svelte create mode 100644 packages/polkadotVault/src/views/global.d.ts create mode 100644 packages/polkadotVault/src/views/index.ts create mode 100644 packages/polkadotVault/tsconfig.json diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 2456be933..911c6f9e2 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -130,6 +130,20 @@ export function gweiToWeiHex(gwei: number): string { return `0x${(gwei * 1e9).toString(16)}` } +export enum ChainIdByGenesisHash { + POLKADOT_ID = '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', + KUSAMA_ID = '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', + WESTEND_ID = '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e', + ROCOCO_ID = '0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e', + ASTAR_NETWORK_ID = '0x9eb76c5184c4ab8679d2d5d819fdf90b9c001403e9e17da2e14b6d8aec4029c6', + CRUST_MAINET_ID = '0x8b404e7ed8789d813982b9cb4c8b664c05b3fbf433309f603af014ec9ce56a8c', + HYDRADX_ID = '0xafdc188f45c71dacbaa0b62e16a91f726c7b8699a9748cdf715459de6b7f366d', + PHALA_ID = '0x1bb969d85965e4bb5a651abbedf21a54b6b31a21f66b5401cc3f1e286268d736', + STATEMINT_ID = '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f', + STAEMINE_ID = '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a', + TURING_ID = '0xd54f0988402deb4548538626ce37e4a318441ea0529ca369400ebec4e04dfe4b' +} + export const chainIdToLabel: Record = { '0x1': 'Ethereum', '0x3': 'Ropsten', @@ -150,18 +164,18 @@ export const chainIdToLabel: Record = { '0x63564C40': 'Harmony One', '0xa4b1': 'Arbitrum One', '0xa4ba': 'Arbitrum Nova', - '91b171bb158e2d3848fa23a9f1c25182' : 'Polkadot', 'acala' : 'Acala', - '9eb76c5184c4ab8679d2d5d819fdf90b' : 'Astar Network', - 'd4c0c08ca49dc7c680c3dac71a7c0703' : 'Crust', - 'afdc188f45c71dacbaa0b62e16a91f72' : 'Hydradx', - 'b0a8d493285c2df73290dfb7e61f870f' : 'Kusama', - '1bb969d85965e4bb5a651abbedf21a54' : 'Phala Network', - '68d56f15f85d3136970ec16946040bc1' : 'Statemint', - '0f62b701fb12d02237a33b84818c11f6' : 'Turing Network', - 'e143f23803ac50e8f6f8e62695d1ce9e' : 'Westend', - '6408de7737c59c238890533af25896a2': 'Rococo', - '48239ef607d7928874027a43a6768920': 'Statemine' + [ChainIdByGenesisHash.POLKADOT_ID] : 'Polkadot', + [ChainIdByGenesisHash.ASTAR_NETWORK_ID] : 'Astar Network', + [ChainIdByGenesisHash.CRUST_MAINET_ID] : 'Crust', + [ChainIdByGenesisHash.HYDRADX_ID] : 'Hydradx', + [ChainIdByGenesisHash.KUSAMA_ID] : 'Kusama', + [ChainIdByGenesisHash.PHALA_ID] : 'Phala Network', + [ChainIdByGenesisHash.STATEMINT_ID] : 'Statemint', + [ChainIdByGenesisHash.TURING_ID] : 'Turing Network', + [ChainIdByGenesisHash.WESTEND_ID] : 'Westend', + [ChainIdByGenesisHash.ROCOCO_ID]: 'Rococo', + [ChainIdByGenesisHash.STAEMINE_ID]: 'Statemine' } export const networkToChainId: Record = { @@ -258,7 +272,7 @@ export const chainStyles: Record = { icon: polygonIcon, color: '#8247E5' }, - '91b171bb158e2d3848fa23a9f1c25182' : { + [ChainIdByGenesisHash.POLKADOT_ID] : { icon : polkadotIcon, color : '#ffffff' }, @@ -266,43 +280,43 @@ export const chainStyles: Record = { icon : acalaIcon, color : '#ffffff' }, - '9eb76c5184c4ab8679d2d5d819fdf90b' : { + [ChainIdByGenesisHash.ASTAR_NETWORK_ID] : { icon : astarNetworkIcon, color : '#ffffff' }, - 'd4c0c08ca49dc7c680c3dac71a7c0703' : { + [ChainIdByGenesisHash.CRUST_MAINET_ID] : { icon: crutsIcon, color: '#000000' }, - 'afdc188f45c71dacbaa0b62e16a91f72' : { + [ChainIdByGenesisHash.HYDRADX_ID] : { icon : hydraxIcon, color : '#f453a1' }, - 'b0a8d493285c2df73290dfb7e61f870f' : { + [ChainIdByGenesisHash.KUSAMA_ID] : { icon : kusamaIcon, color: '#000000' }, - '1bb969d85965e4bb5a651abbedf21a54' : { + [ChainIdByGenesisHash.PHALA_ID]: { icon : phalaNetworkIcon, color: '#000000' }, - '68d56f15f85d3136970ec16946040bc1' : { + [ChainIdByGenesisHash.STATEMINT_ID] : { icon : statemintIcon, color : '#E6007A' }, - '0f62b701fb12d02237a33b84818c11f6' : { + [ChainIdByGenesisHash.TURING_ID] : { icon : turingNetworkIcon, color: '#ffffff' }, - 'e143f23803ac50e8f6f8e62695d1ce9e' : { + [ChainIdByGenesisHash.WESTEND_ID] : { icon : westendIcon, color: '#ffffff' }, - '48239ef607d7928874027a43a6768920': { + [ChainIdByGenesisHash.STATEMINT_ID] : { icon: statemineIcon, color: '#000000' }, - '6408de7737c59c238890533af25896a2': { + [ChainIdByGenesisHash.ROCOCO_ID]: { icon: rococoIcon, color: '#ffffff' } @@ -557,6 +571,9 @@ const TypeWalletSubstratePlatformByLabel : Record{ const provider = wallet.provider as SubstrateProvider; if(wallet.label === 'Ledger') { - wallet.signer = await substrateProvider?.getSignerLedger(address, provider) + wallet.signer = await substrateProvider?.getLedgerSigner(address, provider) } if( wallet.label === 'WalletConnect') { - wallet.signer = await substrateProvider?.getSignerWC(address, provider); + wallet.signer = await substrateProvider?.getWCSigner(address, provider); + } + if(wallet.label === 'Polkadot Vault'){ + wallet.signer = await substrateProvider?.getQrSigner(address, provider, chainId); } await substrateProvider.sendTransaction( address, @@ -96,7 +99,8 @@ function Component ({className, substrateProvider, evmProvider}: Props): React.R autoDismiss: 0 }); try { - wallet.type === 'evm' ? await evmProvider?.signMessage(address) : await substrateProvider?.signMessage(address, wallet.provider as SubstrateProvider, wallet.signer); + wallet.type === 'evm' ? await evmProvider?.signMessage(address) + : await substrateProvider?.signMessage(address, wallet.provider as SubstrateProvider, wallet.signer, wallet.chains[0].id); update({ eventCode: 'dbUpdateSuccess', message: `success message is success`, diff --git a/packages/demo/src/utils/api/substrateApi.ts b/packages/demo/src/utils/api/substrateApi.ts index 9bb9ab975..d0908d783 100644 --- a/packages/demo/src/utils/api/substrateApi.ts +++ b/packages/demo/src/utils/api/substrateApi.ts @@ -1,11 +1,11 @@ -import {ApiPromise, WsProvider} from '@polkadot/api'; -import type {Signer, SignerPayloadJSON, SignerResult} from '@polkadot/types/types'; -import {SubstrateProvider} from "@subwallet_connect/common"; +import { ApiPromise, WsProvider } from '@polkadot/api'; +import type { Signer, SignerPayloadJSON, SignerResult } from '@polkadot/types/types'; +import { SubstrateProvider } from "@subwallet_connect/common"; import web3Onboard from "../../web3-onboard"; -import {RequestArguments} from "../../types"; -import {SIGN_METHODS} from "../methods"; -import {LedgerSignature} from "@polkadot/hw-ledger/types"; - +import { RequestArguments } from "../../types"; +import { SIGN_METHODS } from "../methods"; +import { LedgerSignature } from "@polkadot/hw-ledger/types"; +import { blake2AsU8a } from '@polkadot/util-crypto'; export class substrateApi { private readonly api ?: ApiPromise; @@ -53,7 +53,7 @@ export class substrateApi { } - public async getSignerWC (senderAddress: string, provider: SubstrateProvider) : Promise { + public async getWCSigner (senderAddress: string, provider: SubstrateProvider) : Promise { if(!this.api) return {} ; return { @@ -72,7 +72,7 @@ export class substrateApi { } } - public async getSignerLedger ( senderAddress: string, provider: SubstrateProvider) : Promise { + public async getLedgerSigner ( senderAddress: string, provider: SubstrateProvider) : Promise { if(!this.api) return {} ; return { @@ -93,7 +93,32 @@ export class substrateApi { } - public async signMessage ( address: string, provider: SubstrateProvider, signer ?: Signer ) { + public async getQrSigner ( senderAddress: string, provider: SubstrateProvider, chainId: string) : Promise { + if(!this.api) return {} ; + + return { + signPayload : async (payload: SignerPayloadJSON): Promise => { + console.log(payload) + const raw = this.api?.registry.createType('ExtrinsicPayload', payload, { version: payload.version }); + const args = {} as RequestArguments; + args.method = 'polkadot_sendTransaction'; + const isQrHashed = (payload.method.length > 5000); + const qrPayload = isQrHashed + ? blake2AsU8a(raw?.toU8a(true) || '') + : raw?.toU8a(); + args.params = { + transactionPayload: qrPayload, + genesisHash: chainId, + address: senderAddress + }; + const { signature } = (await provider.request(args)) as any + return { id: 0, signature }; + } + } + + } + + public async signMessage ( address: string, provider: SubstrateProvider, signer ?: Signer, genesisHash ?: string ) { if(signer && signer.signRaw) { const signPromise = signer.signRaw({ address, data: 'This is dummy message', type: 'bytes' }); return await signPromise @@ -101,7 +126,7 @@ export class substrateApi { const args = {} as RequestArguments; args.method = SIGN_METHODS.substrateSign.method; - args.params = [address, SIGN_METHODS.substrateSign.getInput('This is sign message')]; + args.params = [address, SIGN_METHODS.substrateSign.getInput('This is sign message'), genesisHash ]; await provider.request(args); diff --git a/packages/demo/src/utils/network.ts b/packages/demo/src/utils/network.ts index 4b35de4d4..30a0402f6 100644 --- a/packages/demo/src/utils/network.ts +++ b/packages/demo/src/utils/network.ts @@ -24,7 +24,7 @@ export const NetworkInfo : Record = { slug: 'westend', name: 'Westend', namespace: 'substrate', - wsProvider: "wss://rpc.dotters.network/westend" + wsProvider: "wss://westend-rpc.polkadot.io" }, 'Ethereum Mainnet': { slug: 'ethereum', diff --git a/packages/demo/src/web3-onboard.ts b/packages/demo/src/web3-onboard.ts index ed139229c..6f8fe2e9c 100644 --- a/packages/demo/src/web3-onboard.ts +++ b/packages/demo/src/web3-onboard.ts @@ -8,7 +8,7 @@ import subwalletModule from '@subwallet_connect/subwallet'; import talismanModule from '@subwallet_connect/talisman'; import polkadot_jsModule from '@subwallet_connect/polkadot_js'; import subwalletPolkadotModule from '@subwallet_connect/subwallet-polkadot'; -// import polkadotVaultModule from '@subwallet_connect/polkadot-vault'; +import polkadotVaultModule from '@subwallet_connect/polkadot-vault'; import {TransactionHandlerReturn} from "@subwallet_connect/core/dist/types"; import { SubWallet } from "../assets"; @@ -49,7 +49,7 @@ const subwalletWallet = subwalletModule(); const polkadotWallet = polkadot_jsModule(); const subwalletPolkadotWalet = subwalletPolkadotModule(); const talismanWallet = talismanModule(); -// const polkadotVaultWallet = polkadotVaultModule(); +const polkadotVaultWallet = polkadotVaultModule(); export default init({ @@ -77,7 +77,7 @@ export default init({ ledgerPolkadot_, talismanWallet, polkadotWallet, - // polkadotVaultWallet, + polkadotVaultWallet, injected ], // An array of Chains that your app supports @@ -136,7 +136,7 @@ export default init({ chainsPolkadot:[ { // hex encoded string, eg '0x1' for Ethereum Mainnet - id: '91b171bb158e2d3848fa23a9f1c25182', + id: '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', // string indicating chain namespace. Defaults to 'evm' but will allow other chain namespaces in the future namespace: 'substrate', // the native token symbol, eg ETH, BNB, MATIC @@ -148,7 +148,7 @@ export default init({ decimal: 10 }, { - id: 'e143f23803ac50e8f6f8e62695d1ce9e' , + id: '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e' , token: 'WND', decimal : 12, label: 'Westend', @@ -157,7 +157,7 @@ export default init({ }, { - id: '68d56f15f85d3136970ec16946040bc1', + id: '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f', label: 'Polkadot Asset Hub', namespace: 'substrate', decimal: 10, @@ -166,7 +166,7 @@ export default init({ }, { - id: 'b0a8d493285c2df73290dfb7e61f870f', + id: '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', label: 'Kusama', decimal: 12, namespace: 'substrate', @@ -175,7 +175,7 @@ export default init({ }, { - id: '48239ef607d7928874027a43a6768920', + id: '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a', label: 'Kusama Asset Hub', decimal: 12, namespace: 'substrate', diff --git a/packages/hw-common/src/account-select.ts b/packages/hw-common/src/account-select.ts index f28fb52a3..3a3b030fd 100644 --- a/packages/hw-common/src/account-select.ts +++ b/packages/hw-common/src/account-select.ts @@ -76,7 +76,7 @@ const mountAccountSelect = ( --success-700: #129b4d; /* FONTS */ - --font-family-normal: var(--w3o-font-family, Inter, sans-serif); + --font-family-normal: var(--w3o-font-family, 'Plus Jakarta Sans', Inter, sans-serif); --font-size-5: 1rem; --font-size-6: .875rem; --font-size-7: .75rem; diff --git a/packages/hw-common/src/utils.ts b/packages/hw-common/src/utils.ts index e496cecdd..41830fdec 100644 --- a/packages/hw-common/src/utils.ts +++ b/packages/hw-common/src/utils.ts @@ -1,50 +1,64 @@ import type { AppParams, Asset } from './types.js'; +export enum ChainIdByGenesisHash { + POLKADOT_ID = '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3', + KUSAMA_ID = '0xb0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe', + WESTEND_ID = '0xe143f23803ac50e8f6f8e62695d1ce9e4e1d68aa36c1cd2cfd15340213f3423e', + ROCOCO_ID = '0x6408de7737c59c238890533af25896a2c20608d8b380bb01029acb392781063e', + ASTAR_NETWORK_ID = '0x9eb76c5184c4ab8679d2d5d819fdf90b9c001403e9e17da2e14b6d8aec4029c6', + CRUST_MAINET_ID = '0x8b404e7ed8789d813982b9cb4c8b664c05b3fbf433309f603af014ec9ce56a8c', + HYDRADX_ID = '0xafdc188f45c71dacbaa0b62e16a91f726c7b8699a9748cdf715459de6b7f366d', + PHALA_ID = '0x1bb969d85965e4bb5a651abbedf21a54b6b31a21f66b5401cc3f1e286268d736', + STATEMINT_ID = '0x68d56f15f85d3136970ec16946040bc1752654e906147f7e43e9d539d7c3de2f', + STAEMINE_ID = '0x48239ef607d7928874027a43a67689209727dfb3d3dc5e5b03a39bdc2eda771a', + TURING_ID = '0xd54f0988402deb4548538626ce37e4a318441ea0529ca369400ebec4e04dfe4b' +} + export const supportedApps: Record = { - ['91b171bb158e2d3848fa23a9f1c25182']: { + [ChainIdByGenesisHash.POLKADOT_ID]: { name: 'polkadot', path: `m/44'/354'/0'/0`, asset: { label: 'DOT' } }, - ['68d56f15f85d3136970ec16946040bc1']: { + [ChainIdByGenesisHash.STATEMINT_ID]: { name: 'statemint', path: `m/44'/354'/0'/0`, asset: { label: 'DOT' } }, - ['48239ef607d7928874027a43a6768920']: { + [ChainIdByGenesisHash.STAEMINE_ID]: { name: 'statemine', path: `m/44'/434'/0'/0`, asset: { label: 'KSM' } }, - ['b0a8d493285c2df73290dfb7e61f870f']: { + [ChainIdByGenesisHash.KUSAMA_ID]: { name: 'kusama', asset: { label: 'KSM' }, path: `m/44'/434'/0'/0`, }, - ['afdc188f45c71dacbaa0b62e16a91f72']: { + [ChainIdByGenesisHash.HYDRADX_ID]: { name: 'hydraDX', asset: { label: 'DOT' }, path: `m/44'/354'/0'/0`, }, - ['1bb969d85965e4bb5a651abbedf21a54']: { + [ChainIdByGenesisHash.PHALA_ID]: { name: 'phala', asset: { label: 'DOT' }, path: `m/44'/354'/0'/0`, }, - ['9eb76c5184c4ab8679d2d5d819fdf90b'] :{ + [ChainIdByGenesisHash.ASTAR_NETWORK_ID] :{ name: 'astar', asset: { label: 'ASTR' diff --git a/packages/polkadotVault/.eslintrc.cjs b/packages/polkadotVault/.eslintrc.cjs new file mode 100644 index 000000000..873abf5ed --- /dev/null +++ b/packages/polkadotVault/.eslintrc.cjs @@ -0,0 +1,44 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier' + ], + plugins: ['svelte3', '@typescript-eslint'], + ignorePatterns: ['*.cjs'], + overrides: [{ files: ['*.svelte'], processor: 'svelte3/svelte3' }], + settings: { + 'svelte3/typescript': () => require('typescript') + }, + parserOptions: { + sourceType: 'module', + ecmaVersion: 2019 + }, + env: { + browser: true, + node: true, + es2017: true + }, + rules: { + '@typescript-eslint/quotes': [ + 'error', + 'single', + { allowTemplateLiterals: true } + ], + '@typescript-eslint/no-case-declarations': 'off', + 'max-len': [ + 'error', + { + code: 80, + tabWidth: 2, + ignoreStrings: true, + ignoreTemplateLiterals: true + } + ], + 'object-curly-spacing': ['error', 'always'], + '@typescript-eslint/no-empty-function': 'off', + 'eslint-disable-next-line @typescript-eslint/ban-ts-comment': 'off' + } +} diff --git a/packages/polkadotVault/.prettierrc.cjs b/packages/polkadotVault/.prettierrc.cjs new file mode 100644 index 000000000..c62c290ec --- /dev/null +++ b/packages/polkadotVault/.prettierrc.cjs @@ -0,0 +1,9 @@ +module.exports = { + semi: false, + trailingComma: 'none', + singleQuote: true, + printWidth: 80, + tabWidth: 2, + arrowParens: 'avoid', + svelteSortOrder: 'options-scripts-styles-markup' +} diff --git a/packages/polkadotVault/package.json b/packages/polkadotVault/package.json new file mode 100644 index 000000000..7747efc10 --- /dev/null +++ b/packages/polkadotVault/package.json @@ -0,0 +1,94 @@ +{ + "name": "@subwallet_connect/polkadot-vault", + "version": "1.0.0", + "description": "Web3-Onboard makes it simple to connect Ethereum hardware and software wallets to your dapp. Features standardised spec compliant web3 providers for all supported wallets, framework agnostic modern javascript UI with code splitting, CSS customization, multi-chain and multi-account support, reactive wallet state subscriptions and real-time transaction state change notifications.", + "keywords": [ + "Ethereum", + "Web3", + "EVM", + "dapp", + "Multichain", + "Wallet", + "Transaction", + "Provider", + "Hardware Wallet", + "Notifications", + "React", + "Svelte", + "Vue", + "Next", + "Nuxt", + "MetaMask", + "Coinbase", + "WalletConnect", + "Ledger", + "Trezor", + "Connect Wallet", + "Ethereum Hooks", + "Blocknative", + "Mempool", + "pending", + "confirmed", + "Injected Wallet", + "Crypto", + "Crypto Wallet" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/blocknative/web3-onboard.git", + "directory": "packages/hw-common" + }, + "homepage": "https://onboard.blocknative.com", + "bugs": { + "url": "https://github.com/blocknative/web3-onboard/issues" + }, + "scripts": { + "build": "rollup -c", + "dev": "rollup -c -w", + "start": "sirv public --no-clear", + "type-check": "svelte-check --tsconfig ./tsconfig.json", + "lint": "eslint -c './.eslintrc.cjs' './src' && prettier --check './src/**/*'" + }, + "module": "dist/index.js", + "browser": "dist/index.js", + "main": "dist/index.js", + "type": "module", + "typings": "dist/index.d.ts", + "files": [ + "dist" + ], + "license": "MIT", + "devDependencies": { + "@rollup/plugin-json": "^4.1.0", + "@rollup/plugin-node-resolve": "^11.0.0", + "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-typescript": "^8.0.0", + "@tsconfig/svelte": "^2.0.0", + "@typescript-eslint/eslint-plugin": "^4.31.1", + "@typescript-eslint/parser": "^4.31.1", + "eslint": "^7.32.0", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-svelte3": "^3.2.1", + "eventemitter3": "^4.0.7", + "prettier": "^2.4.0", + "prettier-plugin-svelte": "^2.4.0", + "rollup": "^2.3.4", + "rollup-plugin-svelte": "^7.0.0", + "svelte": "^3.49.0", + "svelte-check": "^2.2.6", + "svelte-preprocess": "^4.9.4", + "tslib": "^2.0.0", + "typescript": "^4.5.5" + }, + "dependencies": { + "@subwallet_connect/common": "^1.0.2", + "@polkadot/util": "^12.6.1", + "@polkadot/util-crypto": "^12.6.1", + "@polkadot/types": "^10.11.1", + "joi": "17.9.1", + "rxjs": "^7.5.2", + "jsqr": "^1.4.0", + "svelte-qrcode": "1.0.0" + }, + "author": "" +} diff --git a/packages/polkadotVault/rollup.config.js b/packages/polkadotVault/rollup.config.js new file mode 100644 index 000000000..dab143733 --- /dev/null +++ b/packages/polkadotVault/rollup.config.js @@ -0,0 +1,49 @@ +import svelte from 'rollup-plugin-svelte' +import resolve from '@rollup/plugin-node-resolve' +import replace from '@rollup/plugin-replace' +import json from '@rollup/plugin-json' +import sveltePreprocess from 'svelte-preprocess' +import typescript from '@rollup/plugin-typescript' + +const production = !process.env.ROLLUP_WATCH + +export default { + input: 'src/index.ts', + output: { + format: 'esm', + dir: 'dist/' + }, + plugins: [ + json(), + replace({ + 'process.env.NODE_ENV': JSON.stringify(production), + preventAssignment: true + }), + svelte({ + preprocess: sveltePreprocess({ sourceMap: !production }), + compilerOptions: { + dev: !production + }, + emitCss: false + }), + resolve({ + browser: true, + dedupe: ['svelte', 'svelte-qrcode-image'] + }), + typescript({ + sourceMap: !production, + inlineSources: !production + }) + ], + external: [ + 'joi', + 'rxjs', + '@web3-onboard/common', + 'eventemitter3', + 'jsqr', + '@polkadot/util', + '@polkadot/util-crypto', + '@polkadot/types', + 'svelte-qrcode' + ] +} diff --git a/packages/polkadotVault/src/constants.ts b/packages/polkadotVault/src/constants.ts new file mode 100644 index 000000000..183ea2c71 --- /dev/null +++ b/packages/polkadotVault/src/constants.ts @@ -0,0 +1,29 @@ +export const MOBILE_WINDOW_WIDTH = 768 + + +export const ADDRESS_PREFIX = 'substrate'; +export const SECRET_PREFIX = 'secret'; + +export enum SCAN_TYPE { + READONLY = 'READONLY', + SECRET = 'SECRET', + QR_SIGNER = 'QR_SIGNER' +} + +export const FRAME_SIZE = 1024; +export const CMD_SIGN_MORTAL = 0; +export const CMD_SIGN_HASH = 1; +export const CMD_SIGN_IMMORTAL = 2; +export const CMD_SIGN_MSG = 3; +export const MULTIPART = new Uint8Array([0]); +export const STANDARD_FRAME_SIZE = 2 ** 8; +export const SUBSTRATE_ID = new Uint8Array([0x53]); +export const CRYPTO_SR25519 = new Uint8Array([0x01]); +export const CMD = { + SUBSTRATE: { + SIGN_MORTAL: 0, + SIGN_HASH: 1, + SIGN_IMMORTAL: 2, + SIGN_MSG: 3 + } +}; diff --git a/packages/polkadotVault/src/icon/close.ts b/packages/polkadotVault/src/icon/close.ts new file mode 100644 index 000000000..cf08b6d60 --- /dev/null +++ b/packages/polkadotVault/src/icon/close.ts @@ -0,0 +1,7 @@ +export default ` + + + + + +` diff --git a/packages/polkadotVault/src/icon/imageSquare.ts b/packages/polkadotVault/src/icon/imageSquare.ts new file mode 100644 index 000000000..41a857dc9 --- /dev/null +++ b/packages/polkadotVault/src/icon/imageSquare.ts @@ -0,0 +1,10 @@ +export default ` + + + + + + + + +` diff --git a/packages/polkadotVault/src/icon/index.ts b/packages/polkadotVault/src/icon/index.ts new file mode 100644 index 000000000..11cd63f70 --- /dev/null +++ b/packages/polkadotVault/src/icon/index.ts @@ -0,0 +1,4 @@ + +export { default as closeIcon } from './close.js'; +export { default as logoWallet } from './logoWallet.js'; +export { default as imageSquareIcon } from './imageSquare.js'; diff --git a/packages/polkadotVault/src/icon/logoWallet.ts b/packages/polkadotVault/src/icon/logoWallet.ts new file mode 100644 index 000000000..68d1e8ae0 --- /dev/null +++ b/packages/polkadotVault/src/icon/logoWallet.ts @@ -0,0 +1,7 @@ + +export default ` + + + + +` diff --git a/packages/polkadotVault/src/index.ts b/packages/polkadotVault/src/index.ts new file mode 100644 index 000000000..58e9d1354 --- /dev/null +++ b/packages/polkadotVault/src/index.ts @@ -0,0 +1,151 @@ +import type { SubstrateProvider, WalletInit, WalletInterfaceSubstrate } from '@subwallet_connect/common' +import EventEmitter from 'eventemitter3'; +import type { Signer } from '@polkadot/types/types'; +import type { PayloadParams, RequestArguments } from './types.js'; +import modalConnect from './views/index.js'; +import { generateAccount } from './utils.js'; +import { u8aWrapBytes } from '@polkadot/util'; + + +function PolkadotVault (): WalletInit { + if (typeof window === 'undefined') return () => null + + return () => { + + return { + label: 'Polkadot Vault', + type: 'substrate', + getIcon: async () => (await import('./icon/logoWallet.js')).default, + platforms: ['desktop'], + getInterface: async ({ chains }): Promise => { + + const emitter = new EventEmitter() + const { ProviderRpcError, ProviderRpcErrorCode } = await import( + '@subwallet_connect/common' + ) + + + class PolkadotVaultProvider implements SubstrateProvider{ + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public emit: typeof EventEmitter['emit'] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public on: typeof EventEmitter['on'] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + public removeListener: typeof EventEmitter['removeListener'] + + constructor() { + this.emit = emitter.emit.bind(emitter) + this.on = emitter.on.bind(emitter) + this.removeListener = emitter.removeListener.bind(emitter) + } + + async enable() { + + try { + const account = await this.request({ method: 'polkadot_requestAccounts' }) + + return { + address: [account as string] + } + } catch (e) { + console.log('error', (e as Error).message); + } + } + async signDummy(address: string, data: string, + signer: Signer) { + if (signer && signer.signRaw) { + return (await signer.signRaw({ + address: address, + data: 'This is dummy message', + type: 'bytes' + })).signature as string; + } + return '0x0' + } + + async request ({ method, params } : RequestArguments) { + + if(method === 'polkadot_requestAccounts') { + const account = await modalConnect('getAccount'); + console.log(account) + const { + address, + genesisHash, + isSubstrate + } = generateAccount(account); + + if(!isSubstrate){ + throw new ProviderRpcError({ + code: 4001, + message: 'Type wallet is invalid' + }) + } + + const uniqueChainNetwork = chains.find(({ id, namespace }) => namespace === 'substrate' && genesisHash.includes(id)); + if(uniqueChainNetwork){ + this.emit('chainChanged', uniqueChainNetwork.id) + } + return address; + } + if(method === 'polkadot_signMessage') { + if(!( params && Array.isArray(params) && params.length >= 3)){ + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.INVALID_PARAMS, + message: 'Need params to request this method' + }) + } + try { + + const result = await modalConnect('signTransaction', { + isMessage: true, + genesisHash: params[2], + address: params[0], + transactionPayload: u8aWrapBytes(params[1]) + } as PayloadParams); + return { signature: result }; + }catch (e) { + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.DISCONNECTED, + message: (e as Error).message + }) + } + } + + if(method === 'polkadot_sendTransaction'){ + if(! params){ + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.INVALID_PARAMS, + message: 'Need params to request this method' + }) + } + try { + const result = await modalConnect('signTransaction', { isMessage: false, ...params } as PayloadParams); + return { signature: result }; + }catch (e) { + throw new ProviderRpcError({ + code: ProviderRpcErrorCode.DISCONNECTED, + message: (e as Error).message + }) + } + + } + } + + async disconnect() {} + + + } + + return { + provider: new PolkadotVaultProvider() + } + }, + } + } +} + +export default PolkadotVault diff --git a/packages/polkadotVault/src/streams.ts b/packages/polkadotVault/src/streams.ts new file mode 100644 index 000000000..e8f23edea --- /dev/null +++ b/packages/polkadotVault/src/streams.ts @@ -0,0 +1,8 @@ +import { BehaviorSubject, Subject } from 'rxjs'; +import type { PayloadParams, QRResult } from './types.js'; + + +export const payloadUri$ = + new BehaviorSubject({} as PayloadParams); + +export const resultQrScan$ = new Subject(); diff --git a/packages/polkadotVault/src/types.ts b/packages/polkadotVault/src/types.ts new file mode 100644 index 000000000..a02186f51 --- /dev/null +++ b/packages/polkadotVault/src/types.ts @@ -0,0 +1,32 @@ + +export interface RequestArguments { + method: string; + // eslint-disable-next-line @typescript-eslint/ban-types + params?: unknown[] | Record | object | undefined; +} + + +export type ModalStep = 'showQrCode' | 'scanQrCode' | 'successStep'; + +export interface Account { + isSubstrate: boolean + address: string, + genesisHash: `0x${string}` +} + +export type TypeAction = 'signTransaction' | 'getAccount' + + +export interface SignatureResult { + signature : `0x${string}` +} + +export type QRResult = `0x${string}`; + + +export interface PayloadParams { + address: string; + genesisHash: string; + transactionPayload : Uint8Array; + isMessage: boolean; +} diff --git a/packages/polkadotVault/src/utils.ts b/packages/polkadotVault/src/utils.ts new file mode 100644 index 000000000..a046e6342 --- /dev/null +++ b/packages/polkadotVault/src/utils.ts @@ -0,0 +1,118 @@ +import type { Account, QRResult } from './types.js'; +import { isString, u8aConcat, u8aToU8a } from '@polkadot/util'; +import { decodeAddress } from '@polkadot/util-crypto'; +import { ADDRESS_PREFIX, CRYPTO_SR25519, FRAME_SIZE, SUBSTRATE_ID } from './constants.js'; + + +export function isSVG(str: string): boolean { + return str.includes('> 8, value & 0xff]); +} + +export function encodeString (value: string): Uint8Array { + const count = value.length; + const u8a = new Uint8Array(count); + + for (let i = 0; i < count; i++) { + u8a[i] = value.charCodeAt(i); + } + + return u8a; +} + +export function decodeString (value: Uint8Array): string { + return value.reduce((str, code): string => { + return str + String.fromCharCode(code); + }, ''); +} + +export function createAddressPayload ( + address: string, genesisHash: string +): Uint8Array { + return encodeString(`${ADDRESS_PREFIX}:${address}:${genesisHash}`); +} + +export function createSignPayload ( + address: string, + cmd: number, + payload: string | Uint8Array, + genesisHash: string | Uint8Array +): Uint8Array { + return u8aConcat( + SUBSTRATE_ID, + CRYPTO_SR25519, + new Uint8Array([cmd]), + decodeAddress(address), + u8aToU8a(payload), + u8aToU8a(genesisHash) + ); +} + +export function createFrames (input: Uint8Array): Uint8Array[] { + const frames = []; + let idx = 0; + + while (idx < input.length) { + frames.push(input.subarray(idx, idx + FRAME_SIZE)); + + idx += FRAME_SIZE; + } + + return frames.map((frame, index: number): Uint8Array => + u8aConcat( + MULTIPART, + encodeNumber(frames.length), + encodeNumber(index), + frame + ) + ); +} + +export function createImgSize (size?: string | number): Record { + if (!size) { + return { + height: 'auto', + width: '100%' + }; + } + + const height = isString(size) + ? size + : `${size}px`; + + return { + height, + width: height + }; +} diff --git a/packages/polkadotVault/src/views/CloseButton.svelte b/packages/polkadotVault/src/views/CloseButton.svelte new file mode 100644 index 000000000..51ce1feba --- /dev/null +++ b/packages/polkadotVault/src/views/CloseButton.svelte @@ -0,0 +1,49 @@ + + + + +
+
+ {@html closeIcon} +
+
diff --git a/packages/polkadotVault/src/views/Modal.svelte b/packages/polkadotVault/src/views/Modal.svelte new file mode 100644 index 000000000..99680b058 --- /dev/null +++ b/packages/polkadotVault/src/views/Modal.svelte @@ -0,0 +1,218 @@ + + + + + + + + + +
+
+ +
+
diff --git a/packages/polkadotVault/src/views/ModalConnect.svelte b/packages/polkadotVault/src/views/ModalConnect.svelte new file mode 100644 index 000000000..a23d2e432 --- /dev/null +++ b/packages/polkadotVault/src/views/ModalConnect.svelte @@ -0,0 +1,218 @@ + + + + + + + + + diff --git a/packages/polkadotVault/src/views/QrCodeModal.svelte b/packages/polkadotVault/src/views/QrCodeModal.svelte new file mode 100644 index 000000000..1443e6628 --- /dev/null +++ b/packages/polkadotVault/src/views/QrCodeModal.svelte @@ -0,0 +1,164 @@ + + + + {#if (uri && valueHash)} + +
+ Confirm +
+
+
+ +
+
+ +
+ + + +
+
+ + {/if} diff --git a/packages/polkadotVault/src/views/ScanQRCode.svelte b/packages/polkadotVault/src/views/ScanQRCode.svelte new file mode 100644 index 000000000..9b51d683f --- /dev/null +++ b/packages/polkadotVault/src/views/ScanQRCode.svelte @@ -0,0 +1,189 @@ + + + + +