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' 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, INFT, initAccount, initNFT } 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' import { newAccount, newMnemonic, restoreWalletByMnemonic, } 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 { getJCErc721Info, getTypeByAddress, UNKNOW } from './util/chain.util' var 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 } } export interface IChainData { 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: WalletType = WalletType.INTERNAL private rpcUrl: string = '' public rpc: any = {} 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') } 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(); } this.init({ chains: [322, 80001], password: this.password }) window.jc = { wallet: this } } 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}`) } /** * init wallet connect * @returns */ public async initThirdPartyWallet() { return new Promise((resolve, reject) => { for (const d of AllChains) { const id = d.id this.rpc[id] = d.rpc } this.wConnect = new ZWalletConnect(this.rpc) // return this.wConnect.connect(); this.wConnect.connect() .then(()=>{ resolve && resolve('') }) .catch((err: Error)=>{ console.log('initThirdPartyWallet error: ' + err); reject && reject(err.message); }) }) } /** * init chain data * create local wallet data if there is no local wallet data * @returns */ private init({ chains, password }: { chains: number[]; password: string }) { for (let chain of chains) { this.chainSet.add(chain) if (!this.chainMap.has(chain)) { let data = AllChains.find((o) => o.id === chain) if (data) { this.chainMap.set(chain, data) if (!this._currentChain) { this._currentChain = data } } } } if (this.walletType !== WalletType.INTERNAL) { return; } if (!this.wallet || this.wallet.length === 0) { // this.createAccount(); this.newWallet(password) } } public newWallet(password: string) { 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() } get currentChain() { return this._currentChain } updateCurrentChain(chainId: number) { return new Promise((resolve, reject) => { if (this.walletType === WalletType.INTERNAL) { 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') resolve && resolve(this.currentChain); } else { console.log('third parth wallet change chain: ' + chainId) const chainData = this.chainMap.get(chainId) this.wConnect.addOrChangeChain(chainData) .then(() => { resolve && resolve(chainData); }) .catch(err=>{ reject && reject(err); }) setTimeout(()=>{ // @ts-ignore jumpToWallet() }, 500) } }) } updateListType(type: string) { this.mainHandlers.emit(WALLET_TOKEN_TYPE_CHANGE, type) } get chainList() { return [...this.chainMap.values()] } public saveLocal() {} public loadLocal() {} public saveRemote() {} public loadRemote() {} public currentAccount() { 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) if (!data) { throw new Error('account data not found') } if (!data.tokenData[chain]) { let tokens = DEFALUT_TOKENS[chain] data.tokenData[chain] = { tokens, heros: [], weapons: [], chips: [], } } saveData(this.data) return data } get accounts() { 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) if (!data) { const nickname = `Account ${index + 1}` data = initAccount({ address: account.address, chain, nickname, type: 0, index, }) this.data.push(data) saveData(this.data) } 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) if (!data) { 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.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 for (let i = 0, l = this.data.length; i < l; i++) { if (this.data[i].type !== type) { continue } maxIdx = Math.max(this.data[i].index, maxIdx) } return maxIdx + 1 } private getAccountByAddress(address: string) { 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 } } return { account, index } } public selectAccount(address: string) { 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) } return address } public async sendEth(to: string, amount: number | string) { 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 }) } public async getBalance(address?: string) { console.log('get balance with address: ', address) if (!address) { let accountData = this.wallet[this.accountIndex] if (!accountData) { throw new Error('no account found') } address = accountData.address } let balance = await this.web3.eth.getBalance(address) return balance } public signTypedDataV4(signObj: any) { const account = this.currentAccount() return signTypedData({ data: signObj, privateKey: Buffer.from(account.privateKey.replace('0x', ''), 'hex'), version: SignTypedDataVersion.V4, }) } public loginSign(nonce: string, tips: string, address?: string) { 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); }) setTimeout(()=>{ // @ts-ignore jumpToWallet() }, 500) } }) } public recoverTypedSignatureV4(signObj: any, signature: string) { return recoverTypedSignature({ data: signObj, signature, version: SignTypedDataVersion.V4, }) } public generateIconData(msg: string, diameter: number) { 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 } } public async erc20Balance(address: string, account?: string) { if (!account) { account = this.currentAccount().address } let result = await this.erc20Standard.getBalanceOf(address, account) return result } public async sendErc20(address: string, to: string, amount: string) { let from = this.currentAccount().address let result = await this.erc20Standard.transfer({ address, from, to, amount, }) return result } private async updateTokenInfo(data: INFT, address: string, index: number, account: string) { const tokenId = await this.wallet.erc721Standard.getCollectibleTokenId( address, account, index ) const info = await getJCErc721Info(tokenId); data.tokenId = tokenId; data.name = info.name; data.desc = info.description; data.image = info.image; data.last = Date.now(); } public async nftInfo(address: string, index: number, account: string, refresh: boolean) { account = account || this.currentAccount().address const chain = this.wallet.currentChain.id const { categor, type } = getTypeByAddress(chain, address); let nfts = []; if (categor !== UNKNOW) { nfts = this.currentAccountData.tokenData[chain][`${categor}s`] } let needRefresh = !(nfts.length > index && nfts[index].tokenId && !refresh); if (needRefresh) { this.updateTokenInfo(nfts[index], address, index, account); saveData(this.data); } return nfts[index]; } public async nftList(address: string, account?: string) { account = account || this.currentAccount().address const chain = this.wallet.currentChain.id const amount = await this.erc721Standard.getBalance(address, account) const { categor, type }= getTypeByAddress(chain, address); let nfts = []; if (categor !== UNKNOW) { nfts = this.currentAccountData.tokenData[chain][`${categor}s`] } let refresh = false; if (nfts.length !== amount) { refresh = true; } if (refresh) { nfts.length = 0; for (let i = 0; i < amount; i++) { const nftData = initNFT(address, i, type) nfts.push(nftData) } } return nfts; } public async sendNFT(address: string, to: string, tokenId: string) { let from = this.currentAccount().address let result = await this.erc721Standard.transfer({ address, from, to, tokenId, }) return result } public async erc1155Info(address: string) { } public async erc1155List(address: string, account?: string) { if (!account) { account = this.currentAccount().address } } public async sendErc1155() { } } // 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'