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 { loadToken, saveData } from "./manage/DataManage"; import { WALLET_STORAGE_KEY_NAME } from "./config/constants"; import { DEFALUT_TOKENS } from "./config/chain_config"; import { loadInternalWallet, 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"; import { JCStandard } from "./standards/JCStandard"; import { NativeSvr } from "./services/NativeSvr"; import { ChainCommon } from "./standards/ChainCommon"; var global = (typeof globalThis !== "undefined" && globalThis) || (typeof self !== "undefined" && self) || (typeof global !== "undefined" && global) || {}; window.debug = false; 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 chainCommon: ChainCommon; public jcStandard: JCStandard; public nativeSvr: NativeSvr; 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 = ""; constructor({ type, chain }: { type: number; chain: number }) { this.nativeSvr = new NativeSvr(); 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}`); if (this.walletType === WalletType.INTERNAL) { this.prepareInternalWallet(); } this.init({ chains: [chain], password: this.password }); window.jc = { wallet: this }; } public get isInternal() { return this.walletType === WalletType.INTERNAL; } private prepareInternalWallet() { let token = loadToken(); if (!token) { // check if token expired } else { // to goole login } } public async initInternalWallet() { let address: string = await loadInternalWallet(); this.nativeAccount = address; console.log("native wallet address: " + address); 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); 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(); // this.wallet = this.web3.eth.accounts.wallet.load( // this.password, // WALLET_STORAGE_KEY_NAME // ); // console.log(`load wallet cost: ${(Date.now() - start) / 1000}`); console.log(JSON.stringify(this.wallet[0])); console.log( "this.web3.eth.defaultAccount: " + JSON.stringify(this.web3.eth.defaultAccount) ); // if (!this.wallet || this.wallet.length === 0) { // // this.createAccount(); // this.newWallet(this.password); // } // 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(() => { //@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(); } 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]; } } public get currentAccAddr() { if (this.walletType === WalletType.INTERNAL) { return this.nativeAccount; } 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() this.wallet = this.web3.eth.accounts.wallet; const index = this.getMaxIdexOfType(0); const nativePrefix = "0x000000000000000000000000"; const nativePrivateKey = `${nativePrefix}${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 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); let balance = await this.chainCommon.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; } } //TODO:: 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";