Alex Xu 8 tháng trước cách đây
commit
abbaf81dfd
100 tập tin đã thay đổi với 15304 bổ sung0 xóa
  1. 14 0
      .babelrc
  2. 14 0
      .editorconfig
  3. 8 0
      .env.example
  4. 7 0
      .eslintignore
  5. 36 0
      .eslintrc.js
  6. 24 0
      .gitignore
  7. 14 0
      CHANGELOG.md
  8. 31 0
      README.md
  9. 11 0
      init.sh
  10. 142 0
      package.json
  11. 11 0
      pages/dashboard.html
  12. 22 0
      src/constants/index.ts
  13. 6 0
      src/controllers/index.ts
  14. 40 0
      src/controllers/pingpong/index.ts
  15. 110 0
      src/controllers/tweet/index.ts
  16. 18 0
      src/controllers/types.ts
  17. 71 0
      src/controllers/x/index.ts
  18. 29 0
      src/db/index.ts
  19. 44 0
      src/db/models/Application.ts
  20. 223 0
      src/db/models/Constant.ts
  21. 83 0
      src/db/models/Tweet.ts
  22. 5 0
      src/db/models/index.ts
  23. 11 0
      src/db/models/types.ts
  24. 5 0
      src/exceptions/Common.ts
  25. 31 0
      src/exceptions/DatabaseException.ts
  26. 30 0
      src/exceptions/EndpointException.ts
  27. 11 0
      src/exceptions/HttpException.ts
  28. 9 0
      src/exceptions/NotFoundException.ts
  29. 19 0
      src/exceptions/UserException.ts
  30. 0 0
      src/global.d.ts
  31. 155 0
      src/index.ts
  32. 57 0
      src/middleware/apikey.middleware.ts
  33. 21 0
      src/middleware/error.middleware.ts
  34. 54 0
      src/middleware/jsonResponse.middleware.ts
  35. 80 0
      src/middleware/jwt.middleware.ts
  36. 14 0
      src/middleware/setHeaders.middleware.ts
  37. 45 0
      src/middleware/upload.middleware.ts
  38. 33 0
      src/middleware/utils.ts
  39. 13 0
      src/scripts/env.ts
  40. 35 0
      src/services/twitterService/clients.ts
  41. 198 0
      src/services/twitterService/index.ts
  42. 0 0
      src/services/types.ts
  43. 0 0
      src/services/utils.ts
  44. 32 0
      src/start.ts
  45. 10 0
      src/test/env.ts
  46. 0 0
      src/types.ts
  47. 147 0
      src/utils/EVMHelper/EVMHelper.ts
  48. 181 0
      src/utils/EVMHelper/contracts/AaveABI.json
  49. 53 0
      src/utils/EVMHelper/contracts/MTCoreCard.ts
  50. 677 0
      src/utils/EVMHelper/contracts/MTCoreCardABI.json
  51. 35 0
      src/utils/EVMHelper/contracts/TobotoOGFriend.ts
  52. 747 0
      src/utils/EVMHelper/contracts/TobotoOGFriendABI.json
  53. 24 0
      src/utils/EVMHelper/contracts/UniswapV2PairMini.ts
  54. 27 0
      src/utils/EVMHelper/contracts/UniswapV2PairMiniABI.json
  55. 58 0
      src/utils/EVMHelper/contracts/UniswapV2Route02Mini.ts
  56. 320 0
      src/utils/EVMHelper/contracts/UniswapV2Route02MiniABI.json
  57. 67 0
      src/utils/EVMHelper/contracts/UniswapV3PoolMini.ts
  58. 983 0
      src/utils/EVMHelper/contracts/UniswapV3PoolMiniABI copy.json
  59. 110 0
      src/utils/EVMHelper/contracts/UniswapV3PoolMiniABI.json
  60. 49 0
      src/utils/EVMHelper/contracts/UniswapV3Quoter.ts
  61. 193 0
      src/utils/EVMHelper/contracts/UniswapV3QuoterABI.json
  62. 43 0
      src/utils/EVMHelper/contracts/aave.ts
  63. 17 0
      src/utils/EVMHelper/contracts/base.ts
  64. 190 0
      src/utils/EVMHelper/contracts/degenSwap.ts
  65. 164 0
      src/utils/EVMHelper/contracts/degenSwapABI.json
  66. 76 0
      src/utils/EVMHelper/contracts/degenSwapToken.ts
  67. 686 0
      src/utils/EVMHelper/contracts/degenSwapTokenABI.json
  68. 181 0
      src/utils/EVMHelper/contracts/degenSwapTokenV2.ts
  69. 1095 0
      src/utils/EVMHelper/contracts/degenSwapTokenV2ABI.json
  70. 200 0
      src/utils/EVMHelper/contracts/degenSwapV2.ts
  71. 187 0
      src/utils/EVMHelper/contracts/degenSwapV2ABI.json
  72. 88 0
      src/utils/EVMHelper/contracts/erc20.ts
  73. 222 0
      src/utils/EVMHelper/contracts/erc20ABI.json
  74. 40 0
      src/utils/EVMHelper/contracts/erc20WhaleABI.json
  75. 102 0
      src/utils/EVMHelper/contracts/erc721.ts
  76. 861 0
      src/utils/EVMHelper/contracts/erc721ABI.json
  77. 41 0
      src/utils/EVMHelper/contracts/erc721Mock.ts
  78. 229 0
      src/utils/EVMHelper/contracts/erc721MockABI.json
  79. 14 0
      src/utils/EVMHelper/contracts/filter.ts
  80. 235 0
      src/utils/EVMHelper/contracts/filterABI.json
  81. 20 0
      src/utils/EVMHelper/contracts/filterRule.ts
  82. 124 0
      src/utils/EVMHelper/contracts/filterRuleABI.json
  83. 51 0
      src/utils/EVMHelper/contracts/fox.ts
  84. 607 0
      src/utils/EVMHelper/contracts/foxABI.json
  85. 70 0
      src/utils/EVMHelper/contracts/hacker.ts
  86. 59 0
      src/utils/EVMHelper/contracts/hackerABI.json
  87. 9 0
      src/utils/EVMHelper/contracts/lpToken.ts
  88. 1112 0
      src/utils/EVMHelper/contracts/lpTokenABI.json
  89. 50 0
      src/utils/EVMHelper/contracts/pancake.ts
  90. 973 0
      src/utils/EVMHelper/contracts/pancakeABI.json
  91. 153 0
      src/utils/EVMHelper/contracts/pool.ts
  92. 738 0
      src/utils/EVMHelper/contracts/poolABI.json
  93. 290 0
      src/utils/EVMHelper/contracts/wethABI.json
  94. 58 0
      src/utils/EVMHelper/dbModels/RPC.ts
  95. 3 0
      src/utils/EVMHelper/dbModels/index.ts
  96. 2 0
      src/utils/EVMHelper/index.ts
  97. 64 0
      src/utils/EVMHelper/types.ts
  98. 108 0
      src/utils/SolanaHelper/BloxrouteSolanaHelper.ts
  99. 219 0
      src/utils/SolanaHelper/JitoHelper.ts
  100. 426 0
      src/utils/SolanaHelper/SolanaHelper.ts

+ 14 - 0
.babelrc

@@ -0,0 +1,14 @@
+{
+  "plugins": [
+    ["module-resolver", {
+      "root": ["./"],
+      "alias": {}
+    }],
+    ["@babel/plugin-proposal-decorators", { "legacy": true }],
+    ["@babel/plugin-proposal-class-properties"]
+  ],
+  "presets": [
+    ["@babel/preset-env", { "targets": { "node": "current" } }],
+    "@babel/preset-typescript"
+  ]
+}

+ 14 - 0
.editorconfig

@@ -0,0 +1,14 @@
+# http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+max_line_length = 80
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = 0

+ 8 - 0
.env.example

@@ -0,0 +1,8 @@
+DEBUG=1
+DB_HOST=127.0.0.1
+DB_NAME=wxmonitor
+DB_USER=root
+DB_PASS=
+PORT=8089
+SOLANA_RPC=
+DATA_SERVICR_BASE_URL=

+ 7 - 0
.eslintignore

@@ -0,0 +1,7 @@
+lib
+main.js
+server.js
+.eslintrc.js
+ref
+shellTool
+src/services/solswap.service/pumpfun.service/sdk/*

+ 36 - 0
.eslintrc.js

@@ -0,0 +1,36 @@
+// module.exports = {
+//   parser: '@typescript-eslint/parser',
+//   extends: [
+//     'standard-with-typescript',
+//     'eslint:recommended',
+//     'plugin:@typescript-eslint/recommended',
+//   ],
+//   plugins: ['prettier', '@typescript-eslint'],
+//   rules: {
+//     'prettier/prettier': ['error', { singleQuote: true, semi: false }],
+//     'comma-dangle': 0,
+//     '@typescript-eslint/space-before-function-paren': 0,
+//     'multiline-ternary': 0,
+//     '@typescript-eslint/strict-boolean-expressions': 0,
+//   },
+// }
+
+module.exports = {
+  // parser: '@typescript-eslint/parser',
+  extends: [
+    'standard-with-typescript',
+    'eslint:recommended',
+    'plugin:@typescript-eslint/recommended',
+  ],
+  parserOptions: {
+    project: './tsconfig.json',
+  },
+  plugins: ['prettier'],
+  rules: {
+    'prettier/prettier': ['error', { singleQuote: true, semi: false }],
+    'comma-dangle': 0,
+    '@typescript-eslint/space-before-function-paren': 0,
+    'multiline-ternary': 0,
+    '@typescript-eslint/strict-boolean-expressions': 0,
+  },
+}

+ 24 - 0
.gitignore

@@ -0,0 +1,24 @@
+node_modules
+lib
+output
+*.swp
+*.log
+.env
+upload
+memory
+profile
+public
+src/scripts/const.ts
+ref
+.vscode/*
+.prettierrc.js
+
+*.import.json
+*.import.csv
+temp.ts
+
+src/Monitor/startLocal.ts
+test.sh
+tempScripts
+staticData
+data

+ 14 - 0
CHANGELOG.md

@@ -0,0 +1,14 @@
+### 2023/12/10 更新调整内容
+
+1. CampaignReward 模型新增 overwiteText 字段
+
+### 2023/12/8 更新调整内容
+
+1. campaigns 模型新增 needBindDiscord 字段
+2. campaignSteps 模型 新增 code 类型 joindiscord 
+3. .env.example 新增 DISCORD 字段配置
+DISCORD_API_URL=https://discord.com/api
+DISCORD_CLIENT_ID=
+DISCORD_CLIENT_SECRET=
+DISCORD_REDIRECT_URI=http://localhost:3000/api/v1/discord/callback
+DISCORD_MY_SHELL_GUILD_ID=

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# SATEA VPN INFO MANAGE SYSTEM
+
+## 环境变量
+
+```shell
+DEBUG=1 # debug标识
+# 数据库信息
+DB_HOST=127.0.0.1 
+DB_NAME=aklabs-merchant 
+DB_USER=root
+DB_PASS=
+
+# 服务端口
+PORT=8089
+
+# Catalog结构文件目录,在 init.sh 中会创建一个,可以使用那个
+CATALOG_FILE_PATH=~/staticData/catalog.json
+
+# 支付系统信息
+PAYMENT_SYS_ENDPOINT=http://localhost:8090
+PAYMENT_SYS_APIKEY=local
+
+# 其他环境变量
+TEST_PK=0x000
+```
+
+## 初始化Catalog
+
+```
+yarn run script -- lib/scripts/initCatalog.js
+```

+ 11 - 0
init.sh

@@ -0,0 +1,11 @@
+echo "初始化"
+
+mkdir -p public/
+mkdir -p public/images
+mkdir -p public/audios
+mkdir -p staticData/
+mkdir -p data/
+
+cat "" >> data/.memory-card.json
+
+cp -rf pages public/

+ 142 - 0
package.json

@@ -0,0 +1,142 @@
+{
+  "name": "account-timeline-service",
+  "version": "1.0.1",
+  "main": "index.js",
+  "repository": "https://github.com/sekaiamber/account_timeline-service.git",
+  "author": "xiaomeng xu <a3824036@126.com>",
+  "license": "MIT",
+  "scripts": {
+    "postinstall": "./init.sh",
+    "babel": "babel src --out-dir lib --copy-files --extensions '.ts'",
+    "build": "npm run babel",
+    "lint": "eslint src",
+    "start": "npm run babel && node -r dotenv/config lib/start.js",
+    "start_monitor": "npm run babel && node -r dotenv/config lib/Monitor/start.js",
+    "start_monitor_local": "npm run babel && node -r dotenv/config lib/Monitor/startLocal.js",
+    "test": "npm run babel && node -r dotenv/config lib/test/image.js",
+    "upload_server": "http-server ./upload",
+    "script": "npm run babel && node -r dotenv/config ",
+    "script_no_build": "node -r dotenv/config ",
+    "pm2_start": "pm2 start --name banana npm -- run script -- lib/scripts/simpleStart.js",
+    "pm2_start2": "pm2 start --name banana2 npm -- run script -- lib/scripts/simpleStart2.js",
+    "pm2_start_monitor": "pm2 start --name gmgnlike_m npm -- run start_monitor"
+  },
+  "dependencies": {
+    "@bloxroute/solana-trader-client-ts": "^2.2.0",
+    "@cmdcode/buff-utils": "^2.0.0",
+    "@cmdcode/crypto-utils": "^2.4.6",
+    "@cmdcode/tapscript": "^1.4.3",
+    "@coral-xyz/anchor": "^0.30.1",
+    "@coral-xyz/borsh": "^0.30.1",
+    "@cosmjs/stargate": "^0.31.1",
+    "@goplus/sdk-node": "^1.0.9",
+    "@jup-ag/limit-order-sdk": "^0.1.10",
+    "@project-serum/anchor": "^0.26.0",
+    "@raydium-io/raydium-sdk-v2": "^0.1.73-alpha",
+    "@satea/vpn-info-monitor-utils": "0.0.19",
+    "@solana/spl-token": "^0.3.11",
+    "@solana/web3.js": "^1.95.2",
+    "@trpc/server": "10.45.0",
+    "@types/qrcode-terminal": "^0.12.2",
+    "@uniswap/v3-core": "^1.0.1",
+    "@uniswap/v3-sdk": "^3.9.0",
+    "alchemy-sdk": "^2.8.0",
+    "axios": "^0.24.0",
+    "bs58": "^5.0.0",
+    "cookie-parser": "^1.4.5",
+    "cron": "^2.3.1",
+    "crypto-js": "^4.2.0",
+    "csv-parse": "^5.5.3",
+    "decimal.js": "^10.3.1",
+    "decimal.js-light": "^2.5.1",
+    "dify-client": "^2.0.0",
+    "discord.js": "^14.14.1",
+    "dotenv": "^10.0.0",
+    "ejs": "^3.1.9",
+    "ethers": "^5.4.4",
+    "ethers6": "npm:ethers@^6.3.0",
+    "eventemitter3": "^5.0.1",
+    "express": "^4.17.1",
+    "express-http-proxy": "^2.0.0",
+    "express-winston": "^4.2.0",
+    "helmet": "^4.6.0",
+    "https-proxy-agent": "^5.0.0",
+    "i18next": "^22.4.10",
+    "js-yaml": "^4.1.0",
+    "jwt-simple": "^0.5.6",
+    "lodash": "^4.17.21",
+    "mariadb": "^2.5.5",
+    "markdown-escape": "^1.1.0",
+    "mathjs": "^11.6.0",
+    "memory-card": "1.1.2",
+    "merkletreejs": "^0.3.10",
+    "mkdirp": "^1.0.4",
+    "moment": "^2.29.1",
+    "moment-timezone": "^0.5.43",
+    "morgan": "^1.10.0",
+    "multer": "^1.4.5-lts.1",
+    "nanoid": "3",
+    "node-global-proxy": "^1.0.1",
+    "node-telegram-bot-api": "^0.63.0",
+    "qrcode-terminal": "^0.12.0",
+    "react-markdown": "^9.0.1",
+    "reflect-metadata": "^0.1.13",
+    "seedrandom": "^3.0.5",
+    "sequelize": "^6.9.0",
+    "sequelize-cursor-pagination": "^3.4.0",
+    "sequelize-nested-set": "^1.6.2",
+    "sequelize-typescript": "^2.1.1",
+    "siwe": "^2.1.4",
+    "socket.io": "^4.8.1",
+    "string-progressbar": "^1.0.4",
+    "string-similarity-js": "^2.1.4",
+    "telegraf": "^4.16.3",
+    "twitter-api-sdk": "^1.2.1",
+    "useless-helpers": "^0.0.8",
+    "uuid": "^10.0.0",
+    "wechaty": "^1.20.2",
+    "ws": "^8.18.0",
+    "zksync": "^0.13.1"
+  },
+  "devDependencies": {
+    "@babel/cli": "^7.14.8",
+    "@babel/core": "^7.15.0",
+    "@babel/plugin-proposal-class-properties": "^7.14.5",
+    "@babel/plugin-proposal-decorators": "^7.16.0",
+    "@babel/preset-env": "^7.15.0",
+    "@babel/preset-typescript": "^7.15.0",
+    "@types/bn.js": "^5.1.5",
+    "@types/cookie-parser": "^1.4.2",
+    "@types/cron": "^2.0.1",
+    "@types/crypto-js": "^4.1.2",
+    "@types/express": "^4.17.13",
+    "@types/express-http-proxy": "^1.6.6",
+    "@types/js-yaml": "^4.0.5",
+    "@types/lodash": "^4.14.191",
+    "@types/markdown-escape": "^1.1.0",
+    "@types/mkdirp": "^1.0.2",
+    "@types/morgan": "^1.9.3",
+    "@types/multer": "^1.4.7",
+    "@types/node": "^16.11.6",
+    "@types/node-telegram-bot-api": "^0.61.8",
+    "@types/seedrandom": "^3.0.5",
+    "@types/uuid": "^8.3.1",
+    "@types/validator": "^13.6.6",
+    "@types/web3-provider-engine": "^14.0.1",
+    "@typescript-eslint/eslint-plugin": "^5.20.0",
+    "@typescript-eslint/parser": "^5.54.0",
+    "babel-plugin-module-resolver": "^4.1.0",
+    "eslint": "7",
+    "eslint-config-standard-with-typescript": "^20.0.0",
+    "eslint-plugin-import": "2",
+    "eslint-plugin-node": "11",
+    "eslint-plugin-prettier": "^3.3.1",
+    "eslint-plugin-promise": "4",
+    "prettier": "^2.2.1",
+    "typescript": "^4.3.5",
+    "typescript-eslint": "^0.0.1-alpha.0"
+  },
+  "optionalDependencies": {
+    "bufferutil": "^4.0.8"
+  }
+}

+ 11 - 0
pages/dashboard.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Document</title>
+</head>
+<body>
+  11
+</body>
+</html>

+ 22 - 0
src/constants/index.ts

@@ -0,0 +1,22 @@
+import path from 'path'
+
+export const PORT = process.env.PORT
+export const DB_HOST = process.env.DB_HOST
+export const DB_PORT = process.env.DB_PORT
+export const DB_NAME = process.env.DB_NAME
+export const DB_USER = process.env.DB_USER
+export const DB_PASS = process.env.DB_PASS
+export const TG_BOT_TOKEN = process.env.TG_BOT_TOKEN
+export const DATA_SERVICR_BASE_URL = process.env.DATA_SERVICR_BASE_URL!
+export const DATA_SERVICR_USERNAME = process.env.DATA_SERVICR_USERNAME!
+export const DATA_SERVICR_PASSWORD = process.env.DATA_SERVICR_PASSWORD!
+export const BANANA_ADDRESS = process.env.BANANA_ADDRESS!
+
+export const STATIC_FILEPATH = process.env.STATIC_FILEPATH!
+export const STATIC_IMAGES_FILEPATH = path.join(STATIC_FILEPATH, '/images')
+export const STATIC_AUDIOS_FILEPATH = path.join(STATIC_FILEPATH, '/audios')
+
+export const X_BOT_CLIENT_ID = process.env.X_BOT_CLIENT_ID!
+export const X_BOT_CLIENT_SECRET = process.env.X_BOT_CLIENT_SECRET!
+
+export const MODIFY_TOKEN = process.env.MODIFY_TOKEN!

+ 6 - 0
src/controllers/index.ts

@@ -0,0 +1,6 @@
+import PingpongController from './pingpong'
+import XController from './x'
+import TweetController from './tweet'
+export * from './types'
+
+export { PingpongController, XController, TweetController }

+ 40 - 0
src/controllers/pingpong/index.ts

@@ -0,0 +1,40 @@
+import { Request, RequestHandler, NextFunction, Router } from 'express'
+import { Controller } from '../types'
+import NotFoundException from '../../exceptions/NotFoundException'
+import jsonResponseMiddleware, {
+  JsonResponse,
+} from '../../middleware/jsonResponse.middleware'
+// import apiKeyMiddleware from '../../middleware/apikey.middleware'
+
+export default class PingpongController implements Controller {
+  public path = '/api/v1/pingpong'
+  public router = Router()
+
+  constructor() {
+    this.initializeRoutes()
+  }
+
+  private initializeRoutes(): void {
+    this.router.get(
+      '/:type',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.ping as RequestHandler
+    )
+  }
+
+  private ping(
+    request: Request,
+    response: JsonResponse<{ type: string }>,
+    next: NextFunction
+  ): void {
+    const type = request.params.type
+    if (type === 'ping') {
+      response.jsonSuccess({ type })
+    } else if (type.startsWith('ping-')) {
+      response.jsonSuccess({ type })
+    } else {
+      next(new NotFoundException('Type'))
+    }
+  }
+}

+ 110 - 0
src/controllers/tweet/index.ts

@@ -0,0 +1,110 @@
+import { Request, RequestHandler, NextFunction, Router } from 'express'
+import { Controller } from '../types'
+import NotFoundException from '../../exceptions/NotFoundException'
+import jsonResponseMiddleware, {
+  JsonResponse,
+} from '../../middleware/jsonResponse.middleware'
+import twitterService, { GenerateResult } from '../../services/twitterService'
+import { PaginationConnection } from 'sequelize-cursor-pagination'
+import { TweetData } from '../../db/models/Tweet'
+import { MODIFY_TOKEN } from '../../constants'
+// import apiKeyMiddleware from '../../middleware/apikey.middleware'
+
+interface ImportPayload {
+  token: string
+  urls: string[]
+}
+
+interface TopPayload {
+  token: string
+  statusId: string
+  top?: boolean
+}
+
+export default class TweetController implements Controller {
+  public path = '/api/v1/tweet'
+  public router = Router()
+
+  constructor() {
+    this.initializeRoutes()
+  }
+
+  private initializeRoutes(): void {
+    this.router.get(
+      '/list',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.list as RequestHandler
+    )
+    this.router.post(
+      '/import',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.import as RequestHandler
+    )
+    this.router.post(
+      '/top',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.top as RequestHandler
+    )
+  }
+
+  private list(
+    request: Request<any, any, any, { after?: string }>,
+    response: JsonResponse<PaginationConnection<TweetData>>,
+    next: NextFunction
+  ): void {
+    const after = request.query.after
+    twitterService
+      .paginateTweetByOrder(after)
+      .then((tweets) => {
+        response.jsonSuccess(tweets)
+      })
+      .catch((e) => {
+        response.status(500).jsonError('Server Error', 1010)
+      })
+  }
+
+  private import(
+    request: Request<any, any, ImportPayload>,
+    response: JsonResponse<GenerateResult[]>,
+    next: NextFunction
+  ): void {
+    const { urls, token } = request.body
+    if (token !== MODIFY_TOKEN) {
+      response.status(401).jsonError('Unauthorized', 1012)
+      return
+    }
+
+    twitterService
+      .bulkGenerateTweetsByPublishAPI(urls)
+      .then((tweets) => {
+        response.jsonSuccess(tweets)
+      })
+      .catch((e) => {
+        response.status(500).jsonError('Server Error', 1011)
+      })
+  }
+
+  private top(
+    request: Request<any, any, TopPayload>,
+    response: JsonResponse<boolean>,
+    next: NextFunction
+  ): void {
+    const { statusId, top, token } = request.body
+    if (token !== MODIFY_TOKEN) {
+      response.status(401).jsonError('Unauthorized', 1012)
+      return
+    }
+
+    twitterService
+      .makeTweetTop(statusId, top)
+      .then(() => {
+        response.jsonSuccess(true)
+      })
+      .catch((e) => {
+        response.status(500).jsonError('Server Error', 1014)
+      })
+  }
+}

+ 18 - 0
src/controllers/types.ts

@@ -0,0 +1,18 @@
+import { Router } from 'express'
+
+export interface Controller {
+  path: string
+  router: Router
+}
+
+export interface Pagination {
+  page: string
+  count: string
+}
+
+export interface PaginationResponse<T> {
+  data: T[]
+  total: number
+  page: number
+  count: number
+}

+ 71 - 0
src/controllers/x/index.ts

@@ -0,0 +1,71 @@
+import {
+  Request,
+  Response,
+  RequestHandler,
+  NextFunction,
+  Router,
+} from 'express'
+import { Controller } from '../types'
+import NotFoundException from '../../exceptions/NotFoundException'
+import jsonResponseMiddleware, {
+  JsonResponse,
+} from '../../middleware/jsonResponse.middleware'
+import twitterService from '../../services/twitterService'
+// import apiKeyMiddleware from '../../middleware/apikey.middleware'
+
+export default class XController implements Controller {
+  public path = '/api/v1/x'
+  public router = Router()
+
+  constructor() {
+    this.initializeRoutes()
+  }
+
+  private initializeRoutes(): void {
+    this.router.get('/callback', this.callback as RequestHandler)
+    this.router.get('/login', this.login as RequestHandler)
+  }
+
+  private callback(
+    request: Request,
+    response: Response,
+    next: NextFunction
+  ): void {
+    const { code, state } = request.query
+    twitterService
+      .requestAccessToken(code as string, state as string)
+      .then(() => {
+        response.status(200).send('ok')
+      })
+      .catch((e: any) => {
+        response
+          .status(500)
+          .send(
+            `Failed to get access token: ${
+              (e.message as string) ?? 'unknown error'
+            }`
+          )
+      })
+  }
+
+  private login(
+    request: Request,
+    response: Response,
+    next: NextFunction
+  ): void {
+    twitterService
+      .generateAuthURL()
+      .then((authUrl) => {
+        response.redirect(authUrl)
+      })
+      .catch((e: any) => {
+        response
+          .status(500)
+          .send(
+            `Failed to generate auth URL: ${
+              (e.message as string) ?? 'unknown error'
+            }`
+          )
+      })
+  }
+}

+ 29 - 0
src/db/index.ts

@@ -0,0 +1,29 @@
+import { Sequelize } from 'sequelize-typescript'
+import * as Models from './models'
+import { RPC } from '../utils/EVMHelper/dbModels'
+import { DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS } from '../constants'
+// import { promisify } from 'util'
+
+const { Constant, Application, Tweet } = Models
+
+const sequelize = new Sequelize({
+  host: DB_HOST,
+  port: DB_PORT ? Number.parseInt(DB_PORT) : 3306,
+  database: DB_NAME,
+  dialect: 'mariadb',
+  username: DB_USER,
+  password: DB_PASS,
+  dialectOptions: {
+    timezone: '+00:00',
+    allowPublicKeyRetrieval: true,
+  },
+  logging: false,
+  pool: {
+    max: 25,
+  },
+  models: [Constant, RPC, Application, Tweet],
+})
+
+export default sequelize
+
+export { Models }

+ 44 - 0
src/db/models/Application.ts

@@ -0,0 +1,44 @@
+import {
+  Table,
+  Column,
+  AllowNull,
+  DataType,
+  Model,
+  Default,
+} from 'sequelize-typescript'
+import nanoid from '../../utils/uuid'
+
+export interface ApplicationUuidFormat {
+  alphabet: string
+  size: number
+}
+
+@Table({
+  modelName: 'application',
+  indexes: [
+    {
+      fields: ['name'],
+      unique: true,
+    },
+  ],
+})
+export default class Application extends Model {
+  @AllowNull(false)
+  @Column(DataType.CHAR(30))
+  get name(): string {
+    return this.getDataValue('name')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.CHAR(16))
+  get apikey(): string {
+    return this.getDataValue('apikey')
+  }
+
+  @AllowNull(false)
+  @Default(false)
+  @Column(DataType.BOOLEAN)
+  get disabled(): boolean {
+    return this.getDataValue('disabled')
+  }
+}

+ 223 - 0
src/db/models/Constant.ts

@@ -0,0 +1,223 @@
+import {
+  Table,
+  Column,
+  AllowNull,
+  Unique,
+  DataType,
+  Default,
+  Model,
+} from 'sequelize-typescript'
+import { ConstantData, ConstantKind, ConstantType } from './types'
+
+export type ConstantNames =
+  | 'singleTxBananaLimit'
+  | 'sleepTime'
+  | 'tradeLimitMax'
+  | 'tradeLimitMin'
+  | 'xBotToken'
+export type ConstantDataValue = string | number | boolean | object
+
+export interface BindedUser {
+  id: string
+  name: string
+}
+
+/* eslint-disable */
+type NameMap<T> =
+    T extends 'singleTxBananaLimit' ? string :
+    // string like '[1,2,3]' for endpoint table id
+    T extends 'sleepTime' ? number :
+    T extends 'tradeLimitMax' ? number :
+    T extends 'tradeLimitMin' ? number :
+    T extends 'xBotToken' ? object :
+    never;
+/* eslint-enable */
+
+@Table({
+  modelName: 'constant',
+})
+export default class Constant extends Model {
+  @Unique
+  @AllowNull(false)
+  @Column(DataType.STRING(50))
+  get name(): string {
+    return this.getDataValue('name')
+  }
+
+  @AllowNull(false)
+  @Default('parameter')
+  @Column(DataType.ENUM('parameter', 'information', 'ui'))
+  get kind(): ConstantKind {
+    return this.getDataValue('kind')
+  }
+
+  @AllowNull(false)
+  @Default('string')
+  @Column(DataType.ENUM('decimal', 'string', 'boolean', 'json'))
+  get type(): ConstantType {
+    return this.getDataValue('type')
+  }
+
+  @Column(DataType.DECIMAL(32, 16))
+  get dataDecimal(): number | null {
+    return this.getDataValue('dataDecimal')
+  }
+
+  @Column(DataType.STRING(5000))
+  get dataString(): string | null {
+    return this.getDataValue('dataString')
+  }
+
+  @Column(DataType.BOOLEAN)
+  get dataBoolean(): boolean | null {
+    return this.getDataValue('dataBoolean')
+  }
+
+  @Column(DataType.JSON)
+  get dataJson(): object | null {
+    return this.getDataValue('dataJson')
+  }
+
+  @Column(DataType.STRING(50))
+  get memo(): string | null {
+    return this.getDataValue('memo')
+  }
+
+  @AllowNull(false)
+  @Default(false)
+  @Column(DataType.BOOLEAN)
+  get readOnly(): boolean {
+    return this.getDataValue('readOnly')
+  }
+
+  getConstantValue(): ConstantData {
+    let value: ConstantDataValue | null | undefined
+    switch (this.type) {
+      case 'decimal':
+        value = this.dataDecimal
+        break
+      case 'string':
+        value = this.dataString
+        break
+      case 'boolean':
+        value = this.dataBoolean
+        break
+      case 'json':
+        value = this.dataJson
+        break
+      default:
+        break
+    }
+    const ret: ConstantData = {
+      name: this.name,
+      kind: this.kind,
+      type: this.type,
+      memo: this.memo,
+      readOnly: this.readOnly,
+      value,
+    }
+    return ret
+  }
+
+  setConstantValue(value: ConstantDataValue | null): void {
+    switch (this.type) {
+      case 'decimal':
+        this.setDataValue('dataDecimal', value)
+        break
+      case 'string':
+        this.setDataValue('dataString', value)
+        break
+      case 'boolean':
+        this.setDataValue('dataBoolean', value)
+        break
+      case 'json':
+        this.setDataValue('dataJson', value)
+        break
+      default:
+        break
+    }
+  }
+
+  static async findByName(name: string): Promise<Constant | null> {
+    return await Constant.findOne({
+      where: { name },
+    })
+  }
+
+  static async findValueByName(
+    name: string,
+    defaults?: ConstantDataValue | null
+  ): Promise<ConstantDataValue | null | undefined> {
+    const ins = await Constant.findOne({
+      where: { name },
+    })
+    if (!ins) return defaults ?? null
+    const v = ins.getConstantValue()
+    return v.value
+  }
+
+  static async findOrCreatePyName(
+    name: string,
+    defaultValue: ConstantDataValue,
+    kind: ConstantKind = 'parameter'
+  ): Promise<[Constant, boolean]> {
+    const payload: any = {
+      name,
+      kind,
+    }
+    switch (typeof defaultValue) {
+      case 'number':
+        payload.type = 'decimal'
+        payload.dataDecimal = defaultValue
+        break
+      case 'string':
+        payload.type = 'string'
+        payload.dataString = defaultValue
+        break
+      case 'boolean':
+        payload.type = 'boolean'
+        payload.dataBoolean = defaultValue
+        break
+      case 'object':
+        payload.type = 'json'
+        payload.dataJson = defaultValue
+        break
+      default:
+        break
+    }
+
+    return await Constant.findOrCreate({
+      where: { name },
+      defaults: payload,
+    })
+  }
+
+  static async upsertPyName(
+    name: string,
+    value: ConstantDataValue,
+    kind: ConstantKind = 'parameter'
+  ): Promise<Constant> {
+    const [c, created] = await Constant.findOrCreatePyName(name, value, kind)
+
+    if (created) {
+      return c
+    }
+
+    c.setConstantValue(value)
+    await c.save()
+    await c.reload()
+    return c
+  }
+
+  // eslint-disable-next-line prettier/prettier
+  static async get<T extends ConstantNames>(name: T): Promise<NameMap<T> | null> {
+    const value = await Constant.findValueByName(name)
+    return value as NameMap<T> | null
+  }
+
+  // eslint-disable-next-line prettier/prettier
+  static async set<T extends ConstantNames>(name: T, value: NameMap<T>): Promise<Constant> {
+    const constant = await Constant.upsertPyName(name, value)
+    return constant
+  }
+}

+ 83 - 0
src/db/models/Tweet.ts

@@ -0,0 +1,83 @@
+import {
+  Table,
+  Column,
+  AllowNull,
+  DataType,
+  Model,
+  Default,
+} from 'sequelize-typescript'
+
+export interface TweetData {
+  username: string
+  statusId: string
+  url: string
+  cleanHTML: string
+  body: object
+  order: number
+}
+
+@Table({
+  modelName: 'tweet',
+  indexes: [
+    {
+      fields: ['statusId'],
+      unique: true,
+    },
+  ],
+})
+export default class Tweet extends Model {
+  @AllowNull(false)
+  @Column(DataType.CHAR(255))
+  get username(): string {
+    return this.getDataValue('username')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.CHAR(30))
+  get statusId(): string {
+    return this.getDataValue('statusId')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.TEXT)
+  get url(): string {
+    return this.getDataValue('url')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.TEXT)
+  get cleanHTML(): string {
+    return this.getDataValue('cleanHTML')
+  }
+
+  @AllowNull(false)
+  @Default(false)
+  @Column(DataType.BOOLEAN)
+  get disabled(): boolean {
+    return this.getDataValue('disabled')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.JSON)
+  get body(): object {
+    return this.getDataValue('body')
+  }
+
+  @AllowNull(false)
+  @Default(0)
+  @Column(DataType.INTEGER.UNSIGNED)
+  get order(): number {
+    return this.getDataValue('order')
+  }
+
+  getData(): TweetData {
+    return {
+      username: this.username,
+      statusId: this.statusId,
+      url: this.url,
+      cleanHTML: this.cleanHTML,
+      body: this.body,
+      order: this.order,
+    }
+  }
+}

+ 5 - 0
src/db/models/index.ts

@@ -0,0 +1,5 @@
+import Constant from './Constant'
+import Application from './Application'
+import Tweet from './Tweet'
+
+export { Constant, Application, Tweet }

+ 11 - 0
src/db/models/types.ts

@@ -0,0 +1,11 @@
+export type ConstantKind = 'parameter' | 'information' | 'ui'
+export type ConstantType = 'decimal' | 'string' | 'boolean' | 'json'
+
+export interface ConstantData {
+  name: string
+  kind: ConstantKind
+  type: ConstantType
+  memo: string | null
+  readOnly: boolean
+  value: string | boolean | number | object | null | undefined
+}

+ 5 - 0
src/exceptions/Common.ts

@@ -0,0 +1,5 @@
+export class NotImplementException extends Error {
+  constructor() {
+    super('not implement')
+  }
+}

+ 31 - 0
src/exceptions/DatabaseException.ts

@@ -0,0 +1,31 @@
+import HttpException from './HttpException'
+
+export class DatabaseException extends HttpException {
+  constructor(message = 'database error') {
+    super(500, message)
+  }
+}
+
+export class DatabaseCreateException extends DatabaseException {
+  constructor(id = 'Entity', extra?: string) {
+    super(`${id} create fail${extra ? ': ' + extra : ''}`)
+  }
+}
+
+export class DatabaseModifyException extends DatabaseException {
+  constructor(id = 'Entity', extra?: string) {
+    super(`${id} modify fail${extra ? ': ' + extra : ''}`)
+  }
+}
+
+export class DatabaseDeleteException extends DatabaseException {
+  constructor(id = 'Entity', extra?: string) {
+    super(`${id} delete fail${extra ? ': ' + extra : ''}`)
+  }
+}
+
+export class DatabaseQueryException extends DatabaseException {
+  constructor(message = 'database query error') {
+    super(message)
+  }
+}

+ 30 - 0
src/exceptions/EndpointException.ts

@@ -0,0 +1,30 @@
+import HttpException from './HttpException'
+
+export class EndpointException extends HttpException {
+  constructor(public readonly code: number, message: string, status = 401) {
+    super(status, message)
+  }
+}
+export class EndpointNotFoundException extends EndpointException {
+  constructor() {
+    super(1000, 'endpoint info not found')
+  }
+}
+
+export class EndpointVersionExpiredFoundException extends EndpointException {
+  constructor() {
+    super(1001, 'endpoint version expired')
+  }
+}
+
+export class EndpointCurrentVersionNotFoundException extends EndpointException {
+  constructor() {
+    super(1002, 'current version not found')
+  }
+}
+
+export class EndpointVersionNotValidException extends EndpointException {
+  constructor() {
+    super(1003, 'endpoint version not valid')
+  }
+}

+ 11 - 0
src/exceptions/HttpException.ts

@@ -0,0 +1,11 @@
+class HttpException extends Error {
+  status: number
+  message: string
+  constructor(status: number, message: string) {
+    super(message)
+    this.status = status
+    this.message = message
+  }
+}
+
+export default HttpException

+ 9 - 0
src/exceptions/NotFoundException.ts

@@ -0,0 +1,9 @@
+import HttpException from './HttpException'
+
+class NotFoundException extends HttpException {
+  constructor(id = 'Entity') {
+    super(404, `${id} not found`)
+  }
+}
+
+export default NotFoundException

+ 19 - 0
src/exceptions/UserException.ts

@@ -0,0 +1,19 @@
+import HttpException from './HttpException'
+
+export class UserException extends HttpException {
+  constructor(message = 'user error', code = 500) {
+    super(code, message)
+  }
+}
+
+export class NeedUserException extends UserException {
+  constructor() {
+    super('need user', 401)
+  }
+}
+
+export class UserVerifyFailException extends UserException {
+  constructor(account = 'Account', extra?: string) {
+    super(`${account} verify fail${extra ? ': ' + extra : ''}`, 401)
+  }
+}

+ 0 - 0
src/global.d.ts


+ 155 - 0
src/index.ts

@@ -0,0 +1,155 @@
+import sequelize from './db'
+import { Sequelize } from 'sequelize-typescript'
+import express, { Request } from 'express'
+import path from 'path'
+import cookieParser from 'cookie-parser'
+import logger from 'morgan'
+import helmet from 'helmet'
+import { Controller } from './controllers'
+import errorMiddleware from './middleware/error.middleware'
+import setHeaderMiddleware from './middleware/setHeaders.middleware'
+import { Server } from 'http'
+import 'moment-timezone'
+// import { Constant } from './db/models'
+// import { defaultSolanaHelper } from './utils/SolanaHelper'
+
+const deleteRequestBodySaver = function (
+  req: Request,
+  res: any,
+  buf: any,
+  encoding: any
+): void {
+  if (buf?.length && req.method.toLocaleUpperCase() === 'DELETE') {
+    try {
+      const str = buf.toString(encoding || 'utf8')
+      req.body = JSON.parse(str)
+    } catch (error) {}
+  }
+}
+
+export interface APPOtions {
+  port: number
+  controllers: Controller[]
+  tgBotToken: string
+}
+
+const defaultOptions: APPOtions = {
+  port: 9099,
+  controllers: [],
+  tgBotToken: process.env.TG_BOT_TOKEN ?? '',
+}
+
+export default class APP {
+  private readonly option
+  public app: express.Application
+  private db?: Sequelize
+  private server?: Server
+
+  constructor(options: Partial<APPOtions>) {
+    this.option = {
+      ...defaultOptions,
+      ...options,
+    }
+    this.app = express()
+
+    process.once('SIGINT', () => {
+      this.destory().catch((e) => console.log(e))
+    })
+    process.once('SIGTERM', () => {
+      this.destory().catch((e) => console.log(e))
+    })
+  }
+
+  public async start(): Promise<void> {
+    this.listen()
+    // await this.bot.start()
+  }
+
+  private listen(): void {
+    this.server = this.app.listen(this.option.port, () => {
+      console.log(`App listening on the port ${this.option.port}`)
+    })
+  }
+
+  public async destory(): Promise<void> {
+    if (this.server) {
+      this.server.close()
+    }
+    if (this.db) {
+      await this.db.close()
+    }
+    process.exit(0)
+  }
+
+  async init(): Promise<void> {
+    this.initializeConfig()
+    this.initializeMiddlewares()
+    this.initializeControllers()
+    this.initializeErrorHandling()
+    await this.initializeDatabase()
+    await this.initializeConstants()
+    // await defaultSolanaHelper.getProvider()
+  }
+
+  private initializeConfig(): void {
+    // 设置密钥
+    this.app.set('jwtTokenSecret', process.env.JWT_SECRET ?? '666')
+    // view engine setup
+    this.app.set('views', path.join(__dirname, 'views'))
+    this.app.set('view engine', 'ejs')
+  }
+
+  private initializeMiddlewares(): void {
+    this.app.use(
+      helmet({
+        contentSecurityPolicy: false,
+      })
+    )
+    this.app.use(logger('dev'))
+    this.app.use(cookieParser(process.env.COOKIE_SECRET))
+    this.app.use(express.json({ verify: deleteRequestBodySaver }))
+    this.app.use(express.urlencoded({ extended: true }))
+    this.app.use(
+      express.static(path.join(__dirname, '../public'), {
+        setHeaders(res) {
+          res.set('Access-Control-Allow-Origin', '*')
+        },
+      })
+    )
+    this.app.use(setHeaderMiddleware)
+  }
+
+  private initializeControllers(): void {
+    const controllers = this.option.controllers
+    controllers.forEach((controller) => {
+      this.app.use(controller.path, controller.router)
+    })
+  }
+
+  private initializeErrorHandling(): void {
+    this.app.use((_req, res) => {
+      res.status(404)
+      res.send('This api does not exist!')
+    })
+    this.app.use(errorMiddleware)
+  }
+
+  private async initializeDatabase(force = false): Promise<void> {
+    this.db = await sequelize.sync({ force })
+  }
+
+  private async initializeConstants(): Promise<void> {
+    // const SolFeeReceiver = await Constant.get('SolFeeReceiver')
+    // if (!SolFeeReceiver) {
+    //   throw new Error('SolFeeReceiver not set')
+    // }
+    // const SolFeeBps = await Constant.get('SolFeeBps')
+    // if (!SolFeeBps) {
+    //   throw new Error('SolFeeBps not set')
+    // }
+    // const bootEndpoints = await Constant.get('BootEndpoints')
+    // if (!bootEndpoints) {
+    //   throw new Error('BootEndpoints not set')
+    // }
+  }
+}

+ 57 - 0
src/middleware/apikey.middleware.ts

@@ -0,0 +1,57 @@
+import { NextFunction, Request, Response } from 'express'
+import { verfiyApikey } from '../services/application.service'
+import { Application } from '../db/models'
+
+export interface ApplicationRequest extends Request {
+  authApplication: Application
+}
+
+interface ApiKeyMiddlewareOptions {
+  header: string
+  allowNull: boolean
+}
+
+const defaultOptions: ApiKeyMiddlewareOptions = {
+  header: 'x-api-key',
+  allowNull: false,
+}
+
+const apiKeyMiddleware = (
+  options: Partial<ApiKeyMiddlewareOptions> = {}
+): ((request: Request, response: Response, next: NextFunction) => void) => {
+  const useOptions = { ...defaultOptions, ...options }
+
+  return (request: Request, response: Response, next: NextFunction) => {
+    const token = request.headers[useOptions.header]
+
+    if (!token && useOptions.allowNull) {
+      next()
+      return
+    }
+
+    if (!token) {
+      response.status(401).end('API-KEY needed')
+      return
+    }
+    try {
+      verfiyApikey(token as string)
+        .then((app) => {
+          if (!app) {
+            response.status(401).end('Application not exsit')
+            return
+          }
+          // eslint-disable-next-line @typescript-eslint/no-extra-semi
+          ;(request as ApplicationRequest).authApplication = app
+
+          next()
+        })
+        .catch(() => {
+          response.status(401).end('Find application failed')
+        })
+    } catch (err) {
+      response.status(401).end('Access token parse failed')
+    }
+  }
+}
+
+export default apiKeyMiddleware

+ 21 - 0
src/middleware/error.middleware.ts

@@ -0,0 +1,21 @@
+import { NextFunction, Request, Response } from 'express'
+import HttpException from '../exceptions/HttpException'
+
+function errorMiddleware(
+  error: HttpException,
+  _request: Request,
+  response: Response,
+  _next: NextFunction
+): void {
+  const status = error.status || 500
+  const message = error.message || 'Something went wrong'
+  response.status(status).send({
+    success: false,
+    error: {
+      message,
+      code: status,
+    },
+  })
+}
+
+export default errorMiddleware

+ 54 - 0
src/middleware/jsonResponse.middleware.ts

@@ -0,0 +1,54 @@
+import { NextFunction, Request, Response } from 'express'
+
+export interface JsonResponse<T> extends Response {
+  jsonSuccess: (data: T, token?: string) => void
+  jsonError: (message: string, code: string | number, data?: any) => void
+}
+
+export interface JsonResponseData<T> {
+  success: boolean
+  data?: T
+  error?: {
+    message: string
+    code: string | number
+  }
+  token?: string
+}
+
+function jsonResponseMiddleware<T>(
+  _request: Request,
+  response: Response,
+  next: NextFunction
+): void {
+  try {
+    const resp = response as JsonResponse<T>
+    resp.jsonSuccess = (data: T, token?: string): void => {
+      const ret: JsonResponseData<T> = {
+        success: true,
+        data,
+        token,
+      }
+      response.json(ret)
+    }
+    resp.jsonError = (
+      message: string,
+      code: string | number,
+      data?: any
+    ): void => {
+      const ret: JsonResponseData<T> = {
+        data,
+        success: false,
+        error: {
+          message,
+          code,
+        },
+      }
+      response.json(ret)
+    }
+    next()
+  } catch (error) {
+    next(error)
+  }
+}
+
+export default jsonResponseMiddleware

+ 80 - 0
src/middleware/jwt.middleware.ts

@@ -0,0 +1,80 @@
+import { NextFunction, Request, Response } from 'express'
+import jwt from 'jwt-simple'
+import applicationServices from '../services/application.service'
+import User, { RoleType, UserData } from '../db/models/User'
+import { JWTContent, verfiyJWTToken } from '../utils/jwt'
+
+export interface JWTRequest extends Request {
+  jwtData: JWTContent
+  jwtUser: User
+  jwtRoles: RoleType[]
+  jwtUserData: UserData
+}
+
+export interface jwtMiddlewareOptions {
+  needVip: boolean
+  needAdmin: boolean
+}
+
+const defaultOptions: jwtMiddlewareOptions = {
+  needVip: false,
+  needAdmin: false,
+}
+
+const jwtMiddleware = (
+  options: Partial<jwtMiddlewareOptions> = {}
+): ((request: Request, response: Response, next: NextFunction) => void) => {
+  const useOptions = { ...defaultOptions, ...options }
+
+  return (request: Request, response: Response, next: NextFunction) => {
+    verfiyJWTToken(request, request.get('x-access-token'))
+      .then(async (decoded) => {
+        const [data, user] = await applicationServices.getUserData(decoded.uuid)
+        if (useOptions.needAdmin && !data.roles?.includes('alice')) {
+          throw new Error('Access token has expired, 1')
+        }
+
+        // eslint-disable-next-line @typescript-eslint/no-extra-semi
+        ;(request as JWTRequest).jwtRoles = data.roles ?? []
+        ;(request as JWTRequest).jwtData = decoded
+        ;(request as JWTRequest).jwtUser = user
+        ;(request as JWTRequest).jwtUserData = data
+        next()
+      })
+      .catch((e) => {
+        response.status(401).end(e.message)
+      })
+  }
+}
+
+const jwtAllowNullMiddleware = (
+  request: Request,
+  response: Response,
+  next: NextFunction
+): void => {
+  const token = request.get('x-access-token')
+
+  if (!token || token.length < 10) {
+    next()
+    return
+  }
+  // 解析
+  const { app } = request
+  try {
+    const decoded = jwt.decode(token, app.get('jwtTokenSecret')) as JWTContent
+    // todo  handle token here
+    if (decoded.exp <= Date.now()) {
+      next()
+      return
+    }
+    // eslint-disable-next-line @typescript-eslint/no-extra-semi
+    ;(request as JWTRequest).jwtData = decoded
+    next()
+  } catch (err) {
+    next()
+  }
+}
+
+jwtMiddleware.allowNull = jwtAllowNullMiddleware
+
+export default jwtMiddleware

+ 14 - 0
src/middleware/setHeaders.middleware.ts

@@ -0,0 +1,14 @@
+import { NextFunction, Request, Response } from 'express'
+
+const { DATACENTER } = process.env
+
+function setHeaderMiddleware(
+  request: Request,
+  response: Response,
+  next: NextFunction
+): void {
+  response.header('SB-DATACENTER', DATACENTER)
+  next()
+}
+
+export default setHeaderMiddleware

+ 45 - 0
src/middleware/upload.middleware.ts

@@ -0,0 +1,45 @@
+import multer from 'multer'
+import fs from 'fs'
+import { v4 } from 'uuid'
+import moment from 'moment'
+import { Request } from 'express'
+
+const memoryUpload = multer()
+
+const { UPLOAD_DIST } = process.env
+
+function getToday(): string {
+  return moment(new Date()).format('yyyyMMDD')
+}
+
+const diskStorage = multer.diskStorage({
+  destination(req, file, cb) {
+    const dir = UPLOAD_DIST ?? ''
+    cb(null, dir)
+  },
+  filename(req, file, cb) {
+    const today = getToday()
+    const dir = `${UPLOAD_DIST ?? ''}/${today}`
+    if (!fs.existsSync(dir)) {
+      fs.mkdirSync(dir)
+    }
+
+    const { originalname } = file
+    const fix: string[] = originalname.split('.')
+    const newName = fix.length === 0 ? v4() : `${v4()}.${fix[fix.length - 1]}`
+    cb(null, `${today}/${newName}`)
+  },
+})
+
+const diskUpload = multer({ storage: diskStorage })
+
+const uploadMiddleware = {
+  memory: memoryUpload,
+  disk: diskUpload,
+}
+
+export default uploadMiddleware
+
+export interface FileRequest extends Request {
+  file: Express.Multer.File
+}

+ 33 - 0
src/middleware/utils.ts

@@ -0,0 +1,33 @@
+import { Request } from 'express'
+import parseFingerprint, { Fingerprint } from '../utils/fingerprint'
+import { stringifyIP } from '../utils/ip'
+
+export interface SacteTokenInfo {
+  jwt: string
+  fingerprint: Fingerprint | null
+  ip: string | null
+  userAgent: string | null
+}
+
+export function getTobotoToken(request: Request): SacteTokenInfo | null {
+  // format: [x.y.z](jwt)[.0..0](base64 fingerprint)
+  const token = request.get('x-access-token')
+  if (!token || token.length < 10) return null
+  const arr = token.split('.')
+  if (arr.length < 3) return null
+  const jwt = arr.slice(0, 3).join('.')
+  const ret: SacteTokenInfo = {
+    jwt,
+    fingerprint: null,
+    ip: null,
+    userAgent: request.headers['user-agent'] ?? null,
+  }
+  if (arr[3]) {
+    ret.fingerprint = parseFingerprint(atob(arr[3]))
+  }
+  if (request.headers['x-forwarded-for']) {
+    // format 255.255.255.255
+    ret.ip = stringifyIP(request.headers['x-forwarded-for'] as string)
+  }
+  return ret
+}

+ 13 - 0
src/scripts/env.ts

@@ -0,0 +1,13 @@
+import Decimal from 'decimal.js-light'
+import sequelize from '../db'
+
+Decimal.set({ toExpPos: 999, toExpNeg: -999, precision: 64 })
+
+export default async function env(task: () => Promise<void>): Promise<void> {
+  const db = await sequelize.sync()
+
+  await task()
+
+  console.log('done!')
+  await db.close()
+}

+ 35 - 0
src/services/twitterService/clients.ts

@@ -0,0 +1,35 @@
+import { Client, auth } from 'twitter-api-sdk'
+import { X_BOT_CLIENT_ID, X_BOT_CLIENT_SECRET } from '../../constants'
+import { Constant } from '../../db/models'
+
+// Initialize auth client first
+let authClient: auth.OAuth2User
+
+export async function getAuthClient(): Promise<auth.OAuth2User> {
+  if (authClient) return authClient
+  const token = await Constant.get('xBotToken')
+
+  if (token) {
+    authClient = new auth.OAuth2User({
+      client_id: X_BOT_CLIENT_ID,
+      client_secret: X_BOT_CLIENT_SECRET,
+      callback: 'http://localhost:8089/api/v1/x/callback',
+      scopes: ['tweet.read', 'users.read', 'offline.access'],
+      token,
+    })
+  } else {
+    authClient = new auth.OAuth2User({
+      client_id: X_BOT_CLIENT_ID,
+      client_secret: X_BOT_CLIENT_SECRET,
+      callback: 'http://localhost:8089/api/v1/x/callback',
+      scopes: ['tweet.read', 'users.read', 'offline.access'],
+    })
+  }
+
+  return authClient
+}
+
+export async function getTwitterClient(): Promise<Client> {
+  const authClient = await getAuthClient()
+  return new Client(authClient)
+}

+ 198 - 0
src/services/twitterService/index.ts

@@ -0,0 +1,198 @@
+import { Constant, Tweet } from '../../db/models'
+import axios from 'axios'
+import { getAuthClient, getTwitterClient } from './clients'
+import {
+  makePaginate,
+  PaginateOptions,
+  PaginationConnection,
+} from 'sequelize-cursor-pagination'
+import { TweetData } from '../../db/models/Tweet'
+
+const STATE = 'banana'
+
+async function generateAuthURL(): Promise<string> {
+  const authClient = await getAuthClient()
+  const authUrl = authClient.generateAuthURL({
+    state: STATE,
+    code_challenge_method: 's256',
+  })
+  return authUrl
+}
+
+async function saveAccessToken(token: any): Promise<void> {
+  await Constant.set('xBotToken', token)
+}
+
+async function requestAccessToken(code: string, state: string): Promise<void> {
+  if (state !== STATE) {
+    throw new Error("State isn't matching")
+  }
+  const authClient = await getAuthClient()
+  const token = await authClient.requestAccessToken(code)
+  await saveAccessToken(token.token)
+}
+
+async function refreshAccessToken(): Promise<void> {
+  const authClient = await getAuthClient()
+  const token = await authClient.refreshAccessToken()
+  await saveAccessToken(token.token)
+}
+
+const tweetUrlReg =
+  /^https:\/\/x\.com\/[a-zA-Z0-9_]+\/status\/([0-9]+)(\?=.*)?$/
+
+const removeScriptReg = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi
+
+async function genenrateTweetByPublishAPI(url: string): Promise<Tweet> {
+  const match = url.match(tweetUrlReg)
+  if (!match) {
+    throw new Error('Invalid tweet URL')
+  }
+
+  const statusId = match[1]
+
+  try {
+    const resp = await axios.get('https://publish.twitter.com/oembed', {
+      params: {
+        url,
+        partner: '',
+        hideConversation: 'on',
+        hide_thread: 'true',
+        omit_script: 'false',
+      },
+    })
+
+    const data = resp.data
+    if (!data.html || !data.author_name) {
+      throw new Error('Failed to fetch tweet HTML')
+    }
+    const cleanHTML = data.html.replace(removeScriptReg, '').trim()
+
+    const [tweet, created] = await Tweet.findOrCreate({
+      where: {
+        statusId,
+      },
+      defaults: {
+        statusId,
+        username: data.author_name,
+        url,
+        body: data,
+        cleanHTML,
+      },
+    })
+
+    if (!created) {
+      tweet.setDataValue('username', data.author_name)
+      tweet.setDataValue('url', url)
+      tweet.setDataValue('body', data)
+      tweet.setDataValue('cleanHTML', cleanHTML)
+      await tweet.save()
+    }
+
+    return tweet
+  } catch (error: any) {
+    if (error.response?.data?.error) {
+      throw new Error(error.response.data.error)
+    }
+    throw error
+  }
+}
+
+async function updateTweet(statusId: string): Promise<Tweet> {
+  const tweet = await Tweet.findOne({ where: { statusId } })
+  if (!tweet) {
+    throw new Error('Tweet not found')
+  }
+  return await genenrateTweetByPublishAPI(tweet.url)
+}
+
+// console.log(Tweet.rawAttributes)
+let paginate: (
+  this: unknown,
+  queryOptions: PaginateOptions<Tweet>
+) => Promise<PaginationConnection<Tweet>>
+
+async function paginateTweetByOrder(
+  after?: string,
+  limit = 30
+): Promise<PaginationConnection<TweetData>> {
+  if (!paginate) {
+    paginate = makePaginate(Tweet)
+  }
+
+  const secondResult = await paginate({
+    order: [
+      ['order', 'DESC'],
+      ['id', 'DESC'],
+    ],
+    limit,
+    after,
+    where: {
+      disabled: false,
+    },
+  })
+
+  const ret: PaginationConnection<TweetData> = {
+    ...secondResult,
+    edges: secondResult.edges.map((edge) => ({
+      cursor: edge.node.id,
+      node: edge.node.getData(),
+    })),
+  }
+
+  return ret
+}
+
+export interface GenerateResult {
+  url: string
+  success: boolean
+  error?: string
+}
+
+async function bulkGenerateTweetsByPublishAPI(
+  urls: string[]
+): Promise<GenerateResult[]> {
+  const results: GenerateResult[] = []
+  for (const url of urls) {
+    try {
+      const tweet = await genenrateTweetByPublishAPI(url)
+      results.push({ url, success: true })
+    } catch (e) {
+      results.push({
+        url,
+        success: false,
+        error: (e as Error).message ?? 'unknown error',
+      })
+    }
+  }
+  return results
+}
+
+async function makeTweetTop(statusId: string, top = true): Promise<void> {
+  const tweet = await Tweet.findOne({ where: { statusId } })
+  if (!tweet) {
+    throw new Error('Tweet not found')
+  }
+  if (top) {
+    const maxOrder: number = await Tweet.max('order')
+    tweet.setDataValue('order', maxOrder ? maxOrder + 1 : 1)
+  } else {
+    tweet.setDataValue('order', 0)
+  }
+  await tweet.save()
+}
+
+const twitterService = {
+  getTwitterClient,
+  generateAuthURL,
+  STATE,
+  genenrateTweetByPublishAPI,
+  requestAccessToken,
+  refreshAccessToken,
+  paginateTweetByOrder,
+  updateTweet,
+  bulkGenerateTweetsByPublishAPI,
+  makeTweetTop,
+}
+
+export default twitterService

+ 0 - 0
src/services/types.ts


+ 0 - 0
src/services/utils.ts


+ 32 - 0
src/start.ts

@@ -0,0 +1,32 @@
+import APP from '.'
+import Decimal from 'decimal.js-light'
+import { PingpongController, XController, TweetController } from './controllers'
+import { PORT, TG_BOT_TOKEN } from './constants'
+
+Decimal.set({ toExpPos: 999, toExpNeg: -999, precision: 64 })
+
+const app = new APP({
+  port: PORT ? parseInt(PORT) : undefined,
+  controllers: [
+    new PingpongController(),
+    new XController(),
+    new TweetController(),
+  ],
+  tgBotToken: TG_BOT_TOKEN,
+})
+
+app
+  .init()
+  .then(async () => {
+    await app.start()
+  })
+  .then(() => {
+    console.log('server inited')
+  })
+  .catch(async (e) => {
+    console.log(e)
+    return await app.destory()
+  })
+  .catch((e) => {
+    console.log(e)
+  })

+ 10 - 0
src/test/env.ts

@@ -0,0 +1,10 @@
+// import sequelize from '../db'
+
+// export default async function env(task: () => Promise<void>): Promise<void> {
+//   const db = await sequelize.sync()
+
+//   await task()
+
+//   console.log('done!')
+//   await db.close()
+// }

+ 0 - 0
src/types.ts


+ 147 - 0
src/utils/EVMHelper/EVMHelper.ts

@@ -0,0 +1,147 @@
+import {
+  DefaultEVMHelperChainRPCs,
+  DefaultEVMHelperExplorerURLs,
+  DefaultEVMHelperScanAPIUrls,
+  SupportedEVMHelperChains,
+} from './types'
+import { RPC } from './dbModels'
+import { ethers, JsonRpcProvider, Provider, TransactionReceipt, Wallet } from 'ethers6'
+import axios from 'axios'
+
+function isPrivateKey(pk: string): boolean {
+  return pk.startsWith('0x') && pk.length === 66
+}
+
+export default class EVMHelper {
+  constructor(readonly chain: SupportedEVMHelperChains) {}
+
+  get explorer(): string {
+    return DefaultEVMHelperExplorerURLs[this.chain]
+  }
+
+  async getRandomRPC(): Promise<[string, number]> {
+    const rpcs = await RPC.getRPCList(this.chain)
+    if (rpcs.length === 0) {
+      return [DefaultEVMHelperChainRPCs[this.chain], -1]
+    }
+    const rpc = rpcs[Math.floor(Math.random() * rpcs.length)]
+    return [rpc.rpc, rpc.id]
+  }
+
+  async getRpcProvider(rpc?: string): Promise<JsonRpcProvider> {
+    let usingRpcIndex = rpc ? -2 : -1
+    let usingRpc = rpc ?? ''
+    if (!rpc) {
+      const [rpc, id] = await this.getRandomRPC()
+      usingRpc = rpc
+      usingRpcIndex = id
+    }
+    const provider = new JsonRpcProvider(usingRpc)
+    ;(provider as any).__usingRpcIndex = usingRpcIndex
+    ;(provider as any).__usingRpc = usingRpc
+    return provider
+  }
+
+  generateWallet(provider?: null | Provider): Wallet {
+    return Wallet.createRandom(provider) as unknown as Wallet
+  }
+
+  getWallet(
+    rpcProvider: JsonRpcProvider,
+    privateKeyOrMnemonic: string
+  ): Wallet {
+    let privateKey = privateKeyOrMnemonic
+    // console.log(`Using private key: ${privateKey}`)
+    // console.log(privateKey[privateKey.length - 2])
+    if (!isPrivateKey(privateKey)) {
+      const w = ethers.Wallet.fromPhrase(privateKeyOrMnemonic, rpcProvider)
+      privateKey = w.privateKey
+    }
+    const wallet = new ethers.Wallet(privateKey, rpcProvider)
+    return wallet
+  }
+
+  async getRandomProviderWallet(privateKeyOrMnemonic: string): Promise<Wallet> {
+    const rpcProvider = await this.getRpcProvider()
+    return this.getWallet(rpcProvider, privateKeyOrMnemonic)
+  }
+
+  link(sub: string, hash: string): string {
+    return `${this.explorer}/${sub}/${hash}`
+  }
+
+  txLink(hash: string): string {
+    return this.link('tx', hash)
+  }
+
+  // https://api.bscscan.com/api?module=account&action=txlist&address=0xb62bf8d41fe27622924971887d8f482b00e50660&startblock=0&endblock=99999999&page=1&offset=10&sort=desc
+  async getNormalTransactionsByAddress(
+    address: string,
+    apikey?: string,
+    limit = 10
+  ): Promise<TransactionReceipt[]> {
+    const scanAPI = DefaultEVMHelperScanAPIUrls[this.chain]
+    if (!scanAPI) {
+      throw new Error(`Scan API URL for ${this.chain} not found`)
+    }
+    const url = `${scanAPI}/api?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&page=1&offset=${limit}&sort=desc${
+      apikey ? `&apikey=${apikey}` : ''
+    }`
+    const res = await axios.get(url)
+    const data = res.data
+    if (!data.result) {
+      throw new Error(`Failed to get transactions for ${address}`)
+    }
+    const ret: TransactionReceipt[] = []
+    // {
+    //   "blockNumber": "29985367",
+    //   "blockHash": "0x910b9a716137b5cabdfa49b5934ea8f7a1867fb3ba1ec178a202d2fb89079a80",
+    //   "timeStamp": "1689427357",
+    //   "hash": "0x0a7c9fd6aea3347e67dbb51a2f24ca604a0498e0d2024691aee7a5fcd44ee699",
+    //   "nonce": "12",
+    //   "transactionIndex": "104",
+    //   "from": "0xb62bf8d41fe27622924971887d8f482b00e50660",
+    //   "to": "0xd10d9d9a9a9a8f872ea34608735827ca88f13b2d",
+    //   "value": "8326109000000000",
+    //   "gas": "21000",
+    //   "gasPrice": "3000000000",
+    //   "input": "0x",
+    //   "methodId": "0x",
+    //   "functionName": "",
+    //   "contractAddress": "",
+    //   "cumulativeGasUsed": "15084533",
+    //   "txreceipt_status": "1",
+    //   "gasUsed": "21000",
+    //   "confirmations": "724",
+    //   "isError": "0"
+    //   }
+    const provider = await this.getRpcProvider()
+    if (data.status !== '1') {
+      throw new Error(data.result)
+    }
+    data.result.forEach((scanTx: any) => {
+      const tx = new TransactionReceipt(
+        {
+          blockNumber: parseInt(scanTx.blockNumber),
+          blockHash: scanTx.blockHash,
+          hash: scanTx.hash,
+          to: scanTx.to,
+          from: scanTx.from,
+          index: parseInt(scanTx.transactionIndex),
+          gasUsed: scanTx.gasUsed,
+          gasPrice: scanTx.gasPrice,
+          cumulativeGasUsed: scanTx.cumulativeGasUsed,
+          status: parseInt(scanTx.txreceipt_status),
+          contractAddress: null,
+          logsBloom: '0x0',
+          logs: [],
+          type: 0,
+          root: null,
+        },
+        provider
+      )
+      ret.push(tx)
+    })
+    return ret
+  }
+}

+ 181 - 0
src/utils/EVMHelper/contracts/AaveABI.json

@@ -0,0 +1,181 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "asset",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "interestRateMode",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint16",
+        "name": "referralCode",
+        "type": "uint16"
+      },
+      {
+        "internalType": "address",
+        "name": "onBehalfOf",
+        "type": "address"
+      }
+    ],
+    "name": "borrow",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "asset",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "onBehalfOf",
+        "type": "address"
+      },
+      {
+        "internalType": "uint16",
+        "name": "referralCode",
+        "type": "uint16"
+      }
+    ],
+    "name": "deposit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "addressIn",
+        "type": "address"
+      }
+    ],
+    "name": "getUserAccountData",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "totalCollateralETH",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "totalDebtETH",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "availableBorrowsETH",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "currentLiquidationThreshold",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "ltv",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "healthFactor",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "asset",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "rateMode",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "onBehalfOf",
+        "type": "address"
+      }
+    ],
+    "name": "repay",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "asset",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "withdraw",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 53 - 0
src/utils/EVMHelper/contracts/MTCoreCard.ts

@@ -0,0 +1,53 @@
+import {
+  JsonRpcSigner,
+  ContractTransactionResponse,
+  ContractTransactionReceipt,
+} from 'ethers'
+import { CONTRACT_CORECARD } from '../../../constants'
+import Erc721TokenContract from './erc721'
+import ABI from './MTCoreCardABI.json'
+
+export type MTCoreCardLevel = 0 | 1 | 2 | 3 | 4
+
+export default class MTCoreCardContract extends Erc721TokenContract {
+  constructor(provider: JsonRpcSigner) {
+    super(CONTRACT_CORECARD, provider)
+    this.ABI = ABI
+  }
+
+  async mint(
+    level: MTCoreCardLevel,
+    price: string,
+    address?: string
+  ): Promise<ContractTransactionReceipt> {
+    const useAddress = address ?? (await this.getCallerAddress())
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.mint(
+      useAddress,
+      level,
+      {
+        value: price,
+      }
+    )
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async getMintPrice(level: MTCoreCardLevel): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.getMintPrice(level)
+    return bn.toString()
+  }
+
+  async getAllMintPrice(): Promise<[string, string, string, string, string]> {
+    const lv0 = await this.getMintPrice(0)
+    const lv1 = await this.getMintPrice(1)
+    const lv2 = await this.getMintPrice(2)
+    const lv3 = await this.getMintPrice(3)
+    const lv4 = await this.getMintPrice(4)
+    return [lv0, lv1, lv2, lv3, lv4]
+  }
+}

+ 677 - 0
src/utils/EVMHelper/contracts/MTCoreCardABI.json

@@ -0,0 +1,677 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "string",
+        "name": "name_",
+        "type": "string"
+      },
+      {
+        "internalType": "string",
+        "name": "symbol_",
+        "type": "string"
+      },
+      {
+        "internalType": "string",
+        "name": "baseURI_",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "approved",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "operator",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "bool",
+        "name": "approved",
+        "type": "bool"
+      }
+    ],
+    "name": "ApprovalForAll",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "enum MTPowerCard.Grade",
+        "name": "grade",
+        "type": "uint8"
+      }
+    ],
+    "name": "PowerCardMinted",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "previousAdminRole",
+        "type": "bytes32"
+      },
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "newAdminRole",
+        "type": "bytes32"
+      }
+    ],
+    "name": "RoleAdminChanged",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      }
+    ],
+    "name": "RoleGranted",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      }
+    ],
+    "name": "RoleRevoked",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [],
+    "name": "BURNER_ROLE",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "DEFAULT_ADMIN_ROLE",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "burn",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "getApproved",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      }
+    ],
+    "name": "getRoleAdmin",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "grantRole",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "hasRole",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "operator",
+        "type": "address"
+      }
+    ],
+    "name": "isApprovedForAll",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "enum MTPowerCard.Grade",
+        "name": "grade",
+        "type": "uint8"
+      }
+    ],
+    "name": "mint",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum MTPowerCard.Grade",
+        "name": "grade",
+        "type": "uint8"
+      }
+    ],
+    "name": "getMintPrice",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "ownerOf",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "renounceRole",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "role",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "revokeRole",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "safeTransferFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bytes",
+        "name": "data",
+        "type": "bytes"
+      }
+    ],
+    "name": "safeTransferFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "operator",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "approved",
+        "type": "bool"
+      }
+    ],
+    "name": "setApprovalForAll",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "string",
+        "name": "baseURI_",
+        "type": "string"
+      }
+    ],
+    "name": "setBaseURI",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "burner",
+        "type": "address"
+      }
+    ],
+    "name": "setBurner",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "enum MTPowerCard.Grade",
+        "name": "grade",
+        "type": "uint8"
+      },
+      {
+        "internalType": "uint256",
+        "name": "price",
+        "type": "uint256"
+      }
+    ],
+    "name": "setPrice",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes4",
+        "name": "interfaceId",
+        "type": "bytes4"
+      }
+    ],
+    "name": "supportsInterface",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "tokenURI",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "tokenId",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "withdraw",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 35 - 0
src/utils/EVMHelper/contracts/TobotoOGFriend.ts

@@ -0,0 +1,35 @@
+import TobotoOGFriendABI from './TobotoOGFriendABI.json'
+import {
+  ContractTransactionReceipt,
+  ContractTransactionResponse,
+  ethers,
+} from 'ethers'
+import WrappedContract from './base'
+// import { tokenHumanAmount } from '../../../utils'
+// import dayjs from 'dayjs'
+
+export default class TobotoOGFriendContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(TobotoOGFriendABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async hasClaimed(address: string): Promise<boolean> {
+    const contract = this.getInstance()
+    const bn = await contract.hasClaimed(address)
+    return bn
+  }
+
+  async claim(proof: string[]): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.claim(proof)
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+}

+ 747 - 0
src/utils/EVMHelper/contracts/TobotoOGFriendABI.json

@@ -0,0 +1,747 @@
+[
+	{
+		"inputs": [
+			{
+				"internalType": "string",
+				"name": "name_",
+				"type": "string"
+			},
+			{
+				"internalType": "string",
+				"name": "symbol_",
+				"type": "string"
+			},
+			{
+				"internalType": "uint256",
+				"name": "max_number_",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "nonpayable",
+		"type": "constructor"
+	},
+	{
+		"inputs": [],
+		"name": "ERC721EnumerableForbiddenBatchMint",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "sender",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			},
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "ERC721IncorrectOwner",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "ERC721InsufficientApproval",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "approver",
+				"type": "address"
+			}
+		],
+		"name": "ERC721InvalidApprover",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			}
+		],
+		"name": "ERC721InvalidOperator",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "ERC721InvalidOwner",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "receiver",
+				"type": "address"
+			}
+		],
+		"name": "ERC721InvalidReceiver",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "sender",
+				"type": "address"
+			}
+		],
+		"name": "ERC721InvalidSender",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "ERC721NonexistentToken",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "ERC721OutOfBoundsIndex",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "OwnableInvalidOwner",
+		"type": "error"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "OwnableUnauthorizedAccount",
+		"type": "error"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "approved",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "Approval",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			},
+			{
+				"indexed": false,
+				"internalType": "bool",
+				"name": "approved",
+				"type": "bool"
+			}
+		],
+		"name": "ApprovalForAll",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": false,
+				"internalType": "address",
+				"name": "sender",
+				"type": "address"
+			},
+			{
+				"indexed": false,
+				"internalType": "address",
+				"name": "user",
+				"type": "address"
+			},
+			{
+				"indexed": false,
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "Mint",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "previousOwner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "newOwner",
+				"type": "address"
+			}
+		],
+		"name": "OwnershipTransferred",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "Transfer",
+		"type": "event"
+	},
+	{
+		"inputs": [],
+		"name": "MAX_NUMBER",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "approve",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "balanceOf",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "baseURI",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32[]",
+				"name": "merkleProof_",
+				"type": "bytes32[]"
+			}
+		],
+		"name": "claim",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "to_",
+				"type": "address"
+			},
+			{
+				"internalType": "bytes32[]",
+				"name": "merkleProof_",
+				"type": "bytes32[]"
+			}
+		],
+		"name": "claimTo",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "getApproved",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"name": "hasClaimed",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			}
+		],
+		"name": "isApprovedForAll",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "merkleRoot",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "name",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "owner",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "ownerOf",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "renounceOwnership",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "safeTransferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			},
+			{
+				"internalType": "bytes",
+				"name": "data",
+				"type": "bytes"
+			}
+		],
+		"name": "safeTransferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			},
+			{
+				"internalType": "bool",
+				"name": "approved",
+				"type": "bool"
+			}
+		],
+		"name": "setApprovalForAll",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes4",
+				"name": "interfaceId",
+				"type": "bytes4"
+			}
+		],
+		"name": "supportsInterface",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "symbol",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenByIndex",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenOfOwnerByIndex",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenURI",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "totalSupply",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "transferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "newOwner",
+				"type": "address"
+			}
+		],
+		"name": "transferOwnership",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "string",
+				"name": "baseURI_",
+				"type": "string"
+			}
+		],
+		"name": "updateBaseURI",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "merkleRoot_",
+				"type": "bytes32"
+			}
+		],
+		"name": "updateMerkleRoot",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	}
+]

+ 24 - 0
src/utils/EVMHelper/contracts/UniswapV2PairMini.ts

@@ -0,0 +1,24 @@
+import miniABI from './UniswapV2PairMiniABI.json'
+import WrappedContract from './base'
+import { ethers } from 'ethers'
+
+export default class UniswapV2PairMiniContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(miniABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async getReserves(): Promise<[string, string, string]> {
+    const contract = this.getInstance()
+    const [reserve0, reserve1, blockTimestampLast] =
+      await contract.getReserves()
+    return [
+      reserve0.toString(),
+      reserve1.toString(),
+      blockTimestampLast.toString(),
+    ]
+  }
+}

+ 27 - 0
src/utils/EVMHelper/contracts/UniswapV2PairMiniABI.json

@@ -0,0 +1,27 @@
+[
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "getReserves",
+    "outputs": [
+      {
+        "internalType": "uint112",
+        "name": "reserve0",
+        "type": "uint112"
+      },
+      {
+        "internalType": "uint112",
+        "name": "reserve1",
+        "type": "uint112"
+      },
+      {
+        "internalType": "uint32",
+        "name": "blockTimestampLast",
+        "type": "uint32"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 58 - 0
src/utils/EVMHelper/contracts/UniswapV2Route02Mini.ts

@@ -0,0 +1,58 @@
+import { CONNECT_CHAIN } from '../../../constants'
+import miniABI from './UniswapV2Route02MiniABI.json'
+import WrappedContract from './base'
+import { ethers } from 'ethers'
+
+export default class UniswapV2Route02MiniContract extends WrappedContract {
+  constructor(provider: ethers.Signer, address: string = CONNECT_CHAIN.uniswapV2Router02Address!) {
+    super(miniABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async getReserves(): Promise<[string, string, string]> {
+    const contract = this.getInstance()
+    const [reserve0, reserve1, blockTimestampLast] =
+      await contract.getReserves()
+    return [
+      reserve0.toString(),
+      reserve1.toString(),
+      blockTimestampLast.toString(),
+    ]
+  }
+
+  async signSwapExactTokensForTokens(amountIn: any, amountOutMin: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapExactTokensForTokens.populateTransaction(amountIn, amountOutMin, path, to, deadline)
+    return tx.data
+  }
+  async signSwapTokensForExactTokens(amountOut: any, amountInMax: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapTokensForExactTokens.populateTransaction(amountOut, amountInMax, path, to, deadline)
+    return tx.data
+  }
+  async signSwapExactETHForTokens(amountOutMin: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapExactETHForTokens.populateTransaction(amountOutMin, path, to, deadline)
+    return tx.data
+  }
+  async signSwapTokensForExactETH(amountOut: any, amountInMax: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapTokensForExactETH.populateTransaction(amountOut, amountInMax, path, to, deadline)
+    return tx.data
+  }
+  async signSwapExactTokensForETH(amountIn: any, amountOutMin: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapExactTokensForETH.populateTransaction(amountIn, amountOutMin, path, to, deadline)
+    return tx.data
+  }
+  async signSwapETHForExactTokens(amountOut: any, path: any, to: any, deadline: any): Promise<string> {
+    const contract = this.getInstance()
+    const tx = await contract.signSwapETHForExactTokens.populateTransaction(amountOut, path, to, deadline)
+    return tx.data
+  }
+}
+
+

+ 320 - 0
src/utils/EVMHelper/contracts/UniswapV2Route02MiniABI.json

@@ -0,0 +1,320 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountInMax",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapTokensForExactTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactETHForTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountInMax",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapTokensForExactETH",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForETH",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapETHForExactTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactETHForTokensSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForETHSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 67 - 0
src/utils/EVMHelper/contracts/UniswapV3PoolMini.ts

@@ -0,0 +1,67 @@
+import miniABI from './UniswapV3PoolMiniABI.json'
+import WrappedContract from './base'
+import { ethers } from 'ethers'
+
+export interface UniswapV3Slot0 {
+  sqrtPriceX96: string
+  tick: number
+  observationIndex: string
+  observationCardinality: string
+  observationCardinalityNext: string
+  feeProtocol: string
+  unlocked: boolean
+}
+
+export default class UniswapV3PoolMiniContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(miniABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async token0(): Promise<string> {
+    const contract = this.getInstance()
+    const token0 = await contract.token0()
+    return token0
+  }
+
+  async token1(): Promise<string> {
+    const contract = this.getInstance()
+    const token1 = await contract.token1()
+    return token1
+  }
+
+  async fee(): Promise<number> {
+    const contract = this.getInstance()
+    const fee: bigint = await contract.fee()
+    return parseInt(fee.toString())
+  }
+
+  async tickSpacing(): Promise<string> {
+    const contract = this.getInstance()
+    const tickSpacing: bigint = await contract.tickSpacing()
+    return tickSpacing.toString()
+  }
+
+  async liquidity(): Promise<string> {
+    const contract = this.getInstance()
+    const liquidity: bigint = await contract.liquidity()
+    return liquidity.toString()
+  }
+
+  async slot0(): Promise<UniswapV3Slot0> {
+    const contract = this.getInstance()
+    const slot0 = await contract.slot0()
+    return {
+      sqrtPriceX96: slot0.sqrtPriceX96.toString(),
+      tick: parseInt(slot0.tick),
+      observationIndex: slot0.observationIndex.toString(),
+      observationCardinality: slot0.observationCardinality.toString(),
+      observationCardinalityNext: slot0.observationCardinalityNext.toString(),
+      feeProtocol: slot0.feeProtocol.toString(),
+      unlocked: slot0.unlocked,
+    }
+  }
+}

+ 983 - 0
src/utils/EVMHelper/contracts/UniswapV3PoolMiniABI copy.json

@@ -0,0 +1,983 @@
+[
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount",
+        "type": "uint128"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Burn",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount0",
+        "type": "uint128"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount1",
+        "type": "uint128"
+      }
+    ],
+    "name": "Collect",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount0",
+        "type": "uint128"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount1",
+        "type": "uint128"
+      }
+    ],
+    "name": "CollectProtocol",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "paid0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "paid1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Flash",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint16",
+        "name": "observationCardinalityNextOld",
+        "type": "uint16"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint16",
+        "name": "observationCardinalityNextNew",
+        "type": "uint16"
+      }
+    ],
+    "name": "IncreaseObservationCardinalityNext",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint160",
+        "name": "sqrtPriceX96",
+        "type": "uint160"
+      },
+      {
+        "indexed": false,
+        "internalType": "int24",
+        "name": "tick",
+        "type": "int24"
+      }
+    ],
+    "name": "Initialize",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "indexed": true,
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "amount",
+        "type": "uint128"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Mint",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint8",
+        "name": "feeProtocol0Old",
+        "type": "uint8"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint8",
+        "name": "feeProtocol1Old",
+        "type": "uint8"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint8",
+        "name": "feeProtocol0New",
+        "type": "uint8"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint8",
+        "name": "feeProtocol1New",
+        "type": "uint8"
+      }
+    ],
+    "name": "SetFeeProtocol",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "int256",
+        "name": "amount0",
+        "type": "int256"
+      },
+      {
+        "indexed": false,
+        "internalType": "int256",
+        "name": "amount1",
+        "type": "int256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint160",
+        "name": "sqrtPriceX96",
+        "type": "uint160"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint128",
+        "name": "liquidity",
+        "type": "uint128"
+      },
+      {
+        "indexed": false,
+        "internalType": "int24",
+        "name": "tick",
+        "type": "int24"
+      }
+    ],
+    "name": "Swap",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount",
+        "type": "uint128"
+      }
+    ],
+    "name": "burn",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount0Requested",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount1Requested",
+        "type": "uint128"
+      }
+    ],
+    "name": "collect",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "amount0",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount1",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount0Requested",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount1Requested",
+        "type": "uint128"
+      }
+    ],
+    "name": "collectProtocol",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "amount0",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount1",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "factory",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fee",
+    "outputs": [
+      {
+        "internalType": "uint24",
+        "name": "",
+        "type": "uint24"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "feeGrowthGlobal0X128",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "feeGrowthGlobal1X128",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bytes",
+        "name": "data",
+        "type": "bytes"
+      }
+    ],
+    "name": "flash",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint16",
+        "name": "observationCardinalityNext",
+        "type": "uint16"
+      }
+    ],
+    "name": "increaseObservationCardinalityNext",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceX96",
+        "type": "uint160"
+      }
+    ],
+    "name": "initialize",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "liquidity",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "maxLiquidityPerTick",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      },
+      {
+        "internalType": "uint128",
+        "name": "amount",
+        "type": "uint128"
+      },
+      {
+        "internalType": "bytes",
+        "name": "data",
+        "type": "bytes"
+      }
+    ],
+    "name": "mint",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "index",
+        "type": "uint256"
+      }
+    ],
+    "name": "observations",
+    "outputs": [
+      {
+        "internalType": "uint32",
+        "name": "blockTimestamp",
+        "type": "uint32"
+      },
+      {
+        "internalType": "int56",
+        "name": "tickCumulative",
+        "type": "int56"
+      },
+      {
+        "internalType": "uint160",
+        "name": "secondsPerLiquidityCumulativeX128",
+        "type": "uint160"
+      },
+      {
+        "internalType": "bool",
+        "name": "initialized",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint32[]",
+        "name": "secondsAgos",
+        "type": "uint32[]"
+      }
+    ],
+    "name": "observe",
+    "outputs": [
+      {
+        "internalType": "int56[]",
+        "name": "tickCumulatives",
+        "type": "int56[]"
+      },
+      {
+        "internalType": "uint160[]",
+        "name": "secondsPerLiquidityCumulativeX128s",
+        "type": "uint160[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "key",
+        "type": "bytes32"
+      }
+    ],
+    "name": "positions",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "_liquidity",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint256",
+        "name": "feeGrowthInside0LastX128",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "feeGrowthInside1LastX128",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint128",
+        "name": "tokensOwed0",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "tokensOwed1",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "protocolFees",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "token0",
+        "type": "uint128"
+      },
+      {
+        "internalType": "uint128",
+        "name": "token1",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint8",
+        "name": "feeProtocol0",
+        "type": "uint8"
+      },
+      {
+        "internalType": "uint8",
+        "name": "feeProtocol1",
+        "type": "uint8"
+      }
+    ],
+    "name": "setFeeProtocol",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "slot0",
+    "outputs": [
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceX96",
+        "type": "uint160"
+      },
+      {
+        "internalType": "int24",
+        "name": "tick",
+        "type": "int24"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationIndex",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationCardinality",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationCardinalityNext",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint8",
+        "name": "feeProtocol",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bool",
+        "name": "unlocked",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int24",
+        "name": "tickLower",
+        "type": "int24"
+      },
+      {
+        "internalType": "int24",
+        "name": "tickUpper",
+        "type": "int24"
+      }
+    ],
+    "name": "snapshotCumulativesInside",
+    "outputs": [
+      {
+        "internalType": "int56",
+        "name": "tickCumulativeInside",
+        "type": "int56"
+      },
+      {
+        "internalType": "uint160",
+        "name": "secondsPerLiquidityInsideX128",
+        "type": "uint160"
+      },
+      {
+        "internalType": "uint32",
+        "name": "secondsInside",
+        "type": "uint32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "zeroForOne",
+        "type": "bool"
+      },
+      {
+        "internalType": "int256",
+        "name": "amountSpecified",
+        "type": "int256"
+      },
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceLimitX96",
+        "type": "uint160"
+      },
+      {
+        "internalType": "bytes",
+        "name": "data",
+        "type": "bytes"
+      }
+    ],
+    "name": "swap",
+    "outputs": [
+      {
+        "internalType": "int256",
+        "name": "amount0",
+        "type": "int256"
+      },
+      {
+        "internalType": "int256",
+        "name": "amount1",
+        "type": "int256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int16",
+        "name": "wordPosition",
+        "type": "int16"
+      }
+    ],
+    "name": "tickBitmap",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tickSpacing",
+    "outputs": [
+      {
+        "internalType": "int24",
+        "name": "",
+        "type": "int24"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int24",
+        "name": "tick",
+        "type": "int24"
+      }
+    ],
+    "name": "ticks",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "liquidityGross",
+        "type": "uint128"
+      },
+      {
+        "internalType": "int128",
+        "name": "liquidityNet",
+        "type": "int128"
+      },
+      {
+        "internalType": "uint256",
+        "name": "feeGrowthOutside0X128",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "feeGrowthOutside1X128",
+        "type": "uint256"
+      },
+      {
+        "internalType": "int56",
+        "name": "tickCumulativeOutside",
+        "type": "int56"
+      },
+      {
+        "internalType": "uint160",
+        "name": "secondsPerLiquidityOutsideX128",
+        "type": "uint160"
+      },
+      {
+        "internalType": "uint32",
+        "name": "secondsOutside",
+        "type": "uint32"
+      },
+      {
+        "internalType": "bool",
+        "name": "initialized",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token0",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token1",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 110 - 0
src/utils/EVMHelper/contracts/UniswapV3PoolMiniABI.json

@@ -0,0 +1,110 @@
+[
+  {
+    "inputs": [],
+    "name": "slot0",
+    "outputs": [
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceX96",
+        "type": "uint160"
+      },
+      {
+        "internalType": "int24",
+        "name": "tick",
+        "type": "int24"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationIndex",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationCardinality",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint16",
+        "name": "observationCardinalityNext",
+        "type": "uint16"
+      },
+      {
+        "internalType": "uint8",
+        "name": "feeProtocol",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bool",
+        "name": "unlocked",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "liquidity",
+    "outputs": [
+      {
+        "internalType": "uint128",
+        "name": "",
+        "type": "uint128"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tickSpacing",
+    "outputs": [
+      {
+        "internalType": "int24",
+        "name": "",
+        "type": "int24"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fee",
+    "outputs": [
+      {
+        "internalType": "uint24",
+        "name": "",
+        "type": "uint24"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token0",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token1",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 49 - 0
src/utils/EVMHelper/contracts/UniswapV3Quoter.ts

@@ -0,0 +1,49 @@
+import ABI from './UniswapV3QuoterABI.json'
+import WrappedContract from './base'
+import { ethers } from 'ethers'
+
+export default class UniswapV3QuoterMiniContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(ABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async callQuoteExactInputSingle(
+    tokenIn: string,
+    tokenOut: string,
+    fee: number,
+    amountIn: string,
+    sqrtPriceLimitX96?: string
+  ): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.quoteExactInputSingle.staticCall(
+      tokenIn,
+      tokenOut,
+      fee,
+      amountIn,
+      sqrtPriceLimitX96 ?? 0
+    )
+    return bn.toString()
+  }
+
+  async callQuoteExactOutputSingle(
+    tokenIn: string,
+    tokenOut: string,
+    fee: number,
+    amountOut: string,
+    sqrtPriceLimitX96?: string
+  ): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.quoteExactOutputSingle.staticCall(
+      tokenIn,
+      tokenOut,
+      fee,
+      amountOut,
+      sqrtPriceLimitX96 ?? 0
+    )
+    return bn.toString()
+  }
+}

+ 193 - 0
src/utils/EVMHelper/contracts/UniswapV3QuoterABI.json

@@ -0,0 +1,193 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_factory",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_WETH9",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [],
+    "name": "WETH9",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "factory",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes",
+        "name": "path",
+        "type": "bytes"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      }
+    ],
+    "name": "quoteExactInput",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenOut",
+        "type": "address"
+      },
+      {
+        "internalType": "uint24",
+        "name": "fee",
+        "type": "uint24"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceLimitX96",
+        "type": "uint160"
+      }
+    ],
+    "name": "quoteExactInputSingle",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes",
+        "name": "path",
+        "type": "bytes"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "name": "quoteExactOutput",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenOut",
+        "type": "address"
+      },
+      {
+        "internalType": "uint24",
+        "name": "fee",
+        "type": "uint24"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint160",
+        "name": "sqrtPriceLimitX96",
+        "type": "uint160"
+      }
+    ],
+    "name": "quoteExactOutputSingle",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int256",
+        "name": "amount0Delta",
+        "type": "int256"
+      },
+      {
+        "internalType": "int256",
+        "name": "amount1Delta",
+        "type": "int256"
+      },
+      {
+        "internalType": "bytes",
+        "name": "path",
+        "type": "bytes"
+      }
+    ],
+    "name": "uniswapV3SwapCallback",
+    "outputs": [],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 43 - 0
src/utils/EVMHelper/contracts/aave.ts

@@ -0,0 +1,43 @@
+import { BigNumber, ethers } from 'ethers'
+import WrappedContract from './base'
+import AaveABI from './AaveABI.json'
+import { CONTRACT_AAVEMOCK } from '../../../constants'
+
+interface AaveAccountData {
+  totalCollateralETH: BigNumber
+  totalDebtETH: BigNumber
+  availableBorrowsETH: BigNumber
+  currentLiquidationThreshold: BigNumber
+  ltv: BigNumber
+  healthFactor: BigNumber
+}
+
+export default class AaveContract extends WrappedContract {
+  constructor(provider: ethers.Signer) {
+    super(AaveABI, CONTRACT_AAVEMOCK, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async getUserAccountData(efPoolAddress: string): Promise<AaveAccountData> {
+    const contract = this.getInstance()
+    const [
+      totalCollateralETH,
+      totalDebtETH,
+      availableBorrowsETH,
+      currentLiquidationThreshold,
+      ltv,
+      healthFactor,
+    ] = (await contract.getUserAccountData(efPoolAddress)) as BigNumber[]
+    return {
+      totalCollateralETH,
+      totalDebtETH,
+      availableBorrowsETH,
+      currentLiquidationThreshold,
+      ltv,
+      healthFactor,
+    }
+  }
+}

+ 17 - 0
src/utils/EVMHelper/contracts/base.ts

@@ -0,0 +1,17 @@
+import { ethers } from 'ethers'
+
+export default class WrappedContract {
+  protected ABI: any[]
+  protected address: string
+  protected provider: ethers.Signer
+
+  constructor(abi: any[], address: string, provider: ethers.Signer) {
+    this.ABI = abi
+    this.address = address
+    this.provider = provider
+  }
+
+  async getCallerAddress(): Promise<string> {
+    return await this.provider.getAddress()
+  }
+}

+ 190 - 0
src/utils/EVMHelper/contracts/degenSwap.ts

@@ -0,0 +1,190 @@
+import erc20ABI from './erc20ABI.json'
+import {
+  ethers,
+  ContractTransactionResponse,
+  ContractTransactionReceipt,
+} from 'ethers'
+import WrappedContract from './base'
+import { MAX_ETH_NUM } from '../../../constants'
+import ABI from './degenSwapABI.json'
+import { ChainId, Token, WETH9, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
+import { SWAP_ADDRESS, CONNECT_CHAIN, TOKEN_ADDRESS, WETH_ADDRESS } from '../../../constants'
+import UniswapV2PairMiniABI from './UniswapV2PairMiniABI.json'
+import UniswapV2Route02MiniABI from './UniswapV2Route02MiniABI.json'
+import { Pair, Route, Trade } from '@uniswap/v2-sdk'
+import { BigNumber } from '@ethersproject/bignumber'
+import Decimal from 'decimal.js-light'
+import DegenSwapTokenContract from './degenSwapToken'
+import { timeNumber } from '../../../utils/time'
+
+const DegenToken = new Token(CONNECT_CHAIN.chainId, TOKEN_ADDRESS, 18, 'DegenToken', 'DEGEN', false, BigNumber.from(30), BigNumber.from(30))
+const Weth = new Token(CONNECT_CHAIN.chainId, WETH_ADDRESS, 18, 'WETH', 'WETH', false, BigNumber.from(30), BigNumber.from(30))
+
+async function createPair(provider: ethers.Provider): Promise<Pair> {
+  // TODO: 不知道为啥出错
+  // const pairAddress = Pair.getAddress(Weth, DegenToken)
+  const pairAddress = (window as any).CONSTS.PAIR_ADDRESS
+
+  // Setup provider, import necessary ABI ...
+  const pairContract = new ethers.Contract(pairAddress, UniswapV2PairMiniABI, provider)
+  const reserves = await pairContract["getReserves"]()
+  const [reserve0, reserve1] = reserves
+
+  const tokens = [DegenToken, Weth]
+  const [token0, token1] = tokens[0].sortsBefore(tokens[1]) ? tokens : [tokens[1], tokens[0]]
+
+  const pair = new Pair(CurrencyAmount.fromRawAmount(token0, reserve0.toString()), CurrencyAmount.fromRawAmount(token1, reserve1.toString()))
+  return pair
+}
+
+export interface PriceInfluence {
+  input: string
+  midPrice: string
+  executionPrice: string
+  influence: number
+}
+
+export interface DegenBuyResult {
+  tx: ContractTransactionReceipt
+  critical: boolean
+}
+
+export default class DegenSwapContract extends WrappedContract {
+  constructor(provider: ethers.Signer) {
+    super(ABI, SWAP_ADDRESS, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  getUniInstance(): ethers.Contract {
+    return new ethers.Contract((window as any).CONSTS.V2_ROUTER_ADDRESS, UniswapV2Route02MiniABI, this.provider)
+  }
+
+  async getPoolETHBalance(): Promise<string> {
+    const balance = await this.provider.provider!.getBalance(this.address)
+    return balance.toString()
+  }
+
+  async buy(ethAmount: string): Promise<DegenBuyResult> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.buy(
+      TOKEN_ADDRESS,
+      {
+        value: ethAmount,
+      }
+    )
+
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+
+    const logs = txwait.logs.filter((log) => log.address.toLowerCase() === this.address.toLowerCase())
+
+    const event = logs.map((log) => contract.interface.parseLog(log)).filter((event) => event?.name === 'Critical')
+
+    let critical = false
+    if (event[0]) {
+      critical = event[0].args[0]
+    }
+    return {
+      tx: txwait,
+      critical,
+    }
+  }
+
+  async sell(tokenAmount: string): Promise<ContractTransactionReceipt> {
+    const contract = this.getUniInstance()
+    const owner = await this.provider.getAddress()
+    const ts = Math.floor((new Date().getTime() + timeNumber.day * 30) / 1000)
+
+    const ttt = await contract.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(tokenAmount, '1', [DegenToken.address, Weth.address], owner, ts)
+
+    const price = await this.getExecutionSellPrice(tokenAmount)
+    const slippage = new Decimal(tokenAmount).mul(price).mul(0.7).toFixed(0)
+
+    const tx: ContractTransactionResponse = await contract.swapExactTokensForETHSupportingFeeOnTransferTokens(tokenAmount, slippage, [DegenToken.address, Weth.address], owner, ts)
+    const txwait = await tx.wait()
+    
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async sellAllowance(): Promise<string> {
+    const uniContract = this.getUniInstance()
+    const tokenContract = new DegenSwapTokenContract(this.provider)
+    const owner = await this.provider.getAddress()
+    const spender = await uniContract.getAddress()
+    const bn = await tokenContract.allowance(owner, spender)
+    return bn.toString()
+  }
+
+  async sellApprove(): Promise<ContractTransactionReceipt> {
+    const uniContract = this.getUniInstance()
+    const tokenContract = new DegenSwapTokenContract(this.provider)
+    const spender = await uniContract.getAddress()
+    return await tokenContract.approve(spender, MAX_ETH_NUM)
+  }
+
+  async getBuyPrice(): Promise<string> {
+    const pair = await createPair(this.provider.provider!)
+
+    const route = new Route([pair], Weth, DegenToken)
+    return route.midPrice.toSignificant(10)
+  }
+
+  async getSellPrice(): Promise<string> {
+    const pair = await createPair(this.provider.provider!)
+
+    const route = new Route([pair], DegenToken, Weth)
+    return route.midPrice.toSignificant(10)
+  }
+
+  async getExecutionBuyPrice(ethAmount: string): Promise<string> {
+    const pair = await createPair(this.provider.provider!)
+
+    const route = new Route([pair], Weth, DegenToken)
+
+    const trade = new Trade(route, CurrencyAmount.fromRawAmount(Weth, ethAmount), TradeType.EXACT_INPUT)
+
+    return trade.executionPrice.toSignificant(10)
+  }
+
+  async getExecutionSellPrice(tokenAmount: string): Promise<string> {
+    const pair = await createPair(this.provider.provider!)
+
+    const route = new Route([pair], DegenToken, Weth)
+
+    const trade = new Trade(route, CurrencyAmount.fromRawAmount(DegenToken, tokenAmount), TradeType.EXACT_INPUT)
+
+    return trade.executionPrice.toSignificant(10)
+  }
+
+  async getExecutionBuyPriceInfluence(ethAmount: string): Promise<PriceInfluence> {
+    const midPrice = await this.getBuyPrice()
+    const executionPrice = await this.getExecutionBuyPrice(ethAmount)
+    const influence = new Decimal(midPrice).div(new Decimal(executionPrice)).minus(1).toNumber()
+    return {
+      input: ethAmount,
+      midPrice,
+      executionPrice,
+      influence,
+    }
+  }
+
+  async getExecutionSellPriceInfluence(ethAmount: string): Promise<PriceInfluence> {
+    const midPrice = await this.getSellPrice()
+    const executionPrice = await this.getExecutionSellPrice(ethAmount)
+    const influence = new Decimal(midPrice).div(new Decimal(executionPrice)).minus(1).toNumber()
+    return {
+      input: ethAmount,
+      midPrice,
+      executionPrice,
+      influence,
+    }
+  }
+}

+ 164 - 0
src/utils/EVMHelper/contracts/degenSwapABI.json

@@ -0,0 +1,164 @@
+[
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "_token",
+     "type": "address"
+    }
+   ],
+   "name": "buy",
+   "outputs": [
+    {
+     "internalType": "bool",
+     "name": "",
+     "type": "bool"
+    }
+   ],
+   "stateMutability": "payable",
+   "type": "function"
+  },
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "_uniswapRouter",
+     "type": "address"
+    }
+   ],
+   "stateMutability": "nonpayable",
+   "type": "constructor"
+  },
+  {
+   "anonymous": false,
+   "inputs": [
+    {
+     "indexed": false,
+     "internalType": "bool",
+     "name": "critical",
+     "type": "bool"
+    }
+   ],
+   "name": "Critical",
+   "type": "event"
+  },
+  {
+   "anonymous": false,
+   "inputs": [
+    {
+     "indexed": true,
+     "internalType": "address",
+     "name": "previousOwner",
+     "type": "address"
+    },
+    {
+     "indexed": true,
+     "internalType": "address",
+     "name": "newOwner",
+     "type": "address"
+    }
+   ],
+   "name": "OwnershipTransferred",
+   "type": "event"
+  },
+  {
+   "inputs": [],
+   "name": "renounceOwnership",
+   "outputs": [],
+   "stateMutability": "nonpayable",
+   "type": "function"
+  },
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "_token",
+     "type": "address"
+    },
+    {
+     "internalType": "address",
+     "name": "_pair",
+     "type": "address"
+    }
+   ],
+   "name": "setTokenPairs",
+   "outputs": [],
+   "stateMutability": "nonpayable",
+   "type": "function"
+  },
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "newOwner",
+     "type": "address"
+    }
+   ],
+   "name": "transferOwnership",
+   "outputs": [],
+   "stateMutability": "nonpayable",
+   "type": "function"
+  },
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "tkn",
+     "type": "address"
+    }
+   ],
+   "name": "withdrawStuckTokens",
+   "outputs": [],
+   "stateMutability": "nonpayable",
+   "type": "function"
+  },
+  {
+   "stateMutability": "payable",
+   "type": "receive"
+  },
+  {
+   "inputs": [],
+   "name": "owner",
+   "outputs": [
+    {
+     "internalType": "address",
+     "name": "",
+     "type": "address"
+    }
+   ],
+   "stateMutability": "view",
+   "type": "function"
+  },
+  {
+   "inputs": [
+    {
+     "internalType": "address",
+     "name": "",
+     "type": "address"
+    }
+   ],
+   "name": "tokenPairs",
+   "outputs": [
+    {
+     "internalType": "address",
+     "name": "",
+     "type": "address"
+    }
+   ],
+   "stateMutability": "view",
+   "type": "function"
+  },
+  {
+   "inputs": [],
+   "name": "uniswapV2Router",
+   "outputs": [
+    {
+     "internalType": "contract IUniswapV2Router02",
+     "name": "",
+     "type": "address"
+    }
+   ],
+   "stateMutability": "view",
+   "type": "function"
+  }
+ ]

+ 76 - 0
src/utils/EVMHelper/contracts/degenSwapToken.ts

@@ -0,0 +1,76 @@
+import Erc20TokenContract from './erc20'
+import { TOKEN_ADDRESS } from '../../../constants'
+import { ContractTransactionReceipt, ContractTransactionResponse, ethers } from 'ethers'
+import ABI from './degenSwapTokenABI.json'
+
+
+export type DegenSwapTokenContractStatus = 'waiting' | 'minting' | 'swaping'
+
+export default class DegenSwapTokenContract extends Erc20TokenContract {
+  constructor(provider: ethers.Signer) {
+    super(TOKEN_ADDRESS, provider)
+    this.ABI = ABI
+  }
+
+  async mint(): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const value = await this.calculateMintPrice()
+    const tx: ContractTransactionResponse = await contract.mint({
+      value,
+    })
+
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async mintCount(address: string): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.mintCount(address)
+    return parseInt(bn)
+  }
+
+  async mintLimit(): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.mintLimit()
+    return parseInt(bn)
+  }
+
+  async amountPerMint(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.amountPerMint() as bigint
+    return bn.toString()
+  }
+
+  async calculateMintPrice(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.calculateMintPrice() as bigint
+    return bn.toString()
+  }
+
+  async tradingOpen(): Promise<boolean> {
+    const contract = this.getInstance()
+    const bn = await contract.tradingOpen() as boolean
+    return bn
+  }
+
+  async presaleOpen(): Promise<boolean> {
+    const contract = this.getInstance()
+    const bn = await contract.presaleOpen() as boolean
+    return bn
+  }
+
+  async status(): Promise<DegenSwapTokenContractStatus> {
+    const presaleOpen = await this.presaleOpen()
+    const tradingOpen = await this.tradingOpen()
+    if (tradingOpen) return 'swaping'
+    if (presaleOpen) return 'minting'
+    return 'waiting'
+  }
+
+  static getSymbol(): string {
+    return (window as any).CONSTS.SYMBOL
+  }
+}

+ 686 - 0
src/utils/EVMHelper/contracts/degenSwapTokenABI.json

@@ -0,0 +1,686 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_uniswapRouter",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_degenswapRouter",
+        "type": "address"
+      },
+      {
+        "internalType": "string",
+        "name": "_name",
+        "type": "string"
+      },
+      {
+        "internalType": "string",
+        "name": "_symbol",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "UD60x18",
+        "name": "x",
+        "type": "uint256"
+      }
+    ],
+    "name": "PRBMath_UD60x18_Exp2_InputTooBig",
+    "type": "error"
+  },
+  {
+    "inputs": [],
+    "name": "ReentrancyGuardReentrantCall",
+    "type": "error"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "previousOwner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipTransferred",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "amountPerMint",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "automatedMarketMakerPairs",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "buyTax",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "calculateMintPrice",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "result",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "subtractedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "decreaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "degenSwapRouter",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "excluded",
+        "type": "bool"
+      }
+    ],
+    "name": "excludeFromFees",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "inSwap",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "addedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "increaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "initPrice",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "isExcludedFromFees",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "manualSwap",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "mint",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "mintCount",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "mintLimit",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "minted",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "openTrading",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "presaleOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "renounceOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "sellTax",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "pair",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "value",
+        "type": "bool"
+      }
+    ],
+    "name": "setAutomatedMarketMakerPair",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "startSale",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "targetCount",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tradingOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "transferOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniswapV2Pair",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniswapV2Router",
+    "outputs": [
+      {
+        "internalType": "contract IUniswapV2Router02",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tkn",
+        "type": "address"
+      }
+    ],
+    "name": "withdrawStuckTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "receive"
+  }
+]

+ 181 - 0
src/utils/EVMHelper/contracts/degenSwapTokenV2.ts

@@ -0,0 +1,181 @@
+import Erc20TokenContract from './erc20'
+import { TOKEN_ADDRESS, ZERO_ADDRESS } from '../../../constants'
+import { TransactionReceipt, ContractTransactionResponse, ethers, solidityPackedKeccak256 } from 'ethers'
+import ABI from './degenSwapTokenV2ABI.json'
+import Decimal from 'decimal.js-light'
+import { awaitConfirmations } from '../../../utils/evm'
+
+
+export type DegenSwapTokenContractStatus = 'waiting' | 'minting' | 'swaping'
+
+export type DepositTimeState = 'notStarted' | 'ongoing' | 'ended'
+
+// 邀请是1,ido是2,退款是3
+export enum ClaimCheckType {
+  referral = 1,
+  claim = 2,
+  refund = 3,
+}
+
+// export interface ClaimState {
+//   fairLaunchRoot: string | null
+//   referralRoot: string | null
+//   refundRoot: string | null
+// }
+
+// const zeroByte32 = '0x0000000000000000000000000000000000000000000000000000000000000000'
+
+let uniswapV2PairCache: string | null = null
+
+export default class DegenSwapTokenV2Contract extends Erc20TokenContract {
+  constructor(provider: ethers.Signer) {
+    super(TOKEN_ADDRESS, provider)
+    this.ABI = ABI
+  }
+
+  static getSymbol(): string {
+    return (window as any).CONSTS.SYMBOL
+  }
+
+  async ticketPrice(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.ticketPrice()
+    return bn.toString()
+  }
+
+  async uniswapV2Pair(): Promise<string> {
+    if (uniswapV2PairCache) return uniswapV2PairCache
+    const contract = this.getInstance()
+    const addr = await contract.uniswapV2Pair()
+    uniswapV2PairCache = addr
+    return addr
+  }
+
+  async targetCap(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.cap()
+    return bn.toString()
+  }
+
+  async currentCap(): Promise<string> {
+    const bn = await this.provider.provider!.getBalance(this.address)
+    return bn.toString()
+  }
+
+  async deposit(tickets: number): Promise<TransactionReceipt> {
+    const price = await this.ticketPrice()
+    const contract = this.getInstance()
+    const value = new Decimal(price).mul(tickets).toFixed(0)
+    const tx: ContractTransactionResponse = await contract.deposit(tickets, {
+      value,
+    })
+
+    const txwait = await awaitConfirmations(tx)
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async claim(amount: string, proof: string[]): Promise<TransactionReceipt> {
+    const price = await this.ticketPrice()
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.claim(amount, proof)
+
+    const txwait = await awaitConfirmations(tx)
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async claimReferral(amount: string, proof: string[]): Promise<TransactionReceipt> {
+    const price = await this.ticketPrice()
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.claimReferral(amount, proof)
+
+    const txwait = await awaitConfirmations(tx)
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async refund(amount: string, proof: string[]): Promise<TransactionReceipt> {
+    const price = await this.ticketPrice()
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.refund(amount, proof)
+
+    const txwait = await awaitConfirmations(tx)
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  // 邀请是1,ido是2,退款是3
+  async isClaimedOrigin(address: string, amount: string, type: ClaimCheckType): Promise<boolean> {
+    const contract = this.getInstance()
+    const hash = solidityPackedKeccak256(['address', 'uint256', 'uint'], [address, amount, type])
+    const result = await contract.isClaimed(hash)
+    return result
+  }
+
+  async isClaimed(address: string, amount: string): Promise<boolean> {
+    return await this.isClaimedOrigin(address, amount, ClaimCheckType.claim)
+  }
+
+  async isRefunded(address: string, amount: string): Promise<boolean> {
+    return await this.isClaimedOrigin(address, amount, ClaimCheckType.refund)
+  }
+
+  async isReferralClaimed(address: string, amount: string): Promise<boolean> {
+    return await this.isClaimedOrigin(address, amount, ClaimCheckType.referral)
+  }
+
+  async tradingOpen(): Promise<boolean> {
+    const contract = this.getInstance()
+    const result = await contract.tradingOpen()
+    return result
+  }
+
+  async claimOpen(): Promise<boolean> {
+    const contract = this.getInstance()
+    const result = await contract.claimOpen()
+    return result
+  }
+
+  async claimReferralOpen(): Promise<boolean> {
+    const contract = this.getInstance()
+    const result = await contract.claimReferralOpen()
+    return result
+  }
+
+  async addressDeposits(address: string): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.deposits(address)
+    return bn.toString()
+  }
+
+  async depositTimeState(): Promise<DepositTimeState> {
+    const contract = this.getInstance()
+    const endDateBn = await contract.depositEndDate()
+    const endDate = parseInt(endDateBn) * 1000
+    if (endDate === 0) return 'notStarted'
+    if (Date.now() < endDate) return 'ongoing'
+    return 'ended'
+  }
+
+  // async claimState(): Promise<ClaimState> {
+  //   const contract = this.getInstance()
+  //   const fairLaunchRoot = await contract.fairLaunchRoot()
+  //   const referralRoot = await contract.referralRoot()
+  //   const refundRoot = await contract.refundRoot()
+
+  //   return {
+  //     fairLaunchRoot: fairLaunchRoot === zeroByte32 ? null : fairLaunchRoot.toString(),
+  //     referralRoot: referralRoot === zeroByte32 ? null : referralRoot.toString(),
+  //     refundRoot: refundRoot === zeroByte32 ? null : refundRoot.toString(),
+  //   }
+  // }
+}

+ 1095 - 0
src/utils/EVMHelper/contracts/degenSwapTokenV2ABI.json

@@ -0,0 +1,1095 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_uniswapRouter",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_betSwapRouter",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_marketMaker",
+        "type": "address"
+      },
+      {
+        "internalType": "string",
+        "name": "_name",
+        "type": "string"
+      },
+      {
+        "internalType": "string",
+        "name": "_symbol",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "ClaimedReferralWithProof",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "ClaimedWithProof",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "depositor",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amountETHdeposited",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "totalSubscription",
+        "type": "uint256"
+      }
+    ],
+    "name": "Deposited",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "previousOwner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipTransferred",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "RefundWithProof",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "automatedMarketMakerPairs",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "betSwapRouter",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "buyTax",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "cap",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bytes32[]",
+        "name": "_merkleProof",
+        "type": "bytes32[]"
+      }
+    ],
+    "name": "claim",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "claimOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bytes32[]",
+        "name": "_merkleProof",
+        "type": "bytes32[]"
+      }
+    ],
+    "name": "claimReferral",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "claimReferralOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "subtractedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "decreaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "tickets",
+        "type": "uint256"
+      }
+    ],
+    "name": "deposit",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "depositEndDate",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "deposits",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "ethRefunded",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "excluded",
+        "type": "bool"
+      }
+    ],
+    "name": "excludeFromFees",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fairLaunch",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fairLaunchAllocation",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fairLaunchRoot",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getParticipants",
+    "outputs": [
+      {
+        "internalType": "address[]",
+        "name": "addrs",
+        "type": "address[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "start",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "length",
+        "type": "uint256"
+      }
+    ],
+    "name": "getParticipants",
+    "outputs": [
+      {
+        "internalType": "address[]",
+        "name": "addrs",
+        "type": "address[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tkn",
+        "type": "address"
+      }
+    ],
+    "name": "getStuckTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "idoTokensClaimed",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "inSwap",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "addedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "increaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "_hash",
+        "type": "bytes32"
+      }
+    ],
+    "name": "isClaimed",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "isExcludedFromFees",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "liquidityAllocation",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "manualSwap",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "marketMaker",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "openClaim",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "openClaimReferral",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "openTrading",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "overEth",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "name": "participants",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "referralAllocation",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "referralRoot",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "referralTokensClaimed",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bytes32[]",
+        "name": "_merkleProof",
+        "type": "bytes32[]"
+      }
+    ],
+    "name": "refund",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "refundRoot",
+    "outputs": [
+      {
+        "internalType": "bytes32",
+        "name": "",
+        "type": "bytes32"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "renounceOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "sellTax",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "pair",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "value",
+        "type": "bool"
+      }
+    ],
+    "name": "setAutomatedMarketMakerPair",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "_root",
+        "type": "bytes32"
+      }
+    ],
+    "name": "setFairLaunchRoot",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "_root",
+        "type": "bytes32"
+      }
+    ],
+    "name": "setReferralRoot",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bytes32",
+        "name": "_root",
+        "type": "bytes32"
+      }
+    ],
+    "name": "setRefundRoot",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "stopSale",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "subscription",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "ticketPrice",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "timeout",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tradingOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "transferOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniswapV2Pair",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniswapV2Router",
+    "outputs": [
+      {
+        "internalType": "contract IUniswapV2Router02",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "receive"
+  }
+]

+ 200 - 0
src/utils/EVMHelper/contracts/degenSwapV2.ts

@@ -0,0 +1,200 @@
+import erc20ABI from './erc20ABI.json'
+import {
+  ethers,
+  ContractTransactionResponse,
+  TransactionReceipt,
+} from 'ethers'
+import WrappedContract from './base'
+import { MAX_ETH_NUM } from '../../../constants'
+import ABI from './degenSwapV2ABI.json'
+import { ChainId, Token, WETH9, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
+import { SWAP_ADDRESS, CONNECT_CHAIN, TOKEN_ADDRESS, WETH_ADDRESS } from '../../../constants'
+import UniswapV2PairMiniABI from './UniswapV2PairMiniABI.json'
+import UniswapV2Route02MiniABI from './UniswapV2Route02MiniABI.json'
+import { Pair, Route, Trade } from '@uniswap/v2-sdk'
+import { BigNumber } from '@ethersproject/bignumber'
+import Decimal from 'decimal.js-light'
+import DegenSwapTokenV2Contract from './degenSwapTokenV2'
+import { timeNumber } from '../../../utils/time'
+import { awaitConfirmations } from '../../../utils/evm'
+
+const DegenToken = new Token(CONNECT_CHAIN.chainId, TOKEN_ADDRESS, 18, 'DegenToken', 'DEGEN', false, BigNumber.from(30), BigNumber.from(30))
+const Weth = new Token(CONNECT_CHAIN.chainId, WETH_ADDRESS, 18, 'WETH', 'WETH', false, BigNumber.from(30), BigNumber.from(30))
+
+async function createPair(pairAddress: string, provider: ethers.Provider): Promise<Pair> {
+  // TODO: 不知道为啥出错
+  // const pairAddress = Pair.getAddress(Weth, DegenToken)
+  // const pairAddress = (window as any).CONSTS.PAIR_ADDRESS
+
+  // Setup provider, import necessary ABI ...
+  const pairContract = new ethers.Contract(pairAddress, UniswapV2PairMiniABI, provider)
+  const reserves = await pairContract["getReserves"]()
+  const [reserve0, reserve1] = reserves
+
+  const tokens = [DegenToken, Weth]
+  const [token0, token1] = tokens[0].sortsBefore(tokens[1]) ? tokens : [tokens[1], tokens[0]]
+
+  const pair = new Pair(CurrencyAmount.fromRawAmount(token0, reserve0.toString()), CurrencyAmount.fromRawAmount(token1, reserve1.toString()))
+  return pair
+}
+
+export interface PriceInfluence {
+  input: string
+  midPrice: string
+  executionPrice: string
+  influence: number
+}
+
+export interface DegenBuyResult {
+  tx: TransactionReceipt
+  critical: boolean
+}
+
+export default class DegenSwapV2Contract extends WrappedContract {
+  constructor(provider: ethers.Signer) {
+    super(ABI, SWAP_ADDRESS, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  getUniInstance(): ethers.Contract {
+    return new ethers.Contract((window as any).CONSTS.V2_ROUTER_ADDRESS, UniswapV2Route02MiniABI, this.provider)
+  }
+
+  async getPoolETHBalance(): Promise<string> {
+    const balance = await this.provider.provider!.getBalance(this.address)
+    return balance.toString()
+  }
+
+  async buy(ethAmount: string, criticalRatio: number): Promise<DegenBuyResult> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.buy(
+      TOKEN_ADDRESS,
+      criticalRatio,
+      {
+        value: ethAmount,
+        gasLimit: 500000
+      }
+    )
+
+    const txwait = await awaitConfirmations(tx)
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+
+    const logs = txwait.logs.filter((log) => log.address.toLowerCase() === this.address.toLowerCase())
+    // console.log(logs)
+    // console.log(logs.map((log) => contract.interface.parseLog(log)))
+
+    const event = logs.map((log) => contract.interface.parseLog(log)).filter((event) => event?.name === 'Critical')
+    // console.log(event)
+
+    let critical = false
+    if (event[0]) {
+      critical = event[0].args[0]
+    }
+    return {
+      tx: txwait,
+      critical,
+    }
+  }
+
+  async sell(tokenAmount: string): Promise<TransactionReceipt> {
+    const contract = this.getUniInstance()
+    const owner = await this.provider.getAddress()
+    const ts = Math.floor((new Date().getTime() + timeNumber.day * 30) / 1000)
+
+    const ttt = await contract.swapExactTokensForETHSupportingFeeOnTransferTokens.populateTransaction(tokenAmount, '1', [DegenToken.address, Weth.address], owner, ts)
+
+    const price = await this.getExecutionSellPrice(tokenAmount)
+    const slippage = new Decimal(tokenAmount).mul(price).mul(0.7).toFixed(0)
+
+    const tx: ContractTransactionResponse = await contract.swapExactTokensForETHSupportingFeeOnTransferTokens(tokenAmount, slippage, [DegenToken.address, Weth.address], owner, ts)
+    const txwait = await awaitConfirmations(tx)
+    
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async sellAllowance(): Promise<string> {
+    const uniContract = this.getUniInstance()
+    const tokenContract = new DegenSwapTokenV2Contract(this.provider)
+    const owner = await this.provider.getAddress()
+    const spender = await uniContract.getAddress()
+    const bn = await tokenContract.allowance(owner, spender)
+    return bn.toString()
+  }
+
+  async sellApprove(): Promise<TransactionReceipt> {
+    const uniContract = this.getUniInstance()
+    const tokenContract = new DegenSwapTokenV2Contract(this.provider)
+    const spender = await uniContract.getAddress()
+    return await tokenContract.approve(spender, MAX_ETH_NUM)
+  }
+
+  async getBuyPrice(): Promise<string> {
+    const pairAddress = await new DegenSwapTokenV2Contract(this.provider).uniswapV2Pair()
+    const pair = await createPair(pairAddress, this.provider.provider!)
+
+    const route = new Route([pair], Weth, DegenToken)
+    return route.midPrice.toSignificant(10)
+  }
+
+  async getSellPrice(): Promise<string> {
+    const pairAddress = await new DegenSwapTokenV2Contract(this.provider).uniswapV2Pair()
+    const pair = await createPair(pairAddress, this.provider.provider!)
+
+    const route = new Route([pair], DegenToken, Weth)
+    return route.midPrice.toSignificant(10)
+  }
+
+  async getExecutionBuyPrice(ethAmount: string): Promise<string> {
+    const pairAddress = await new DegenSwapTokenV2Contract(this.provider).uniswapV2Pair()
+    const pair = await createPair(pairAddress, this.provider.provider!)
+
+    const route = new Route([pair], Weth, DegenToken)
+
+    const trade = new Trade(route, CurrencyAmount.fromRawAmount(Weth, ethAmount), TradeType.EXACT_INPUT)
+
+    return trade.executionPrice.toSignificant(10)
+  }
+
+  async getExecutionSellPrice(tokenAmount: string): Promise<string> {
+    const pairAddress = await new DegenSwapTokenV2Contract(this.provider).uniswapV2Pair()
+    const pair = await createPair(pairAddress, this.provider.provider!)
+
+    const route = new Route([pair], DegenToken, Weth)
+
+    const trade = new Trade(route, CurrencyAmount.fromRawAmount(DegenToken, tokenAmount), TradeType.EXACT_INPUT)
+
+    return trade.executionPrice.toSignificant(10)
+  }
+
+  async getExecutionBuyPriceInfluence(ethAmount: string): Promise<PriceInfluence> {
+    const midPrice = await this.getBuyPrice()
+    const executionPrice = await this.getExecutionBuyPrice(ethAmount)
+    const influence = new Decimal(midPrice).div(new Decimal(executionPrice)).minus(1).toNumber()
+    return {
+      input: ethAmount,
+      midPrice,
+      executionPrice,
+      influence,
+    }
+  }
+
+  async getExecutionSellPriceInfluence(ethAmount: string): Promise<PriceInfluence> {
+    const midPrice = await this.getSellPrice()
+    const executionPrice = await this.getExecutionSellPrice(ethAmount)
+    const influence = new Decimal(midPrice).div(new Decimal(executionPrice)).minus(1).toNumber()
+    return {
+      input: ethAmount,
+      midPrice,
+      executionPrice,
+      influence,
+    }
+  }
+}

+ 187 - 0
src/utils/EVMHelper/contracts/degenSwapV2ABI.json

@@ -0,0 +1,187 @@
+[
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "_token",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "_criticalRatio",
+				"type": "uint256"
+			}
+		],
+		"name": "buy",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "payable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "_uniswapV2Router",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "_uniswapV2Factory",
+				"type": "address"
+			}
+		],
+		"stateMutability": "nonpayable",
+		"type": "constructor"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": false,
+				"internalType": "bool",
+				"name": "critical",
+				"type": "bool"
+			}
+		],
+		"name": "Critical",
+		"type": "event"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "tkn",
+				"type": "address"
+			}
+		],
+		"name": "getStuckTokens",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "previousOwner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "newOwner",
+				"type": "address"
+			}
+		],
+		"name": "OwnershipTransferred",
+		"type": "event"
+	},
+	{
+		"inputs": [],
+		"name": "renounceOwnership",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "_token",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "_pair",
+				"type": "address"
+			}
+		],
+		"name": "setTokenPairs",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "newOwner",
+				"type": "address"
+			}
+		],
+		"name": "transferOwnership",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"stateMutability": "payable",
+		"type": "receive"
+	},
+	{
+		"inputs": [],
+		"name": "owner",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"name": "tokenPairs",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "uniswapV2Factory",
+		"outputs": [
+			{
+				"internalType": "contract IUniswapV2Factory",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "uniswapV2Router",
+		"outputs": [
+			{
+				"internalType": "contract IUniswapV2Router02",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	}
+]

+ 88 - 0
src/utils/EVMHelper/contracts/erc20.ts

@@ -0,0 +1,88 @@
+import erc20ABI from './erc20ABI.json'
+import {
+  ethers,
+  ContractTransactionResponse,
+  ContractTransactionReceipt,
+} from 'ethers'
+import WrappedContract from './base'
+import { MAX_ETH_NUM } from '../../../constants'
+
+export default class Erc20TokenContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(erc20ABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async balanceOf(address: string): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.balanceOf(address)
+    return bn.toString()
+  }
+
+  async approve(
+    address: string,
+    amount: string = MAX_ETH_NUM
+  ): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.approve(
+      address,
+      amount
+    )
+
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async transfer(
+    address: string,
+    amount: string
+  ): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.transfer(
+      address,
+      amount
+    )
+
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async allowance(owner: string, spender: string): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.allowance(owner, spender)
+    return bn.toString()
+  }
+
+  async symbol(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.symbol()
+    return bn
+  }
+
+  async name(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.name()
+    return bn
+  }
+
+  async decimals(): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.decimals()
+    return parseInt(bn)
+  }
+
+  async totalSupply(): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.totalSupply()
+    return bn.toString()
+  }
+}

+ 222 - 0
src/utils/EVMHelper/contracts/erc20ABI.json

@@ -0,0 +1,222 @@
+[
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_spender",
+        "type": "address"
+      },
+      {
+        "name": "_value",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_from",
+        "type": "address"
+      },
+      {
+        "name": "_to",
+        "type": "address"
+      },
+      {
+        "name": "_value",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_owner",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "name": "balance",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "constant": false,
+    "inputs": [
+      {
+        "name": "_to",
+        "type": "address"
+      },
+      {
+        "name": "_value",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "constant": true,
+    "inputs": [
+      {
+        "name": "_owner",
+        "type": "address"
+      },
+      {
+        "name": "_spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "payable": false,
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "payable": true,
+    "stateMutability": "payable",
+    "type": "fallback"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  }
+]

+ 40 - 0
src/utils/EVMHelper/contracts/erc20WhaleABI.json

@@ -0,0 +1,40 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "whale",
+        "type": "uint256"
+      }
+    ],
+    "name": "whaleToFragment",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "whale",
+        "type": "uint256"
+      }
+    ],
+    "name": "fragmentToWhale",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  }
+]

+ 102 - 0
src/utils/EVMHelper/contracts/erc721.ts

@@ -0,0 +1,102 @@
+import erc721ABI from './erc721ABI.json'
+import {
+  ethers,
+  ContractTransactionResponse,
+  ContractTransactionReceipt,
+} from 'ethers'
+import WrappedContract from './base'
+
+interface NFTTokenURI {
+  id: number
+  uri: string
+}
+
+export default class Erc72TokenContract extends WrappedContract {
+  constructor(address: string, provider: ethers.Signer) {
+    super(erc721ABI, address, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async balanceOf(address: string): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.balanceOf(address)
+    return parseInt(bn)
+  }
+
+  async tokenOfOwnerByIndex(address: string, index: number): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.tokenOfOwnerByIndex(address, index)
+    return parseInt(bn)
+  }
+
+  async tokenURI(tokenId: number): Promise<string> {
+    const contract = this.getInstance()
+    return await contract.tokenURI(tokenId)
+  }
+
+  async safeTransferFrom(
+    addressFrom: string,
+    addressTo: string,
+    tokenId: number
+  ): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const method = contract['safeTransferFrom(address,address,uint256)']
+    const tx: ContractTransactionResponse = await method(
+      addressFrom,
+      addressTo,
+      tokenId
+    )
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async approve(
+    addressTo: string,
+    tokenId: number
+  ): Promise<ContractTransactionReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransactionResponse = await contract.approve(
+      addressTo,
+      tokenId
+    )
+    const txwait = await tx.wait()
+    if (!txwait) {
+      throw new Error('Transaction failed')
+    }
+    return txwait
+  }
+
+  async getApproved(tokenId: number): Promise<string> {
+    const contract = this.getInstance()
+    return await contract.getApproved(tokenId)
+  }
+
+  // other
+  async tokenIdsOf(address: string): Promise<number[]> {
+    const count = await this.balanceOf(address)
+    const list: number[] = []
+    for (let i = 0; i < count; i++) {
+      list[i] = await this.tokenOfOwnerByIndex(address, i)
+    }
+    return list
+  }
+
+  async tokenURIListOf(address: string): Promise<NFTTokenURI[]> {
+    const ids = await this.tokenIdsOf(address)
+    const list: NFTTokenURI[] = []
+    for (let i = 0; i < ids.length; i++) {
+      const uri = await this.tokenURI(ids[i])
+      list[i] = {
+        id: ids[i],
+        uri,
+      }
+    }
+    return list
+  }
+}

+ 861 - 0
src/utils/EVMHelper/contracts/erc721ABI.json

@@ -0,0 +1,861 @@
+[
+	{
+		"inputs": [
+			{
+				"internalType": "string",
+				"name": "baseTokenURI",
+				"type": "string"
+			}
+		],
+		"stateMutability": "nonpayable",
+		"type": "constructor"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "approved",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "Approval",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			},
+			{
+				"indexed": false,
+				"internalType": "bool",
+				"name": "approved",
+				"type": "bool"
+			}
+		],
+		"name": "ApprovalForAll",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": false,
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "Paused",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"indexed": true,
+				"internalType": "bytes32",
+				"name": "previousAdminRole",
+				"type": "bytes32"
+			},
+			{
+				"indexed": true,
+				"internalType": "bytes32",
+				"name": "newAdminRole",
+				"type": "bytes32"
+			}
+		],
+		"name": "RoleAdminChanged",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "sender",
+				"type": "address"
+			}
+		],
+		"name": "RoleGranted",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "sender",
+				"type": "address"
+			}
+		],
+		"name": "RoleRevoked",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"indexed": true,
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "Transfer",
+		"type": "event"
+	},
+	{
+		"anonymous": false,
+		"inputs": [
+			{
+				"indexed": false,
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "Unpaused",
+		"type": "event"
+	},
+	{
+		"inputs": [],
+		"name": "DEFAULT_ADMIN_ROLE",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "DOMAIN_SEPARATOR",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "MINTER_ROLE",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "PAUSER_ROLE",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "approve",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "balanceOf",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "burn",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "getApproved",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "getMetadata",
+		"outputs": [
+			{
+				"internalType": "bytes",
+				"name": "metadata",
+				"type": "bytes"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			}
+		],
+		"name": "getRoleAdmin",
+		"outputs": [
+			{
+				"internalType": "bytes32",
+				"name": "",
+				"type": "bytes32"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "getRoleMember",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			}
+		],
+		"name": "getRoleMemberCount",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "grantRole",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "hasRole",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			}
+		],
+		"name": "isApprovedForAll",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "bytes",
+				"name": "metadata",
+				"type": "bytes"
+			}
+		],
+		"name": "mint",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "bytes",
+				"name": "metadata",
+				"type": "bytes"
+			},
+			{
+				"internalType": "uint256",
+				"name": "deadline",
+				"type": "uint256"
+			},
+			{
+				"internalType": "uint8",
+				"name": "v",
+				"type": "uint8"
+			},
+			{
+				"internalType": "bytes32",
+				"name": "r",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "bytes32",
+				"name": "s",
+				"type": "bytes32"
+			}
+		],
+		"name": "mintPermit",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "name",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			}
+		],
+		"name": "nonces",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "ownerOf",
+		"outputs": [
+			{
+				"internalType": "address",
+				"name": "",
+				"type": "address"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "pause",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "paused",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "renounceRole",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes32",
+				"name": "role",
+				"type": "bytes32"
+			},
+			{
+				"internalType": "address",
+				"name": "account",
+				"type": "address"
+			}
+		],
+		"name": "revokeRole",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "safeTransferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			},
+			{
+				"internalType": "bytes",
+				"name": "_data",
+				"type": "bytes"
+			}
+		],
+		"name": "safeTransferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "operator",
+				"type": "address"
+			},
+			{
+				"internalType": "bool",
+				"name": "approved",
+				"type": "bool"
+			}
+		],
+		"name": "setApprovalForAll",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			},
+			{
+				"internalType": "bytes",
+				"name": "metadata",
+				"type": "bytes"
+			}
+		],
+		"name": "setMetadata",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "bytes4",
+				"name": "interfaceId",
+				"type": "bytes4"
+			}
+		],
+		"name": "supportsInterface",
+		"outputs": [
+			{
+				"internalType": "bool",
+				"name": "",
+				"type": "bool"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "symbol",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenByIndex",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "owner",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "index",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenOfOwnerByIndex",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "tokenURI",
+		"outputs": [
+			{
+				"internalType": "string",
+				"name": "",
+				"type": "string"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "totalSupply",
+		"outputs": [
+			{
+				"internalType": "uint256",
+				"name": "",
+				"type": "uint256"
+			}
+		],
+		"stateMutability": "view",
+		"type": "function"
+	},
+	{
+		"inputs": [
+			{
+				"internalType": "address",
+				"name": "from",
+				"type": "address"
+			},
+			{
+				"internalType": "address",
+				"name": "to",
+				"type": "address"
+			},
+			{
+				"internalType": "uint256",
+				"name": "tokenId",
+				"type": "uint256"
+			}
+		],
+		"name": "transferFrom",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	},
+	{
+		"inputs": [],
+		"name": "unpause",
+		"outputs": [],
+		"stateMutability": "nonpayable",
+		"type": "function"
+	}
+]

+ 41 - 0
src/utils/EVMHelper/contracts/erc721Mock.ts

@@ -0,0 +1,41 @@
+import erc721MockABI from './erc721MockABI.json'
+import { BigNumber, ethers } from 'ethers'
+import Decimal from 'decimal.js-light'
+import WrappedContract from './base'
+import { CONTRACT_ERC721 } from '../../../constants'
+
+export default class Erc721MockContract extends WrappedContract {
+  constructor(provider: ethers.Signer | ethers.providers.Provider) {
+    super(erc721MockABI, CONTRACT_ERC721, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async getPrice(): Promise<string> {
+    const contract = this.getInstance()
+    const price: BigNumber = await contract.getPrice()
+    return price.toString()
+  }
+
+  async mint(amount = 1, txOptions = {}): Promise<ethers.ContractReceipt> {
+    const price = await this.getPrice()
+    const value = new Decimal(price).mul(amount).toString()
+    const contract = this.getInstance()
+    const status = await contract.mint(amount, {
+      // gasPrice: ethers.utils.parseUnits('1', 'gwei'),
+      gasLimit: 800000,
+      value,
+      ...txOptions,
+    })
+    const tx = await status.wait()
+    return tx
+  }
+
+  async balanceOf(address: string): Promise<BigNumber> {
+    const contract = this.getInstance()
+    const bn = await contract.balanceOf(address)
+    return bn
+  }
+}

+ 229 - 0
src/utils/EVMHelper/contracts/erc721MockABI.json

@@ -0,0 +1,229 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_ef_token",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_aave",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_weth",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_vault",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "fallback"
+  },
+  {
+    "inputs": [],
+    "name": "aave",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "balancer",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      }
+    ],
+    "name": "canWithdraw",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "deposit",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "ef_token",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getDebt",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getVolume",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "contract IERC20[]",
+        "name": "tokens",
+        "type": "address[]"
+      },
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      },
+      {
+        "internalType": "uint256[]",
+        "name": "feeAmounts",
+        "type": "uint256[]"
+      },
+      {
+        "internalType": "bytes",
+        "name": "userData",
+        "type": "bytes"
+      }
+    ],
+    "name": "receiveFlashLoan",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "recovery",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "sandbagsAddress",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "setAddress",
+        "type": "address"
+      }
+    ],
+    "name": "setQueueAddress",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "setAddress",
+        "type": "address"
+      }
+    ],
+    "name": "setSandbagAddress",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "weth",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "withdraw",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 14 - 0
src/utils/EVMHelper/contracts/filter.ts

@@ -0,0 +1,14 @@
+import filterABI from './filterABI.json'
+import { ethers } from 'ethers'
+import WrappedContract from './base'
+import { CONTRACT_FILTER } from '../../../constants'
+
+export default class FilterContract extends WrappedContract {
+  constructor(provider: ethers.Signer | ethers.providers.Provider) {
+    super(filterABI, CONTRACT_FILTER, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+}

+ 235 - 0
src/utils/EVMHelper/contracts/filterABI.json

@@ -0,0 +1,235 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_filter",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "user",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "index",
+        "type": "uint256"
+      }
+    ],
+    "name": "InQueue",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "execute",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_toAddress",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "filterMint",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getExecuteAddress",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getFilterRuleAddress",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getIndex",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getIsFilterWorking",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getOwner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "index",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "isFilterWorking",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "revert",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_execute",
+        "type": "address"
+      }
+    ],
+    "name": "setExecuteAddress",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_filter",
+        "type": "address"
+      }
+    ],
+    "name": "setFilterRuleAddress",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "bool",
+        "name": "_isWorking",
+        "type": "bool"
+      }
+    ],
+    "name": "setIsFilterWorking",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "receive"
+  }
+]

+ 20 - 0
src/utils/EVMHelper/contracts/filterRule.ts

@@ -0,0 +1,20 @@
+import filterABI from './filterRuleABI.json'
+import { ethers, BigNumber } from 'ethers'
+import WrappedContract from './base'
+import { CONTRACT_FILTER_RULE } from '../../../constants'
+
+export default class FilterRuleContract extends WrappedContract {
+  constructor(provider: ethers.Signer | ethers.providers.Provider) {
+    super(filterABI, CONTRACT_FILTER_RULE, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async getMaxGasPrice(): Promise<string> {
+    const contract = this.getInstance()
+    const bn: BigNumber = await contract.getMaxGasPrice()
+    return bn.toString()
+  }
+}

+ 124 - 0
src/utils/EVMHelper/contracts/filterRuleABI.json

@@ -0,0 +1,124 @@
+[
+  {
+    "inputs": [],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "index",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "caller",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "orgin",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "EOAFail",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "index",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "address",
+        "name": "caller",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "gasPrice",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "gasTooHigh",
+    "type": "event"
+  },
+  {
+    "inputs": [],
+    "name": "getMaxGasPrice",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_maxGasPrice",
+        "type": "uint256"
+      }
+    ],
+    "name": "setMaxGasPrice",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_caller",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_index",
+        "type": "uint256"
+      }
+    ],
+    "name": "test",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 51 - 0
src/utils/EVMHelper/contracts/fox.ts

@@ -0,0 +1,51 @@
+import { ethers } from 'ethers'
+import WrappedContract from './base'
+import FoxABI from './foxABI.json'
+
+export class FoxReadOnlyContract {
+  protected ABI: any[] = FoxABI
+  protected address: string
+  protected provider: ethers.Provider
+
+  constructor(provider: ethers.Provider) {
+    this.address = (window as any).CONSTS.CONTRACT_ADDRESS
+    this.provider = provider
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async mintTimes(address: string): Promise<number> {
+    const contract = this.getInstance()
+    const bn = await contract.mintTimes(address)
+    return parseInt(bn)
+  }
+}
+
+
+export default class FoxContract extends WrappedContract {
+  constructor(provider: ethers.Signer) {
+    super(FoxABI, (window as any).CONSTS.CONTRACT_ADDRESS, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async remainMintTimes(): Promise<number> {
+    const contract = this.getInstance()
+    const address = await this.provider.getAddress()
+    const max: bigint = await contract.maxMintTimes()
+    const bn: bigint = await contract.mintTimes(address)
+    const remain = max - bn
+    return parseInt(remain.toString())
+  }
+
+  async currentTokens(): Promise<string> {
+    const contract = this.getInstance()
+    const bn: bigint = await contract.currentTokens()
+    return bn.toString()
+  }
+}
+

+ 607 - 0
src/utils/EVMHelper/contracts/foxABI.json

@@ -0,0 +1,607 @@
+[
+  {
+    "inputs": [],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [],
+    "name": "ReentrancyGuardReentrantCall",
+    "type": "error"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "previousOwner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipTransferred",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "automatedMarketMakerPairs",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "blackHole",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "boostCount",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "currentTokens",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "tokens",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "subtractedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "decreaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "excluded",
+        "type": "bool"
+      }
+    ],
+    "name": "excludeFromFees",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "inSwap",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "addedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "increaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "isExcludedFromFees",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "launchTime",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "magicNumber",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "maxMintTimes",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "mintTimes",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "openTrading",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "renounceOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "saleCount",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "sellTax",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "pair",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "value",
+        "type": "bool"
+      }
+    ],
+    "name": "setAutomatedMarketMakerPair",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tradingOpen",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "transferOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "uniswapV2Pair",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_sellTax",
+        "type": "uint256"
+      }
+    ],
+    "name": "updateSellTax",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tkn",
+        "type": "address"
+      }
+    ],
+    "name": "withdrawStuckTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "receive"
+  }
+]

+ 70 - 0
src/utils/EVMHelper/contracts/hacker.ts

@@ -0,0 +1,70 @@
+import { ethers, ContractTransaction } from 'ethers'
+import WrappedContract from './base'
+import hackerABI from './hackerABI.json'
+import {
+  CONTRACT_EF,
+  CONTRACT_EFTOKEN,
+  CONTRACT_EF_SANDBAGS,
+  CONTRACT_HACKER,
+  CONTRACT_SAFE_EFTOKEN,
+  CONTRACT_FLASHLOANMOCK,
+  CONTRACT_AAVEMOCK,
+  CONTRACT_WETHTOKEN,
+} from '../../../constants'
+import Decimal from 'decimal.js-light'
+import { EFTokenErc20TokenContract } from './erc20'
+
+export default class HackerContract extends WrappedContract {
+  private poolAddress: string = ''
+  private efTokenAddress: string = ''
+
+  constructor(provider: ethers.Signer, isProtected: boolean = false) {
+    super(hackerABI, CONTRACT_HACKER, provider)
+    this.protected = isProtected
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  get protected(): boolean {
+    return this.poolAddress === CONTRACT_EF_SANDBAGS
+  }
+
+  set protected(value: boolean) {
+    this.poolAddress = value ? CONTRACT_EF_SANDBAGS : CONTRACT_EF
+    this.efTokenAddress = value ? CONTRACT_SAFE_EFTOKEN : CONTRACT_EFTOKEN
+  }
+
+  async checkAllowanceOfEf(account: string): Promise<boolean> {
+    const token = new EFTokenErc20TokenContract(this.provider, this.protected)
+    const allow = await token.allowance(account, this.address)
+    const userAmount = await token.balanceOf(account)
+    return new Decimal(allow).eq(userAmount)
+  }
+
+  async approveEf(): Promise<ethers.ContractReceipt> {
+    const token = new EFTokenErc20TokenContract(this.provider, this.protected)
+    const userAddress = await token.getCallerAddress()
+    const userAmount = await token.balanceOf(userAddress)
+    return await token.approve(this.address, userAmount)
+  }
+
+  async takeMoney(): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    return contract
+      .takeMoney(
+        this.poolAddress,
+        this.efTokenAddress,
+        CONTRACT_FLASHLOANMOCK,
+        CONTRACT_AAVEMOCK,
+        CONTRACT_WETHTOKEN,
+        {
+          gasLimit: 1000000,
+        }
+      )
+      .then(async (tx: ContractTransaction) => {
+        return await tx.wait()
+      })
+  }
+}

+ 59 - 0
src/utils/EVMHelper/contracts/hackerABI.json

@@ -0,0 +1,59 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_aave",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_target",
+        "type": "address"
+      }
+    ],
+    "name": "getVolume",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "ef",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "efToken",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "vault",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "aave",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "weth",
+        "type": "address"
+      }
+    ],
+    "name": "takeMoney",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 9 - 0
src/utils/EVMHelper/contracts/lpToken.ts

@@ -0,0 +1,9 @@
+import { ethers } from 'ethers'
+import { CONTRACT_PAIR } from '../../../constants'
+import Erc20TokenContract from './erc20'
+
+export default class LPTokenErc20TokenContract extends Erc20TokenContract {
+  constructor(provider: ethers.Signer) {
+    super(CONTRACT_PAIR, provider)
+  }
+}

+ 1112 - 0
src/utils/EVMHelper/contracts/lpTokenABI.json

@@ -0,0 +1,1112 @@
+[
+  {
+    "inputs": [],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "Burn",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "recipient",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Claim",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Fees",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Mint",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "sender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0In",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1In",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount0Out",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount1Out",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "Swap",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "reserve0",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "reserve1",
+        "type": "uint256"
+      }
+    ],
+    "name": "Sync",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "blockTimestampLast",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "burn",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amount0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount1",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "claimFees",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "claimed0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "claimed1",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "claimable0",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "claimable1",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      }
+    ],
+    "name": "current",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "currentCumulativePrices",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "reserve0Cumulative",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserve1Cumulative",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "blockTimestamp",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "fees",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      }
+    ],
+    "name": "getAmountOut",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "getReserves",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "_reserve0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_reserve1",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_blockTimestampLast",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "index0",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "index1",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "lastObservation",
+    "outputs": [
+      {
+        "components": [
+          {
+            "internalType": "uint256",
+            "name": "timestamp",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "reserve0Cumulative",
+            "type": "uint256"
+          },
+          {
+            "internalType": "uint256",
+            "name": "reserve1Cumulative",
+            "type": "uint256"
+          }
+        ],
+        "internalType": "struct Pair.Observation",
+        "name": "",
+        "type": "tuple"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "metadata",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "dec0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "dec1",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "r0",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "r1",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bool",
+        "name": "st",
+        "type": "bool"
+      },
+      {
+        "internalType": "address",
+        "name": "t0",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "t1",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "mint",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "nonces",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "observationLength",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "name": "observations",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "timestamp",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserve0Cumulative",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserve1Cumulative",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint8",
+        "name": "v",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "r",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "s",
+        "type": "bytes32"
+      }
+    ],
+    "name": "permit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "points",
+        "type": "uint256"
+      }
+    ],
+    "name": "prices",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "granularity",
+        "type": "uint256"
+      }
+    ],
+    "name": "quote",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "reserve0",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "reserve0CumulativeLast",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "reserve1",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "reserve1CumulativeLast",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenIn",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "points",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "window",
+        "type": "uint256"
+      }
+    ],
+    "name": "sample",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      }
+    ],
+    "name": "skim",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "stable",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "supplyIndex0",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "supplyIndex1",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amount0Out",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount1Out",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "bytes",
+        "name": "data",
+        "type": "bytes"
+      }
+    ],
+    "name": "swap",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "sync",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token0",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "token1",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "tokens",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "dst",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "src",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "dst",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 50 - 0
src/utils/EVMHelper/contracts/pancake.ts

@@ -0,0 +1,50 @@
+import pancakeABI from './pancakeABI.json'
+import { ContractTransaction, ethers } from 'ethers'
+import WrappedContract from './base'
+import {
+  CONTRACT_PANCAKE,
+  CONTRACT_SBT,
+  CONTRACT_USDT,
+} from '../../../constants'
+import dayjs from 'dayjs'
+
+export default class PancakeContract extends WrappedContract {
+  constructor(provider: ethers.Signer | ethers.providers.Provider) {
+    super(pancakeABI, CONTRACT_PANCAKE, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async swapExactTokensForTokens(
+    amountIn: string,
+    path: string[],
+    to: string,
+    deadline = dayjs().add(15, 'minute').unix()
+  ): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.swapExactTokensForTokens(
+      amountIn,
+      0,
+      path,
+      to,
+      deadline,
+      {
+        gasLimit: 300000,
+      }
+    )
+    return await tx.wait()
+  }
+
+  async swapExactSBTForUSDT(
+    amountIn: string,
+    to: string
+  ): Promise<ethers.ContractReceipt> {
+    return await this.swapExactTokensForTokens(
+      amountIn,
+      [CONTRACT_SBT, CONTRACT_USDT],
+      to
+    )
+  }
+}

+ 973 - 0
src/utils/EVMHelper/contracts/pancakeABI.json

@@ -0,0 +1,973 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "_factory",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "_WETH",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "inputs": [],
+    "name": "WETH",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenA",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenB",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountADesired",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountBDesired",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountAMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountBMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "addLiquidity",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountA",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountB",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenDesired",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETHMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "addLiquidityETH",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountToken",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETH",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "factory",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveOut",
+        "type": "uint256"
+      }
+    ],
+    "name": "getAmountIn",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveOut",
+        "type": "uint256"
+      }
+    ],
+    "name": "getAmountOut",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      }
+    ],
+    "name": "getAmountsIn",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      }
+    ],
+    "name": "getAmountsOut",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountA",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveA",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "reserveB",
+        "type": "uint256"
+      }
+    ],
+    "name": "quote",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountB",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenA",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenB",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountAMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountBMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "removeLiquidity",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountA",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountB",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETHMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "removeLiquidityETH",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountToken",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETH",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETHMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "removeLiquidityETHSupportingFeeOnTransferTokens",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountETH",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETHMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bool",
+        "name": "approveMax",
+        "type": "bool"
+      },
+      {
+        "internalType": "uint8",
+        "name": "v",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "r",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "s",
+        "type": "bytes32"
+      }
+    ],
+    "name": "removeLiquidityETHWithPermit",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountToken",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETH",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "token",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountTokenMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountETHMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bool",
+        "name": "approveMax",
+        "type": "bool"
+      },
+      {
+        "internalType": "uint8",
+        "name": "v",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "r",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "s",
+        "type": "bytes32"
+      }
+    ],
+    "name": "removeLiquidityETHWithPermitSupportingFeeOnTransferTokens",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountETH",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "tokenA",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "tokenB",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "liquidity",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountAMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountBMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bool",
+        "name": "approveMax",
+        "type": "bool"
+      },
+      {
+        "internalType": "uint8",
+        "name": "v",
+        "type": "uint8"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "r",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "s",
+        "type": "bytes32"
+      }
+    ],
+    "name": "removeLiquidityWithPermit",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountA",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountB",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapETHForExactTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactETHForTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactETHForTokensSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "payable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForETH",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForETHSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountIn",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountOutMin",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountInMax",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapTokensForExactETH",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "amountOut",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amountInMax",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address[]",
+        "name": "path",
+        "type": "address[]"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "deadline",
+        "type": "uint256"
+      }
+    ],
+    "name": "swapTokensForExactTokens",
+    "outputs": [
+      {
+        "internalType": "uint256[]",
+        "name": "amounts",
+        "type": "uint256[]"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "stateMutability": "payable",
+    "type": "receive"
+  }
+]

+ 153 - 0
src/utils/EVMHelper/contracts/pool.ts

@@ -0,0 +1,153 @@
+import poolABI from './poolABI.json'
+import { ContractTransaction, ethers } from 'ethers'
+import WrappedContract from './base'
+import { CONTRACT_POOL } from '../../../constants'
+import { WHALETokenErc20TokenContract } from './erc20'
+// import { tokenHumanAmount } from '../../../utils'
+// import dayjs from 'dayjs'
+
+export enum WhalePresaleStatus {
+  upcoming = 'upcoming',
+  living = 'living',
+  ended = 'ended',
+}
+
+export interface PoolUserInfo {
+  amount: string
+  rewardDebt: string
+  lockEndedTimestamp: string
+}
+
+export default class PoolContract extends WrappedContract {
+  constructor(provider: ethers.Signer) {
+    super(poolABI, CONTRACT_POOL, provider)
+  }
+
+  getInstance(): ethers.Contract {
+    return new ethers.Contract(this.address, this.ABI as any, this.provider)
+  }
+
+  async deposit(mode: 0 | 1, value: string): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.deposit(mode, value)
+    return await tx.wait()
+  }
+
+  async depositToken(value: string): Promise<ethers.ContractReceipt> {
+    return await this.deposit(1, value)
+  }
+
+  async depositLP(value: string): Promise<ethers.ContractReceipt> {
+    return await this.deposit(0, value)
+  }
+
+  async depositMax(mode: 0 | 1): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.depositMax(mode)
+    return await tx.wait()
+  }
+
+  async depositMaxToken(): Promise<ethers.ContractReceipt> {
+    return await this.depositMax(1)
+  }
+
+  async depositMaxLP(): Promise<ethers.ContractReceipt> {
+    return await this.depositMax(0)
+  }
+
+  async getUserInfo(mode: 0 | 1, address: string): Promise<PoolUserInfo> {
+    const contract = this.getInstance()
+    const ui = await contract.userInfo(mode, address)
+    return {
+      amount: ui.amount.toString(),
+      rewardDebt: ui.rewardDebt.toString(),
+      lockEndedTimestamp: ui.lockEndedTimestamp.toString(),
+    }
+  }
+
+  async getTokenUserInfo(address: string): Promise<PoolUserInfo> {
+    const userInfo = await this.getUserInfo(1, address)
+    const tokenContract = new WHALETokenErc20TokenContract(this.provider)
+    const toWhale = await tokenContract.whaleToFragment(userInfo.amount)
+    userInfo.amount = toWhale.toString()
+    return userInfo
+  }
+
+  async getLPUserInfo(address: string): Promise<PoolUserInfo> {
+    return await this.getUserInfo(0, address)
+  }
+
+  async getDepositBalance(mode: 0 | 1, address: string): Promise<string> {
+    const { amount } = await this.getUserInfo(mode, address)
+    return amount.toString()
+  }
+
+  async getTokenDepositBalance(address: string): Promise<string> {
+    const whaleAmount = await this.getDepositBalance(1, address)
+    const tokenContract = new WHALETokenErc20TokenContract(this.provider)
+    const toWhale = await tokenContract.whaleToFragment(whaleAmount)
+    return toWhale.toString()
+  }
+
+  async getLPDepositBalance(address: string): Promise<string> {
+    return await this.getDepositBalance(0, address)
+  }
+
+  async withdraw(mode: 0 | 1, value: string): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.withdraw(mode, value)
+    return await tx.wait()
+  }
+
+  async withdrawToken(value: string): Promise<ethers.ContractReceipt> {
+    const tokenContract = new WHALETokenErc20TokenContract(this.provider)
+    const amount = await tokenContract.fragmentToWhale(value)
+    return await this.withdraw(1, amount)
+  }
+
+  async withdrawLP(value: string): Promise<ethers.ContractReceipt> {
+    return await this.withdraw(0, value)
+  }
+
+  async withdrawMax(mode: 0 | 1): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.withdrawMax(mode)
+    return await tx.wait()
+  }
+
+  async withdrawMaxToken(): Promise<ethers.ContractReceipt> {
+    return await this.withdrawMax(1)
+  }
+
+  async withdrawMaxLP(): Promise<ethers.ContractReceipt> {
+    return await this.withdrawMax(0)
+  }
+
+  async claim(mode: 0 | 1, address: string): Promise<ethers.ContractReceipt> {
+    const contract = this.getInstance()
+    const tx: ContractTransaction = await contract.claim(mode, address)
+    return await tx.wait()
+  }
+
+  async claimToken(address: string): Promise<ethers.ContractReceipt> {
+    return await this.claim(1, address)
+  }
+
+  async claimLP(address: string): Promise<ethers.ContractReceipt> {
+    return await this.claim(0, address)
+  }
+
+  async pendingReward(mode: 0 | 1, address: string): Promise<string> {
+    const contract = this.getInstance()
+    const bn = await contract.pendingReward(mode, address)
+    return bn.toString()
+  }
+
+  async pendingRewardToken(address: string): Promise<string> {
+    return await this.pendingReward(1, address)
+  }
+
+  async pendingRewardLP(address: string): Promise<string> {
+    return await this.pendingReward(0, address)
+  }
+}

+ 738 - 0
src/utils/EVMHelper/contracts/poolABI.json

@@ -0,0 +1,738 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "contract IWhaleToken",
+        "name": "_whale",
+        "type": "address"
+      },
+      {
+        "internalType": "contract IUniswapPair",
+        "name": "_whaleLp",
+        "type": "address"
+      },
+      {
+        "internalType": "contract IOVM_L1BlockNumber",
+        "name": "_l1BlockNumber",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_rewardPerBlock",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_startBlock",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "constructor"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "user",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Deposit",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "allocPoint",
+        "type": "uint256"
+      },
+      {
+        "indexed": true,
+        "internalType": "contract IERC20",
+        "name": "lpToken",
+        "type": "address"
+      }
+    ],
+    "name": "LogPoolAddition",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "LogRewardPerBlock",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "lockDuration",
+        "type": "uint256"
+      }
+    ],
+    "name": "LogSetLockDuration",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "allocPoint",
+        "type": "uint256"
+      }
+    ],
+    "name": "LogSetPool",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "lastRewardBlock",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "lpSupply",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "accRewardPerShare",
+        "type": "uint256"
+      }
+    ],
+    "name": "LogUpdatePool",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "previousOwner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "OwnershipTransferred",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "user",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "RewardPaid",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "user",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint256",
+        "name": "pid",
+        "type": "uint256"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "Withdraw",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_allocPoint",
+        "type": "uint256"
+      },
+      {
+        "internalType": "contract IERC20",
+        "name": "_lpToken",
+        "type": "address"
+      },
+      {
+        "internalType": "bool",
+        "name": "_withUpdate",
+        "type": "bool"
+      }
+    ],
+    "name": "add",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "_account",
+        "type": "address"
+      }
+    ],
+    "name": "claim",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "principal",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "ratio",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "n",
+        "type": "uint256"
+      }
+    ],
+    "name": "compound",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "compoundRatio",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "deposit",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      }
+    ],
+    "name": "depositMax",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "l1BlockNumber",
+    "outputs": [
+      {
+        "internalType": "contract IOVM_L1BlockNumber",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "lastBlock",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "name": "lockDurations",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "massUpdatePools",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "owner",
+    "outputs": [
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "_user",
+        "type": "address"
+      }
+    ],
+    "name": "pendingReward",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "name": "poolInfo",
+    "outputs": [
+      {
+        "internalType": "contract IERC20",
+        "name": "lpToken",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "allocPoint",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "lastRewardBlock",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "accRewardPerShare",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "poolLength",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "int128",
+        "name": "x",
+        "type": "int128"
+      },
+      {
+        "internalType": "uint256",
+        "name": "n",
+        "type": "uint256"
+      }
+    ],
+    "name": "pow",
+    "outputs": [
+      {
+        "internalType": "int128",
+        "name": "r",
+        "type": "int128"
+      }
+    ],
+    "stateMutability": "pure",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "renounceOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "rewardPerBlock",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_allocPoint",
+        "type": "uint256"
+      },
+      {
+        "internalType": "bool",
+        "name": "_withUpdate",
+        "type": "bool"
+      }
+    ],
+    "name": "set",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_lockDuration",
+        "type": "uint256"
+      }
+    ],
+    "name": "setLockDuration",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "startBlock",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalAllocPoint",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalLocked",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "newOwner",
+        "type": "address"
+      }
+    ],
+    "name": "transferOwnership",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      }
+    ],
+    "name": "updatePool",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_rewardPerBlock",
+        "type": "uint256"
+      }
+    ],
+    "name": "updateRewardPerBlock",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      },
+      {
+        "internalType": "address",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "name": "userInfo",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "rewardDebt",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "lockEndedTimestamp",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "whale",
+    "outputs": [
+      {
+        "internalType": "contract IWhaleToken",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "whaleLp",
+    "outputs": [
+      {
+        "internalType": "contract IUniswapPair",
+        "name": "",
+        "type": "address"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      },
+      {
+        "internalType": "uint256",
+        "name": "_amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "withdraw",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "uint256",
+        "name": "_pid",
+        "type": "uint256"
+      }
+    ],
+    "name": "withdrawMax",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 290 - 0
src/utils/EVMHelper/contracts/wethABI.json

@@ -0,0 +1,290 @@
+[
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Approval",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "indexed": false,
+        "internalType": "uint256",
+        "name": "value",
+        "type": "uint256"
+      }
+    ],
+    "name": "Transfer",
+    "type": "event"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "owner",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      }
+    ],
+    "name": "allowance",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "approve",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      }
+    ],
+    "name": "balanceOf",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "decimals",
+    "outputs": [
+      {
+        "internalType": "uint8",
+        "name": "",
+        "type": "uint8"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "subtractedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "decreaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "spender",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "addedValue",
+        "type": "uint256"
+      }
+    ],
+    "name": "increaseAllowance",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "account",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "mint",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "name",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "symbol",
+    "outputs": [
+      {
+        "internalType": "string",
+        "name": "",
+        "type": "string"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [],
+    "name": "totalSupply",
+    "outputs": [
+      {
+        "internalType": "uint256",
+        "name": "",
+        "type": "uint256"
+      }
+    ],
+    "stateMutability": "view",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transfer",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "from",
+        "type": "address"
+      },
+      {
+        "internalType": "address",
+        "name": "to",
+        "type": "address"
+      },
+      {
+        "internalType": "uint256",
+        "name": "amount",
+        "type": "uint256"
+      }
+    ],
+    "name": "transferFrom",
+    "outputs": [
+      {
+        "internalType": "bool",
+        "name": "",
+        "type": "bool"
+      }
+    ],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 58 - 0
src/utils/EVMHelper/dbModels/RPC.ts

@@ -0,0 +1,58 @@
+import { Op } from 'sequelize'
+import {
+  Table,
+  Column,
+  AllowNull,
+  DataType,
+  ForeignKey,
+  BelongsTo,
+  HasOne,
+  Unique,
+  BeforeUpdate,
+  BeforeCreate,
+  Is,
+  Model,
+  Default,
+  BelongsToMany,
+  BeforeFind,
+  HasMany,
+} from 'sequelize-typescript'
+import { SupportedEVMHelperChains } from '../types'
+
+@Table({
+  modelName: 'evmh_rpc',
+  indexes: [
+    {
+      fields: ['chainName'],
+    },
+    {
+      fields: ['chainName', 'rpc'],
+      unique: true,
+    },
+  ],
+})
+export default class RPC extends Model {
+  @AllowNull(false)
+  @Column(DataType.CHAR(32))
+  get chainName(): SupportedEVMHelperChains {
+    return this.getDataValue('chainName')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.CHAR(128).BINARY)
+  get rpc(): string {
+    return this.getDataValue('rpc')
+  }
+
+  static async getRPCList(chainName: SupportedEVMHelperChains): Promise<RPC[]> {
+    if (!this.sequelize) {
+      return []
+    }
+    const r = await RPC.findAll({
+      where: {
+        chainName,
+      },
+    })
+    return r
+  }
+}

+ 3 - 0
src/utils/EVMHelper/dbModels/index.ts

@@ -0,0 +1,3 @@
+import RPC from './RPC'
+
+export { RPC }

+ 2 - 0
src/utils/EVMHelper/index.ts

@@ -0,0 +1,2 @@
+import EVMHelper from './EVMHelper'
+export default EVMHelper

+ 64 - 0
src/utils/EVMHelper/types.ts

@@ -0,0 +1,64 @@
+export enum SupportedEVMHelperChains {
+  mainnet = 'mainnet',
+  goerli = 'goerli',
+  scrollAlpha = 'scrollAlpha',
+  bsc = 'bsc',
+  bscTestnet = 'bscTestnet',
+  arbitrum = 'arbitrum',
+  optimism = 'optimism',
+  avax = 'avax',
+  polygon = 'polygon',
+  zkSyncMainnet = 'zkSyncMainnet',
+  blastSepoliaTestnet = 'blastSepoliaTestnet',
+}
+
+// eslint-disable-next-line prettier/prettier
+export const DefaultEVMHelperChainRPCs: Record<SupportedEVMHelperChains, string> = {
+  mainnet: 'https://eth.llamarpc.com',
+  goerli: 'https://eth-goerli.public.blastapi.io',
+  scrollAlpha: 'https://alpha-rpc.scroll.io/l2',
+  bsc: 'https://bsc-dataseed.binance.org',
+  bscTestnet: 'https://data-seed-prebsc-1-s3.binance.org:8545',
+  arbitrum: 'https://endpoints.omniatech.io/v1/arbitrum/one/public',
+  optimism: 'https://endpoints.omniatech.io/v1/op/mainnet/public',
+  avax: 'https://api.avax.network/ext/bc/C/rpc',
+  polygon: 'https://polygon.blockpi.network/v1/rpc/public',
+  zkSyncMainnet: 'https://mainnet.era.zksync.io',
+  blastSepoliaTestnet: 'https://sepolia.blast.io',
+}
+
+// eslint-disable-next-line prettier/prettier
+export const DefaultEVMHelperExplorerURLs: Record<SupportedEVMHelperChains, string> = {
+  mainnet: 'https://etherscan.io',
+  goerli: 'https://goerli.etherscan.io',
+  scrollAlpha: 'https://blockscout.scroll.io',
+  bsc: 'https://bscscan.com',
+  bscTestnet: 'https://testnet.bscscan.com',
+  arbitrum: 'https://arbiscan.io',
+  optimism: 'https://optimistic.etherscan.io',
+  avax: 'https://snowtrace.io',
+  polygon: 'https://polygonscan.com',
+  zkSyncMainnet: 'https://explorer.zksync.io',
+  blastSepoliaTestnet: 'https://testnet.blastscan.io',
+}
+
+export const MAX_ETH_NUM =
+  '115792089237316195423570985008687907853269984665640564039457584007913129639935'
+
+export const MAX_UINT128 = '340282366920938463463374607431768211455'
+
+// eslint-disable-next-line prettier/prettier
+export const DefaultEVMHelperScanAPIUrls: Record<SupportedEVMHelperChains, string> = {
+  mainnet: '',
+  goerli: '',
+  scrollAlpha: '',
+  bsc: 'https://api.bscscan.com',
+  bscTestnet: '',
+  arbitrum: '',
+  optimism: '',
+  avax: '',
+  polygon: '',
+  zkSyncMainnet: '',
+  blastSepoliaTestnet:
+    'https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan',
+}

+ 108 - 0
src/utils/SolanaHelper/BloxrouteSolanaHelper.ts

@@ -0,0 +1,108 @@
+import {
+  Commitment,
+  Keypair,
+  PublicKey,
+  SystemProgram,
+  TransactionInstruction,
+  Transaction,
+} from '@solana/web3.js'
+import axios, { AxiosInstance } from 'axios'
+import { lamports } from '..'
+import SolanaHelper, { PriorityFee } from './SolanaHelper'
+import { addMemoToSerializedTxn } from '@bloxroute/solana-trader-client-ts'
+
+export enum BloxrouteSolanaRpc {
+  England = 'https://uk.solana.dex.blxrbdn.com',
+  NewYork = 'https://ny.solana.dex.blxrbdn.com',
+}
+
+const TRADER_API_TIP_WALLET = 'HWEoBxYs7ssKuudEjzjmpfJVX7Dvi7wescFsVx2L5yoY'
+
+export default class BloxrouteSolanaHelper {
+  private readonly caller: AxiosInstance
+
+  constructor(
+    private readonly solanaHelper: SolanaHelper,
+    private readonly token = process.env.BLOXROUTE_TOKEN!,
+    private readonly rpc: BloxrouteSolanaRpc = BloxrouteSolanaRpc.NewYork
+  ) {
+    this.caller = axios.create({
+      baseURL: rpc,
+      headers: {
+        Authorization: token,
+      },
+    })
+  }
+
+  async getRecentPriorityFee(
+    project: 'P_JUPITER' | 'P_RAYDIUM' = 'P_JUPITER'
+  ): Promise<bigint> {
+    const response = await this.caller.get('/api/v2/system/priority-fee', {
+      params: { project },
+    })
+    return BigInt(response.data.feeAtPercentile)
+  }
+
+  CreateTraderAPITipInstruction(
+    payer: PublicKey,
+    tipAmount: number = parseInt(lamports('0.001'))
+  ): TransactionInstruction {
+    const tipAddress = new PublicKey(TRADER_API_TIP_WALLET)
+
+    return SystemProgram.transfer({
+      fromPubkey: payer,
+      toPubkey: tipAddress,
+      lamports: tipAmount,
+    })
+  }
+
+  async sendTx(
+    tx: Transaction,
+    payer: PublicKey,
+    signers: Keypair[],
+    priorityFees?: PriorityFee,
+    tipAmount: number = parseInt(lamports('0.01')),
+    commitment: Commitment = 'confirmed'
+  ): Promise<string> {
+    const connection = await this.solanaHelper.getProvider()
+    const blockHash = (await connection.getLatestBlockhash(commitment))
+      .blockhash
+    const feeedTx = this.solanaHelper.makePriorityFeesTx(tx, priorityFees)
+    console.log('tip amount:', tipAmount)
+    const tipTx = this.CreateTraderAPITipInstruction(payer, tipAmount)
+    feeedTx.add(tipTx)
+
+    console.log('Transaction to be sent:', feeedTx)
+
+    const versionedTx = await this.solanaHelper.buildVersionedTx(
+      payer,
+      feeedTx,
+      blockHash
+    )
+    versionedTx.sign(signers)
+
+    const serializedTransaztionBytes = versionedTx.serialize()
+    const buff = Buffer.from(serializedTransaztionBytes)
+    const b64Content = buff.toString('base64')
+    const memoedB64Content = addMemoToSerializedTxn(b64Content)
+
+    console.log('Base64 transaction:', memoedB64Content)
+    // throw new Error('Failed to send transaction')
+    const response = await this.caller.post('/api/v2/submit', {
+      transaction: { content: memoedB64Content },
+      frontRunningProtection: false,
+      useStakedRPCs: true,
+    })
+
+    console.log('Transaction sent:', response.data)
+
+    return '111'
+  }
+
+  async getTxStatus(signature: string): Promise<void> {
+    const response = await this.caller.get('/api/v2/transaction', {
+      params: { signature },
+    })
+    console.log(response.data)
+  }
+}

+ 219 - 0
src/utils/SolanaHelper/JitoHelper.ts

@@ -0,0 +1,219 @@
+import { PublicKey, Transaction, VersionedTransaction } from '@solana/web3.js'
+import axios from 'axios'
+import bs58 from 'bs58'
+import SolanaHelper from './SolanaHelper'
+import { timeNumber } from '../time'
+import { sleep } from '..'
+
+// export interface JitoHelperBlockEngineConfig {
+//   BLOCK_ENGINE_URL: string
+//   SHRED_RECEIVER_ADDR: string
+//   RELAYER_URL: string
+// }
+
+export interface JitoHelperOptions {
+  // blockEngineConfig: JitoHelperBlockEngineConfig
+  rpc: string
+}
+
+export interface WaitForLandedOptions {
+  throwOnInvalid: boolean
+  maxTry: number
+  interval: number
+  verbose: boolean
+}
+
+const defaultWaitForLandedOptions: WaitForLandedOptions = {
+  throwOnInvalid: true,
+  maxTry: 99,
+  interval: timeNumber.second * 5,
+  verbose: false,
+}
+
+export interface JitoJRPCResponse<T> {
+  jsonrpc: '2.0'
+  result: {
+    context: {
+      slot: number
+    }
+    value: T
+  }
+  id: number
+}
+
+export interface JitoJRPCInflightBundleStatus {
+  bundle_id: string
+  status: 'Invalid' | 'Pending' | 'Failed' | 'Landed'
+  landed_slot: number | null
+}
+
+// eslint-disable-next-line prettier/prettier
+export type JitoJRPCInflightBundleStatusesResponse = JitoJRPCResponse<JitoJRPCInflightBundleStatus[]>
+
+export interface JitoJRPCBundleStatus {
+  bundle_id: string
+  transactions: string[]
+  slot: number
+  confirmation_status: 'processed' | 'confirmed' | 'finalized'
+  err: {
+    Ok: null
+  }
+}
+
+// eslint-disable-next-line prettier/prettier
+export type JitoJRPCBundleStatusesResponse = JitoJRPCResponse<JitoJRPCBundleStatus[]>
+
+export default class JitoJRPCHelper {
+  constructor(
+    readonly options: JitoHelperOptions,
+    private readonly solanaHelper: SolanaHelper
+  ) {}
+
+  async getInflightBundleStatuses(
+    id1: string,
+    id2?: string,
+    id3?: string,
+    id4?: string,
+    id5?: string
+  ): Promise<JitoJRPCInflightBundleStatusesResponse> {
+    const url = `${this.options.rpc}/api/v1/bundles`
+    const data = {
+      jsonrpc: '2.0',
+      id: 1,
+      method: 'getInflightBundleStatuses',
+      params: [[id1]],
+    }
+    if (id2) data.params[0].push(id2)
+    if (id3) data.params[0].push(id3)
+    if (id4) data.params[0].push(id4)
+    if (id5) data.params[0].push(id5)
+    const response = await axios.post(url, data)
+    return response.data
+  }
+
+  async getBundleStatuses(
+    id1: string,
+    id2?: string,
+    id3?: string,
+    id4?: string,
+    id5?: string
+  ): Promise<JitoJRPCBundleStatusesResponse> {
+    const url = `${this.options.rpc}/api/v1/bundles`
+    const data = {
+      jsonrpc: '2.0',
+      id: 1,
+      method: 'getBundleStatuses',
+      params: [[id1]],
+    }
+    if (id2) data.params[0].push(id2)
+    if (id3) data.params[0].push(id3)
+    if (id4) data.params[0].push(id4)
+    if (id5) data.params[0].push(id5)
+    const response = await axios.post(url, data)
+    return response.data
+  }
+
+  async getTipAccounts(): Promise<string[]> {
+    const url = `${this.options.rpc}/api/v1/bundles`
+    const data = {
+      jsonrpc: '2.0',
+      id: 1,
+      method: 'getTipAccounts',
+      params: [],
+    }
+    interface Resp {
+      jsonrpc: '2.0'
+      result: string[]
+      id: number
+    }
+    const response = await axios.post<Resp>(url, data)
+    return response.data.result
+  }
+
+  async sendBundle(
+    tx1: VersionedTransaction,
+    tx2?: VersionedTransaction,
+    tx3?: VersionedTransaction,
+    tx4?: VersionedTransaction,
+    tx5?: VersionedTransaction
+  ): Promise<string> {
+    // make tx1 to fully-signed transactions, as base-58 encoded strings
+    const url = `${this.options.rpc}/api/v1/bundles`
+    const data = {
+      jsonrpc: '2.0',
+      id: 1,
+      method: 'sendBundle',
+      params: [[bs58.encode(tx1.serialize())]],
+    }
+    // console.log(url)
+    if (tx2) data.params[0].push(bs58.encode(tx2.serialize()))
+    if (tx3) data.params[0].push(bs58.encode(tx3.serialize()))
+    if (tx4) data.params[0].push(bs58.encode(tx4.serialize()))
+    if (tx5) data.params[0].push(bs58.encode(tx5.serialize()))
+    const response = await axios.post(url, data)
+    return response.data.result
+    // return 'test'
+  }
+
+  async addTipTransactionToTx(
+    tx: Transaction,
+    payer: PublicKey,
+    tipLamports: bigint
+  ): Promise<void> {
+    const tipAccounts = await this.getTipAccounts()
+    if (tipAccounts.length === 0) {
+      throw new Error('No tip accounts available')
+    }
+    const tipAccount = tipAccounts[0]
+    const transferTx = this.solanaHelper.makeTransferTx(
+      payer,
+      new PublicKey(tipAccount),
+      tipLamports
+    )
+    tx.add(transferTx)
+  }
+
+  async waitForLanded(
+    bundleId: string,
+    options: Partial<WaitForLandedOptions> = {}
+  ): Promise<JitoJRPCBundleStatus> {
+    const { throwOnInvalid, maxTry, interval, verbose } = {
+      ...defaultWaitForLandedOptions,
+      ...options,
+    }
+
+    let tryCount = 0
+    while (true) {
+      if (tryCount >= maxTry) {
+        throw new Error('Bundle not landed after max tries')
+      }
+      const resp = await this.getInflightBundleStatuses(bundleId)
+      if (verbose) {
+        console.log(resp.result)
+      }
+      const inflight = resp.result.value[0]
+      if (!inflight) {
+        await sleep(interval)
+        continue
+      }
+      switch (inflight.status) {
+        case 'Landed': {
+          const status = await this.getBundleStatuses(bundleId)
+          return status.result.value[0]
+        }
+        case 'Failed':
+          throw new Error('Bundle failed')
+        case 'Invalid': {
+          if (throwOnInvalid) {
+            throw new Error('Bundle is invalid')
+          }
+          break
+        }
+        case 'Pending':
+          break
+      }
+      tryCount++
+      await sleep(interval)
+    }
+  }
+}

+ 426 - 0
src/utils/SolanaHelper/SolanaHelper.ts

@@ -0,0 +1,426 @@
+import {
+  ComputeBudgetProgram,
+  Connection,
+  TransactionInstruction,
+  Keypair,
+  PublicKey,
+  LAMPORTS_PER_SOL,
+  Transaction,
+  Commitment,
+  Finality,
+  TransactionMessage,
+  VersionedTransaction,
+  VersionedTransactionResponse,
+  SendTransactionError,
+  SystemProgram,
+  sendAndConfirmTransaction,
+  VersionedBlockResponse,
+} from '@solana/web3.js'
+import bs58 from 'bs58'
+import axios from 'axios'
+import JitoJRPCHelper, { JitoHelperOptions } from './JitoHelper'
+import BloxrouteSolanaHelper from './BloxrouteSolanaHelper'
+import { sleep } from '..'
+import { timeNumber } from '../time'
+
+export interface SolanaHelperOptions {
+  rpc: string
+  jitoHelperOptions: JitoHelperOptions
+}
+
+export interface PriorityFee {
+  unitLimit?: number
+  unitPrice?: number
+}
+
+export interface PriorityFeeInfo {
+  median: number
+  average: number
+  max: number
+  min: number
+  numSamples: number
+}
+
+export interface TransactionResult {
+  signature?: string
+  error?: unknown
+  results?: VersionedTransactionResponse
+  success: boolean
+}
+
+const defaultSolanaHelperOptions: SolanaHelperOptions = {
+  rpc: 'https://rpc.ankr.com/solana',
+  jitoHelperOptions: {
+    rpc: 'https://mainnet.block-engine.jito.wtf',
+  },
+}
+
+export default class SolanaHelper {
+  solDecimals = 9
+  lamportsPerSol = LAMPORTS_PER_SOL
+  private readonly options: SolanaHelperOptions
+  private provider: Connection | null = null
+  readonly jitoHelper: JitoJRPCHelper
+  readonly bloxrouteSolanaHelper
+
+  constructor(options: Partial<SolanaHelperOptions> = {}) {
+    this.options = {
+      ...defaultSolanaHelperOptions,
+      ...options,
+    }
+    this.jitoHelper = new JitoJRPCHelper(this.options.jitoHelperOptions, this)
+    this.bloxrouteSolanaHelper = new BloxrouteSolanaHelper(this)
+  }
+
+  async getProvider(): Promise<Connection> {
+    if (this.provider) return this.provider
+    this.provider = new Connection(this.options.rpc, 'confirmed')
+    return this.provider
+  }
+
+  get connection(): Connection {
+    if (!this.provider) throw new Error('Provider not initialized')
+    return this.provider
+  }
+
+  async getLatestBlockhash(
+    commitment: Commitment = 'confirmed'
+  ): Promise<string> {
+    const provider = await this.getProvider()
+    const blockhash = await provider.getLatestBlockhash(commitment)
+    return blockhash.blockhash
+  }
+
+  getAccount(pk: string): Keypair {
+    return Keypair.fromSecretKey(bs58.decode(pk))
+  }
+
+  getPrivateKey(account: Keypair): string {
+    return bs58.encode(account.secretKey)
+  }
+
+  async getBalance(accountPublicKey: string): Promise<string> {
+    const provider = await this.getProvider()
+    const balance = await provider.getBalance(new PublicKey(accountPublicKey))
+    return balance.toString()
+  }
+
+  async getSPLBalance(
+    walletPublicKey: string,
+    tokenPublicKey: string
+  ): Promise<string> {
+    const provider = await this.getProvider()
+    const token = await provider.getTokenAccountsByOwner(
+      new PublicKey(walletPublicKey),
+      {
+        mint: new PublicKey(tokenPublicKey),
+      }
+    )
+    if (token.value.length === 0) {
+      return '0'
+    }
+
+    const balance = await provider.getTokenAccountBalance(token.value[0].pubkey)
+    if (!balance) {
+      return '0'
+    } else {
+      return balance.value.amount.toString()
+    }
+  }
+
+  generateAccount(): Keypair {
+    const account = Keypair.generate()
+    return account
+  }
+
+  combineTransactions(...transactions: Transaction[]): Transaction {
+    const totalTxs = new Transaction()
+    for (const tx of transactions) {
+      totalTxs.add(tx)
+    }
+    return totalTxs
+  }
+
+  makePriorityFeesTx(tx: Transaction, priorityFees?: PriorityFee): Transaction {
+    if (!priorityFees) return tx
+    const newTx = new Transaction()
+
+    if (priorityFees.unitLimit) {
+      const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
+        units: priorityFees.unitLimit,
+      })
+      newTx.add(modifyComputeUnits)
+    }
+    if (priorityFees.unitPrice) {
+      const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
+        microLamports: priorityFees.unitPrice,
+      })
+      newTx.add(addPriorityFee)
+    }
+
+    newTx.add(tx)
+    return newTx
+  }
+
+  makeTransferTx(
+    payer: PublicKey,
+    recipient: PublicKey,
+    lamports: bigint
+  ): Transaction {
+    const newTx = new Transaction()
+    newTx.add(
+      SystemProgram.transfer({
+        fromPubkey: payer,
+        toPubkey: recipient,
+        lamports,
+      })
+    )
+    return newTx
+  }
+
+  async signTx(
+    tx: Transaction,
+    payer: PublicKey,
+    signers: Keypair[],
+    blockHash: string,
+    priorityFees?: PriorityFee
+  ): Promise<VersionedTransaction> {
+    const newTx = this.makePriorityFeesTx(tx, priorityFees)
+
+    const versionedTx = await this.buildVersionedTx(payer, newTx, blockHash)
+    versionedTx.sign(signers)
+    return versionedTx
+  }
+
+  async sendTx(
+    tx: Transaction,
+    payer: PublicKey,
+    signers: Keypair[],
+    priorityFees?: PriorityFee,
+    commitment: Commitment = 'confirmed',
+    finality: Finality = 'confirmed'
+  ): Promise<TransactionResult> {
+    const connection = await this.getProvider()
+    const blockHash = (await connection.getLatestBlockhash(commitment))
+      .blockhash
+
+    console.log('Blockhash:', blockHash)
+    // const versionedTx = await this.signTx(
+    //   tx,
+    //   payer,
+    //   signers,
+    //   blockHash,
+    //   priorityFees
+    // )
+
+    // throw new Error('Transaction failed to sign')
+
+    const feeedTx = this.makePriorityFeesTx(tx, priorityFees)
+
+    try {
+      const sig = await sendAndConfirmTransaction(
+        connection,
+        feeedTx,
+        signers
+        // {
+        //   skipPreflight: false,
+        //   commitment: 'confirmed',
+        // }
+      )
+      console.log('sig:', `https://solscan.io/tx/${sig}`)
+      // const sig = await connection.sendTransaction(versionedTx, {
+      //   skipPreflight: false,
+      // })
+      // console.log('sig:', `https://solscan.io/tx/${sig}`)
+
+      // const txResult = await this.getTxDetails(
+      //   connection,
+      //   sig,
+      //   commitment,
+      //   finality
+      // )
+      // if (!txResult) {
+      //   return {
+      //     success: false,
+      //     error: 'Transaction failed',
+      //   }
+      // }
+      return {
+        success: true,
+        signature: sig,
+        // results: txResult,
+      }
+    } catch (e) {
+      if (e instanceof SendTransactionError) {
+        const ste = e
+        console.log(await ste.getLogs(connection))
+      } else {
+        console.error(e)
+      }
+      return {
+        error: e,
+        success: false,
+      }
+    }
+  }
+
+  async buildVersionedTx(
+    payer: PublicKey,
+    tx: Transaction,
+    blockHash: string
+  ): Promise<VersionedTransaction> {
+    const messageV0 = new TransactionMessage({
+      payerKey: payer,
+      recentBlockhash: blockHash,
+      instructions: tx.instructions,
+    }).compileToV0Message()
+
+    return new VersionedTransaction(messageV0)
+  }
+
+  async getTxDetails(
+    connection: Connection,
+    sig: string,
+    commitment: Commitment = 'confirmed',
+    finality: Finality = 'confirmed'
+  ): Promise<VersionedTransactionResponse | null> {
+    const latestBlockHash = await connection.getLatestBlockhash()
+    await connection.confirmTransaction(
+      {
+        blockhash: latestBlockHash.blockhash,
+        lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
+        signature: sig,
+      },
+      commitment
+    )
+
+    return await connection.getTransaction(sig, {
+      maxSupportedTransactionVersion: 0,
+      commitment: finality,
+    })
+  }
+
+  async getRecentPriorityFees(
+    programId?: string,
+    numBlocks = 10
+  ): Promise<PriorityFeeInfo> {
+    try {
+      const connection = await this.getProvider()
+      const latestBlockheight = await connection.getSlot('finalized')
+      // 获取最近的区块生产时间数据
+      const blocks = await connection.getBlocks(latestBlockheight - numBlocks)
+
+      const priorityFees: Array<number[] | null> = []
+
+      const bss: number[][] = []
+
+      for (let i = 0; i < Math.ceil(numBlocks / 10); i++) {
+        const bs = blocks.slice(i * 10, (i + 1) * 10)
+        bss.push(bs)
+      }
+
+      let doneCount = 0
+
+      const checkDone = (): boolean => {
+        return doneCount === bss.length
+      }
+
+      const getFees = async (bs: number[]): Promise<void> => {
+        // 获取这些区块的优先费用数据
+        const fees = await Promise.all(
+          bs.map(async (block) => {
+            try {
+              // const blockProdTime = await connection.getBlockTime(block)
+              // if (!blockProdTime) return null
+
+              const blockDetails = await connection.getBlock(block, {
+                maxSupportedTransactionVersion: 0,
+              })
+
+              if (!blockDetails?.transactions) return null
+
+              const txs = blockDetails.transactions.flat()
+              const programedTxs = txs.filter((tx) => {
+                if (!programId) return true
+
+                // 检查交易中的程序ID
+                // if (!tx.transaction.message.accountKeys) return false
+
+                const accountKeys = ((tx.transaction.message as any)
+                  .accountKeys ?? []) as PublicKey[]
+                const accountKeyArr = accountKeys.map((key) => key.toBase58())
+                const containsTargetProgram = accountKeyArr.some(
+                  (key) => key === programId
+                )
+                return containsTargetProgram
+              })
+
+              // 提取所有交易的优先费用
+              return programedTxs
+                .map((tx) => {
+                  const priorityFeesSol = tx.meta?.fee
+                    ? (tx.meta.fee - 5000) / LAMPORTS_PER_SOL // 减去基础费用 5000 lamports
+                    : 0
+                  return priorityFeesSol > 0 ? priorityFeesSol : 0
+                })
+                .filter((fee) => fee > 0)
+            } catch (error) {
+              console.error(`Error processing block ${block}:`, error)
+              return null
+            }
+          })
+        )
+
+        priorityFees.push(...fees)
+
+        console.log('done += 1', doneCount)
+        doneCount += 1
+      }
+
+      for (const bs of bss) {
+        getFees(bs).catch((error) => console.error(error.message))
+        await sleep(200)
+      }
+
+      while (!checkDone()) {
+        console.log(doneCount)
+        await sleep(200)
+      }
+
+      console.log('length:', priorityFees)
+
+      // 将所有费用合并到一个数组
+      const allFees = priorityFees
+        .filter((fees): fees is number[] => fees !== null)
+        .flat()
+
+      if (allFees.length === 0) {
+        return {
+          median: 0,
+          average: 0,
+          max: 0,
+          min: 0,
+          numSamples: 0,
+        }
+      }
+
+      // 计算统计数据
+      allFees.sort((a, b) => a - b)
+      const median = allFees[Math.floor(allFees.length / 2)]
+      const average = allFees.reduce((a, b) => a + b) / allFees.length
+      const max = Math.max(...allFees)
+      const min = Math.min(...allFees)
+
+      return {
+        median,
+        average,
+        max,
+        min,
+        numSamples: allFees.length,
+      }
+    } catch (error) {
+      console.error('Error fetching priority fees:', error)
+      throw error
+    }
+  }
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác