project init

This commit is contained in:
cebgcontract 2022-07-05 16:58:58 +08:00
commit 715ef231f5
25 changed files with 7995 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
build
dist
.DS_Store
yarn-error.log

26
package.json Normal file
View File

@ -0,0 +1,26 @@
{
"name": "jcwallet",
"version": "1.0.0",
"description": "embed wallet for game",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "rm -rf ./dist && webpack && tsc --declaration -p ./ -t es2015 --emitDeclarationOnly --outDir dist ",
"dts": "tsc --declaration -p ./ -t es2015 --emitDeclarationOnly --outDir dist "
},
"author": "zhl",
"license": "ISC",
"dependencies": {
"@metamask/eth-sig-util": "^4.0.1",
"bip39": "^3.0.4",
"ethereumjs-wallet": "^1.0.2",
"web3": "^1.7.4",
"whatwg-fetch": "^3.6.2"
},
"devDependencies": {
"ts-loader": "~8.2.0",
"typescript": "~4.1.5",
"webpack": "^4.44.2",
"webpack-cli": "^4.9.1"
}
}

326
src/abis/abiERC1155.ts Normal file
View File

@ -0,0 +1,326 @@
import { AbiItem } from "web3-utils"
export const abiERC1155: AbiItem[] = [
{
inputs: [
{
internalType: 'string',
name: 'uri_',
type: 'string',
},
],
stateMutability: 'nonpayable',
type: 'constructor',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'account',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: false,
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'ApprovalForAll',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
{
indexed: false,
internalType: 'uint256[]',
name: 'values',
type: 'uint256[]',
},
],
name: 'TransferBatch',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
internalType: 'address',
name: 'operator',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: false,
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
{
indexed: false,
internalType: 'uint256',
name: 'value',
type: 'uint256',
},
],
name: 'TransferSingle',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: false,
internalType: 'string',
name: 'value',
type: 'string',
},
{
indexed: true,
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
],
name: 'URI',
type: 'event',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
],
name: 'balanceOf',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address[]',
name: 'accounts',
type: 'address[]',
},
{
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
],
name: 'balanceOfBatch',
outputs: [
{
internalType: 'uint256[]',
name: '',
type: 'uint256[]',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'account',
type: 'address',
},
{
internalType: 'address',
name: 'operator',
type: 'address',
},
],
name: 'isApprovedForAll',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256[]',
name: 'ids',
type: 'uint256[]',
},
{
internalType: 'uint256[]',
name: 'amounts',
type: 'uint256[]',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
],
name: 'safeBatchTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'from',
type: 'address',
},
{
internalType: 'address',
name: 'to',
type: 'address',
},
{
internalType: 'uint256',
name: 'id',
type: 'uint256',
},
{
internalType: 'uint256',
name: 'amount',
type: 'uint256',
},
{
internalType: 'bytes',
name: 'data',
type: 'bytes',
},
],
name: 'safeTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'address',
name: 'operator',
type: 'address',
},
{
internalType: 'bool',
name: 'approved',
type: 'bool',
},
],
name: 'setApprovalForAll',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [
{
internalType: 'bytes4',
name: 'interfaceId',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
internalType: 'bool',
name: '',
type: 'bool',
},
],
stateMutability: 'view',
type: 'function',
},
{
inputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256',
},
],
name: 'uri',
outputs: [
{
internalType: 'string',
name: '',
type: 'string',
},
],
stateMutability: 'view',
type: 'function',
},
]

274
src/abis/abiERC20.ts Normal file
View File

@ -0,0 +1,274 @@
import { AbiItem } from "web3-utils"
export let abiERC20: AbiItem[] = [
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_spender',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'approve',
outputs: [
{
name: 'success',
type: 'bool',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'totalSupply',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_from',
type: 'address',
},
{
name: '_to',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'transferFrom',
outputs: [
{
name: 'success',
type: 'bool',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'decimals',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'version',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
name: 'balance',
type: 'uint256',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_to',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
],
name: 'transfer',
outputs: [
{
name: 'success',
type: 'bool',
},
],
payable: false,
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_spender',
type: 'address',
},
{
name: '_value',
type: 'uint256',
},
{
name: '_extraData',
type: 'bytes',
},
],
name: 'approveAndCall',
outputs: [
{
name: 'success',
type: 'bool',
},
],
payable: false,
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
{
name: '_spender',
type: 'address',
},
],
name: 'allowance',
outputs: [
{
name: 'remaining',
type: 'uint256',
},
],
payable: false,
type: 'function',
},
{
inputs: [
{
name: '_initialAmount',
type: 'uint256',
},
{
name: '_tokenName',
type: 'string',
},
{
name: '_decimalUnits',
type: 'uint8',
},
{
name: '_tokenSymbol',
type: 'string',
},
],
type: 'constructor',
},
{
payable: false,
type: 'fallback',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: '_from',
type: 'address',
},
{
indexed: true,
name: '_to',
type: 'address',
},
{
indexed: false,
name: '_value',
type: 'uint256',
},
],
name: 'Transfer',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: '_owner',
type: 'address',
},
{
indexed: true,
name: '_spender',
type: 'address',
},
{
indexed: false,
name: '_value',
type: 'uint256',
},
],
name: 'Approval',
type: 'event',
},
]

378
src/abis/abiERC721.ts Normal file
View File

@ -0,0 +1,378 @@
import { AbiItem } from "web3-utils"
export const abiERC721: AbiItem[] = [
{
constant: true,
inputs: [
{
name: 'interfaceID',
type: 'bytes4',
},
],
name: 'supportsInterface',
outputs: [
{
name: '',
type: 'bool',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'name',
outputs: [
{
name: '_name',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'getApproved',
outputs: [
{
name: '',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_approved',
type: 'address',
},
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'approve',
outputs: [],
payable: true,
stateMutability: 'payable',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'totalSupply',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_from',
type: 'address',
},
{
name: '_to',
type: 'address',
},
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'transferFrom',
outputs: [],
payable: true,
stateMutability: 'payable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
{
name: '_index',
type: 'uint256',
},
],
name: 'tokenOfOwnerByIndex',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_from',
type: 'address',
},
{
name: '_to',
type: 'address',
},
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'safeTransferFrom',
outputs: [],
payable: true,
stateMutability: 'payable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_index',
type: 'uint256',
},
],
name: 'tokenByIndex',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'ownerOf',
outputs: [
{
name: '',
type: 'address',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
],
name: 'balanceOf',
outputs: [
{
name: '',
type: 'uint256',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [],
name: 'symbol',
outputs: [
{
name: '_symbol',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_operator',
type: 'address',
},
{
name: '_approved',
type: 'bool',
},
],
name: 'setApprovalForAll',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
constant: false,
inputs: [
{
name: '_from',
type: 'address',
},
{
name: '_to',
type: 'address',
},
{
name: '_tokenId',
type: 'uint256',
},
{
name: 'data',
type: 'bytes',
},
],
name: 'safeTransferFrom',
outputs: [],
payable: true,
stateMutability: 'payable',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_tokenId',
type: 'uint256',
},
],
name: 'tokenURI',
outputs: [
{
name: '',
type: 'string',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: true,
inputs: [
{
name: '_owner',
type: 'address',
},
{
name: '_operator',
type: 'address',
},
],
name: 'isApprovedForAll',
outputs: [
{
name: '',
type: 'bool',
},
],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: '_from',
type: 'address',
},
{
indexed: true,
name: '_to',
type: 'address',
},
{
indexed: true,
name: '_tokenId',
type: 'uint256',
},
],
name: 'Transfer',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: '_owner',
type: 'address',
},
{
indexed: true,
name: '_approved',
type: 'address',
},
{
indexed: true,
name: '_tokenId',
type: 'uint256',
},
],
name: 'Approval',
type: 'event',
},
{
anonymous: false,
inputs: [
{
indexed: true,
name: '_owner',
type: 'address',
},
{
indexed: true,
name: '_operator',
type: 'address',
},
{
indexed: false,
name: '_approved',
type: 'bool',
},
],
name: 'ApprovalForAll',
type: 'event',
},
]

35
src/common/WalletEvent.ts Normal file
View File

@ -0,0 +1,35 @@
let createWalletEvents = () => ({
events: {},
emit (event, ...args) {
for (let i of this.events[event] || []) {
i(...args)
}
},
on (event, cb) {
;(this.events[event] = this.events[event] || []).push(cb)
return () => (this.events[event] = this.events[event].filter(i => i !== cb))
},
listen(event, cb) {
;(this.events[event] = this.events[event] || []).push(cb)
return () => (this.events[event] = this.events[event].filter(i => i !== cb))
}
})
export { createWalletEvents }
export const WALLET_CHAIN_CHANGE = 'wallet_chain_change'
export const WALLET_ACCOUNT_CHANGE = 'wallet_account_change'
export const WALLET_TOKEN_TYPE_CHANGE = 'wallet_token_type_change'
// BEGIN of UI
export const WALLET_SHOW_QR = 'wallet_show_qr'
export const WALLET_SHOW_ACCOUNT_LIST = 'wallet_show_account_list'
export const WALLET_HIDE_ACCOUNT_LIST = 'wallet_hide_account_list'
export const WALLET_SHOW_MODAL = 'wallet_show_modal'
export const WALLET_HIDE_MODAL = 'wallet_hide_modal'

12
src/common/ZError.ts Normal file
View File

@ -0,0 +1,12 @@
export class ZError implements Error {
code: string;
statusCode?: number;
message: string;
name: string;
constructor(statusCode: number, message: string) {
this.statusCode = statusCode;
this.message = message;
}
}

View File

@ -0,0 +1,71 @@
export const DEFALUT_TOKENS = {
321: [
{
type: 'eth',
address: 'eth',
symbol: 'KCS',
decimal: 18
},
{
type: 'erc20',
address: '0xcaA011E902103752435AC98657B046B282E36509',
symbol: 'CEC',
decimal: 18
},
{
type: 'erc20',
address: '0xBE023C9D294A4668f0d510E2570CB81B4536cE77',
symbol: 'CEG',
decimal: 18
}
],
322: [
{
type: 'eth',
address: 'eth',
symbol: 'tKCS',
decimal: 18
},
{
type: 'erc20',
address: '0xdb6D4bB22E2C12686Efff25a79EC78f9f078fe7D',
symbol: 'CEC',
decimal: 18
},
{
type: 'erc20',
address: '0xC5Cd606b3e9B80b758e8274B198c76D929aA094A',
symbol: 'CEG',
decimal: 18
}
],
97: [
{
type: 'eth',
address: 'eth',
symbol: 'BNB',
decimal: 18
}
],
56: [
{
type: 'eth',
address: 'eth',
symbol: 'BNB',
decimal: 18
}
]
}
export const DEFAULT_NFT_TYPES = {
321: {
hero: '0x0EB362BD40F2288fF25A6Ee1b487cB0cb4638e0D',
weapon: '0x29F67A372AC1c6AcF478A564992D421FE20F2cc8',
chip: '0x54B6ED7EDe9355b471985439421Aa1DC7Da6Dc20'
},
322: {
hero: '0x52917087cd4E48bDb5f336012E677f471f9E1C2D',
weapon: '0x500AD8A4D50d71Af5cA8eA3b12B914f7aE5466f7',
chip: '0x0640958BDb4D7956e1452FacEBD550C6Cf42aC94'
}
}

1
src/config/constants.ts Normal file
View File

@ -0,0 +1 @@
export const WALLET_STORAGE_KEY_NAME = 'jc_wallet_data'

62
src/data/DataModel.ts Normal file
View File

@ -0,0 +1,62 @@
import { DEFALUT_TOKENS } from "../config/chain_config"
export interface IToken {
address: string
type: 'eth'|'erc20'
default: boolean
symbol?: string
balance?: string
decimal: number
image?: string
last?: number
}
export interface INFT {
address: string
index: number
tokenId?: string
image?: string
name?: string
desc?: string
last?: number
}
export function initNFT(address: string, index: number) {
return {
address,
index
}
}
export interface ITokenData {
tokens: IToken[]
heros: INFT[]
weapons: INFT[]
chips: INFT[]
}
export interface IAccount {
address: string
nickname?: string
avatar?: string
tokenData: {
[key: number]: ITokenData
}
}
export function initAccount(address: string, chain: number, nickname: string): IAccount {
let chainData = {}
let data: IAccount = {
address,
nickname,
tokenData: chainData
}
let tokens = DEFALUT_TOKENS[chain]
chainData[chain] = {
tokens,
heros: [],
weapons: [],
chips: []
}
//TODO: add default tokens
return data
}

278
src/data/allchain.ts Normal file
View File

@ -0,0 +1,278 @@
export const AllChains = [
{
name: 'Ethereum Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.ankr.com/eth',
id: 1,
symbol: 'ETH',
explorerurl: 'https://etherscan.io'
},
{
name: 'Ethereum Ropsten Testnet RPC',
type: 'Testnet',
rpc: 'https://ropsten.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
id: 3,
symbol: 'ETH',
explorerurl: 'https://ropsten.etherscan.io'
},
{
name: 'Ethereum Rinkeby Testnet RPC',
type: 'Testnet',
rpc: 'https://rinkey.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
id: 4,
symbol: 'ETH',
explorerurl: 'https://rinkey.etherscan.io'
},
{
name: 'Ethereum Goerli Testnet RPC',
type: 'Testnet',
rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
id: 5,
symbol: 'ETH',
explorerurl: 'https://goerli.etherscan.io'
},
{
name: 'Ethereum Kovan Testnet RPC',
type: 'Testnet',
rpc: 'https://kovan.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161',
id: 6,
symbol: 'ETH',
explorerurl: 'https://kovan.etherscan.io'
},
{
name: 'Ubiq Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.octano.dev/',
id: 8,
symbol: 'UBQ',
explorerurl: 'https://ubiqscan.io/'
},
{
name: 'Elastos ETH Mainnet RPC',
type: 'Mainnet',
rpc: 'https://api.elastos.io/eth',
id: 20,
symbol: 'ELA',
explorerurl: 'https://explorer.elaeth.io/'
},
{
name: 'Cronos Mainnet RPC',
type: 'Mainnet',
rpc: 'https://evm-cronos.crypto.org',
id: 25,
symbol: 'CRO',
explorerurl: 'https://cronos.crypto.org/explorer/'
},
{
name: 'Telos EVM Mainnet RPC',
type: 'Mainnet',
rpc: 'https://mainnet.telos.net/evm',
id: 40,
symbol: 'TLOS',
explorerurl: 'https://telos.net/'
},
{
name: 'Binance Smart Chain',
type: 'Mainnet',
rpc: 'https://rpc.ankr.com/bsc',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTAwIiBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9IjAgMCAyMDAwIDIwMDAiPjxnIGZpbGw9IiNmM2JhMmYiPjxwYXRoIGQ9Ik02MTEuNTkgODQwLjQybDM4OC40LTM4OC4zOSAzODguNiAzODguNTkgMjI2LTIyNkw5OTkuOTkgMCAzODUuNiA2MTQuNDJsMjI1Ljk5IDIyNk0uMDA2IDk5OS45NjlsMjI2LjAwNy0yMjYuMDA3IDIyNS45OTIgMjI1Ljk5M0wyMjYgMTIyNS45NnpNNjExLjU5IDExNTkuNThsMzg4LjQgMzg4LjM5IDM4OC41OS0zODguNTggMjI2LjEyIDIyNS44OC0uMTEuMTJMOTk5Ljk5IDIwMDBsLTYxNC40MS02MTQuNC0uMzItLjMyIDIyNi4zMy0yMjUuN00xNTQ4LjAxMyAxMDAwLjA5M2wyMjYuMDA3LTIyNi4wMDYgMjI1Ljk5MiAyMjUuOTkyLTIyNi4wMDYgMjI2LjAwN3oiLz48cGF0aCBkPSJNMTIyOS4yMiA5OTkuODhoLjFMOTk5Ljk5IDc3MC41NSA4MzAuNTEgOTQwLjAzaC0uMDFsLTE5LjQ3IDE5LjQ4LTQwLjE2IDQwLjE3LS4zMi4zMS4zMi4zMyAyMjkuMTIgMjI5LjEzIDIyOS4zMy0yMjkuMzMuMTEtLjEzLS4yMS0uMTEiLz48L2c+PC9zdmc+',
id: 56,
symbol: 'BNB',
explorerurl: 'https://bscscan.com'
},
{
name: 'OKExChain Mainnet RPC',
type: 'Mainnet',
rpc: 'https://exchainrpc.okex.org',
id: 66,
symbol: 'OKT',
explorerurl: 'https://www.oklink.com/okexchain'
},
{
name: 'Hoo Mainnet RPC',
type: 'Mainnet',
rpc: 'https://http-mainnet.hoosmartchain.com',
id: 70,
symbol: 'HOO',
explorerurl: 'https://hooscan.com'
},
{
name: 'Binance Testnet',
type: 'Testnet',
rpc: 'https://data-seed-prebsc-1-s1.binance.org:8545/',
id: 97,
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNTAwIiBoZWlnaHQ9IjI1MDAiIHZpZXdCb3g9IjAgMCAyMDAwIDIwMDAiPjxnIGZpbGw9IiNmM2JhMmYiPjxwYXRoIGQ9Ik02MTEuNTkgODQwLjQybDM4OC40LTM4OC4zOSAzODguNiAzODguNTkgMjI2LTIyNkw5OTkuOTkgMCAzODUuNiA2MTQuNDJsMjI1Ljk5IDIyNk0uMDA2IDk5OS45NjlsMjI2LjAwNy0yMjYuMDA3IDIyNS45OTIgMjI1Ljk5M0wyMjYgMTIyNS45NnpNNjExLjU5IDExNTkuNThsMzg4LjQgMzg4LjM5IDM4OC41OS0zODguNTggMjI2LjEyIDIyNS44OC0uMTEuMTJMOTk5Ljk5IDIwMDBsLTYxNC40MS02MTQuNC0uMzItLjMyIDIyNi4zMy0yMjUuN00xNTQ4LjAxMyAxMDAwLjA5M2wyMjYuMDA3LTIyNi4wMDYgMjI1Ljk5MiAyMjUuOTkyLTIyNi4wMDYgMjI2LjAwN3oiLz48cGF0aCBkPSJNMTIyOS4yMiA5OTkuODhoLjFMOTk5Ljk5IDc3MC41NSA4MzAuNTEgOTQwLjAzaC0uMDFsLTE5LjQ3IDE5LjQ4LTQwLjE2IDQwLjE3LS4zMi4zMS4zMi4zMyAyMjkuMTIgMjI5LjEzIDIyOS4zMy0yMjkuMzMuMTEtLjEzLS4yMS0uMTEiLz48L2c+PC9zdmc+',
symbol: 'BNB',
explorerurl: 'https://testnet.bscscan.com'
},
{
name: 'xDai Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.xdaichain.com/',
id: 100,
symbol: 'XDAI',
explorerurl: 'https://blockscout.com/xdai/mainnet/'
},
{
name: 'Fuse Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.fuse.io',
id: 122,
symbol: 'FUSE',
explorerurl: 'https://explorer.fuse.io/'
},
{
name: 'HECO Mainnet RPC',
type: 'Mainnet',
rpc: 'https://http-mainnet-node.huobichain.com/',
id: 128,
symbol: 'HT',
explorerurl: 'https://hecoinfo.com/'
},
{
name: 'Matic Mainnet RPC',
type: 'Mainnet',
rpc: 'https://polygon-rpc.com',
id: 137,
symbol: 'MATIC',
explorerurl: 'https://explorer.matic.network/'
},
{
name: 'Fantom Opera Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.ftm.tools/',
id: 250,
symbol: 'FTM',
explorerurl: 'https://ftmscan.com'
},
{
name: 'HECO Testnet RPC',
type: 'Testnet',
rpc: 'https://http-testnet.hecochain.com',
id: 256,
symbol: 'HT',
explorerurl: 'https://testnet.hecoinfo.com/'
},
{
name: 'KCC Mainnet',
type: 'Mainnet',
rpc: 'https://rpc-mainnet.kcc.network',
id: 321,
logo: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMiAzMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzIgMzI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojMDA5M0REO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPGc+Cgk8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPgoJPHBhdGggY2xhc3M9InN0MSIgZD0iTTEzLjUsMTZsNS4yLDUuM0wyMiwxOGMwLjYtMC42LDEuNS0wLjYsMiwwYzAsMCwwLDAsMCwwYzAuNiwwLjYsMC42LDEuNiwwLDIuMmwtNC4zLDQuNAoJCWMtMC42LDAuNi0xLjUsMC42LTIuMSwwYzAsMCwwLDAsMCwwbC02LjItNi40VjIyYzAsMC44LTAuNywxLjUtMS41LDEuNWMtMC44LDAtMS41LTAuNy0xLjUtMS41VjEwYzAtMC44LDAuNy0xLjUsMS41LTEuNQoJCWMwLjgsMCwxLjUsMC43LDEuNSwxLjV2My44bDYuMi02LjRjMC42LTAuNiwxLjUtMC42LDIuMSwwYzAsMCwwLDAsMCwwbDQuMyw0LjRjMC42LDAuNiwwLjYsMS42LDAsMi4yYy0wLjYsMC42LTEuNSwwLjYtMiwwCgkJYzAsMCwwLDAsMCwwbC0zLjMtMy40TDEzLjUsMTZ6IE0xOC43LDE0LjVjMC44LDAsMS41LDAuNywxLjUsMS41cy0wLjcsMS41LTEuNSwxLjVzLTEuNS0wLjctMS41LTEuNQoJCUMxNy4yLDE1LjIsMTcuOSwxNC41LDE4LjcsMTQuNXoiLz4KPC9nPgo8L3N2Zz4K',
symbol: 'KCS',
explorerurl: 'https://scan.kcc.network'
},
{
name: 'KCC Testnet',
type: 'Testnet',
logo: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzMiAzMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzIgMzI7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojMDA5M0REO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRkZGRkZGO30KPC9zdHlsZT4KPGc+Cgk8Y2lyY2xlIGNsYXNzPSJzdDAiIGN4PSIxNiIgY3k9IjE2IiByPSIxNiIvPgoJPHBhdGggY2xhc3M9InN0MSIgZD0iTTEzLjUsMTZsNS4yLDUuM0wyMiwxOGMwLjYtMC42LDEuNS0wLjYsMiwwYzAsMCwwLDAsMCwwYzAuNiwwLjYsMC42LDEuNiwwLDIuMmwtNC4zLDQuNAoJCWMtMC42LDAuNi0xLjUsMC42LTIuMSwwYzAsMCwwLDAsMCwwbC02LjItNi40VjIyYzAsMC44LTAuNywxLjUtMS41LDEuNWMtMC44LDAtMS41LTAuNy0xLjUtMS41VjEwYzAtMC44LDAuNy0xLjUsMS41LTEuNQoJCWMwLjgsMCwxLjUsMC43LDEuNSwxLjV2My44bDYuMi02LjRjMC42LTAuNiwxLjUtMC42LDIuMSwwYzAsMCwwLDAsMCwwbDQuMyw0LjRjMC42LDAuNiwwLjYsMS42LDAsMi4yYy0wLjYsMC42LTEuNSwwLjYtMiwwCgkJYzAsMCwwLDAsMCwwbC0zLjMtMy40TDEzLjUsMTZ6IE0xOC43LDE0LjVjMC44LDAsMS41LDAuNywxLjUsMS41cy0wLjcsMS41LTEuNSwxLjVzLTEuNS0wLjctMS41LTEuNQoJCUMxNy4yLDE1LjIsMTcuOSwxNC41LDE4LjcsMTQuNXoiLz4KPC9nPgo8L3N2Zz4K',
rpc: 'https://rpc-testnet.kcc.network',
id: 322,
symbol: 'tKCS',
explorerurl: 'https://scan-testnet.kcc.network'
},
{
name: 'Moonriver Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.moonriver.moonbeam.network',
id: 1285,
symbol: 'MOVR',
explorerurl: 'https://blockscout.moonriver.moonbeam.network/'
},
{
name: 'Fantom Testnet RPC',
type: 'Testnet',
rpc: 'https://rpc.testnet.fantom.network/',
id: 4002,
symbol: 'FTM',
explorerurl: 'https://testnet.ftmscan.com'
},
{
name: 'IoTeX Mainnet RPC',
type: 'Mainnet',
rpc: 'https://babel-api.mainnet.iotex.io',
id: 4689,
symbol: 'IOTEX',
explorerurl: 'https://iotexscan.io/'
},
{
name: 'Nahmii Mainnet RPC',
type: 'Mainnet',
rpc: 'https://l2.nahmii.io/',
id: 5551,
symbol: 'ETH',
explorerurl: 'https://explorer.nahmii.io/'
},
{
name: 'Nahmii Testnet RPC',
type: 'Testnet',
rpc: 'https://l2.testnet.nahmii.io/',
id: 5553,
symbol: 'ETH',
explorerurl: 'https://explorer.testnet.nahmii.io/'
},
{
name: 'Arbitrum Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.ankr.com/arbitrum',
id: 42161,
symbol: 'ETH',
explorerurl: 'https://arbiscan.io/'
},
{
name: 'Celo Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.ankr.com/celo',
id: 42220,
symbol: 'CELO',
explorerurl: 'https://celoscan.com'
},
{
name: 'Avalanche C Chain Local RPC',
type: 'Testnet',
rpc: 'https://localhost:9650/ext/bc/C/rpc',
id: 43112,
symbol: 'AVAX',
explorerurl: 'https://snowtrace.io'
},
{
name: 'Avalanche FUJI Testnet RPC',
type: 'Testnet',
rpc: 'https://api.avax-test.network/ext/bc/C/rpc',
id: 43113,
symbol: 'AVAX',
explorerurl: 'https://testnet.explorer.avax.network/'
},
{
name: 'Avalanche C Chain Mainnet RPC',
type: 'Mainnet',
rpc: 'https://rpc.ankr.com/avalanche',
id: 43114,
symbol: 'AVAX',
explorerurl: 'https://snowtrace.io'
},
{
name: 'Matic Testnet RPC',
type: 'Testnet',
rpc: 'https://rpc-mumbai.maticvigil.com',
id: 80001,
symbol: 'MATIC',
explorerurl: 'https://mumbai.polygonscan.com/'
},
{
name: 'Harmony Mainnet RPC',
type: 'Mainnet',
rpc: 'https://api.harmony.one/',
id: 1666600000,
symbol: 'ONE',
explorerurl: 'https://explorer.harmony.one'
},
{
name: 'Harmony Testnet RPC',
type: 'Testnet',
rpc: 'https://api.s0.b.hmny.io/',
id: 1666700000,
symbol: 'ONE',
explorerurl: 'https://explorer.harmony.one'
}
]

View File

@ -0,0 +1,27 @@
/**
* @singleton
* class Test {}
* new Test() === new Test() // returns `true`
* const TestSingleton = singleton(Test)
* new TestSingleton() === new TestSingleton() //returns 'true'
*/
// eslint-disable-next-line symbol-description
export const SINGLETON_KEY = Symbol()
export type Singleton<T extends new (...args: any[]) => any> = T & {
[SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never
}
export const singleton = <T extends new (...args: any[]) => any>(classTarget: T) =>
new Proxy(classTarget, {
construct(target: Singleton<T>, argumentsList, newTarget) {
// Skip proxy for children
if (target.prototype !== newTarget.prototype) {
return Reflect.construct(target, argumentsList, newTarget)
}
if (!target[SINGLETON_KEY]) {
target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)
}
return target[SINGLETON_KEY]
}
})

235
src/index.ts Normal file
View File

@ -0,0 +1,235 @@
import { singleton } from "./decorator/singleton.decorator";
import Web3 from 'web3';
import 'whatwg-fetch'
import { ZError } from "./common/ZError";
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, initAccount } from "./data/DataModel";
import { DataManage } from "./manage/DataManage";
import { WALLET_STORAGE_KEY_NAME } from "./config/constants";
import { DEFALUT_TOKENS } from "./config/chain_config";
import { recoverTypedSignature, signTypedData, SignTypedDataVersion } from "@metamask/eth-sig-util";
var global =
(typeof globalThis !== 'undefined' && globalThis) ||
(typeof self !== 'undefined' && self) ||
(typeof global !== 'undefined' && global) ||
{}
declare global {
interface Window {
jc: {
wallet: JCWallet;
}
}
}
export interface IChainData {
name: string,
type: string,
rpc: string,
id: number,
symbol: string,
explorerurl: string
}
@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 mainHandlers = createWalletEvents()
public uiHandlers = createWalletEvents()
private dataManage = new DataManage()
public data: IAccount[] = []
public iconType = 'jazz'
private accountIndex = 0
constructor() {
// this.web3 = new Web3('https://rpc-mainnet.kcc.network')
this.web3 = new Web3('https://rpc-testnet.kcc.network')
this.erc20Standard = new ERC20Standard(this.web3);
this.erc721Standard = new ERC721Standard(this.web3);
this.wallet = this.web3.eth.accounts.wallet.load(this.password, WALLET_STORAGE_KEY_NAME)
if (!this.wallet || this.wallet.length === 0) {
let key = '0xa6c4354fb93a55fb67117969a12465209395ec31089fea9e6e061f873b87a473'
this.wallet.add(key);
this.web3.eth.accounts.wallet.save(this.password, WALLET_STORAGE_KEY_NAME);
}
this.data = this.dataManage.loadData()
window.jc = { wallet: this };
}
public init({chains}: {chains: number[]}) {
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
}
}
}
}
}
get currentChain() {
return this._currentChain
}
updateCurrentChain(chainId: number) {
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')
}
updateListType(type: string) {
this.mainHandlers.emit(WALLET_TOKEN_TYPE_CHANGE, type)
}
get chainList() {
return [...this.chainMap.values()]
}
public saveLocal() {
}
public loadLocal() {
}
public saveRemove() {
}
public loadRemote() {
}
public currentAccount() {
return this.wallet[this.accountIndex];
}
get currentAccountData() {
let address = this.currentAccount().address;
const chain = this.currentChain.id
let data = this.data.find(o => o.address === address)
if (!data) {
let accountName = `Account${this.wallet.length}`
data = initAccount(address, chain, accountName)
this.data.push(data)
} else {
if (!data.tokenData[chain]) {
let tokens = DEFALUT_TOKENS[chain]
data.tokenData[chain] = {
tokens,
heros: [],
weapons: [],
chips: []
}
}
}
this.dataManage.saveData(this.data)
return data
}
get accounts() {
return this.data
}
public createAccount() {
let account = this.web3.eth.accounts.create()
this.wallet.add(account)
this.wallet.save(this.password, WALLET_STORAGE_KEY_NAME)
this.accountIndex = this.wallet.length - 1
this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, account.address)
}
public selectAccount(address: string) {
let index = 0
for (let i = 0, l = this.wallet.length; i < l ; i ++) {
if (this.wallet[i].address === address) {
index = i
break
}
}
if (index !== this.accountIndex && index < this.wallet.length) {
this.accountIndex = index
this.mainHandlers.emit(WALLET_ACCOUNT_CHANGE, this.wallet[index].address)
}
}
public importAccount(privateKey: string) {
let account = this.web3.eth.accounts.privateKeyToAccount(privateKey)
if (this.wallet[account.address]) {
return false
}
this.wallet.add(account);
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 true
}
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 ZError(10, '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 recoverTypedSignatureV4(signObj: any, signature: string) {
return recoverTypedSignature({
data: signObj,
signature,
version: SignTypedDataVersion.V4
})
}
}
// 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 './config/chain_config';

9
src/lib/Http.ts Normal file
View File

@ -0,0 +1,9 @@
import 'whatwg-fetch'
export async function GET(url: string) {
return fetch(url)
}
export async function GET_JSON(url: string) {
return fetch(url).then(res => {return res.json()})
}

211
src/lib/mersenne-twister.ts Normal file
View File

@ -0,0 +1,211 @@
/*
https://github.com/banksean wrapped Makoto Matsumoto and Takuji Nishimura's code in a namespace
so it's better encapsulated. Now you can have multiple random number generators
and they won't stomp all over eachother's state.
If you want to use this as a substitute for Math.random(), use the random()
method like so:
var m = new MersenneTwister();
var randomNumber = m.random();
You can also call the other genrand_{foo}() methods on the instance.
If you want to use a specific seed in order to get a repeatable random
sequence, pass an integer into the constructor:
var m = new MersenneTwister(123);
and that will always produce the same random sequence.
Sean McCullough (banksean@gmail.com)
*/
/*
A C-program for MT19937, with initialization improved 2002/1/26.
Coded by Takuji Nishimura and Makoto Matsumoto.
Before using, initialize the state by using init_seed(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
*/
export default class MersenneTwister {
N = 624;
M = 397;
MATRIX_A = 0x9908b0df; /* constant vector a */
UPPER_MASK = 0x80000000; /* most significant w-r bits */
LOWER_MASK = 0x7fffffff; /* least significant r bits */
mt = new Array(this.N); /* the array for the state vector */
mti=this.N+1; /* mti==N+1 means mt[N] is not initialized */
constructor(seed) {
if (seed == undefined) {
seed = new Date().getTime();
}
/* Period parameters */
if (seed.constructor == Array) {
this.init_by_array(seed, seed.length);
}
else {
this.init_seed(seed);
}
}
/* initializes mt[N] with a seed */
/* origin name init_genrand */
init_seed(s1: number) {
this.mt[0] = s1 >>> 0;
for (this.mti=1; this.mti<this.N; this.mti++) {
var s = this.mt[this.mti-1] ^ (this.mt[this.mti-1] >>> 30);
this.mt[this.mti] = (((((s & 0xffff0000) >>> 16) * 1812433253) << 16) + (s & 0x0000ffff) * 1812433253)
+ this.mti;
/* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
/* In the previous versions, MSBs of the seed affect */
/* only MSBs of the array mt[]. */
/* 2002/01/09 modified by Makoto Matsumoto */
this.mt[this.mti] >>>= 0;
/* for >32 bit machines */
}
}
/* initialize by an array with array-length */
/* init_key is the array for initializing keys */
/* key_length is its length */
/* slight change for C++, 2004/2/26 */
init_by_array(init_key, key_length) {
var i, j, k;
this.init_seed(19650218);
i=1; j=0;
k = (this.N>key_length ? this.N : key_length);
for (; k; k--) {
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30)
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1664525) << 16) + ((s & 0x0000ffff) * 1664525)))
+ init_key[j] + j; /* non linear */
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
i++; j++;
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
if (j>=key_length) j=0;
}
for (k=this.N-1; k; k--) {
var s = this.mt[i-1] ^ (this.mt[i-1] >>> 30);
this.mt[i] = (this.mt[i] ^ (((((s & 0xffff0000) >>> 16) * 1566083941) << 16) + (s & 0x0000ffff) * 1566083941))
- i; /* non linear */
this.mt[i] >>>= 0; /* for WORDSIZE > 32 machines */
i++;
if (i>=this.N) { this.mt[0] = this.mt[this.N-1]; i=1; }
}
this.mt[0] = 0x80000000; /* MSB is 1; assuring non-zero initial array */
}
/* generates a random number on [0,0xffffffff]-interval */
/* origin name genrand_int32 */
random_int() {
var y;
var mag01 = new Array(0x0, this.MATRIX_A);
/* mag01[x] = x * MATRIX_A for x=0,1 */
if (this.mti >= this.N) { /* generate N words at one time */
var kk;
if (this.mti == this.N+1) /* if init_seed() has not been called, */
this.init_seed(5489); /* a default initial seed is used */
for (kk=0;kk<this.N-this.M;kk++) {
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
this.mt[kk] = this.mt[kk+this.M] ^ (y >>> 1) ^ mag01[y & 0x1];
}
for (;kk<this.N-1;kk++) {
y = (this.mt[kk]&this.UPPER_MASK)|(this.mt[kk+1]&this.LOWER_MASK);
this.mt[kk] = this.mt[kk+(this.M-this.N)] ^ (y >>> 1) ^ mag01[y & 0x1];
}
y = (this.mt[this.N-1]&this.UPPER_MASK)|(this.mt[0]&this.LOWER_MASK);
this.mt[this.N-1] = this.mt[this.M-1] ^ (y >>> 1) ^ mag01[y & 0x1];
this.mti = 0;
}
y = this.mt[this.mti++];
/* Tempering */
y ^= (y >>> 11);
y ^= (y << 7) & 0x9d2c5680;
y ^= (y << 15) & 0xefc60000;
y ^= (y >>> 18);
return y >>> 0;
}
/* generates a random number on [0,0x7fffffff]-interval */
/* origin name genrand_int31 */
random_int31() {
return (this.random_int()>>>1);
}
/* generates a random number on [0,1]-real-interval */
/* origin name genrand_real1 */
random_incl() {
return this.random_int()*(1.0/4294967295.0);
/* divided by 2^32-1 */
}
/* generates a random number on [0,1)-real-interval */
random() {
return this.random_int()*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on (0,1)-real-interval */
/* origin name genrand_real3 */
random_excl() {
return (this.random_int() + 0.5)*(1.0/4294967296.0);
/* divided by 2^32 */
}
/* generates a random number on [0,1) with 53-bit resolution*/
/* origin name genrand_res53 */
random_long() {
var a=this.random_int()>>>5, b=this.random_int()>>>6;
return(a*67108864.0+b)*(1.0/9007199254740992.0);
}
}

24
src/manage/DataManage.ts Normal file
View File

@ -0,0 +1,24 @@
import { IAccount } from "../data/DataModel";
import { singleton } from "../decorator/singleton.decorator";
const LOCAL_ACCOUNT_DATAS = 'local_account_datas'
@singleton
export class DataManage{
public loadData(){
const dataStr = localStorage.getItem(LOCAL_ACCOUNT_DATAS)
let result: IAccount[] = []
if (dataStr) {
try {
result = JSON.parse(dataStr)
} catch (err) {
console.log('load local data error')
}
}
return result
}
public saveData(datas: IAccount[]) {
const dataStr = JSON.stringify(datas)
localStorage.setItem(LOCAL_ACCOUNT_DATAS, dataStr)
}
}

View File

@ -0,0 +1,172 @@
import Web3 from "web3";
import { abiERC20 } from "../abis/abiERC20";
import { BN, toUtf8 } from 'ethereumjs-util';
export class ERC20Standard {
private web3: Web3;
constructor(web3: Web3) {
this.web3 = web3;
}
/**
* Get balance or count for current account on specific asset contract.
*
* @param address - Asset ERC20 contract address.
* @param selectedAddress - Current account public address.
* @returns Promise resolving to BN object containing balance for current account on specific asset contract.
*/
async getBalanceOf(
address: string,
selectedAddress: string
): Promise<BN> {
const contract = new this.web3.eth.Contract(abiERC20, address);
return new Promise<BN>((resolve, reject) => {
contract.methods
.balanceOf(selectedAddress)
.call(
{ from: selectedAddress },
(error: Error, result: BN) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
}
);
});
}
/**
* Query for the decimals for a given ERC20 asset.
*
* @param address - ERC20 asset contract string.
* @returns Promise resolving to the 'decimals'.
*/
async getTokenDecimals(address: string): Promise<string> {
const contract = new this.web3.eth.Contract(abiERC20, address);
return new Promise<string>((resolve, reject) => {
contract.methods
.decimals()
.call((error: Error, result: BN | string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result.toString());
});
});
}
/**
* Query for symbol for a given ERC20 asset.
*
* @param address - ERC20 asset contract address.
* @returns Promise resolving to the 'symbol'.
*/
async getTokenSymbol(address: string): Promise<string> {
const contract = new this.web3.eth.Contract(abiERC20, address);
return new Promise<string>((resolve, reject) => {
contract.methods
.symbol()
.call((error: Error, result: BN | string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result.toString());
});
});
// Signature for calling `symbol()`
// const payload = { to: address, data: '0x95d89b41' };
// return new Promise<string>((resolve, reject) => {
// this.web3.eth.call(payload, undefined, (error: Error, result: string) => {
// /* istanbul ignore if */
// if (error) {
// reject(error);
// return;
// }
// // Parse as string
// try {
// const decoded = Web3.utils.hexToUtf8(result);
// if (decoded) {
// console.log('decoded')
// resolve(decoded);
// return;
// }
// } catch {
// // Ignore error
// }
// // Parse as bytes
// try {
// const utf8 = Web3.utils.toUtf8(result);
// console.log('utf8')
// resolve(utf8);
// return;
// } catch {
// // Ignore error
// }
// reject(new Error('Failed to parse token symbol'));
// });
// });
}
/**
* Query if a contract implements an interface.
*
* @param address - Asset contract address.
* @param userAddress - The public address for the currently active user's account.
* @returns Promise resolving an object containing the standard, decimals, symbol and balance of the given contract/userAddress pair.
*/
async getDetails(
address: string,
userAddress?: string
): Promise<{
standard: string;
symbol: string | undefined;
decimals: string | undefined;
balance: BN | undefined;
}> {
const [decimals, symbol] = await Promise.all([
this.getTokenDecimals(address),
this.getTokenSymbol(address),
]);
let balance;
if (userAddress) {
balance = await this.getBalanceOf(address, userAddress);
}
return {
decimals,
symbol,
balance,
standard: "ERC20",
};
}
async transfer({
address,
from,
to,
amount,
gas,
}: {
address: string;
from: string;
to: string;
amount: number | string;
gas?: number;
}) {
const contract = new this.web3.eth.Contract(abiERC20, address);
const amountBN = Web3.utils.toBN(Web3.utils.toWei(amount + ""));
return contract.methods.transfer(to, amountBN).send({
from,
gas: gas || 1000000,
});
}
}

View File

@ -0,0 +1,288 @@
import Web3 from "web3";
import { abiERC721 } from "../abis/abiERC721";
import { timeoutFetch } from '../util/net.util';
import { getFormattedIpfsUrl } from '../util/wallet.util';
export const ERC721 = 'ERC721';
export const ERC721_INTERFACE_ID = '0x80ac58cd';
export const ERC721_METADATA_INTERFACE_ID = '0x5b5e139f';
export const ERC721_ENUMERABLE_INTERFACE_ID = '0x780e9d63';
export class ERC721Standard {
private web3: Web3;
constructor(web3: Web3) {
this.web3 = web3;
}
/**
* Query if contract implements ERC721Metadata interface.
*
* @param address - ERC721 asset contract address.
* @returns Promise resolving to whether the contract implements ERC721Metadata interface.
*/
contractSupportsMetadataInterface = async (
address: string,
): Promise<boolean> => {
return this.contractSupportsInterface(
address,
ERC721_METADATA_INTERFACE_ID,
);
};
/**
* Query if contract implements ERC721Enumerable interface.
*
* @param address - ERC721 asset contract address.
* @returns Promise resolving to whether the contract implements ERC721Enumerable interface.
*/
contractSupportsEnumerableInterface = async (
address: string,
): Promise<boolean> => {
return this.contractSupportsInterface(
address,
ERC721_ENUMERABLE_INTERFACE_ID,
);
};
/**
* Query if contract implements ERC721 interface.
*
* @param address - ERC721 asset contract address.
* @returns Promise resolving to whether the contract implements ERC721 interface.
*/
contractSupportsBase721Interface = async (
address: string,
): Promise<boolean> => {
return this.contractSupportsInterface(address, ERC721_INTERFACE_ID);
};
/**
* Enumerate assets assigned to an owner.
*
* @param address - ERC721 asset contract address.
* @param selectedAddress - Current account public address.
* @param index - A collectible counter less than `balanceOf(selectedAddress)`.
* @returns Promise resolving to token identifier for the 'index'th asset assigned to 'selectedAddress'.
*/
getCollectibleTokenId = async (
address: string,
selectedAddress: string,
index: number,
): Promise<string> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<string>((resolve, reject) => {
contract.methods.tokenOfOwnerByIndex(
selectedAddress,
index).call((error: Error, result: string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
},
);
});
};
getBalance = async (
address: string,
selectedAddress: string
): Promise<number> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<number>((resolve, reject) => {
contract.methods.balanceOf(
selectedAddress).call((error: Error, result: number) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
},
);
});
};
/**
* Query for tokenURI for a given asset.
*
* @param address - ERC721 asset contract address.
* @param tokenId - ERC721 asset identifier.
* @returns Promise resolving to the 'tokenURI'.
*/
getTokenURI = async (address: string, tokenId: string): Promise<string> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
const supportsMetadata = await this.contractSupportsMetadataInterface(
address,
);
if (!supportsMetadata) {
throw new Error('Contract does not support ERC721 metadata interface.');
}
return new Promise<string>((resolve, reject) => {
contract.methods.tokenURI(tokenId).call( (error: Error, result: string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
});
});
};
/**
* Query for name for a given asset.
*
* @param address - ERC721 asset contract address.
* @returns Promise resolving to the 'name'.
*/
getAssetName = async (address: string): Promise<string> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<string>((resolve, reject) => {
contract.methods.name().call((error: Error, result: string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
});
});
};
/**
* Query for symbol for a given asset.
*
* @param address - ERC721 asset contract address.
* @returns Promise resolving to the 'symbol'.
*/
getAssetSymbol = async (address: string): Promise<string> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<string>((resolve, reject) => {
contract.methods.symbol().call((error: Error, result: string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
});
});
};
/**
* Query for owner for a given ERC721 asset.
*
* @param address - ERC721 asset contract address.
* @param tokenId - ERC721 asset identifier.
* @returns Promise resolving to the owner address.
*/
async getOwnerOf(address: string, tokenId: string): Promise<string> {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<string>((resolve, reject) => {
contract.methods.ownerOf(tokenId).call( (error: Error, result: string) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
});
});
}
/**
* Query if a contract implements an interface.
*
* @param address - Asset contract address.
* @param interfaceId - Interface identifier.
* @returns Promise resolving to whether the contract implements `interfaceID`.
*/
private contractSupportsInterface = async (
address: string,
interfaceId: string,
): Promise<boolean> => {
const contract = new this.web3.eth.Contract(abiERC721, address);
return new Promise<boolean>((resolve, reject) => {
contract.methods.supportsInterface(
interfaceId).call((error: Error, result: boolean) => {
/* istanbul ignore if */
if (error) {
reject(error);
return;
}
resolve(result);
},
);
});
};
/**
* Query if a contract implements an interface.
*
* @param address - Asset contract address.
* @param ipfsGateway - The user's preferred IPFS gateway.
* @param tokenId - tokenId of a given token in the contract.
* @returns Promise resolving an object containing the standard, tokenURI, symbol and name of the given contract/tokenId pair.
*/
getDetails = async (
address: string,
ipfsGateway: string,
tokenId?: string,
): Promise<{
standard: string;
tokenURI: string | undefined;
symbol: string | undefined;
name: string | undefined;
image: string | undefined;
}> => {
const isERC721 = await this.contractSupportsBase721Interface(address);
if (!isERC721) {
throw new Error("This isn't a valid ERC721 contract");
}
let tokenURI, image, symbol, name;
// TODO upgrade to use Promise.allSettled for name/symbol when we can refactor to use es2020 in tsconfig
try {
symbol = await this.getAssetSymbol(address);
} catch {
// ignore
}
try {
name = await this.getAssetName(address);
} catch {
// ignore
}
if (tokenId) {
try {
tokenURI = await this.getTokenURI(address, tokenId);
if (tokenURI.startsWith('ipfs://')) {
tokenURI = getFormattedIpfsUrl(ipfsGateway, tokenURI, true);
}
const response = await timeoutFetch(tokenURI);
const object = await response.json();
image = object ? object.image : ''
if (image.startsWith('ipfs://')) {
image = getFormattedIpfsUrl(ipfsGateway, image, true);
}
} catch {
// ignore
}
}
return {
standard: ERC721,
tokenURI,
symbol,
name,
image,
};
};
}

45
src/util/chain.util.ts Normal file
View File

@ -0,0 +1,45 @@
/**
* change price with customer decimals to bigNum with 18 decimals
* @param {number} price
* @param {number} decimals
* @return {string}
*/
export function parsePrice(price: number, decimals: number) {
const n = 19 - decimals
return price + new Array(n).join('0')
}
/**
* format price with customer decimals to string for display
* @param {number | string} price
* @param {number} decimals
* @param {number} fixed
* @return {number | string}
*/
export function formatPrice(price: number|string, decimals?: number, fixed = 2) {
if (!decimals) {
return price
}
let str = price + ''
const length = str.length
str = str.padStart(decimals, '0')
if (decimals >= length) {
str = '0.' + str
} else {
const pos = length - decimals
str = str.slice(0, pos) + '.' + str.slice(pos)
}
str = str.slice(0, str.lastIndexOf('.') + fixed + 1)
return str
// return str.replace(/0+$/, '').replace(/\.+$/, '')
}
/**
* number to hex string
* @param {number} chainId
* @return {string}
*/
export function toHexChainId(chainId: number) {
return '0x' + chainId.toString(16)
}

126
src/util/net.util.ts Normal file
View File

@ -0,0 +1,126 @@
import 'whatwg-fetch'
const TIMEOUT_ERROR = new Error("timeout");
const hexRe = /^[0-9A-Fa-f]+$/gu;
/**
* Execute fetch and verify that the response was successful.
*
* @param request - Request information.
* @param options - Fetch options.
* @returns The fetch response.
*/
export async function successfulFetch(request: string, options?: RequestInit) {
const response = await fetch(request, options);
if (!response.ok) {
throw new Error(
`Fetch failed with status '${response.status}' for request '${request}'`
);
}
return response;
}
/**
* Execute fetch and return object response.
*
* @param request - The request information.
* @param options - The fetch options.
* @returns The fetch response JSON data.
*/
export async function handleFetch(request: string, options?: RequestInit) {
const response = await successfulFetch(request, options);
const object = await response.json();
return object;
}
/**
* Execute fetch and return object response, log if known error thrown, otherwise rethrow error.
*
* @param request - the request options object
* @param request.url - The request url to query.
* @param request.options - The fetch options.
* @param request.timeout - Timeout to fail request
* @param request.errorCodesToCatch - array of error codes for errors we want to catch in a particular context
* @returns The fetch response JSON data or undefined (if error occurs).
*/
export async function fetchWithErrorHandling({
url,
options,
timeout,
errorCodesToCatch,
}: {
url: string;
options?: RequestInit;
timeout?: number;
errorCodesToCatch?: number[];
}) {
let result;
try {
if (timeout) {
result = Promise.race([
await handleFetch(url, options),
new Promise<Response>((_, reject) =>
setTimeout(() => {
reject(TIMEOUT_ERROR);
}, timeout)
),
]);
} else {
result = await handleFetch(url, options);
}
} catch (e) {
logOrRethrowError(e, errorCodesToCatch);
}
return result;
}
/**
* Fetch that fails after timeout.
*
* @param url - Url to fetch.
* @param options - Options to send with the request.
* @param timeout - Timeout to fail request.
* @returns Promise resolving the request.
*/
export async function timeoutFetch(
url: string,
options?: RequestInit,
timeout = 500
): Promise<Response> {
return Promise.race([
successfulFetch(url, options),
new Promise<Response>((_, reject) =>
setTimeout(() => {
reject(TIMEOUT_ERROR);
}, timeout)
),
]);
}
/**
* Utility method to log if error is a common fetch error and otherwise rethrow it.
*
* @param error - Caught error that we should either rethrow or log to console
* @param codesToCatch - array of error codes for errors we want to catch and log in a particular context
*/
function logOrRethrowError(error: any, codesToCatch: number[] = []) {
if (!error) {
return;
}
const includesErrorCodeToCatch = codesToCatch.some((code) =>
error.message.includes(`Fetch failed with status '${code}'`)
);
if (
error instanceof Error &&
(includesErrorCodeToCatch ||
error.message.includes("Failed to fetch") ||
error === TIMEOUT_ERROR)
) {
console.error(error);
} else {
throw error;
}
}

216
src/util/number.util.ts Normal file
View File

@ -0,0 +1,216 @@
import Web3 from "web3";
import { BN } from 'ethereumjs-util';
/**
* Converts some token minimal unit to render format string, showing 5 decimals
*
* @param {Number|String|BN} tokenValue - Token value to convert
* @param {Number} decimals - Token decimals to convert
* @param {Number} decimalsToShow - Decimals to 5
* @returns {String} - Number of token minimal unit, in render format
* If value is less than 5 precision decimals will show '< 0.00001'
*/
export function renderFromTokenMinimalUnit(
tokenValue,
decimals,
decimalsToShow = 5
) {
const minimalUnit = fromTokenMinimalUnit(tokenValue || 0, decimals);
const minimalUnitNumber = parseFloat(minimalUnit);
let renderMinimalUnit;
if (minimalUnitNumber < 0.00001 && minimalUnitNumber > 0) {
renderMinimalUnit = "< 0.00001";
} else {
const base = Math.pow(10, decimalsToShow);
renderMinimalUnit = (
Math.round(minimalUnitNumber * base) / base
).toString();
}
return renderMinimalUnit;
}
/**
* Converts token minimal unit to readable string value
*
* @param {number|string|Object} minimalInput - Token minimal unit to convert
* @param {string} decimals - Token decimals to convert
* @returns {string} - String containing the new number
*/
export function fromTokenMinimalUnit(minimalInput, decimals) {
minimalInput = addHexPrefix(Number(minimalInput).toString(16));
let minimal = safeNumberToBN(minimalInput);
const negative = minimal.lt(new BN(0));
const base = Web3.utils.toBN(Math.pow(10, decimals).toString());
if (negative) {
minimal = minimal.mul(new BN(-1));
}
let fraction = minimal.mod(base).toString(10);
while (fraction.length < decimals) {
fraction = "0" + fraction;
}
fraction = fraction.match(/^([0-9]*[1-9]|0)(0*)/)[1];
const whole = minimal.div(base).toString(10);
let value = "" + whole + (fraction === "0" ? "" : "." + fraction);
if (negative) {
value = "-" + value;
}
return value;
}
/**
* Converts wei to render format string, showing 5 decimals
*
* @param {Number|String|BN} value - Wei to convert
* @param {Number} decimalsToShow - Decimals to 5
* @returns {String} - Number of token minimal unit, in render format
* If value is less than 5 precision decimals will show '< 0.00001'
*/
export function renderFromWei(value, decimalsToShow = 5) {
let renderWei = '0';
// avoid undefined
if (value) {
const wei = Web3.utils.fromWei(value);
const weiNumber = parseFloat(wei);
if (weiNumber < 0.00001 && weiNumber > 0) {
renderWei = '< 0.00001';
} else {
const base = Math.pow(10, decimalsToShow);
renderWei = (Math.round(weiNumber * base) / base).toString();
}
}
return renderWei;
}
/**
* Converts token BN value to hex string number to be sent
*
* @param {Object} value - BN instance to convert
* @param {number} decimals - Decimals to be considered on the conversion
* @returns {string} - String of the hex token value
*/
export function calcTokenValueToSend(value, decimals) {
return value ? (value * Math.pow(10, decimals)).toString(16) : 0;
}
/**
* Determines if a string is a valid decimal
*
* @param {string} value - String to check
* @returns {boolean} - True if the string is a valid decimal
*/
export function isDecimal(value) {
return (
Number.isFinite(parseFloat(value)) &&
!Number.isNaN(parseFloat(value)) &&
!isNaN(+value)
);
}
/**
* Creates a BN object from a string
*
* @param {string} value - Some numeric value represented as a string
* @returns {Object} - BN instance
*/
export function toBN(value) {
return Web3.utils.toBN(value);
}
/**
* Prefixes a hex string with '0x' or '-0x' and returns it. Idempotent.
*
* @param {string} str - The string to prefix.
* @returns {string} The prefixed string.
*/
export const addHexPrefix = (str: string) => {
if (typeof str !== "string" || str.match(/^-?0x/u)) {
return str;
}
if (str.match(/^-?0X/u)) {
return str.replace("0X", "0x");
}
if (str.startsWith("-")) {
return str.replace("-", "-0x");
}
return `0x${str}`;
};
/**
* Wraps 'numberToBN' method to avoid potential undefined and decimal values
*
* @param {number|string} value - number
* @returns {Object} - The converted value as BN instance
*/
export function safeNumberToBN(value: number | string) {
const safeValue = fastSplit(value.toString()) || "0";
return numberToBN(safeValue);
}
/**
* Performs a fast string split and returns the first item of the string based on the divider provided
*
* @param {number|string} value - number/string to be splitted
* @param {string} divider - string value to use to split the string (default '.')
* @returns {string} - the selected splitted element
*/
export function fastSplit(value, divider = ".") {
value += "";
const [from, to] = [value.indexOf(divider), 0];
return value.substring(from, to) || value;
}
export function stripHexPrefix(str: string) {
if (typeof str !== "string") {
return str;
}
return str.slice(0, 2) === "0x" ? str.slice(2) : str;
}
export function numberToBN(arg) {
if (typeof arg === "string" || typeof arg === "number") {
var multiplier = Web3.utils.toBN(1); // eslint-disable-line
var formattedString = String(arg).toLowerCase().trim();
var isHexPrefixed =
formattedString.substr(0, 2) === "0x" ||
formattedString.substr(0, 3) === "-0x";
var stringArg = stripHexPrefix(formattedString); // eslint-disable-line
if (stringArg.substr(0, 1) === "-") {
stringArg = stripHexPrefix(stringArg.slice(1));
multiplier = Web3.utils.toBN(-1);
}
stringArg = stringArg === "" ? "0" : stringArg;
if (
(!stringArg.match(/^-?[0-9]+$/) && stringArg.match(/^[0-9A-Fa-f]+$/)) ||
stringArg.match(/^[a-fA-F]+$/) ||
(isHexPrefixed === true && stringArg.match(/^[0-9A-Fa-f]+$/))
) {
return Web3.utils.toBN(stringArg).mul(multiplier);
}
if (
(stringArg.match(/^-?[0-9]+$/) || stringArg === "") &&
isHexPrefixed === false
) {
return Web3.utils.toBN(stringArg).mul(multiplier);
}
} else if (typeof arg === "object" && arg.toString && !arg.pop && !arg.push) {
if (
arg.toString(10).match(/^-?[0-9]+$/) &&
(arg.mul || arg.dividedToIntegerBy)
) {
return Web3.utils.toBN(arg.toString(10));
}
}
throw new Error(
"[number-to-bn] while converting number " +
JSON.stringify(arg) +
" to BN.js instance, error: invalid number value. Value must be an integer, hex string, BN or BigNumber instance. Note, decimals are not supported."
);
}

119
src/util/wallet.util.ts Normal file
View File

@ -0,0 +1,119 @@
import { renderFromTokenMinimalUnit } from "./number.util";
/**
* Removes IPFS protocol prefix from input string.
*
* @param ipfsUrl - An IPFS url (e.g. ipfs://{content id})
* @returns IPFS content identifier and (possibly) path in a string
* @throws Will throw if the url passed is not IPFS.
*/
export function removeIpfsProtocolPrefix(ipfsUrl: string) {
if (ipfsUrl.startsWith("ipfs://ipfs/")) {
return ipfsUrl.replace("ipfs://ipfs/", "");
} else if (ipfsUrl.startsWith("ipfs://")) {
return ipfsUrl.replace("ipfs://", "");
}
// this method should not be used with non-ipfs urls (i.e. startsWith('ipfs://') === true)
throw new Error("this method should not be used with non ipfs urls");
}
/**
* Extracts content identifier and path from an input string.
*
* @param ipfsUrl - An IPFS URL minus the IPFS protocol prefix
* @returns IFPS content identifier (cid) and sub path as string.
* @throws Will throw if the url passed is not ipfs.
*/
export function getIpfsCIDv1AndPath(ipfsUrl: string): {
cid: string;
path?: string;
} {
const url = removeIpfsProtocolPrefix(ipfsUrl);
// check if there is a path
// (CID is everything preceding first forward slash, path is everything after)
const index = url.indexOf("/");
const cid = index !== -1 ? url.substring(0, index) : url;
const path = index !== -1 ? url.substring(index) : undefined;
//TODO:
// We want to ensure that the CID is v1 (https://docs.ipfs.io/concepts/content-addressing/#identifier-formats)
// because most cid v0s appear to be incompatible with IPFS subdomains
// return {
// cid: CID.parse(cid).toV1().toString(),
// path,
// };
return {
cid,
path,
};
}
/**
* Adds URL protocol prefix to input URL string if missing.
*
* @param urlString - An IPFS URL.
* @returns A URL with a https:// prepended.
*/
export function addUrlProtocolPrefix(urlString: string): string {
if (!urlString.match(/(^http:\/\/)|(^https:\/\/)/u)) {
return `https://${urlString}`;
}
return urlString;
}
/**
* Formats URL correctly for use retrieving assets hosted on IPFS.
*
* @param ipfsGateway - The users preferred IPFS gateway (full URL or just host).
* @param ipfsUrl - The IFPS URL pointed at the asset.
* @param subdomainSupported - Boolean indicating whether the URL should be formatted with subdomains or not.
* @returns A formatted URL, with the user's preferred IPFS gateway and format (subdomain or not), pointing to an asset hosted on IPFS.
*/
export function getFormattedIpfsUrl(
ipfsGateway: string,
ipfsUrl: string,
subdomainSupported: boolean
): string {
const { host, protocol, origin } = new URL(addUrlProtocolPrefix(ipfsGateway));
if (subdomainSupported) {
const { cid, path } = getIpfsCIDv1AndPath(ipfsUrl);
return `${protocol}//${cid}.ipfs.${host}${path || ""}`;
}
const cidAndPath = removeIpfsProtocolPrefix(ipfsUrl);
return `${origin}/ipfs/${cidAndPath}`;
}
/**
* Returns whether the given code corresponds to a smart contract.
*
* @param code - The potential smart contract code.
* @returns Whether the code was smart contract code or not.
*/
export function isSmartContractCode(code: string) {
/* istanbul ignore if */
if (!code) {
return false;
}
// Geth will return '0x', and ganache-core v2.2.1 will return '0x0'
const smartContractCode = code !== '0x' && code !== '0x0';
return smartContractCode;
}
export function formatAddress(address: string) {
if (address.length >= 10) {
return address.substring(0, 6) + '...' + address.substring(address.length - 4)
} else if (address.length > 0 && address.length < 10) {
return address
} else {
return ''
}
}
export function formatMoney(balance: number | string, symbol: string) {
if (balance === '-') {
return `- ${symbol}`;
}
let money = renderFromTokenMinimalUnit(balance, 18, 4)
return `${money} ${symbol}`;
}

24
tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"module": "CommonJS",
"rootDir": "src",
"outDir": "build",
"lib": [ "dom", "es5", "es2015", "es2015.promise" ],
"target": "es5",
"sourceMap": true,
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true,
"downlevelIteration": true
},
"exclude": [
"node_modules",
"library",
"local",
"temp",
"build",
"settings",
"dist"
]
}

27
webpack.config.js Normal file
View File

@ -0,0 +1,27 @@
const path = require("path");
module.exports = {
mode: "production",
// mode: 'development',
entry: "./build/index.js",
// devtool: "inline-source-map",
target: "web",
// module: {
// rules: [
// {
// test: /\.ts?$/,
// use: "ts-loader"
// },
// ],
// },
// resolve: {
// extensions: [".tsx", ".ts", ".js"],
// },
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
library: "jcwallet",
libraryTarget: "commonjs2",
// libraryTarget: "window",
},
};

5004
yarn.lock Normal file

File diff suppressed because it is too large Load Diff