Bladeren bron

bn wallet

helium3@sina.com 7 maanden geleden
bovenliggende
commit
6743f69f11

+ 3 - 0
package.json

@@ -11,6 +11,7 @@
     "build": "npm run babel",
     "lint": "eslint src",
     "start": "npm run babel && node -r dotenv/config lib/start.js",
+    "monitor": "npm run babel && node -r dotenv/config lib/monitor/bnTaskMonitor.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",
@@ -76,6 +77,7 @@
     "multer": "^1.4.5-lts.1",
     "mysql2": "^3.11.5",
     "nanoid": "3",
+    "node-cron": "^3.0.3",
     "node-fetch": "^3.3.2",
     "node-global-proxy": "^1.0.1",
     "node-telegram-bot-api": "^0.63.0",
@@ -119,6 +121,7 @@
     "@types/morgan": "^1.9.3",
     "@types/multer": "^1.4.7",
     "@types/node": "^16.11.6",
+    "@types/node-cron": "^3.0.11",
     "@types/node-telegram-bot-api": "^0.61.8",
     "@types/seedrandom": "^3.0.5",
     "@types/uuid": "^8.3.1",

+ 66 - 0
src/controllers/bn/index.ts

@@ -0,0 +1,66 @@
+import { Request, RequestHandler, NextFunction, Router } from 'express'
+import { Controller } from '../types'
+import jsonResponseMiddleware, {
+  JsonResponse,
+} from '../../middleware/jsonResponse.middleware'
+import { PaginationConnection } from 'sequelize-cursor-pagination'
+import { MODIFY_TOKEN } from '../../constants'
+import tiktokService, {
+  GenerateTiktokResult,
+  ShotData,
+} from '../../services/tiktokService'
+import { TiktokData } from '../../db/models/Tiktok'
+import { PaginationData } from '../../services/types'
+import bnService, { BnData, BnRequest } from '../../services/bnService'
+
+export default class BnTask implements Controller {
+  public path = '/api/v1'
+  public router = Router()
+
+  constructor() {
+    this.initializeRoutes()
+  }
+
+  private initializeRoutes(): void {
+    this.router.get(
+      '/task/completion',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.searchAddress as RequestHandler
+    )
+    this.router.get(
+      '/time',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.time as RequestHandler
+    )
+  }
+
+  private time(
+    request: Request<any, any, any, { after?: string }>,
+    response: JsonResponse<BnData>,
+    next: NextFunction
+  ): void {
+    const currentTimestamp = Date.now()
+    response.status(200).jsonSuccess({
+      code: '000000',
+      message: 'success',
+      data: currentTimestamp.toString(),
+    })
+  }
+
+  private searchAddress(
+    request: Request<any, any, any, { after?: string }>,
+    response: JsonResponse<BnData>,
+    next: NextFunction
+  ): void {
+    bnService
+      .searchStatus(request.query as BnRequest)
+      .then((bnData) => {
+        response.status(200).jsonSuccess(bnData)
+      })
+      .catch((e) => {
+        response.status(500).jsonError('Server Error', 1010)
+      })
+  }
+}

+ 2 - 0
src/controllers/index.ts

@@ -4,9 +4,11 @@ import TweetController from './tweet'
 import ScoreController from './score'
 import ArtistController from './artist'
 import TiktokController from './tiktok'
+import BnController from './bn'
 export * from './types'
 
 export {
+  BnController,
   PingpongController,
   XController,
   TweetController,

+ 25 - 4
src/db/index.ts

@@ -1,16 +1,26 @@
 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 { DB_HOST, DB_NAME, DB_PASS, DB_PORT, DB_USER } from '../constants'
 // import { promisify } from 'util'
 
-const { Constant, Application, Tweet, Score, Art, Tiktok } = Models
+const {
+  Constant,
+  Application,
+  Tweet,
+  Score,
+  Art,
+  Tiktok,
+  BnMonitor,
+  BnWhiteList,
+  BnTransaction,
+} = Models
 
 const sequelize = new Sequelize({
   host: DB_HOST,
   port: DB_PORT ? Number.parseInt(DB_PORT) : 3306,
   database: DB_NAME,
-  dialect: 'mariadb',
+  dialect: 'mysql',
   username: DB_USER,
   password: DB_PASS,
   dialectOptions: {
@@ -21,7 +31,18 @@ const sequelize = new Sequelize({
   pool: {
     max: 25,
   },
-  models: [Constant, RPC, Application, Tweet, Score, Art, Tiktok],
+  models: [
+    Constant,
+    RPC,
+    Application,
+    Tweet,
+    Score,
+    Art,
+    Tiktok,
+    BnMonitor,
+    BnWhiteList,
+    BnTransaction,
+  ],
 })
 
 export default sequelize

+ 36 - 0
src/db/models/BnMonitor.ts

@@ -0,0 +1,36 @@
+import { AllowNull, Column, DataType, Model, Table } from 'sequelize-typescript'
+
+export interface BnMonitorData {
+  chainId: bigint
+  syncBlockNumber: number
+}
+
+@Table({
+  modelName: 'BnMonitor',
+  indexes: [
+    {
+      fields: ['chainId'],
+      unique: true,
+    },
+  ],
+})
+export default class BnMonitor extends Model {
+  @AllowNull(false)
+  @Column(DataType.INTEGER.UNSIGNED)
+  get chainId(): bigint {
+    return this.getDataValue('chainId')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.INTEGER.UNSIGNED)
+  get syncBlockNumber(): number {
+    return this.getDataValue('syncBlockNumber')
+  }
+
+  getData(): BnMonitorData {
+    return {
+      chainId: this.chainId,
+      syncBlockNumber: this.syncBlockNumber,
+    }
+  }
+}

+ 28 - 0
src/db/models/BnWhiteList.ts

@@ -0,0 +1,28 @@
+import { AllowNull, Column, DataType, Model, Table } from 'sequelize-typescript'
+
+export interface BnWhiteListData {
+  address: string
+}
+
+@Table({
+  modelName: 'BnWhiteList',
+  indexes: [
+    {
+      fields: ['address'],
+      unique: true,
+    },
+  ],
+})
+export default class BnWhiteList extends Model {
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get address(): string {
+    return this.getDataValue('address')
+  }
+
+  getData(): BnWhiteListData {
+    return {
+      address: this.address,
+    }
+  }
+}

+ 78 - 0
src/db/models/TransferInfo.ts

@@ -0,0 +1,78 @@
+import { AllowNull, Column, DataType, Model, Table } from 'sequelize-typescript'
+
+export interface BnTransactionData {
+  id: number
+  hash: string
+  from: string
+  to: string
+  maker: string
+  amount: string
+  block: number
+  type: number
+}
+
+@Table({
+  modelName: 'BnTransaction',
+  indexes: [
+    {
+      fields: ['id'],
+      unique: true,
+    },
+  ],
+})
+export default class BnTransaction extends Model {
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get hash(): string {
+    return this.getDataValue('hash')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get from(): string {
+    return this.getDataValue('from')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get to(): string {
+    return this.getDataValue('to')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get maker(): string {
+    return this.getDataValue('maker')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.STRING)
+  get amount(): string {
+    return this.getDataValue('amount')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.INTEGER.UNSIGNED)
+  get block(): number {
+    return this.getDataValue('number')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.INTEGER.UNSIGNED)
+  get type(): number {
+    return this.getDataValue('type')
+  }
+
+  getData(): BnTransactionData {
+    return {
+      id: this.id,
+      hash: this.hash,
+      from: this.from,
+      to: this.to,
+      maker: this.maker,
+      amount: this.amount,
+      block: this.block,
+      type: this.type,
+    }
+  }
+}

+ 14 - 2
src/db/models/index.ts

@@ -4,5 +4,17 @@ import Tweet from './Tweet'
 import Score from './Score'
 import Art from './Art'
 import Tiktok from './Tiktok'
-
-export { Constant, Application, Tweet, Score, Art, Tiktok }
+import BnMonitor from './BnMonitor'
+import BnWhiteList from './BnWhiteList'
+import BnTransaction from './TransferInfo'
+export {
+  Constant,
+  Application,
+  Tweet,
+  Score,
+  Art,
+  Tiktok,
+  BnMonitor,
+  BnWhiteList,
+  BnTransaction,
+}

+ 2 - 0
src/index.ts

@@ -10,6 +10,7 @@ import errorMiddleware from './middleware/error.middleware'
 import setHeaderMiddleware from './middleware/setHeaders.middleware'
 import { Server } from 'http'
 import 'moment-timezone'
+import { bnMonitor } from './monitor/bnTaskMonitor'
 // import { Constant } from './db/models'
 // import { defaultSolanaHelper } from './utils/SolanaHelper'
 
@@ -88,6 +89,7 @@ export default class APP {
     this.initializeErrorHandling()
     await this.initializeDatabase()
     await this.initializeConstants()
+    // await bnMonitor()
     // await defaultSolanaHelper.getProvider()
   }
 

+ 145 - 0
src/monitor/bnTaskMonitor.ts

@@ -0,0 +1,145 @@
+import cron from 'node-cron'
+// init dotenv
+import dotenv from 'dotenv'
+import { ethers, TransactionResponse } from 'ethers6'
+import BnMonitor from '../db/models/BnMonitor'
+import BnWhiteList from '../db/models/BnWhiteList'
+import sequelize from '../db'
+import BnTransaction from '../db/models/TransferInfo'
+
+dotenv.config()
+let networkInfo: ethers.Network
+const provider = new ethers.JsonRpcProvider('https://binance.llamarpc.com')
+let monitorRunning = false
+export async function bnMonitor(): Promise<void> {
+  await initMonitor()
+  const blockMonitorTask = cron.schedule(
+    '* * * * * *',
+    () => {
+      if (monitorRunning) return // 如果正在执行,则跳过当前任务
+      monitorRunning = true
+      startMonitor()
+        .catch((err) => {
+          console.error('Monitor error occurred:', err)
+        })
+        .finally(() => {
+          monitorRunning = false
+        })
+    },
+    { runOnInit: true }
+  )
+
+  // stop cron job when server is stopped
+  process.on('SIGINT', () => {
+    blockMonitorTask.stop()
+    process.exit(0)
+  })
+}
+
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+async function startMonitor() {
+  console.log(`current time: ${new Date().toLocaleString()}`)
+  const onchainBlockNumber = await provider.getBlockNumber()
+  const monitorStatus = await BnMonitor.findOne({
+    where: { chainId: networkInfo.chainId },
+  })
+  if (!monitorStatus) {
+    throw new Error('create monitor raw first')
+  }
+  // 上一次的块号
+  const syncBlockNumber = monitorStatus.syncBlockNumber
+  // 下一个块号
+  const nextBlockNumber = syncBlockNumber + 1
+
+  // 如果下一个块号小于当前链上块号,说明有新块产生
+  if (nextBlockNumber < onchainBlockNumber) {
+    const blockInfo = await provider.getBlock(nextBlockNumber, true)
+    if (!blockInfo) {
+      console.warn(
+        'No block information found for block number:',
+        nextBlockNumber
+      )
+      return
+    }
+    const whiteList = await BnWhiteList.findAll()
+    const addressList: string[] = whiteList.map((item) => item.address)
+    const transactions: TransactionResponse[] = blockInfo.prefetchedTransactions
+
+    const transferTransaction = transactions.filter(
+      (tx) => addressList.includes(tx.to!) || addressList.includes(tx.from)
+    )
+    const transferInfo = transferTransaction
+      .map((tx) => {
+        if (tx.data === '0x') {
+          return {
+            from: tx.from,
+            to: tx.to!,
+            amount: tx.value.toString(),
+            hash: tx.hash,
+            maker: tx.to!,
+            block: tx.blockNumber!,
+            type: 0,
+          }
+        } else if (tx.to === '0x1A0A18AC4BECDDbd6389559687d1A73d8927E416') {
+          return {
+            from: tx.from,
+            to: tx.to,
+            amount: tx.value.toString(),
+            hash: tx.hash,
+            maker: tx.from,
+            block: tx.blockNumber!,
+            type: 1,
+          }
+        }
+        return null
+      })
+      .filter((tx) => tx !== null)
+    if (transferInfo.length > 0) {
+      await sequelize.transaction(async (tx) => {
+        // 更新监控状态
+        await BnMonitor.update(
+          { syncBlockNumber: nextBlockNumber },
+          { where: { chainId: networkInfo.chainId }, transaction: tx }
+        )
+
+        // 批量插入交易信息
+        await BnTransaction.bulkCreate(transferInfo as any[], {
+          ignoreDuplicates: true,
+          transaction: tx,
+        })
+      })
+      console.log(`Processed block number: ${nextBlockNumber}`)
+    } else {
+      await BnMonitor.update(
+        { syncBlockNumber: nextBlockNumber },
+        { where: { chainId: networkInfo.chainId } }
+      )
+      console.log(
+        `No relevant transactions found in block number: ${nextBlockNumber}`
+      )
+    }
+  } else {
+    console.log('No new block found')
+  }
+}
+// 45322143
+async function initMonitor(): Promise<void> {
+  const blockNumber = await provider.getBlockNumber()
+  networkInfo = await provider.getNetwork()
+
+  await BnMonitor.findOrCreate({
+    where: {
+      chainId: networkInfo.chainId,
+    },
+    defaults: {
+      chainId: networkInfo.chainId,
+      syncBlockNumber: blockNumber,
+    },
+  })
+
+  console.log('monitor status for chainId: %s created', networkInfo.chainId)
+}
+
+bnMonitor()
+  .then((r) => console.log(r))
+  .catch((e) => console.error(e))

+ 73 - 0
src/services/bnService/index.ts

@@ -0,0 +1,73 @@
+import { BnTransaction, BnWhiteList } from '../../db/models'
+
+export interface BnData {
+  code: string
+  message: string
+  data: TaskStatus | null | string
+}
+export interface TaskStatus {
+  eligibility?: boolean
+  deposit?: boolean
+  pancake?: boolean
+}
+export interface BnRequest {
+  walletAddress: string
+  task: string[]
+  recvWindow: string
+  timestamp: string
+}
+
+async function searchStatus(option: Partial<BnRequest> = {}): Promise<BnData> {
+  console.log('searchStatus', option)
+  const address = option.walletAddress
+  const data: TaskStatus = {}
+  if (!address) {
+    return {
+      code: '000006',
+      message: 'invalid argument',
+      data: null,
+    }
+  }
+  const taskList =
+    option.task && option.task.length > 0
+      ? option.task
+      : ['eligibility', 'deposit', 'pancake']
+
+  if (taskList.includes('eligibility')) {
+    const inWhiteList = await BnWhiteList.findOne({
+      where: {
+        address,
+      },
+    })
+    data.eligibility = !!inWhiteList
+  }
+  if (taskList.includes('deposit')) {
+    const deposit = await BnTransaction.findOne({
+      where: {
+        maker: address,
+        type: 0,
+      },
+    })
+    data.deposit = !!deposit
+  }
+  if (taskList.includes('pancake')) {
+    const pancake = await BnTransaction.findOne({
+      where: {
+        maker: address,
+        type: 1,
+      },
+    })
+    data.pancake = !!pancake
+  }
+  return {
+    code: '000000',
+    message: 'success',
+    data,
+  }
+}
+
+const bnService = {
+  searchStatus,
+}
+
+export default bnService

+ 2 - 0
src/start.ts

@@ -7,6 +7,7 @@ import {
   TiktokController,
   ScoreController,
   ArtistController,
+  BnController,
 } from './controllers'
 import { PORT, TG_BOT_TOKEN } from './constants'
 
@@ -21,6 +22,7 @@ const app = new APP({
     new TweetController(),
     new ScoreController(),
     new ArtistController(),
+    new BnController(),
   ],
   tgBotToken: TG_BOT_TOKEN,
 })

File diff suppressed because it is too large
+ 362 - 199
yarn.lock


Some files were not shown because too many files changed in this diff