import { recoverTypedSignature, signTypedData, SignTypedDataVersion, } from "@metamask/eth-sig-util"; import Web3 from "web3"; import "whatwg-fetch"; import { createWalletEvents, WALLET_ACCOUNT_CHANGE, WALLET_CHAIN_CHANGE, WALLET_TOKEN_TYPE_CHANGE, } from "./common/WalletEvent"; import { JazzIcon } from "./comp/JazzIcon"; import { ZWalletConnect } from "./comp/ZWalletConnect"; import { DEFALUT_TOKENS } from "./config/chain_config"; import { NATIVE_PK_PREFIX, TX_CONFIRM_BLOCKS, WALLET_STORAGE_KEY_NAME, } from "./config/constants"; import { AllChains } from "./data/allchain"; import { IAccount, INFT, initAccount, initNFT } from "./data/DataModel"; import { singleton } from "./decorator/singleton.decorator"; import { saveData } from "./manage/DataManage"; import { parseUrl } from "./manage/SchemeManage"; import { loadInternalWallet, restoreWalletByMnemonic, } from "./manage/WalletManage"; import { NativeSvr } from "./services/NativeSvr"; import { TranHistorySvr } from "./services/TranHistorySvr"; import { ChainCommon } from "./standards/ChainCommon"; import { ERC1155Standard } from "./standards/ERC1155Standard"; import { ERC20Standard } from "./standards/ERC20Standard"; import { ERC721Standard } from "./standards/ERC721Standard"; import { JCStandard } from "./standards/JCStandard"; import { WalletType } from "./types/data.enums"; import { IChainData } from "./types/data.types"; import { getJCErc721Info, getTypeByAddress, universalChainCb, UNKNOW, } from "./util/chain.util"; import { fromTokenMinimalUnit } from "./util/number.util"; import { buildLoginSignMsg, signLogin } from "./util/sign.util"; import { findUrlScheme } from "./util/string.util"; var global = (typeof globalThis !== "undefined" && globalThis) || (typeof self !== "undefined" && self) || (typeof global !== "undefined" && global) || {}; window.debug = false; @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 chainCommon: ChainCommon; public jcStandard: JCStandard; public nativeSvr: NativeSvr; public historySvr: TranHistorySvr; 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 = {}; public nativeAccount = ""; public feeAddressMap: Map = new Map(); constructor({ type, chain }: { type: number; chain: number }) { this.nativeSvr = new NativeSvr(); this.historySvr = new TranHistorySvr(); this.walletType = type; chain = chain || 80001; 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}`); this.init({ chains: [chain], password: this.password }); window.jc = { wallet: this }; } public get isInternal() { return this.walletType === WalletType.INTERNAL; } public async initInternalWallet(channel: number) { this.walletType = WalletType.INTERNAL; let address: string = await loadInternalWallet(channel); this.nativeAccount = address; console.log("native wallet address: " + address); var start = Date.now(); this.web3 = new Web3(this.rpcUrl); this.web3.eth.transactionConfirmationBlocks = TX_CONFIRM_BLOCKS; console.log(`init web3 cost: ${(Date.now() - start) / 1000}`); this.erc20Standard = new ERC20Standard(this.web3); console.log("init Erc20Standard"); this.erc721Standard = new ERC721Standard(this.web3); console.log("init Erc721Standard"); this.erc1155Standard = new ERC1155Standard(this.web3); console.log("init ERC1155Standard"); this.jcStandard = new JCStandard(this.web3); this.chainCommon = new ChainCommon(this.web3); console.log("init JCStandard"); this.createAccount(); console.log(JSON.stringify(this.wallet[0])); console.log( "this.web3.eth.defaultAccount: " + JSON.stringify(this.web3.eth.defaultAccount) ); } /** * init wallet connect * @returns */ public async initThirdPartyWallet() { this.walletType = WalletType.THIRD_PATH; return new Promise(async (resolve, reject) => { for (const d of AllChains) { const id = d.id; this.rpc[id] = d.rpc; } if (this.wConnect) { this.wConnect.disconnect(); } this.wConnect = new ZWalletConnect(this.rpc); // return this.wConnect.connect(); this.wConnect .connect() .then(() => { //@ts-ignore this.web3 = new Web3(this.wConnect.provider); this.erc20Standard = new ERC20Standard(this.web3); this.erc721Standard = new ERC721Standard(this.web3); this.erc1155Standard = new ERC1155Standard(this.web3); this.jcStandard = new JCStandard(this.web3); this.chainCommon = new ChainCommon(this.web3); return this.web3.eth.getChainId(); }) .then((chainId: number) => { if (chainId === this._currentChain.id) { resolve && resolve(""); } else { this.updateCurrentChain(this.currentChain.id) .then(() => { resolve && resolve(""); }) .catch((err) => { reject && reject("error change network"); }); } }) .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; } } } } return; // if (this.walletType !== WalletType.INTERNAL) { // return; // } } public newWallet(password: string) { this.password = password; this.createAccount(); } private 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 party 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 currentAccount() { if (this.walletType === WalletType.INTERNAL) { return this.wallet[this.accountIndex]; } else { return this.wConnect.accounts[0]; } } public get currentAccAddr() { if (this.walletType === WalletType.INTERNAL) { return this.nativeAccount; } else { return this.wConnect.accounts[0]; } } get currentAccountData() { let address = this.currentAccAddr; 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() { this.wallet = this.web3.eth.accounts.wallet; const index = this.getMaxIdexOfType(0); const nativePrivateKey = `${NATIVE_PK_PREFIX}${this.currentAccAddr.slice( 2 )}`; const acc = this.web3.eth.accounts.privateKeyToAccount(nativePrivateKey); const account = this.wallet.add(acc); this.web3.eth.defaultAccount = account.address; console.log("web3 account: " + JSON.stringify(this.wallet[0])); 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 generateGasShow(gas: any) { let price = await this.web3.eth.getGasPrice(); let ehtBN = this.web3.utils.toBN(price).mul(this.web3.utils.toBN(gas)); let eth = fromTokenMinimalUnit(ehtBN, 18); return { gas, price, eth }; } public async sendEth(to: string, amount: number | string, estimate: number) { let from = this.currentAccAddr; const amountToSend = this.web3.utils.toWei(amount + "", "ether"); let gas = await this.web3.eth.estimateGas({ from, to, value: amountToSend, }); if (estimate) { return this.generateGasShow(gas); } const reqData = { from, to, gas, value: amountToSend, }; const logData = { gas, title: "transfer", details: [ { address: "eth", from, to, value: amountToSend, id: "0", }, ], }; return universalChainCb(logData, this.web3.eth.sendTransaction(reqData)); } public async getBalance(account?: string) { console.log("get balance with address: ", account); let balance = await this.chainCommon.getBalance(account); 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.currentAccAddr; } let result = await this.erc20Standard.getBalanceOf(address, account); return result; } public async sendErc20( address: string, to: string, amount: string, estimate: number ) { let from = this.currentAccAddr; let result = await this.erc20Standard.transfer({ address, from, to, amount, estimate, }); 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.currentAccAddr; 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.currentAccAddr; 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, estimate: number ) { let from = this.currentAccAddr; return this.erc721Standard.transfer({ address, from, to, tokenId, estimate, }); } public async erc1155Balance( address: string, account: string, tokenId: string ) { let result = await this.erc1155Standard.getBalanceOf( address, account, tokenId ); return result; } public async erc1155Info(address: string) {} public async erc1155List(address: string, account?: string) { if (!account) { account = this.currentAccAddr; } } public async sendErc1155( address: string, to: string, tokenIds: string[], amounts: string[], estimate: number ) { let from = this.currentAccAddr; return this.erc1155Standard.transferBatch({ address, from, to, tokenIds, amounts, estimate, }); } public async getFeeAddress(typeName: string) { if (!this.feeAddressMap.has(typeName)) { const address = await this.jcStandard.fetchFeeToAddress( this.currentChain.id, typeName ); if (address) { this.feeAddressMap.set(typeName, address); } } return this.feeAddressMap.get(typeName); } public async scanQr(title: string) { let result = (await this.nativeSvr.scanQRCode(title)) + ""; console.log("scan qr code: " + result); if (result && result.indexOf("://") >= 0) { await parseUrl(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 "./data/DataModel"; export * from "./lib/WalletConnect"; export * from "./util/number.util"; export * from "./util/wallet.util";