Alex Xu 8 meses atrás
pai
commit
a678292de6

+ 3 - 1
package.json

@@ -39,7 +39,7 @@
     "@uniswap/v3-core": "^1.0.1",
     "@uniswap/v3-sdk": "^3.9.0",
     "alchemy-sdk": "^2.8.0",
-    "axios": "^0.24.0",
+    "axios": "^1.7.9",
     "bs58": "^5.0.0",
     "cookie-parser": "^1.4.5",
     "cron": "^2.3.1",
@@ -54,6 +54,7 @@
     "ethers": "^5.4.4",
     "ethers6": "npm:ethers@^6.3.0",
     "eventemitter3": "^5.0.1",
+    "eventsource-parser": "^3.0.0",
     "express": "^4.17.1",
     "express-http-proxy": "^2.0.0",
     "express-winston": "^4.2.0",
@@ -74,6 +75,7 @@
     "morgan": "^1.10.0",
     "multer": "^1.4.5-lts.1",
     "nanoid": "3",
+    "node-fetch": "^3.3.2",
     "node-global-proxy": "^1.0.1",
     "node-telegram-bot-api": "^0.63.0",
     "qrcode-terminal": "^0.12.0",

+ 2 - 1
src/controllers/index.ts

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

+ 125 - 0
src/controllers/score/index.ts

@@ -0,0 +1,125 @@
+import {
+  Request,
+  RequestHandler,
+  NextFunction,
+  Router,
+  Response,
+} from 'express'
+import { Controller } from '../types'
+import scoreService from '../../services/scoreService'
+import { CompletionMessagesPayload } from '../../services/difyService'
+import jsonResponseMiddleware, {
+  JsonResponse,
+} from '../../middleware/jsonResponse.middleware'
+import { PaginationConnection } from 'sequelize-cursor-pagination'
+import { ScoreData } from '../../db/models/Score'
+// import apiKeyMiddleware from '../../middleware/apikey.middleware'
+
+type MakeScorePayload = Omit<CompletionMessagesPayload, 'onUpdate'>
+
+export default class ScoreController implements Controller {
+  public path = '/api/v1/score'
+  public router = Router()
+
+  constructor() {
+    this.initializeRoutes()
+  }
+
+  private initializeRoutes(): void {
+    this.router.post(
+      '/make',
+      // apiKeyMiddleware(),
+      // jsonResponseMiddleware,
+      this.make as RequestHandler
+    )
+    this.router.get(
+      '/list',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.list as RequestHandler
+    )
+    this.router.get(
+      '/id/:id',
+      // apiKeyMiddleware(),
+      jsonResponseMiddleware,
+      this.byId as RequestHandler
+    )
+  }
+
+  private make(
+    request: Request<any, any, MakeScorePayload>,
+    response: Response,
+    next: NextFunction
+  ): void {
+    response.writeHead(200, {
+      'Content-Type': 'text/event-stream',
+      'Cache-Control': 'no-cache',
+      Connection: 'keep-alive',
+    })
+
+    const onUpdate = (chunk: string): void => {
+      response.write(chunk)
+    }
+
+    scoreService
+      .getCompletionMessages({
+        ...request.body,
+        onUpdate,
+      })
+      .then((row) => {
+        const rowData = row.getData()
+        const data: any = { event: 'db_row', ...rowData }
+        const message = JSON.stringify(data)
+        onUpdate(`data: ${message}\n\n`)
+        response.end()
+      })
+      .catch((e) => {
+        const data: any = {
+          event: 'error',
+          signature: request.body.signature,
+          message: e.message,
+        }
+        const message = JSON.stringify(data)
+        onUpdate(`data: ${message}\n\n`)
+        response.end()
+      })
+  }
+
+  private list(
+    request: Request<any, any, any, { after?: string }>,
+    response: JsonResponse<PaginationConnection<ScoreData>>,
+    next: NextFunction
+  ): void {
+    const after = request.query.after
+    scoreService
+      .paginateScoreByOrder(after)
+      .then((scores) => {
+        response.jsonSuccess(scores)
+      })
+      .catch((e) => {
+        console.log(e)
+        response.status(500).jsonError('Server Error', 1010)
+      })
+  }
+
+  private byId(
+    request: Request<{ id: string }>,
+    response: JsonResponse<ScoreData>,
+    next: NextFunction
+  ): void {
+    const { id } = request.params
+    scoreService
+      .getScoreById(parseInt(id))
+      .then((score) => {
+        response.jsonSuccess(score.getData())
+      })
+      .catch((e) => {
+        console.log(e)
+        if (e.message === 'Score not found') {
+          response.status(404).jsonError('Score not found', 1011)
+          return
+        }
+        response.status(500).jsonError('Server Error', 1010)
+      })
+  }
+}

+ 2 - 2
src/db/index.ts

@@ -4,7 +4,7 @@ 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 { Constant, Application, Tweet, Score } = Models
 
 const sequelize = new Sequelize({
   host: DB_HOST,
@@ -21,7 +21,7 @@ const sequelize = new Sequelize({
   pool: {
     max: 25,
   },
-  models: [Constant, RPC, Application, Tweet],
+  models: [Constant, RPC, Application, Tweet, Score],
 })
 
 export default sequelize

+ 2 - 12
src/db/models/Constant.ts

@@ -9,12 +9,7 @@ import {
 } from 'sequelize-typescript'
 import { ConstantData, ConstantKind, ConstantType } from './types'
 
-export type ConstantNames =
-  | 'singleTxBananaLimit'
-  | 'sleepTime'
-  | 'tradeLimitMax'
-  | 'tradeLimitMin'
-  | 'xBotToken'
+export type ConstantNames = 'difyTimespanLimit'
 export type ConstantDataValue = string | number | boolean | object
 
 export interface BindedUser {
@@ -24,12 +19,7 @@ export interface BindedUser {
 
 /* 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 :
+    T extends 'difyTimespanLimit' ? number :
     never;
 /* eslint-enable */
 

+ 105 - 0
src/db/models/Score.ts

@@ -0,0 +1,105 @@
+import {
+  Table,
+  Column,
+  AllowNull,
+  DataType,
+  Model,
+  Default,
+} from 'sequelize-typescript'
+import crypto from 'crypto'
+
+export interface ScoreData {
+  id: number
+  signature: string
+  query: string
+  scoreId: string
+  answer: string
+  messageId: string
+  score: number | null
+  scoreText: string | null
+}
+
+const scoreReg = /((-?\d+(.\d+)?)|(-?[∞π]))🍌/
+
+@Table({
+  modelName: 'score',
+  indexes: [
+    {
+      fields: ['scoreId'],
+      unique: true,
+    },
+    {
+      fields: ['messageId'],
+    },
+  ],
+})
+export default class Score extends Model {
+  @AllowNull(false)
+  @Column(DataType.CHAR(255))
+  get signature(): string {
+    return this.getDataValue('signature')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.TEXT)
+  get query(): string {
+    return this.getDataValue('query')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.CHAR(64))
+  get scoreId(): string {
+    return this.getDataValue('scoreId')
+  }
+
+  static getScoreId(query: string): string {
+    const cleanQuery = query.trim().toLowerCase().replaceAll('\n', '')
+    const hash = crypto.createHash('md5').update(cleanQuery).digest('hex')
+    return hash
+  }
+
+  @AllowNull(false)
+  @Column(DataType.TEXT)
+  get answer(): string {
+    return this.getDataValue('answer')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.CHAR(100))
+  get messageId(): string {
+    return this.getDataValue('messageId')
+  }
+
+  @AllowNull(false)
+  @Column(DataType.DECIMAL(32, 16))
+  get score(): number | null {
+    return this.getDataValue('score')
+  }
+
+  static getScore(answer: string): [string | null, number | null] {
+    const match = answer.match(scoreReg)
+    if (!match) return [null, null]
+    const scoreText = match[1]
+    const score = parseFloat(scoreText)
+    return [scoreText, isNaN(score) ? null : score]
+  }
+
+  @AllowNull(false)
+  @Column(DataType.TEXT)
+  get scoreText(): string | null {
+    return this.getDataValue('scoreText')
+  }
+
+  getData(): ScoreData {
+    return {
+      id: this.id,
+      signature: this.signature,
+      query: this.query,
+      scoreId: this.scoreId,
+      answer: this.answer,
+      messageId: this.messageId,
+      score: this.score,
+      scoreText: this.scoreText,
+    }
+  }
+}

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

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

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

@@ -0,0 +1,169 @@
+import axios from 'axios'
+import { createParser, type EventSourceMessage } from 'eventsource-parser'
+import { Constant, Score } from '../../db/models'
+import { sleep } from '../../utils'
+import { timeNumber } from '../../utils/time'
+
+const { DIFY_ENDPOINT, DIFY_API_KEY } = process.env
+
+if (!DIFY_ENDPOINT || !DIFY_API_KEY) {
+  console.error(
+    new Error('Missing DIFY_ENDPOINT or DIFY_API_KEY environment variables')
+  )
+  process.exit(1)
+}
+
+const baseURL = `${DIFY_ENDPOINT}`
+const caller = axios.create({
+  baseURL,
+  headers: {
+    Authorization: `Bearer ${DIFY_API_KEY}`,
+  },
+  adapter: 'fetch',
+})
+
+async function getCompletionMessages(query: string): Promise<void> {
+  caller
+    .post(
+      '/completion-messages',
+      {
+        inputs: { query },
+        response_mode: 'streaming',
+        user: 'abc-123',
+      },
+      {
+        headers: {
+          Accept: 'text/event-stream',
+        },
+      }
+    )
+    .then(async (response) => {
+      console.log('axios got a response')
+      console.log(response.data)
+      const stream = response.data
+      // consume response
+      const reader = stream.pipeThrough(new TextDecoderStream()).getReader()
+
+      const start = Date.now()
+      while (true) {
+        const { value, done } = await reader.read()
+        if (done) break
+        if (Date.now() - start > 5000) break
+        console.log(value)
+      }
+    })
+    .catch((e) => {
+      throw new Error(`Error in fetching completion messages: ${e}`)
+    })
+}
+
+export interface CompletionMessagesPayload {
+  query: string
+  onUpdate: (chunk: string) => void
+  signature: string
+}
+
+interface DifyResult {
+  messageId: string
+  answer: string
+}
+
+let lastAPICallTime = 0
+
+export class DifyRateLimitExceedError extends Error {
+  constructor(public serveAt: number, message = 'Rate limit exceeded') {
+    super(message)
+  }
+}
+
+async function getCompletionMessagesByFetchAPI(
+  payload: CompletionMessagesPayload
+): Promise<DifyResult> {
+  const difyTimespanLimit =
+    ((await Constant.get('difyTimespanLimit')) as number) ||
+    timeNumber.second * 30
+  if (Date.now() - lastAPICallTime < difyTimespanLimit) {
+    const e = new DifyRateLimitExceedError(difyTimespanLimit + lastAPICallTime)
+    throw e
+  }
+
+  const { query, onUpdate, signature } = payload
+
+  const response = await fetch(`${DIFY_ENDPOINT!}/completion-messages`, {
+    method: 'POST',
+    body: JSON.stringify({
+      inputs: { query },
+      response_mode: 'streaming',
+      user: 'abc-123',
+    }),
+    headers: {
+      'Content-Type': 'application/json',
+      Accept: 'text/event-stream',
+      Authorization: `Bearer ${DIFY_API_KEY!}`,
+    },
+  })
+  // Get the readable stream from the response body
+  const stream = response.body!
+  // Get the reader from the stream
+  const reader = stream.getReader()
+
+  let totalAnswer = ''
+  let totalMessageId = ''
+
+  function onEvent(eventMessage: EventSourceMessage): void {
+    try {
+      const obj = JSON.parse(eventMessage.data)
+      const { event, message_id: messageId, answer } = obj
+
+      const data: any = { event, messageId, signature }
+
+      if (data.event === 'message') {
+        data.answer = answer ?? ''
+        totalAnswer += data.answer as string
+      }
+
+      const message = JSON.stringify(data)
+
+      onUpdate(`data: ${message}\n\n`)
+
+      totalMessageId = messageId
+    } catch (error) {}
+  }
+
+  const parser = createParser({ onEvent })
+
+  const decoder = new TextDecoder()
+
+  // Define a function to read each chunk
+  const readChunk = async (): Promise<void> => {
+    // Read a chunk from the reader
+    const { value, done } = await reader.read()
+    // Check if the stream is done
+    if (done) {
+      // Return from the function
+      return
+    }
+    // Convert the chunk value to a string
+    const chunkString = decoder.decode(value)
+    // Log the chunk string
+    parser.feed(chunkString)
+    // Read the next chunk
+    await readChunk()
+  }
+  // Start reading the first chunk
+  await readChunk()
+
+  lastAPICallTime = new Date().getTime()
+
+  return {
+    messageId: totalMessageId,
+    answer: totalAnswer,
+  }
+}
+
+const difyService = {
+  getCompletionMessages,
+  getCompletionMessagesByFetchAPI,
+}
+
+export default difyService

+ 126 - 0
src/services/scoreService/index.ts

@@ -0,0 +1,126 @@
+import { Score } from '../../db/models'
+import { ScoreData } from '../../db/models/Score'
+import { sleep } from '../../utils'
+import difyService, {
+  CompletionMessagesPayload,
+  DifyRateLimitExceedError,
+} from '../difyService'
+import {
+  makePaginate,
+  PaginateOptions,
+  PaginationConnection,
+} from 'sequelize-cursor-pagination'
+
+async function getCompletionMessages(
+  payload: CompletionMessagesPayload
+): Promise<Score> {
+  const { query, onUpdate, signature } = payload
+  const scoreId = Score.getScoreId(query)
+  const exist = await Score.findOne({ where: { scoreId } })
+
+  if (exist) {
+    const { messageId } = exist
+
+    const words = exist.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 exist
+  }
+
+  try {
+    const { answer, messageId } =
+      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,
+      },
+    })
+    return row
+  } catch (e) {
+    if (e instanceof DifyRateLimitExceedError) {
+      const { serveAt } = e
+
+      const intime = Math.ceil((serveAt - Date.now()) / 1000)
+
+      throw new Error(
+        `Master is talking with other guys, please try after ${intime}s.`
+      )
+    }
+    throw e
+  }
+}
+
+// console.log(Tweet.rawAttributes)
+let paginate: (
+  this: unknown,
+  queryOptions: PaginateOptions<Score>
+) => Promise<PaginationConnection<Score>>
+
+async function paginateScoreByOrder(
+  after?: string,
+  limit = 10
+): Promise<PaginationConnection<ScoreData>> {
+  if (!paginate) {
+    paginate = makePaginate(Score)
+  }
+
+  const secondResult = await paginate({
+    order: [['id', 'DESC']],
+    limit,
+    after,
+  })
+
+  const ret: PaginationConnection<ScoreData> = {
+    ...secondResult,
+    edges: secondResult.edges.map((edge) => ({
+      cursor: edge.node.id,
+      node: edge.node.getData(),
+    })),
+  }
+
+  return ret
+}
+
+async function getScoreById(id: number): Promise<Score> {
+  const row = await Score.findByPk(id)
+
+  if (!row) {
+    throw new Error('Score not found')
+  }
+
+  return row
+}
+
+const scoreService = {
+  getCompletionMessages,
+  paginateScoreByOrder,
+  getScoreById,
+}
+
+export default scoreService

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

@@ -1,35 +0,0 @@
-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)
-}

+ 1 - 34
src/services/twitterService/index.ts

@@ -1,6 +1,5 @@
-import { Constant, Tweet } from '../../db/models'
+import { Tweet } from '../../db/models'
 import axios from 'axios'
-import { getAuthClient, getTwitterClient } from './clients'
 import {
   makePaginate,
   PaginateOptions,
@@ -10,34 +9,6 @@ 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
@@ -273,12 +244,8 @@ async function bulkImportShot(datas: ShotData[]): Promise<GenerateResult[]> {
 }
 
 const twitterService = {
-  getTwitterClient,
-  generateAuthURL,
   STATE,
   genenrateTweetByPublishAPI,
-  requestAccessToken,
-  refreshAccessToken,
   paginateTweetByOrder,
   updateTweet,
   bulkGenerateTweetsByPublishAPI,

+ 7 - 1
src/start.ts

@@ -1,6 +1,11 @@
 import APP from '.'
 import Decimal from 'decimal.js-light'
-import { PingpongController, XController, TweetController } from './controllers'
+import {
+  PingpongController,
+  XController,
+  TweetController,
+  ScoreController,
+} from './controllers'
 import { PORT, TG_BOT_TOKEN } from './constants'
 
 Decimal.set({ toExpPos: 999, toExpNeg: -999, precision: 64 })
@@ -11,6 +16,7 @@ const app = new APP({
     new PingpongController(),
     new XController(),
     new TweetController(),
+    new ScoreController(),
   ],
   tgBotToken: TG_BOT_TOKEN,
 })

+ 54 - 8
yarn.lock

@@ -3682,13 +3682,6 @@ axios@^0.21.2:
   dependencies:
     follow-redirects "^1.14.0"
 
-axios@^0.24.0:
-  version "0.24.0"
-  resolved "https://registry.yarnpkg.com/axios/-/axios-0.24.0.tgz#804e6fa1e4b9c5288501dd9dff56a7a0940d20d6"
-  integrity sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==
-  dependencies:
-    follow-redirects "^1.14.4"
-
 axios@^0.26.1:
   version "0.26.1"
   resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9"
@@ -3705,6 +3698,15 @@ axios@^1.1.3, axios@^1.3.5, axios@^1.6.8, axios@^1.7.2:
     form-data "^4.0.0"
     proxy-from-env "^1.1.0"
 
+axios@^1.7.9:
+  version "1.7.9"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.9.tgz#d7d071380c132a24accda1b2cfc1535b79ec650a"
+  integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==
+  dependencies:
+    follow-redirects "^1.15.6"
+    form-data "^4.0.0"
+    proxy-from-env "^1.1.0"
+
 babel-plugin-module-resolver@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/babel-plugin-module-resolver/-/babel-plugin-module-resolver-4.1.0.tgz#22a4f32f7441727ec1fbf4967b863e1e3e9f33e2"
@@ -4642,6 +4644,11 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
+data-uri-to-buffer@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
+  integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
+
 data-view-buffer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
@@ -5683,6 +5690,11 @@ eventemitter3@^4.0.7:
   resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
   integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
 
+eventsource-parser@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.0.tgz#9303e303ef807d279ee210a17ce80f16300d9f57"
+  integrity sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==
+
 execa@^8.0.1:
   version "8.0.1"
   resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c"
@@ -5858,6 +5870,14 @@ fastq@^1.6.0:
   dependencies:
     reusify "^1.0.4"
 
+fetch-blob@^3.1.2, fetch-blob@^3.1.4:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
+  integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
+  dependencies:
+    node-domexception "^1.0.0"
+    web-streams-polyfill "^3.0.3"
+
 figures@^1.7.0:
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
@@ -6013,7 +6033,7 @@ flatted@^3.2.9:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a"
   integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==
 
-follow-redirects@^1.14.0, follow-redirects@^1.14.4, follow-redirects@^1.14.8, follow-redirects@^1.15.0, follow-redirects@^1.15.6:
+follow-redirects@^1.14.0, follow-redirects@^1.14.8, follow-redirects@^1.15.0, follow-redirects@^1.15.6:
   version "1.15.9"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1"
   integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
@@ -6067,6 +6087,13 @@ form-data@~2.3.2:
     combined-stream "^1.0.6"
     mime-types "^2.1.12"
 
+formdata-polyfill@^4.0.10:
+  version "4.0.10"
+  resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
+  integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
+  dependencies:
+    fetch-blob "^3.1.2"
+
 formidable@^1.2.2:
   version "1.2.6"
   resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.6.tgz#d2a51d60162bbc9b4a055d8457a7c75315d1a168"
@@ -8293,6 +8320,11 @@ no-case@^3.0.4:
     lower-case "^2.0.2"
     tslib "^2.0.3"
 
+node-domexception@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
+  integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
+
 node-fetch@2, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.7.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
@@ -8300,6 +8332,15 @@ node-fetch@2, node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.7.0:
   dependencies:
     whatwg-url "^5.0.0"
 
+node-fetch@^3.3.2:
+  version "3.3.2"
+  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
+  integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
+  dependencies:
+    data-uri-to-buffer "^4.0.0"
+    fetch-blob "^3.1.4"
+    formdata-polyfill "^4.0.10"
+
 node-global-proxy@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/node-global-proxy/-/node-global-proxy-1.0.1.tgz#6d1f66ed60427cb3f70066f7da0b70c78a1547a4"
@@ -10875,6 +10916,11 @@ wcwidth@^1.0.1:
   dependencies:
     defaults "^1.0.3"
 
+web-streams-polyfill@^3.0.3:
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
+  integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
+
 web3-utils@^1.3.4:
   version "1.10.4"
   resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.10.4.tgz#0daee7d6841641655d8b3726baf33b08eda1cbec"