├── .eslintrc.js ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.js ├── package.json ├── rollup.config.js ├── src ├── api │ └── index.ts ├── config │ └── index.ts ├── constants │ ├── abi │ │ └── erc20.ts │ └── index.ts ├── index.ts ├── lib │ ├── arweave.ts │ ├── ethereum.ts │ ├── hashPersonalMessage.ts │ ├── interface.ts │ ├── popup.ts │ ├── sign.ts │ └── smartAccount.ts ├── types │ ├── api.ts │ └── index.ts └── utils │ ├── check.ts │ ├── errors.ts │ └── util.ts ├── test ├── +balance.test.ts ├── +balances.test.ts ├── +bundle.test.ts ├── +deposit.conflux.test.ts ├── +deposit.ethereum.test.ts ├── +deposit.moonbase.test.ts ├── +fee.test.ts ├── +info.test.ts ├── +sign.arweave.test.ts ├── +sign.ethereum.test.ts ├── +signMessage.test.ts ├── +transfer.arweave.test.ts ├── +transfer.ethereum.test.ts ├── +transfer.pst.test.ts ├── +txs.test.ts ├── +txsByAccount.test.ts ├── +verifyMessage.test.ts ├── +withdraw.ethereum.test.ts ├── _expressInfo.test.ts ├── _withdraw.quick.test.ts ├── deposit.arweave.test.ts ├── deposit.pst.test.ts ├── set.tx.test.ts ├── withdraw.arweave.test.ts └── withdraw.pst.test.ts ├── tsconfig.cjs.json ├── tsconfig.eslint.json ├── tsconfig.json └── yarn.lock /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true, 5 | es2021: true 6 | }, 7 | extends: [ 8 | 'eslint:recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'standard-with-typescript' 11 | ], 12 | parser: '@typescript-eslint/parser', 13 | parserOptions: { 14 | ecmaVersion: 12, 15 | sourceType: 'module', 16 | project: './tsconfig.eslint.json' 17 | }, 18 | plugins: ['@typescript-eslint'], 19 | ignorePatterns: ['/lib'] 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | cjs 107 | 108 | esm 109 | 110 | umd 111 | 112 | test/constants/wallet.ts -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | test 3 | node_modules 4 | .eslintrc.js 5 | .gitignore 6 | babel.config.js 7 | jest.config.js 8 | package-lock.json 9 | tsconfig.cjs.json 10 | tsconfig.eslint.json 11 | tsconfig.json 12 | yarn-error.log 13 | yarn.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 everFinance 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # everpay-js 2 | 3 | [![npm](https://img.shields.io/npm/v/everpay.svg)](https://www.npmjs.com/package/everpay) [![License](https://img.shields.io/npm/l/everpay.svg)](https://www.npmjs.com/package/everpay) 4 | 5 | See [everpay-js docs](https://docs.everpay.io/en/docs/sdk/everpay-js/intro) 6 | 7 | everVision Team. 8 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/env', { targets: { node: 'current' } }], 4 | ['@babel/typescript'] 5 | ], 6 | plugins: ['@babel/plugin-transform-modules-commonjs'] 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.[t|j]sx?$': 'babel-jest' 4 | }, 5 | testEnvironment: 'node', 6 | testTimeout: 300000, 7 | testRegex: '/test/[+].*\\.test\\.ts$', 8 | // testRegex: 'test/deposit\\.pst\\.test\\.ts$', 9 | transformIgnorePatterns: ['./node_modules/(?!lodash-es)'] 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "everpay", 3 | "version": "1.5.0", 4 | "main": "./cjs/index.js", 5 | "module": "./esm/index.js", 6 | "files": [ 7 | "cjs", 8 | "esm", 9 | "umd" 10 | ], 11 | "repository": "git@github.com:everFinance/everpay-js.git", 12 | "keywords": [ 13 | "everPay", 14 | "ethereum", 15 | "arweave", 16 | "js", 17 | "blockchain", 18 | "crypto" 19 | ], 20 | "description": "everPay client JS-SDK, supports web and nodeJS to make everPay payments", 21 | "author": "everFinance Team ", 22 | "contributors": [ 23 | "Xaber " 24 | ], 25 | "license": "MIT", 26 | "scripts": { 27 | "lint": "eslint src test", 28 | "test": "jest", 29 | "build": "rm -rf ./esm && rm -rf ./cjs && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && rollup -c rollup.config.js --bundleConfigAsCjs", 30 | "prepublish": "npm run build" 31 | }, 32 | "sideEffects": false, 33 | "devDependencies": { 34 | "@babel/core": "^7.14.8", 35 | "@babel/plugin-transform-modules-commonjs": "^7.14.5", 36 | "@babel/preset-env": "^7.14.8", 37 | "@babel/preset-typescript": "^7.14.5", 38 | "@rollup/plugin-commonjs": "^24.0.1", 39 | "@rollup/plugin-json": "^6.0.0", 40 | "@rollup/plugin-node-resolve": "^15.0.1", 41 | "@rollup/plugin-strip": "^3.0.2", 42 | "@rollup/plugin-typescript": "^11.0.0", 43 | "@types/jest": "^26.0.24", 44 | "@types/keccak": "^3.0.1", 45 | "@types/node": "^16.4.0", 46 | "@types/uuid": "^8.3.1", 47 | "@typescript-eslint/eslint-plugin": "^4.28.4", 48 | "@typescript-eslint/parser": "^4.28.3", 49 | "babel-jest": "^27.0.6", 50 | "eslint": "^7.31.0", 51 | "eslint-config-standard": "^16.0.3", 52 | "eslint-config-standard-with-typescript": "^20.0.0", 53 | "eslint-plugin-import": "^2.23.4", 54 | "eslint-plugin-node": "^11.1.0", 55 | "eslint-plugin-promise": "^5.1.0", 56 | "jest": "^27.0.6", 57 | "lint-staged": "^11.0.1", 58 | "rollup": "^3.14.0", 59 | "rollup-plugin-node-polyfills": "^0.2.1", 60 | "rollup-plugin-terser": "^7.0.2", 61 | "typescript": "^4.9.5" 62 | }, 63 | "dependencies": { 64 | "@types/crypto-js": "^4.2.1", 65 | "@types/lodash": "^4.14.180", 66 | "arweave": "1.11.9", 67 | "axios": "^0.21.1", 68 | "bignumber.js": "^9.0.1", 69 | "crypto-js": "^4.1.1", 70 | "ethers": "^5.4.6", 71 | "keccak": "^3.0.2", 72 | "lodash": "^4.17.21", 73 | "query-string": "^7.0.1", 74 | "uuid": "^8.3.2", 75 | "@permaweb/aoconnect": "^0.0.48", 76 | "arseeding-arbundles": "^0.6.27" 77 | }, 78 | "gitHooks": { 79 | "pre-commit": "lint-staged" 80 | }, 81 | "lint-staged": { 82 | "*.{js,ts}": [ 83 | "npm run lint", 84 | "git add" 85 | ] 86 | } 87 | } -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from "@rollup/plugin-commonjs"; 2 | import resolve from "@rollup/plugin-node-resolve"; 3 | import strip from "@rollup/plugin-strip"; 4 | import typescript from "@rollup/plugin-typescript"; 5 | import path from "path"; 6 | // import externals from "rollup-plugin-node-externals"; 7 | import { terser } from "rollup-plugin-terser" 8 | import json from '@rollup/plugin-json' 9 | import rollupNodePolyFill from 'rollup-plugin-node-polyfills' 10 | // import builtins from 'rollup-plugin-node-builtins'; 11 | 12 | // import pkg from "./package.json" assert { type: 'json' }; 13 | 14 | export default [ 15 | { 16 | input: "./src/index.ts", // 入口文件 17 | output: [ 18 | { 19 | name: 'Everpay', //浏览器引入的全局变量名称 20 | file: 'umd/index.umd.js', //输出文件 21 | format: 'umd', //输出格式 22 | exports: 'named', //导出的是全局变量命名方式 23 | plugins: [ 24 | terser() //terser插件在rollup打包过程当中实现代码压缩 25 | ], 26 | }, 27 | ], 28 | plugins: [ 29 | // 自动将dependencies依赖声明为 externals 30 | // externals({ 31 | // devDeps: false, 32 | // }), 33 | // 处理外部依赖 34 | resolve({ 35 | browser: true, 36 | preferBuiltins: false 37 | }), 38 | json(), 39 | // 支持基于 CommonJS 模块引入 40 | commonjs({}), 41 | // 支持 typescript,并导出声明文件 42 | typescript({ 43 | }), 44 | // 清除调试代码 45 | strip(), 46 | rollupNodePolyFill({ include: null }), 47 | ], 48 | }, 49 | ] -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig, AxiosResponse } from 'axios' 2 | import isObject from 'lodash/isObject' 3 | import isString from 'lodash/isString' 4 | import { stringify as qsStringify } from 'query-string' 5 | import { 6 | EverpayInfo, 7 | EverpayTransaction, 8 | EverpayTx, 9 | TxsResult, 10 | FeeItem, 11 | ExpressInfo, 12 | EmailRegisterData 13 | } from '../types' 14 | import { 15 | GetEverpayTransactionsParams, 16 | GetEverpayBalanceParams, 17 | GetEverpayBalanceResult, 18 | GetEverpayBalancesParams, 19 | GetEverpayBalancesResult, 20 | PostEverpayTxResult, 21 | VerifySigParams, 22 | VerifySigResult 23 | } from '../types/api' 24 | 25 | // `validateStatus` defines whether to resolve or reject the promise for a given 26 | // HTTP response status code. If `validateStatus` returns `true` (or is set to `null` 27 | // or `undefined`), the promise will be resolved; otherwise, the promise will be rejected. 28 | const validateStatus = function (status: number): boolean { 29 | return status >= 200 && status < 300 // default 30 | } 31 | 32 | const rConfig = { 33 | timeout: 15000, 34 | validateStatus, 35 | headers: { 36 | 'Content-Type': 'application/json' 37 | } 38 | } 39 | 40 | export const sendRequest = async (config: AxiosRequestConfig): Promise => { 41 | return await new Promise((resolve, reject) => { 42 | axios({ 43 | ...rConfig, 44 | ...config 45 | }).then((res: AxiosResponse) => { 46 | if (res.data !== undefined) { 47 | resolve(res) 48 | } else { 49 | reject(new Error(`${config.url ?? ''}: null response`)) 50 | } 51 | }).catch(error => { 52 | if (isString(error)) { 53 | reject(new Error(error)) 54 | } else if (isObject(error.response) && isObject(error.response.data)) { 55 | // like { error: 'err_invalid_signature' } 56 | reject(new Error(error.response.data.error)) 57 | } else { 58 | reject(new Error(error)) 59 | } 60 | }) 61 | }) 62 | } 63 | 64 | export const getEverpayInfo = async (apiHost: string): Promise => { 65 | const url = `${apiHost}/info` 66 | const result = await sendRequest({ 67 | url, 68 | method: 'GET' 69 | }) 70 | 71 | return result.data 72 | } 73 | 74 | export const getEverpayBalance = async (apiHost: string, { 75 | account, 76 | tokenTag 77 | }: GetEverpayBalanceParams): Promise => { 78 | const url = `${apiHost}/balance/${tokenTag}/${account}` 79 | const result = await sendRequest({ 80 | url, 81 | method: 'GET' 82 | }) 83 | return result.data 84 | } 85 | 86 | export const getEverpayBalances = async (apiHost: string, { 87 | account 88 | }: GetEverpayBalancesParams): Promise => { 89 | const url = `${apiHost}/balances/${account}` 90 | const result = await sendRequest({ 91 | url, 92 | method: 'GET' 93 | }) 94 | return result.data 95 | } 96 | 97 | // 没有 targetChainTxHash 此方法已废弃 98 | export const getEverpayTransactions = async (apiHost: string, params: GetEverpayTransactionsParams): Promise => { 99 | const { account, tokenTag, action, withoutAction, cursor } = params 100 | const baseUrl = account !== undefined ? `${apiHost}/txs/${account}` : `${apiHost}/txs` 101 | const queryString = qsStringify({ cursor, tokenTag, action, withoutAction }, { skipNull: true }) 102 | const result = await sendRequest({ 103 | ...rConfig, 104 | url: `${baseUrl}${queryString !== '' ? `?${queryString}` : ''}`, 105 | method: 'GET' 106 | }) 107 | return result.data 108 | } 109 | 110 | export const getExplorerTransactions = async (apiHost: string, params: GetEverpayTransactionsParams): Promise => { 111 | const { account, tokenTag, action, withoutAction, cursor } = params 112 | const baseUrl = `${apiHost}/txs` 113 | const queryString = qsStringify({ address: account, cursor, tokenTag, action, withoutAction }, { skipNull: true }) 114 | const result = await sendRequest({ 115 | ...rConfig, 116 | url: `${baseUrl}${queryString !== '' ? `?${queryString}` : ''}`, 117 | method: 'GET' 118 | }) 119 | return result.data 120 | } 121 | 122 | // 没有 targetChainTxHash 此方法已废弃 123 | export const getEverpayTransaction = async (apiHost: string, everHash: string): Promise => { 124 | const url = `${apiHost}/tx/${everHash}` 125 | const result = await sendRequest({ 126 | ...rConfig, 127 | url, 128 | method: 'GET' 129 | }) 130 | return result.data.tx 131 | } 132 | 133 | export const getExplorerTransaction = async (apiHost: string, everHash: string): Promise => { 134 | const url = `${apiHost}/tx/${everHash}` 135 | const result = await sendRequest({ 136 | ...rConfig, 137 | url, 138 | method: 'GET' 139 | }) 140 | return result.data 141 | } 142 | 143 | export const getMintdEverpayTransactionByChainTxHash = async (apiHost: string, chainTxHash: string): Promise => { 144 | const url = `${apiHost}/minted/${chainTxHash}` 145 | const result = await sendRequest({ 146 | ...rConfig, 147 | url, 148 | method: 'GET' 149 | }) 150 | return result.data.tx 151 | } 152 | 153 | export const getFees = async (apiHost: string): Promise => { 154 | const url = `${apiHost}/fees` 155 | const result = await sendRequest({ 156 | ...rConfig, 157 | url, 158 | method: 'GET' 159 | }) 160 | return result.data.fees 161 | } 162 | 163 | export const getFee = async (apiHost: string, tokenTag: string): Promise => { 164 | const url = `${apiHost}/fee/${tokenTag}` 165 | const result = await sendRequest({ 166 | ...rConfig, 167 | url, 168 | method: 'GET' 169 | }) 170 | return result.data.fee 171 | } 172 | 173 | export const postTx = async (apiHost: string, params: EverpayTx): Promise => { 174 | const url = `${apiHost}/tx` 175 | const result = await sendRequest({ 176 | url, 177 | method: 'POST', 178 | data: params 179 | }) 180 | return result.data 181 | } 182 | 183 | export const getExpressInfo = async (apiHost: string): Promise => { 184 | const url = `${apiHost}/withdraw/info` 185 | const result = await sendRequest({ 186 | url, 187 | method: 'GET' 188 | }) 189 | 190 | return result.data 191 | } 192 | 193 | export const getEmailRegisterData = async (apiHost: string, email: string): Promise => { 194 | const url = `${apiHost}/account/register/${email}` 195 | const result = await sendRequest({ 196 | url, 197 | method: 'GET' 198 | }) 199 | 200 | return result.data 201 | } 202 | 203 | export const getAccountData = async (apiHost: string, account: string): Promise => { 204 | const url = `${apiHost}/account/${account}` 205 | const result = await sendRequest({ 206 | url, 207 | method: 'GET' 208 | }) 209 | 210 | return result.data 211 | } 212 | 213 | export const verifySig = async (apiHost: string, params: VerifySigParams): Promise => { 214 | const url = `${apiHost}/verify` 215 | const queryString = qsStringify(params, { skipNull: true }) 216 | const result = await sendRequest({ 217 | url: `${url}${queryString !== '' ? `?${queryString}` : ''}`, 218 | method: 'GET' 219 | }) 220 | 221 | return result.data 222 | } -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export const everpayTxVersion = 'v1' 2 | 3 | export const bundleInternalTxVersion = 'v1' 4 | 5 | export const getEverpayHost = (debug?: boolean): string => { 6 | return debug === true ? 'https://api-dev.everpay.io' : 'https://api.everpay.io' 7 | } 8 | 9 | export const getExpressHost = (debug?: boolean): string => { 10 | return debug === true ? 'https://express-dev.everpay.io' : 'https://express.everpay.io' 11 | } 12 | 13 | export const getExplorerHost = (debug?: boolean): string => { 14 | return debug === true ? 'https://api-explorer-dev.everpay.io' : 'https://api-explorer.everpay.io' 15 | } -------------------------------------------------------------------------------- /src/constants/abi/erc20.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | // Read-Only Functions 3 | 'function balanceOf(address owner) view returns (uint256)', 4 | 'function decimals() view returns (uint8)', 5 | 'function symbol() view returns (string)', 6 | 7 | // Authenticated Functions 8 | 'function transfer(address to, uint amount) returns (boolean)', 9 | 10 | // Events 11 | 'event Transfer(address indexed from, address indexed to, uint amount)' 12 | ] 13 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const NATIVE_CHAIN_TOKENS = [ 2 | { 3 | chainType: 'ethereum', 4 | network: 'mainnet', 5 | chainId: 1, 6 | nativeSymbol: 'eth', 7 | isProd: true 8 | }, 9 | { 10 | chainType: 'ethereum', 11 | network: 'goerli', 12 | chainId: 5, 13 | nativeSymbol: 'eth', 14 | isProd: false 15 | }, 16 | { 17 | chainType: 'moon', 18 | network: 'moonbeam', 19 | chainId: 1284, 20 | nativeSymbol: 'glmr', 21 | isProd: true 22 | }, 23 | { 24 | chainType: 'moon', 25 | network: 'moonbase-alphanet', 26 | chainId: 1287, 27 | nativeSymbol: 'dev', 28 | isProd: false 29 | }, 30 | { 31 | chainType: 'conflux', 32 | network: 'conflux eSpace', 33 | chainId: 1030, 34 | nativeSymbol: 'cfx', 35 | isProd: true 36 | }, 37 | { 38 | chainType: 'conflux', 39 | network: 'conflux eSpace Testnet', 40 | chainId: 71, 41 | nativeSymbol: 'cfx', 42 | isProd: false 43 | }, 44 | { 45 | chainType: 'bsc', 46 | network: 'bsc mainnet', 47 | chainId: 56, 48 | nativeSymbol: 'bnb', 49 | isProd: true 50 | }, 51 | { 52 | chainType: 'bsc', 53 | network: 'bsc testnet', 54 | chainId: 97, 55 | nativeSymbol: 'bnb', 56 | isProd: false 57 | }, 58 | { 59 | chainType: 'platon', 60 | network: 'platon mainnet', 61 | chainId: 210425, 62 | nativeSymbol: 'lat', 63 | isProd: true 64 | }, 65 | { 66 | chainType: 'platon', 67 | network: 'platon testnet', 68 | chainId: 2206132, 69 | nativeSymbol: 'lat', 70 | isProd: false 71 | }, 72 | { 73 | chainType: 'mapo', 74 | network: 'mapo mainnet', 75 | chainId: 22776, 76 | nativeSymbol: 'mapo', 77 | isProd: true 78 | }, 79 | { 80 | chainType: 'mapo', 81 | network: 'mapo testnet', 82 | chainId: 212, 83 | nativeSymbol: 'mapo', 84 | isProd: false 85 | } 86 | ] 87 | 88 | export const EVERPAY_JS_VERSION = '1.5.0' 89 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { getEverpayTxMessage, signMessageAsync, signRegisterAsync, transferAsync, getRedPackTxMessage } from './lib/sign' 2 | import { getEverpayBalance, getEverpayBalances, getEverpayInfo, getExpressInfo, getExplorerTransactions, getMintdEverpayTransactionByChainTxHash, postTx, getFees, getFee, getEmailRegisterData, getAccountData, verifySig, getExplorerTransaction } from './api' 3 | import { everpayTxVersion, getExpressHost, getEverpayHost, getExplorerHost } from './config' 4 | import { getTimestamp, toBN, getAccountChainType, fromDecimalToUnit, genTokenTag, matchTokenTag, genExpressData, fromUnitToDecimalBN, genBundleData, getTokenBurnFeeByChainType, getChainDecimalByChainType, isArweaveChainPSTMode, getTokenByTag, isArweaveL2PSTTokenSymbol, isSmartAccount, genEverId, getUserId, isArweaveAddress, uint8ArrayToHex, isEthereumAddress, isArweaveAOSTestToken } from './utils/util' 5 | import { GetEverpayBalanceParams, GetEverpayBalancesParams, GetEverpayTransactionsParams } from './types/api' 6 | import { checkParams } from './utils/check' 7 | import sha256 from 'crypto-js/sha256' 8 | import { 9 | CliamParams, 10 | Config, 11 | EverpayInfo, 12 | EverpayBase, 13 | BalanceParams, 14 | BalancesParams, 15 | DepositParams, 16 | SendEverpayTxResult, 17 | TransferParams, 18 | WithdrawParams, 19 | EverpayTxWithoutSig, 20 | EverpayAction, 21 | BundleData, 22 | FeeItem, 23 | ChainType, 24 | BalanceItem, 25 | TxsParams, 26 | TxsByAccountParams, 27 | TxsResult, 28 | EverpayTransaction, 29 | Token, 30 | EthereumTransaction, 31 | ArweaveTransaction, 32 | ExpressInfo, 33 | CachedInfo, 34 | InternalTransferItem, 35 | BundleDataWithSigs, 36 | BundleParams, 37 | EverpayTx, 38 | AddTokenSet, 39 | NewToken, SetParams, TargetChainMeta, AddTargetChainSet, TokenDisplaySet, OwnershipSet, EmailRegisterData, EmailRegisterDataWithCode, VerifyMessageResult, VerifyMessageParams, SignMessageResult, SmartAccountAuthResult 40 | } from './types' 41 | import { ERRORS } from './utils/errors' 42 | import { utils } from 'ethers' 43 | import { v4 as uuidv4 } from 'uuid' 44 | 45 | import { openPopup, runPopup } from './lib/popup' 46 | import { EVERPAY_JS_VERSION } from './constants' 47 | import hashPersonalMessage from './lib/hashPersonalMessage' 48 | import isString from 'lodash/isString' 49 | 50 | export * from './types' 51 | 52 | const _cachedInfo: CachedInfo = {} as any 53 | const cacheHelper = async (key: 'everpay' | 'express', host: string): Promise => { 54 | const timestamp = getTimestamp() 55 | // cache info 3 mins 56 | if (_cachedInfo[key]?.value != null && 57 | (_cachedInfo[key] as any).timestamp > timestamp - 3 * 60) { 58 | return _cachedInfo[key]?.value as EverpayInfo | ExpressInfo 59 | } 60 | 61 | if (key === 'everpay') { 62 | const value = await getEverpayInfo(host) 63 | _cachedInfo[key] = { value, timestamp } 64 | } else if (key === 'express') { 65 | const value = await getExpressInfo(host) 66 | _cachedInfo[key] = { value, timestamp } 67 | } 68 | return _cachedInfo[key]?.value as EverpayInfo | ExpressInfo 69 | } 70 | 71 | class Everpay extends EverpayBase { 72 | constructor (config?: Config) { 73 | super() 74 | this._config = { 75 | ...config, 76 | account: config?.account ?? '', 77 | chainType: config?.chainType ?? ChainType.ethereum 78 | } 79 | this._apiHost = getEverpayHost(config?.debug) 80 | this._expressHost = getExpressHost(config?.debug) 81 | this._explorerHost = getExplorerHost(config?.debug) 82 | } 83 | 84 | private readonly _apiHost: string 85 | private readonly _explorerHost: string 86 | private readonly _expressHost: string 87 | private readonly _config: Config 88 | 89 | getAccountChainType = getAccountChainType 90 | 91 | async info (): Promise { 92 | const result = await cacheHelper('everpay', this._apiHost) 93 | return result as EverpayInfo 94 | } 95 | 96 | async expressInfo (): Promise { 97 | const result = await cacheHelper('express', this._expressHost) 98 | return result as ExpressInfo 99 | } 100 | 101 | async getEmailRegisterData (): Promise { 102 | const result = await getEmailRegisterData(this._apiHost, this._config.account as string) 103 | return result 104 | } 105 | 106 | async getAccountData (): Promise { 107 | const acc = isSmartAccount(this._config.account as string) ? genEverId(this._config.account as string) : this._config.account as string 108 | const result = await getAccountData(this._apiHost, acc) 109 | return result 110 | } 111 | 112 | async balance (params: BalanceParams): Promise { 113 | await this.info() 114 | const { tag, account } = params 115 | const accTemp = account ?? this._config.account as string 116 | const acc = isSmartAccount(accTemp) ? genEverId(accTemp) : accTemp 117 | const token = getTokenByTag(tag, _cachedInfo?.everpay?.value.tokenList) 118 | checkParams({ account: acc, tag, token }) 119 | const mergedParams: GetEverpayBalanceParams = { 120 | tokenTag: genTokenTag(token as Token), 121 | account: acc 122 | } 123 | const everpayBalance = await getEverpayBalance(this._apiHost, mergedParams) 124 | return fromDecimalToUnit(everpayBalance.balance.amount, everpayBalance.balance.decimals) 125 | } 126 | 127 | async balances (params?: BalancesParams): Promise { 128 | const info = await this.info() 129 | params = (params ?? {}) as BalanceParams 130 | const { account } = params 131 | const accTemp = account ?? this._config.account as string 132 | const acc = isSmartAccount(accTemp) ? genEverId(accTemp) : accTemp 133 | checkParams({ account: acc }) 134 | const mergedParams: GetEverpayBalancesParams = { 135 | account: acc 136 | } 137 | const everpayBalances = await getEverpayBalances(this._apiHost, mergedParams) 138 | const argTag = !this._config.debug ? 'bsc-arg-0xb5eadfdbdb40257d1d24a1432faa2503a867c270' : 'bsc-arg-0x7846cf6e181bb5c909d6010d15af5fffd3b61229' 139 | const fraTag = !this._config.debug ? 'bsc-fra-0xeb042ffdabc535de2716c6b51a965f124050d4e1' : 'bsc-fra-0xa98242557818f0135b2381893caec3d4a64f88e5' 140 | const deleteTag = [ 141 | argTag, 142 | fraTag 143 | ] 144 | const balances = everpayBalances.balances.filter((t) => t.tag !== deleteTag[0] && t.tag !== deleteTag[1]).map(item => { 145 | const tag = item.tag 146 | const token = info.tokenList.find(token => token.tag === tag) as Token 147 | return { 148 | chainType: token?.chainType, 149 | symbol: token?.symbol.toUpperCase(), 150 | tag: token?.tag, 151 | address: token?.id, 152 | balance: fromDecimalToUnit(item.amount, item.decimals) 153 | } 154 | }) 155 | return balances 156 | } 157 | 158 | private async getMergedTxsParams (params: TxsParams): Promise { 159 | const { page, tag, action, withoutAction, cursor } = params 160 | const mergedParams: GetEverpayTransactionsParams = {} 161 | if (page !== undefined) { 162 | mergedParams.page = page 163 | } 164 | if (cursor !== undefined) { 165 | mergedParams.cursor = cursor 166 | } 167 | if (tag !== undefined) { 168 | await this.info() 169 | const token = getTokenByTag(tag, _cachedInfo?.everpay?.value.tokenList) as Token 170 | checkParams({ token }) 171 | mergedParams.tokenTag = token.tag 172 | } 173 | if (action !== undefined) { 174 | checkParams({ action }) 175 | mergedParams.action = action 176 | } 177 | if (withoutAction !== undefined) { 178 | mergedParams.withoutAction = withoutAction 179 | } 180 | return mergedParams 181 | } 182 | 183 | async txs (params: TxsParams): Promise { 184 | const mergedParams: GetEverpayTransactionsParams = await this.getMergedTxsParams(params) 185 | return await getExplorerTransactions(this._explorerHost, mergedParams) 186 | } 187 | 188 | async txsByAccount (params: TxsByAccountParams): Promise { 189 | const accTemp = params.account ?? this._config.account as string 190 | const acc = isSmartAccount(accTemp) ? genEverId(accTemp) : accTemp 191 | checkParams({ account: acc }) 192 | const mergedParams: GetEverpayTransactionsParams = await this.getMergedTxsParams(params) 193 | mergedParams.account = acc 194 | return await getExplorerTransactions(this._explorerHost, mergedParams) 195 | } 196 | 197 | async txByHash (everHash: string): Promise { 198 | checkParams({ everHash }) 199 | return await getExplorerTransaction(this._explorerHost, everHash) 200 | } 201 | 202 | async mintedTxByChainTxHash (chainTxHash: string): Promise { 203 | checkParams({ chainTxHash }) 204 | return await getMintdEverpayTransactionByChainTxHash(this._apiHost, chainTxHash) 205 | } 206 | 207 | async fees (): Promise { 208 | return await getFees(this._apiHost) 209 | } 210 | 211 | async fee (tag: string): Promise { 212 | await this.info() 213 | const token = getTokenByTag(tag, _cachedInfo?.everpay?.value.tokenList) as Token 214 | checkParams({ tag, token }) 215 | return await getFee(this._apiHost, genTokenTag(token)) 216 | } 217 | 218 | async smartAccountAuth (logo: string, email?: string, emailEditable?: boolean): Promise { 219 | const debug = Boolean(this._config.debug) 220 | email = isSmartAccount(email as any) ? email : '' 221 | // eslint-disable-next-line no-unneeded-ternary 222 | emailEditable = emailEditable === false ? false : true 223 | const url = `https://app${debug ? '-dev' : ''}.everpay.io/auth?host=${encodeURIComponent(window.location.host)}&logo=${encodeURIComponent(logo)}&version=${EVERPAY_JS_VERSION}&email=${email}&editable=${emailEditable ? 1 : 0}` 224 | // const url = `http://localhost:8080/auth?host=${encodeURIComponent(window.location.host)}&logo=${encodeURIComponent(logo)}&version=${EVERPAY_JS_VERSION}&email=${email}&editable=${emailEditable ? 1 : 0}` 225 | const popup = await openPopup(url) 226 | return await runPopup({ 227 | popup, 228 | type: 'auth' 229 | }) 230 | } 231 | 232 | genEverId (account?: string): string { 233 | const acc = account ?? this._config.account as string 234 | return isSmartAccount(acc) ? genEverId(acc) : acc 235 | } 236 | 237 | hashMessage (message: string): string { 238 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(message)) 239 | const personalMsgHex = `0x${personalMsgHashBuffer.toString('hex')}` 240 | return personalMsgHex 241 | } 242 | 243 | async signMessage (message: string, smartAccountDirectly?: boolean): Promise { 244 | if (!isString(message)) { 245 | throw new Error(ERRORS.MESSAGE_INCORRECT) 246 | } 247 | if (isSmartAccount(this._config.account as string)) { 248 | if (smartAccountDirectly != null) { 249 | if (message.length >= 96) { 250 | const { sig } = await signMessageAsync(this._config, message, undefined, true) 251 | return { message, sig } 252 | } else { 253 | throw new Error(ERRORS.MESSAGE_INCORRECT) 254 | } 255 | } else { 256 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(message)) 257 | const messageHash = `0x${personalMsgHashBuffer.toString('hex')}` 258 | const { sig } = await signMessageAsync(this._config, message) 259 | return { message: messageHash, sig } 260 | } 261 | } else { 262 | const { sig } = await signMessageAsync(this._config, message) 263 | return { message, sig } 264 | } 265 | } 266 | 267 | async verifyMessage (params: VerifyMessageParams): Promise { 268 | const { account, message, type, sig } = params 269 | const everId = this.genEverId(account) 270 | let messageHash = '' 271 | 272 | if (isEthereumAddress(account)) { 273 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(message)) 274 | messageHash = `0x${personalMsgHashBuffer.toString('hex')}` 275 | } else if (isArweaveAddress(account)) { 276 | messageHash = `0x${sha256(message).toString()}` 277 | } else if (isSmartAccount(account)) { 278 | messageHash = `0x${uint8ArrayToHex(Buffer.from(message))}` 279 | } else { 280 | throw new Error(ERRORS.ACCOUNT_INVALID) 281 | } 282 | 283 | const [splitSig, splitPublic] = sig.split(',') 284 | return await verifySig(this._apiHost, { 285 | account: everId, 286 | message: messageHash, 287 | type, 288 | sig: splitSig, 289 | public: splitPublic 290 | }) 291 | } 292 | 293 | async deposit (params: DepositParams): Promise { 294 | await this.info() 295 | const { amount, tag } = params 296 | const from = this._config.account 297 | const token = getTokenByTag(tag, _cachedInfo?.everpay?.value.tokenList) as Token 298 | const chainType = this._config.chainType 299 | checkParams({ account: from, tag, token, amount }) 300 | 301 | // arweave 上的 PST 充值必须是整数 302 | if (isArweaveChainPSTMode(token) && chainType === ChainType.arweave && !isArweaveL2PSTTokenSymbol(token.symbol) && parseInt(amount) !== +amount) { 303 | throw new Error(ERRORS.DEPOSIT_ARWEAVE_PST_MUST_BE_INTEGER) 304 | } 305 | 306 | const chainDecimal = getChainDecimalByChainType(token, isArweaveAOSTestToken(token) ? ChainType.aostest : chainType as ChainType) 307 | const value = utils.parseUnits(toBN(amount).toString(), chainDecimal) 308 | 309 | return await transferAsync(this._config, _cachedInfo.everpay?.value as EverpayInfo, { 310 | symbol: token.symbol, 311 | token, 312 | from: from ?? '', 313 | value 314 | }) 315 | } 316 | 317 | // amount 为实际收款数量 318 | async getEverpayTxWithoutSig ( 319 | type: 'transfer' | 'withdraw' | 'bundle' | 'set' | 'setAcc', 320 | params: TransferParams | WithdrawParams | BundleParams | SetParams 321 | ): Promise { 322 | if (_cachedInfo?.everpay?.value == null) { 323 | await this.info() 324 | } 325 | const { tag, amount, fee, quickMode } = params as WithdrawParams 326 | const token = getTokenByTag(tag, _cachedInfo?.everpay?.value.tokenList) 327 | const from = this._config.account as string 328 | let data = params.data 329 | let to = params?.to as string 330 | let decimalFeeBN = toBN(0) 331 | let decimalOperateAmountBN = toBN(0) 332 | let action = EverpayAction.transfer 333 | 334 | checkParams({ account: from, tag, token, to }) 335 | 336 | if (type === 'transfer') { 337 | checkParams({ amount }) 338 | action = EverpayAction.transfer 339 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0) 340 | } else if (type === 'bundle') { 341 | action = EverpayAction.bundle 342 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0) 343 | 344 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 345 | } else if (type === 'set') { 346 | action = EverpayAction.set 347 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0) 348 | } else if (type === 'setAcc') { 349 | action = EverpayAction.setAcc 350 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0) 351 | } else if (type === 'withdraw') { 352 | checkParams({ amount }) 353 | const chainType = (params as WithdrawParams).chainType 354 | 355 | // PST 提现到 arweave 网络必须是整数 356 | if (isArweaveChainPSTMode(token) && chainType === ChainType.arweave && !isArweaveL2PSTTokenSymbol(token?.symbol as string) && parseInt(amount) !== +amount) { 357 | throw new Error(ERRORS.PST_WITHDARW_TO_ARWEAVE_MUST_BE_INTEGER) 358 | } 359 | 360 | const balance = await this.balance({ tag }) 361 | const decimalBalanceBN = fromUnitToDecimalBN(balance, token?.decimals ?? 0) 362 | 363 | // 快速提现 364 | if (quickMode === true) { 365 | action = EverpayAction.transfer 366 | const expressInfo = await this.expressInfo() 367 | const tokenTag = genTokenTag(token as Token) 368 | const foundExpressTokenData = expressInfo.tokens.find(t => matchTokenTag(tokenTag, t.tokenTag)) 369 | if (foundExpressTokenData == null) { 370 | throw new Error(ERRORS.WITHDRAW_TOKEN_NOT_SUPPORT_QUICK_MODE) 371 | } 372 | 373 | const quickWithdrawLimitBN = fromUnitToDecimalBN(foundExpressTokenData.walletBalance, token?.decimals ?? 0) 374 | 375 | // 快速提现的手续费,只放入 data 字段中 376 | const quickWithdrawFeeBN = fee !== undefined 377 | ? fromUnitToDecimalBN(fee, token?.decimals ?? 0) 378 | : toBN(foundExpressTokenData.withdrawFee) 379 | 380 | // 快速提现的 amount 为全部数量 381 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0).plus(quickWithdrawFeeBN) 382 | 383 | if (decimalOperateAmountBN.gt(decimalBalanceBN)) { 384 | throw new Error(ERRORS.WITHDRAW_AMOUNT_LESS_THAN_FEE) 385 | } 386 | 387 | if (decimalOperateAmountBN.gt(quickWithdrawLimitBN)) { 388 | throw new Error(ERRORS.INSUFFICIENT_QUICK_WITHDRAWAL_AMOUNT) 389 | } 390 | 391 | const expressData = genExpressData({ 392 | chainType, to, fee: quickWithdrawFeeBN.toString() 393 | }) 394 | data = data !== undefined ? { ...data, ...expressData } : { ...expressData } 395 | 396 | // to 需要更改为快速提现收款账户 397 | to = expressInfo.address 398 | 399 | // 普通提现 400 | } else { 401 | action = EverpayAction.withdraw 402 | 403 | if (fee !== undefined) { 404 | decimalFeeBN = fromUnitToDecimalBN(fee, token?.decimals ?? 0) 405 | } else { 406 | const feeItem = await getFee(this._apiHost, genTokenTag(token as Token)) 407 | decimalFeeBN = toBN(getTokenBurnFeeByChainType(token as Token, feeItem, chainType) ?? '0') 408 | } 409 | 410 | const targetChainType = chainType 411 | data = data !== undefined ? { ...data, targetChainType } : { targetChainType } 412 | decimalOperateAmountBN = fromUnitToDecimalBN(amount, token?.decimals ?? 0) 413 | 414 | if (decimalOperateAmountBN.plus(decimalFeeBN).gt(decimalBalanceBN)) { 415 | throw new Error(ERRORS.WITHDRAW_AMOUNT_LESS_THAN_FEE) 416 | } 417 | } 418 | } 419 | 420 | const everpayTxWithoutSig: EverpayTxWithoutSig = { 421 | tokenSymbol: token?.symbol as string, 422 | action, 423 | from: isSmartAccount(from) ? genEverId(from) : from, 424 | to: isSmartAccount(to) ? genEverId(to) : to, 425 | amount: decimalOperateAmountBN.toString(), 426 | fee: decimalFeeBN.toString(), 427 | feeRecipient: _cachedInfo?.everpay?.value.feeRecipient ?? '', 428 | nonce: Date.now().toString(), 429 | tokenID: token?.id as string, 430 | chainType: token?.chainType as string, 431 | chainID: token?.chainID as string, 432 | data: data !== undefined ? JSON.stringify(data) : '', 433 | version: everpayTxVersion 434 | } 435 | return everpayTxWithoutSig 436 | } 437 | 438 | getEverpayTxMessage (everpayTxWithoutSig: EverpayTxWithoutSig): string { 439 | return getEverpayTxMessage(everpayTxWithoutSig) 440 | } 441 | 442 | async signedEverpayTx (everpayTxWithoutSig: EverpayTxWithoutSig): Promise<{everpayTx: EverpayTx, everHash: string}> { 443 | const messageData = getEverpayTxMessage(everpayTxWithoutSig) 444 | const { sig, everHash } = await signMessageAsync(this._config, messageData) 445 | const everpayTx = { 446 | ...everpayTxWithoutSig, 447 | sig 448 | } 449 | return { everpayTx, everHash } 450 | } 451 | 452 | // sig redpacket 453 | async signedRedPackTx (redPackTxSig: CliamParams): Promise<{redpackTx: CliamParams, everHash: string}> { 454 | const messageData = getRedPackTxMessage(redPackTxSig) 455 | const { sig, everHash } = await signMessageAsync(this._config, messageData) 456 | const redpackTx = { 457 | ...redPackTxSig, 458 | signature: sig 459 | } 460 | return { redpackTx, everHash } 461 | } 462 | 463 | async sendEverpayTx (everpayTxWithoutSig: EverpayTxWithoutSig): Promise { 464 | const { everpayTx, everHash } = await this.signedEverpayTx(everpayTxWithoutSig) 465 | const postEverpayTxResult = await postTx(this._apiHost, everpayTx) 466 | return { 467 | ...postEverpayTxResult, 468 | everpayTx, 469 | everHash 470 | } 471 | } 472 | 473 | async register (params?: EmailRegisterDataWithCode, attachment?: string): Promise { 474 | if (_cachedInfo?.everpay?.value == null) { 475 | await this.info() 476 | } 477 | const everpayInfo = _cachedInfo?.everpay?.value as EverpayInfo 478 | const token = everpayInfo.tokenList.find((t) => { 479 | return t.symbol.toUpperCase() === 'ETH' 480 | }) as any 481 | const everpayTxWithoutSig: EverpayTxWithoutSig = { 482 | tokenSymbol: token?.symbol as string, 483 | action: 'register' as any, 484 | from: isSmartAccount(this._config.account as string) ? genEverId(this._config.account as string) : this._config.account as string, 485 | to: isSmartAccount(this._config.account as string) ? genEverId(this._config.account as string) : this._config.account as string, 486 | amount: '0', 487 | fee: '0', 488 | feeRecipient: _cachedInfo?.everpay?.value.feeRecipient ?? '', 489 | nonce: Date.now().toString(), 490 | tokenID: token?.id as string, 491 | chainType: token?.chainType as string, 492 | chainID: token?.chainID as string, 493 | data: params !== undefined ? JSON.stringify({ mailVerify: params }) : '', 494 | version: everpayTxVersion 495 | } 496 | const messageData = getEverpayTxMessage(everpayTxWithoutSig) 497 | const { sig, everHash } = await signRegisterAsync(this._config, messageData, attachment) 498 | const everpayTx = { 499 | ...everpayTxWithoutSig, 500 | sig 501 | } 502 | const postEverpayTxResult = await postTx(this._apiHost, everpayTx) 503 | return { 504 | ...postEverpayTxResult, 505 | everpayTx, 506 | everHash 507 | } 508 | } 509 | 510 | async transfer (params: TransferParams): Promise { 511 | const everpayTxWithoutSig = await this.getEverpayTxWithoutSig('transfer', params) 512 | return await this.sendEverpayTx(everpayTxWithoutSig) 513 | } 514 | 515 | async withdraw (params: WithdrawParams): Promise { 516 | if (_cachedInfo?.everpay?.value == null) { 517 | await this.info() 518 | } 519 | const to = params.to ?? this._config.account as string 520 | const everpayTxWithoutSig = await this.getEverpayTxWithoutSig('withdraw', { 521 | ...params, 522 | to 523 | }) 524 | return await this.sendEverpayTx(everpayTxWithoutSig) 525 | } 526 | 527 | async getBundleData (items: InternalTransferItem[], expiration?: number, data?: string): Promise { 528 | if (_cachedInfo?.everpay?.value == null) { 529 | await this.info() 530 | } 531 | return genBundleData({ 532 | items, 533 | tokenList: _cachedInfo.everpay?.value?.tokenList as Token[], 534 | // 设置 60s 过期 535 | expiration: expiration ?? Math.round(Date.now() / 1000) + 60, 536 | data 537 | }) 538 | } 539 | 540 | async signBundleData (bundleData: BundleData | BundleDataWithSigs): Promise { 541 | const { items, expiration, salt, version, data } = bundleData 542 | const { sig } = await signMessageAsync(this._config, JSON.stringify({ 543 | // 只签名这几个字段,并且顺序需要保持一致 544 | items, expiration, salt, version 545 | })) 546 | const sigs = (bundleData as BundleDataWithSigs).sigs != null ? (bundleData as BundleDataWithSigs).sigs : {} 547 | const acc = isSmartAccount(this._config.account as string) ? genEverId(this._config.account as string) : this._config.account as string 548 | sigs[acc] = sig 549 | return { 550 | items, expiration, salt, version, sigs, data 551 | } 552 | } 553 | 554 | async bundle (params: BundleParams): Promise { 555 | const everpayTxWithoutSig = await this.getEverpayTxWithoutSig('bundle', params) 556 | return await this.sendEverpayTx(everpayTxWithoutSig) 557 | } 558 | 559 | async signAddTokenSet (newToken: NewToken): Promise { 560 | const addToken: AddTokenSet = { 561 | action: 'addToken', 562 | operator: this._config.account as string, 563 | salt: uuidv4(), 564 | version: 'v1', 565 | expiration: Math.round(Date.now() / 1000) + 100, 566 | token: newToken, 567 | sig: '' 568 | } 569 | const { sig } = await signMessageAsync(this._config, JSON.stringify({ 570 | action: addToken.action, 571 | operator: addToken.operator, 572 | salt: addToken.salt, 573 | version: addToken.version, 574 | expiration: addToken.expiration, 575 | token: addToken.token 576 | })) 577 | addToken.sig = sig 578 | return addToken 579 | } 580 | 581 | async signAddTargetChainSet (tokenTag: string, targetChain: TargetChainMeta): Promise { 582 | const addTargetChain: AddTargetChainSet = { 583 | action: 'addTargetChain', 584 | operator: this._config.account as string, 585 | salt: uuidv4(), 586 | version: 'v1', 587 | expiration: Math.round(Date.now() / 1000) + 100, 588 | tokenTag: tokenTag, 589 | targetChain: targetChain, 590 | sig: '' 591 | } 592 | const { sig } = await signMessageAsync(this._config, JSON.stringify({ 593 | action: addTargetChain.action, 594 | operator: addTargetChain.operator, 595 | salt: addTargetChain.salt, 596 | version: addTargetChain.version, 597 | expiration: addTargetChain.expiration, 598 | tokenTag: addTargetChain.tokenTag, 599 | targetChain: addTargetChain.targetChain 600 | })) 601 | addTargetChain.sig = sig 602 | return addTargetChain 603 | } 604 | 605 | async signTokenDisplaySet (tokenTag: string, display: boolean): Promise { 606 | const tokenDisplay: TokenDisplaySet = { 607 | action: 'setTokenDisplay', 608 | operator: this._config.account as string, 609 | salt: uuidv4(), 610 | version: 'v1', 611 | expiration: Math.round(Date.now() / 1000) + 100, 612 | tokenTag: tokenTag, 613 | display: display, 614 | sig: '' 615 | } 616 | const { sig } = await signMessageAsync(this._config, JSON.stringify({ 617 | action: tokenDisplay.action, 618 | operator: tokenDisplay.operator, 619 | salt: tokenDisplay.salt, 620 | version: tokenDisplay.version, 621 | expiration: tokenDisplay.expiration, 622 | tokenTag: tokenDisplay.tokenTag, 623 | display: tokenDisplay.display 624 | })) 625 | tokenDisplay.sig = sig 626 | return tokenDisplay 627 | } 628 | 629 | async signOwnershipSet (newOwner: string): Promise { 630 | const ownership: OwnershipSet = { 631 | action: 'transferOwnership', 632 | operator: this._config.account as string, 633 | salt: uuidv4(), 634 | version: 'v1', 635 | expiration: Math.round(Date.now() / 1000) + 100, 636 | newOwner: newOwner, 637 | sig: '' 638 | } 639 | const { sig } = await signMessageAsync(this._config, JSON.stringify({ 640 | action: ownership.action, 641 | operator: ownership.operator, 642 | salt: ownership.salt, 643 | version: ownership.version, 644 | expiration: ownership.expiration, 645 | newOwner: ownership.newOwner 646 | })) 647 | ownership.sig = sig 648 | return ownership 649 | } 650 | 651 | async setTx (setData: any): Promise { 652 | const setParams: SetParams = { amount: '0', data: setData, symbol: 'eth', to: this._config.account as string } 653 | const everpayTxWithoutSig = await this.getEverpayTxWithoutSig('set', setParams) 654 | return await this.sendEverpayTx(everpayTxWithoutSig) 655 | } 656 | 657 | async setAcc (data: any, accountData?: any): Promise { 658 | const params = { 659 | amount: '0', 660 | data, 661 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', 662 | to: this._config.account as string 663 | } 664 | const everpayTxWithoutSig = await this.getEverpayTxWithoutSig('setAcc', params) 665 | 666 | const messageData = getEverpayTxMessage(everpayTxWithoutSig) 667 | const { sig, everHash } = await signMessageAsync(this._config, messageData, accountData) 668 | const everpayTx = { 669 | ...everpayTxWithoutSig, 670 | sig 671 | } 672 | const postEverpayTxResult = await postTx(this._apiHost, everpayTx) 673 | return { 674 | ...postEverpayTxResult, 675 | everpayTx, 676 | everHash 677 | } 678 | } 679 | } 680 | 681 | export default Everpay 682 | -------------------------------------------------------------------------------- /src/lib/arweave.ts: -------------------------------------------------------------------------------- 1 | import Arweave from 'arweave' 2 | import isString from 'lodash/isString' 3 | import { ArJWK, ArweaveTransaction, ChainType } from '../types' 4 | import { getTokenAddrByChainType, hexToUint8Array, isArweaveAOSTestToken, isArweaveL2PSTTokenSymbol } from '../utils/util' 5 | import { TransferAsyncParams } from './interface' 6 | import { sendRequest } from '../api' 7 | import sha256 from 'crypto-js/sha256' 8 | import { connect } from '@permaweb/aoconnect' 9 | 10 | import { DataItem } from 'arseeding-arbundles' 11 | 12 | const options = { 13 | host: 'arweave.net', // Hostname or IP address for a Arweave host 14 | port: 443, // Port 15 | protocol: 'https', // Network protocol http or https 16 | timeout: 20000, // Network request timeouts in milliseconds 17 | logging: false // Enable network request logging 18 | } 19 | 20 | const defaultAOConfig = { 21 | CU_URL: 'https://cu.ao-testnet.xyz', 22 | MU_URL: 'https://mu.ao-testnet.xyz', 23 | GATEWAY_URL: 'https://g8way.io:443' 24 | } 25 | 26 | // TODO: to fix arConnect return result and interface 27 | enum ERRORS { 28 | PLEASE_INSTALL_ARCONNECT = 'PLEASE_INSTALL_ARCONNECT', 29 | ACCESS_ADDRESS_PERMISSION_NEEDED = 'ACCESS_ADDRESS_PERMISSION_NEEDED', 30 | ACCESS_PUBLIC_KEY_PERMISSION_NEEDED = 'ACCESS_PUBLIC_KEY_PERMISSION_NEEDED', 31 | SIGNATURE_PERMISSION_NEEDED = 'NEED_SIGNATURE_PERMISSION', 32 | SIGN_TRANSACTION_PERMISSION_NEEDED = 'SIGN_TRANSACTION_PERMISSION_NEEDED', 33 | SIGNATURE_FAILED = 'SIGNATURE_FAILED', 34 | TRANSACTION_POST_ERROR = 'TRANSACTION_POST_ERROR', 35 | ACCESS_PUBLIC_KEY_FAILED = 'ACCESS_PUBLIC_KEY_FAILED' 36 | } 37 | 38 | export const checkArPermissions = async (permissions: string[] | string): Promise => { 39 | let existingPermissions: string[] = [] 40 | permissions = isString(permissions) ? [permissions] : permissions 41 | 42 | try { 43 | existingPermissions = await window.arweaveWallet.getPermissions() 44 | } catch { 45 | throw new Error(ERRORS.PLEASE_INSTALL_ARCONNECT) 46 | } 47 | 48 | if (permissions.length === 0) { 49 | return 50 | } 51 | 52 | if (permissions.some(permission => { 53 | return !existingPermissions.includes(permission) 54 | })) { 55 | await window.arweaveWallet.connect(permissions as never[]) 56 | } 57 | } 58 | 59 | const signMessageAsync = async (debug: boolean, arJWK: ArJWK, address: string, messageData: string): Promise => { 60 | const arweave = Arweave.init(options) 61 | const msgDataBuffer = Buffer.from(messageData, 'utf-8') 62 | let arOwner = '' 63 | let signatureB64url = '' 64 | // web 65 | if (arJWK === 'use_wallet') { 66 | try { 67 | await checkArPermissions('ACCESS_PUBLIC_KEY') 68 | } catch { 69 | throw new Error(ERRORS.ACCESS_PUBLIC_KEY_PERMISSION_NEEDED) 70 | } 71 | try { 72 | // TODO: wait arweave-js update arconnect.d.ts 73 | arOwner = await (window.arweaveWallet).getActivePublicKey() 74 | } catch { 75 | throw new Error(ERRORS.ACCESS_PUBLIC_KEY_FAILED) 76 | } 77 | 78 | try { 79 | await checkArPermissions('SIGNATURE') 80 | } catch { 81 | throw new Error(ERRORS.SIGNATURE_PERMISSION_NEEDED) 82 | } 83 | 84 | const algorithm = { 85 | name: 'RSA-PSS', 86 | saltLength: 32 87 | } 88 | 89 | if ((window.arweaveWallet as any).signMessage !== undefined) { 90 | try { 91 | const signature = await (window.arweaveWallet as any).signMessage( 92 | msgDataBuffer, 93 | { hashAlgorithm: 'SHA-256' } 94 | ) 95 | const buf = new Uint8Array(Object.values(signature)) 96 | signatureB64url = Arweave.utils.bufferTob64Url(buf) 97 | } catch { 98 | throw new Error(ERRORS.SIGNATURE_FAILED) 99 | } 100 | } else { 101 | try { 102 | const hash = sha256(messageData) 103 | const signature = await (window.arweaveWallet).signature( 104 | hexToUint8Array(hash.toString()), 105 | algorithm 106 | ) 107 | const buf = new Uint8Array(Object.values(signature)) 108 | signatureB64url = Arweave.utils.bufferTob64Url(buf) 109 | } catch { 110 | throw new Error(ERRORS.SIGNATURE_FAILED) 111 | } 112 | } 113 | 114 | // node 115 | } else { 116 | const hash = sha256(messageData) 117 | const buf = await arweave.crypto.sign(arJWK, hexToUint8Array(hash.toString()), { 118 | saltLength: 32 119 | }) 120 | arOwner = arJWK.n 121 | signatureB64url = Arweave.utils.bufferTob64Url(buf) 122 | } 123 | 124 | return `${signatureB64url},${arOwner}` 125 | } 126 | 127 | // TODO: only support browser yet 128 | export const sendAoTransfer = async ( 129 | process: string, 130 | recipient: string, 131 | amount: string, 132 | addTags: Array<{ name: string, value: string }> = [], 133 | replaceTags?: any 134 | ) => { 135 | const ao = connect(defaultAOConfig) 136 | 137 | try { 138 | const createDataItemSigner = 139 | () => 140 | async ({ 141 | data, 142 | tags = [], 143 | target, 144 | anchor 145 | }: { 146 | data: any 147 | tags?: Array<{ name: string, value: string }> 148 | target?: string 149 | anchor?: string 150 | }): Promise<{ id: string, raw: ArrayBuffer }> => { 151 | await checkArPermissions([ 152 | 'ACCESS_ADDRESS', 153 | 'ACCESS_ALL_ADDRESSES', 154 | 'ACCESS_PUBLIC_KEY', 155 | 'SIGN_TRANSACTION', 156 | 'SIGNATURE' 157 | ]) 158 | 159 | const signed = await (window.arweaveWallet as any).signDataItem({ 160 | data, 161 | tags, 162 | anchor, 163 | target 164 | }) 165 | const dataItem = new DataItem(Buffer.from(signed)) 166 | 167 | return { 168 | id: await dataItem.id, 169 | raw: await dataItem.getRaw() 170 | } 171 | } 172 | const signer = createDataItemSigner() as any 173 | const messageID = await ao.message({ 174 | process, 175 | signer, 176 | tags: replaceTags || [ 177 | { name: 'Action', value: 'Transfer' }, 178 | { 179 | name: 'Recipient', 180 | value: recipient 181 | }, 182 | { name: 'Quantity', value: amount }, 183 | ...addTags 184 | ] 185 | }) 186 | return messageID 187 | } catch (err) { 188 | console.log('err', err) 189 | throw err 190 | } 191 | } 192 | 193 | const transferAsync = async (arJWK: ArJWK, chainType: ChainType, { 194 | symbol, 195 | token, 196 | from, 197 | to, 198 | value 199 | }: TransferAsyncParams): Promise => { 200 | const arweave = Arweave.init(options) 201 | let transactionTransfer: any 202 | 203 | if (isArweaveAOSTestToken(token)) { 204 | const tokenID = getTokenAddrByChainType(token, ChainType.aostest) 205 | const transferID = await sendAoTransfer(tokenID, to as string, value.toString()) 206 | console.log('transferID', transferID) 207 | return { 208 | id: transferID, 209 | status: 200, 210 | data: {} 211 | } as any 212 | } 213 | 214 | if (symbol.toUpperCase() === 'AR') { 215 | transactionTransfer = await arweave.createTransaction({ 216 | target: to, 217 | quantity: value.toString() 218 | }, arJWK) 219 | 220 | // PST Token 221 | } else { 222 | const tokenID = getTokenAddrByChainType(token, ChainType.arweave) 223 | transactionTransfer = await arweave.createTransaction({ 224 | data: (Math.random() * 10000).toFixed(), 225 | last_tx: isArweaveL2PSTTokenSymbol(token.symbol) ? 'p7vc1iSP6bvH_fCeUFa9LqoV5qiyW-jdEKouAT0XMoSwrNraB9mgpi29Q10waEpO' : undefined, 226 | reward: isArweaveL2PSTTokenSymbol(token.symbol) ? '0' : undefined 227 | }, arJWK) 228 | transactionTransfer.addTag('App-Name', 'SmartWeaveAction') 229 | transactionTransfer.addTag('App-Version', '0.3.0') 230 | transactionTransfer.addTag('Contract', tokenID) 231 | transactionTransfer.addTag('Input', JSON.stringify({ 232 | function: 'transfer', 233 | qty: value.toNumber(), 234 | target: to 235 | })) 236 | } 237 | 238 | if (arJWK === 'use_wallet') { 239 | try { 240 | const existingPermissions = await window.arweaveWallet.getPermissions() as string[] 241 | if (!existingPermissions.includes('SIGN_TRANSACTION')) { 242 | await window.arweaveWallet.connect(['SIGN_TRANSACTION']) 243 | } 244 | } catch (_a) { 245 | // Permission is already granted 246 | } 247 | const signedTransaction = await window.arweaveWallet.sign(transactionTransfer) 248 | // TODO: Temp fix arConnect modify reward 249 | transactionTransfer.reward = signedTransaction.reward 250 | transactionTransfer.setSignature({ 251 | id: signedTransaction.id, 252 | owner: signedTransaction.owner, 253 | tags: signedTransaction.tags, 254 | signature: signedTransaction.signature 255 | }) 256 | } else { 257 | // 直接给原来 transaction 赋值了 signature 值 258 | await arweave.transactions.sign(transactionTransfer, arJWK) 259 | } 260 | let responseTransfer = null as any 261 | if (isArweaveL2PSTTokenSymbol(token.symbol)) { 262 | await sendRequest({ 263 | url: 'https://gw.warp.cc/gateway/sequencer/register', 264 | data: transactionTransfer, 265 | headers: { 266 | // 'Accept-Encoding': 'gzip, deflate, br', 267 | 'Content-Type': 'application/json', 268 | Accept: 'application/json' 269 | }, 270 | method: 'POST' 271 | }) 272 | responseTransfer = { 273 | status: 200, 274 | data: {} 275 | } 276 | // responseTransfer = await fetch('https://gateway.warp.cc/gateway/sequencer/register', { 277 | // method: 'POST', 278 | // body: JSON.stringify(transactionTransfer), 279 | // headers: { 280 | // 'Accept-Encoding': 'gzip, deflate, br', 281 | // 'Content-Type': 'application/json', 282 | // Accept: 'application/json' 283 | // } 284 | // }) 285 | } else { 286 | responseTransfer = await arweave.transactions.post(transactionTransfer) 287 | } 288 | 289 | if (responseTransfer.status === 200) { 290 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 291 | if (responseTransfer.data.error) { 292 | throw new Error(responseTransfer.data.error) 293 | } 294 | return transactionTransfer 295 | } 296 | throw new Error(ERRORS.TRANSACTION_POST_ERROR) 297 | } 298 | 299 | export default { 300 | signMessageAsync, 301 | transferAsync 302 | } 303 | -------------------------------------------------------------------------------- /src/lib/ethereum.ts: -------------------------------------------------------------------------------- 1 | import { Contract, Signer, utils, providers } from 'ethers' 2 | import { TransferAsyncParams } from './interface' 3 | import erc20Abi from '../constants/abi/erc20' 4 | import { getTokenAddrByChainType } from '../utils/util' 5 | import { ChainType, EthereumTransaction } from '../types' 6 | import { NATIVE_CHAIN_TOKENS } from '../constants' 7 | 8 | // 参考自 zkSync 9 | // https://github.com/WalletConnect/walletconnect-monorepo/issues/347#issuecomment-880553018 10 | const signMessageAsync = async (debug: boolean, ethConnectedSigner: Signer, address: string, message: string): Promise => { 11 | const messageBytes = utils.toUtf8Bytes(message) 12 | if (ethConnectedSigner instanceof providers.JsonRpcSigner) { 13 | try { 14 | const signature = await ethConnectedSigner.provider.send('personal_sign', [ 15 | utils.hexlify(messageBytes), 16 | address.toLowerCase() 17 | ]) as string 18 | return signature 19 | } catch (e: any) { 20 | const noPersonalSign: boolean = e.message.includes('personal_sign') 21 | if (noPersonalSign) { 22 | const signature = await ethConnectedSigner.signMessage(messageBytes) 23 | return signature 24 | } 25 | throw e 26 | } 27 | } else { 28 | const signature = await ethConnectedSigner.signMessage(messageBytes) 29 | return signature 30 | } 31 | } 32 | 33 | const transferAsync = async (ethConnectedSigner: Signer, chainType: ChainType, { 34 | symbol, 35 | token, 36 | from, 37 | to, 38 | value 39 | }: TransferAsyncParams): Promise => { 40 | let transactionResponse: EthereumTransaction 41 | const foundNative = NATIVE_CHAIN_TOKENS.find(t => { 42 | return t.chainType === chainType && t.nativeSymbol === symbol.toLowerCase() 43 | }) 44 | 45 | // TODO: check balance 46 | if (foundNative != null) { 47 | const transactionRequest = { 48 | from: from.toLowerCase(), 49 | to: to?.toLowerCase(), 50 | gasLimit: 25000, 51 | value 52 | } 53 | transactionResponse = await ethConnectedSigner.sendTransaction(transactionRequest) 54 | } else { 55 | const tokenID = getTokenAddrByChainType(token, chainType) 56 | const erc20RW = new Contract(tokenID.toLowerCase(), erc20Abi, ethConnectedSigner) 57 | transactionResponse = await erc20RW.transfer(to, value, { 58 | gasLimit: 200000 59 | }) 60 | } 61 | return transactionResponse 62 | } 63 | 64 | export default { 65 | signMessageAsync, 66 | transferAsync 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/hashPersonalMessage.ts: -------------------------------------------------------------------------------- 1 | import createKeccakHash from 'keccak' 2 | import { Hash } from 'crypto' 3 | 4 | function createHashFunction ( 5 | hashConstructor: () => Hash 6 | ): (msg: Buffer) => Buffer { 7 | return msg => { 8 | const hash = hashConstructor() 9 | hash.update(msg) 10 | return Buffer.from(hash.digest()) 11 | } 12 | } 13 | 14 | const keccak256 = createHashFunction((): Hash => { 15 | return createKeccakHash('keccak256') as Hash 16 | }) 17 | 18 | // cp from: https://github.com/ethereumjs/ethereumjs-util/blob/ebf40a0fba8b00ba9acae58405bca4415e383a0d/src/signature.ts#L168 19 | const hashPersonalMessage = function (message: Buffer): Buffer { 20 | const prefix = Buffer.from( 21 | `\u0019Ethereum Signed Message:\n${message.length.toString()}`, 22 | 'utf-8' 23 | ) 24 | return keccak256(Buffer.concat([prefix, message])) 25 | } 26 | 27 | export default hashPersonalMessage 28 | -------------------------------------------------------------------------------- /src/lib/interface.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from '@ethersproject/bignumber' 2 | import { Token } from '../types' 3 | 4 | export interface TransferAsyncParams { 5 | symbol: string 6 | token: Token 7 | from: string 8 | to?: string 9 | value: BigNumber 10 | } 11 | 12 | export interface SignMessageAsyncResult { 13 | sig: string 14 | everHash: string 15 | } 16 | -------------------------------------------------------------------------------- /src/lib/popup.ts: -------------------------------------------------------------------------------- 1 | export interface PopupConfigOptions { 2 | /** 3 | * The number of seconds to wait for a popup response before 4 | * throwing a timeout error. Defaults to 300s 5 | */ 6 | timeoutInSeconds?: number 7 | 8 | /** 9 | * Accepts an already-created popup window to use. If not specified, the SDK 10 | * will create its own. This may be useful for platforms like iOS that have 11 | * security restrictions around when popups can be invoked (e.g. from a user click event) 12 | */ 13 | popup?: any 14 | type: 'auth' | 'sign' | 'signPageLoaded' 15 | keepPopup?: boolean 16 | } 17 | 18 | const DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS = 480 19 | 20 | export const openPopup = async (url = ''): Promise => { 21 | const width = 375 22 | const height = 660 23 | const left = window.screenX + (window.innerWidth - width) / 2 24 | const top = window.screenY + (window.innerHeight - height) / 2 25 | 26 | return await new Promise((resolve) => { 27 | setTimeout(() => { 28 | const popup = window.open( 29 | url, 30 | 'everpay:authorize:popup', 31 | `left=${left},top=${top},width=${width},height=${height},resizable,scrollbars=yes,status=1` 32 | ) 33 | resolve(popup) 34 | }, 0) 35 | }) 36 | } 37 | 38 | export const runPopup = async ( 39 | config: PopupConfigOptions 40 | ): Promise => 41 | await new Promise((resolve, reject) => { 42 | // eslint-disable-next-line prefer-const 43 | let popupEventListener: any 44 | // eslint-disable-next-line prefer-const 45 | let timeoutId: any 46 | // Check each second if the popup is closed triggering a PopupCancelledError 47 | const popupTimer = setInterval(() => { 48 | if ((Boolean(config.popup)) && (Boolean(config.popup.closed))) { 49 | clearInterval(popupTimer) 50 | clearTimeout(timeoutId) 51 | window.removeEventListener('message', popupEventListener, false) 52 | reject(new Error('POPUP_CLOSED')) 53 | } 54 | }, 1000) 55 | 56 | timeoutId = setTimeout(() => { 57 | clearInterval(popupTimer) 58 | config.popup.close() 59 | reject(new Error('AUTHORIZE_TIMEOUT')) 60 | window.removeEventListener('message', popupEventListener, false) 61 | }, (config.timeoutInSeconds != null ? config.timeoutInSeconds : DEFAULT_AUTHORIZE_TIMEOUT_IN_SECONDS) * 1000) 62 | 63 | popupEventListener = (e: MessageEvent) => { 64 | if (!e.origin.includes('everpay.io') && !e.origin.includes('localhost')) { 65 | return 66 | } 67 | // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions 68 | if (!e.data || typeof e.data !== 'string') { 69 | return 70 | } 71 | 72 | let data: any = {} 73 | 74 | try { 75 | data = JSON.parse(e.data) 76 | } catch {} 77 | 78 | if (data.type !== config.type) { 79 | return 80 | } 81 | 82 | clearTimeout(timeoutId) 83 | clearInterval(popupTimer) 84 | window.removeEventListener('message', popupEventListener, false) 85 | if (!config.keepPopup) { 86 | config.popup.close() 87 | } 88 | 89 | if (data.err != null) { 90 | reject(new Error(data.err)) 91 | } 92 | 93 | resolve(data.data) 94 | } 95 | 96 | window.addEventListener('message', popupEventListener) 97 | }) 98 | -------------------------------------------------------------------------------- /src/lib/sign.ts: -------------------------------------------------------------------------------- 1 | import { SignMessageAsyncResult, TransferAsyncParams } from './interface' 2 | import ethereumLib from './ethereum' 3 | import arweaveLib from './arweave' 4 | import smartAccountLib from './smartAccount' 5 | import { ArJWK, ChainType, Config, EverpayInfo, EverpayTxWithoutSig, EthereumTransaction, ArweaveTransaction, CliamParams, Token } from '../types' 6 | import { checkSignConfig } from '../utils/check' 7 | 8 | import { Signer } from '@ethersproject/abstract-signer' 9 | import { ERRORS } from '../utils/errors' 10 | import hashPersonalMessage from './hashPersonalMessage' 11 | import { isArweaveAOSTestToken, isNodeJs, isPermaswapHaloTokenSymbol } from '../utils/util' 12 | import { openPopup, runPopup } from './popup' 13 | 14 | export const getDepositAddr = (info: EverpayInfo, accountChainType: ChainType, token: Token): string => { 15 | const symbol = token.symbol 16 | if (isArweaveAOSTestToken(token)) { 17 | return info?.lockers.aostest 18 | } 19 | if (isPermaswapHaloTokenSymbol(symbol)) { 20 | return info?.lockers.psntest 21 | } 22 | if (accountChainType === ChainType.ethereum) { 23 | return info?.lockers.ethereum 24 | } else if (accountChainType === ChainType.arweave) { 25 | // AR 大小写敏感 26 | return info?.lockers.arweave 27 | } else if (accountChainType === ChainType.moon) { 28 | return info?.lockers.moon 29 | } else if (accountChainType === ChainType.conflux) { 30 | return info?.lockers.conflux 31 | } else if (accountChainType === ChainType.bsc) { 32 | return info?.lockers.bsc 33 | } else if (accountChainType === ChainType.platon) { 34 | return info?.lockers.platon 35 | } else if (accountChainType === ChainType.mapo) { 36 | return info?.lockers.mapo 37 | } 38 | throw new Error(ERRORS.INVALID_ACCOUNT_TYPE) 39 | } 40 | 41 | export const getEverpayTxMessage = (everpayTxWithoutSig: EverpayTxWithoutSig): string => { 42 | const keys = [ 43 | 'tokenSymbol', 44 | 'action', 45 | 'from', 46 | 'to', 47 | 'amount', 48 | 'fee', 49 | 'feeRecipient', 50 | 'nonce', 51 | 'tokenID', 52 | 'chainType', 53 | 'chainID', 54 | 'data', 55 | 'version' 56 | ] as const 57 | return keys.map(key => `${key}:${everpayTxWithoutSig[key]}`).join('\n') 58 | } 59 | 60 | export const signRegisterAsync = async (config: Config, messageData: string, attachment?: string): Promise => { 61 | const from = config.account as string 62 | const accountChainType = config.chainType as ChainType 63 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(messageData)) 64 | const personalMsgHex = `0x${personalMsgHashBuffer.toString('hex')}` 65 | let sig = '' 66 | checkSignConfig(accountChainType, config) 67 | 68 | if (config.isSmartAccount ?? false) { 69 | sig = await smartAccountLib.signRegisterAsync(Boolean(config.debug), Boolean(config.isSmartAccount), from, personalMsgHex, undefined, attachment) 70 | } else if ([ 71 | ChainType.ethereum, 72 | ChainType.moon, 73 | ChainType.conflux, 74 | ChainType.bsc, 75 | ChainType.platon, 76 | ChainType.mapo 77 | ].includes(accountChainType)) { 78 | sig = await ethereumLib.signMessageAsync(Boolean(config.debug), config.ethConnectedSigner as Signer, from, messageData) 79 | sig = `${sig},,ECDSA` 80 | } else if (accountChainType === ChainType.arweave) { 81 | sig = await arweaveLib.signMessageAsync(Boolean(config.debug), config.arJWK as ArJWK, from, messageData) 82 | sig = `${sig},RSA` 83 | } else { 84 | throw new Error(ERRORS.INVALID_ACCOUNT_TYPE) 85 | } 86 | return { everHash: personalMsgHex, sig } 87 | } 88 | 89 | export const signMessageAsync = async (config: Config, messageData: string, accountData?: any, directly?: boolean): Promise => { 90 | const from = config.account as string 91 | const accountChainType = config.chainType as ChainType 92 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(messageData)) 93 | const personalMsgHex = `0x${personalMsgHashBuffer.toString('hex')}` 94 | let sig = '' 95 | checkSignConfig(accountChainType, config) 96 | 97 | if (!isNodeJs() && Boolean(config.isSmartAccount) && !window.location.host.includes('everpay.io')) { 98 | const url = `https://app${(config.debug ?? false) ? '-dev' : ''}.everpay.io/sign?account=${config.account as string}&host=${encodeURIComponent(window.location.host)}&directly=${directly ? '1' : '0'}` 99 | // const url = `http://localhost:8080/sign?account=${config.account as string}&host=${encodeURIComponent(window.location.host)}&directly=${directly ? '1' : '0'}` 100 | const popup = await openPopup(url) 101 | // 先接收到子窗口的 signPageLoaded 事件 102 | await runPopup({ 103 | popup, 104 | type: 'signPageLoaded', 105 | keepPopup: true 106 | }) 107 | // 再向子窗口发送 messageData 108 | popup.postMessage(JSON.stringify({ data: encodeURIComponent(messageData), type: 'signPageMessageData' }), '*') 109 | sig = await runPopup({ 110 | popup, 111 | type: 'sign' 112 | }) 113 | } else { 114 | if (config.isSmartAccount ?? false) { 115 | sig = await smartAccountLib.signMessageAsync(Boolean(config.debug), Boolean(config.isSmartAccount), from, directly ? messageData : personalMsgHex, accountData) 116 | } else if ([ 117 | ChainType.ethereum, 118 | ChainType.moon, 119 | ChainType.conflux, 120 | ChainType.bsc, 121 | ChainType.platon, 122 | ChainType.mapo 123 | ].includes(accountChainType)) { 124 | sig = await ethereumLib.signMessageAsync(Boolean(config.debug), config.ethConnectedSigner as Signer, from, messageData) 125 | } else if (accountChainType === ChainType.arweave) { 126 | sig = await arweaveLib.signMessageAsync(Boolean(config.debug), config.arJWK as ArJWK, from, messageData) 127 | } else { 128 | throw new Error(ERRORS.INVALID_ACCOUNT_TYPE) 129 | } 130 | } 131 | return { everHash: personalMsgHex, sig } 132 | } 133 | 134 | export const getRedPackTxMessage = (redPackTxSig: CliamParams): string => { 135 | const keys = [ 136 | 'redpacketUUID', 137 | 'claimBy', 138 | 'salt', 139 | 'createdAt' 140 | ] as const 141 | return keys.map(key => `${key}:${redPackTxSig[key]}`).join('\n') 142 | } 143 | 144 | export const transferAsync = async ( 145 | config: Config, 146 | info: EverpayInfo, 147 | params: TransferAsyncParams 148 | ): Promise => { 149 | checkSignConfig(config.chainType as ChainType, config) 150 | 151 | const to = getDepositAddr(info, config.chainType as ChainType, params.token) 152 | const paramsMergedTo = { ...params, to } 153 | 154 | if ([ 155 | ChainType.ethereum, 156 | ChainType.moon, 157 | ChainType.conflux, 158 | ChainType.bsc, 159 | ChainType.platon, 160 | ChainType.mapo 161 | ].includes(config.chainType as ChainType)) { 162 | return await ethereumLib.transferAsync(config.ethConnectedSigner as Signer, config.chainType as ChainType, paramsMergedTo) 163 | } else if (config.chainType as ChainType === ChainType.arweave) { 164 | return await arweaveLib.transferAsync(config.arJWK as ArJWK, config.chainType as ChainType, paramsMergedTo) 165 | } 166 | 167 | throw new Error(ERRORS.INVALID_ACCOUNT_TYPE) 168 | } 169 | -------------------------------------------------------------------------------- /src/lib/smartAccount.ts: -------------------------------------------------------------------------------- 1 | import Arweave from 'arweave' 2 | import { TransferAsyncParams } from './interface' 3 | import { ChainType } from '../types' 4 | import { ERRORS } from '../utils/errors' 5 | import { getAccountData } from '../api' 6 | import { getEverpayHost } from '../config' 7 | import { genEverId, getUserId, isMobile } from '../utils/util' 8 | 9 | export const getRpId = (): string => { 10 | const domain = document.domain 11 | if (domain === 'localhost') { 12 | return domain 13 | } 14 | if (domain.includes('everpay.io')) { 15 | return 'everpay.io' 16 | } 17 | return domain 18 | } 19 | 20 | const signRegisterAsync = async ( 21 | debug: boolean, 22 | isSmartAccount: boolean, 23 | email: string, 24 | everHash: string, 25 | accountData?: any, 26 | attachment?: string, 27 | expiration?: number 28 | ): Promise => { 29 | const everId = genEverId(email) 30 | // expiration 用于添加多密钥时,避免 userId 相同,Safari 14.4+ excludeCredentials bug 导致覆盖丢失密钥 31 | // https://forums.developer.apple.com/forums/thread/749232?login=true&page=1#785836022 32 | const userId = getUserId(debug, everId, expiration) 33 | const arr = accountData && accountData.publicValues ? Object.entries(accountData.publicValues) : [] 34 | const credential = await navigator.credentials.create({ 35 | publicKey: { 36 | rp: { 37 | name: 'everpay', 38 | id: getRpId() 39 | }, 40 | user: { 41 | name: email, 42 | displayName: email, 43 | id: Arweave.utils.b64UrlToBuffer(userId) 44 | }, 45 | challenge: Arweave.utils.b64UrlToBuffer(window.btoa(everHash)), 46 | attestation: 'direct', 47 | pubKeyCredParams: [ 48 | { 49 | type: 'public-key', 50 | alg: -7 51 | }, 52 | { 53 | type: 'public-key', 54 | alg: -257 55 | } 56 | ], 57 | // 排除已经注册过的 58 | excludeCredentials: arr.map((publicIdValueArr: any) => { 59 | const id = publicIdValueArr[0] 60 | return { 61 | type: 'public-key', 62 | id: Arweave.utils.b64UrlToBuffer(id), 63 | transports: [ 64 | 'internal', 65 | 'usb', 66 | 'nfc', 67 | 'ble' 68 | ].concat(!isMobile ? ['hybrid'] : []) as any 69 | } 70 | }), 71 | timeout: 300000, 72 | authenticatorSelection: { 73 | requireResidentKey: true, 74 | residentKey: 'required', 75 | userVerification: 'required', 76 | ...(attachment === 'platform' ? { 77 | authenticatorAttachment: 'platform', 78 | } : {}) 79 | } 80 | } 81 | }) as any 82 | 83 | if (credential === null) { 84 | throw new Error('cancelled') 85 | } 86 | const sigJson = { 87 | id: credential.id, 88 | rawId: Arweave.utils.bufferTob64Url(credential.rawId), 89 | attestationObject: Arweave.utils.bufferTob64Url(credential.response.attestationObject), 90 | clientDataJSON: Arweave.utils.bufferTob64Url(credential.response.clientDataJSON), 91 | userId 92 | } 93 | const signResult = window.btoa(JSON.stringify(sigJson)) 94 | 95 | return `${signResult},,FIDO2` 96 | } 97 | 98 | const signMessageAsync = async (debug: boolean, isSmartAccount: boolean, email: string, everHash: string, accountData?: any): Promise => { 99 | if (accountData == null) { 100 | const everpayHost = getEverpayHost(debug) 101 | const everId = genEverId(email) 102 | accountData = await getAccountData(everpayHost, everId) 103 | } 104 | const arr = Object.entries(accountData.publicValues) as any 105 | const publicKeyData = { 106 | allowCredentials: arr.map((publicIdValueArr: any) => { 107 | const id = publicIdValueArr[0] 108 | return { 109 | type: 'public-key', 110 | id: Arweave.utils.b64UrlToBuffer(id), 111 | transports: [ 112 | 'internal', 113 | 'usb', 114 | 'nfc', 115 | 'ble' 116 | ].concat(!isMobile ? ['hybrid'] : []) 117 | } 118 | }) 119 | } 120 | const assertion = await navigator.credentials.get({ 121 | publicKey: { 122 | ...publicKeyData, 123 | timeout: 300000, 124 | userVerification: 'required', 125 | challenge: Arweave.utils.b64UrlToBuffer(window.btoa(everHash)), 126 | rpId: getRpId() 127 | } as any 128 | }) as any 129 | if (assertion === null) { 130 | throw new Error('cancelled') 131 | } 132 | const authenticatorData = assertion.response.authenticatorData 133 | const clientDataJSON = assertion.response.clientDataJSON 134 | const rawId = assertion.rawId 135 | const signature = assertion.response.signature 136 | const userHandle = assertion.response.userHandle 137 | const sigJson = { 138 | id: assertion?.id, 139 | rawId: Arweave.utils.bufferTob64Url(rawId), 140 | clientDataJSON: Arweave.utils.bufferTob64Url(clientDataJSON), 141 | authenticatorData: Arweave.utils.bufferTob64Url(authenticatorData), 142 | signature: Arweave.utils.bufferTob64Url(signature), 143 | userHandle: Arweave.utils.bufferTob64Url(userHandle) 144 | } 145 | const sig = window.btoa(JSON.stringify(sigJson)) 146 | return `${sig},${accountData.publicValues[assertion?.id]},FIDO2` 147 | } 148 | 149 | const transferAsync = async (isSmartAccount: boolean, chainType: ChainType, { 150 | symbol, 151 | token, 152 | from, 153 | to, 154 | value 155 | }: TransferAsyncParams): Promise => { 156 | throw new Error(ERRORS.SMART_ACCOUNT_DEPOSIT_NOT_SUPPORT) 157 | } 158 | 159 | export default { 160 | signRegisterAsync, 161 | signMessageAsync, 162 | transferAsync 163 | } 164 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | export interface GetEverpayTransactionsParams { 2 | account?: string 3 | tokenTag?: string 4 | action?: string 5 | withoutAction?: string 6 | cursor?: number 7 | page?: number 8 | } 9 | 10 | export interface BalanceItemFromServer { 11 | tag: string 12 | amount: string 13 | decimals: number 14 | } 15 | export interface GetEverpayBalanceParams { 16 | account: string 17 | tokenTag: string 18 | } 19 | 20 | export interface GetEverpayBalanceResult { 21 | accid: string 22 | balance: BalanceItemFromServer 23 | } 24 | export interface GetEverpayBalancesParams { 25 | account: string 26 | } 27 | 28 | export interface GetEverpayBalancesResult { 29 | accid: string 30 | balances: BalanceItemFromServer[] 31 | } 32 | export interface PostEverpayTxResult { 33 | // TODO: ok or other status 34 | status: string 35 | } 36 | 37 | export interface VerifySigParams { 38 | account: string 39 | type: 'register' | 'sign' 40 | message: string 41 | sig: string 42 | public?: string 43 | } 44 | 45 | export interface VerifySigResult { 46 | publicId: string 47 | public: string 48 | } -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from 'ethers' 2 | import { JWKInterface } from 'arweave/node/lib/wallet' 3 | import { PostEverpayTxResult } from './api' 4 | import type { TransactionResponse as EthereumTransaction } from '@ethersproject/abstract-provider' 5 | import type { TransactionInterface as ArweaveTransaction } from 'arweave/node/lib/transaction' 6 | 7 | export enum ChainType { 8 | ethereum = 'ethereum', 9 | moon = 'moon', 10 | arweave = 'arweave', 11 | conflux = 'conflux', 12 | bsc = 'bsc', 13 | platon = 'platon', 14 | mapo = 'mapo', 15 | aostest = 'aostest', 16 | psntest = 'psntest' 17 | } 18 | 19 | export type ArJWK = JWKInterface | 'use_wallet' 20 | 21 | export { EthereumTransaction, ArweaveTransaction } 22 | 23 | export interface Config { 24 | debug?: boolean 25 | account?: string 26 | chainType?: ChainType 27 | ethConnectedSigner?: Signer 28 | arJWK?: ArJWK 29 | isSmartAccount?: boolean 30 | } 31 | 32 | export interface CrossChainInfo { 33 | targetChainId: string 34 | targetChainType: ChainType 35 | targetDecimals: number 36 | targetTokenId: string 37 | } 38 | 39 | export interface Token { 40 | tag: string 41 | id: string 42 | symbol: string 43 | decimals: number 44 | totalSupply: string 45 | chainID: string 46 | chainType: ChainType | string 47 | crossChainInfoList: { 48 | [propname: string]: CrossChainInfo 49 | } 50 | } 51 | 52 | export interface EmailRegisterData { 53 | timestamp: number 54 | sig: string 55 | } 56 | 57 | export interface EmailRegisterDataWithCode extends EmailRegisterData { 58 | code: string 59 | } 60 | 61 | export interface FeeItem { 62 | tokenTag: string 63 | burnFeeMap: { 64 | [propname: string]: string 65 | } 66 | transferFee: string 67 | atomicBundleFee: string 68 | updatedAt: string 69 | } 70 | 71 | export interface EverpayInfo { 72 | isClosed: boolean 73 | isSynced: boolean 74 | lockers: { 75 | [propName: string]: string 76 | } 77 | ethChainID: string 78 | feeRecipient: string 79 | owner: string 80 | everRootHash: string 81 | rootHash: string 82 | tokenList: Token[] 83 | } 84 | 85 | interface ExpressTokenItem { 86 | tokenTag: string 87 | withdrawFee: string 88 | walletBalance: string 89 | } 90 | export interface ExpressInfo { 91 | address: string 92 | withdrawTimeCost: number 93 | tokens: ExpressTokenItem[] 94 | } 95 | 96 | export enum EverpayAction { 97 | transfer = 'transfer', 98 | withdraw = 'burn', 99 | bundle = 'bundle', 100 | set = 'set', 101 | register = 'register', 102 | setAcc = 'setAcc' 103 | } 104 | 105 | export interface InternalTransferItem { 106 | tag: string 107 | from: string 108 | to: string 109 | amount: string 110 | data?: string 111 | } 112 | 113 | export interface BundleItem { 114 | amount: string 115 | chainID: string 116 | from: string 117 | to: string 118 | tag: string 119 | data: string 120 | } 121 | 122 | export interface BundleData { 123 | items: BundleItem[] 124 | expiration: number 125 | salt: string 126 | version: string 127 | data: string 128 | } 129 | 130 | export interface BundleDataWithSigs extends BundleData { 131 | sigs: { 132 | [account: string]: string 133 | } 134 | } 135 | 136 | export interface CommonSet { 137 | action: string 138 | operator: string 139 | salt: string 140 | version: string 141 | expiration: number 142 | } 143 | 144 | export interface TargetChainMeta { 145 | targetChainId: string 146 | targetChainType: string 147 | targetDecimals: number 148 | targetTokenId: string 149 | } 150 | 151 | export interface NewToken { 152 | tokenID: string 153 | symbol: string 154 | chainType: string 155 | chainID: string 156 | everDecimals: number 157 | targetChains: TargetChainMeta[] 158 | } 159 | 160 | export interface AddTokenSet extends CommonSet { 161 | token: NewToken 162 | sig: string 163 | } 164 | 165 | export interface AddTargetChainSet extends CommonSet { 166 | tokenTag: string 167 | targetChain: TargetChainMeta 168 | sig: string 169 | } 170 | 171 | export interface TokenDisplaySet extends CommonSet { 172 | tokenTag: string 173 | display: boolean 174 | sig: string 175 | } 176 | 177 | export interface OwnershipSet extends CommonSet { 178 | newOwner: string 179 | sig: string 180 | } 181 | 182 | export interface EverpayTxWithoutSig { 183 | tokenSymbol: string 184 | action: EverpayAction 185 | from: string 186 | to: string 187 | amount: string 188 | fee: string 189 | feeRecipient: string 190 | nonce: string 191 | tokenID: string 192 | chainType: ChainType | string 193 | chainID: string 194 | data: string 195 | version: string 196 | } 197 | 198 | export interface EverpayTx extends EverpayTxWithoutSig { 199 | sig: string 200 | } 201 | 202 | export enum EverpayActionWithDeposit { 203 | transfer = 'transfer', 204 | withdraw = 'burn', 205 | deposit = 'mint', 206 | bundle = 'bundle', 207 | setAcc = 'setAcc' 208 | } 209 | 210 | enum EverpayTransactionStatus { 211 | // deposit 下,经过 6 个区块 everPay confirm 212 | // mint、burn,后端接收到信息,会先 confirmed 213 | confirmed = 'confirmed', 214 | // JSON 文件存储交易打包完成,变成 packaged 215 | packaged = 'packaged' 216 | } 217 | 218 | export interface EverpayTransaction { 219 | // a transaction that everpay json saved to ar 220 | id: string 221 | tokenSymbol: string 222 | nonce: number 223 | action: EverpayActionWithDeposit 224 | from: string 225 | to: string 226 | amount: string 227 | data: string 228 | fee: string 229 | feeRecipient: string 230 | sig: string 231 | everHash: string 232 | chainID: string 233 | chainType: string 234 | tokenID: string 235 | version: string 236 | status: EverpayTransactionStatus 237 | internalStatus: string 238 | timestamp: number 239 | targetChainTxHash?: string 240 | express: { 241 | chainTxHash: string 242 | withdrawFee: string 243 | refundEverHash: string 244 | err: string 245 | } 246 | } 247 | 248 | export interface TxsResult { 249 | accid?: string 250 | currentPage: number 251 | totalPages: number 252 | hasNextPage: boolean 253 | txs: EverpayTransaction[] 254 | } 255 | 256 | export interface BalanceParams { 257 | tag: string 258 | account?: string 259 | } 260 | 261 | export interface BalancesParams { 262 | account?: string 263 | } 264 | 265 | export interface BalanceItem { 266 | chainType: string 267 | tag: string 268 | symbol: string 269 | balance: string 270 | address: string 271 | } 272 | 273 | export interface DepositParams { 274 | tag: string 275 | amount: string 276 | } 277 | 278 | export interface WithdrawParams { 279 | chainType: ChainType 280 | tag: string 281 | amount: string 282 | fee?: string 283 | quickMode?: boolean 284 | data?: Record 285 | to?: string 286 | } 287 | 288 | export interface TransferParams { 289 | tag: string 290 | amount: string 291 | data?: Record 292 | to: string 293 | } 294 | 295 | export interface BundleParams { 296 | tag: string 297 | amount: string 298 | data: { 299 | bundle: BundleDataWithSigs 300 | } 301 | to: string 302 | } 303 | 304 | export interface SetParams { 305 | symbol: string 306 | amount: string 307 | data: any 308 | to: string 309 | } 310 | 311 | export interface TxsParams { 312 | page?: number 313 | cursor?: number 314 | tag?: string 315 | action?: EverpayActionWithDeposit 316 | withoutAction?: EverpayActionWithDeposit 317 | } 318 | 319 | export interface TxsByAccountParams { 320 | page?: number 321 | account?: string 322 | cursor?: number 323 | tag?: string 324 | action?: EverpayActionWithDeposit 325 | withoutAction?: EverpayActionWithDeposit 326 | } 327 | 328 | export interface SendEverpayTxResult extends PostEverpayTxResult { 329 | everpayTx: EverpayTx 330 | everHash: string 331 | } 332 | 333 | export interface CachedInfo { 334 | everpay?: { 335 | value: EverpayInfo 336 | timestamp: number 337 | } 338 | express?: { 339 | value: ExpressInfo 340 | timestamp: number 341 | } 342 | } 343 | 344 | export interface CliamParams { 345 | redpacketUUID: string 346 | claimBy: string 347 | salt: string 348 | createdAt: string 349 | signature?: string 350 | } 351 | 352 | export interface SignMessageResult { 353 | message: string 354 | sig: string 355 | } 356 | 357 | export interface VerifyMessageParams { 358 | account: string 359 | type: 'register' | 'sign' 360 | message: string 361 | sig: string 362 | } 363 | 364 | export interface VerifyMessageResult { 365 | publicId: string 366 | public: string 367 | } 368 | 369 | export interface SmartAccountAuthResult { 370 | account: string 371 | publicId: string 372 | public: string 373 | type: 'sign' | 'register' 374 | message: string 375 | sig: string 376 | } 377 | 378 | export abstract class EverpayBase { 379 | abstract getAccountChainType (address: string): ChainType 380 | abstract info (): Promise 381 | abstract expressInfo (): Promise 382 | abstract balance (params?: BalanceParams): Promise 383 | abstract txs (params: TxsParams): Promise 384 | abstract txsByAccount (params: TxsByAccountParams): Promise 385 | abstract txByHash (everHash: string): Promise 386 | abstract mintedTxByChainTxHash (chainTxHash: string): Promise 387 | abstract getEverpayTxMessage (everpayTxWithoutSig: EverpayTxWithoutSig): string 388 | abstract sendEverpayTx (everpayTxWithoutSig: EverpayTxWithoutSig): Promise 389 | abstract deposit (params: DepositParams): Promise 390 | abstract withdraw (params: WithdrawParams): Promise 391 | abstract transfer (params: TransferParams): Promise 392 | } 393 | -------------------------------------------------------------------------------- /src/utils/check.ts: -------------------------------------------------------------------------------- 1 | import { ChainType, Config, EverpayActionWithDeposit } from '../types' 2 | import { ERRORS } from './errors' 3 | 4 | interface CaseObject { 5 | [key: string]: string 6 | } 7 | 8 | const cases: CaseObject = { 9 | symbol: ERRORS.SYMBOL_NOT_FOUND, 10 | token: ERRORS.TOKEN_NOT_FOUND, 11 | account: ERRORS.ACCOUNT_NOT_FOUND, 12 | everHash: ERRORS.EVERHASH_NOT_FOUND, 13 | chainTxHash: ERRORS.CHAIN_TX_HASH_NOT_FOUND, 14 | tag: ERRORS.TAG_NOT_FOUND, 15 | action: ERRORS.INVALID_ACTION, 16 | to: ERRORS.TO_NOT_FOUND, 17 | ethConnectedSigner: ERRORS.ETH_SIGNER_NOT_FOUND 18 | } 19 | 20 | export const checkItem = (itemName: string, param?: unknown): void => { 21 | if (param === null || param === undefined || param === '' || param === 0) { 22 | throw new Error(cases[itemName]) 23 | } 24 | if (itemName === 'amount' && !((param as number) >= 0)) { 25 | throw new Error(ERRORS.INVALID_AMOUNT) 26 | } 27 | const actions = [ 28 | EverpayActionWithDeposit.deposit, 29 | EverpayActionWithDeposit.withdraw, 30 | EverpayActionWithDeposit.transfer, 31 | EverpayActionWithDeposit.bundle 32 | ] 33 | if (itemName === 'action' && !actions.includes(param as any)) { 34 | throw new Error(cases.action) 35 | } 36 | } 37 | 38 | export const checkParams = (params: Record): void => { 39 | Object.keys(params).forEach(key => checkItem(key, params[key])) 40 | } 41 | 42 | export const checkSignConfig = (accountType: ChainType, config: Config): void => { 43 | if (config.isSmartAccount ?? false) { 44 | checkItem('account', config.account) 45 | } else if (accountType === ChainType.ethereum) { 46 | checkItem('ethConnectedSigner', config.ethConnectedSigner) 47 | } else if (accountType === ChainType.arweave) { 48 | checkItem('arJWK', config.arJWK) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/errors.ts: -------------------------------------------------------------------------------- 1 | export enum ERRORS { 2 | INVALID_CONFIG_PARAMS = 'INVALID_CONFIG_PARAMS', 3 | REQUEST_5S_TIMEOUT = 'REQUEST_5S_TIMEOUT', 4 | ETH_SIGNER_NOT_FOUND = 'ETH_SIGNER_NOT_FOUND', 5 | AR_JWK_NOT_FOUND = 'AR_JWK_NOT_FOUND', 6 | TO_NOT_FOUND = 'TO_NOT_FOUND', 7 | SYMBOL_NOT_FOUND = 'SYMBOL_NOT_FOUND', 8 | TAG_NOT_FOUND = 'TAG_NOT_FOUND', 9 | TOKEN_NOT_FOUND = 'TOKEN_NOT_FOUND', 10 | ACCOUNT_NOT_FOUND = 'ACCOUNT_NOT_FOUND', 11 | ACCOUNT_INVALID = 'ACCOUNT_INVALID', 12 | EVERHASH_NOT_FOUND = 'EVERHASH_NOT_FOUND', 13 | CHAIN_TX_HASH_NOT_FOUND = 'CHAIN_TX_HASH_NOT_FOUND', 14 | INVALID_ACCOUNT_TYPE = 'INVALID_ACCOUNT_TYPE', 15 | INVALID_ACTION = 'INVALID_ACTION', 16 | INVALID_AMOUNT = 'INVALID_AMOUNT', 17 | WITHDRAW_AMOUNT_LESS_THAN_FEE = 'WITHDRAW_AMOUNT_LESS_THAN_FEE', 18 | INSUFFICIENT_QUICK_WITHDRAWAL_AMOUNT = 'INSUFFICIENT_QUICK_WITHDRAWAL_AMOUNT', 19 | WITHDRAW_TOKEN_NOT_SUPPORT_QUICK_MODE = 'WITHDRAW_TOKEN_NOT_SUPPORT_QUICK_MODE', 20 | DEPOSIT_ARWEAVE_PST_MUST_BE_INTEGER = 'DEPOSIT_ARWEAVE_PST_MUST_BE_INTEGER', 21 | PST_WITHDARW_TO_ARWEAVE_MUST_BE_INTEGER = 'PST_WITHDARW_TO_ARWEAVE_MUST_BE_INTEGER', 22 | SMART_ACCOUNT_DEPOSIT_NOT_SUPPORT = 'SMART_ACCOUNT_DEPOSIT_NOT_SUPPORT', 23 | MESSAGE_INCORRECT = 'MESSAGE_INCORRECT' 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/util.ts: -------------------------------------------------------------------------------- 1 | import { isAddress } from '@ethersproject/address' 2 | import isString from 'lodash/isString' 3 | import { v4 as uuidv4 } from 'uuid' 4 | import BN from 'bignumber.js' 5 | import { ERRORS } from './errors' 6 | import { BundleData, ChainType, InternalTransferItem, Token, FeeItem } from '../types' 7 | import { bundleInternalTxVersion } from '../config' 8 | import sha256 from 'crypto-js/sha256' 9 | import encHex from 'crypto-js/enc-hex' 10 | 11 | BN.config({ 12 | EXPONENTIAL_AT: 1000, 13 | }) 14 | 15 | export const toBN = (x: number | string | BN): BN => { 16 | if (isNaN(Number(x))) return new BN(0) 17 | if (x instanceof BN) return x 18 | 19 | if (typeof x === 'string') { 20 | if (x.indexOf('0x') === 0 || x.indexOf('-0x') === 0) { 21 | return new BN((x).replace('0x', ''), 16) 22 | } 23 | } 24 | return new BN(x) 25 | } 26 | 27 | export const fromUnitToDecimalBN = (x: number | string | BN, decimals: number): BN => { 28 | return toBN(x).times(toBN(10).pow(decimals)) 29 | } 30 | 31 | export const fromUnitToDecimal = (x: number | string | BN, decimals: number): string => { 32 | return fromUnitToDecimalBN(x, decimals).toString() 33 | } 34 | 35 | export const fromDecimalToUnitBN = (x: number | string | BN, decimals: number): BN => { 36 | return toBN(x).dividedBy(toBN(10).pow(decimals)) 37 | } 38 | 39 | export const fromDecimalToUnit = (x: number | string | BN, decimals: number): string => { 40 | return fromDecimalToUnitBN(x, decimals).toString() 41 | } 42 | 43 | export const getTimestamp = (): number => Math.round(Date.now() / 1000) 44 | 45 | export const getTokenByTag = (tag: string, tokenList?: Token[]): Token | undefined => { 46 | return tokenList?.find(t => matchTokenTag(genTokenTag(t), tag)) 47 | } 48 | 49 | export const isEthereumAddress = isAddress 50 | 51 | export const isArweaveAddress = (address: string): boolean => { 52 | return isString(address) && address.length === 43 && address.search(/[a-z0-9A-Z_-]{43}/g) === 0 53 | } 54 | 55 | export const isArweaveChainPSTMode = (token?: Token): boolean => { 56 | if (token == null) return false 57 | return token.crossChainInfoList[ChainType.arweave] != null && token.symbol.toUpperCase() !== 'AR' 58 | } 59 | 60 | export const isArweaveL2PSTTokenSymbol = (symbol: string): boolean => { 61 | return ['STAMP', 'U'].includes(symbol?.toUpperCase()) 62 | } 63 | 64 | export const isArweaveAOSTestToken = (token: Token): boolean => { 65 | return token.chainType.includes(ChainType.aostest) && ['AOCRED', '0RBT', 'TRUNK', 'EXP', 'NEXP', 'EXP(ARIO)', 'BP', 'AR'].includes(token?.symbol?.toUpperCase()) 66 | } 67 | 68 | export const isPermaswapHaloTokenSymbol = (symbol: string): boolean => { 69 | return symbol?.toUpperCase() === 'HALO' 70 | } 71 | 72 | export const getAccountChainType = (from: string): ChainType => { 73 | if (isEthereumAddress(from)) { 74 | return ChainType.ethereum 75 | } 76 | 77 | if (isArweaveAddress(from)) { 78 | return ChainType.arweave 79 | } 80 | 81 | throw new Error(ERRORS.INVALID_ACCOUNT_TYPE) 82 | } 83 | 84 | export const getTokenAddrByChainType = (token: Token, chainType: ChainType): string => { 85 | const crossChainInfo = token.crossChainInfoList[chainType] 86 | return crossChainInfo.targetTokenId 87 | } 88 | 89 | export const getChainDecimalByChainType = (token: Token, chainType: ChainType): number => { 90 | const crossChainInfo = token.crossChainInfoList[chainType] 91 | return crossChainInfo.targetDecimals 92 | } 93 | 94 | export const getTokenBurnFeeByChainType = (token: Token, feeItem: FeeItem, chainType: ChainType): string => { 95 | return feeItem.burnFeeMap[chainType] 96 | } 97 | 98 | export const genTokenTag = (token: Token): string => { 99 | const { chainType, symbol, id } = token 100 | const chainTypes = chainType.split(',') 101 | const tokenAddrs = id.split(',').map((addr: string, index: number) => { 102 | if ([ 103 | ChainType.ethereum, 104 | ChainType.bsc, 105 | ChainType.conflux, 106 | ChainType.moon, 107 | ChainType.platon, 108 | 'everpay' 109 | ].includes(chainTypes[index] as ChainType)) { 110 | return addr.toLowerCase() 111 | } 112 | return addr 113 | }) 114 | return `${chainType.toLowerCase()}-${symbol.toLowerCase()}-${tokenAddrs.join(',')}` 115 | } 116 | 117 | export const matchTokenTag = (tag1: string, tag2: string): boolean => { 118 | return tag1?.toLowerCase() === tag2?.toLowerCase() 119 | } 120 | 121 | interface GenExpressDataParams { 122 | chainType: ChainType 123 | to: string 124 | fee: string 125 | } 126 | interface ExpressData { 127 | appId: 'express' 128 | withdrawAction: 'pay' 129 | withdrawTo: string 130 | withdrawChainType: ChainType 131 | withdrawFee: string 132 | } 133 | 134 | export const genExpressData = (params: GenExpressDataParams): ExpressData => { 135 | const { chainType, to, fee } = params 136 | return { 137 | appId: 'express', 138 | withdrawAction: 'pay', 139 | withdrawTo: to, 140 | withdrawChainType: chainType, 141 | withdrawFee: fee 142 | } 143 | } 144 | 145 | interface GenBundleDataParams { 146 | tokenList: Token[] 147 | items: InternalTransferItem[] 148 | data?: string 149 | expiration: number 150 | } 151 | 152 | export const genBundleData = (params: GenBundleDataParams): BundleData => { 153 | const items = params.items.map((item: InternalTransferItem) => { 154 | const { tag, amount, from, to, data } = item 155 | const token = getTokenByTag(tag, params.tokenList) as Token 156 | // 注意:顺序必须与后端保持一致,来让 JSON.stringify() 生成的字符串顺序与后端也一致 157 | return { 158 | tag: genTokenTag(token), 159 | chainID: token.chainID, 160 | from, 161 | to, 162 | data: data != null ? data : '', 163 | amount: fromUnitToDecimal(amount, token.decimals) 164 | } 165 | }) 166 | const salt = uuidv4() 167 | const version = bundleInternalTxVersion 168 | return { 169 | // 注意:顺序必须与后端保持一致,来让 JSON.stringify() 生成的字符串顺序与后端也一致 170 | items, 171 | expiration: params.expiration, 172 | salt, 173 | version, 174 | data: params.data != null ? params.data : '' 175 | } 176 | } 177 | 178 | export const isSmartAccount = (account: string): boolean => { 179 | return /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/.test(account) 180 | } 181 | 182 | export const getUserId = (debug: boolean, everId: string, expiration?: number): string => { 183 | const everpayChainIdStr = debug ? '5' : '1' 184 | // hash the message 185 | const hash = sha256(`${everId.trim().toLowerCase()}${everpayChainIdStr}${expiration ? expiration : ''}`) 186 | const arrBuffer = hexToUint8Array(hash.toString()).slice(0, 10) 187 | const b64encoded = window.btoa(String.fromCharCode.apply(null, arrBuffer as any)) 188 | return b64encoded 189 | } 190 | 191 | const checkSum = (idBytes: Uint8Array): Uint8Array => { 192 | const hash = sha256(encHex.parse(uint8ArrayToHex(Uint8Array.from([...Buffer.from('eid'), ...idBytes])))) 193 | return hexToUint8Array(hash.toString()).slice(0, 2) 194 | } 195 | 196 | export const uint8ArrayToHex = (uint8Array: Uint8Array): string => { 197 | return [...uint8Array].map((b) => { 198 | return b.toString(16).padStart(2, '0') 199 | }).join('') 200 | } 201 | 202 | export const hexToUint8Array = (hexString: string): Uint8Array => 203 | Uint8Array.from((hexString.match(/.{1,2}/g) as any).map((byte: any) => parseInt(byte, 16))) 204 | 205 | export const genEverId = (email: string): string => { 206 | const str = email.toLowerCase().trim() 207 | const hash = sha256(str) 208 | const idBytes = hexToUint8Array(hash.toString()) 209 | const sum = checkSum(idBytes) 210 | const concatArray = Uint8Array.from([...idBytes, ...sum]) 211 | return `eid${uint8ArrayToHex(concatArray)}` 212 | } 213 | 214 | export const isNodeJs = (): boolean => 215 | typeof process !== 'undefined' && 216 | process.versions != null && 217 | process.versions.node != null 218 | 219 | const mobileRE = /(android|bb\d+|meego).+mobile|armv7l|avantgo|bada\/|ipad|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series[46]0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i 220 | 221 | const tabletRE = /android|ipad|playbook|silk/i 222 | 223 | export const isMobileDevice = (opts?: any): boolean => { 224 | if (opts == null) opts = {} 225 | let ua = opts.ua 226 | if (ua == null && typeof navigator !== 'undefined') ua = navigator.userAgent 227 | if (ua?.headers != null && typeof ua.headers['user-agent'] === 'string') { 228 | ua = ua.headers['user-agent'] 229 | } 230 | if (typeof ua !== 'string') return false 231 | 232 | let result = mobileRE.test(ua) || (opts?.tablet != null && tabletRE.test(ua)) 233 | 234 | if ( 235 | !result && 236 | opts?.tablet != null && 237 | opts?.featureDetect != null && 238 | navigator?.maxTouchPoints > 1 && 239 | ua.includes('Macintosh') && 240 | ua.includes('Safari') 241 | ) { 242 | result = true 243 | } 244 | 245 | return result 246 | } 247 | 248 | export const isMobile = isMobileDevice({ 249 | tablet: true, 250 | featureDetect: true 251 | }) -------------------------------------------------------------------------------- /test/+balance.test.ts: -------------------------------------------------------------------------------- 1 | import { ethWalletHasUSDT, ethWalletHasUSDT2 } from './constants/wallet' 2 | import Everpay from '../src/index' 3 | import { ERRORS } from '../src/utils/errors' 4 | 5 | const everpay1 = new Everpay({ 6 | account: ethWalletHasUSDT.address, 7 | debug: true 8 | }) 9 | 10 | const everpay2 = new Everpay({ 11 | debug: true 12 | }) 13 | 14 | describe('test balance', () => { 15 | test(`${ethWalletHasUSDT.address} eth balance is greater than 0`, async () => { 16 | return await everpay1.balance({ 17 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000' 18 | }).then(balance => { 19 | console.log(`${ethWalletHasUSDT.address} eth balance: ${balance}`) 20 | expect(+balance).toBeGreaterThanOrEqual(0) 21 | }) 22 | }) 23 | 24 | test(`${ethWalletHasUSDT.address} ar balance is greater than 0`, async () => { 25 | return await everpay1.balance({ 26 | tag: 'arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8' 27 | }).then(balance => { 28 | console.log(`${ethWalletHasUSDT.address} ar balance: ${balance}`) 29 | expect(+balance).toBeGreaterThan(0) 30 | }) 31 | }) 32 | 33 | test(`${ethWalletHasUSDT.address} usdt balance is greater than 0`, async () => { 34 | return await everpay1.balance({ 35 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712' 36 | }).then(balance => { 37 | console.log(`${ethWalletHasUSDT.address} usdt balance: ${balance}`) 38 | expect(+balance).toBeGreaterThan(0) 39 | }) 40 | }) 41 | 42 | test(`${ethWalletHasUSDT2.address} usdt balance is greater than 0`, async () => { 43 | return await everpay2.balance({ 44 | account: ethWalletHasUSDT.address, 45 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712' 46 | }).then(balance => { 47 | console.log(`${ethWalletHasUSDT2.address} balance: ${balance}`) 48 | expect(+balance).toBeGreaterThan(0) 49 | }) 50 | }) 51 | }) 52 | 53 | describe('test balance error', () => { 54 | test('no account', async () => { 55 | await expect( 56 | everpay2.balance({ 57 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000' 58 | }) 59 | ) 60 | .rejects 61 | .toThrow(ERRORS.ACCOUNT_NOT_FOUND) 62 | }) 63 | 64 | test('no tag', async () => { 65 | await expect( 66 | everpay2.balance({ 67 | account: ethWalletHasUSDT2.address, 68 | tag: '' 69 | }) 70 | ) 71 | .rejects 72 | .toThrow(ERRORS.TAG_NOT_FOUND) 73 | }) 74 | 75 | test('no token', async () => { 76 | await expect( 77 | everpay2.balance({ 78 | account: ethWalletHasUSDT2.address, 79 | tag: 'btc' 80 | }) 81 | ) 82 | .rejects 83 | .toThrow(ERRORS.TOKEN_NOT_FOUND) 84 | }) 85 | }) 86 | -------------------------------------------------------------------------------- /test/+balances.test.ts: -------------------------------------------------------------------------------- 1 | import { ethWalletHasUSDT, ethWalletHasUSDT2 } from './constants/wallet' 2 | import Everpay from '../src/index' 3 | import { ERRORS } from '../src/utils/errors' 4 | 5 | const everpay1 = new Everpay({ 6 | account: ethWalletHasUSDT.address, 7 | debug: true 8 | }) 9 | 10 | const everpay2 = new Everpay({ 11 | debug: true 12 | }) 13 | 14 | describe('test balance', () => { 15 | test(`${ethWalletHasUSDT.address} eth balance is greater than 0`, async () => { 16 | return await everpay1.balances({ 17 | }).then(balances => { 18 | console.log('balances', balances) 19 | expect(balances.length).toBeGreaterThan(0) 20 | expect(+balances.find(i => i.symbol === 'ETH').balance).toBeGreaterThanOrEqual(0) 21 | }) 22 | }) 23 | 24 | test(`${ethWalletHasUSDT2.address} tusc balance is greater than 0`, async () => { 25 | return await everpay2.balances({ 26 | account: ethWalletHasUSDT.address 27 | }).then(balances => { 28 | expect(balances.length).toBeGreaterThan(0) 29 | expect(+balances.find(i => i.symbol === 'TUSDC').balance).toBeGreaterThan(0) 30 | }) 31 | }) 32 | }) 33 | 34 | describe('test balance error', () => { 35 | test('no account', async () => { 36 | await expect( 37 | everpay2.balances() 38 | ) 39 | .rejects 40 | .toThrow(ERRORS.ACCOUNT_NOT_FOUND) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /test/+bundle.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { ethWalletHasUSDT, arWallet1 } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | import { genTokenTag } from '../src/utils/util' 5 | import hashPersonalMessage from '../src/lib/hashPersonalMessage' 6 | import Arweave from 'arweave' 7 | import { b64UrlToBuffer } from 'arweave/web/lib/utils' 8 | import { ChainType } from '../src/types' 9 | 10 | const options = { 11 | host: 'arweave.net', // Hostname or IP address for a Arweave host 12 | port: 443, // Port 13 | protocol: 'https', // Network protocol http or https 14 | timeout: 20000, // Network request timeouts in milliseconds 15 | logging: false // Enable network request logging 16 | } 17 | 18 | const provider = new ethers.providers.InfuraProvider('kovan') 19 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 20 | 21 | const everpayEthAccount = new Everpay({ 22 | account: ethWalletHasUSDT.address, 23 | ethConnectedSigner: signer, 24 | chainType: ChainType.ethereum, 25 | debug: true 26 | }) 27 | 28 | const everpayArAccount = new Everpay({ 29 | account: arWallet1.address, 30 | arJWK: arWallet1.jwk, 31 | chainType: ChainType.arweave, 32 | debug: true 33 | }) 34 | 35 | describe('test bundle data generate & sign', () => { 36 | test('test bundle data generate', async () => { 37 | const everpayInfo = await everpayArAccount.info() 38 | const bundleData = await everpayArAccount.getBundleData([ 39 | { 40 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', 41 | from: ethWalletHasUSDT.address, 42 | to: arWallet1.address, 43 | amount: '0.001' 44 | }, { 45 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 46 | from: arWallet1.address, 47 | to: ethWalletHasUSDT.address, 48 | amount: '10' 49 | } 50 | ]) 51 | 52 | expect(bundleData.items[0].tag).toBe(genTokenTag(everpayInfo.tokenList.find(t => t.symbol.toUpperCase() === 'ETH'))) 53 | expect(bundleData.items[1].tag).toBe(genTokenTag(everpayInfo.tokenList.find(t => t.symbol.toUpperCase() === 'TUSDC'))) 54 | }) 55 | 56 | test('test sign bundle data', async () => { 57 | const bundleData = await everpayArAccount.getBundleData([ 58 | { 59 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', 60 | from: ethWalletHasUSDT.address, 61 | to: arWallet1.address, 62 | amount: '0.001' 63 | }, { 64 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 65 | from: arWallet1.address, 66 | to: ethWalletHasUSDT.address, 67 | amount: '10' 68 | } 69 | ]) 70 | const bundleDataWithSigs1 = await everpayArAccount.signBundleData(bundleData) 71 | const bundleDataWithSigs2 = await everpayEthAccount.signBundleData(bundleDataWithSigs1) 72 | 73 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(JSON.stringify(bundleData))) 74 | const arweave = Arweave.init(options) 75 | const verified1 = await arweave.crypto.verify( 76 | arWallet1.jwk.n, 77 | personalMsgHashBuffer, 78 | b64UrlToBuffer(bundleDataWithSigs2.sigs[arWallet1.address].split(',')[0]) 79 | ) 80 | expect(verified1).toBe(true) 81 | 82 | const recoveredAddress = await ethers.utils.verifyMessage(JSON.stringify(bundleData), bundleDataWithSigs2.sigs[ethWalletHasUSDT.address]) 83 | expect(recoveredAddress.toLowerCase()).toBe(ethWalletHasUSDT.address.toLowerCase()) 84 | }) 85 | 86 | test('send bundle tx', async () => { 87 | const bundleData = await everpayArAccount.getBundleData([ 88 | { 89 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 90 | from: ethWalletHasUSDT.address, 91 | to: arWallet1.address, 92 | amount: '0.001' 93 | }, { 94 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 95 | from: arWallet1.address, 96 | to: ethWalletHasUSDT.address, 97 | amount: '10' 98 | } 99 | ]) 100 | const bundleDataWithSigs1 = await everpayArAccount.signBundleData(bundleData) 101 | const bundleDataWithSigs2 = await everpayEthAccount.signBundleData(bundleDataWithSigs1) 102 | 103 | const bundleResult = await everpayArAccount.bundle({ 104 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', 105 | to: arWallet1.address, 106 | amount: '0', 107 | data: { 108 | bundle: bundleDataWithSigs2 109 | } 110 | }) 111 | expect(bundleResult.status).toBe('ok') 112 | 113 | const everpayTransaction = await everpayArAccount.txByHash(bundleResult.everHash) 114 | expect(JSON.parse(everpayTransaction.internalStatus).status).toBe('success') 115 | }) 116 | }) 117 | -------------------------------------------------------------------------------- /test/+deposit.conflux.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay, { ChainType } from '../src/index' 2 | import { ethWalletHasUSDT } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | 5 | const providerURL = 'https://evmtestnet.confluxrpc.com' 6 | // Define Provider 7 | const provider = new ethers.providers.StaticJsonRpcProvider(providerURL, { 8 | chainId: 71, 9 | name: 'Conflux eSpace (Testnet)' 10 | }) 11 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 12 | 13 | const everpay = new Everpay({ 14 | account: ethWalletHasUSDT.address, 15 | chainType: ChainType.conflux, 16 | ethConnectedSigner: signer, 17 | debug: true 18 | }) 19 | 20 | test(`check Conflux ${ethWalletHasUSDT.address} deposit cfx`, async () => { 21 | return await everpay.deposit({ 22 | tag: 'conflux-cfx-0x0000000000000000000000000000000000000000', 23 | amount: '0.01' 24 | }).then(ethTx => { 25 | console.log('ethTx', ethTx) 26 | expect(ethTx).toBeTruthy() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/+deposit.ethereum.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { ethWalletHasUSDT } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | 5 | const providerURL = 'https://rpc.ankr.com/eth_goerli' 6 | // Define Provider 7 | const provider = new ethers.providers.StaticJsonRpcProvider(providerURL, { 8 | chainId: 5, 9 | name: 'Görli' 10 | }) 11 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 12 | 13 | const everpay = new Everpay({ 14 | account: ethWalletHasUSDT.address, 15 | ethConnectedSigner: signer, 16 | debug: true 17 | }) 18 | 19 | test(`check ${ethWalletHasUSDT.address} deposit eth`, async () => { 20 | return await everpay.deposit({ 21 | tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', 22 | amount: '0.000001' 23 | }).then(ethTx => { 24 | console.log('ethTx', ethTx) 25 | expect(ethTx).toBeTruthy() 26 | }) 27 | }) 28 | 29 | // test(`check ${ethWalletHasUSDT.address} deposit usdt`, async () => { 30 | // return await everpay.deposit({ 31 | // chainType: ChainType.ethereum, 32 | // symbol: 'usd', 33 | // amount: '1000' 34 | // }).then(usdtTx => { 35 | // console.log('usdtTx', usdtTx) 36 | // expect(usdtTx).toBeTruthy() 37 | // }) 38 | // }) 39 | -------------------------------------------------------------------------------- /test/+deposit.moonbase.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay, { ChainType } from '../src/index' 2 | import { ethWalletHasUSDT } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | 5 | const providerURL = 'https://rpc.api.moonbase.moonbeam.network' 6 | // Define Provider 7 | const provider = new ethers.providers.StaticJsonRpcProvider(providerURL, { 8 | chainId: 1287, 9 | name: 'moonbase-alphanet' 10 | }) 11 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 12 | 13 | const everpay = new Everpay({ 14 | account: ethWalletHasUSDT.address, 15 | chainType: ChainType.moon, 16 | ethConnectedSigner: signer, 17 | debug: true 18 | }) 19 | 20 | test(`check moonbase ${ethWalletHasUSDT.address} deposit dev`, async () => { 21 | return await everpay.deposit({ 22 | tag: 'moonbase-dev-0x0000000000000000000000000000000000000000', 23 | amount: '0.01' 24 | }).then(ethTx => { 25 | console.log('ethTx', ethTx) 26 | expect(ethTx).toBeTruthy() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /test/+fee.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | 3 | const everpay = new Everpay({ 4 | debug: true 5 | }) 6 | 7 | test('everpey fees got correct', async () => { 8 | return await everpay.fees().then(fees => { 9 | expect(fees.length).toBeGreaterThan(0) 10 | expect(fees.find(t => t.tokenTag.toLowerCase().includes('ethereum'))).toBeTruthy() 11 | }) 12 | }) 13 | 14 | test('everpey fee got correct', async () => { 15 | return await everpay.fee('arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8').then(fee => { 16 | expect(fee.burnFeeMap.arweave).toBeTruthy() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/+info.test.ts: -------------------------------------------------------------------------------- 1 | import { isString } from 'lodash' 2 | import Everpay from '../src/index' 3 | 4 | const everpay = new Everpay({ 5 | debug: true 6 | }) 7 | 8 | test('everpey info got correct', async () => { 9 | return await everpay.info().then(info => { 10 | const { ethLocker, owner, ethChainID, feeRecipient, tokenList } = info 11 | for (const item of [ethLocker, owner, feeRecipient]) { 12 | expect(isString(item)).toBe(true) 13 | } 14 | expect(+ethChainID).toBeGreaterThanOrEqual(0) 15 | expect(tokenList.length).toBeGreaterThan(0) 16 | expect(tokenList.find(t => t.symbol.toLowerCase() === 'eth')).toBeTruthy() 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/+sign.arweave.test.ts: -------------------------------------------------------------------------------- 1 | import Arweave from 'arweave' 2 | import arweaveLib from '../src/lib/arweave' 3 | import hashPersonalMessage from '../src/lib/hashPersonalMessage' 4 | import { getEverpayTxMessage, signMessageAsync } from '../src/lib/sign' 5 | import { b64UrlToBuffer } from 'arweave/web/lib/utils' 6 | import { arWallet1 } from './constants/wallet' 7 | import { ChainType } from '../src/types' 8 | 9 | const options = { 10 | host: 'arweave.net', // Hostname or IP address for a Arweave host 11 | port: 443, // Port 12 | protocol: 'https', // Network protocol http or https 13 | timeout: 20000, // Network request timeouts in milliseconds 14 | logging: false // Enable network request logging 15 | } 16 | 17 | test('check arweaveLib.signMessageAsync', async () => { 18 | const bundleData = { items: [{ tag: 'ethereum-eth-0x0000000000000000000000000000000000000000', chainID: '42', from: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo', to: '0x9430dBaAD43b5e0Bebe142f84582111Dd1D7cd00', amount: '606000000000000000' }, { tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', chainID: '42', from: '0x9430dBaAD43b5e0Bebe142f84582111Dd1D7cd00', to: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo', amount: '526526000000' }], expiration: 1629791059, salt: '83ea4d31-5819-4b02-8c1a-1e5d361d03be', version: 'v1' } 19 | const { sig } = await signMessageAsync({ 20 | account: arWallet1.address, 21 | arJWK: arWallet1.jwk, 22 | chainType: ChainType.arweave 23 | }, JSON.stringify(bundleData)) 24 | const msgBase64WithArOwner = sig 25 | 26 | const personalMsgHashBuffer = hashPersonalMessage(Buffer.from(JSON.stringify(bundleData))) 27 | const arweave = Arweave.init(options) 28 | const verified = await arweave.crypto.verify(arWallet1.jwk.n, personalMsgHashBuffer, b64UrlToBuffer(msgBase64WithArOwner.split(',')[0])) 29 | expect(verified).toBe(true) 30 | }) 31 | 32 | test('check arweaveLib.signMessageAsync', async () => { 33 | const everHash = '0xdfc9ef6613b52c043d68496ec1bd2db0cd2d5891d878208938c4ce31a4826274' 34 | const dataBuf = Buffer.from(everHash.slice(2), 'hex') 35 | const msgBase64WithArOwner = await arweaveLib.signMessageAsync(arWallet1.jwk, arWallet1.address, everHash) 36 | 37 | const arweave = Arweave.init(options) 38 | const verified = await arweave.crypto.verify(arWallet1.jwk.n, dataBuf, b64UrlToBuffer(msgBase64WithArOwner.split(',')[0])) 39 | expect(verified).toBe(true) 40 | }) 41 | 42 | test('check exist test data', async () => { 43 | const json = { tokenSymbol: 'USDT', action: 'transfer', from: '5npqybdisipjzpeyixuz7beh_w7bek_mb8hxbd3ohxo', to: '0x26361130d5d6e798e9319114643af8c868412859', amount: '1000000', fee: '0', feeRecipient: '0x6451eb7f668de69fb4c943db72bcf2a73deec6b1', nonce: '1621849358202', tokenID: '0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee', chainType: 'ethereum', chainID: '42', data: '{"arOwner":"odtNk97a4PARR0I8g3kQpzlFVmPg-udyjfl81fbTioyP2pEw5tP5A1-FVqR-QFFPskW-j7yAze5usYNWHEir7oVQ9d9bbkcZIDEPqwSTO1JoD1BKXeeBK0xsmiSgxeY7uuRXWdhXREhlmIMsV8ObakEeXdbbxbs89XaZHBuES7boASrRVDXRz_mhMu6u_58OdLeMwR3I1BCH6nphNGVOehA7GOOqEBvtesBset0bNaLCb0JpSg5ZW_0AGLP-XydzE3IPLLx4NQEEJY21y8fChxYM4jntI78l5hojp9NlmS69EXlj0PoMjsbaWaz9WtnZaMAbnaOGAHhv8Y_TNmBI0FHpqHaGPP906Mnrgdm3tl2L40EX-Q6-liNVkB56CmPxXzSesu-4x5LLYxQ-aX3W6Hj7RCDTacxqUJHzOrhJqXSx6Jx0t8CwyfReMgVv4p5t1C3OZ8yYbJ_H3LdkeriVniaC5jQdMyIJ6QBMzr1XdXIw9WuEG2kCIYtvOp2qDuu9o2SY-9W4Yv7VWRDfWO38xxR4ZO65MMAdZxeaZ4w8sK_owH46Wm0XoT3Al-LPypaeijWqlHEu4R8c2ersD3xkDvXC_lNtaQw_qyfI3UEH5fWupY4zhZeDGkvXQh32Fv4CxlZL58iUHv9SvR7p5LgBCC3AVUbn7Sqc4xPUCZMj-Tc"}', version: 'v1' } 44 | const message = getEverpayTxMessage(json as any) 45 | const personalMsgHash = hashPersonalMessage(Buffer.from(message)) 46 | const dataBuf = personalMsgHash 47 | const msgBase64 = 'FPKBn1Nd3eshEP8C3ct8oqzi60VkpE1hDwXsLW9tOdDEOYkKaZ5OCjJWISY1zGnNTb9maj3GGxQkPCMITV12D8bNbGFo+Le/xlnyhXkXth+txqXnq8xzoCXmusVkdgcqF5jOg2wLes/6n94qXMnXqGct8yEDYfkq0vs9asexOHfE6Vep9IcgrpQfUTkTJnr1Uk+pK0x2LFAHrfzjilJvmNFmTj170iWAbnSQaWUSHsNlWp+Ku79SG7SaUudJj2EC+QN/KLdGQB6u+dicj0j8s25ARLcEUO0aq0vk+Xg3uZVm4oCpQmvlCkz8u50yWxtZDzrG3vaciMto2kMrNWDetMLw9gFYz7MbiqsQ4Eo0pjKu/QpEl74l9ReWn0vDY06wwC9HDhMozyi9p+aNtv20DnZWdv34YXpH5qs/1JMSHWE0Zzl+OOcOrpQI2KBw8PYa/iaNk7LLVD5ZlizHdOi1diIoHXNi7aBeIqjCnodTdRnzyotqMZwdynFBLYVmSe+iTxTmBjMrU0Iq7uFuoEBmUOV+bi9iN65taH9ZxVH2Rt2Msk0Ql4wmL55GnIF09H9nRh0/gc3A7hpqV44jrKWXq806xqLOpZfz6DEvDTm8N/L4aqOfhhShZKUqa3ru9CBazh8OZiOMdnZqpOyDB998h9A8TMufW1+YbirTJZBJ4Iw=' 48 | 49 | const arweave = Arweave.init(options) 50 | const verified = await arweave.crypto.verify(arWallet1.jwk.n, dataBuf, Buffer.from(msgBase64, 'base64')) 51 | expect(verified).toBe(true) 52 | }) 53 | 54 | test('check exist test data2 using arweave to string to base64', async () => { 55 | const json = { tokenSymbol: 'USDT', action: 'transfer', from: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo', to: '0x26361130d5d6E798E9319114643AF8c868412859', amount: '1', fee: '0', feeRecipient: '0x6451eb7f668de69fb4c943db72bcf2a73deec6b1', nonce: '1621924791625', tokenID: '0xd85476c906b5301e8e9eb58d174a6f96b9dfc5ee', chainType: 'ethereum', chainID: '42', data: '{"arOwner":"odtNk97a4PARR0I8g3kQpzlFVmPg-udyjfl81fbTioyP2pEw5tP5A1-FVqR-QFFPskW-j7yAze5usYNWHEir7oVQ9d9bbkcZIDEPqwSTO1JoD1BKXeeBK0xsmiSgxeY7uuRXWdhXREhlmIMsV8ObakEeXdbbxbs89XaZHBuES7boASrRVDXRz_mhMu6u_58OdLeMwR3I1BCH6nphNGVOehA7GOOqEBvtesBset0bNaLCb0JpSg5ZW_0AGLP-XydzE3IPLLx4NQEEJY21y8fChxYM4jntI78l5hojp9NlmS69EXlj0PoMjsbaWaz9WtnZaMAbnaOGAHhv8Y_TNmBI0FHpqHaGPP906Mnrgdm3tl2L40EX-Q6-liNVkB56CmPxXzSesu-4x5LLYxQ-aX3W6Hj7RCDTacxqUJHzOrhJqXSx6Jx0t8CwyfReMgVv4p5t1C3OZ8yYbJ_H3LdkeriVniaC5jQdMyIJ6QBMzr1XdXIw9WuEG2kCIYtvOp2qDuu9o2SY-9W4Yv7VWRDfWO38xxR4ZO65MMAdZxeaZ4w8sK_owH46Wm0XoT3Al-LPypaeijWqlHEu4R8c2ersD3xkDvXC_lNtaQw_qyfI3UEH5fWupY4zhZeDGkvXQh32Fv4CxlZL58iUHv9SvR7p5LgBCC3AVUbn7Sqc4xPUCZMj-Tc"}', version: 'v1' } 56 | const message = getEverpayTxMessage(json as any) 57 | const personalMsgHash = hashPersonalMessage(Buffer.from(message)) 58 | const dataBuf = personalMsgHash 59 | const msgBase64 = 'Elq-BDzh_-PFKriOiAPfshuYLrqQmbPsGDihwpAHVnJjPcCb4Tm731ESiqu8zecTZPQ9zEOgbZjjHFUyroDtDzQf_ONNULoGkMnKqrGz8mFgeH7C-eHeWbD2tb5B0F_NBqXXDctos8siBThhfM3ESvfO3hhtmmX7w4hSke-oY18WEQKiAi3uBj0ERSwUidhNX369-qPHfR6NxJkNI1VUCvzOwq4jIFhYsdjwtS9LE78qHSzhk1uImTWjSZ8dX4YtU3BGyyjT9JaV0s8AcUZQEx0TPqZGpWZ6TnPuHaXq_GMofGhbVdPO_WRxMVJUTT_2JhqXG1XRhVVqT8cz_LLLdtg0S8xyJ7IMqq1WVkfQrh3D9684o17UryKuP9RYRYbNailYCysoDMBBs5jFcnLMb0_yWxaU2ZhNdo8__lSxK_csdrSvRFanvwkCzLNgom4Ex5boknrnBG_rLIu2Gd7P4PWpgopfCuuQuIl9Sp-jG3i54ylJ9GycL9G-e3ieLKEJwa6Wy1Va2b8Sxm1YoMEn4lTkb_FLq9I0phMkYT7kg9R_JJDIFXo4aFlipN9B8oiApgaBXqBfC1FgCetdCIGNrE3TGBDfp-f7dVZLsvk9bayp9Ii8SeVqyW3A6mblTx9D2K4MwKyxB-OEm-O-yUWDDUIUZCXOp5FW0uVcTe5vPvw' 60 | 61 | const arweave = Arweave.init(options) 62 | const verified = await arweave.crypto.verify(arWallet1.jwk.n, dataBuf, b64UrlToBuffer(msgBase64)) 63 | expect(verified).toBe(true) 64 | }) 65 | -------------------------------------------------------------------------------- /test/+sign.ethereum.test.ts: -------------------------------------------------------------------------------- 1 | import ethereumLib from '../src/lib/ethereum' 2 | import { ethWalletHasUSDT } from './constants/wallet' 3 | 4 | import { ethers } from 'ethers' 5 | 6 | const provider = new ethers.providers.InfuraProvider('kovan') 7 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 8 | 9 | test('check arweaveLib.signMessageAsync', async () => { 10 | const message = 'hello world' 11 | const signature = await ethereumLib.signMessageAsync(signer, ethWalletHasUSDT.address, message) 12 | const recoveredAddress = await ethers.utils.verifyMessage(message, signature) 13 | expect(recoveredAddress.toLowerCase()).toBe(ethWalletHasUSDT.address.toLowerCase()) 14 | }) 15 | -------------------------------------------------------------------------------- /test/+signMessage.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { ethWalletHasUSDT, arWallet1 } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | 5 | const provider = new ethers.providers.InfuraProvider('kovan') 6 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 7 | 8 | const everpayEVM = new Everpay({ 9 | debug: true, 10 | account: ethWalletHasUSDT.address, 11 | chainType: 'ethereum' as any, 12 | ethConnectedSigner: signer 13 | }) 14 | 15 | const everpayAR = new Everpay({ 16 | account: arWallet1.address, 17 | arJWK: arWallet1.jwk, 18 | chainType: 'arweave' as any, 19 | debug: true 20 | }) 21 | 22 | describe('signMessage for EVM', () => { 23 | test('signMessage EVM should be OK', async () => { 24 | const message = 'verify' 25 | const signResult = await everpayEVM.signMessage(message) 26 | expect(signResult.message).toBe(message) 27 | return await everpayEVM.verifyMessage({ 28 | type: 'sign', 29 | message, 30 | account: ethWalletHasUSDT.address, 31 | sig: signResult.sig 32 | }).then(result => { 33 | expect(result.public).toBeTruthy() 34 | expect(result.publicId.toLowerCase()).toBe(ethWalletHasUSDT.address.toLowerCase()) 35 | }) 36 | }) 37 | }) 38 | 39 | describe('signMessage for Arweave', () => { 40 | test('signMessage for Arweave should be OK', async () => { 41 | const account = arWallet1.address 42 | const message = 'verify' 43 | const signResult = await everpayAR.signMessage(message) 44 | expect(signResult.message).toBe(message) 45 | return await everpayAR.verifyMessage({ 46 | type: 'sign', 47 | message, 48 | account, 49 | sig: signResult.sig 50 | }).then(result => { 51 | expect(result.public).toBeTruthy() 52 | expect(result.publicId.toLowerCase()).toBe(account.toLowerCase()) 53 | }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/+transfer.arweave.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet2, ethWalletHasUSDT } from './constants/wallet' 3 | import { ChainType } from '../src/types' 4 | 5 | const everpay = new Everpay({ 6 | account: arWallet2.address, 7 | arJWK: arWallet2.jwk, 8 | chainType: ChainType.arweave, 9 | debug: true 10 | }) 11 | 12 | test(`check ${arWallet2.address} transfer ar`, async () => { 13 | return await everpay.transfer({ 14 | tag: 'arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8', 15 | amount: '0.0000000001', 16 | to: ethWalletHasUSDT.address, 17 | data: { hello: 'world', this: 'is everpay' } 18 | }).then((transferResult) => { 19 | console.log('transfer ar result', transferResult) 20 | expect(transferResult.status).toBe('ok') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/+transfer.ethereum.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { ethWalletHasUSDT, ethWalletHasUSDT2 } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | 5 | const provider = new ethers.providers.InfuraProvider('kovan') 6 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 7 | 8 | const everpay = new Everpay({ 9 | account: ethWalletHasUSDT.address, 10 | ethConnectedSigner: signer, 11 | debug: true 12 | }) 13 | 14 | test(`check ${ethWalletHasUSDT.address} transfer usdt to ${ethWalletHasUSDT2.address}`, async () => { 15 | return await everpay.transfer({ 16 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 17 | amount: '5.26', 18 | to: '5NPqYBdIsIpJzPeYixuz7BEH_W7BEk_mb8HxBD3OHXo', 19 | data: { hello: 'world', this: 'is everpay' } 20 | }).then(transferResult => { 21 | console.log('transfer usdt result', transferResult) 22 | expect(transferResult.status).toBe('ok') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /test/+transfer.pst.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet2, ethWalletHasUSDT } from './constants/wallet' 3 | import { ChainType } from '../src/types' 4 | 5 | test(`${arWallet2.address} transfer VRT to ${ethWalletHasUSDT.address}`, async () => { 6 | const everpay = new Everpay({ 7 | account: arWallet2.address, 8 | arJWK: arWallet2.jwk, 9 | chainType: ChainType.arweave, 10 | debug: true 11 | }) 12 | 13 | return await everpay.transfer({ 14 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 15 | amount: '0.000010001', 16 | to: ethWalletHasUSDT.address 17 | }).then(withdrawResult => { 18 | console.log('withdrawResult', withdrawResult) 19 | expect(withdrawResult.status).toBe('ok') 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /test/+txs.test.ts: -------------------------------------------------------------------------------- 1 | // import { isString } from 'lodash' 2 | // import { EverpayAction } from '../src/types' 3 | import Everpay from '../src/index' 4 | 5 | const everpay = new Everpay({ 6 | debug: true 7 | }) 8 | 9 | test('everpey txs got correct', async () => { 10 | return await everpay.txs({ page: 1 }).then(txResult => { 11 | expect(txResult.txs.length).toBeGreaterThan(0) 12 | expect(txResult.currentPage).toBe(1) 13 | // for (const tx of transactions) { 14 | // const { action, data } = tx 15 | // if (action === EverpayAction.transfer) { 16 | // expect(data).toBe('') 17 | // } else if (action === EverpayAction.withdraw) { 18 | // expect(data[0]).toBe('{') 19 | // } 20 | // } 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /test/+txsByAccount.test.ts: -------------------------------------------------------------------------------- 1 | // import { isString } from 'lodash' 2 | // import { EverpayAction } from '../src/types' 3 | import Everpay, { EverpayActionWithDeposit } from '../src/index' 4 | 5 | const everpay = new Everpay({ 6 | account: '0x26361130d5d6E798E9319114643AF8c868412859', 7 | debug: true 8 | }) 9 | 10 | test('everpey txsByAccount got correct', async () => { 11 | return await everpay.txsByAccount({ page: 1, tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', action: EverpayActionWithDeposit.transfer }).then(txResult => { 12 | expect(txResult.txs.length).toBeGreaterThan(0) 13 | expect(txResult.currentPage).toBe(1) 14 | expect(txResult.txs.every(item => item.action === EverpayActionWithDeposit.transfer)).toBe(true) 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/+verifyMessage.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | 3 | const everpay = new Everpay({ 4 | debug: true 5 | }) 6 | 7 | describe('verifyMessage for EVM', () => { 8 | test('verifyMessage EVM should be OK', async () => { 9 | const account = '0xC90D9eA24864415190CCD6F91B279fbfe7A159d2' 10 | const everHash = '0xdc1a35ccc02c4e94be671bc6520510b20eaef57d4b15429b62c78162b25d6f85' 11 | const tx = await everpay.txByHash(everHash) 12 | const message = everpay.getEverpayTxMessage(tx as any) 13 | const sig = tx.sig 14 | return await everpay.verifyMessage({ 15 | type: 'sign', 16 | message, 17 | account, 18 | sig 19 | }).then(result => { 20 | expect(result.public).toBeTruthy() 21 | expect(result.publicId.toLowerCase()).toBe(account.toLowerCase()) 22 | }) 23 | }) 24 | }) 25 | 26 | describe('verifyMessage for Arweave', () => { 27 | test('verifyMessage Arweave should be OK', async () => { 28 | const account = '3tot2o_PcueolCwU0cVCDpBIuPC2c5F5dB0vI9zLmrM' 29 | const everHash = '0xa2db76aa2b7ce48e3d3fff8a8be7a8d279bbe54602fcb530ea04b362a3e03640' 30 | const tx = await everpay.txByHash(everHash) 31 | const message = everpay.getEverpayTxMessage(tx as any) 32 | const sig = tx.sig 33 | return await everpay.verifyMessage({ 34 | type: 'sign', 35 | message, 36 | account, 37 | sig 38 | }).then(result => { 39 | expect(result.public).toBeTruthy() 40 | expect(result.publicId.toLowerCase()).toBe(account.toLowerCase()) 41 | }) 42 | }) 43 | }) 44 | 45 | describe('verifyMessage for FIDO2 type sign', () => { 46 | test('verifyMessage FIDO2 type sign should be OK', async () => { 47 | const account = 'registerforkindle@gmail.com' 48 | const everHash = '0xdc9a5521098f480104b89226041dbc008aee44d04ac85e6a045c4f9b9722d137' 49 | const tx = await everpay.txByHash(everHash) 50 | const sig = tx.sig 51 | return await everpay.verifyMessage({ 52 | type: 'sign', 53 | message: everHash, 54 | account, 55 | sig 56 | }).then(result => { 57 | expect(result.public).toBeTruthy() 58 | expect(result.publicId.toLowerCase()).toBeTruthy() 59 | }) 60 | }) 61 | }) 62 | 63 | describe('verifyMessage for FIDO2 type register', () => { 64 | test('verifyMessage FIDO2 type register should be OK', async () => { 65 | const account = 'registerforkindle@gmail.com' 66 | const everHash = '0x399c26de125202d998e493a6f93c273c235f61f37b5fc86f3a76394e6b1a40a8' 67 | const tx = await everpay.txByHash(everHash) 68 | const sig = tx.sig 69 | return await everpay.verifyMessage({ 70 | type: 'register', 71 | message: everHash, 72 | account, 73 | sig 74 | }).then(result => { 75 | expect(result.public).toBeTruthy() 76 | expect(result.publicId.toLowerCase()).toBeTruthy() 77 | }) 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /test/+withdraw.ethereum.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { ethWalletHasUSDT, ethWalletHasUSDT2 } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | import { ChainType } from '../src/types' 5 | 6 | const provider = new ethers.providers.InfuraProvider('kovan') 7 | 8 | test(`${ethWalletHasUSDT.address} withdraw USDT to ${ethWalletHasUSDT2.address}`, async () => { 9 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 10 | const everpay = new Everpay({ 11 | account: ethWalletHasUSDT.address, 12 | ethConnectedSigner: signer, 13 | debug: true 14 | }) 15 | 16 | return await everpay.withdraw({ 17 | chainType: ChainType.bsc, 18 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 19 | amount: '100', 20 | to: ethWalletHasUSDT2.address 21 | }).then(withdrawResult => { 22 | console.log('withdrawResult', withdrawResult) 23 | expect(withdrawResult.status).toBe('ok') 24 | }) 25 | }) 26 | 27 | test(`use another ${ethWalletHasUSDT.address} singer to sign ${ethWalletHasUSDT2.address}'s tx`, async () => { 28 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 29 | const everpay = new Everpay({ 30 | account: ethWalletHasUSDT2.address, 31 | ethConnectedSigner: signer, 32 | debug: true 33 | }) 34 | 35 | await expect( 36 | everpay.withdraw({ 37 | chainType: ChainType.bsc, 38 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 39 | amount: '101', 40 | to: ethWalletHasUSDT.address 41 | }) 42 | ) 43 | .rejects 44 | .toThrow('err_invalid_signature') 45 | }) 46 | -------------------------------------------------------------------------------- /test/_expressInfo.test.ts: -------------------------------------------------------------------------------- 1 | import { isString, isNumber } from 'lodash' 2 | import Everpay from '../src/index' 3 | 4 | const everpay = new Everpay({ 5 | debug: true 6 | }) 7 | 8 | test('express info got correct', async () => { 9 | return await everpay.expressInfo().then(expressInfo => { 10 | const { address, withdrawTimeCost, tokens } = expressInfo 11 | expect(isString(address)).toBe(true) 12 | expect(isNumber(withdrawTimeCost)).toBe(true) 13 | expect(tokens.length).toBeGreaterThan(0) 14 | expect(tokens.find(t => t.tokenTag.toLowerCase().includes('usdt'))).toBeTruthy() 15 | }) 16 | }) 17 | -------------------------------------------------------------------------------- /test/_withdraw.quick.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet1, ethWalletHasUSDT } from './constants/wallet' 3 | import { ethers } from 'ethers' 4 | import { ChainType } from '../src/types' 5 | 6 | const provider = new ethers.providers.InfuraProvider('kovan') 7 | const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 8 | 9 | test(`${ethWalletHasUSDT.address} quick withdraw tUSDC to ${ethWalletHasUSDT.address}`, async () => { 10 | const everpay = new Everpay({ 11 | account: ethWalletHasUSDT.address, 12 | ethConnectedSigner: signer, 13 | chainType: ChainType.ethereum, 14 | debug: true 15 | }) 16 | 17 | return await everpay.withdraw({ 18 | chainType: ChainType.bsc, 19 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 20 | amount: '99', 21 | quickMode: false 22 | }).then(withdrawResult => { 23 | console.log('withdrawResult', withdrawResult) 24 | expect(withdrawResult.status).toBe('ok') 25 | }) 26 | }) 27 | 28 | test(`${arWallet1.address} quick withdraw USDT to ${ethWalletHasUSDT.address}`, async () => { 29 | const everpay = new Everpay({ 30 | account: arWallet1.address, 31 | arJWK: arWallet1.jwk, 32 | chainType: ChainType.arweave, 33 | debug: true 34 | }) 35 | 36 | return await everpay.withdraw({ 37 | chainType: ChainType.bsc, 38 | tag: 'bsc-tusdc-0xf17a50ecc5fe5f476de2da5481cdd0f0ffef7712', 39 | amount: '52.6', 40 | quickMode: false, 41 | to: ethWalletHasUSDT.address 42 | }).then(withdrawResult => { 43 | console.log('withdrawResult', withdrawResult) 44 | expect(withdrawResult.status).toBe('ok') 45 | }) 46 | }) 47 | -------------------------------------------------------------------------------- /test/deposit.arweave.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet1 } from './constants/wallet' 3 | import { ArweaveTransaction, ChainType } from '../src/types' 4 | 5 | const everpay = new Everpay({ 6 | account: arWallet1.address, 7 | arJWK: arWallet1.jwk, 8 | chainType: ChainType.arweave, 9 | debug: true 10 | }) 11 | 12 | test(`check ${arWallet1.address} deposit ar`, async () => { 13 | return await everpay.deposit({ 14 | tag: 'arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8', 15 | amount: '0.0000001' 16 | }).then((arTx) => { 17 | console.log('arTx', arTx as ArweaveTransaction) 18 | expect((arTx as ArweaveTransaction).id).toBeTruthy() 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /test/deposit.pst.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet2 } from './constants/wallet' 3 | import { ArweaveTransaction, ChainType } from '../src/types' 4 | // import { ethers } from 'ethers' 5 | 6 | // const provider = new ethers.providers.InfuraProvider('kovan') 7 | // const signer = new ethers.Wallet(ethWalletHasUSDT.privateKey, provider) 8 | 9 | // const everpayEthereumMode = new Everpay({ 10 | // account: ethWalletHasUSDT.address, 11 | // ethConnectedSigner: signer, 12 | // chainType: ChainType.ethereum, 13 | // debug: true 14 | // }) 15 | 16 | // test(`check ${ethWalletHasUSDT.address} deposit vrt`, async () => { 17 | // return await everpayEthereumMode.deposit({ 18 | // tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 19 | // amount: '0.000000001' 20 | // }).then(ethTx => { 21 | // console.log('ethTx', ethTx) 22 | // expect(ethTx).toBeTruthy() 23 | // }) 24 | // }) 25 | 26 | const everpayARMode = new Everpay({ 27 | account: arWallet2.address, 28 | arJWK: arWallet2.jwk, 29 | chainType: ChainType.arweave, 30 | debug: true 31 | }) 32 | 33 | test(`check ${arWallet2.address} deposit VRT`, async () => { 34 | return await everpayARMode.deposit({ 35 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 36 | amount: '1' 37 | }).then((arTx) => { 38 | console.log('arTx', arTx as ArweaveTransaction) 39 | expect((arTx as ArweaveTransaction).id).toBeTruthy() 40 | }) 41 | }) 42 | 43 | test(`check ${arWallet2.address} deposit VRT failed`, async () => { 44 | await expect( 45 | everpayARMode.deposit({ 46 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 47 | amount: '0.1' 48 | }) 49 | ) 50 | .rejects 51 | .toThrow('DEPOSIT_ARWEAVE_PST_MUST_BE_INTEGER') 52 | }) 53 | -------------------------------------------------------------------------------- /test/set.tx.test.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'ethers' 2 | import { ethWalletSet } from './constants/wallet' 3 | import Everpay, { NewToken, TargetChainMeta } from '../src' 4 | const provider = new ethers.providers.InfuraProvider('kovan') 5 | 6 | test('add token set', async () => { 7 | const signer = new ethers.Wallet(ethWalletSet.privateKey, provider) 8 | const everpay = new Everpay({ 9 | account: ethWalletSet.address, 10 | ethConnectedSigner: signer, 11 | debug: true 12 | }) 13 | 14 | const newToken: NewToken = { 15 | tokenID: '0xb5EadFdbDB40257D1d24A1432faa2503A867C270', 16 | symbol: 'DDD', 17 | chainType: 'bsc', 18 | chainID: '97', 19 | everDecimals: 18, 20 | targetChains: [ 21 | { 22 | targetChainId: '97', 23 | targetChainType: 'bsc', 24 | targetDecimals: 18, 25 | targetTokenId: '0xb5EadFdbDB40257D1d24A1432faa2503A867C270' 26 | } 27 | ] 28 | } 29 | const ats = await everpay.signAddTokenSet(newToken) 30 | const res = await everpay.setTx(ats) 31 | console.log(res) 32 | }) 33 | 34 | test('add target chain', async () => { 35 | const signer = new ethers.Wallet(ethWalletSet.privateKey, provider) 36 | const everpay = new Everpay({ 37 | account: ethWalletSet.address, 38 | ethConnectedSigner: signer, 39 | debug: true 40 | }) 41 | const tokenTag = 'bsc-ddd-0xb5eadfdbdb40257d1d24a1432faa2503a867c270' 42 | const targetChain: TargetChainMeta = { 43 | targetChainId: '1287', 44 | targetChainType: 'moon', 45 | targetDecimals: 18, 46 | targetTokenId: '0x1930fD66A3faea9B8e7F120d735BB431C048Ad26' 47 | } 48 | const atc = await everpay.signAddTargetChainSet(tokenTag, targetChain) 49 | const res = await everpay.setTx(atc) 50 | console.log(res) 51 | }) 52 | 53 | test('set token display', async () => { 54 | const signer = new ethers.Wallet(ethWalletSet.privateKey, provider) 55 | const everpay = new Everpay({ 56 | account: ethWalletSet.address, 57 | ethConnectedSigner: signer, 58 | debug: true 59 | }) 60 | 61 | const tokenTag = 'bsc-ddd-0xb5eadfdbdb40257d1d24a1432faa2503a867c270' 62 | const display = true 63 | const td = await everpay.signTokenDisplaySet(tokenTag, display) 64 | const res = await everpay.setTx(td) 65 | console.log(res) 66 | }) 67 | -------------------------------------------------------------------------------- /test/withdraw.arweave.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet1, ethWalletHasUSDT } from './constants/wallet' 3 | import { ChainType } from '../src/types' 4 | 5 | test(`${arWallet1.address} withdraw ar to ${arWallet1.address}`, async () => { 6 | const everpay = new Everpay({ 7 | account: arWallet1.address, 8 | chainType: ChainType.arweave, 9 | arJWK: arWallet1.jwk, 10 | debug: true 11 | }) 12 | 13 | return await everpay.withdraw({ 14 | chainType: ChainType.arweave, 15 | tag: 'arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8', 16 | amount: '0.000010001', 17 | to: arWallet1.address 18 | }).then(withdrawResult => { 19 | console.log('withdrawResult', withdrawResult) 20 | expect(withdrawResult.status).toBe('ok') 21 | }) 22 | }) 23 | 24 | test(`${arWallet1.address} withdraw ar to ethereum address ${ethWalletHasUSDT.address}`, async () => { 25 | const everpay = new Everpay({ 26 | account: arWallet1.address, 27 | chainType: ChainType.arweave, 28 | arJWK: arWallet1.jwk, 29 | debug: true 30 | }) 31 | 32 | return await everpay.withdraw({ 33 | chainType: ChainType.ethereum, 34 | tag: 'arweave,ethereum-ar-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA,0x83ea4a2fe3ead9a7b204ab2d56cb0b81d71489c8', 35 | amount: '0.000010001', 36 | to: ethWalletHasUSDT.address 37 | }).then(withdrawResult => { 38 | console.log('withdrawResult', withdrawResult) 39 | expect(withdrawResult.status).toBe('ok') 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /test/withdraw.pst.test.ts: -------------------------------------------------------------------------------- 1 | import Everpay from '../src/index' 2 | import { arWallet2, ethWalletHasUSDT } from './constants/wallet' 3 | import { ChainType } from '../src/types' 4 | 5 | test(`${arWallet2.address} withdraw vrt to ${arWallet2.address}`, async () => { 6 | const everpay = new Everpay({ 7 | account: arWallet2.address, 8 | arJWK: arWallet2.jwk, 9 | chainType: ChainType.arweave, 10 | debug: true 11 | }) 12 | 13 | return await everpay.withdraw({ 14 | chainType: ChainType.arweave, 15 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 16 | amount: '1', 17 | to: arWallet2.address 18 | }).then(withdrawResult => { 19 | console.log('withdrawResult', withdrawResult) 20 | expect(withdrawResult.status).toBe('ok') 21 | }) 22 | }) 23 | 24 | test(`check ${arWallet2.address} withdraw VRT failed`, async () => { 25 | const everpay = new Everpay({ 26 | account: arWallet2.address, 27 | arJWK: arWallet2.jwk, 28 | chainType: ChainType.arweave, 29 | debug: true 30 | }) 31 | await expect( 32 | everpay.withdraw({ 33 | chainType: ChainType.arweave, 34 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 35 | amount: '0.1' 36 | }) 37 | ) 38 | .rejects 39 | .toThrow('PST_WITHDARW_TO_ARWEAVE_MUST_BE_INTEGER') 40 | }) 41 | 42 | test(`${arWallet2.address} withdraw vrt to ethereum address ${ethWalletHasUSDT.address}`, async () => { 43 | const everpay = new Everpay({ 44 | account: arWallet2.address, 45 | arJWK: arWallet2.jwk, 46 | chainType: ChainType.arweave, 47 | debug: true 48 | }) 49 | 50 | return await everpay.withdraw({ 51 | chainType: ChainType.ethereum, 52 | tag: 'arweave-vrt-usjm4PCxUd5mtaon7zc97-dt-3qf67yPyqgzLnLqk5A', 53 | amount: '0.000010001', 54 | to: ethWalletHasUSDT.address 55 | }).then(withdrawResult => { 56 | console.log('withdrawResult', withdrawResult) 57 | expect(withdrawResult.status).toBe('ok') 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "outDir": "./cjs", 6 | "declarationDir": "./cjs", 7 | }, 8 | } -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src", 5 | "test", 6 | ".eslintrc.js", 7 | "jest.config.js", 8 | "babel.config.js" 9 | ] 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "module": "es2020", 5 | "allowJs": true, 6 | "jsx": "preserve", 7 | "declarationMap": true, 8 | "outDir": "./esm", 9 | "declaration": true, 10 | "declarationDir": "./esm", 11 | "strict": true, 12 | "moduleResolution": "node", 13 | "rootDirs": [ 14 | "src" 15 | ], 16 | "typeRoots": [], 17 | "types": [ 18 | "node", 19 | // 注意:https://stackoverflow.com/a/63484466/10091449 20 | "@types/jest" 21 | ], 22 | "esModuleInterop": true, 23 | "skipLibCheck": true, 24 | "forceConsistentCasingInFileNames": true 25 | }, 26 | "include": [ 27 | "src" 28 | ], 29 | "exclude": [ 30 | "node_modules" 31 | ] 32 | } --------------------------------------------------------------------------------