Alex Xu 7 miesięcy temu
rodzic
commit
d46aa61535

+ 15 - 1
src/db/models/Score.ts

@@ -10,6 +10,7 @@ import crypto from 'crypto'
 
 export interface ScoreData {
   id: number
+  address: string
   signature: string
   query: string
   scoreId: string
@@ -25,15 +26,27 @@ const scoreReg = /((-?\d+(.\d+)?)|(-?[∞π]))🍌/
   modelName: 'score',
   indexes: [
     {
-      fields: ['scoreId'],
+      fields: ['signature'],
       unique: true,
     },
+    {
+      fields: ['address'],
+    },
+    {
+      fields: ['scoreId'],
+    },
     {
       fields: ['messageId'],
     },
   ],
 })
 export default class Score extends Model {
+  @AllowNull(false)
+  @Column(DataType.CHAR(255))
+  get address(): string {
+    return this.getDataValue('address')
+  }
+
   @AllowNull(false)
   @Column(DataType.CHAR(255))
   get signature(): string {
@@ -93,6 +106,7 @@ export default class Score extends Model {
   getData(): ScoreData {
     return {
       id: this.id,
+      address: this.address,
       signature: this.signature,
       query: this.query,
       scoreId: this.scoreId,

+ 108 - 0
src/services/chainService/index.ts

@@ -0,0 +1,108 @@
+import {
+  JsonRpcProvider,
+  Interface,
+  FunctionFragment,
+  TransactionResponse,
+} from 'ethers6'
+import EVMHelper from '../../utils/EVMHelper'
+import { SupportedEVMHelperChains } from '../../utils/EVMHelper/types'
+import { BANANA_ADDRESS } from '../../constants'
+import Erc20ABI from '../../utils/EVMHelper/contracts/erc20ABI.json'
+import Decimal from 'decimal.js-light'
+import { tokenRealAmount } from '../../utils'
+
+const BLACKHOLE_ADDRESS = '0x000000000000000000000000000000000000dEaD'
+
+const evmHelper = new EVMHelper(SupportedEVMHelperChains.bsc)
+
+async function getRpc(): Promise<JsonRpcProvider> {
+  const provider = await evmHelper.getRpcProvider(process.env.BSC_RPC)
+  return provider
+}
+
+const interfaceBanana = new Interface(Erc20ABI)
+const transferFragment = interfaceBanana.fragments.find(
+  (f) => f.type === 'function' && (f as FunctionFragment).name === 'transfer'
+) as FunctionFragment
+
+interface CheckTxIsSendBananaToBlackholeResult {
+  tx: TransactionResponse | null
+  success: boolean
+  reason: number
+}
+
+async function checkTxIsSendBananaToBlackhole(
+  address: string,
+  txHash: string,
+  blackholeAddress = BLACKHOLE_ADDRESS
+): Promise<CheckTxIsSendBananaToBlackholeResult> {
+  const provider = await getRpc()
+  const tx = await provider.getTransaction(txHash)
+  if (!transferFragment) {
+    return {
+      tx: null,
+      success: false,
+      reason: -1,
+    }
+  }
+  if (!tx) {
+    return {
+      tx: null,
+      success: false,
+      reason: -2,
+    }
+  }
+  const { from, to, data } = tx
+  if (from?.toLowerCase() !== address.toLowerCase()) {
+    return {
+      tx,
+      success: false,
+      reason: -3,
+    }
+  }
+  if (to?.toLowerCase() !== BANANA_ADDRESS.toLowerCase()) {
+    return {
+      tx,
+      success: false,
+      reason: -3,
+    }
+  }
+  try {
+    const decodeData = interfaceBanana.decodeFunctionData(
+      transferFragment,
+      data
+    )
+    const [recipient, amount] = decodeData
+    if (recipient.toLowerCase() !== blackholeAddress.toLowerCase()) {
+      return {
+        tx,
+        success: false,
+        reason: -4,
+      }
+    }
+    if (new Decimal(amount.toString()).lt(tokenRealAmount('1'))) {
+      return {
+        tx,
+        success: false,
+        reason: -5,
+      }
+    }
+    return {
+      tx,
+      success: true,
+      reason: 1,
+    }
+  } catch (error) {
+    return {
+      tx,
+      success: false,
+      reason: -99,
+    }
+  }
+}
+
+const chainService = {
+  checkTxIsSendBananaToBlackhole,
+}
+
+export default chainService

+ 1 - 0
src/services/difyService/index.ts

@@ -61,6 +61,7 @@ export interface CompletionMessagesPayload {
   query: string
   onUpdate: (chunk: string) => void
   signature: string
+  address: string
 }
 
 interface DifyResult {

+ 58 - 13
src/services/scoreService/index.ts

@@ -1,6 +1,7 @@
 import { Score } from '../../db/models'
 import { ScoreData } from '../../db/models/Score'
 import { sleep } from '../../utils'
+import chainService from '../chainService'
 import difyService, {
   CompletionMessagesPayload,
   DifyRateLimitExceedError,
@@ -14,7 +15,43 @@ import {
 async function getCompletionMessages(
   payload: CompletionMessagesPayload
 ): Promise<Score> {
-  const { query, onUpdate, signature } = payload
+  const { query, onUpdate, signature, address } = payload
+
+  const signatureRowExist = await Score.findOne({ where: { signature } })
+
+  if (signatureRowExist) {
+    const { messageId } = signatureRowExist
+
+    const words = signatureRowExist.answer.split(' ').map((word) => ` ${word}`)
+    if (words[0]) {
+      words[0] = words[0].trim()
+    }
+
+    for (const word of words) {
+      const data = { event: 'message', messageId, signature, answer: word }
+
+      const message = JSON.stringify(data)
+
+      onUpdate(`data: ${message}\n\n`)
+      await sleep(20)
+    }
+
+    const data = { event: 'message_end', messageId, signature }
+
+    const message = JSON.stringify(data)
+
+    onUpdate(`data: ${message}\n\n`)
+
+    return signatureRowExist
+  }
+
+  const checkResult = await chainService.checkTxIsSendBananaToBlackhole(
+    address,
+    signature
+  )
+  if (!checkResult.success) {
+    throw new Error('Invalid transaction signature')
+  }
   const scoreId = Score.getScoreId(query)
   const exist = await Score.findOne({ where: { scoreId } })
 
@@ -41,7 +78,17 @@ async function getCompletionMessages(
 
     onUpdate(`data: ${message}\n\n`)
 
-    return exist
+    const row = await Score.create({
+      address,
+      signature,
+      scoreId: exist.scoreId,
+      query: exist.query,
+      answer: exist.answer,
+      messageId,
+      score: exist.score,
+      scoreText: exist.scoreText,
+    })
+    return row
   }
 
   try {
@@ -49,17 +96,15 @@ async function getCompletionMessages(
       await difyService.getCompletionMessagesByFetchAPI(payload)
 
     const [scoreText, score] = Score.getScore(answer)
-    const [row] = await Score.findOrCreate({
-      where: { scoreId },
-      defaults: {
-        signature,
-        scoreId,
-        query,
-        answer,
-        messageId,
-        score,
-        scoreText,
-      },
+    const row = await Score.create({
+      address,
+      signature,
+      scoreId,
+      query,
+      answer,
+      messageId,
+      score,
+      scoreText,
     })
     return row
   } catch (e) {

+ 7 - 1
src/utils/EVMHelper/EVMHelper.ts

@@ -5,7 +5,13 @@ import {
   SupportedEVMHelperChains,
 } from './types'
 import { RPC } from './dbModels'
-import { ethers, JsonRpcProvider, Provider, TransactionReceipt, Wallet } from 'ethers6'
+import {
+  ethers,
+  JsonRpcProvider,
+  Provider,
+  TransactionReceipt,
+  Wallet,
+} from 'ethers6'
 import axios from 'axios'
 
 function isPrivateKey(pk: string): boolean {