653 lines
18 KiB
TypeScript
653 lines
18 KiB
TypeScript
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<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 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<string, string> = 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";
|