From 59cdbd2c0cc6c9ebda9e11f5df117680c9c6a0c1 Mon Sep 17 00:00:00 2001 From: cebgcontract <99630598+cebgcontract@users.noreply.github.com> Date: Tue, 23 Aug 2022 19:45:04 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=BA=9Bwalletconne?= =?UTF-8?q?ct=E7=9A=84=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/comp/ZWalletConnect.ts | 62 +++-- src/index.ts | 415 +++++++++++++++++-------------- src/lib/WalletConnectProvider.ts | 5 +- src/util/sign.util.ts | 9 +- 4 files changed, 284 insertions(+), 207 deletions(-) diff --git a/src/comp/ZWalletConnect.ts b/src/comp/ZWalletConnect.ts index c5ec865..b32899e 100644 --- a/src/comp/ZWalletConnect.ts +++ b/src/comp/ZWalletConnect.ts @@ -5,17 +5,35 @@ import { toHexChainId } from '../util/chain.util' export class ZWalletConnect { provider: WalletConnectProvider - constructor(rpc: String) { + accounts: string[] = [] + + constructor(rpc: any) { + console.log('ZWalletConnect constructor'); this.provider = new WalletConnectProvider({ - rpc + rpc, + qrcodeModal: { + open(uri: string, cb: any, opts?: any): void { + console.log('walletconnect open qrcode modal') + //@ts-ignore + jumpToWallet(uri); + }, + close(): void { + + } + } }) this._subscribeToEvents(this.provider) } + public async connect() { + console.log('wallet connect begin connect') + return this.provider.enable() + } + private _subscribeToEvents(provider: WalletConnectProvider) { this.provider.on('accountsChanged', async(accounts: string[]) => { console.log('accountsChanged: ', accounts) - + this.accounts = accounts; }) // Subscribe to chainId change @@ -32,16 +50,15 @@ export class ZWalletConnect { /** * @param data */ - public async addOrChangeChain(data: IChainData, cb?: () => void) { - const onChainChange = (chainId: string) => { - console.log('switchEthereumChain: ', chainId) - this.provider.removeListener('chainChanged', onChainChange) - cb && cb() - } - this.provider.on('chainChanged', onChainChange) - - try { - await this.provider.request({ + public async addOrChangeChain(data: IChainData) { + return new Promise((resolve, reject) => { + const onChainChange = (chainId: string) => { + console.log('switchEthereumChain: ', chainId) + this.provider.removeListener('chainChanged', onChainChange) + resolve && resolve(chainId); + } + this.provider.on('chainChanged', onChainChange) + this.provider.request({ method: 'wallet_addEthereumChain', params: [ { @@ -57,17 +74,24 @@ export class ZWalletConnect { } ] }) - console.log('add chain success') - } catch (addError) { - console.error('add chain error: ', addError) - this.provider.removeListener('chainChanged', onChainChange) - } + .then(() => { + console.log('add chain success, wait result'); + }) + .catch(err=>{ + console.error('add chain error: ', err); + this.provider.removeListener('chainChanged', onChainChange); + reject && reject(err); + }) + }) } public async signData(signObj: any, signer: string) { const msgParams = JSON.stringify(signObj) - const from = signer + const from = signer || this.accounts[0] + if (!from) { + throw new Error('no account'); + } console.log('clicked, sending personal sign req', 'from', from, msgParams) const params = [from, msgParams] const result: any = await this.sendCmd({ diff --git a/src/index.ts b/src/index.ts index 3044337..a90876d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,169 +1,198 @@ -import { singleton } from "./decorator/singleton.decorator"; -import Web3 from "web3"; +import { singleton } from './decorator/singleton.decorator' +import Web3 from 'web3' import { recoverTypedSignature, signTypedData, SignTypedDataVersion, -} from "@metamask/eth-sig-util"; -import "whatwg-fetch"; -import { AllChains } from "./data/allchain"; +} from '@metamask/eth-sig-util' +import 'whatwg-fetch' +import { AllChains } from './data/allchain' import { createWalletEvents, WALLET_ACCOUNT_CHANGE, WALLET_CHAIN_CHANGE, WALLET_TOKEN_TYPE_CHANGE, -} from "./common/WalletEvent"; -import { ERC20Standard } from "./standards/ERC20Standard"; -import { ERC721Standard } from "./standards/ERC721Standard"; -import { IAccount, initAccount } from "./data/DataModel"; +} from './common/WalletEvent' +import { ERC20Standard } from './standards/ERC20Standard' +import { ERC721Standard } from './standards/ERC721Standard' +import { IAccount, initAccount } from './data/DataModel' import { checkPassword, loadData, saveData, savePassword, -} from "./manage/DataManage"; -import { WALLET_STORAGE_KEY_NAME } from "./config/constants"; -import { DEFALUT_TOKENS } from "./config/chain_config"; +} from './manage/DataManage' +import { WALLET_STORAGE_KEY_NAME } from './config/constants' +import { DEFALUT_TOKENS } from './config/chain_config' import { newAccount, newMnemonic, restoreWalletByMnemonic, -} from "./manage/WalletManage"; -import { signLogin } from "./util/sign.util"; -import { JazzIcon } from "./comp/JazzIcon"; -import { ERC1155Standard } from "./standards/ERC1155Standard"; -import { ZWalletConnect } from "./comp/ZWalletConnect"; - - +} from './manage/WalletManage' +import { buildLoginSignMsg, signLogin } from './util/sign.util' +import { JazzIcon } from './comp/JazzIcon' +import { ERC1155Standard } from './standards/ERC1155Standard' +import { ZWalletConnect } from './comp/ZWalletConnect' +import { rejects } from 'assert' var global = - (typeof globalThis !== "undefined" && globalThis) || - (typeof self !== "undefined" && self) || - (typeof global !== "undefined" && global) || - {}; + (typeof globalThis !== 'undefined' && globalThis) || + (typeof self !== 'undefined' && self) || + (typeof global !== 'undefined' && global) || + {} declare global { interface Window { jc: { - wallet: JCWallet; - }; - jcwallet: JCWallet; - ethSigUtil: any; - cc: any; - Stream: any; + wallet: JCWallet + } + jcwallet: JCWallet + ethSigUtil: any + cc: any + Stream: any } } - export interface IChainData { - name: string; - type: string; - rpc: string; - id: number; - symbol: string; - explorerurl: string; + name: string + type: string + rpc: string + id: number + symbol: string + explorerurl: string decimals?: number } + +export enum WalletType { + INTERNAL = 0, + THIRD_PATH = 1, +} @singleton export default class JCWallet { - web3: Web3 = null; - private wallet: any = null; - private password: string = "111111"; - private chainSet: Set = new Set(); - private chainMap: Map = new Map(); - private _currentChain: IChainData; - public erc20Standard: ERC20Standard; - public erc721Standard: ERC721Standard; - public erc1155Standard: ERC1155Standard; - public wConnect: ZWalletConnect; - public mainHandlers = createWalletEvents(); - public data: IAccount[] = []; - public iconType = "jazz"; - private accountIndex = 0; - private walletType: number = 0; + web3: Web3 = null + private wallet: any = null + private password: string = '111111' + private chainSet: Set = new Set() + private chainMap: Map = new Map() + private _currentChain: IChainData + public erc20Standard: ERC20Standard + public erc721Standard: ERC721Standard + public erc1155Standard: ERC1155Standard + public wConnect: ZWalletConnect + public mainHandlers = createWalletEvents() + public data: IAccount[] = [] + public iconType = 'jazz' + private accountIndex = 0 + private walletType: WalletType = WalletType.INTERNAL + private rpcUrl: string = '' + public rpc: any = {} - constructor(type: number, password: string) { - // this.web3 = new Web3('https://rpc-mainnet.kcc.network') - if (!password) { - throw new Error("need password"); + + constructor({ type, chain, password }: { type: number; chain: number, password: string }) { + this.walletType = type + chain = chain || 322 + let data = AllChains.find((o) => o.id === chain) + if (!data) { + throw new Error('no current chain data') } - if (!checkPassword(password)) { - throw new Error("password error"); + this._currentChain = data; + this.rpcUrl = data.rpc; + console.log(`rpc url: ${this.rpcUrl}`); + if (this.walletType === WalletType.INTERNAL) { + if (!password) { + throw new Error('need password') + } + if (!checkPassword(password)) { + throw new Error('password error') + } + this.password = password + savePassword(password) + this.initInternalWallet(); + } else { + this.initThirdPartyWallet(chain); } - this.walletType = type; - this.password = password; - savePassword(password); - var start = Date.now(); - const rpc = "https://rpc-testnet.kcc.network" - this.web3 = new Web3(rpc); - console.log(`init web3 cost: ${(Date.now() - start) / 1000}`); - this.erc20Standard = new ERC20Standard(this.web3); - this.erc721Standard = new ERC721Standard(this.web3); - this.erc1155Standard = new ERC1155Standard(this.web3); - this.wConnect = new ZWalletConnect(rpc); - start = Date.now(); - this.wallet = this.web3.eth.accounts.wallet.load( - this.password, - WALLET_STORAGE_KEY_NAME - ); - console.log(`load wallet cost: ${(Date.now() - start) / 1000}`); - start = Date.now(); - this.data = loadData(); - console.log(`init wallet ext data cost: ${(Date.now() - start) / 1000}`); - window.jc = { wallet: this }; - this.init({ chains: [322, 97], password }); + window.jc = { wallet: this } } - public init({ chains, password }: { chains: number[]; password: string }) { + private initInternalWallet() { + var start = Date.now() + this.web3 = new Web3(this.rpcUrl) + console.log(`init web3 cost: ${(Date.now() - start) / 1000}`) + this.erc20Standard = new ERC20Standard(this.web3) + this.erc721Standard = new ERC721Standard(this.web3) + this.erc1155Standard = new ERC1155Standard(this.web3) + + start = Date.now() + this.wallet = this.web3.eth.accounts.wallet.load( + this.password, + WALLET_STORAGE_KEY_NAME, + ) + console.log(`load wallet cost: ${(Date.now() - start) / 1000}`) + start = Date.now() + this.data = loadData() + console.log(`init wallet ext data cost: ${(Date.now() - start) / 1000}`) + this.init({ chains: [322, 97], password: this.password }) + } + + private initThirdPartyWallet(chain: number) { + for (const d of AllChains) { + const id = d.id + this.rpc[id] = d.rpc + } + + this.wConnect = new ZWalletConnect(this.rpc) + } + + private init({ chains, password }: { chains: number[]; password: string }) { for (let chain of chains) { - this.chainSet.add(chain); + this.chainSet.add(chain) if (!this.chainMap.has(chain)) { - let data = AllChains.find((o) => o.id === chain); + let data = AllChains.find((o) => o.id === chain) if (data) { - this.chainMap.set(chain, data); + this.chainMap.set(chain, data) if (!this._currentChain) { - this._currentChain = data; + this._currentChain = data } } } } if (!this.wallet || this.wallet.length === 0) { // this.createAccount(); - this.newWallet(password); + this.newWallet(password) } } public newWallet(password: string) { - this.password = password; - newMnemonic(this.password); - this.createAccount(); + this.password = password + newMnemonic(this.password) + this.createAccount() } public restoreFromMnemonic(mnemonic: string, password: string) { - this.password = password; - this.wallet.clear(); - restoreWalletByMnemonic(mnemonic, this.password); - return this.createAccount(); + this.password = password + this.wallet.clear() + restoreWalletByMnemonic(mnemonic, this.password) + return this.createAccount() } get currentChain() { - return this._currentChain; + return this._currentChain } updateCurrentChain(chainId: number) { - const chainData = this.chainMap.get(chainId); - this._currentChain = chainData; - this.web3.eth.setProvider(chainData.rpc); - this.mainHandlers.emit(WALLET_CHAIN_CHANGE, chainData); - this.updateListType("tokens"); - return this.currentChain; + const chainData = this.chainMap.get(chainId) + this._currentChain = chainData + this.web3.eth.setProvider(chainData.rpc) + this.mainHandlers.emit(WALLET_CHAIN_CHANGE, chainData) + this.updateListType('tokens') + return this.currentChain } updateListType(type: string) { - this.mainHandlers.emit(WALLET_TOKEN_TYPE_CHANGE, type); + this.mainHandlers.emit(WALLET_TOKEN_TYPE_CHANGE, type) } get chainList() { - return [...this.chainMap.values()]; + return [...this.chainMap.values()] } public saveLocal() {} @@ -175,153 +204,173 @@ export default class JCWallet { public loadRemote() {} public currentAccount() { - return this.wallet[this.accountIndex]; + if (this.walletType === WalletType.INTERNAL) { + return this.wallet[this.accountIndex] + } else { + return this.wConnect.accounts[0] + } } get currentAccountData() { - let address = this.currentAccount().address; - const chain = this.currentChain.id; - let data = this.data.find((o) => o.address === address); + let address = this.currentAccount().address + const chain = this.currentChain.id + let data = this.data.find((o) => o.address === address) if (!data) { - throw new Error("account data not found"); + throw new Error('account data not found') } if (!data.tokenData[chain]) { - let tokens = DEFALUT_TOKENS[chain]; + let tokens = DEFALUT_TOKENS[chain] data.tokenData[chain] = { tokens, heros: [], weapons: [], chips: [], - }; + } } - saveData(this.data); - return data; + saveData(this.data) + return data } get accounts() { - return this.data; + return this.data } public createAccount() { // let account = this.web3.eth.accounts.create() - const index = this.getMaxIdexOfType(0); - const accountNew = newAccount(this.password, index); - const account = this.wallet.add(accountNew); - this.wallet.save(this.password, WALLET_STORAGE_KEY_NAME); - const chain = this.currentChain.id; - let data = this.data.find((o) => o.address === account.address); + const index = this.getMaxIdexOfType(0) + const accountNew = newAccount(this.password, index) + const account = this.wallet.add(accountNew) + this.wallet.save(this.password, WALLET_STORAGE_KEY_NAME) + const chain = this.currentChain.id + let data = this.data.find((o) => o.address === account.address) if (!data) { - const nickname = `Account ${index + 1}`; + const nickname = `Account ${index + 1}` data = initAccount({ address: account.address, chain, nickname, type: 0, index, - }); - this.data.push(data); - saveData(this.data); + }) + this.data.push(data) + saveData(this.data) } - this.accountIndex = this.wallet.length - 1; - this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, account.address); - return account.address; + this.accountIndex = this.wallet.length - 1 + this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, account.address) + return account.address } public importAccount(privateKey: string) { - const account = this.wallet.add(privateKey); - const chain = this.currentChain.id; - let data = this.data.find((o) => o.address === account.address); + const account = this.wallet.add(privateKey) + const chain = this.currentChain.id + let data = this.data.find((o) => o.address === account.address) if (!data) { - const index = this.getMaxIdexOfType(1); - const nickname = `Imported ${index + 1}`; + const index = this.getMaxIdexOfType(1) + const nickname = `Imported ${index + 1}` data = initAccount({ address: account.address, chain, nickname, type: 1, index, - }); - this.data.push(data); - saveData(this.data); + }) + this.data.push(data) + saveData(this.data) } - this.web3.eth.accounts.wallet.save(this.password, WALLET_STORAGE_KEY_NAME); - this.accountIndex = this.wallet.length - 1; - this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, account.address); - return account.address; + this.web3.eth.accounts.wallet.save(this.password, WALLET_STORAGE_KEY_NAME) + this.accountIndex = this.wallet.length - 1 + this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, account.address) + return account.address } private getMaxIdexOfType(type: number) { - let maxIdx = -1; + let maxIdx = -1 for (let i = 0, l = this.data.length; i < l; i++) { if (this.data[i].type !== type) { - continue; + continue } - maxIdx = Math.max(this.data[i].index, maxIdx); + maxIdx = Math.max(this.data[i].index, maxIdx) } - return maxIdx + 1; + return maxIdx + 1 } private getAccountByAddress(address: string) { - let account; - let index = 0; + let account + let index = 0 for (let i = 0, l = this.wallet.length; i < l; i++) { if (this.wallet[i].address === address) { - account = this.wallet[i]; - index = i; - break; + account = this.wallet[i] + index = i + break } } - return {account, index}; + return { account, index } } public selectAccount(address: string) { - const { index } = this.getAccountByAddress(address); + const { index } = this.getAccountByAddress(address) if (index !== this.accountIndex && index < this.wallet.length) { - this.accountIndex = index; - this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, this.wallet[index].address); + this.accountIndex = index + this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, this.wallet[index].address) } - return address; + return address } public async sendEth(to: string, amount: number | string) { - let from = this.currentAccount().address; - const amountToSend = this.web3.utils.toWei(amount + "", "ether"); + let from = this.currentAccount().address + const amountToSend = this.web3.utils.toWei(amount + '', 'ether') let gas = await this.web3.eth.estimateGas({ from, to, value: amountToSend, - }); - this.web3.eth.sendTransaction({ from, to, gas, value: amountToSend }); + }) + this.web3.eth.sendTransaction({ from, to, gas, value: amountToSend }) } public async getBalance(address?: string) { - console.log("get balance with address: ", address); + console.log('get balance with address: ', address) if (!address) { - let accountData = this.wallet[this.accountIndex]; + let accountData = this.wallet[this.accountIndex] if (!accountData) { - throw new Error("no account found"); + throw new Error('no account found') } - address = accountData.address; + address = accountData.address } - let balance = await this.web3.eth.getBalance(address); - return balance; + let balance = await this.web3.eth.getBalance(address) + return balance } public signTypedDataV4(signObj: any) { - const account = this.currentAccount(); + const account = this.currentAccount() return signTypedData({ data: signObj, - privateKey: Buffer.from(account.privateKey.replace("0x", ""), "hex"), + privateKey: Buffer.from(account.privateKey.replace('0x', ''), 'hex'), version: SignTypedDataVersion.V4, - }); + }) } public loginSign(nonce: string, tips: string, address?: string) { - const account = this.currentAccount(); - return signLogin(nonce, tips, account.privateKey); + return new Promise((resolve, reject) => { + const account = this.currentAccount() + if (this.walletType === WalletType.INTERNAL) { + const sig = signLogin(nonce, tips, account.privateKey) + resolve && resolve(sig); + } else { + const signObj = buildLoginSignMsg(nonce, tips); + this.wConnect.signData(signObj, account) + .then(sig => { + resolve && resolve(sig); + }) + .catch(err=> { + reject && reject(err); + }) + // @ts-ignore + jumpToWallet() + } + }) } public recoverTypedSignatureV4(signObj: any, signature: string) { @@ -329,26 +378,26 @@ export default class JCWallet { data: signObj, signature, version: SignTypedDataVersion.V4, - }); + }) } public generateIconData(msg: string, diameter: number) { - let icon = new JazzIcon(); - return icon.init(msg, diameter); + let icon = new JazzIcon() + return icon.init(msg, diameter) } public async erc20Info(address: string) { - let symbol = await this.erc20Standard.getTokenSymbol(address); - let decimal = await this.erc20Standard.getTokenDecimals(address); - return {symbol, decimal}; + let symbol = await this.erc20Standard.getTokenSymbol(address) + let decimal = await this.erc20Standard.getTokenDecimals(address) + return { symbol, decimal } } public async erc20Balance(address: string, account?: string) { if (!account) { - account = this.currentAccount().address; + account = this.currentAccount().address } - let result = await this.erc20Standard.getBalanceOf(address, account); - return result; + let result = await this.erc20Standard.getBalanceOf(address, account) + return result } public async sendErc20(address: string, to: string, amount: string) { @@ -357,18 +406,18 @@ export default class JCWallet { address, from, to, - amount + amount, }) - return result; + return result } } // window.jc = window.jc || {wallet: new JCWallet()}; -export * from "./common/WalletEvent"; -export * from "./common/ZError"; -export * from "./config/chain_config"; -export * from "./util/number.util"; -export * from "./util/wallet.util"; -export * from "./data/DataModel"; -export * from "./lib/WalletConnect"; +export * from './common/WalletEvent' +export * from './common/ZError' +export * from './config/chain_config' +export * from './util/number.util' +export * from './util/wallet.util' +export * from './data/DataModel' +export * from './lib/WalletConnect' diff --git a/src/lib/WalletConnectProvider.ts b/src/lib/WalletConnectProvider.ts index 39f8bb0..2c122e1 100644 --- a/src/lib/WalletConnectProvider.ts +++ b/src/lib/WalletConnectProvider.ts @@ -19,6 +19,7 @@ const SubscriptionsSubprovider = require("web3-provider-engine/subproviders/subs class WalletConnectProvider extends ProviderEngine { public bridge = "https://bridge.walletconnect.org"; + public qrcodeModal; public rpc: IRPCMap | null = null; public infuraId = ""; public http: HttpConnection | null = null; @@ -35,13 +36,12 @@ class WalletConnectProvider extends ProviderEngine { this.bridge = opts.connector ? opts.connector.bridge : opts.bridge || "https://bridge.walletconnect.org"; - this.qrcode = typeof opts.qrcode === "undefined" || opts.qrcode !== false; this.qrcodeModal = opts.qrcodeModal || this.qrcodeModal; - this.qrcodeModalOptions = opts.qrcodeModalOptions; this.wc = opts.connector || new WalletConnect({ bridge: this.bridge, + qrcodeModal: this.qrcodeModal, storageId: opts?.storageId, signingMethods: opts?.signingMethods, clientMeta: opts?.clientMeta, @@ -314,6 +314,7 @@ class WalletConnectProvider extends ProviderEngine { updateHttpConnection() { if (this.rpcUrl) { + console.log(`updateHttpConnection: ${this.rpcUrl}`) this.http = new HttpConnection(this.rpcUrl); this.http.on("payload", payload => this.emit("payload", payload)); this.http.on("error", error => this.emit("error", error)); diff --git a/src/util/sign.util.ts b/src/util/sign.util.ts index e9b3b68..6c8ce21 100644 --- a/src/util/sign.util.ts +++ b/src/util/sign.util.ts @@ -1,7 +1,6 @@ import { signTypedData, SignTypedDataVersion, TypedMessage } from "@metamask/eth-sig-util" - -export function signLogin(nonce: string, tips: string, privateKey: string) { +export function buildLoginSignMsg(nonce: string, tips: string) { const signMsg = { tips, nonce, @@ -24,7 +23,11 @@ export function signLogin(nonce: string, tips: string, privateKey: string) { }, message: signMsg } - + return signObj +} + +export function signLogin(nonce: string, tips: string, privateKey: string) { + const signObj = buildLoginSignMsg(nonce, tips); return signTypedData({ //@ts-ignore data: signObj,