jcwallet/src/index.ts
2022-09-16 11:44:04 +08:00

549 lines
15 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 {
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<number> = new Set()
private chainMap: Map<number, IChainData> = 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'