diff --git a/.github/workflows/bridge-explorer-develop-staging.yml b/.github/workflows/bridge-explorer-develop-staging.yml index 0bc38772..acc52c0b 100644 --- a/.github/workflows/bridge-explorer-develop-staging.yml +++ b/.github/workflows/bridge-explorer-develop-staging.yml @@ -18,36 +18,8 @@ permissions: contents: write # This is required for actions/checkout jobs: - security-audit: - name: Dependency Security Audit - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - - name: Enable Corepack - run: | - npm install -g corepack@0.34.6 - corepack enable - - - name: Setup Node - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 - with: - node-version-file: '.nvmrc' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run security audit - run: pnpm audit --audit-level=high - build-and-push: name: Build and Push to Artifact Registry - needs: security-audit runs-on: ubuntu-latest outputs: environment: ${{ steps.set-env.outputs.environment }} diff --git a/.github/workflows/publish-ghcr-image-release.yml b/.github/workflows/publish-ghcr-image-release.yml index 80a658df..3b6dd016 100644 --- a/.github/workflows/publish-ghcr-image-release.yml +++ b/.github/workflows/publish-ghcr-image-release.yml @@ -11,7 +11,34 @@ permissions: packages: write # This is required for pushing to GHCR jobs: + security-audit: + name: Dependency Security Audit + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Enable Corepack + run: | + npm install -g corepack@0.34.6 + corepack enable + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version-file: '.nvmrc' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run security audit + run: pnpm audit --audit-level=high + Build_and_push_image_to_GHCR: + needs: security-audit runs-on: ubuntu-latest steps: - name: Git clone the repository diff --git a/alerts/.dockerignore b/alerts/.dockerignore new file mode 100644 index 00000000..7210cb53 --- /dev/null +++ b/alerts/.dockerignore @@ -0,0 +1,106 @@ +# Dependencies +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage +.grunt + +# Bower dependency directory +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons +build/Release + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Runtime logs +logs +*.log + +# Build outputs (already copied from builder stage) +dist + +# Development files +src/**/*.test.ts +src/**/*.spec.ts +**/*.test.js +**/*.spec.js + +# IDE files +.vscode +.idea +*.swp +*.swo +*~ + +# OS files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore +.gitattributes + +# Docker +Dockerfile +docker-compose.yml +docker-compose.*.yml +.dockerignore + +# Documentation +README.md +*.md +docs/ + +# Alert state data (will be mounted as volume) +data/ + +# Monitoring config +monitoring/ \ No newline at end of file diff --git a/alerts/.env.example b/alerts/.env.example index e8aa4fea..a3561713 100644 --- a/alerts/.env.example +++ b/alerts/.env.example @@ -4,10 +4,36 @@ SLACK_TOKEN= # Slack Channel to send a message SLACK_CHANNEL= +# self-contained slack webhook, doesn't required SLACK_TOKEN & SLACK_CHANNEL if SLACK_WEBHOOK_URL is created +SLACK_WEBHOOK_URL= + # Providers URLs MAINNET_RPC_URL=https://rpc.ankr.com/eth GNOSIS_RPC_URL=https://rpc.ankr.com/gnosis -# Subgraph URLs -SUBGRAPH_API_NATIVE=https://api.thegraph.com/subgraphs/name/laimejesus/bridge-monitor-native -SUBGRAPH_API_FOREIGN=https://api.thegraph.com/subgraphs/name/laimejesus/bridge-monitor-foreign +# Envio Indexer URL +ENVIO_INDEXER_URL= + +## Validator balance +# true= only track validator balance on Gnosis Chain +IS_VALIDATOR_BALANCE_ON_GC=true +MIN_XDAI_BALANCE_THRESHOLD=1 # 1xdai +MIN_ETH_BALANCE_THRESHOLD=0 # set to 0 because the validator doesn't require balance to claim on Ethereum + +## Inactive validator +# max hrs to consider a validator inactive (not calling on chain function) +INACTIVITY_THRESHOLD_HOURS=12 + +## Stucked tx +# max hrs after first initiated a tx but still at 'COLLECTING' status +TRANSACTION_TIMEOUT_HOURS=1 + +# Scheduler Configuration +SCHEDULE_ENABLED=true +SCHEDULE_CRON=*/15 * * * * +RUN_ONCE_ON_START=true + +# Alert State Management +ALERT_STATE_FILE=./data/stuck-tx-alerts.json +ALERT_CLEANUP_HOURS=48 +NODE_ENV=PROD # or DEV \ No newline at end of file diff --git a/alerts/.gitignore b/alerts/.gitignore index 88ab1572..948c9aba 100644 --- a/alerts/.gitignore +++ b/alerts/.gitignore @@ -10,5 +10,10 @@ # custom logs /logs +# alert state data +/data + #types src/types/ + +/dist \ No newline at end of file diff --git a/alerts/Dockerfile b/alerts/Dockerfile new file mode 100644 index 00000000..9a43e3d1 --- /dev/null +++ b/alerts/Dockerfile @@ -0,0 +1,37 @@ +# Production stage +FROM node:22-alpine + +# Accept build arguments +ARG ENVIO_INDEXER_URL + +ENV ENVIO_INDEXER_URL=$ENVIO_INDEXER_URL + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package.json ./ + +# Install dependencies using pnpm +RUN corepack enable && corepack prepare pnpm@latest --activate +COPY pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +# Copy source code +COPY . . + +# Generate types +RUN pnpm typechain +RUN pnpm build + +# Create data directory with correct ownership +RUN mkdir -p /app/data + +# Use dumb-init to handle signals properly +ENTRYPOINT ["dumb-init", "--"] + +# Default command +CMD ["pnpm", "start"] diff --git a/alerts/README.md b/alerts/README.md index 0669370b..57cb2d23 100644 --- a/alerts/README.md +++ b/alerts/README.md @@ -6,7 +6,22 @@ The alerts that will be sent are: - Low Balance (XDAI or ETH) for validators - Low Limits for XDAI Native and Foreign Bridge Contracts (dailyLimit and executionDailyLimit) -- .... +- Invalid validator: When validator is not signing transactions for threshold amount of time +- Stucked Transaction: When a transaction is initiated but still in 'Collecting' status for threshold amount of time + +# Dev + +```shell +cp .env.example .env +pnpm install && pnpm typechain && pnpm build +pnpm dev # or pnpm start +``` + +# Run docker + +```shell +docker compose up --build +``` ### Validators @@ -19,6 +34,7 @@ A limit is low when it is under 25%? of the total limit per day. (TBD) ## Configuration Remember to configure Slack App: + - create slack app - add slack into workspace - create scope permissions for slack @@ -29,10 +45,10 @@ Remember to configure Slack App: - Setup Env Variables - cp .env.example .env -- Install npm dependencies - - yarn install +- Install dependencies + - pnpm install - Run script - - yarn dev + - pnpm dev ### References diff --git a/alerts/docker-compose.yml b/alerts/docker-compose.yml new file mode 100644 index 00000000..89417fa5 --- /dev/null +++ b/alerts/docker-compose.yml @@ -0,0 +1,28 @@ +version: "3.8" + +services: + alerts: + build: + context: . + args: + - ENVIO_INDEXER_URL=${ENVIO_INDEXER_URL} + environment: + - SLACK_TOKEN=${SLACK_TOKEN} + - SLACK_CHANNEL=${SLACK_CHANNEL} + - SLACK_WEBHOOK_URL=${SLACK_WEBHOOK_URL} + - MAINNET_RPC_URL=${MAINNET_RPC_URL} + - GNOSIS_RPC_URL=${GNOSIS_RPC_URL} + - ENVIO_INDEXER_URL=${ENVIO_INDEXER_URL} + - IS_VALIDATOR_BALANCE_ON_GC=${IS_VALIDATOR_BALANCE_ON_GC} + - INACTIVITY_THRESHOLD_HOURS=${INACTIVITY_THRESHOLD_HOURS} + - TRANSACTION_TIMEOUT_HOURS=${TRANSACTION_TIMEOUT_HOURS} + - SCHEDULE_ENABLED=${SCHEDULE_ENABLED:-true} + - SCHEDULE_CRON=${SCHEDULE_CRON} + - RUN_ONCE_ON_START=${RUN_ONCE_ON_START} + - ALERT_STATE_FILE=${ALERT_STATE_FILE:-./data/stuck-tx-alerts.json} + - ALERT_CLEANUP_HOURS=${ALERT_CLEANUP_HOURS:-48} + env_file: + - .env + volumes: + - ./data:/app/data + restart: unless-stopped diff --git a/alerts/package.json b/alerts/package.json index 5ceefc33..6717a940 100644 --- a/alerts/package.json +++ b/alerts/package.json @@ -9,40 +9,39 @@ ], "scripts": { "dev": "ts-node src/index.ts", + "dev:schedule": "SCHEDULE_ENABLED=true RUN_ONCE_ON_START=true ts-node src/index.ts", + "dev:once": "SCHEDULE_ENABLED=false ts-node src/index.ts", "start": "node ./dist/index.js", - "build": "tsc --build --clean", + "start:schedule": "SCHEDULE_ENABLED=true node ./dist/index.js", + "start:once": "SCHEDULE_ENABLED=false node ./dist/index.js", + "build": "tsc", "typechain": "typechain --target=ethers-v5 ./src/abis/**/*.json --out-dir ./src/types/typechain", - "subgraph": "graphql-codegen --config subgraph-config.js", "types": "tsc --project tsconfig.json", - "postinstall": "yarn typechain && yarn subgraph", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "MIT", "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/contracts": "^5.7.0", - "@ethersproject/providers": "^5.7.1", - "@slack/web-api": "^6.7.2", - "dotenv": "^16.0.3", - "ethers": "^5.7.1", - "graphql": "^16.6.0", - "graphql-request": "^5.0.0", - "graphql-tag": "^2.12.6" + "@ethersproject/address": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/providers": "5.7.1", + "@slack/web-api": "6.7.2", + "dotenv": "17.2.3", + "ethers": "5.7.1", + "graphql": "16.6.0", + "graphql-request": "5.2.0", + "graphql-tag": "2.12.6", + "node-cron": "3.0.3" }, "devDependencies": { - "@graphql-codegen/cli": "^2.13.6", - "@graphql-codegen/introspection": "^2.2.1", - "@graphql-codegen/typescript": "^2.7.4", - "@graphql-codegen/typescript-graphql-request": "^4.5.6", - "@graphql-codegen/typescript-operations": "^2.5.4", - "@typechain/ethers-v5": "^10.1.0", - "@types/node": "^18.8.3", - "ts-node": "^10.9.1", - "typechain": "^8.1.0", - "typescript": "^4.8.4", - "typescript-graphql-request": "^4.4.6" + "@typechain/ethers-v5": "11.1.2", + "@types/node": "25.0.10", + "@types/node-cron": "3.0.11", + "@types/node-fetch": "2.6.13", + "ts-node": "10.9.2", + "typechain": "8.1.0", + "typescript": "4.8.4" } } diff --git a/alerts/src/alerts/inactiveValidators.ts b/alerts/src/alerts/inactiveValidators.ts new file mode 100644 index 00000000..e1906950 --- /dev/null +++ b/alerts/src/alerts/inactiveValidators.ts @@ -0,0 +1,65 @@ +import { title } from 'process'; +import { fetchValidators, Validator } from '../validators'; +import { Message, MessageType } from "./messages" + +const INACTIVITY_THRESHOLD_HOURS = parseInt(process.env.INACTIVITY_THRESHOLD_HOURS) || 0.01; + + +const createInactiveValidatorMessage = (validator: Validator, inactiveSince: string, lastActivityUTC: string) =>{ + return{ + title: `Inactive validator alert: ${validator.name} on ${validator.bridgeType}`, + type: MessageType.INACTIVE_VALIDATOR, + createdBy: validator.name, + createdByLink: `https://gnosisscan.io/address/${validator.address}`, + timestamp: new Date(), + body: `Inactive since: ${inactiveSince}, last activity in ${lastActivityUTC}` + + } +} + +export const checkInactiveValidators = async (): Promise => { + console.log('Checking for inactive validators...'); + try { + const allValidators = await fetchValidators(); + const now = Math.floor(Date.now() / 1000); + const inactivityCutoff = now - INACTIVITY_THRESHOLD_HOURS * 60 * 60; + + const inactiveValidators = allValidators.filter(validator => { + if (!validator.lastActivity) { + return true; // Consider validators never seen as inactive + } + return parseInt(validator.lastActivity, 10) < inactivityCutoff; + }); + + if (inactiveValidators.length > 0) { + console.log(`Found ${inactiveValidators.length} inactive validators.`); + + const message = inactiveValidators.map(( validator: Validator) => { + + // Calculate inactivity duration + const diffInSeconds = now - parseInt(validator.lastActivity, 10); + const diffInHours = Math.round(diffInSeconds / 3600); + const hourText = diffInHours === 1 ? 'hr' : 'hrs'; + const inactiveSince = `${diffInHours} ${hourText} ago`; + const lastActivityUTC = new Date(parseInt(validator.lastActivity, 10) * 1000).toUTCString() + + + return createInactiveValidatorMessage(validator,inactiveSince, lastActivityUTC) + + }); + return message + + + + } else { + console.log('All validators are active.'); + } + } catch (e) { + if (e instanceof Error) { + console.error('Could not check for inactive validators due to an error:', e.message); + } else { + console.error('An unknown error occurred while checking for inactive validators.'); + } + } + return null; +}; diff --git a/alerts/src/alerts/lowBalance.ts b/alerts/src/alerts/lowBalance.ts index 0660df61..dda3bcbe 100644 --- a/alerts/src/alerts/lowBalance.ts +++ b/alerts/src/alerts/lowBalance.ts @@ -1,58 +1,69 @@ -import { gnosisScanAddressURL, mainnetScanAddressLink } from "../chains" -import { TokenBalance } from "../tokens" -import { fetchValidators, Validator } from "../validators" -import { Message, MessageType } from "./messages" +// Note: tokenbalances entity is removed from the new envio indexer, hence this is for reference purpose only +// import { gnosisScanAddressURL, mainnetScanAddressLink } from "../chains" +// import { TokenBalance } from "../tokens" +// import { fetchValidators, Validator } from "../validators" +// import { Message, MessageType } from "./messages" -// @todo add logic to disable alert +// // @todo add logic to disable alert -// @todo define better thresholds for each token -const MIN_XDAI_BALANCE_THRESHOLD = 0.5 -const MIN_ETH_BALANCE_THRESHOLD = 0.5 +// // @todo define better thresholds for each token +// const MIN_XDAI_BALANCE_THRESHOLD = parseInt(process.env.MIN_XDAI_BALANCE_THRESHOLD) || 1 +// const MIN_ETH_BALANCE_THRESHOLD = parseInt(process.env.MIN_ETH_BALANCE_THRESHOLD) || 0.01 -const getXDAIBalance = (v: Validator) => v.tokensBalances[0] -const getETHBalance = (v: Validator) => v.tokensBalances[1] +// const getXDAIBalance = (v: Validator) => v.tokensBalances[0] +// const getETHBalance = (v: Validator) => v.tokensBalances[1] -const createTitle = (validator: Validator, token: TokenBalance) => `Validator ${validator.name} has low ${token.name} balance` +// const createTitle = (validator: Validator, token: TokenBalance) => `Validator ${validator.name} has low ${token.name} balance` -const createBody = (validator: Validator, token: TokenBalance) => `The validator ${validator.name} has *${token.balance}* of *${token.name}* balance` +// const createBody = (validator: Validator, token: TokenBalance) => `The validator ${validator.name} has *${token.balance}* of *${token.name}* balance` -const createBalanceMessage = (validator: Validator, token: TokenBalance, link: string) => { - return { - title: createTitle(validator, token), - type: MessageType.LOW_BALANCE, - createdBy: validator.name, - createdByLink: link, - timestamp: new Date(), - body: createBody(validator, token), - } -} +// const createBalanceMessage = (validator: Validator, token: TokenBalance, link: string) => { +// return { +// title: createTitle(validator, token), +// type: MessageType.LOW_BALANCE, +// createdBy: validator.name, +// createdByLink: link, +// timestamp: new Date(), +// body: createBody(validator, token), +// } +// } -const createValidatorXDAIBalanceMessage = (validator: Validator): Message => { - const token = getXDAIBalance(validator) - const scanLink = gnosisScanAddressURL(validator.address) +// const createValidatorXDAIBalanceMessage = (validator: Validator): Message => { +// const token = getXDAIBalance(validator) +// const scanLink = gnosisScanAddressURL(validator.address) - // @todo define a specific threshold - if (token.balance > MIN_XDAI_BALANCE_THRESHOLD) return null +// // @todo define a specific threshold +// if (token.balance > MIN_XDAI_BALANCE_THRESHOLD) return null - return createBalanceMessage(validator, token, scanLink) -} +// return createBalanceMessage(validator, token, scanLink) +// } -const createValidatorETHBalanceMessage = (validator: Validator): Message => { - const token = getETHBalance(validator) - const scanLink = mainnetScanAddressLink(validator.address) +// const createValidatorETHBalanceMessage = (validator: Validator): Message => { +// const token = getETHBalance(validator) +// const scanLink = mainnetScanAddressLink(validator.address) - // @todo define a specific threshold - if (token.balance > MIN_ETH_BALANCE_THRESHOLD) return null +// // @todo define a specific threshold +// if (token.balance > MIN_ETH_BALANCE_THRESHOLD) return null - return createBalanceMessage(validator, token, scanLink) -} +// return createBalanceMessage(validator, token, scanLink) +// } -const lowBalanceAlerts = async (): Promise => { - const validators = await fetchValidators() - - const lowETHMessages = validators.map(createValidatorETHBalanceMessage).filter(Boolean) - const lowXDAIMessages = validators.map(createValidatorXDAIBalanceMessage).filter(Boolean) - return lowETHMessages.concat(lowXDAIMessages) -} +// const lowBalanceAlerts = async (): Promise => { +// const validators = await fetchValidators() -export { lowBalanceAlerts } +// let lowXDAIMessages,lowETHMessages +// if(process.env.IS_VALIDATOR_BALANCE_ON_GC == 'true'){ + +// lowXDAIMessages = validators.map(createValidatorXDAIBalanceMessage).filter(Boolean) + +// return lowXDAIMessages +// }else{ +// lowETHMessages = validators.map(createValidatorETHBalanceMessage).filter(Boolean) +// lowXDAIMessages = validators.map(createValidatorXDAIBalanceMessage).filter(Boolean) +// return lowETHMessages.concat(lowXDAIMessages) +// } + + +// } + +// export { lowBalanceAlerts } diff --git a/alerts/src/alerts/messages.ts b/alerts/src/alerts/messages.ts index 5ffb569f..90efc564 100644 --- a/alerts/src/alerts/messages.ts +++ b/alerts/src/alerts/messages.ts @@ -1,12 +1,14 @@ import { bridgeLimits } from "./bridgeLimits" -import { lowBalanceAlerts } from "./lowBalance" +import { checkInactiveValidators } from "./inactiveValidators" +// import { lowBalanceAlerts } from "./lowBalance" +import { checkStuckTransactions } from "./stuckedTx" export type Message = { title: string type: string createdBy: string createdByLink: string - timestamp: Date + timestamp?: Date body: string } @@ -14,12 +16,23 @@ export enum MessageType { LOW_BALANCE = 'LowBalance', DAILY_LIMIT = 'DailyLimit', EXECUTION_DAILY_LIMIT = 'ExecutionDailyLimit', + INACTIVE_VALIDATOR = 'InactiveValidator', + STUCK_TRANSACTION = 'StuckTx' } const messages = async () => { - const bridgeLimitsAlerts = await bridgeLimits() - const lowValidatorBalanceAlerts = await lowBalanceAlerts() - return bridgeLimitsAlerts.concat(lowValidatorBalanceAlerts) + // To run only certain alerts, comment out the lines for the checks you want to disable. + const alertPromises = [ + // bridgeLimits(), + // lowBalanceAlerts(), + // checkInactiveValidators(), + checkStuckTransactions(), + ] + + const results = await Promise.all(alertPromises) + + // The flat() method removes empty arrays and filter(Boolean) removes null/undefined values. + return results.flat().filter(Boolean) as Message[] } export { messages } diff --git a/alerts/src/alerts/stuckedTx.ts b/alerts/src/alerts/stuckedTx.ts new file mode 100644 index 00000000..478e0526 --- /dev/null +++ b/alerts/src/alerts/stuckedTx.ts @@ -0,0 +1,312 @@ +import gql from 'graphql-tag' +import * as fs from 'fs' +import * as path from 'path' +import { useGraphqlClient } from '../graphql' +import { Message, MessageType } from './messages' + +// With Envio indexer, all transaction data (both chains) is in a single unified endpoint. +// A transaction is considered stuck if it remains in INITIATED or COLLECTING status +// after the timeout period. + +const TRANSACTION_TIMEOUT_HOURS = parseInt(process.env.TRANSACTION_TIMEOUT_HOURS) || 2 +const ALERT_STATE_FILE = process.env.ALERT_STATE_FILE || path.join(__dirname, '../../data/stuck-tx-alerts.json') +const ALERT_CLEANUP_HOURS = parseInt(process.env.ALERT_CLEANUP_HOURS) || 48 // Cleanup resolved alerts after 48 hours + +// Query to get all non-completed transactions in time range +const TRANSACTIONS_QUERY = gql` + query EnvioTransactions($where: Transaction_bool_exp, $order_by: [Transaction_order_by!], $limit: Int, $offset: Int) { + Transaction(where: $where, order_by: $order_by, limit: $limit, offset: $offset) { + id + messageId + bridgeType + transactionHash + timestamp + initiatorNetwork + initiator + initiatorToken + initiatorAmount + receiverNetwork + receiver + receiverToken + receiverAmount + transactionStatus + execution { + id + transactionHash + timestamp + executorAddress + } + validations { + id + transactionHash + timestamp + validatorAddress + } + } + } +` + +type Transaction = { + id: string + messageId: string + transactionHash: string + bridgeType: string + transactionStatus: string + timestamp: string + initiator: string + initiatorNetwork: number + receiverNetwork: number + initiatorToken: string + receiverToken: string + initiatorAmount: string + receiverAmount: string + validations: Array<{ + id: string + timestamp: string + validatorAddress: string + }> + execution: { + id: string + transactionHash: string + timestamp: string + executorAddress: string + } | null +} + +type TransactionsResponse = { + Transaction: Transaction[] +} + +type TransactionsVariables = { + where: { + _and: Array> + } + order_by: Array<{ timestamp: string }> + limit: number +} + +type AlertState = { + transactionId: string + transactionHash: string + status: string + firstAlertTime: number + lastAlertTime: number +} + +type AlertStateFile = { + version: string + alerts: Record + lastCleanup: number + lastCheckedTimestamp: number +} + +// State management functions +// Store the stuck txs in /data folder to avoid duplicate signaling +const ensureDataDirectory = () => { + const dataDir = path.dirname(ALERT_STATE_FILE) + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }) + } +} + +const loadAlertState = (): AlertStateFile => { + try { + ensureDataDirectory() + if (fs.existsSync(ALERT_STATE_FILE)) { + const data = fs.readFileSync(ALERT_STATE_FILE, 'utf-8') + const parsed = JSON.parse(data) as AlertStateFile + if (parsed) { + return parsed + } + } + } catch (error) { + console.warn('Failed to load alert state file:', error) + } + + return { + version: "1.0", + alerts: {}, + lastCleanup: Date.now(), + lastCheckedTimestamp: 0 + } +} + +const saveAlertState = (state: AlertStateFile): void => { + try { + ensureDataDirectory() + fs.writeFileSync(ALERT_STATE_FILE, JSON.stringify(state, null, 2), 'utf-8') + } catch (error) { + console.error('Failed to save alert state file:', error) + } +} + +const cleanupResolvedAlerts = (state: AlertStateFile, currentStuckTxIds: Set): AlertStateFile => { + const now = Date.now() + const cleanupThreshold = now - (ALERT_CLEANUP_HOURS * 60 * 60 * 1000) + + const cleanedAlerts: Record = {} + + Object.entries(state.alerts).forEach(([txId, alertState]) => { + const isStillStuck = currentStuckTxIds.has(txId) + const isRecent = alertState.lastAlertTime > cleanupThreshold + + if (isStillStuck || isRecent) { + cleanedAlerts[txId] = alertState + } else { + console.log(`Cleaning up resolved alert for transaction: ${alertState.transactionHash}`) + } + }) + + return { + ...state, + alerts: cleanedAlerts, + lastCleanup: now + } +} + +const shouldAlertTransaction = (tx: Transaction, alertState: AlertStateFile): boolean => { + const existingAlert = alertState.alerts[tx.id] + + if (!existingAlert) { + return true + } + + console.log(`Skipping already alerted transaction: ${tx.transactionHash} (first alerted: ${new Date(existingAlert.firstAlertTime).toISOString()})`) + return false +} + +const recordAlert = (tx: Transaction, alertState: AlertStateFile): void => { + const now = Date.now() + const existingAlert = alertState.alerts[tx.id] + + alertState.alerts[tx.id] = { + transactionId: tx.id, + transactionHash: tx.transactionHash, + status: tx.transactionStatus, + firstAlertTime: existingAlert?.firstAlertTime || now, + lastAlertTime: now, + } +} + +const getNetworkName = (networkId: number | string): string => { + const id = typeof networkId === 'string' ? parseInt(networkId) : networkId + switch (id) { + case 1: return 'Ethereum' + case 100: return 'Gnosis' + default: return `Chain ${id}` + } +} + +const getScanUrl = (networkId: number | string, txHash: string): string => { + const id = typeof networkId === 'string' ? parseInt(networkId) : networkId + switch (id) { + case 1: return `https://etherscan.io/tx/${txHash}` + case 100: return `https://gnosisscan.io/tx/${txHash}` + default: return txHash + } +} + +const formatDuration = (hours: number): string => { + if (hours < 24) { + return `${Math.round(hours)} hour${hours !== 1 ? 's' : ''}` + } + const days = Math.floor(hours / 24) + const remainingHours = Math.round(hours % 24) + return `${days} day${days !== 1 ? 's' : ''} ${remainingHours} hour${remainingHours !== 1 ? 's' : ''}` +} + +const createStuckTransactionMessage = (tx: Transaction): Message => { + const now = Date.now() + const hoursStuck = (now - parseInt(tx.timestamp) * 1000) / (3600 * 1000) + const validationCount = tx.validations?.length || 0 + const networkFrom = getNetworkName(tx.initiatorNetwork) + const networkTo = getNetworkName(tx.receiverNetwork) + + let body = `• \`${tx.transactionHash}\` (${formatDuration(hoursStuck)} stuck, ${validationCount} validations)\n` + body += ` Bridge: ${tx.bridgeType} | ${networkFrom} -> ${networkTo}\n` + body += '\n' + + return { + title: `Stuck Tx Alert on ${tx.bridgeType} for ${formatDuration(hoursStuck)}`, + type: MessageType.STUCK_TRANSACTION, + createdBy: tx.bridgeType, + createdByLink: getScanUrl(tx.initiatorNetwork, tx.transactionHash), + timestamp: new Date(), + body: body + } +} + +const checkStuckTransactions = async (): Promise => { + console.log("Checking for stuck transactions via Envio indexer...") + const messages: Message[] = [] + + // Check period for [maxDelay, minDelay] + let minDelay = Date.now() - TRANSACTION_TIMEOUT_HOURS * 3600 * 1000 + let maxDelay = minDelay - 24 * 3600 * 1000 // default: 24 hours period from minDelay + + try { + let alertState = loadAlertState() + console.log(`Loaded alert state: ${Object.keys(alertState.alerts).length} tracked alerts`) + + if (alertState.lastCheckedTimestamp === 0) { + console.log(`First startup: checking transactions from ${new Date(maxDelay).toISOString()} to ${new Date(minDelay).toISOString()}`) + } else { + maxDelay = alertState.lastCheckedTimestamp + console.log(`Regular check: monitoring transactions from ${new Date(maxDelay).toISOString()} to ${new Date(minDelay).toISOString()}`) + } + + const client = useGraphqlClient() + + const queryVariables: TransactionsVariables = { + where: { + _and: [ + { timestamp: { _is_null: false } }, + { timestamp: { _gte: Math.floor(maxDelay / 1000).toString() } }, + { timestamp: { _lt: Math.floor(minDelay / 1000).toString() } }, + { transactionStatus: { _in: ['INITIATED', 'COLLECTING'] } } + ] + }, + order_by: [{ timestamp: "asc" }], + limit: 200 + } + + const response = await client(TRANSACTIONS_QUERY, queryVariables) + const stuckTransactions = response.Transaction + console.log(`Found ${stuckTransactions.length} stuck transactions`) + + const currentStuckTxIds = new Set(stuckTransactions.map(tx => tx.id)) + + // Clean up resolved alerts periodically + const shouldCleanup = Date.now() - alertState.lastCleanup > (60 * 60 * 1000) + if (shouldCleanup) { + console.log('Performing periodic cleanup of resolved alerts...') + alertState = cleanupResolvedAlerts(alertState, currentStuckTxIds) + } + + if (stuckTransactions.length > 0) { + const newTransactions = stuckTransactions.filter(tx => shouldAlertTransaction(tx, alertState)) + console.log(`${stuckTransactions.length} stuck transactions, ${newTransactions.length} new alerts`) + + stuckTransactions.forEach(tx => { + recordAlert(tx, alertState) + }) + + if (newTransactions.length > 0) { + newTransactions.forEach(tx => { + messages.push(createStuckTransactionMessage(tx)) + }) + } + } + + alertState.lastCheckedTimestamp = minDelay + saveAlertState(alertState) + console.log(`Saved alert state: ${Object.keys(alertState.alerts).length} tracked alerts`) + + } catch (error) { + console.error('Error checking stuck transactions:', error) + } + + return messages +} + +export { checkStuckTransactions } diff --git a/alerts/src/graphql.ts b/alerts/src/graphql.ts index b9edb868..77351791 100644 --- a/alerts/src/graphql.ts +++ b/alerts/src/graphql.ts @@ -1,31 +1,25 @@ import { DocumentNode } from 'graphql' -import { GraphQLClient } from 'graphql-request' +import { GraphQLClient, Variables } from 'graphql-request' -const NATIVE_ENDPOINT = process.env.SUBGRAPH_API_NATIVE -const FOREIGN_ENDPOINT = process.env.SUBGRAPH_API_FOREIGN +const ENVIO_INDEXER_URL = process.env.ENVIO_INDEXER_URL -const SG_ENDPOINTS = [NATIVE_ENDPOINT, FOREIGN_ENDPOINT] - -const initGraphQLClients = (apiURLs: string[]) => { - const clients: Record = {} - apiURLs.forEach((apiUrl) => { - clients[apiUrl] = new GraphQLClient(apiUrl) - }) - return clients +if (!ENVIO_INDEXER_URL) { + throw new Error('ENVIO_INDEXER_URL environment variable is required') } -const graphqlClients = initGraphQLClients(SG_ENDPOINTS) +const graphqlClient = new GraphQLClient(ENVIO_INDEXER_URL) -const useGraphqlFetcher = (apiURL: string) => ( +const useGraphqlClient = () => async ( query: DocumentNode, - variables?: Variables, -) => { - if (!graphqlClients[apiURL]) throw new Error('graphql endpoint not initialized') - const fetcher = graphqlClients[apiURL] - return fetcher.request(query, variables) + variables?: TVariables, +): Promise => { + try { + const result = await graphqlClient.request(query, variables) + return result + } catch (error) { + console.error("GraphQL Error:", error) + throw error + } } -const useNativeGraphqlClient = () => useGraphqlFetcher(NATIVE_ENDPOINT) -const useForeignGraphqlClient = () => useGraphqlFetcher(FOREIGN_ENDPOINT) - -export { useNativeGraphqlClient, useForeignGraphqlClient } +export { useGraphqlClient } diff --git a/alerts/src/index.ts b/alerts/src/index.ts index 75ac213a..ada68609 100644 --- a/alerts/src/index.ts +++ b/alerts/src/index.ts @@ -1,17 +1,152 @@ import * as dotenv from "dotenv" dotenv.config() +import * as cron from "node-cron" import { messages } from "./alerts/messages" import { sendMessage } from "./slack" -const main = async () => { - console.log('Creating messages to send...') - const msgs = await messages() - console.log(`Sending ${msgs.length} total of messages...`) - const promises = msgs.map(msg => { - return sendMessage(msg) +// Configuration from environment variables +const SCHEDULE_ENABLED = process.env.SCHEDULE_ENABLED === 'true' +let SCHEDULE_CRON: string +if(process.env.NODE_ENV === 'DEV'){ + SCHEDULE_CRON= process.env.SCHEDULE_CRON || '0 */15 * * * *' +}else if (process.env.NODE_ENV ==='PROD'){ + SCHEDULE_CRON = process.env.SCHEDULE_CRON || '*/15 * * * *' // Default: every 15 minutes, use 5 elements instead of 6 to run in docker environment + +} +const RUN_ONCE_ON_START = process.env.RUN_ONCE_ON_START === 'true' + +let isRunning = false +let lastRunTime: Date | null = null +let lastError: Error | null = null + +const runAlerts = async () => { + if (isRunning) { + console.log('ā³ Alert job already running, skipping this execution') + return + } + + isRunning = true + const startTime = new Date() + + try { + console.log(`šŸš€ Starting alert job at ${startTime.toISOString()}`) + + const msgs = await messages() + console.log(`šŸ“Š Generated ${msgs.length} alert message(s)`) + + if (msgs.length > 0) { + console.log('šŸ“¤ Sending messages to Slack...') + const promises = msgs.map((msg, index) => { + console.log(` ${index + 1}. ${msg.title} (${msg.type})`) + return sendMessage(msg) + }) + + await Promise.all(promises) + console.log(`āœ… Successfully sent ${msgs.length} message(s)`) + } else { + console.log('ā„¹ļø No alerts to send - all systems normal') + } + + lastRunTime = startTime + lastError = null + + } catch (error) { + lastError = error instanceof Error ? error : new Error(String(error)) + console.error('āŒ Alert job failed:', lastError.message) + console.error('Stack trace:', lastError.stack) + } finally { + isRunning = false + const duration = Date.now() - startTime.getTime() + console.log(`ā±ļø Alert job completed in ${duration}ms\n`) + } +} + +const startScheduler = () => { + console.log('šŸ•’ Starting scheduled alert system...') + console.log(`āš™ļø Schedule: ${SCHEDULE_CRON}`) + console.log(`šŸ”„ Run once on start: ${RUN_ONCE_ON_START}`) + + // Validate cron expression + if (!cron.validate(SCHEDULE_CRON)) { + throw new Error(`Invalid cron expression: ${SCHEDULE_CRON}`) + } + // Schedule the job + const task = cron.schedule(SCHEDULE_CRON, () => { + runAlerts().catch(error => { + console.error('šŸ”„ Unhandled error in scheduled task:', error) + }) +}) + +// Start the task +task.start(); + +// Conditionally run the job once on start +if (RUN_ONCE_ON_START) { + console.log('šŸ”„ Running job once on start...') + runAlerts().catch(error => { + console.error('šŸ”„ Unhandled error during initial run:', error) + }) +} + + + console.log('āœ… Scheduler started successfully') + + // Health check endpoint (log status every hour) + setInterval(() => { + const status = { + isRunning, + lastRunTime: lastRunTime?.toISOString(), + lastError: lastError?.message, + nextRun: 'Based on cron schedule', + uptime: process.uptime() + } + console.log('šŸ“Š Alert system status:', JSON.stringify(status, null, 2)) + }, 24 * 60 * 60 * 1000) // Every 24 hour + + // Graceful shutdown + process.on('SIGINT', () => { + console.log('\nšŸ›‘ Received SIGINT, stopping scheduler...') + task.stop() + console.log('āœ… Scheduler stopped gracefully') + process.exit(0) + }) + + process.on('SIGTERM', () => { + console.log('\nšŸ›‘ Received SIGTERM, stopping scheduler...') + task.stop() + console.log('āœ… Scheduler stopped gracefully') + process.exit(0) }) - await Promise.all(promises) - console.log('Messages sent') } -main() +// Main execution logic +const main = async () => { + console.log('šŸ”§ Bridge Monitor Alert System') + console.log('================================') + + if (SCHEDULE_ENABLED) { + startScheduler() + + } else { + console.log('šŸ“ Running in one-time mode...') + await runAlerts() + console.log('šŸ One-time execution completed') + process.exit(0) + } +} + +// Handle uncaught exceptions +process.on('uncaughtException', (error) => { + console.error('šŸ’„ Uncaught Exception:', error) + process.exit(1) +}) + +process.on('unhandledRejection', (reason, promise) => { + console.error('šŸ’„ Unhandled Rejection at:', promise, 'reason:', reason) + process.exit(1) +}) + +main().catch(error => { + console.error('šŸ’„ Failed to start alert system:', error) + process.exit(1) +}) diff --git a/alerts/src/queries/validators.ts b/alerts/src/queries/validators.ts index cc964ad9..1beca9bf 100644 --- a/alerts/src/queries/validators.ts +++ b/alerts/src/queries/validators.ts @@ -1,12 +1,13 @@ -import gql from 'graphql-tag' +import gql from 'graphql-tag' export const VALIDATORS_QUERY = gql` - query Validators { - validators { + query EnvioValidators { + Validator { id + address name bridgeType - address + lastActivity } } ` diff --git a/alerts/src/slack.ts b/alerts/src/slack.ts index 6242d6df..0c2beff5 100644 --- a/alerts/src/slack.ts +++ b/alerts/src/slack.ts @@ -1,81 +1,118 @@ -import { Block, KnownBlock, WebClient } from '@slack/web-api' +import { Block, KnownBlock, WebClient } from '@slack/web-api'; import { Message } from './alerts/messages'; +import fetch from 'node-fetch'; -const token = process.env.SLACK_TOKEN || '' -const channel = process.env.SLACK_CHANNEL || '' +const token = process.env.SLACK_TOKEN || ''; +const channel = process.env.SLACK_CHANNEL || ''; +const webhookURL = process.env.SLACK_WEBHOOK_URL || ''; const web = new WebClient(token); const createBlocksMessage = (msg: Message): (Block | KnownBlock)[] => { return [ { - "type": "header", - "text": { - "type": "plain_text", - "text": msg.title, - "emoji": true - } + type: 'header', + text: { + type: 'plain_text', + text: msg.title, + emoji: true, + }, }, { - "type": "section", - "fields": [ + type: 'section', + fields: [ { - "type": "mrkdwn", - "text": `*Type:*\n${msg.type}` + type: 'mrkdwn', + text: `*Type:*\n${msg.type}`, }, { - "type": "mrkdwn", - "text": `*Contract:*\n<${msg.createdByLink}|${msg.createdBy}>` + type: 'mrkdwn', + text: `*Contract:*\n<${msg.createdByLink}|${msg.createdBy}>`, }, - ] + ], }, { - "type": "section", - "fields": [ + type: 'section', + fields: [ { - "type": "mrkdwn", - "text": `*When:*\n${msg.timestamp.toISOString()}` + type: 'mrkdwn', + text: `*When:*\n${(msg.timestamp || new Date()).toISOString()}`, }, - ] + ], }, { - "type": "section", - "fields": [ + type: 'section', + fields: [ { - "type": "mrkdwn", - "text": `*Message:*\n${msg.body}`, - } - ] + type: 'mrkdwn', + text: `*Message:*\n${msg.body}`, + }, + ], }, - ] -} + ]; +}; const channels = async () => { try { - const result = await web.conversations.list() - return result.channels - } catch(e) { - return Promise.resolve([]) + const result = await web.conversations.list(); + return result.channels; + } catch (e) { + return Promise.resolve([]); } -} +}; const sendMessageToChannel = async (msg: Message, channelId: string) => { try { const result = await web.chat.postMessage({ text: msg.title, channel: channelId, - blocks: createBlocksMessage(msg) - }) - const timestamp = result.ts ? new Date(parseInt(result.ts) * 1000) : new Date() + blocks: createBlocksMessage(msg), + }); + const timestamp = result.ts ? new Date(parseInt(result.ts) * 1000) : new Date(); + + console.log(`Successfully send message at ${timestamp} in channel ${channelId}`); + } catch (e) { + console.log({ e }); + const timestamp = new Date(); + console.log(`Unsuccessfully send message at ${timestamp} in channel ${channelId}`); + } +}; - console.log(`Successfully send message at ${timestamp} in channel ${channelId}`) +const sendMessageOverWebhook = async (msg: Message) => { + try { + const payload = { + text: msg.title, + blocks: createBlocksMessage(msg) + }; + + const response = await fetch(webhookURL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(payload), + }); + + if (response.ok) { + console.log(`Successfully sent message via webhook.`); + } else { + const responseText = await response.text(); + console.error(`Failed to send message via webhook: ${response.status} ${response.statusText}`); + console.error(`Response body: ${responseText}`); + } } catch (e) { - console.log({ e }) - const timestamp = new Date() - console.log(`Unsuccessfully send message at ${timestamp} in channel ${channelId}`) + console.error('Error sending message via webhook:', e); } } -const sendMessage = (msg: Message) => sendMessageToChannel(msg, channel) +const sendMessage = (msg: Message) => { + if (webhookURL) { + return sendMessageOverWebhook(msg); + } + if (token && channel) { + return sendMessageToChannel(msg, channel); + } + console.error('No Slack configuration found. Please set SLACK_WEBHOOK_URL or SLACK_TOKEN and SLACK_CHANNEL.'); +} -export { sendMessage, channels } +export { sendMessage, channels }; diff --git a/alerts/src/validators.ts b/alerts/src/validators.ts index c7b4049d..2c8e606d 100644 --- a/alerts/src/validators.ts +++ b/alerts/src/validators.ts @@ -1,66 +1,76 @@ -import { useForeignGraphqlClient, useNativeGraphqlClient } from "./graphql" +import { useGraphqlClient } from "./graphql" import { gnosis, mainnet } from "./providers" import { VALIDATORS_QUERY } from "./queries/validators" import { getNativeBalance, TokenBalance, TokenBalanceType } from "./tokens" -import { BridgeType, ValidatorsQuery, ValidatorsQueryVariables } from "./types/subgraph/subgraph" const mainnetProvider = mainnet() const gnosisProvider = gnosis() +export type BridgeType = 'XDAI' | 'AMB' + export type Validator = { - __typename?: 'Validator' id: string address?: string bridgeType?: BridgeType - lastSeen?: string + lastActivity?: string name?: string - tokensBalances: TokenBalance[] -} - -const fetchNativeValidators = async (filter?: ValidatorsQueryVariables) => { - const { validators } = await useNativeGraphqlClient()< - ValidatorsQuery, - ValidatorsQueryVariables - >(VALIDATORS_QUERY, filter) - return validators } -const fetchForeignValidators = async (filter?: ValidatorsQueryVariables) => { - const { validators } = await useForeignGraphqlClient()< - ValidatorsQuery, - ValidatorsQueryVariables - >(VALIDATORS_QUERY, filter) - return validators +type ValidatorsResponse = { + Validator: Validator[] } const fetchValidators = async () => { - const validatorsData = await Promise.all([fetchNativeValidators(), fetchForeignValidators()]) - const validatorsNative = validatorsData[0] - // @todo verify that both coincide - const validatorsForeign = validatorsData[1] - if (validatorsNative.length !== validatorsForeign.length) throw new Error('Validators mismatch') - const validatorsPromises = validatorsNative.map>(async (validator) => { - const [xdaiBalance, ethBalance] = await Promise.all([ - getNativeBalance(gnosisProvider, validator.address), - getNativeBalance(mainnetProvider, validator.address), - ]) - const xdaiToken: TokenBalance = { - type: TokenBalanceType.Native, - name: "XDAI", - balance: xdaiBalance - } - const ethToken: TokenBalance = { - type: TokenBalanceType.Native, - name: "ETH", - balance: ethBalance - } - return { - ...validator, - tokensBalances: [xdaiToken, ethToken], - } - }) - const validators = await Promise.all(validatorsPromises) - return validators + const graphqlClient = useGraphqlClient() + const result = await graphqlClient(VALIDATORS_QUERY) + const validators = result?.Validator || [] + + // Filter out validators with the specific address + const excludedAddress = '0x456c255a8bc1f33778603a2a48eb6b0c69f4d48e' // telepathy + const filteredValidators = validators.filter( + (validator: Validator) => validator.address?.toLowerCase() !== excludedAddress.toLowerCase() + ) + + if (process.env.IS_VALIDATOR_BALANCE_ON_GC == 'true') { + const validatorsPromises = filteredValidators.map(async (validator: Validator) => { + const [xdaiBalance] = await Promise.all([ + getNativeBalance(gnosisProvider, validator.address), + ]) + const xdaiToken: TokenBalance = { + type: TokenBalanceType.Native, + name: "XDAI", + balance: xdaiBalance + } + + return { + ...validator, + tokensBalances: [xdaiToken], + } + }) + return await Promise.all(validatorsPromises) + } else { + const validatorsPromises = filteredValidators.map(async (validator: Validator) => { + const [xdaiBalance, ethBalance] = await Promise.all([ + getNativeBalance(gnosisProvider, validator.address), + getNativeBalance(mainnetProvider, validator.address), + ]) + const xdaiToken: TokenBalance = { + type: TokenBalanceType.Native, + name: "XDAI", + balance: xdaiBalance + } + const ethToken: TokenBalance = { + type: TokenBalanceType.Native, + name: "ETH", + balance: ethBalance + } + return { + ...validator, + tokensBalances: [xdaiToken, ethToken], + } + }) + return await Promise.all(validatorsPromises) + } } export { fetchValidators } diff --git a/alerts/subgraph-config.js b/alerts/subgraph-config.js deleted file mode 100644 index 218d1cc4..00000000 --- a/alerts/subgraph-config.js +++ /dev/null @@ -1,28 +0,0 @@ -// eslint-disable-next-line @typescript-eslint/no-var-requires -const dotenv = require("dotenv") -dotenv.config() - -const codeGenOutDir = 'src/types/subgraph/subgraph.ts' - -const FOREIGN_ENDPOINT = process.env.SUBGRAPH_API_FOREIGN || '' -const NATIVE_ENDPOINT = process.env.SUBGRAPH_API_NATIVE || '' -const schemas = [NATIVE_ENDPOINT, FOREIGN_ENDPOINT] - -module.exports = { - overwrite: true, - schema: schemas, - documents: 'src/queries/**/*.ts', - generates: { - [codeGenOutDir]: { - plugins: [ - 'typescript', - 'typescript-operations', - 'typescript-graphql-request' - ], - }, - }, - config: { - rawRequest: false, - autogenSWRKey: true, - }, -} diff --git a/app/package.json b/app/package.json index 089d7e9d..331a7923 100644 --- a/app/package.json +++ b/app/package.json @@ -39,7 +39,7 @@ "graphql-request": "4.0.0", "graphql-tag": "2.12.6", "lodash": "4.18.1", - "next": "15.5.14", + "next": "15.5.15", "nextjs-google-analytics": "1.2.0", "nullthrows": "1.1.1", "react": "18.3.1", diff --git a/app/src/utils/validators/amb.json b/app/src/utils/validators/amb.json index 827370ac..ca0fb91e 100644 --- a/app/src/utils/validators/amb.json +++ b/app/src/utils/validators/amb.json @@ -25,7 +25,7 @@ }, { "id": 5, - "address": "0x674c97db4ce6cac04a124d745979f3e4cba0e9f0", + "address": "0xCC46a3873BfCaa08a6a946a308bB621535D6E6Dd", "name": "CoW Protocol", "bridgeType": "AMB", "shortName": "CP", @@ -71,4 +71,4 @@ "shortName": "GW", "status": "default" } -] +] \ No newline at end of file diff --git a/app/src/utils/validators/xdai.json b/app/src/utils/validators/xdai.json index 1bc0a9ed..ad31b0c8 100644 --- a/app/src/utils/validators/xdai.json +++ b/app/src/utils/validators/xdai.json @@ -25,7 +25,7 @@ }, { "id": 5, - "address": "0x587c0d02b40822f15f05301d87c16f6a08aaddde", + "address": "0xAeE7C90Ef0fC461ec63c4d451B12c340642bc656", "name": "CoW Protocol", "bridgeType": "XDAI", "shortName": "CP", @@ -71,4 +71,4 @@ "shortName": "GW", "status": "default" } -] +] \ No newline at end of file diff --git a/envio-indexer/package.json b/envio-indexer/package.json index bc33e4bf..5725842a 100644 --- a/envio-indexer/package.json +++ b/envio-indexer/package.json @@ -22,7 +22,7 @@ "typescript": "5.6.3" }, "dependencies": { - "envio": "2.32.10", + "envio": "2.32.12", "viem": "2.37.8" }, "optionalDependencies": { diff --git a/envio-indexer/src/seed/validators.json b/envio-indexer/src/seed/validators.json index 0813632f..489e9f76 100644 --- a/envio-indexer/src/seed/validators.json +++ b/envio-indexer/src/seed/validators.json @@ -49,16 +49,16 @@ "removed": false }, { - "address": "0x587c0d02b40822f15f05301d87c16f6a08aaddde", + "address": "0xAeE7C90Ef0fC461ec63c4d451B12c340642bc656", "bridgeType": "XDAI", - "id": "0x587c0d02b40822f15f05301d87c16f6a08aaddde-XDAI", + "id": "0xAeE7C90Ef0fC461ec63c4d451B12c340642bc656-XDAI", "name": "Cow Protocol", "removed": false }, { - "address": "0x674c97db4ce6cac04a124d745979f3e4cba0e9f0", + "address": "0xCC46a3873BfCaa08a6a946a308bB621535D6E6Dd", "bridgeType": "AMB", - "id": "0x674c97db4ce6cac04a124d745979f3e4cba0e9f0-AMB", + "id": "0xCC46a3873BfCaa08a6a946a308bB621535D6E6Dd-AMB", "name": "Cow Protocol", "removed": false }, @@ -132,4 +132,4 @@ "name": "Kleros", "removed": false } -] +] \ No newline at end of file diff --git a/package.json b/package.json index 77e51302..3a7e5458 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "indexer:build": "pnpm --filter envio-indexer build", "indexer:codegen": "pnpm --filter envio-indexer codegen", "indexer:start": "pnpm --filter envio-indexer start", - "indexer:stop": "pnpm --filter envio-indexer stop" + "indexer:stop": "pnpm --filter envio-indexer stop", + "audit": "pnpm audit --audit-level=high" }, "license": "MIT", "private": true, @@ -39,9 +40,10 @@ "serialize-javascript": "7.0.3", "body-parser": "1.20.3", "path-to-regexp": "0.1.13", - "defu": "6.1.7", - "lodash": "4.18.1", - "picomatch": "4.0.4" + "defu": "6.1.7", + "lodash": "4.18.1", + "picomatch": "4.0.4", + "axios": "1.15.0" } } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d82430c..ecf4b0ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,7 @@ overrides: defu: 6.1.7 lodash: 4.18.1 picomatch: 4.0.4 + axios: 1.15.0 importers: @@ -91,11 +92,11 @@ importers: specifier: 4.18.1 version: 4.18.1 next: - specifier: 15.5.14 - version: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 15.5.15 + version: 15.5.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextjs-google-analytics: specifier: 1.2.0 - version: 1.2.0(next@15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + version: 1.2.0(next@15.5.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) nullthrows: specifier: 1.1.1 version: 1.1.1 @@ -221,8 +222,8 @@ importers: envio-indexer: dependencies: envio: - specifier: 2.32.10 - version: 2.32.10(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) + specifier: 2.32.12 + version: 2.32.12(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) viem: specifier: 2.37.8 version: 2.37.8(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) @@ -257,7 +258,7 @@ importers: dependencies: '@rescript/react': specifier: 0.12.1 - version: 0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 0.12.1(react-dom@18.3.1(react@18.2.0))(react@18.2.0) bignumber.js: specifier: 9.1.2 version: 9.1.2 @@ -278,13 +279,13 @@ importers: version: 4.19.2 ink: specifier: 3.2.0 - version: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) + version: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6) ink-big-text: specifier: 1.2.0 - version: 1.2.0(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1) + version: 1.2.0(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6))(react@18.2.0) ink-spinner: specifier: 4.0.3 - version: 4.0.3(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1) + version: 4.0.3(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6))(react@18.2.0) js-sdsl: specifier: 4.4.2 version: 4.4.2 @@ -298,8 +299,8 @@ importers: specifier: 15.0.0 version: 15.0.0 react: - specifier: 18.3.1 - version: 18.3.1 + specifier: 18.2.0 + version: 18.2.0 rescript: specifier: 11.1.3 version: 11.1.3 @@ -572,8 +573,8 @@ packages: cpu: [arm64] os: [darwin] - '@envio-dev/hypersync-client-darwin-arm64@1.1.0': - resolution: {integrity: sha512-WO/yxYiN9nz7pHw7xLs/4hnEHQM5OVcTwvtxZTZHox7It3n7aeBzsJfrGYYqYW1bPxDwSYwb6x9l0/fQc59xeA==} + '@envio-dev/hypersync-client-darwin-arm64@1.3.0': + resolution: {integrity: sha512-JZwiVRbMSuJnKsVUpfjTHc3YgAMvGlyuqWQxVc7Eok4Xp/sZLUCXRQUykbCh6fOUWRmoa2JG/ykP/NotoTRCBg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -584,8 +585,8 @@ packages: cpu: [x64] os: [darwin] - '@envio-dev/hypersync-client-darwin-x64@1.1.0': - resolution: {integrity: sha512-BOD4gaZ6NAyLDDVgfIhpD25ikjEQpTXNBqbIYuTjWS1D0NiQD5mmyZCFcSMmaJJ0j4iobBQV1LMiNX8nDLahPA==} + '@envio-dev/hypersync-client-darwin-x64@1.3.0': + resolution: {integrity: sha512-2eSzQqqqFBMK2enVucYGcny5Ep4DEKYxf3Xme7z9qp2d3c6fMcbVvM4Gt8KOzb7ySjwJ2gU+qY2h545T2NiJXQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -596,8 +597,8 @@ packages: cpu: [arm64] os: [linux] - '@envio-dev/hypersync-client-linux-arm64-gnu@1.1.0': - resolution: {integrity: sha512-TDRpAGbfuI4u83Swy50IkGZSMaWXZw/SNC8BiMqLBmYVt1GqFDB1VGrkecf4AhQBPlQk8iDTOxxIKNuOCsY//A==} + '@envio-dev/hypersync-client-linux-arm64-gnu@1.3.0': + resolution: {integrity: sha512-gsjMp3WKekwnA89HvJXvcTM3BE5wVFG/qTF4rmk3rGiXhZ+MGaZQKrYRAhnzQZblueFtF/xnnBYpO35Z3ZFThg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -608,8 +609,8 @@ packages: cpu: [x64] os: [linux] - '@envio-dev/hypersync-client-linux-x64-gnu@1.1.0': - resolution: {integrity: sha512-ooOdLfrhq8W3J76pcJDWuXIpu9cTBjZR86XkkHaheDNkYQlBZ7h5oG3IhNTLnooFkih+6+9dfcfOdG4NMmylGA==} + '@envio-dev/hypersync-client-linux-x64-gnu@1.3.0': + resolution: {integrity: sha512-Lkvi4lRVwCyFOXf9LYH2X91zmW2l1vbfojKhTwKgqFWv6PMN5atlYjt+/NcUCAAhk5EUavWGjoikwnvLp870cg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -620,8 +621,8 @@ packages: cpu: [x64] os: [linux] - '@envio-dev/hypersync-client-linux-x64-musl@1.1.0': - resolution: {integrity: sha512-WS717WyRyTZPRMfYmscRslRIhxWNKzdQR3SLugqQs0z1W7wo58WMufhr5EpzID5/GR10zexz+Y3a4HWMAGiu+Q==} + '@envio-dev/hypersync-client-linux-x64-musl@1.3.0': + resolution: {integrity: sha512-UIjB/gUX2sl23EMXLBxqtkgMnOjNSiaHK+CSU5vXMXkzL3fOGbz24bvyaPsSv82cxCFEE0yTwlSKkCX6/L8o6Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -636,8 +637,8 @@ packages: resolution: {integrity: sha512-0r4lPFtk49zB94uvZiONV0SWdr9kigdNIYfYTYcSSuZ396E77tjskjMigDwimZsAA5Qf64x6MsIyzUYIzk/KPg==} engines: {node: '>= 10'} - '@envio-dev/hypersync-client@1.1.0': - resolution: {integrity: sha512-tTCW/fvYPFYIcL6SLcWd5Fe6uPCcXQbLgZaYL3NSvkhvrpw5VSx3M2QbHdpIrhmzoDkZdpIYyFD+pdZhC6Y75g==} + '@envio-dev/hypersync-client@1.3.0': + resolution: {integrity: sha512-wUdfZzbsFPbGq6n/1mmUMsWuiAil+m+fL/GBX5LGUyMJV86TXy2SBtAqYYNyDxWLO6gvGr6PYKrP8pLVAUZDZg==} engines: {node: '>= 10'} '@esbuild/aix-ppc64@0.19.12': @@ -1271,7 +1272,7 @@ packages: '@lit/react@1.0.8': resolution: {integrity: sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==} peerDependencies: - '@types/react': 17 || 18 || 19 + '@types/react': 17.0.2 '@lit/reactive-element@2.1.2': resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} @@ -1290,56 +1291,56 @@ packages: '@next/bundle-analyzer@12.2.1': resolution: {integrity: sha512-P5rj+mty9sB/58H32TFiLihy/W2Pd6vw9EorcpoyatI8tfpnyrBQOTW77QUFSeI3G4W9ovvv6ozUS/GkQvPwyQ==} - '@next/env@15.5.14': - resolution: {integrity: sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==} + '@next/env@15.5.15': + resolution: {integrity: sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg==} '@next/eslint-plugin-next@16.1.6': resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==} - '@next/swc-darwin-arm64@15.5.14': - resolution: {integrity: sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==} + '@next/swc-darwin-arm64@15.5.15': + resolution: {integrity: sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@15.5.14': - resolution: {integrity: sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==} + '@next/swc-darwin-x64@15.5.15': + resolution: {integrity: sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@15.5.14': - resolution: {integrity: sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==} + '@next/swc-linux-arm64-gnu@15.5.15': + resolution: {integrity: sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@15.5.14': - resolution: {integrity: sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==} + '@next/swc-linux-arm64-musl@15.5.15': + resolution: {integrity: sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@15.5.14': - resolution: {integrity: sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==} + '@next/swc-linux-x64-gnu@15.5.15': + resolution: {integrity: sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@15.5.14': - resolution: {integrity: sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==} + '@next/swc-linux-x64-musl@15.5.15': + resolution: {integrity: sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@15.5.14': - resolution: {integrity: sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==} + '@next/swc-win32-arm64-msvc@15.5.15': + resolution: {integrity: sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@15.5.14': - resolution: {integrity: sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==} + '@next/swc-win32-x64-msvc@15.5.15': + resolution: {integrity: sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2538,10 +2539,10 @@ packages: axios-retry@4.5.0: resolution: {integrity: sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==} peerDependencies: - axios: 0.x || 1.x + axios: 1.15.0 - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.15.0: + resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==} axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} @@ -3161,8 +3162,8 @@ packages: cpu: [arm64] os: [darwin] - envio-darwin-arm64@2.32.10: - resolution: {integrity: sha512-dqwC6Oduur1Micwbv9KSgtVyoNmGTVnucndJ3PFzUnen0VLR3gVn52VxbzF4h264dyS0lQ47PBpHeapuDVqNjA==} + envio-darwin-arm64@2.32.12: + resolution: {integrity: sha512-TLs9jjXUHVqKcBReMHgD7C06lbfWfnMkit3uT55XmgiJYc8zS85T0XmDCnCX4BRbZN7uzMNORqnUc2J3/LR9sQ==} cpu: [arm64] os: [darwin] @@ -3171,8 +3172,8 @@ packages: cpu: [x64] os: [darwin] - envio-darwin-x64@2.32.10: - resolution: {integrity: sha512-6rJkn1yDTt0fPwLvh+h15VGrON/aMIbcg2Wf3caTzTk0W+UpcW4JDagMSd5Tp5DocDziEkpo0h3DTiYqtwrsBw==} + envio-darwin-x64@2.32.12: + resolution: {integrity: sha512-JfKU3LaqxO/aabEAIvpHGKhDGNEiVGvcmmi98cZfG1/vP4S5lO+8KDEp563CaB986N6KtGJRKnDWivvCsseZMw==} cpu: [x64] os: [darwin] @@ -3181,8 +3182,8 @@ packages: cpu: [arm64] os: [linux] - envio-linux-arm64@2.32.10: - resolution: {integrity: sha512-6gBkRsEJtri5QTuh9c8AHahK+8oXt2Y3/yMbEde+NcrpSq6xdtW9sva8CkOwfm1hSlCK6vgRV3Kh9AzOH5tBbA==} + envio-linux-arm64@2.32.12: + resolution: {integrity: sha512-3sBfuR6JLcAkrFcoEfw2WiaPU3VyXGy4kf26HB5BJE/iJUqha+wHoDbv46MfFGuaC0QyM34QvlG0yGRES0ohPw==} cpu: [arm64] os: [linux] @@ -3191,8 +3192,8 @@ packages: cpu: [x64] os: [linux] - envio-linux-x64@2.32.10: - resolution: {integrity: sha512-wByqjv8eAaWz/lCf53n/+YCgSk/dBcmIDjoYub0X6mcVP8xoAFJlSj+PihMUPH93ADM/u45hCHj64kJWiFC2+w==} + envio-linux-x64@2.32.12: + resolution: {integrity: sha512-886q+yztKVrhgkwOfoFKARDStbjk1032YBtA6tqrCN8uWjqgzAf30ZDPurJGlq26hQqYNKRp2LhgxChpivsvFw==} cpu: [x64] os: [linux] @@ -3200,8 +3201,8 @@ packages: resolution: {integrity: sha512-JygGxP9yZLApF9vC205T/JQeU+u1vcqU1bZwAzG5XCNy3hwaKcc0Xivx8Q6Xx3g0BjeDbUUbrkkNTiZ6wb+gQQ==} hasBin: true - envio@2.32.10: - resolution: {integrity: sha512-ESx4+70p8XtjpBadavb7jqUE8hBxqtlQ7sWYqHx+fmARH7crKWGTwCJ4VopTvag+wG2XehkNb6s6Um5zFIbXFw==} + envio@2.32.12: + resolution: {integrity: sha512-bk9y/AjU+kYxO1a9c/jg8RFDrKKKWU0wCffnwtoXo7KGKmPDKq1WyNzVw6sTeboSfGB0i82hJ97WgSAwRAnR1Q==} hasBin: true error-ex@1.3.4: @@ -4517,8 +4518,8 @@ packages: next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} - next@15.5.14: - resolution: {integrity: sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==} + next@15.5.15: + resolution: {integrity: sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} hasBin: true peerDependencies: @@ -4859,8 +4860,9 @@ packages: proxy-compare@3.0.1: resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} @@ -5002,6 +5004,10 @@ packages: react: '>=15.0.0' react-dom: '>=15.0.0' + react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -6211,7 +6217,7 @@ snapshots: idb-keyval: 6.2.1 ox: 0.6.9(typescript@5.6.3)(zod@3.25.76) preact: 10.24.2 - viem: 2.37.8(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) + viem: 2.21.0(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) zustand: 5.0.3(@types/react@18.3.24)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) transitivePeerDependencies: - '@types/react' @@ -6234,8 +6240,8 @@ snapshots: '@solana/kit': 5.5.1(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6) '@solana/web3.js': 1.98.4(bufferutil@4.1.0)(encoding@0.1.13)(typescript@5.6.3)(utf-8-validate@6.0.6) abitype: 1.0.6(typescript@5.6.3)(zod@3.25.76) - axios: 1.13.5 - axios-retry: 4.5.0(axios@1.13.5) + axios: 1.15.0 + axios-retry: 4.5.0(axios@1.15.0) jose: 6.2.1 md5: 2.3.0 uncrypto: 0.1.3 @@ -6436,31 +6442,31 @@ snapshots: '@envio-dev/hypersync-client-darwin-arm64@0.6.6': optional: true - '@envio-dev/hypersync-client-darwin-arm64@1.1.0': + '@envio-dev/hypersync-client-darwin-arm64@1.3.0': optional: true '@envio-dev/hypersync-client-darwin-x64@0.6.6': optional: true - '@envio-dev/hypersync-client-darwin-x64@1.1.0': + '@envio-dev/hypersync-client-darwin-x64@1.3.0': optional: true '@envio-dev/hypersync-client-linux-arm64-gnu@0.6.6': optional: true - '@envio-dev/hypersync-client-linux-arm64-gnu@1.1.0': + '@envio-dev/hypersync-client-linux-arm64-gnu@1.3.0': optional: true '@envio-dev/hypersync-client-linux-x64-gnu@0.6.6': optional: true - '@envio-dev/hypersync-client-linux-x64-gnu@1.1.0': + '@envio-dev/hypersync-client-linux-x64-gnu@1.3.0': optional: true '@envio-dev/hypersync-client-linux-x64-musl@0.6.6': optional: true - '@envio-dev/hypersync-client-linux-x64-musl@1.1.0': + '@envio-dev/hypersync-client-linux-x64-musl@1.3.0': optional: true '@envio-dev/hypersync-client-win32-x64-msvc@0.6.6': @@ -6475,13 +6481,13 @@ snapshots: '@envio-dev/hypersync-client-linux-x64-musl': 0.6.6 '@envio-dev/hypersync-client-win32-x64-msvc': 0.6.6 - '@envio-dev/hypersync-client@1.1.0': + '@envio-dev/hypersync-client@1.3.0': optionalDependencies: - '@envio-dev/hypersync-client-darwin-arm64': 1.1.0 - '@envio-dev/hypersync-client-darwin-x64': 1.1.0 - '@envio-dev/hypersync-client-linux-arm64-gnu': 1.1.0 - '@envio-dev/hypersync-client-linux-x64-gnu': 1.1.0 - '@envio-dev/hypersync-client-linux-x64-musl': 1.1.0 + '@envio-dev/hypersync-client-darwin-arm64': 1.3.0 + '@envio-dev/hypersync-client-darwin-x64': 1.3.0 + '@envio-dev/hypersync-client-linux-arm64-gnu': 1.3.0 + '@envio-dev/hypersync-client-linux-x64-gnu': 1.3.0 + '@envio-dev/hypersync-client-linux-x64-musl': 1.3.0 '@esbuild/aix-ppc64@0.19.12': optional: true @@ -7466,34 +7472,34 @@ snapshots: - bufferutil - utf-8-validate - '@next/env@15.5.14': {} + '@next/env@15.5.15': {} '@next/eslint-plugin-next@16.1.6': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@15.5.14': + '@next/swc-darwin-arm64@15.5.15': optional: true - '@next/swc-darwin-x64@15.5.14': + '@next/swc-darwin-x64@15.5.15': optional: true - '@next/swc-linux-arm64-gnu@15.5.14': + '@next/swc-linux-arm64-gnu@15.5.15': optional: true - '@next/swc-linux-arm64-musl@15.5.14': + '@next/swc-linux-arm64-musl@15.5.15': optional: true - '@next/swc-linux-x64-gnu@15.5.14': + '@next/swc-linux-x64-gnu@15.5.15': optional: true - '@next/swc-linux-x64-musl@15.5.14': + '@next/swc-linux-x64-musl@15.5.15': optional: true - '@next/swc-win32-arm64-msvc@15.5.14': + '@next/swc-win32-arm64-msvc@15.5.15': optional: true - '@next/swc-win32-x64-msvc@15.5.14': + '@next/swc-win32-x64-msvc@15.5.15': optional: true '@noble/ciphers@1.3.0': {} @@ -7841,10 +7847,10 @@ snapshots: - utf-8-validate - zod - '@rescript/react@0.12.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@rescript/react@0.12.1(react-dom@18.3.1(react@18.2.0))(react@18.2.0)': dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 18.2.0 + react-dom: 18.3.1(react@18.2.0) '@rtsao/scc@1.1.0': {} @@ -9609,17 +9615,17 @@ snapshots: axe-core@4.11.1: {} - axios-retry@4.5.0(axios@1.13.5): + axios-retry@4.5.0(axios@1.15.0): dependencies: - axios: 1.13.5 + axios: 1.15.0 is-retry-allowed: 2.2.0 optional: true - axios@1.13.5: + axios@1.15.0: dependencies: follow-redirects: 1.15.11 form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug optional: true @@ -10227,25 +10233,25 @@ snapshots: envio-darwin-arm64@2.31.0: optional: true - envio-darwin-arm64@2.32.10: + envio-darwin-arm64@2.32.12: optional: true envio-darwin-x64@2.31.0: optional: true - envio-darwin-x64@2.32.10: + envio-darwin-x64@2.32.12: optional: true envio-linux-arm64@2.31.0: optional: true - envio-linux-arm64@2.32.10: + envio-linux-arm64@2.32.12: optional: true envio-linux-x64@2.31.0: optional: true - envio-linux-x64@2.32.10: + envio-linux-x64@2.32.12: optional: true envio@2.31.0(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76): @@ -10271,11 +10277,11 @@ snapshots: - utf-8-validate - zod - envio@2.32.10(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76): + envio@2.32.12(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76): dependencies: '@elastic/ecs-pino-format': 1.4.0 '@envio-dev/hyperfuel-client': 1.2.2 - '@envio-dev/hypersync-client': 1.1.0 + '@envio-dev/hypersync-client': 1.3.0 bignumber.js: 9.1.2 eventsource: 4.1.0 pino: 8.16.1 @@ -10285,10 +10291,10 @@ snapshots: rescript-schema: 9.3.0(rescript@11.1.3) viem: 2.21.0(bufferutil@4.1.0)(typescript@5.6.3)(utf-8-validate@6.0.6)(zod@3.25.76) optionalDependencies: - envio-darwin-arm64: 2.32.10 - envio-darwin-x64: 2.32.10 - envio-linux-arm64: 2.32.10 - envio-linux-x64: 2.32.10 + envio-darwin-arm64: 2.32.12 + envio-darwin-x64: 2.32.12 + envio-linux-arm64: 2.32.12 + envio-linux-x64: 2.32.12 transitivePeerDependencies: - bufferutil - typescript @@ -11307,20 +11313,20 @@ snapshots: ini@1.3.8: {} - ink-big-text@1.2.0(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1): + ink-big-text@1.2.0(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6))(react@18.2.0): dependencies: cfonts: 2.10.1 - ink: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) + ink: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6) prop-types: 15.8.1 - react: 18.3.1 + react: 18.2.0 - ink-spinner@4.0.3(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6))(react@18.3.1): + ink-spinner@4.0.3(ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6))(react@18.2.0): dependencies: cli-spinners: 2.9.2 - ink: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6) - react: 18.3.1 + ink: 3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6) + react: 18.2.0 - ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.3.1)(utf-8-validate@6.0.6): + ink@3.2.0(@types/react@18.3.24)(bufferutil@4.1.0)(react@18.2.0)(utf-8-validate@6.0.6): dependencies: ansi-escapes: 4.3.2 auto-bind: 4.0.0 @@ -11333,9 +11339,9 @@ snapshots: is-ci: 2.0.0 lodash: 4.18.1 patch-console: 1.0.0 - react: 18.3.1 + react: 18.2.0 react-devtools-core: 4.28.5(bufferutil@4.1.0)(utf-8-validate@6.0.6) - react-reconciler: 0.26.2(react@18.3.1) + react-reconciler: 0.26.2(react@18.2.0) scheduler: 0.20.2 signal-exit: 3.0.7 slice-ansi: 3.0.0 @@ -11946,9 +11952,9 @@ snapshots: next-tick@1.1.0: {} - next@15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.5.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 15.5.14 + '@next/env': 15.5.15 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001777 postcss: 8.4.31 @@ -11956,23 +11962,23 @@ snapshots: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.6(@babel/core@7.29.0)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 15.5.14 - '@next/swc-darwin-x64': 15.5.14 - '@next/swc-linux-arm64-gnu': 15.5.14 - '@next/swc-linux-arm64-musl': 15.5.14 - '@next/swc-linux-x64-gnu': 15.5.14 - '@next/swc-linux-x64-musl': 15.5.14 - '@next/swc-win32-arm64-msvc': 15.5.14 - '@next/swc-win32-x64-msvc': 15.5.14 + '@next/swc-darwin-arm64': 15.5.15 + '@next/swc-darwin-x64': 15.5.15 + '@next/swc-linux-arm64-gnu': 15.5.15 + '@next/swc-linux-arm64-musl': 15.5.15 + '@next/swc-linux-x64-gnu': 15.5.15 + '@next/swc-linux-x64-musl': 15.5.15 + '@next/swc-win32-arm64-msvc': 15.5.15 + '@next/swc-win32-x64-msvc': 15.5.15 '@opentelemetry/api': 1.9.0 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextjs-google-analytics@1.2.0(next@15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): + nextjs-google-analytics@1.2.0(next@15.5.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): dependencies: - next: 15.5.14(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.5.15(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 node-exports-info@1.6.0: @@ -12357,7 +12363,7 @@ snapshots: proxy-compare@3.0.1: {} - proxy-from-env@1.1.0: + proxy-from-env@2.1.0: optional: true pump@3.0.4: @@ -12426,6 +12432,12 @@ snapshots: - bufferutil - utf-8-validate + react-dom@18.3.1(react@18.2.0): + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.2 + react-dom@18.3.1(react@18.3.1): dependencies: loose-envify: 1.4.0 @@ -12470,11 +12482,11 @@ snapshots: react-fast-compare: 3.2.2 warning: 4.0.3 - react-reconciler@0.26.2(react@18.3.1): + react-reconciler@0.26.2(react@18.2.0): dependencies: loose-envify: 1.4.0 object-assign: 4.1.1 - react: 18.3.1 + react: 18.2.0 scheduler: 0.20.2 react-resize-detector@7.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -12507,6 +12519,10 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-lifecycles-compat: 3.0.4 + react@18.2.0: + dependencies: + loose-envify: 1.4.0 + react@18.3.1: dependencies: loose-envify: 1.4.0