jcwallet/src/index.ts

586 lines
17 KiB
TypeScript

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<number> = new Set();
private chainMap: Map<number, IChainData> = 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";