From 104dbb3e962994e44473324c58a687dd658e91a9 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 10 Feb 2025 14:25:04 +1100 Subject: [PATCH 01/33] Initial scaffold --- database/schema.ts | 33 +++-- env.ts | 27 +++-- index.ts | 133 +++++++++------------ terraform/elasticache.tf | 48 ++++++++ terraform/environments/main.backend.tfvars | 3 + terraform/environments/main.tfvars | 13 ++ terraform/environments/test.backend.tfvars | 3 + terraform/environments/test.tfvars | 13 ++ terraform/eventbridge.tf | 24 ++++ terraform/iam.tf | 11 ++ terraform/lambda.tf | 86 +++++++++++++ terraform/main.tf | 25 ++++ terraform/rds.tf | 3 + terraform/secrets.tf | 15 +++ terraform/var.tf | 70 +++++++++++ 15 files changed, 398 insertions(+), 109 deletions(-) create mode 100644 terraform/elasticache.tf create mode 100644 terraform/environments/main.backend.tfvars create mode 100644 terraform/environments/main.tfvars create mode 100644 terraform/environments/test.backend.tfvars create mode 100644 terraform/environments/test.tfvars create mode 100644 terraform/eventbridge.tf create mode 100644 terraform/iam.tf create mode 100644 terraform/lambda.tf create mode 100644 terraform/main.tf create mode 100644 terraform/rds.tf create mode 100644 terraform/secrets.tf create mode 100644 terraform/var.tf diff --git a/database/schema.ts b/database/schema.ts index 847489b..edc8105 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -1,21 +1,18 @@ -import { env } from "../env"; +import { env } from '../env' -import { integer, text, boolean, pgSchema } from "drizzle-orm/pg-core"; +import { integer, text, boolean, pgSchema } from 'drizzle-orm/pg-core' -export const schema = pgSchema("test" as string); //TODO: change to env.DB_SCHEMA +export const schema = pgSchema(env.DB_SCHEMA || 'test') -export const fractalityTokenMigrations = schema.table( - "fractality_token_migrations", - { - transactionHash: text("transaction_hash").primaryKey(), - migrationContractAddress: text("migration_contract_address"), - eventName: text("event_name"), - caller: text("caller"), - migrationAddress: text("migration_address"), - amount: text("amount"), - foundAt: text("found_at"), - status: text("status"), - migratedAt: text("migrated_at"), - migratedAmount: text("migrated_amount"), - } -); +export const fractalityTokenMigrations = schema.table('fractality_token_migrations', { + transactionHash: text('transaction_hash').primaryKey(), + migrationContractAddress: text('migration_contract_address'), + eventName: text('event_name'), + caller: text('caller'), + migrationAddress: text('migration_address'), + amount: text('amount'), + foundAt: text('found_at'), + status: text('status'), + migratedAt: text('migrated_at'), + migratedAmount: text('migrated_amount') +}) diff --git a/env.ts b/env.ts index dea86ee..28318e9 100644 --- a/env.ts +++ b/env.ts @@ -1,6 +1,6 @@ -import { z } from "zod"; -import dotenv from "dotenv"; -dotenv.config(); +import { z } from 'zod' +import dotenv from 'dotenv' +dotenv.config() const envSchema = z.object({ PRIVATE_KEY: z.string(), @@ -18,25 +18,26 @@ const envSchema = z.object({ DB_PORT: z.coerce.number().optional(), DB_USER: z.string().optional(), DB_PASSWORD: z.string().optional(), + DB_SCHEMA: z.string().optional(), BLOCKCHAIN_ENVIRONMENT: z.string(), BLOCK_START_NUMBER: z.string(), SAFETY_CUSHION_NUMBER_OF_BLOCKS: z.coerce.number(), REDIS_CONNECTION_STRING: z.string(), - REDIS_USE_TLS: z.preprocess((str) => str === "true", z.boolean()), -}); + REDIS_USE_TLS: z.preprocess((str) => str === 'true', z.boolean()) +}) -const parsedEnv = envSchema.safeParse(process.env); +const parsedEnv = envSchema.safeParse(process.env) if (!parsedEnv.success) { const formattedErrors = parsedEnv.error.errors.map((err) => ({ - path: err.path.join("."), - message: err.message, - })); + path: err.path.join('.'), + message: err.message + })) - console.error("Environment variable validation failed:", formattedErrors); - throw new Error("Invalid environment variables."); + console.error('Environment variable validation failed:', formattedErrors) + throw new Error('Invalid environment variables.') } -export type BotEnv = z.infer; +export type BotEnv = z.infer -export const env = parsedEnv.data; +export const env = parsedEnv.data diff --git a/index.ts b/index.ts index 70cadb7..e0e9861 100644 --- a/index.ts +++ b/index.ts @@ -1,122 +1,104 @@ -import { PrivateKeyManager } from "./libs/PrivateKeyManager"; -import IORedis from "ioredis"; -const privateKeyManager = new PrivateKeyManager(); -import { env } from "./env"; -import { BlockchainConnectionProvider } from "./libs/BlockchainConnectionProvider"; -import { Address } from "viem"; +import { PrivateKeyManager } from './libs/PrivateKeyManager' +import IORedis from 'ioredis' +const privateKeyManager = new PrivateKeyManager() +import { env } from './env' +import { BlockchainConnectionProvider } from './libs/BlockchainConnectionProvider' +import { Address } from 'viem' import { addFractalityTokenMigrations, getUnmigratedFractalityTokenMigrations, finalizeHlMigrations, setHLMigrationStatus, - TokenMigration, -} from "./database"; -import { - HLMigration, - MigrationRegisteredEvent, - MigrationStatus, -} from "./interfaces"; -import { initializeDatabaseConnection } from "./database"; -import cron from "node-cron"; -import { PreviousBlockManager } from "./libs/PreviousBlockManager"; -import { HyperliquidManager } from "./libs/HyperliquidManager"; + TokenMigration +} from './database' +import { HLMigration, MigrationRegisteredEvent, MigrationStatus } from './interfaces' +import { initializeDatabaseConnection } from './database' +import cron from 'node-cron' +import { PreviousBlockManager } from './libs/PreviousBlockManager' +import { HyperliquidManager } from './libs/HyperliquidManager' + async function main() { - await privateKeyManager.init(); - await initializeDatabaseConnection(); + await privateKeyManager.init() + await initializeDatabaseConnection() - const redisConnection = await initRedisConnection(); + const redisConnection = await initRedisConnection() - const hlManager = new HyperliquidManager( - true, - true, - privateKeyManager.getPrivateKey() - ); + const hlManager = new HyperliquidManager(true, true, privateKeyManager.getPrivateKey()) const blockchainConnectionProvider = new BlockchainConnectionProvider({ providerUrl: env.PROVIDER_URL, y2kTokenMigrationAddress: env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, - frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address, - }); + frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address + }) - await hlManager.init( - await blockchainConnectionProvider.getArbitrumTokenDecimals() - ); + await hlManager.init(await blockchainConnectionProvider.getArbitrumTokenDecimals()) const blockManager = new PreviousBlockManager( redisConnection, BigInt(env.SAFETY_CUSHION_NUMBER_OF_BLOCKS), () => blockchainConnectionProvider.getCurrentBlockNumber() - ); + ) - console.log("starting cron job for migrations, running every 5 minutes"); - cron.schedule("* * * * *", async () => { - const fromBlock = await blockManager.getFromBlockForScan(); - const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); - console.log( - `looking for migrations from block ${fromBlock} to block ${toBlock}` - ); + console.log('starting cron job for migrations, running every 5 minutes') + cron.schedule('* * * * *', async () => { + const fromBlock = await blockManager.getFromBlockForScan() + const toBlock = await blockManager.setFromBlockForScanToCurrentBlock() + console.log(`looking for migrations from block ${fromBlock} to block ${toBlock}`) const y2kMigrations = await blockchainConnectionProvider.scanMigrations( env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ); + ) const frctRMigrations = await blockchainConnectionProvider.scanMigrations( env.FRCT_R_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ); - await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); + ) + await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]) //get migrations that still have not been sent to hyperliquid - const unmigratedMigrations: TokenMigration[] = - await getUnmigratedFractalityTokenMigrations(); + const unmigratedMigrations: TokenMigration[] = await getUnmigratedFractalityTokenMigrations() //calcualate the amount of tokens to send to hyperliquid - const hlMigrations = await prepForHLMigration( - hlManager, - unmigratedMigrations - ); - console.log("hlMigrations", hlMigrations); - - const { successes, failures } = await hlManager.sendHLMigrations( - hlMigrations - ); - console.log("successes", successes); - console.log("failures", failures); + const hlMigrations = await prepForHLMigration(hlManager, unmigratedMigrations) + console.log('hlMigrations', hlMigrations) + + const { successes, failures } = await hlManager.sendHLMigrations(hlMigrations) + console.log('successes', successes) + console.log('failures', failures) try { - await finalizeHlMigrations(successes); + await finalizeHlMigrations(successes) } catch (error) { - console.error("FATAL ERROR: Error finalizing HL migrations", error); + console.error('FATAL ERROR: Error finalizing HL migrations', error) } - console.log("done"); - }); + console.log('done') + }) } async function prepForHLMigration( hlManager: HyperliquidManager, unmigratedMigrations: TokenMigration[] ): Promise { - const hlMigrations: HLMigration[] = []; + const hlMigrations: HLMigration[] = [] for (const unmigratedMigration of unmigratedMigrations) { if (unmigratedMigration.amount && unmigratedMigration.migrationAddress) { - const arbitrumAmount = BigInt(unmigratedMigration.amount); - const hlAmount = - hlManager.decimalConversion!.convertToHlToken(arbitrumAmount); + const arbitrumAmount = BigInt(unmigratedMigration.amount) + const hlAmount = hlManager.decimalConversion!.convertToHlToken(arbitrumAmount) hlMigrations.push({ originalTransactionHash: unmigratedMigration.transactionHash, hlTokenAmount: hlAmount, - sendToAddress: unmigratedMigration.migrationAddress, - }); + sendToAddress: unmigratedMigration.migrationAddress + }) } else { console.error( `migration with hash ${unmigratedMigration.transactionHash} has no amount or no migration address` - ); + ) } } - return hlMigrations; + return hlMigrations } async function initRedisConnection() { @@ -125,25 +107,20 @@ async function initRedisConnection() { enableReadyCheck: false, tls: env.REDIS_USE_TLS ? { - rejectUnauthorized: false, + rejectUnauthorized: false } - : undefined, - }); + : undefined + }) } async function addMigrationsToDatabase(migrations: MigrationRegisteredEvent[]) { try { - const result = await addFractalityTokenMigrations(migrations); //TODO: make this batch + const result = await addFractalityTokenMigrations(migrations) //TODO: make this batch console.log( `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` - ); - console.info( - `existing migrations that already exist in the database`, - result.existingTxs - ); + ) + console.info(`existing migrations that already exist in the database`, result.existingTxs) } catch (e) { - console.error("Error adding migration to database", e); + console.error('Error adding migration to database', e) } } - -main(); diff --git a/terraform/elasticache.tf b/terraform/elasticache.tf new file mode 100644 index 0000000..7b6d6c2 --- /dev/null +++ b/terraform/elasticache.tf @@ -0,0 +1,48 @@ +resource "aws_elasticache_subnet_group" "redis_subnet" { + name = "${var.name}-redis-subnet-${var.environment}" + description = "Subnet group for Redis" + subnet_ids = var.subnet_ids +} + +resource "aws_security_group" "redis_sg" { + name = "${var.name}-redis-sg-${var.environment}" + description = "Security group for Redis" + vpc_id = var.vpc_id + + ingress { + description = "Allow Lambda to connect on Redis port" + from_port = 6379 + to_port = 6379 + protocol = "tcp" + security_groups = [aws_security_group.lambda_sg.id] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.name}-redis-sg" + } +} + +resource "aws_elasticache_replication_group" "redis_cluster" { + replication_group_id = "${var.name}-redis-${var.environment}" + description = "{var.name} cluster for ${var.environment}" + engine = "redis" + engine_version = "6.x" + security_group_ids = [ + aws_security_group.redis_sg.id, + aws_security_group.lambda_sg.id + ] + subnet_group_name = aws_elasticache_subnet_group.redis_subnet.name + automatic_failover_enabled = true + + + tags = { + Name = "${var.name}-redis" + } +} diff --git a/terraform/environments/main.backend.tfvars b/terraform/environments/main.backend.tfvars new file mode 100644 index 0000000..0a204f2 --- /dev/null +++ b/terraform/environments/main.backend.tfvars @@ -0,0 +1,3 @@ +bucket = "fractality-backend-terraform" +key = "migration-service/mainnet.tfstate" +region = "ap-southeast-1" diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars new file mode 100644 index 0000000..4b563ea --- /dev/null +++ b/terraform/environments/main.tfvars @@ -0,0 +1,13 @@ +name = "migration-service" +project = "fractality" +environment = "main" +vpc_id = "" +subnet_ids = [] + +provider_url = "" +public_address = "" +token_address = "" +y2k_token_migration_address = "" +frct_r_migration_address = "" +block_start_number = "" +safe_cushion_number_of_blocks = "" diff --git a/terraform/environments/test.backend.tfvars b/terraform/environments/test.backend.tfvars new file mode 100644 index 0000000..351ae09 --- /dev/null +++ b/terraform/environments/test.backend.tfvars @@ -0,0 +1,3 @@ +bucket = "fractality-backend-terraform" +key = "migration-service/testnet.tfstate" +region = "ap-southeast-1" diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars new file mode 100644 index 0000000..f334cbe --- /dev/null +++ b/terraform/environments/test.tfvars @@ -0,0 +1,13 @@ +name = "migration-service-test" +project = "fractality" +environment = "test" +vpc_id = "" +subnet_ids = [] + +provider_url = "" +public_address = "" +token_address = "" +y2k_token_migration_address = "" +frct_r_migration_address = "" +block_start_number = "" +safe_cushion_number_of_blocks = "" diff --git a/terraform/eventbridge.tf b/terraform/eventbridge.tf new file mode 100644 index 0000000..fd1b8c7 --- /dev/null +++ b/terraform/eventbridge.tf @@ -0,0 +1,24 @@ +variable "schedule_expression" { + type = string + description = "Schedule expression for EventBridge trigger" + default = "rate(5 minutes)" +} + +resource "aws_cloudwatch_event_rule" "lambda_schedule_rule" { + name = "${var.name}-lambda-schedule-${var.environment}" + schedule_expression = var.schedule_expression +} + +resource "aws_cloudwatch_event_target" "lambda_schedule_target" { + rule = aws_cloudwatch_event_rule.lambda_schedule_rule.name + arn = aws_lambda_function.default.arn + target_id = "invoke-lambda" +} + +resource "aws_lambda_permission" "allow_cloudwatch" { + statement_id = "AllowExecutionFromCloudWatch" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.default.function_name + principal = "events.amazonaws.com" + source_arn = aws_cloudwatch_event_rule.lambda_schedule_rule.arn +} diff --git a/terraform/iam.tf b/terraform/iam.tf new file mode 100644 index 0000000..fe83e70 --- /dev/null +++ b/terraform/iam.tf @@ -0,0 +1,11 @@ +data "aws_iam_policy_document" "lambda_assume_role" { + statement { + effect = "Allow" + actions = ["sts:AssumeRole"] + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + diff --git a/terraform/lambda.tf b/terraform/lambda.tf new file mode 100644 index 0000000..36ab04c --- /dev/null +++ b/terraform/lambda.tf @@ -0,0 +1,86 @@ +locals { + secret_data = jsondecode(data.aws_secretsmanager_secret_version.db.secret_string) + db_username = local.secret_data["USERNAME"] + db_password = local.secret_data["PASSWORD"] + db_schema = var.environment == "main" ? "main" : "test" + db_name = var.db_name +} + +resource "aws_security_group" "lambda_sg" { + name = "${var.name}-lambda-sg-${var.environment}" + description = "Security group for Lambda functions" + vpc_id = "vpc-08ae44a5cd755d8b0" + + ingress { + description = "Allow Lambda to access RDS on port 5432" + from_port = 5432 + to_port = 5432 + protocol = "tcp" + security_groups = ["sg-03117204f06e38ba8"] + } + + egress { + description = "Allow all outbound traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "${var.name}-lambda-sg" + } +} + +resource "aws_lambda_function" "default" { + function_name = var.name + handler = "index.handler" + runtime = "nodejs18.x" + role = aws_iam_role.default.arn + filename = "${path.module}/../dist/bundle.zip" + source_code_hash = filebase64sha256("${path.module}/../dist/bundle.zip") + timeout = 300 + + environment { + variables = { + PROVIDER_URL = var.provider_url + BLOCKCHAIN_ENVIRONMENT = var.environment + PUBLIC_ADDRESS = var.public_address + TOKEN_ADDRESS = var.token_address + MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn + DB_USER = local.db_username + DB_PASSWORD = local.db_password + DB_HOST = data.aws_db_instance.db.address + DB_NAME = local.db_name + DB_SCHEMA = local.db_schema + Y2K_TOKEN_MIGRATION_ADDRESS = var.y2k_token_migration_address + FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address + BLOCK_START_NUMBER = var.block_start_number + SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${aws_elasticache_replication_group.redis_cluster.port}" + REDIS_USE_TLS = "true" + } + } + + vpc_config { + subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] + security_group_ids = [aws_security_group.lambda_sg.id] + } +} + + +resource "aws_iam_role" "default" { + name = "${var.name}-service-role" + assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json +} + +resource "aws_iam_role_policy_attachment" "lambda_vpc_access" { + role = aws_iam_role.default.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" +} + +resource "aws_iam_role_policy_attachment" "lambda_policy" { + role = aws_iam_role.default.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" +} + diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..86697ee --- /dev/null +++ b/terraform/main.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "5.62.0" + } + } + backend "s3" { + encrypt = true + } +} + +provider "aws" { + region = var.region + default_tags { + tags = { + Name = var.name + Project = var.project + Environment = var.environment + Version = var.project_version + } + } +} + +data "aws_caller_identity" "current" {} diff --git a/terraform/rds.tf b/terraform/rds.tf new file mode 100644 index 0000000..1c044c3 --- /dev/null +++ b/terraform/rds.tf @@ -0,0 +1,3 @@ +data "aws_db_instance" "db" { + db_instance_identifier = var.db_instance_id +} diff --git a/terraform/secrets.tf b/terraform/secrets.tf new file mode 100644 index 0000000..6bd1498 --- /dev/null +++ b/terraform/secrets.tf @@ -0,0 +1,15 @@ +data "aws_secretsmanager_secret" "db" { + name = "FRACTALITY_DB" +} + +data "aws_secretsmanager_secret_version" "db" { + secret_id = data.aws_secretsmanager_secret.db.id +} + +data "aws_secretsmanager_secret" "mnemonic" { + name = "FRACTALITY_DB_${var.environment}" +} + +data "aws_secretsmanager_secret_version" "mnemonic" { + secret_id = data.aws_secretsmanager_secret.mnemonic.id +} diff --git a/terraform/var.tf b/terraform/var.tf new file mode 100644 index 0000000..d314dd4 --- /dev/null +++ b/terraform/var.tf @@ -0,0 +1,70 @@ +variable "account_id" { + type = string + description = "AWS account ID" +} + +variable "region" { + type = string + description = "AWS region" +} + +variable "environment" { + type = string +} + +variable "name" { + type = string +} + +variable "vpc_id" { + type = string +} + +variable "subnet_ids" { + type = list(string) + default = [] +} + +variable "project" { + type = string +} + +variable "project_version" { + type = string +} + +variable "db_name" { + type = string +} + +variable "db_instance_id" { + type = string +} + +variable "provider_url" { + type = string +} + +variable "public_address" { + type = string +} + +variable "token_address" { + type = string +} + +variable "y2k_token_migration_address" { + type = string +} + +variable "frct_r_migration_address" { + type = string +} + +variable "block_start_number" { + type = string +} + +variable "safety_cushion_number_of_blocks" { + type = string +} From 57e90cfdee01b83f0cc5041dcadb503c8f5a3116 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 10 Feb 2025 14:29:45 +1100 Subject: [PATCH 02/33] Rename mnemonic secret --- terraform/secrets.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/secrets.tf b/terraform/secrets.tf index 6bd1498..d286401 100644 --- a/terraform/secrets.tf +++ b/terraform/secrets.tf @@ -7,7 +7,7 @@ data "aws_secretsmanager_secret_version" "db" { } data "aws_secretsmanager_secret" "mnemonic" { - name = "FRACTALITY_DB_${var.environment}" + name = "FRACTALITY_MIGRATION_${var.environment}" } data "aws_secretsmanager_secret_version" "mnemonic" { From 2d51164ae9b1233c4b06c836bc7ad6be659c40a5 Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Wed, 12 Feb 2025 18:10:09 -0500 Subject: [PATCH 03/33] format for easier reading (for jose), we need to get the same styling going. --- database/schema.ts | 33 ++++++------ env.ts | 26 ++++----- index.ts | 130 ++++++++++++++++++++++++++------------------- interfaces.ts | 7 ++- 4 files changed, 110 insertions(+), 86 deletions(-) diff --git a/database/schema.ts b/database/schema.ts index edc8105..a466894 100644 --- a/database/schema.ts +++ b/database/schema.ts @@ -1,18 +1,21 @@ -import { env } from '../env' +import { env } from "../env"; -import { integer, text, boolean, pgSchema } from 'drizzle-orm/pg-core' +import { integer, text, boolean, pgSchema } from "drizzle-orm/pg-core"; -export const schema = pgSchema(env.DB_SCHEMA || 'test') +export const schema = pgSchema(env.DB_SCHEMA || "test"); -export const fractalityTokenMigrations = schema.table('fractality_token_migrations', { - transactionHash: text('transaction_hash').primaryKey(), - migrationContractAddress: text('migration_contract_address'), - eventName: text('event_name'), - caller: text('caller'), - migrationAddress: text('migration_address'), - amount: text('amount'), - foundAt: text('found_at'), - status: text('status'), - migratedAt: text('migrated_at'), - migratedAmount: text('migrated_amount') -}) +export const fractalityTokenMigrations = schema.table( + "fractality_token_migrations", + { + transactionHash: text("transaction_hash").primaryKey(), + migrationContractAddress: text("migration_contract_address"), + eventName: text("event_name"), + caller: text("caller"), + migrationAddress: text("migration_address"), + amount: text("amount"), + foundAt: text("found_at"), + status: text("status"), + migratedAt: text("migrated_at"), + migratedAmount: text("migrated_amount"), + } +); diff --git a/env.ts b/env.ts index 28318e9..7921426 100644 --- a/env.ts +++ b/env.ts @@ -1,6 +1,6 @@ -import { z } from 'zod' -import dotenv from 'dotenv' -dotenv.config() +import { z } from "zod"; +import dotenv from "dotenv"; +dotenv.config(); const envSchema = z.object({ PRIVATE_KEY: z.string(), @@ -23,21 +23,21 @@ const envSchema = z.object({ BLOCK_START_NUMBER: z.string(), SAFETY_CUSHION_NUMBER_OF_BLOCKS: z.coerce.number(), REDIS_CONNECTION_STRING: z.string(), - REDIS_USE_TLS: z.preprocess((str) => str === 'true', z.boolean()) -}) + REDIS_USE_TLS: z.preprocess((str) => str === "true", z.boolean()), +}); -const parsedEnv = envSchema.safeParse(process.env) +const parsedEnv = envSchema.safeParse(process.env); if (!parsedEnv.success) { const formattedErrors = parsedEnv.error.errors.map((err) => ({ - path: err.path.join('.'), - message: err.message - })) + path: err.path.join("."), + message: err.message, + })); - console.error('Environment variable validation failed:', formattedErrors) - throw new Error('Invalid environment variables.') + console.error("Environment variable validation failed:", formattedErrors); + throw new Error("Invalid environment variables."); } -export type BotEnv = z.infer +export type BotEnv = z.infer; -export const env = parsedEnv.data +export const env = parsedEnv.data; diff --git a/index.ts b/index.ts index e0e9861..c54cd81 100644 --- a/index.ts +++ b/index.ts @@ -1,104 +1,123 @@ -import { PrivateKeyManager } from './libs/PrivateKeyManager' -import IORedis from 'ioredis' -const privateKeyManager = new PrivateKeyManager() -import { env } from './env' -import { BlockchainConnectionProvider } from './libs/BlockchainConnectionProvider' -import { Address } from 'viem' +import { PrivateKeyManager } from "./libs/PrivateKeyManager"; +import IORedis from "ioredis"; +const privateKeyManager = new PrivateKeyManager(); +import { env } from "./env"; +import { BlockchainConnectionProvider } from "./libs/BlockchainConnectionProvider"; +import { Address } from "viem"; import { addFractalityTokenMigrations, getUnmigratedFractalityTokenMigrations, finalizeHlMigrations, setHLMigrationStatus, - TokenMigration -} from './database' -import { HLMigration, MigrationRegisteredEvent, MigrationStatus } from './interfaces' -import { initializeDatabaseConnection } from './database' -import cron from 'node-cron' -import { PreviousBlockManager } from './libs/PreviousBlockManager' -import { HyperliquidManager } from './libs/HyperliquidManager' + TokenMigration, +} from "./database"; +import { + HLMigration, + MigrationRegisteredEvent, + MigrationStatus, +} from "./interfaces"; +import { initializeDatabaseConnection } from "./database"; +import cron from "node-cron"; +import { PreviousBlockManager } from "./libs/PreviousBlockManager"; +import { HyperliquidManager } from "./libs/HyperliquidManager"; async function main() { - await privateKeyManager.init() - await initializeDatabaseConnection() + await privateKeyManager.init(); + await initializeDatabaseConnection(); - const redisConnection = await initRedisConnection() + const redisConnection = await initRedisConnection(); - const hlManager = new HyperliquidManager(true, true, privateKeyManager.getPrivateKey()) + const hlManager = new HyperliquidManager( + true, + true, + privateKeyManager.getPrivateKey() + ); const blockchainConnectionProvider = new BlockchainConnectionProvider({ providerUrl: env.PROVIDER_URL, y2kTokenMigrationAddress: env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, - frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address - }) + frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address, + }); - await hlManager.init(await blockchainConnectionProvider.getArbitrumTokenDecimals()) + await hlManager.init( + await blockchainConnectionProvider.getArbitrumTokenDecimals() + ); const blockManager = new PreviousBlockManager( redisConnection, BigInt(env.SAFETY_CUSHION_NUMBER_OF_BLOCKS), () => blockchainConnectionProvider.getCurrentBlockNumber() - ) + ); - console.log('starting cron job for migrations, running every 5 minutes') - cron.schedule('* * * * *', async () => { - const fromBlock = await blockManager.getFromBlockForScan() - const toBlock = await blockManager.setFromBlockForScanToCurrentBlock() - console.log(`looking for migrations from block ${fromBlock} to block ${toBlock}`) + console.log("starting cron job for migrations, running every 5 minutes"); + cron.schedule("* * * * *", async () => { + const fromBlock = await blockManager.getFromBlockForScan(); + const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); + console.log( + `looking for migrations from block ${fromBlock} to block ${toBlock}` + ); const y2kMigrations = await blockchainConnectionProvider.scanMigrations( env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ) + ); const frctRMigrations = await blockchainConnectionProvider.scanMigrations( env.FRCT_R_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ) - await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]) + ); + await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); //get migrations that still have not been sent to hyperliquid - const unmigratedMigrations: TokenMigration[] = await getUnmigratedFractalityTokenMigrations() + const unmigratedMigrations: TokenMigration[] = + await getUnmigratedFractalityTokenMigrations(); //calcualate the amount of tokens to send to hyperliquid - const hlMigrations = await prepForHLMigration(hlManager, unmigratedMigrations) - console.log('hlMigrations', hlMigrations) - - const { successes, failures } = await hlManager.sendHLMigrations(hlMigrations) - console.log('successes', successes) - console.log('failures', failures) + const hlMigrations = await prepForHLMigration( + hlManager, + unmigratedMigrations + ); + console.log("hlMigrations", hlMigrations); + + const { successes, failures } = await hlManager.sendHLMigrations( + hlMigrations + ); + console.log("successes", successes); + console.log("failures", failures); try { - await finalizeHlMigrations(successes) + await finalizeHlMigrations(successes); } catch (error) { - console.error('FATAL ERROR: Error finalizing HL migrations', error) + console.error("FATAL ERROR: Error finalizing HL migrations", error); } - console.log('done') - }) + console.log("done"); + }); } async function prepForHLMigration( hlManager: HyperliquidManager, unmigratedMigrations: TokenMigration[] ): Promise { - const hlMigrations: HLMigration[] = [] + const hlMigrations: HLMigration[] = []; for (const unmigratedMigration of unmigratedMigrations) { if (unmigratedMigration.amount && unmigratedMigration.migrationAddress) { - const arbitrumAmount = BigInt(unmigratedMigration.amount) - const hlAmount = hlManager.decimalConversion!.convertToHlToken(arbitrumAmount) + const arbitrumAmount = BigInt(unmigratedMigration.amount); + const hlAmount = + hlManager.decimalConversion!.convertToHlToken(arbitrumAmount); hlMigrations.push({ originalTransactionHash: unmigratedMigration.transactionHash, hlTokenAmount: hlAmount, - sendToAddress: unmigratedMigration.migrationAddress - }) + sendToAddress: unmigratedMigration.migrationAddress, + }); } else { console.error( `migration with hash ${unmigratedMigration.transactionHash} has no amount or no migration address` - ) + ); } } - return hlMigrations + return hlMigrations; } async function initRedisConnection() { @@ -107,20 +126,23 @@ async function initRedisConnection() { enableReadyCheck: false, tls: env.REDIS_USE_TLS ? { - rejectUnauthorized: false + rejectUnauthorized: false, } - : undefined - }) + : undefined, + }); } async function addMigrationsToDatabase(migrations: MigrationRegisteredEvent[]) { try { - const result = await addFractalityTokenMigrations(migrations) //TODO: make this batch + const result = await addFractalityTokenMigrations(migrations); //TODO: make this batch console.log( `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` - ) - console.info(`existing migrations that already exist in the database`, result.existingTxs) + ); + console.info( + `existing migrations that already exist in the database`, + result.existingTxs + ); } catch (e) { - console.error('Error adding migration to database', e) + console.error("Error adding migration to database", e); } } diff --git a/interfaces.ts b/interfaces.ts index 684daed..68385a6 100644 --- a/interfaces.ts +++ b/interfaces.ts @@ -16,8 +16,7 @@ export interface HLMigration { } export enum MigrationStatus { - FOUND_ON_ARBITRUM = "FOUND_ON_ARBITRUM", - PREPPED_FOR_HL = "PREPPED_FOR_HL", - SENT_TO_HL = "SENT_TO_HL", - ERRORED_IN_SENDING_TO_HL = "ERRORED_IN_SENDING_TO_HL", + FOUND_ON_ARBITRUM = "FOUND_ON_ARBITRUM", //Just found on arbitrum, not filtered. + SENT_TO_HL = "SENT_TO_HL", //Sent to HL + ERRORED_IN_SENDING_TO_HL = "ERRORED_IN_SENDING_TO_HL", //Errored in sending to HL } From 9735e460b54a9e1a46aac83c3c836c6a0f32e70a Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Wed, 12 Feb 2025 19:00:27 -0500 Subject: [PATCH 04/33] - added variables into the terraform files - refactored so it can be run both manually with cron for testing and via lambda functions --- handler.ts | 19 ++++++ index.ts => migrationService.ts | 98 ++++++++++++++++++------------ package-lock.json | 83 ++++++++++++++++++++++++- package.json | 6 +- run.ts | 3 + terraform/environments/main.tfvars | 18 +++++- terraform/environments/test.tfvars | 18 +++++- 7 files changed, 198 insertions(+), 47 deletions(-) create mode 100644 handler.ts rename index.ts => migrationService.ts (62%) create mode 100644 run.ts diff --git a/handler.ts b/handler.ts new file mode 100644 index 0000000..57abd34 --- /dev/null +++ b/handler.ts @@ -0,0 +1,19 @@ +import { type Context, type Handler } from "aws-lambda"; + +import { main } from "./migrationService"; +interface ReportEvent { + DAGSTER_PIPES_CONTEXT: string; + DAGSTER_PIPES_MESSAGES: string; +} + +export const handler: Handler = async ( + event: ReportEvent, + context: Context +): Promise => { + try { + await main(false); + } catch (error) { + console.error("Error With Migration Service:", error); + throw error; + } +}; diff --git a/index.ts b/migrationService.ts similarity index 62% rename from index.ts rename to migrationService.ts index c54cd81..c9f360d 100644 --- a/index.ts +++ b/migrationService.ts @@ -21,7 +21,7 @@ import cron from "node-cron"; import { PreviousBlockManager } from "./libs/PreviousBlockManager"; import { HyperliquidManager } from "./libs/HyperliquidManager"; -async function main() { +export async function main(runWithCron: boolean) { await privateKeyManager.init(); await initializeDatabaseConnection(); @@ -49,51 +49,71 @@ async function main() { () => blockchainConnectionProvider.getCurrentBlockNumber() ); - console.log("starting cron job for migrations, running every 5 minutes"); - cron.schedule("* * * * *", async () => { - const fromBlock = await blockManager.getFromBlockForScan(); - const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); - console.log( - `looking for migrations from block ${fromBlock} to block ${toBlock}` + if (runWithCron) { + console.log("starting cron job for migrations, running every 5 minutes"); + cron.schedule("* * * * *", async () => { + await coreMigrationService( + blockManager, + blockchainConnectionProvider, + hlManager + ); + }); + } else { + await coreMigrationService( + blockManager, + blockchainConnectionProvider, + hlManager ); + } +} - const y2kMigrations = await blockchainConnectionProvider.scanMigrations( - env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, - fromBlock, - toBlock - ); - const frctRMigrations = await blockchainConnectionProvider.scanMigrations( - env.FRCT_R_MIGRATION_ADDRESS as Address, - fromBlock, - toBlock - ); - await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); +export async function coreMigrationService( + blockManager: PreviousBlockManager, + blockchainConnectionProvider: BlockchainConnectionProvider, + hlManager: HyperliquidManager +) { + const fromBlock = await blockManager.getFromBlockForScan(); + const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); + console.log( + `looking for migrations from block ${fromBlock} to block ${toBlock}` + ); + + const y2kMigrations = await blockchainConnectionProvider.scanMigrations( + env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, + fromBlock, + toBlock + ); + const frctRMigrations = await blockchainConnectionProvider.scanMigrations( + env.FRCT_R_MIGRATION_ADDRESS as Address, + fromBlock, + toBlock + ); + await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); - //get migrations that still have not been sent to hyperliquid - const unmigratedMigrations: TokenMigration[] = - await getUnmigratedFractalityTokenMigrations(); + //get migrations that still have not been sent to hyperliquid + const unmigratedMigrations: TokenMigration[] = + await getUnmigratedFractalityTokenMigrations(); - //calcualate the amount of tokens to send to hyperliquid - const hlMigrations = await prepForHLMigration( - hlManager, - unmigratedMigrations - ); - console.log("hlMigrations", hlMigrations); + //calcualate the amount of tokens to send to hyperliquid + const hlMigrations = await prepForHLMigration( + hlManager, + unmigratedMigrations + ); + console.log("hlMigrations", hlMigrations); - const { successes, failures } = await hlManager.sendHLMigrations( - hlMigrations - ); - console.log("successes", successes); - console.log("failures", failures); + const { successes, failures } = await hlManager.sendHLMigrations( + hlMigrations + ); + console.log("successes", successes); + console.log("failures", failures); - try { - await finalizeHlMigrations(successes); - } catch (error) { - console.error("FATAL ERROR: Error finalizing HL migrations", error); - } + try { + await finalizeHlMigrations(successes); + } catch (error) { + console.error("FATAL ERROR: Error finalizing HL migrations", error); + } - console.log("done"); - }); + console.log("done"); } async function prepForHLMigration( diff --git a/package-lock.json b/package-lock.json index e948d12..2f1bd65 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,8 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", + "@types/aws-lambda": "^8.10.147", + "aws-lambda": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "dotenv": "^16.4.5", @@ -1410,6 +1412,11 @@ "typescript": ">=4.7.0" } }, + "node_modules/@types/aws-lambda": { + "version": "8.10.147", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.147.tgz", + "integrity": "sha512-nD0Z9fNIZcxYX5Mai2CTmFD7wX7UldCkW2ezCF8D1T5hdiLsnTWDGRpfRYntU6VjTdLQjOvyszru7I1c1oCQew==" + }, "node_modules/@types/node": { "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", @@ -1523,6 +1530,14 @@ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, "node_modules/array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", @@ -1551,6 +1566,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/aws-lambda": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz", + "integrity": "sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w==", + "dependencies": { + "aws-sdk": "^2.814.0", + "commander": "^3.0.2", + "js-yaml": "^3.14.1", + "watchpack": "^2.0.0-beta.10" + }, + "bin": { + "lambda": "bin/lambda" + } + }, "node_modules/aws-sdk": { "version": "2.1692.0", "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1692.0.tgz", @@ -1895,6 +1924,11 @@ "node": ">=8" } }, + "node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2155,6 +2189,18 @@ "node": ">=0.8.0" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/ethers": { "version": "6.13.5", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", @@ -2408,6 +2454,11 @@ "node": ">= 6" } }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2422,8 +2473,7 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has-flag": { "version": "3.0.0", @@ -2716,6 +2766,18 @@ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", "dev": true }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -3108,6 +3170,11 @@ "node": ">=10" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -3449,6 +3516,18 @@ } } }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/which-typed-array": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", diff --git a/package.json b/package.json index 2b21fbd..3245dca 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "start": "node dist/index.js", - "dev": "nodemon index.ts", + "start": "node dist/run.js", + "dev": "nodemon run.ts", "build": "tsc", "format": "prettier --write \"**/*.ts\"" }, @@ -14,6 +14,8 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", + "@types/aws-lambda": "^8.10.147", + "aws-lambda": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", "dotenv": "^16.4.5", diff --git a/run.ts b/run.ts new file mode 100644 index 0000000..08b47ed --- /dev/null +++ b/run.ts @@ -0,0 +1,3 @@ +import { main } from "./migrationService"; + +main(true); diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars index 4b563ea..0e55aca 100644 --- a/terraform/environments/main.tfvars +++ b/terraform/environments/main.tfvars @@ -4,10 +4,24 @@ environment = "main" vpc_id = "" subnet_ids = [] -provider_url = "" +private_key = "" public_address = "" +mnemonic_secret_arn = "" +aws_region = "" +testnet = false token_address = "" +provider_url = "" y2k_token_migration_address = "" frct_r_migration_address = "" +node_env = "" +db_name = "" +db_host = "" +db_port = "" +db_user = "" +db_password = "" +db_schema = "" +blockchain_environment = "arbitrum" block_start_number = "" -safe_cushion_number_of_blocks = "" +safety_cushion_number_of_blocks = "250" +redis_connection_string = "" +redis_use_tls = "" \ No newline at end of file diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index f334cbe..7d052b0 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -4,10 +4,24 @@ environment = "test" vpc_id = "" subnet_ids = [] -provider_url = "" +private_key = "" public_address = "" +mnemonic_secret_arn = "" +aws_region = "" +testnet = true token_address = "" +provider_url = "" y2k_token_migration_address = "" frct_r_migration_address = "" +node_env = "" +db_name = "" +db_host = "" +db_port = "" +db_user = "" +db_password = "" +db_schema = "" +blockchain_environment = test block_start_number = "" -safe_cushion_number_of_blocks = "" +safety_cushion_number_of_blocks = "250" +redis_connection_string = "" +redis_use_tls = "" From 218ddf0622cfd48e102ba0cc1bd8399e91a00889 Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Wed, 12 Feb 2025 21:37:03 -0500 Subject: [PATCH 05/33] filled in more non secret env vars --- terraform/environments/test.tfvars | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index 7d052b0..599d93a 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -5,14 +5,14 @@ vpc_id = "" subnet_ids = [] private_key = "" -public_address = "" +public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" mnemonic_secret_arn = "" aws_region = "" testnet = true -token_address = "" +token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" provider_url = "" -y2k_token_migration_address = "" -frct_r_migration_address = "" +y2k_token_migration_address = "0x2839449acf90EDee7055FdA315875e9539359aA0" +frct_r_migration_address = "0xB78028Ca8246d5E8F08Fd656F9337C4E77Cd2389" node_env = "" db_name = "" db_host = "" @@ -21,7 +21,7 @@ db_user = "" db_password = "" db_schema = "" blockchain_environment = test -block_start_number = "" +block_start_number = "119867300" safety_cushion_number_of_blocks = "250" redis_connection_string = "" redis_use_tls = "" From b08537cdbbe4d9eb0cc26471969d8985c6612693 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 17 Feb 2025 21:40:04 +1100 Subject: [PATCH 06/33] Prepare deployment --- .github/workflows/deploy.yaml | 61 ++++++++++++++++++++++++++++++ handler.ts | 20 ++++------ package-lock.json | 1 + terraform/environments/main.tfvars | 26 +++---------- terraform/environments/test.tfvars | 33 ++++++---------- terraform/lambda.tf | 7 ++-- terraform/var.tf | 10 ++++- 7 files changed, 100 insertions(+), 58 deletions(-) create mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..9c656c1 --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,61 @@ +name: Build/Deploy + +on: + push: + branches: + - main + - test + - '*' + pull_request: + branches: + - main + - test + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test') + environment: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }} + + env: + DESTINATION_BRANCH: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }} + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v1 + with: + terraform_wrapper: false + + - name: Terraform Init + run: terraform init -backend-config="environments/${{ env.DESTINATION_BRANCH }}.backend.tfvars" + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + working-directory: ./terraform + + - name: Set Version + if: github.ref == 'refs/heads/main' + run: echo "PROJECT_VERSION=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV + + - name: Terraform Deploy + run: | + terraform apply -auto-approve \ + -var-file="environments/${{ env.DESTINATION_BRANCH }}.tfvars" \ + -var="account_id=${{ vars.AWS_ACCOUNT_ID }}" \ + -var="region=${{ vars.AWS_REGION }}" \ + -var="project_version=${{ env.PROJECT_VERSION }}" \ + -var="tesnet_provider_url=${{ secrets.TESTNET_PROVIDER_URL }}" \ + -var="mainnet_provider_key=${{ secrets.MAINNET_PROVIDER_KEY }}" + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + PROJECT_VERSION: ${{ env.PROJECT_VERSION }} + working-directory: ./terraform diff --git a/handler.ts b/handler.ts index 57abd34..4ec2a77 100644 --- a/handler.ts +++ b/handler.ts @@ -1,19 +1,15 @@ -import { type Context, type Handler } from "aws-lambda"; +import { type Context, type Handler } from 'aws-lambda' -import { main } from "./migrationService"; -interface ReportEvent { - DAGSTER_PIPES_CONTEXT: string; - DAGSTER_PIPES_MESSAGES: string; -} +import { main } from './migrationService' -export const handler: Handler = async ( - event: ReportEvent, +export const handler: Handler = async ( + event: void, context: Context ): Promise => { try { - await main(false); + await main(false) } catch (error) { - console.error("Error With Migration Service:", error); - throw error; + console.error('Error With Migration Service:', error) + throw error } -}; +} diff --git a/package-lock.json b/package-lock.json index 2f1bd65..990a9cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1570,6 +1570,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz", "integrity": "sha512-9GNFMRrEMG5y3Jvv+V4azWvc+qNWdWLTjDdhf/zgMlz8haaaLWv0xeAIWxz9PuWUBawsVxy0zZotjCdR3Xq+2w==", + "license": "MIT", "dependencies": { "aws-sdk": "^2.814.0", "commander": "^3.0.2", diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars index 0e55aca..da82a29 100644 --- a/terraform/environments/main.tfvars +++ b/terraform/environments/main.tfvars @@ -4,24 +4,10 @@ environment = "main" vpc_id = "" subnet_ids = [] -private_key = "" -public_address = "" -mnemonic_secret_arn = "" -aws_region = "" -testnet = false -token_address = "" -provider_url = "" -y2k_token_migration_address = "" -frct_r_migration_address = "" -node_env = "" -db_name = "" -db_host = "" -db_port = "" -db_user = "" -db_password = "" -db_schema = "" -blockchain_environment = "arbitrum" -block_start_number = "" +public_address = "" +token_address = "" +provider_url = "" +y2k_token_migration_address = "" +frct_r_migration_address = "" +block_start_number = "" safety_cushion_number_of_blocks = "250" -redis_connection_string = "" -redis_use_tls = "" \ No newline at end of file diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index 599d93a..f085268 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -1,27 +1,16 @@ name = "migration-service-test" project = "fractality" environment = "test" -vpc_id = "" -subnet_ids = [] +vpc_id = "vpc-08ae44a5cd755d8b0" +subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] -private_key = "" -public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" -mnemonic_secret_arn = "" -aws_region = "" -testnet = true -token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" -provider_url = "" -y2k_token_migration_address = "0x2839449acf90EDee7055FdA315875e9539359aA0" -frct_r_migration_address = "0xB78028Ca8246d5E8F08Fd656F9337C4E77Cd2389" -node_env = "" -db_name = "" -db_host = "" -db_port = "" -db_user = "" -db_password = "" -db_schema = "" -blockchain_environment = test -block_start_number = "119867300" + +testnet = true +public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" +token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" +provider_url = "https://arb-sepolia.g.alchemy.com/v2/SA1EqG4RW6GcciKBnPtu8n6OJqxy164t" +y2k_token_migration_address = "0x2839449acf90EDee7055FdA315875e9539359aA0" +frct_r_migration_address = "0xB78028Ca8246d5E8F08Fd656F9337C4E77Cd2389" +block_start_number = "119867300" safety_cushion_number_of_blocks = "250" -redis_connection_string = "" -redis_use_tls = "" + diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 36ab04c..ca8309c 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -9,7 +9,7 @@ locals { resource "aws_security_group" "lambda_sg" { name = "${var.name}-lambda-sg-${var.environment}" description = "Security group for Lambda functions" - vpc_id = "vpc-08ae44a5cd755d8b0" + vpc_id = var.vpc_id ingress { description = "Allow Lambda to access RDS on port 5432" @@ -43,10 +43,11 @@ resource "aws_lambda_function" "default" { environment { variables = { - PROVIDER_URL = var.provider_url + PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url BLOCKCHAIN_ENVIRONMENT = var.environment PUBLIC_ADDRESS = var.public_address TOKEN_ADDRESS = var.token_address + TESTNET = var.testnet MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn DB_USER = local.db_username DB_PASSWORD = local.db_password @@ -63,7 +64,7 @@ resource "aws_lambda_function" "default" { } vpc_config { - subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] + subnet_ids = var.subnet_ids security_group_ids = [aws_security_group.lambda_sg.id] } } diff --git a/terraform/var.tf b/terraform/var.tf index d314dd4..9a4fa30 100644 --- a/terraform/var.tf +++ b/terraform/var.tf @@ -41,7 +41,11 @@ variable "db_instance_id" { type = string } -variable "provider_url" { +variable "testnet_provider_url" { + type = string +} + +variable "mainnet_provider_url" { type = string } @@ -68,3 +72,7 @@ variable "block_start_number" { variable "safety_cushion_number_of_blocks" { type = string } + +variable "testnet" { + type = bool +} From 91af631b54aa335b4f94ee4d169268d74563bceb Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Thu, 8 May 2025 21:45:12 -0500 Subject: [PATCH 07/33] refactor some things and also add robust error handling, specially in the fatal error scenario --- database/index.ts | 341 ++++++++++++++------------- errors.ts | 54 +++++ libs/BlockchainConnectionProvider.ts | 12 +- libs/DecimalConversion.ts | 6 + libs/HyperliquidManager.ts | 23 +- libs/PreviousBlockManager.ts | 22 +- migrationService.ts | 155 +++++++----- redisOperations/redisOperations.ts | 71 ++++++ 8 files changed, 436 insertions(+), 248 deletions(-) create mode 100644 errors.ts create mode 100644 redisOperations/redisOperations.ts diff --git a/database/index.ts b/database/index.ts index cc4d12b..c8d70ea 100644 --- a/database/index.ts +++ b/database/index.ts @@ -9,6 +9,7 @@ import { MigrationRegisteredEvent, MigrationStatus, } from "../interfaces"; +import { DatabaseError } from "../errors"; let db: PostgresJsDatabase | null = null; @@ -41,28 +42,33 @@ export async function initializeDatabaseConnection(): Promise< return db; } -export async function addFractalityTokenMigrations( + +export async function filterAndaddNewFractalityTokenMigrations( migrations: MigrationRegisteredEvent[] ): Promise<{ newMigrations: MigrationRegisteredEvent[]; existingTxs: { txHash: string }[]; }> { if (!db) { - throw new Error("Database not initialized"); + throw new DatabaseError("Database not initialized"); } // Get all transaction hashes from the incoming migrations const incomingTxHashes = migrations.map((m) => m.transactionHash); - - // Check which transactions already exist in the database - const existingTxs = await db - .select({ txHash: schema.fractalityTokenMigrations.transactionHash }) - .from(schema.fractalityTokenMigrations) - .where( - inArray( - schema.fractalityTokenMigrations.transactionHash, - incomingTxHashes - ) - ); + let existingTxs: { txHash: string }[] = []; + try { + // Check which transactions already exist in the database + existingTxs = await db + .select({ txHash: schema.fractalityTokenMigrations.transactionHash }) + .from(schema.fractalityTokenMigrations) + .where( + inArray( + schema.fractalityTokenMigrations.transactionHash, + incomingTxHashes + ) + ); + } catch (error) { + throw new DatabaseError("Error fetching existing migrations from database: " + error); + } // Filter out migrations that already exist const newMigrations = migrations.filter( @@ -72,185 +78,190 @@ export async function addFractalityTokenMigrations( // If there are new migrations, insert them if (newMigrations.length > 0) { - await db.insert(schema.fractalityTokenMigrations).values( - newMigrations.map((migration) => ({ - transactionHash: migration.transactionHash, - migrationContractAddress: migration.migrationContractAddress, - eventName: migration.eventName, - caller: migration.caller, - migrationAddress: migration.migrationAddress, - amount: migration.amount.toString(), - foundAt: new Date().toISOString(), - status: MigrationStatus.FOUND_ON_ARBITRUM, - migratedAt: null, - migratedAmount: null, - })) - ); + try { + await db.insert(schema.fractalityTokenMigrations).values( + newMigrations.map((migration) => ({ + transactionHash: migration.transactionHash, + migrationContractAddress: migration.migrationContractAddress, + eventName: migration.eventName, + caller: migration.caller, + migrationAddress: migration.migrationAddress, + amount: migration.amount.toString(), + foundAt: new Date().toISOString(), + status: MigrationStatus.FOUND_ON_ARBITRUM, + migratedAt: null, + migratedAmount: null, + })) + ); + } catch (error) { + throw new DatabaseError("Error inserting new migrations batch into database: " + error); + } } - - return { newMigrations: newMigrations, existingTxs: existingTxs }; // Optionally return number of new insertions + return { newMigrations: newMigrations, existingTxs: existingTxs }; } -export async function dbCleanup(): Promise { - try { - console.log("Starting database cleanup..."); + export async function dbCleanup(): Promise { + try { + console.log("Starting database cleanup..."); - if (db) { - // Get the underlying postgres connection from drizzle - const client = (db as any).session?.config?.connection; - if (client) { - await client.end(); - console.log("Database connection closed"); + if (db) { + // Get the underlying postgres connection from drizzle + const client = (db as any).session?.config?.connection; + if (client) { + await client.end(); + console.log("Database connection closed"); + } + db = null; } - db = null; - } - console.log("Database cleanup completed"); - } catch (error) { - console.error("Error during database cleanup:", error); - throw error; + console.log("Database cleanup completed"); + } catch (error) { + console.error("Error during database cleanup:", error); + throw error; + } } -} -export async function getUnmigratedFractalityTokenMigrations(): Promise< - TokenMigration[] -> { - if (!db) { - throw new Error("Database not initialized"); - } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - not( - eq( - schema.fractalityTokenMigrations.status, - MigrationStatus.SENT_TO_HL + export async function getUnmigratedFractalityTokenMigrations(): Promise< + TokenMigration[] + > { + if (!db) { + throw new Error("Database not initialized"); + } + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where( + not( + eq( + schema.fractalityTokenMigrations.status, + MigrationStatus.SENT_TO_HL + ) ) - ) - ); - return migrations; - } catch (error) { - console.error( - "Error fetching unmigrated fractality token migrations:", - error - ); - throw error; - } -} - -export async function getAllFractalityTokenMigrations(): Promise< - TokenMigration[] -> { - if (!db) { - throw new Error("Database not initialized"); - } - - try { - return await db.select().from(schema.fractalityTokenMigrations); - } catch (error) { - console.error("Error fetching fractality token migrations:", error); - throw error; + ); + return migrations; + } catch (error) { + throw new DatabaseError("Error fetching unmigrated fractality token migrations: " + error); + } } -} -export async function getFractalityTokenMigrationsByAddress( - migrationAddress: string -): Promise { - if (!db) { - throw new Error("Database not initialized"); - } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - eq(schema.fractalityTokenMigrations.migrationAddress, migrationAddress) - ); + export async function getAllFractalityTokenMigrations(): Promise< + TokenMigration[] + > { + if (!db) { + throw new Error("Database not initialized"); + } - return migrations; - } catch (error) { - console.error("Error fetching migrations by address:", error); - throw error; + try { + return await db.select().from(schema.fractalityTokenMigrations); + } catch (error) { + console.error("Error fetching fractality token migrations:", error); + throw error; + } } -} -export async function getFractalityTokenMigrationsByMigrationContractAddress( - migrationContractAddress: string -): Promise { - if (!db) { - throw new Error("Database not initialized"); - } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - eq( - schema.fractalityTokenMigrations.migrationContractAddress, - migrationContractAddress - ) - ); + export async function getFractalityTokenMigrationsByAddress( + migrationAddress: string + ): Promise { + if (!db) { + throw new Error("Database not initialized"); + } + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where( + eq(schema.fractalityTokenMigrations.migrationAddress, migrationAddress) + ); - return migrations; - } catch (error) { - console.error( - "Error fetching migrations by migration contract address:", - error - ); - throw error; + return migrations; + } catch (error) { + console.error("Error fetching migrations by address:", error); + throw error; + } } -} -export async function setHLMigrationStatus( - migrationHash: string, - status: MigrationStatus -) { - try { + export async function getFractalityTokenMigrationsByMigrationContractAddress( + migrationContractAddress: string + ): Promise { if (!db) { throw new Error("Database not initialized"); } - await db - .update(schema.fractalityTokenMigrations) - .set({ status: status }) - .where( - eq(schema.fractalityTokenMigrations.transactionHash, migrationHash) + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where( + eq( + schema.fractalityTokenMigrations.migrationContractAddress, + migrationContractAddress + ) + ); + + return migrations; + } catch (error) { + console.error( + "Error fetching migrations by migration contract address:", + error ); - } catch (error) { - console.error(`ERROR setting HL migration status. Error: ${error}`); - throw error; + throw error; + } } -} -export async function finalizeHlMigrations(migrations: HLMigration[]) { - if (!db) { - throw new Error("Database not initialized"); - } - try { - for (const migration of migrations) { - console.log( - `Setting HL migration for ${migration.originalTransactionHash}` - ); + export async function setHLMigrationStatus( + migrationHash: string, + status: MigrationStatus + ) { + try { + if (!db) { + throw new Error("Database not initialized"); + } await db .update(schema.fractalityTokenMigrations) - .set({ - status: MigrationStatus.SENT_TO_HL, - migratedAt: new Date().toISOString(), - migratedAmount: migration.hlTokenAmount, - }) + .set({ status: status }) .where( - eq( - schema.fractalityTokenMigrations.transactionHash, - migration.originalTransactionHash - ) + eq(schema.fractalityTokenMigrations.transactionHash, migrationHash) ); + } catch (error) { + console.error(`ERROR setting HL migration status. Error: ${error}`); + throw error; } - } catch (error) { - console.error(`FATAL ERROR updating HL migrations. Error: ${error}`); - throw error; } -} -export type TokenMigration = - typeof schema.fractalityTokenMigrations.$inferSelect; + export async function finalizeHlMigrations(migrations: HLMigration[]) { + const successes = []; + const failures = []; + if (!db) { + throw new Error("Database not initialized"); + } + + for (const migration of migrations) { + try { + console.log( + `Setting HL migration for ${migration.originalTransactionHash}` + ); + await db + .update(schema.fractalityTokenMigrations) + .set({ + status: MigrationStatus.SENT_TO_HL, + migratedAt: new Date().toISOString(), + migratedAmount: migration.hlTokenAmount, + }) + .where( + eq( + schema.fractalityTokenMigrations.transactionHash, + migration.originalTransactionHash + ) + ); + successes.push(migration); + } catch (error) { + failures.push(migration); + //THhis needs to shut down the service, as the next run could cause a double send. + console.error(`ERROR updating HL ${migration} migrations. Error: ${error}`); + } + } + return { successes, failures }; + } + + export type TokenMigration = + typeof schema.fractalityTokenMigrations.$inferSelect; diff --git a/errors.ts b/errors.ts new file mode 100644 index 0000000..7207fa4 --- /dev/null +++ b/errors.ts @@ -0,0 +1,54 @@ +import { HLMigration } from "./interfaces"; + +export class FatalFinalizationError extends Error { + failedMigrations: HLMigration[]; + constructor(message: string, failedMigrations: HLMigration[]) { + super(message); + this.name = "FatalError"; + this.failedMigrations = failedMigrations; + } + } + +export class RedisError extends Error { + constructor(message: string) { + super(message); + this.name = "RedisError"; + } + } + +export class DecimalConversionError extends Error { + constructor(message: string) { + super(message); + this.name = "DecimalConversionError"; + } + } + + + export class HyperliquidError extends Error { + constructor(message: string) { + super(message); + this.name = "HyperliquidError"; + } + } + + export class BlockchainConnectionError extends Error { + constructor(message: string) { + super(message); + this.name = "BlockchainConnectionError"; + } + } + + export class DatabaseError extends Error { + constructor(message: string) { + super(message); + this.name = "DatabaseError"; + } + } + + export class MigrationPrepError extends Error { + constructor(message: string) { + super(message); + this.name = "MigrationPrepError"; + } + } + \ No newline at end of file diff --git a/libs/BlockchainConnectionProvider.ts b/libs/BlockchainConnectionProvider.ts index 8ede50f..a20aa68 100644 --- a/libs/BlockchainConnectionProvider.ts +++ b/libs/BlockchainConnectionProvider.ts @@ -13,6 +13,7 @@ import { env } from "../env"; import abi from "../contracts/FractalityTokenMigration.sol.json"; import { MigrationRegisteredEvent } from "../interfaces"; import { MySqlBigInt64BuilderInitial } from "drizzle-orm/mysql-core"; +import { BlockchainConnectionError } from "../errors"; interface BlockchainConnectionProviderOptions { providerUrl: string; y2kTokenMigrationAddress: Address; @@ -80,7 +81,12 @@ export class BlockchainConnectionProvider { }; public async getCurrentBlockNumber(): Promise { - return this._viemClient.getBlockNumber(); + try { + return this._viemClient.getBlockNumber(); + } catch (error) { + throw new BlockchainConnectionError("Error getting current block number: " + error); + } + } public async getArbitrumTokenDecimals(): Promise { @@ -127,6 +133,7 @@ export class BlockchainConnectionProvider { fromBlock: bigint, toBlock: bigint ): Promise { +try{ //This is the current block number if (fromBlock >= toBlock) { throw new Error("from block must be before toBlock"); @@ -161,5 +168,8 @@ export class BlockchainConnectionProvider { }); }); return decodedLogs; + } catch (error) { + throw new BlockchainConnectionError("Error scanning migrations: " + error); + } } } diff --git a/libs/DecimalConversion.ts b/libs/DecimalConversion.ts index 17b37c4..6673cf6 100644 --- a/libs/DecimalConversion.ts +++ b/libs/DecimalConversion.ts @@ -1,9 +1,15 @@ import { ethers } from "ethers"; +import { DecimalConversionError } from "../errors"; export class DecimalConversion { hlTokenDecimals: bigint; arbitrumTokenDecimals: bigint; constructor(hlTokenDecimals: bigint, arbitrumTokenDecimals: bigint) { + if (hlTokenDecimals > arbitrumTokenDecimals) { + throw new DecimalConversionError( + `HL token decimals (${hlTokenDecimals}) must be strictly smaller or equal to Arbitrum token decimals (${arbitrumTokenDecimals}).` + ); + } this.hlTokenDecimals = hlTokenDecimals; this.arbitrumTokenDecimals = arbitrumTokenDecimals; } diff --git a/libs/HyperliquidManager.ts b/libs/HyperliquidManager.ts index fad5691..4db65fc 100644 --- a/libs/HyperliquidManager.ts +++ b/libs/HyperliquidManager.ts @@ -1,6 +1,7 @@ const { Hyperliquid } = require("hyperliquid"); import { setHLMigrationStatus } from "../database"; import { env } from "../env"; +import { HyperliquidError } from "../errors"; import { HLMigration, MigrationStatus } from "../interfaces"; import { DecimalConversion } from "./DecimalConversion"; @@ -33,11 +34,15 @@ export class HyperliquidManager { } async getUserTokenBalances(userAddress: string) { - const balances = await this.hlSdk.info.spot.getSpotClearinghouseState( - userAddress, - false - ); - return balances; + try { + const balances = await this.hlSdk.info.spot.getSpotClearinghouseState( + userAddress, + false + ); + return balances; + } catch (error) { + throw new HyperliquidError("Error getting user token balances: " + error); + } } getTokenDecimals() { @@ -45,7 +50,11 @@ export class HyperliquidManager { } async getTokenInfo(tokenAddress: string) { - return this.hlSdk.info.spot.getTokenDetails(tokenAddress); + try { + return this.hlSdk.info.spot.getTokenDetails(tokenAddress); + } catch (error) { + throw new HyperliquidError("Error getting token info: " + error); + } } //NOTE: the amount here is NOT in wei. It is in decimal representation. @@ -60,7 +69,7 @@ export class HyperliquidManager { console.log("Transfer successful"); } else { console.log("Transfer failed", result.response); - throw new Error("Transfer failed"); + throw new HyperliquidError("Transfer failed: " + result.response); } } //4000 migrations will take aroud 1000$ USDC! diff --git a/libs/PreviousBlockManager.ts b/libs/PreviousBlockManager.ts index 00adcb9..d17379a 100644 --- a/libs/PreviousBlockManager.ts +++ b/libs/PreviousBlockManager.ts @@ -1,41 +1,33 @@ import IORedis from "ioredis"; import { env } from "../env"; +import { RedisOperations } from "../redisOperations/redisOperations"; export class PreviousBlockManager { - private redisConnection: IORedis | null = null; + private redisOperations: RedisOperations; private safetyCushionNumberOfBlocks: bigint; private getCurrentBlock: () => Promise; constructor( - redisConnection: IORedis, + redisOperations: RedisOperations, safetyCushionNumberOfBlocks: bigint, getCurrentBlock: () => Promise ) { - this.redisConnection = redisConnection; + this.redisOperations = redisOperations; this.safetyCushionNumberOfBlocks = safetyCushionNumberOfBlocks; this.getCurrentBlock = getCurrentBlock; } //Returns the block number of the last scan, or the current block number minus the number of blocks back that corresponds to the scan period, as a start. async getFromBlockForScan() { - if (!this.redisConnection) { - throw new Error("Redis connection is not initialized"); - } - const blockNumber = await this.redisConnection.get("lastScanBlockNumber"); + const blockNumber = await this.redisOperations.getLastScanBlockNumber(); if (!blockNumber) { return BigInt(env.BLOCK_START_NUMBER); } else { - return BigInt(blockNumber) - this.safetyCushionNumberOfBlocks; //Add safety cushion to the block number, so we don't miss any blocks, repeated events can be ignored + return blockNumber - this.safetyCushionNumberOfBlocks; //Add safety cushion to the block number, so we don't miss any blocks, repeated events can be ignored } } async setFromBlockForScanToCurrentBlock(): Promise { - if (!this.redisConnection) { - throw new Error("Redis connection is not initialized"); - } const currentBlockNumber = await this.getCurrentBlock(); - await this.redisConnection.set( - "lastScanBlockNumber", - currentBlockNumber.toString() - ); + await this.redisOperations.setLastScanBlockNumber(currentBlockNumber); return currentBlockNumber; } } diff --git a/migrationService.ts b/migrationService.ts index c9f360d..4fe5711 100644 --- a/migrationService.ts +++ b/migrationService.ts @@ -1,11 +1,10 @@ import { PrivateKeyManager } from "./libs/PrivateKeyManager"; -import IORedis from "ioredis"; const privateKeyManager = new PrivateKeyManager(); import { env } from "./env"; import { BlockchainConnectionProvider } from "./libs/BlockchainConnectionProvider"; import { Address } from "viem"; import { - addFractalityTokenMigrations, + filterAndaddNewFractalityTokenMigrations, getUnmigratedFractalityTokenMigrations, finalizeHlMigrations, setHLMigrationStatus, @@ -20,12 +19,15 @@ import { initializeDatabaseConnection } from "./database"; import cron from "node-cron"; import { PreviousBlockManager } from "./libs/PreviousBlockManager"; import { HyperliquidManager } from "./libs/HyperliquidManager"; +import { FatalFinalizationError, MigrationPrepError, RedisError } from "./errors"; +import { RedisOperations } from "./redisOperations/redisOperations"; export async function main(runWithCron: boolean) { await privateKeyManager.init(); await initializeDatabaseConnection(); - const redisConnection = await initRedisConnection(); + const redisOperations = new RedisOperations(); + await redisOperations.initialize(); const hlManager = new HyperliquidManager( true, @@ -44,27 +46,50 @@ export async function main(runWithCron: boolean) { ); const blockManager = new PreviousBlockManager( - redisConnection, + redisOperations, BigInt(env.SAFETY_CUSHION_NUMBER_OF_BLOCKS), () => blockchainConnectionProvider.getCurrentBlockNumber() ); if (runWithCron) { console.log("starting cron job for migrations, running every 5 minutes"); - cron.schedule("* * * * *", async () => { - await coreMigrationService( - blockManager, - blockchainConnectionProvider, - hlManager - ); + const scheduledTask = cron.schedule("* * * * *", async () => { + try { + await coreMigrationService( + blockManager, + blockchainConnectionProvider, + hlManager + ); + } catch (error) { + if (error instanceof FatalFinalizationError) { + scheduledTask.stop(); + } else { + console.log("Error in core migration service, this run will be skipped", error); + } + } }); } else { - await coreMigrationService( - blockManager, - blockchainConnectionProvider, - hlManager - ); + try { + if (await redisOperations.shouldRunAccordingToStopRunningFlag()) { + await coreMigrationService( + blockManager, + blockchainConnectionProvider, + hlManager + ); + } else { + console.log("stopRunning flag is set, not running core migration service"); + return; + } + } catch (error) { + if (error instanceof FatalFinalizationError) { + await redisOperations.setStopRunningFlag(); + } else { + console.log("Error in core migration service, this run will be skipped", error); + } + throw error; + } } + } export async function coreMigrationService( @@ -72,12 +97,16 @@ export async function coreMigrationService( blockchainConnectionProvider: BlockchainConnectionProvider, hlManager: HyperliquidManager ) { + //This, if fails will do a scan from the start block, not a big dea. const fromBlock = await blockManager.getFromBlockForScan(); + + //This gets the current block and sets it in redis. If fails, will bubble up and this run will be skipped. const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); console.log( `looking for migrations from block ${fromBlock} to block ${toBlock}` ); + //Gets logs from the blockchain. If fails, will bubble up and this run will be skipped. const y2kMigrations = await blockchainConnectionProvider.scanMigrations( env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, fromBlock, @@ -88,81 +117,87 @@ export async function coreMigrationService( fromBlock, toBlock ); + //This is atomic, if it fails, nothing was written and we can try again next time. await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); //get migrations that still have not been sent to hyperliquid + //This includes the ones we added above... as well as those that were not migrated for some reason. + //If this fails, we will skip this run and try again next time. const unmigratedMigrations: TokenMigration[] = await getUnmigratedFractalityTokenMigrations(); //calcualate the amount of tokens to send to hyperliquid + //If all the migrations are not able to be prepped, we will skip this run and try again next time. + //However, I don't see this failing as it's just doing some math. const hlMigrations = await prepForHLMigration( hlManager, unmigratedMigrations ); console.log("hlMigrations", hlMigrations); + //This fails gracefully, the ones we could not send are in the faulures array. const { successes, failures } = await hlManager.sendHLMigrations( hlMigrations ); console.log("successes", successes); console.log("failures", failures); - try { - await finalizeHlMigrations(successes); - } catch (error) { - console.error("FATAL ERROR: Error finalizing HL migrations", error); + let finalizationMaxRetries = 3; + let migrationsToFinalize = successes; + for (const attemptNumber of Array(finalizationMaxRetries).keys()) { + const finalizationResults = await finalizeHlMigrations(migrationsToFinalize); + if (finalizationResults.failures.length === 0) { + break; + } + migrationsToFinalize = finalizationResults.failures; + if (attemptNumber === finalizationMaxRetries - 1) { + throw new FatalFinalizationError( + "FATAL ERROR: Error finalizing HL migrations. The following migration need to manually be marked as sent to HL", + finalizationResults.failures + ); + } } - - console.log("done"); } + +//Maybe I can add some in success and failure buckets. async function prepForHLMigration( hlManager: HyperliquidManager, unmigratedMigrations: TokenMigration[] ): Promise { - const hlMigrations: HLMigration[] = []; - for (const unmigratedMigration of unmigratedMigrations) { - if (unmigratedMigration.amount && unmigratedMigration.migrationAddress) { - const arbitrumAmount = BigInt(unmigratedMigration.amount); - const hlAmount = - hlManager.decimalConversion!.convertToHlToken(arbitrumAmount); - hlMigrations.push({ - originalTransactionHash: unmigratedMigration.transactionHash, - hlTokenAmount: hlAmount, - sendToAddress: unmigratedMigration.migrationAddress, - }); - } else { - console.error( - `migration with hash ${unmigratedMigration.transactionHash} has no amount or no migration address` - ); + try { + const hlMigrations: HLMigration[] = []; + for (const unmigratedMigration of unmigratedMigrations) { + if (unmigratedMigration.amount && unmigratedMigration.migrationAddress) { + const arbitrumAmount = BigInt(unmigratedMigration.amount); + const hlAmount = + hlManager.decimalConversion!.convertToHlToken(arbitrumAmount); + hlMigrations.push({ + originalTransactionHash: unmigratedMigration.transactionHash, + hlTokenAmount: hlAmount, + sendToAddress: unmigratedMigration.migrationAddress, + }); + } else { + console.error( + `migration with hash ${unmigratedMigration.transactionHash} has no amount or no migration address` + ); + } } + return hlMigrations; + } catch (error) { + throw new MigrationPrepError("Error preparing for HL migration: " + error); } - return hlMigrations; } -async function initRedisConnection() { - return new IORedis(env.REDIS_CONNECTION_STRING, { - maxRetriesPerRequest: null, - enableReadyCheck: false, - tls: env.REDIS_USE_TLS - ? { - rejectUnauthorized: false, - } - : undefined, - }); -} + async function addMigrationsToDatabase(migrations: MigrationRegisteredEvent[]) { - try { - const result = await addFractalityTokenMigrations(migrations); //TODO: make this batch - console.log( - `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` - ); - console.info( - `existing migrations that already exist in the database`, - result.existingTxs - ); - } catch (e) { - console.error("Error adding migration to database", e); - } + const result = await filterAndaddNewFractalityTokenMigrations(migrations); //TODO: make this batch + console.log( + `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` + ); + console.info( + `existing migrations that already exist in the database`, + result.existingTxs + ); } diff --git a/redisOperations/redisOperations.ts b/redisOperations/redisOperations.ts new file mode 100644 index 0000000..061ffb2 --- /dev/null +++ b/redisOperations/redisOperations.ts @@ -0,0 +1,71 @@ +import { env } from "../env"; +import IORedis from "ioredis"; +import { RedisError } from "../errors"; + +export class RedisOperations { + private redisConnection: IORedis | null = null; + + + async initialize() { + this.redisConnection = await this.initRedisConnection(); + } + + + async initRedisConnection() { + return new IORedis(env.REDIS_CONNECTION_STRING, { + maxRetriesPerRequest: null, + enableReadyCheck: false, + tls: env.REDIS_USE_TLS + ? { + rejectUnauthorized: false, + } + : undefined, + }); + } + + async shouldRunAccordingToStopRunningFlag(): Promise { + if (!this.redisConnection) { + return false; + } + let stopRunningFlag = null; + try { + stopRunningFlag = await this.redisConnection.get("stopRunning"); + } catch (error) { + return false; + } + if (stopRunningFlag==="true") { + return false; + } + return true; + } + + + async getLastScanBlockNumber(): Promise { + if (!this.redisConnection) { + return null; + } + const lastScanBlockNumber = await this.redisConnection.get("lastScanBlockNumber"); + if(!lastScanBlockNumber) { + return null; + } + return BigInt(lastScanBlockNumber); + } + + async setLastScanBlockNumber(blockNumber: bigint) { + if (!this.redisConnection) { + throw new RedisError("Redis connection is not initialized"); + } + try { + await this.redisConnection.set("lastScanBlockNumber", blockNumber.toString()); + } catch (error) { + throw new RedisError("Error setting lastScanBlockNumber in redis"); + } + } + async setStopRunningFlag() { + if (!this.redisConnection) { + return; + } + await this.redisConnection.set("stopRunning", "true"); + } + +} \ No newline at end of file From 6fc7bcb93dbc66edb154b775015467622ba1b521 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 12 May 2025 17:02:46 +1000 Subject: [PATCH 08/33] Update variable names --- .github/workflows/deploy.yaml | 4 ++-- terraform/environments/main.tfvars | 3 ++- terraform/environments/test.tfvars | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 9c656c1..1f4d146 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -52,8 +52,8 @@ jobs: -var="account_id=${{ vars.AWS_ACCOUNT_ID }}" \ -var="region=${{ vars.AWS_REGION }}" \ -var="project_version=${{ env.PROJECT_VERSION }}" \ - -var="tesnet_provider_url=${{ secrets.TESTNET_PROVIDER_URL }}" \ - -var="mainnet_provider_key=${{ secrets.MAINNET_PROVIDER_KEY }}" + -var="testnet_provider_url=${{ secrets.TESTNET_PROVIDER_URL }}" \ + -var="mainnet_provider_url=${{ secrets.MAINNET_PROVIDER_URL }}" env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars index da82a29..48f758a 100644 --- a/terraform/environments/main.tfvars +++ b/terraform/environments/main.tfvars @@ -4,9 +4,10 @@ environment = "main" vpc_id = "" subnet_ids = [] +db_name = "fractality" +db_instance_id = "fund-data-pipeline-db" public_address = "" token_address = "" -provider_url = "" y2k_token_migration_address = "" frct_r_migration_address = "" block_start_number = "" diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index f085268..f60a7a4 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -4,11 +4,11 @@ environment = "test" vpc_id = "vpc-08ae44a5cd755d8b0" subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] - +db_name = "fractality" +db_instance_id = "fund-data-pipeline-db" testnet = true public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" -provider_url = "https://arb-sepolia.g.alchemy.com/v2/SA1EqG4RW6GcciKBnPtu8n6OJqxy164t" y2k_token_migration_address = "0x2839449acf90EDee7055FdA315875e9539359aA0" frct_r_migration_address = "0xB78028Ca8246d5E8F08Fd656F9337C4E77Cd2389" block_start_number = "119867300" From 80e506a77d7c9e8f8031da1aea1268b7369d0695 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 12 May 2025 18:50:59 +1000 Subject: [PATCH 09/33] Add webpack config --- .github/workflows/deploy.yaml | 24 +++++++--- webpack.config.js | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 webpack.config.js diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 1f4d146..65d2389 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -13,12 +13,11 @@ on: jobs: build: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/test') + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [21.x] environment: ${{ github.event_name == 'pull_request' && github.base_ref || github.ref_name }} env: @@ -29,6 +28,21 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} + - name: Set Version + if: github.ref == 'refs/heads/beraborrow' + run: echo "PROJECT_VERSION=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: npm install + + - name: Build Application + run: npm run build + - name: Set up Terraform uses: hashicorp/setup-terraform@v1 with: diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..d2b0ef4 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,83 @@ +const path = require('path') +const archiver = require('archiver') +const fs = require('fs') + +class ZipPlugin { + apply(compiler) { + compiler.hooks.afterEmit.tapAsync('ZipPlugin', (compilation, callback) => { + const outputPath = path.join(__dirname, 'dist') + const outputFilePath = path.join(outputPath, 'bundle.zip') + + if (!fs.existsSync(outputPath)) { + console.error(`Output directory "${outputPath}" does not exist.`) + return callback(new Error(`Output directory "${outputPath}" does not exist.`)) + } + + const output = fs.createWriteStream(outputFilePath) + const archive = archiver('zip', { zlib: { level: 9 } }) + + output.on('close', () => { + console.log(`bundle.zip has been created (${archive.pointer()} total bytes)`) + callback() + }) + + archive.on('error', (err) => { + console.error('Error during zipping process:', err) + return callback(err) + }) + + archive.pipe(output) + + fs.readdir(outputPath, (err, files) => { + if (err) { + console.error('Error reading output directory:', err) + return callback(err) + } + + files.forEach((file) => { + if (file !== 'bundle.zip') { + const filePath = path.join(outputPath, file) + if (fs.statSync(filePath).isFile()) { + archive.file(filePath, { name: file }) + } else if (fs.statSync(filePath).isDirectory()) { + archive.directory(filePath, file) + } + } + }) + + archive.finalize() + }) + }) + } +} + +module.exports = { + entry: './src/handler.ts', + target: 'node', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'index.js', + libraryTarget: 'commonjs2' + }, + resolve: { + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts$/, + use: 'ts-loader', + exclude: /node_modules/ + } + ] + }, + mode: 'production', + optimization: { + minimize: true, + usedExports: true + }, + externals: { + 'aws-sdk': 'commonjs aws-sdk' + }, + plugins: [new ZipPlugin()] +} From 12454d91eacaf4ce720f5e0baaae80256983ff40 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Mon, 12 May 2025 18:59:49 +1000 Subject: [PATCH 10/33] Use webpack build script --- package-lock.json | 2185 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 8 +- webpack.config.js | 2 +- 3 files changed, 2139 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 990a9cf..c4fb8dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,11 @@ "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", "@types/aws-lambda": "^8.10.147", + "archiver": "^7.0.1", "aws-lambda": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", + "bufferutil": "^4.0.9", "dotenv": "^16.4.5", "drizzle-orm": "^0.36.1", "ethers": "^6.13.4", @@ -22,9 +24,13 @@ "ioredis": "^5.4.2", "node-cron": "^3.0.3", "postgres": "^3.4.5", + "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "typescript": "^5.6.3", + "utf-8-validate": "^6.0.5", "viem": "^2.21.44", + "webpack": "^5.99.8", + "webpack-cli": "^6.0.1", "zod": "^3.23.8" }, "devDependencies": { @@ -714,11 +720,61 @@ "node": ">=12" } }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, "node_modules/@ioredis/commands": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -727,6 +783,35 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -771,6 +856,16 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@scure/base": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.4.tgz", @@ -1417,6 +1512,38 @@ "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.147.tgz", "integrity": "sha512-nD0Z9fNIZcxYX5Mai2CTmFD7wX7UldCkW2ezCF8D1T5hdiLsnTWDGRpfRYntU6VjTdLQjOvyszru7I1c1oCQew==" }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", @@ -1450,6 +1577,208 @@ "@types/node": "*" } }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "license": "Apache-2.0" + }, "node_modules/abitype": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.7.tgz", @@ -1470,6 +1799,18 @@ } } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/acorn": { "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", @@ -1497,11 +1838,67 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1525,6 +1922,86 @@ "node": ">= 8" } }, + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "license": "MIT", + "dependencies": { + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1547,6 +2024,12 @@ "node": ">=6" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1620,11 +2103,23 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -1676,7 +2171,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -1684,6 +2178,38 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "4.9.2", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", @@ -1694,6 +2220,34 @@ "isarray": "^1.0.0" } }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -1738,11 +2292,30 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1758,7 +2331,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -1767,7 +2339,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -1799,10 +2370,33 @@ "fsevents": "~2.3.2" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", "engines": { "node": ">=0.10.0" } @@ -1811,7 +2405,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1822,8 +2415,13 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1930,17 +2528,78 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" }, + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "license": "MIT", + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2154,6 +2813,49 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.151", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", + "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.14.0.tgz", + "integrity": "sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==", + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2170,6 +2872,12 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2181,6 +2889,15 @@ "node": ">= 0.4" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -2190,6 +2907,19 @@ "node": ">=0.8.0" } }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -2202,6 +2932,36 @@ "node": ">=4" } }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/ethers": { "version": "6.13.5", "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.13.5.tgz", @@ -2247,6 +3007,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", @@ -2260,6 +3029,34 @@ "node": ">=0.4.x" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-xml-parser": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", @@ -2281,11 +3078,19 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2305,6 +3110,28 @@ "node": ">=4.0.0" } }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -2332,6 +3159,22 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", @@ -2579,6 +3422,25 @@ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", "dev": true }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2595,6 +3457,15 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/ioredis": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.2.tgz", @@ -2656,6 +3527,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2665,6 +3551,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", @@ -2698,11 +3593,22 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -2720,6 +3626,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", @@ -2739,6 +3657,21 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isows": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", @@ -2753,6 +3686,59 @@ "ws": "*" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -2779,6 +3765,18 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", @@ -2788,20 +3786,91 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } }, - "node_modules/lodash.defaults": { - "version": "4.2.0", + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" }, @@ -2810,6 +3879,12 @@ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2823,6 +3898,25 @@ "node": ">= 0.4" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2854,6 +3948,15 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -2871,6 +3974,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-cron": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", @@ -2890,6 +3999,23 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, "node_modules/nodemon": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", @@ -2922,7 +4048,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2989,6 +4114,57 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2998,11 +4174,47 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -3010,6 +4222,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -3045,6 +4269,21 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -3070,6 +4309,114 @@ "node": ">=0.4.x" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readable-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/readable-stream/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/readable-stream/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3082,6 +4429,18 @@ "node": ">=8.10.0" } }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, "node_modules/redis-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", @@ -3110,6 +4469,76 @@ "node": ">=6" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -3131,11 +4560,29 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "node_modules/schema-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", + "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -3143,6 +4590,15 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -3156,36 +4612,218 @@ "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-format": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", + "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", + "dev": true + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", "dependencies": { - "semver": "^7.5.3" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" - }, - "node_modules/standard-as-callback": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/strnum": { "version": "1.0.5", @@ -3204,6 +4842,18 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/table-layout": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", @@ -3237,11 +4887,107 @@ "node": ">=8" } }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/terser": { + "version": "5.39.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", + "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", + "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -3282,6 +5028,35 @@ "typescript": ">=3.7.0" } }, + "node_modules/ts-loader": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", + "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -3394,6 +5169,36 @@ "node": ">= 4.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -3403,6 +5208,19 @@ "querystring": "0.2.0" } }, + "node_modules/utf-8-validate": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz", + "integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -3415,6 +5233,12 @@ "which-typed-array": "^1.1.2" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -3529,6 +5353,151 @@ "node": ">=10.13.0" } }, + "node_modules/webpack": { + "version": "5.99.8", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", + "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.2", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.11", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-typed-array": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", @@ -3548,6 +5517,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "license": "MIT" + }, "node_modules/wordwrapjs": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", @@ -3570,6 +5545,94 @@ "node": ">=8" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -3624,6 +5687,20 @@ "node": ">=6" } }, + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "license": "MIT", + "dependencies": { + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/zod": { "version": "3.24.1", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", diff --git a/package.json b/package.json index 3245dca..187b7e3 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "node dist/run.js", "dev": "nodemon run.ts", - "build": "tsc", + "build": "webpack", "format": "prettier --write \"**/*.ts\"" }, "author": "Jose Herrera", @@ -15,9 +15,11 @@ "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", "@types/aws-lambda": "^8.10.147", + "archiver": "^7.0.1", "aws-lambda": "^1.0.7", "aws-sdk": "^2.1692.0", "axios": "^1.7.7", + "bufferutil": "^4.0.9", "dotenv": "^16.4.5", "drizzle-orm": "^0.36.1", "ethers": "^6.13.4", @@ -25,9 +27,13 @@ "ioredis": "^5.4.2", "node-cron": "^3.0.3", "postgres": "^3.4.5", + "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "typescript": "^5.6.3", + "utf-8-validate": "^6.0.5", "viem": "^2.21.44", + "webpack": "^5.99.8", + "webpack-cli": "^6.0.1", "zod": "^3.23.8" }, "devDependencies": { diff --git a/webpack.config.js b/webpack.config.js index d2b0ef4..c3ed91b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -52,7 +52,7 @@ class ZipPlugin { } module.exports = { - entry: './src/handler.ts', + entry: './handler.ts', target: 'node', output: { path: path.resolve(__dirname, 'dist'), From 0997a45b8ef7b95bbacbea5f7ba732bce2ad7ba8 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 17:55:58 +1000 Subject: [PATCH 11/33] Use PK for test mnemonic for main --- terraform/environments/test.tfvars | 15 ++++++----- terraform/lambda.tf | 41 ++++++++++++++++-------------- terraform/secrets.tf | 10 +++++++- 3 files changed, 39 insertions(+), 27 deletions(-) diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index f60a7a4..90007ee 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -4,13 +4,14 @@ environment = "test" vpc_id = "vpc-08ae44a5cd755d8b0" subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] -db_name = "fractality" -db_instance_id = "fund-data-pipeline-db" -testnet = true +db_name = "fractality" +db_instance_id = "fund-data-pipeline-db" +testnet = true + public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" -y2k_token_migration_address = "0x2839449acf90EDee7055FdA315875e9539359aA0" -frct_r_migration_address = "0xB78028Ca8246d5E8F08Fd656F9337C4E77Cd2389" -block_start_number = "119867300" -safety_cushion_number_of_blocks = "250" +y2k_token_migration_address = "0xb8b47E61188Cc197F36C48B2298cb05afE4332E1" +frct_r_migration_address = "0x06a4F1CAa90d22a4b461fB970D4C22Ef63987a5c" +block_start_number = "335048846" +safety_cushion_number_of_blocks = "5" diff --git a/terraform/lambda.tf b/terraform/lambda.tf index ca8309c..def8ef2 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -42,25 +42,28 @@ resource "aws_lambda_function" "default" { timeout = 300 environment { - variables = { - PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url - BLOCKCHAIN_ENVIRONMENT = var.environment - PUBLIC_ADDRESS = var.public_address - TOKEN_ADDRESS = var.token_address - TESTNET = var.testnet - MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn - DB_USER = local.db_username - DB_PASSWORD = local.db_password - DB_HOST = data.aws_db_instance.db.address - DB_NAME = local.db_name - DB_SCHEMA = local.db_schema - Y2K_TOKEN_MIGRATION_ADDRESS = var.y2k_token_migration_address - FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address - BLOCK_START_NUMBER = var.block_start_number - SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks - REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${aws_elasticache_replication_group.redis_cluster.port}" - REDIS_USE_TLS = "true" - } + variables = merge( + { + PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url + BLOCKCHAIN_ENVIRONMENT = var.environment + PUBLIC_ADDRESS = var.public_address + TOKEN_ADDRESS = var.token_address + TESTNET = var.testnet + DB_USER = local.db_username + DB_PASSWORD = local.db_password + DB_HOST = data.aws_db_instance.db.address + DB_NAME = local.db_name + DB_SCHEMA = local.db_schema + Y2K_TOKEN_MIGRATION_ADDRESS = var.y2k_token_migration_address + FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address + BLOCK_START_NUMBER = var.block_start_number + SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${aws_elasticache_replication_group.redis_cluster.port}" + REDIS_USE_TLS = "true" + }, + var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, + var.environment == "test" ? { PRIVATE_KEY = data.aws_secretsmanager_secret.private_key.arn } : {} + ) } vpc_config { diff --git a/terraform/secrets.tf b/terraform/secrets.tf index d286401..8c3494e 100644 --- a/terraform/secrets.tf +++ b/terraform/secrets.tf @@ -7,9 +7,17 @@ data "aws_secretsmanager_secret_version" "db" { } data "aws_secretsmanager_secret" "mnemonic" { - name = "FRACTALITY_MIGRATION_${var.environment}" + name = "FRACTALITY_MIGRATION_MN_${upper(var.environment)}" } data "aws_secretsmanager_secret_version" "mnemonic" { secret_id = data.aws_secretsmanager_secret.mnemonic.id } + +data "aws_secretsmanager_secret" "private_key" { + name = "FRACTALITY_MIGRATION_PK_${upper(var.environment)}" +} + +data "aws_secretsmanager_secret_version" "private_key" { + secret_id = data.aws_secretsmanager_secret.private_key.id +} From c56a680156fbe13334955ba6744c86fc399c58a5 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 18:15:34 +1000 Subject: [PATCH 12/33] Have a default value for the redis cluster --- terraform/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index def8ef2..4c06ce4 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -58,7 +58,7 @@ resource "aws_lambda_function" "default" { FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address BLOCK_START_NUMBER = var.block_start_number SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks - REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${aws_elasticache_replication_group.redis_cluster.port}" + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${try(aws_elasticache_replication_group.redis_cluster.port, 6379)}" REDIS_USE_TLS = "true" }, var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, From aba606627a7e28a4082f4a374f1e8f4f9f1fe6fe Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 18:19:19 +1000 Subject: [PATCH 13/33] Hardcode port --- terraform/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 4c06ce4..1f0fac4 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -58,7 +58,7 @@ resource "aws_lambda_function" "default" { FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address BLOCK_START_NUMBER = var.block_start_number SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks - REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:${try(aws_elasticache_replication_group.redis_cluster.port, 6379)}" + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379}" REDIS_USE_TLS = "true" }, var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, From 2678a240ed3bd9ece6629324f839434f74deb960 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 18:27:04 +1000 Subject: [PATCH 14/33] Add redis node type --- terraform/elasticache.tf | 1 + 1 file changed, 1 insertion(+) diff --git a/terraform/elasticache.tf b/terraform/elasticache.tf index 7b6d6c2..27ac280 100644 --- a/terraform/elasticache.tf +++ b/terraform/elasticache.tf @@ -34,6 +34,7 @@ resource "aws_elasticache_replication_group" "redis_cluster" { description = "{var.name} cluster for ${var.environment}" engine = "redis" engine_version = "6.x" + node_type = "cache.t3.micro" security_group_ids = [ aws_security_group.redis_sg.id, aws_security_group.lambda_sg.id From ed1d9498bd67b13cf706b47e18cf8fd21343265c Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 20:03:51 +1000 Subject: [PATCH 15/33] Use one cache cluster --- terraform/elasticache.tf | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/terraform/elasticache.tf b/terraform/elasticache.tf index 27ac280..41f103f 100644 --- a/terraform/elasticache.tf +++ b/terraform/elasticache.tf @@ -40,7 +40,8 @@ resource "aws_elasticache_replication_group" "redis_cluster" { aws_security_group.lambda_sg.id ] subnet_group_name = aws_elasticache_subnet_group.redis_subnet.name - automatic_failover_enabled = true + automatic_failover_enabled = false + num_cache_clusters = 1 tags = { From f4afc2668c5d8508dbade6bfa898a939d1a9bb87 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 20:25:25 +1000 Subject: [PATCH 16/33] Make NODE_ENV optional --- env.ts | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/env.ts b/env.ts index 7921426..c3d17d9 100644 --- a/env.ts +++ b/env.ts @@ -1,6 +1,6 @@ -import { z } from "zod"; -import dotenv from "dotenv"; -dotenv.config(); +import { z } from 'zod' +import dotenv from 'dotenv' +dotenv.config() const envSchema = z.object({ PRIVATE_KEY: z.string(), @@ -12,7 +12,7 @@ const envSchema = z.object({ PROVIDER_URL: z.string(), Y2K_TOKEN_MIGRATION_ADDRESS: z.string(), FRCT_R_MIGRATION_ADDRESS: z.string(), - NODE_ENV: z.string(), + NODE_ENV: z.string().optional(), DB_NAME: z.string(), DB_HOST: z.string().optional(), DB_PORT: z.coerce.number().optional(), @@ -23,21 +23,21 @@ const envSchema = z.object({ BLOCK_START_NUMBER: z.string(), SAFETY_CUSHION_NUMBER_OF_BLOCKS: z.coerce.number(), REDIS_CONNECTION_STRING: z.string(), - REDIS_USE_TLS: z.preprocess((str) => str === "true", z.boolean()), -}); + REDIS_USE_TLS: z.preprocess((str) => str === 'true', z.boolean()) +}) -const parsedEnv = envSchema.safeParse(process.env); +const parsedEnv = envSchema.safeParse(process.env) if (!parsedEnv.success) { const formattedErrors = parsedEnv.error.errors.map((err) => ({ - path: err.path.join("."), - message: err.message, - })); + path: err.path.join('.'), + message: err.message + })) - console.error("Environment variable validation failed:", formattedErrors); - throw new Error("Invalid environment variables."); + console.error('Environment variable validation failed:', formattedErrors) + throw new Error('Invalid environment variables.') } -export type BotEnv = z.infer; +export type BotEnv = z.infer -export const env = parsedEnv.data; +export const env = parsedEnv.data From a730b2b0fe76ded566cfa69d777fa06e8b3eaded Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 22:33:49 +1000 Subject: [PATCH 17/33] Fix connection string --- terraform/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 1f0fac4..c758de4 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -58,7 +58,7 @@ resource "aws_lambda_function" "default" { FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address BLOCK_START_NUMBER = var.block_start_number SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks - REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379}" + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379" REDIS_USE_TLS = "true" }, var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, From 51260eab4f7d149de8576795b56109d6eabe28de Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 22:56:21 +1000 Subject: [PATCH 18/33] Disable TLS --- terraform/lambda.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index c758de4..da081c8 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -59,7 +59,7 @@ resource "aws_lambda_function" "default" { BLOCK_START_NUMBER = var.block_start_number SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379" - REDIS_USE_TLS = "true" + REDIS_USE_TLS = "false" }, var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, var.environment == "test" ? { PRIVATE_KEY = data.aws_secretsmanager_secret.private_key.arn } : {} From ab13c33e2cbd4774148c25c7a37a4c8d10f4cdfc Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 23:01:46 +1000 Subject: [PATCH 19/33] Use secret with key pair --- terraform/secrets.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/secrets.tf b/terraform/secrets.tf index 8c3494e..49fd40d 100644 --- a/terraform/secrets.tf +++ b/terraform/secrets.tf @@ -15,7 +15,7 @@ data "aws_secretsmanager_secret_version" "mnemonic" { } data "aws_secretsmanager_secret" "private_key" { - name = "FRACTALITY_MIGRATION_PK_${upper(var.environment)}" + name = "FRACTALITY_MIGRATION_PRIVATE_KEY_${upper(var.environment)}" } data "aws_secretsmanager_secret_version" "private_key" { From 2e6c88fcc68199ab38f05de6c3707802841185ed Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 23:10:02 +1000 Subject: [PATCH 20/33] Use private key as MN --- terraform/lambda.tf | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index da081c8..25639a0 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -42,28 +42,25 @@ resource "aws_lambda_function" "default" { timeout = 300 environment { - variables = merge( - { - PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url - BLOCKCHAIN_ENVIRONMENT = var.environment - PUBLIC_ADDRESS = var.public_address - TOKEN_ADDRESS = var.token_address - TESTNET = var.testnet - DB_USER = local.db_username - DB_PASSWORD = local.db_password - DB_HOST = data.aws_db_instance.db.address - DB_NAME = local.db_name - DB_SCHEMA = local.db_schema - Y2K_TOKEN_MIGRATION_ADDRESS = var.y2k_token_migration_address - FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address - BLOCK_START_NUMBER = var.block_start_number - SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks - REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379" - REDIS_USE_TLS = "false" - }, - var.environment == "main" ? { MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.mnemonic.arn } : {}, - var.environment == "test" ? { PRIVATE_KEY = data.aws_secretsmanager_secret.private_key.arn } : {} - ) + variables = { + PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url + BLOCKCHAIN_ENVIRONMENT = var.environment + PUBLIC_ADDRESS = var.public_address + TOKEN_ADDRESS = var.token_address + TESTNET = var.testnet + DB_USER = local.db_username + DB_PASSWORD = local.db_password + DB_HOST = data.aws_db_instance.db.address + DB_NAME = local.db_name + DB_SCHEMA = local.db_schema + Y2K_TOKEN_MIGRATION_ADDRESS = var.y2k_token_migration_address + FRCT_R_MIGRATION_ADDRESS = var.frct_r_migration_address + BLOCK_START_NUMBER = var.block_start_number + SAFETY_CUSHION_NUMBER_OF_BLOCKS = var.safety_cushion_number_of_blocks + REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379" + REDIS_USE_TLS = "false" + MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.private_key.arn + } } vpc_config { From 3120b4458cdf07fe511ee95f263b77081a25934d Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 23:13:13 +1000 Subject: [PATCH 21/33] Make private key optional --- env.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/env.ts b/env.ts index c3d17d9..3be6bbc 100644 --- a/env.ts +++ b/env.ts @@ -3,7 +3,7 @@ import dotenv from 'dotenv' dotenv.config() const envSchema = z.object({ - PRIVATE_KEY: z.string(), + PRIVATE_KEY: z.string().optional(), PUBLIC_ADDRESS: z.string(), MNEMONIC_SECRET_ARN: z.string().optional(), AWS_REGION: z.string().optional(), From 444a23c0cf529613b6afa4418750ee47aec95749 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 23:19:04 +1000 Subject: [PATCH 22/33] Add debug logging --- libs/SecretManager.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/libs/SecretManager.ts b/libs/SecretManager.ts index 6a1a804..872615d 100644 --- a/libs/SecretManager.ts +++ b/libs/SecretManager.ts @@ -1,30 +1,28 @@ -import { - SecretsManagerClient, - GetSecretValueCommand, -} from "@aws-sdk/client-secrets-manager"; -import { env } from "../env"; +import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager' +import { env } from '../env' async function getSecret(): Promise { const client = new SecretsManagerClient({ - region: env.AWS_REGION, - }); + region: env.AWS_REGION + }) try { const response = await client.send( new GetSecretValueCommand({ SecretId: env.MNEMONIC_SECRET_ARN, - VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified + VersionStage: 'AWSCURRENT' // VersionStage defaults to AWSCURRENT if unspecified }) - ); + ) if (!response.SecretString) { - return null; + return null } - return response.SecretString; + return response.SecretString } catch (error) { - return null; + console.error('Error retrieving secret:', error) + return null } } -export default getSecret; +export default getSecret From e6417b33ae18fd3ac189cba329648dbe86adb097 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Wed, 14 May 2025 23:20:26 +1000 Subject: [PATCH 23/33] Add secrets policy --- terraform/lambda.tf | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 25639a0..4caed9f 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -80,6 +80,29 @@ resource "aws_iam_role_policy_attachment" "lambda_vpc_access" { policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" } +data "aws_iam_policy_document" "secretsmanager_access" { + statement { + effect = "Allow" + actions = [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret" + ] + resources = [ + data.aws_secretsmanager_secret.private_key.arn, + ] + } +} + +resource "aws_iam_policy" "secretsmanager_access" { + name = "${var.name}-secretsmanager-access-${var.environment}" + policy = data.aws_iam_policy_document.secretsmanager_access.json +} + +resource "aws_iam_role_policy_attachment" "secretsmanager_access" { + role = aws_iam_role.default.name + policy_arn = aws_iam_policy.secretsmanager_access.arn +} + resource "aws_iam_role_policy_attachment" "lambda_policy" { role = aws_iam_role.default.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" From af3472b6632a2be6749c82efc7d044bbae6c23cf Mon Sep 17 00:00:00 2001 From: dylmanning Date: Thu, 15 May 2025 21:11:24 +1000 Subject: [PATCH 24/33] Use mainnet for test --- terraform/environments/test.tfvars | 2 +- terraform/lambda.tf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index 90007ee..02959ad 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -6,7 +6,7 @@ subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] db_name = "fractality" db_instance_id = "fund-data-pipeline-db" -testnet = true +testnet = false public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" diff --git a/terraform/lambda.tf b/terraform/lambda.tf index 4caed9f..c3d09d5 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -44,7 +44,7 @@ resource "aws_lambda_function" "default" { environment { variables = { PROVIDER_URL = var.environment == "main" ? var.mainnet_provider_url : var.testnet_provider_url - BLOCKCHAIN_ENVIRONMENT = var.environment + BLOCKCHAIN_ENVIRONMENT = "main" PUBLIC_ADDRESS = var.public_address TOKEN_ADDRESS = var.token_address TESTNET = var.testnet From 31e14d950d53c10353c394761441c8ea989ac6f9 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Thu, 15 May 2025 21:45:57 +1000 Subject: [PATCH 25/33] Chunk getContractEvents into 500 block range --- libs/BlockchainConnectionProvider.ts | 225 ++++++++++++++------------- 1 file changed, 115 insertions(+), 110 deletions(-) diff --git a/libs/BlockchainConnectionProvider.ts b/libs/BlockchainConnectionProvider.ts index a20aa68..61c743b 100644 --- a/libs/BlockchainConnectionProvider.ts +++ b/libs/BlockchainConnectionProvider.ts @@ -1,92 +1,81 @@ -import { - Address, - createPublicClient, - Log, - decodeEventLog, - http, - GetContractEventsReturnType, - getContract, -} from "viem"; -import IORedis from "ioredis"; -import { arbitrum, arbitrumSepolia } from "viem/chains"; -import { env } from "../env"; -import abi from "../contracts/FractalityTokenMigration.sol.json"; -import { MigrationRegisteredEvent } from "../interfaces"; -import { MySqlBigInt64BuilderInitial } from "drizzle-orm/mysql-core"; -import { BlockchainConnectionError } from "../errors"; +import { Address, createPublicClient, decodeEventLog, http, getContract } from 'viem' +import { arbitrum, arbitrumSepolia } from 'viem/chains' +import { env } from '../env' +import abi from '../contracts/FractalityTokenMigration.sol.json' +import { MigrationRegisteredEvent } from '../interfaces' +import { BlockchainConnectionError } from '../errors' interface BlockchainConnectionProviderOptions { - providerUrl: string; - y2kTokenMigrationAddress: Address; - frctRTokenMigrationAddress: Address; + providerUrl: string + y2kTokenMigrationAddress: Address + frctRTokenMigrationAddress: Address } const ERC20Abi = [ { - name: "decimals", - type: "function", + name: 'decimals', + type: 'function', inputs: [], - outputs: [{ name: "", type: "uint8" }], - stateMutability: "view", + outputs: [{ name: '', type: 'uint8' }], + stateMutability: 'view' }, { - name: "balanceOf", - type: "function", - inputs: [{ name: "account", type: "address" }], - outputs: [{ name: "", type: "uint256" }], - stateMutability: "view", + name: 'balanceOf', + type: 'function', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view' }, { - name: "totalSupply", - type: "function", + name: 'totalSupply', + type: 'function', inputs: [], - outputs: [{ name: "", type: "uint256" }], - stateMutability: "view", + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view' }, { - name: "name", - type: "function", + name: 'name', + type: 'function', inputs: [], - outputs: [{ name: "", type: "string" }], - stateMutability: "view", + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view' }, { - name: "symbol", - type: "function", + name: 'symbol', + type: 'function', inputs: [], - outputs: [{ name: "", type: "string" }], - stateMutability: "view", - }, -]; + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view' + } +] export class BlockchainConnectionProvider { - public lastAssetProcessedBlock: bigint = BigInt(0); - public lastShareProcessedBlock: bigint = BigInt(0); - public y2kTokenMigrationAddress: Address; - public frctRTokenMigrationAddress: Address; + public lastAssetProcessedBlock: bigint = BigInt(0) + public lastShareProcessedBlock: bigint = BigInt(0) + public y2kTokenMigrationAddress: Address + public frctRTokenMigrationAddress: Address - public _viemClient: ReturnType; + public _viemClient: ReturnType constructor(opts: BlockchainConnectionProviderOptions) { - this.y2kTokenMigrationAddress = opts.y2kTokenMigrationAddress; - this.frctRTokenMigrationAddress = opts.frctRTokenMigrationAddress; - this._viemClient = this._init(opts); - console.log("Connected to blockchain", this._viemClient.chain!.name); + this.y2kTokenMigrationAddress = opts.y2kTokenMigrationAddress + this.frctRTokenMigrationAddress = opts.frctRTokenMigrationAddress + this._viemClient = this._init(opts) + console.log('Connected to blockchain', this._viemClient.chain!.name) } private _init = (opts: BlockchainConnectionProviderOptions) => { return createPublicClient({ - chain: env.BLOCKCHAIN_ENVIRONMENT === "test" ? arbitrumSepolia : arbitrum, - transport: http(opts.providerUrl), - }); - }; + chain: env.BLOCKCHAIN_ENVIRONMENT === 'test' ? arbitrumSepolia : arbitrum, + transport: http(opts.providerUrl) + }) + } public async getCurrentBlockNumber(): Promise { try { - return this._viemClient.getBlockNumber(); + return this._viemClient.getBlockNumber() } catch (error) { - throw new BlockchainConnectionError("Error getting current block number: " + error); + throw new BlockchainConnectionError('Error getting current block number: ' + error) } - } public async getArbitrumTokenDecimals(): Promise { @@ -94,82 +83,98 @@ export class BlockchainConnectionProvider { const y2kMigrationContract = getContract({ address: this.y2kTokenMigrationAddress, abi: abi.abi, - client: { public: this._viemClient }, - }); + client: { public: this._viemClient } + }) const frctRMigrationContract = getContract({ address: this.frctRTokenMigrationAddress, abi: abi.abi, - client: { public: this._viemClient }, - }); - const y2kToken = await y2kMigrationContract.read.token(); - const frctRToken = await frctRMigrationContract.read.token(); + client: { public: this._viemClient } + }) + const y2kToken = await y2kMigrationContract.read.token() + const frctRToken = await frctRMigrationContract.read.token() const y2kErc20 = getContract({ address: y2kToken as Address, abi: ERC20Abi, - client: { public: this._viemClient }, - }); + client: { public: this._viemClient } + }) const frctRErc20 = getContract({ address: frctRToken as Address, abi: ERC20Abi, - client: { public: this._viemClient }, - }); + client: { public: this._viemClient } + }) - const y2kDecimals = (await y2kErc20.read.decimals()) as bigint; - const frctRDecimals = (await frctRErc20.read.decimals()) as bigint; + const y2kDecimals = (await y2kErc20.read.decimals()) as bigint + const frctRDecimals = (await frctRErc20.read.decimals()) as bigint if (y2kDecimals !== frctRDecimals) { - throw new Error("Fatal error:Y2K and FRCT-R decimals do not match"); + throw new Error('Fatal error:Y2K and FRCT-R decimals do not match') } - return y2kDecimals; + return y2kDecimals } - //TODO: make sure that we run this in a way that it doesn't exceed alchemy's 2000 block limit public async scanMigrations( migrationAddress: Address, fromBlock: bigint, toBlock: bigint ): Promise { -try{ - //This is the current block number - if (fromBlock >= toBlock) { - throw new Error("from block must be before toBlock"); + try { + if (fromBlock >= toBlock) { + throw new Error('from block must be before toBlock') + } + + console.log(`Getting blocks from ${fromBlock} to ${toBlock}`) + + // NOTE: Alchemy block limit + const CHUNK_SIZE = BigInt(500) + + const decodedLogs: MigrationRegisteredEvent[] = [] + + let currentFromBlock = fromBlock + + while (currentFromBlock <= toBlock) { + const chunkToBlock = + currentFromBlock + CHUNK_SIZE > toBlock + ? toBlock + : currentFromBlock + CHUNK_SIZE - BigInt(1) + + console.log(`Fetching chunk from ${currentFromBlock} to ${chunkToBlock}`) + + const logs = await this._viemClient.getContractEvents({ + address: migrationAddress, + abi: abi.abi, + eventName: 'MigrationRegistered', + fromBlock: currentFromBlock, + toBlock: chunkToBlock + }) + + logs.forEach((log) => { + const decodedLog = decodeEventLog({ + abi: abi.abi, + data: log.data, + topics: log.topics, + eventName: 'MigrationRegistered' + }) + + decodedLogs.push({ + migrationContractAddress: migrationAddress, + eventName: decodedLog.eventName, + transactionHash: log.transactionHash, + caller: (decodedLog.args as any).caller as Address, + migrationAddress: (decodedLog.args as any).migrationAddress as Address, + amount: BigInt((decodedLog.args as any).amount as string) + }) + }) + + currentFromBlock = chunkToBlock + BigInt(1) + } + + return decodedLogs + } catch (error) { + throw new BlockchainConnectionError('Error scanning migrations: ' + error) } - - console.log(`Getting blocks from ${fromBlock} to ${toBlock}`); - const logs = await this._viemClient.getContractEvents({ - address: migrationAddress, - abi: abi.abi, - eventName: "MigrationRegistered", - fromBlock, - toBlock, - }); - - let decodedLogs: MigrationRegisteredEvent[] = []; - - logs.forEach((log) => { - const decodedLog = decodeEventLog({ - abi: abi.abi, - data: log.data, - topics: log.topics, - eventName: "MigrationRegistered", - }); - - decodedLogs.push({ - migrationContractAddress: migrationAddress, - eventName: decodedLog.eventName, - transactionHash: log.transactionHash, - caller: (decodedLog.args as any).caller as Address, - migrationAddress: (decodedLog.args as any).migrationAddress as Address, - amount: BigInt((decodedLog.args as any).amount as string), - }); - }); - return decodedLogs; - } catch (error) { - throw new BlockchainConnectionError("Error scanning migrations: " + error); - } } } From 8b8201aae6e0cd5ebfbe5372dd1113fdb7bc73c3 Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Thu, 15 May 2025 19:21:54 -0400 Subject: [PATCH 26/33] added more accurate error type --- errors.ts | 7 +++++++ libs/BlockchainConnectionProvider.ts | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/errors.ts b/errors.ts index 7207fa4..33984b3 100644 --- a/errors.ts +++ b/errors.ts @@ -16,6 +16,13 @@ export class RedisError extends Error { } } + export class AlchemyScanError extends Error { + constructor(message: string) { + super(message); + this.name = "AlchemyScanError"; + } + } + export class DecimalConversionError extends Error { constructor(message: string) { super(message); diff --git a/libs/BlockchainConnectionProvider.ts b/libs/BlockchainConnectionProvider.ts index 61c743b..27217c3 100644 --- a/libs/BlockchainConnectionProvider.ts +++ b/libs/BlockchainConnectionProvider.ts @@ -3,7 +3,7 @@ import { arbitrum, arbitrumSepolia } from 'viem/chains' import { env } from '../env' import abi from '../contracts/FractalityTokenMigration.sol.json' import { MigrationRegisteredEvent } from '../interfaces' -import { BlockchainConnectionError } from '../errors' +import { AlchemyScanError, BlockchainConnectionError } from '../errors' interface BlockchainConnectionProviderOptions { providerUrl: string y2kTokenMigrationAddress: Address @@ -174,7 +174,7 @@ export class BlockchainConnectionProvider { return decodedLogs } catch (error) { - throw new BlockchainConnectionError('Error scanning migrations: ' + error) + throw new AlchemyScanError(`Error scanning migrations in block range ${fromBlock} to ${toBlock}: ${error}`) } } } From a5f99e2f3560b39b0af344331f841d8efa631042 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Tue, 20 May 2025 14:10:19 +1000 Subject: [PATCH 27/33] Change token address --- terraform/environments/test.tfvars | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index 02959ad..fb13140 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -9,7 +9,7 @@ db_instance_id = "fund-data-pipeline-db" testnet = false public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" -token_address = "0xccbee9ae8e6666c53e55058c7b97ccee" +token_address = "0xbdeaa95bd62d96a76f2511fcd8ac810f" y2k_token_migration_address = "0xb8b47E61188Cc197F36C48B2298cb05afE4332E1" frct_r_migration_address = "0x06a4F1CAa90d22a4b461fB970D4C22Ef63987a5c" block_start_number = "335048846" From 49efa0f780349f5807a114454df157cf7a94782b Mon Sep 17 00:00:00 2001 From: dylmanning Date: Tue, 20 May 2025 15:25:59 +1000 Subject: [PATCH 28/33] Add slack logging --- .github/workflows/deploy.yaml | 3 +- env.ts | 4 +- libs/Slack.ts | 100 +++++++++++++++++ migrationService.ts | 171 ++++++++++++----------------- package-lock.json | 132 +++++++++++++++++++++- package.json | 1 + terraform/environments/main.tfvars | 1 + terraform/environments/test.tfvars | 1 + terraform/lambda.tf | 2 + terraform/var.tf | 9 ++ 10 files changed, 319 insertions(+), 105 deletions(-) create mode 100644 libs/Slack.ts diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 65d2389..2782fc4 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -67,7 +67,8 @@ jobs: -var="region=${{ vars.AWS_REGION }}" \ -var="project_version=${{ env.PROJECT_VERSION }}" \ -var="testnet_provider_url=${{ secrets.TESTNET_PROVIDER_URL }}" \ - -var="mainnet_provider_url=${{ secrets.MAINNET_PROVIDER_URL }}" + -var="mainnet_provider_url=${{ secrets.MAINNET_PROVIDER_URL }}" \ + -var="slack_token=${{ secrets.SLACK_TOKEN }}" env: AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/env.ts b/env.ts index 3be6bbc..1af9b69 100644 --- a/env.ts +++ b/env.ts @@ -23,7 +23,9 @@ const envSchema = z.object({ BLOCK_START_NUMBER: z.string(), SAFETY_CUSHION_NUMBER_OF_BLOCKS: z.coerce.number(), REDIS_CONNECTION_STRING: z.string(), - REDIS_USE_TLS: z.preprocess((str) => str === 'true', z.boolean()) + REDIS_USE_TLS: z.preprocess((str) => str === 'true', z.boolean()), + SLACK_TOKEN: z.string().optional(), + SLACK_CHANNEL_ID: z.string().optional() }) const parsedEnv = envSchema.safeParse(process.env) diff --git a/libs/Slack.ts b/libs/Slack.ts new file mode 100644 index 0000000..003765d --- /dev/null +++ b/libs/Slack.ts @@ -0,0 +1,100 @@ +import { WebClient } from '@slack/web-api' + +export class Slack { + private static instance: Slack + private service: string + private client: WebClient + private channelId: string + private console: typeof console + + private messageQueue: { level: 'info' | 'warn' | 'error'; args: any[] }[] = [] + private isProcessingQueue = false + + private constructor(token: string, channelId: string) { + this.service = 'migration-service' + this.client = new WebClient(token) + this.channelId = channelId + this.console = { ...console } + this.hookConsole() + } + + static initialize(token: string, channelId: string): Slack { + console.log('Initializing Slack hook...') + if (!Slack.instance) { + Slack.instance = new Slack(token, channelId) + } + return Slack.instance + } + + static getInstance(): Slack { + if (!Slack.instance) { + throw new Error('Slack not initialized. Call Slack.initialize first') + } + return Slack.instance + } + + private hookConsole() { + console.info = (...args) => { + this.console.info(...args) + this.enqueueMessage('info', args) + } + + console.warn = (...args) => { + this.console.warn(...args) + this.enqueueMessage('warn', args) + } + + console.error = (...args) => { + this.console.error(...args) + this.enqueueMessage('error', args) + } + } + + private getMessagePrefix(): string { + return `[${this.service}] [${new Date().toISOString()}]` + } + + private enqueueMessage(level: 'info' | 'warn' | 'error', args: any[]) { + this.messageQueue.push({ level, args }) + void this.processQueue() + } + + private async processQueue() { + if (this.isProcessingQueue) return + + this.isProcessingQueue = true + while (this.messageQueue.length > 0) { + const { level, args } = this.messageQueue.shift()! + const prefix = this.getMessagePrefix() + const formattedArgs = [prefix, ...args] + + try { + switch (level) { + case 'info': + await this.sendMessage(`ℹ️ \`INFO\`\n\`\`\`${formattedArgs.join(' ')}\`\`\``) + break + case 'warn': + await this.sendMessage(`⚠️ \`WARN\`\n\`\`\`${formattedArgs.join(' ')}\`\`\``) + break + case 'error': + await this.sendMessage(`🚨 \`ERROR\`\n\`\`\`${formattedArgs.join(' ')}\`\`\``) + break + } + } catch (error) { + this.console.error('Error sending message to Slack:', error) + } + } + this.isProcessingQueue = false + } + + async sendMessage(message: string) { + try { + await this.client.chat.postMessage({ + channel: this.channelId, + text: message + }) + } catch (error) { + this.console.error('Error sending message to Slack:', error) + } + } +} diff --git a/migrationService.ts b/migrationService.ts index 4fe5711..8489e20 100644 --- a/migrationService.ts +++ b/migrationService.ts @@ -1,95 +1,81 @@ -import { PrivateKeyManager } from "./libs/PrivateKeyManager"; -const privateKeyManager = new PrivateKeyManager(); -import { env } from "./env"; -import { BlockchainConnectionProvider } from "./libs/BlockchainConnectionProvider"; -import { Address } from "viem"; +import { PrivateKeyManager } from './libs/PrivateKeyManager' +const privateKeyManager = new PrivateKeyManager() +import { env } from './env' +import { BlockchainConnectionProvider } from './libs/BlockchainConnectionProvider' +import { Address } from 'viem' import { filterAndaddNewFractalityTokenMigrations, getUnmigratedFractalityTokenMigrations, finalizeHlMigrations, setHLMigrationStatus, - TokenMigration, -} from "./database"; -import { - HLMigration, - MigrationRegisteredEvent, - MigrationStatus, -} from "./interfaces"; -import { initializeDatabaseConnection } from "./database"; -import cron from "node-cron"; -import { PreviousBlockManager } from "./libs/PreviousBlockManager"; -import { HyperliquidManager } from "./libs/HyperliquidManager"; -import { FatalFinalizationError, MigrationPrepError, RedisError } from "./errors"; -import { RedisOperations } from "./redisOperations/redisOperations"; + TokenMigration +} from './database' +import { HLMigration, MigrationRegisteredEvent, MigrationStatus } from './interfaces' +import { initializeDatabaseConnection } from './database' +import cron from 'node-cron' +import { PreviousBlockManager } from './libs/PreviousBlockManager' +import { HyperliquidManager } from './libs/HyperliquidManager' +import { FatalFinalizationError, MigrationPrepError, RedisError } from './errors' +import { RedisOperations } from './redisOperations/redisOperations' +import { Slack } from './libs/Slack' export async function main(runWithCron: boolean) { - await privateKeyManager.init(); - await initializeDatabaseConnection(); + if (env.SLACK_TOKEN) { + Slack.initialize(process.env.SLACK_TOKEN!, process.env.SLACK_CHANNEL_ID!) + } + + await privateKeyManager.init() + await initializeDatabaseConnection() - const redisOperations = new RedisOperations(); - await redisOperations.initialize(); + const redisOperations = new RedisOperations() + await redisOperations.initialize() - const hlManager = new HyperliquidManager( - true, - true, - privateKeyManager.getPrivateKey() - ); + const hlManager = new HyperliquidManager(true, true, privateKeyManager.getPrivateKey()) const blockchainConnectionProvider = new BlockchainConnectionProvider({ providerUrl: env.PROVIDER_URL, y2kTokenMigrationAddress: env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, - frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address, - }); + frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address + }) - await hlManager.init( - await blockchainConnectionProvider.getArbitrumTokenDecimals() - ); + await hlManager.init(await blockchainConnectionProvider.getArbitrumTokenDecimals()) const blockManager = new PreviousBlockManager( redisOperations, BigInt(env.SAFETY_CUSHION_NUMBER_OF_BLOCKS), () => blockchainConnectionProvider.getCurrentBlockNumber() - ); + ) if (runWithCron) { - console.log("starting cron job for migrations, running every 5 minutes"); - const scheduledTask = cron.schedule("* * * * *", async () => { + console.log('starting cron job for migrations, running every 5 minutes') + const scheduledTask = cron.schedule('* * * * *', async () => { try { - await coreMigrationService( - blockManager, - blockchainConnectionProvider, - hlManager - ); + await coreMigrationService(blockManager, blockchainConnectionProvider, hlManager) } catch (error) { if (error instanceof FatalFinalizationError) { - scheduledTask.stop(); + scheduledTask.stop() } else { - console.log("Error in core migration service, this run will be skipped", error); + console.log('Error in core migration service, this run will be skipped', error) } } - }); + }) } else { try { if (await redisOperations.shouldRunAccordingToStopRunningFlag()) { - await coreMigrationService( - blockManager, - blockchainConnectionProvider, - hlManager - ); + await coreMigrationService(blockManager, blockchainConnectionProvider, hlManager) } else { - console.log("stopRunning flag is set, not running core migration service"); - return; + console.log('stopRunning flag is set, not running core migration service') + return } } catch (error) { if (error instanceof FatalFinalizationError) { - await redisOperations.setStopRunningFlag(); + await redisOperations.setStopRunningFlag() } else { - console.log("Error in core migration service, this run will be skipped", error); + console.log('Error in core migration service, this run will be skipped', error) } - throw error; + throw error } } - } export async function coreMigrationService( @@ -98,106 +84,91 @@ export async function coreMigrationService( hlManager: HyperliquidManager ) { //This, if fails will do a scan from the start block, not a big dea. - const fromBlock = await blockManager.getFromBlockForScan(); + const fromBlock = await blockManager.getFromBlockForScan() //This gets the current block and sets it in redis. If fails, will bubble up and this run will be skipped. - const toBlock = await blockManager.setFromBlockForScanToCurrentBlock(); - console.log( - `looking for migrations from block ${fromBlock} to block ${toBlock}` - ); + const toBlock = await blockManager.setFromBlockForScanToCurrentBlock() + console.log(`looking for migrations from block ${fromBlock} to block ${toBlock}`) //Gets logs from the blockchain. If fails, will bubble up and this run will be skipped. const y2kMigrations = await blockchainConnectionProvider.scanMigrations( env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ); + ) const frctRMigrations = await blockchainConnectionProvider.scanMigrations( env.FRCT_R_MIGRATION_ADDRESS as Address, fromBlock, toBlock - ); + ) //This is atomic, if it fails, nothing was written and we can try again next time. - await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]); + await addMigrationsToDatabase([...y2kMigrations, ...frctRMigrations]) //get migrations that still have not been sent to hyperliquid //This includes the ones we added above... as well as those that were not migrated for some reason. //If this fails, we will skip this run and try again next time. - const unmigratedMigrations: TokenMigration[] = - await getUnmigratedFractalityTokenMigrations(); + const unmigratedMigrations: TokenMigration[] = await getUnmigratedFractalityTokenMigrations() //calcualate the amount of tokens to send to hyperliquid //If all the migrations are not able to be prepped, we will skip this run and try again next time. //However, I don't see this failing as it's just doing some math. - const hlMigrations = await prepForHLMigration( - hlManager, - unmigratedMigrations - ); - console.log("hlMigrations", hlMigrations); + const hlMigrations = await prepForHLMigration(hlManager, unmigratedMigrations) + console.log('hlMigrations', hlMigrations) //This fails gracefully, the ones we could not send are in the faulures array. - const { successes, failures } = await hlManager.sendHLMigrations( - hlMigrations - ); - console.log("successes", successes); - console.log("failures", failures); - - let finalizationMaxRetries = 3; - let migrationsToFinalize = successes; + const { successes, failures } = await hlManager.sendHLMigrations(hlMigrations) + console.log('successes', successes) + console.log('failures', failures) + + let finalizationMaxRetries = 3 + let migrationsToFinalize = successes for (const attemptNumber of Array(finalizationMaxRetries).keys()) { - const finalizationResults = await finalizeHlMigrations(migrationsToFinalize); + const finalizationResults = await finalizeHlMigrations(migrationsToFinalize) if (finalizationResults.failures.length === 0) { - break; + break } - migrationsToFinalize = finalizationResults.failures; + migrationsToFinalize = finalizationResults.failures if (attemptNumber === finalizationMaxRetries - 1) { throw new FatalFinalizationError( - "FATAL ERROR: Error finalizing HL migrations. The following migration need to manually be marked as sent to HL", + 'FATAL ERROR: Error finalizing HL migrations. The following migration need to manually be marked as sent to HL', finalizationResults.failures - ); + ) } } } - //Maybe I can add some in success and failure buckets. async function prepForHLMigration( hlManager: HyperliquidManager, unmigratedMigrations: TokenMigration[] ): Promise { try { - const hlMigrations: HLMigration[] = []; + const hlMigrations: HLMigration[] = [] for (const unmigratedMigration of unmigratedMigrations) { if (unmigratedMigration.amount && unmigratedMigration.migrationAddress) { - const arbitrumAmount = BigInt(unmigratedMigration.amount); - const hlAmount = - hlManager.decimalConversion!.convertToHlToken(arbitrumAmount); + const arbitrumAmount = BigInt(unmigratedMigration.amount) + const hlAmount = hlManager.decimalConversion!.convertToHlToken(arbitrumAmount) hlMigrations.push({ originalTransactionHash: unmigratedMigration.transactionHash, hlTokenAmount: hlAmount, - sendToAddress: unmigratedMigration.migrationAddress, - }); + sendToAddress: unmigratedMigration.migrationAddress + }) } else { console.error( `migration with hash ${unmigratedMigration.transactionHash} has no amount or no migration address` - ); + ) } } - return hlMigrations; + return hlMigrations } catch (error) { - throw new MigrationPrepError("Error preparing for HL migration: " + error); + throw new MigrationPrepError('Error preparing for HL migration: ' + error) } } - - async function addMigrationsToDatabase(migrations: MigrationRegisteredEvent[]) { - const result = await filterAndaddNewFractalityTokenMigrations(migrations); //TODO: make this batch + const result = await filterAndaddNewFractalityTokenMigrations(migrations) //TODO: make this batch console.log( `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` - ); - console.info( - `existing migrations that already exist in the database`, - result.existingTxs - ); + ) + console.info(`existing migrations that already exist in the database`, result.existingTxs) } diff --git a/package-lock.json b/package-lock.json index c4fb8dc..d009c43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", + "@slack/web-api": "^7.9.1", "@types/aws-lambda": "^8.10.147", "archiver": "^7.0.1", "aws-lambda": "^1.0.7", @@ -946,6 +947,53 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@slack/logger": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz", + "integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==", + "license": "MIT", + "dependencies": { + "@types/node": ">=18.0.0" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, + "node_modules/@slack/types": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@slack/types/-/types-2.14.0.tgz", + "integrity": "sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0", + "npm": ">= 6.12.0" + } + }, + "node_modules/@slack/web-api": { + "version": "7.9.1", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.9.1.tgz", + "integrity": "sha512-qMcb1oWw3Y/KlUIVJhkI8+NcQXq1lNymwf+ewk93ggZsGd6iuz9ObQsOEbvlqlx1J+wd8DmIm3DORGKs0fcKdg==", + "license": "MIT", + "dependencies": { + "@slack/logger": "^4.0.0", + "@slack/types": "^2.9.0", + "@types/node": ">=18.0.0", + "@types/retry": "0.12.0", + "axios": "^1.8.3", + "eventemitter3": "^5.0.1", + "form-data": "^4.0.0", + "is-electron": "2.2.2", + "is-stream": "^2", + "p-queue": "^6", + "p-retry": "^4", + "retry": "^0.13.1" + }, + "engines": { + "node": ">= 18", + "npm": ">= 8.6.0" + } + }, "node_modules/@smithy/abort-controller": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", @@ -1564,6 +1612,12 @@ "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -2094,9 +2148,10 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -3542,6 +3597,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4114,6 +4175,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -4141,6 +4211,53 @@ "node": ">=8" } }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -4519,6 +4636,15 @@ "node": ">=8" } }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 187b7e3..fa503e5 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", + "@slack/web-api": "^7.9.1", "@types/aws-lambda": "^8.10.147", "archiver": "^7.0.1", "aws-lambda": "^1.0.7", diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars index 48f758a..e54ef3b 100644 --- a/terraform/environments/main.tfvars +++ b/terraform/environments/main.tfvars @@ -12,3 +12,4 @@ y2k_token_migration_address = "" frct_r_migration_address = "" block_start_number = "" safety_cushion_number_of_blocks = "250" +slack_channel_id = "" diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index fb13140..b650337 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -14,4 +14,5 @@ y2k_token_migration_address = "0xb8b47E61188Cc197F36C48B2298cb05afE4332E1" frct_r_migration_address = "0x06a4F1CAa90d22a4b461fB970D4C22Ef63987a5c" block_start_number = "335048846" safety_cushion_number_of_blocks = "5" +slack_channel_id = "C08TGV7UG8Z" diff --git a/terraform/lambda.tf b/terraform/lambda.tf index c3d09d5..7260b08 100644 --- a/terraform/lambda.tf +++ b/terraform/lambda.tf @@ -60,6 +60,8 @@ resource "aws_lambda_function" "default" { REDIS_CONNECTION_STRING = "${aws_elasticache_replication_group.redis_cluster.primary_endpoint_address}:6379" REDIS_USE_TLS = "false" MNEMONIC_SECRET_ARN = data.aws_secretsmanager_secret.private_key.arn + SLACK_TOKEN = var.slack_token + SLACK_CHANNEL_ID = var.slack_channel_id } } diff --git a/terraform/var.tf b/terraform/var.tf index 9a4fa30..65ad617 100644 --- a/terraform/var.tf +++ b/terraform/var.tf @@ -76,3 +76,12 @@ variable "safety_cushion_number_of_blocks" { variable "testnet" { type = bool } + +variable "slack_channel_id" { + type = string +} + +variable "slack_token" { + type = string + sensitive = true +} From bae5f426199855c88e073164a2c8ea175beb53e8 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Tue, 20 May 2025 16:11:25 +1000 Subject: [PATCH 29/33] Use console.info --- database/index.ts | 331 ++++++++++++++++--------------------- libs/HyperliquidManager.ts | 85 +++++----- migrationService.ts | 20 +-- 3 files changed, 195 insertions(+), 241 deletions(-) diff --git a/database/index.ts b/database/index.ts index c8d70ea..f7ab494 100644 --- a/database/index.ts +++ b/database/index.ts @@ -1,80 +1,67 @@ -import { drizzle, PostgresJsDatabase } from "drizzle-orm/postgres-js"; -import postgres from "postgres"; -import { eq, asc, and, gt, sql, inArray, not } from "drizzle-orm"; +import { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import { eq, asc, and, gt, sql, inArray, not } from 'drizzle-orm' -import * as schema from "./schema"; -import { env } from "../env"; -import { - HLMigration, - MigrationRegisteredEvent, - MigrationStatus, -} from "../interfaces"; -import { DatabaseError } from "../errors"; +import * as schema from './schema' +import { env } from '../env' +import { HLMigration, MigrationRegisteredEvent, MigrationStatus } from '../interfaces' +import { DatabaseError } from '../errors' -let db: PostgresJsDatabase | null = null; +let db: PostgresJsDatabase | null = null -export async function initializeDatabaseConnection(): Promise< - PostgresJsDatabase -> { +export async function initializeDatabaseConnection(): Promise> { if (db) { - return db; + return db } - let connection: postgres.Sql<{}> | null = null; - if (env.NODE_ENV === "local") { + let connection: postgres.Sql<{}> | null = null + if (env.NODE_ENV === 'local') { //local defaults for a local db instance - console.log("connected to local database"); + console.log('connected to local database') connection = postgres({ - database: env.DB_NAME, - }); + database: env.DB_NAME + }) } else { - console.log("connected to ", env.DB_HOST); + console.log('connected to ', env.DB_HOST) connection = postgres({ host: env.DB_HOST, port: env.DB_PORT, user: env.DB_USER, password: env.DB_PASSWORD, database: env.DB_NAME, - ssl: "prefer", - }); + ssl: 'prefer' + }) } - db = drizzle(connection, { schema }); - console.log("database connection initialized"); - return db; + db = drizzle(connection, { schema }) + console.log('database connection initialized') + return db } - export async function filterAndaddNewFractalityTokenMigrations( migrations: MigrationRegisteredEvent[] ): Promise<{ - newMigrations: MigrationRegisteredEvent[]; - existingTxs: { txHash: string }[]; + newMigrations: MigrationRegisteredEvent[] + existingTxs: { txHash: string }[] }> { if (!db) { - throw new DatabaseError("Database not initialized"); + throw new DatabaseError('Database not initialized') } // Get all transaction hashes from the incoming migrations - const incomingTxHashes = migrations.map((m) => m.transactionHash); - let existingTxs: { txHash: string }[] = []; + const incomingTxHashes = migrations.map((m) => m.transactionHash) + let existingTxs: { txHash: string }[] = [] try { // Check which transactions already exist in the database existingTxs = await db .select({ txHash: schema.fractalityTokenMigrations.transactionHash }) .from(schema.fractalityTokenMigrations) - .where( - inArray( - schema.fractalityTokenMigrations.transactionHash, - incomingTxHashes - ) - ); + .where(inArray(schema.fractalityTokenMigrations.transactionHash, incomingTxHashes)) } catch (error) { - throw new DatabaseError("Error fetching existing migrations from database: " + error); + throw new DatabaseError('Error fetching existing migrations from database: ' + error) } // Filter out migrations that already exist const newMigrations = migrations.filter( - (migration) => - !existingTxs.some((tx) => tx.txHash === migration.transactionHash) - ); + (migration) => !existingTxs.some((tx) => tx.txHash === migration.transactionHash) + ) // If there are new migrations, insert them if (newMigrations.length > 0) { @@ -90,178 +77,148 @@ export async function filterAndaddNewFractalityTokenMigrations( foundAt: new Date().toISOString(), status: MigrationStatus.FOUND_ON_ARBITRUM, migratedAt: null, - migratedAmount: null, + migratedAmount: null })) - ); + ) } catch (error) { - throw new DatabaseError("Error inserting new migrations batch into database: " + error); + throw new DatabaseError('Error inserting new migrations batch into database: ' + error) } } - return { newMigrations: newMigrations, existingTxs: existingTxs }; + return { newMigrations: newMigrations, existingTxs: existingTxs } } - export async function dbCleanup(): Promise { - try { - console.log("Starting database cleanup..."); - - if (db) { - // Get the underlying postgres connection from drizzle - const client = (db as any).session?.config?.connection; - if (client) { - await client.end(); - console.log("Database connection closed"); - } - db = null; +export async function dbCleanup(): Promise { + try { + console.log('Starting database cleanup...') + + if (db) { + // Get the underlying postgres connection from drizzle + const client = (db as any).session?.config?.connection + if (client) { + await client.end() + console.log('Database connection closed') } - - console.log("Database cleanup completed"); - } catch (error) { - console.error("Error during database cleanup:", error); - throw error; + db = null } + + console.log('Database cleanup completed') + } catch (error) { + console.error('Error during database cleanup:', error) + throw error } +} - export async function getUnmigratedFractalityTokenMigrations(): Promise< - TokenMigration[] - > { - if (!db) { - throw new Error("Database not initialized"); - } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - not( - eq( - schema.fractalityTokenMigrations.status, - MigrationStatus.SENT_TO_HL - ) - ) - ); - return migrations; - } catch (error) { - throw new DatabaseError("Error fetching unmigrated fractality token migrations: " + error); - } +export async function getUnmigratedFractalityTokenMigrations(): Promise { + if (!db) { + throw new Error('Database not initialized') } + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where(not(eq(schema.fractalityTokenMigrations.status, MigrationStatus.SENT_TO_HL))) + return migrations + } catch (error) { + throw new DatabaseError('Error fetching unmigrated fractality token migrations: ' + error) + } +} - export async function getAllFractalityTokenMigrations(): Promise< - TokenMigration[] - > { - if (!db) { - throw new Error("Database not initialized"); - } +export async function getAllFractalityTokenMigrations(): Promise { + if (!db) { + throw new Error('Database not initialized') + } - try { - return await db.select().from(schema.fractalityTokenMigrations); - } catch (error) { - console.error("Error fetching fractality token migrations:", error); - throw error; - } + try { + return await db.select().from(schema.fractalityTokenMigrations) + } catch (error) { + console.error('Error fetching fractality token migrations:', error) + throw error } +} - export async function getFractalityTokenMigrationsByAddress( - migrationAddress: string - ): Promise { - if (!db) { - throw new Error("Database not initialized"); - } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - eq(schema.fractalityTokenMigrations.migrationAddress, migrationAddress) - ); +export async function getFractalityTokenMigrationsByAddress( + migrationAddress: string +): Promise { + if (!db) { + throw new Error('Database not initialized') + } + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where(eq(schema.fractalityTokenMigrations.migrationAddress, migrationAddress)) - return migrations; - } catch (error) { - console.error("Error fetching migrations by address:", error); - throw error; - } + return migrations + } catch (error) { + console.error('Error fetching migrations by address:', error) + throw error } +} - export async function getFractalityTokenMigrationsByMigrationContractAddress( - migrationContractAddress: string - ): Promise { +export async function getFractalityTokenMigrationsByMigrationContractAddress( + migrationContractAddress: string +): Promise { + if (!db) { + throw new Error('Database not initialized') + } + try { + const migrations = await db + .select() + .from(schema.fractalityTokenMigrations) + .where( + eq(schema.fractalityTokenMigrations.migrationContractAddress, migrationContractAddress) + ) + + return migrations + } catch (error) { + console.error('Error fetching migrations by migration contract address:', error) + throw error + } +} + +export async function setHLMigrationStatus(migrationHash: string, status: MigrationStatus) { + try { if (!db) { - throw new Error("Database not initialized"); + throw new Error('Database not initialized') } - try { - const migrations = await db - .select() - .from(schema.fractalityTokenMigrations) - .where( - eq( - schema.fractalityTokenMigrations.migrationContractAddress, - migrationContractAddress - ) - ); + await db + .update(schema.fractalityTokenMigrations) + .set({ status: status }) + .where(eq(schema.fractalityTokenMigrations.transactionHash, migrationHash)) + } catch (error) { + console.error(`ERROR setting HL migration status. Error: ${error}`) + throw error + } +} - return migrations; - } catch (error) { - console.error( - "Error fetching migrations by migration contract address:", - error - ); - throw error; - } +export async function finalizeHlMigrations(migrations: HLMigration[]) { + const successes = [] + const failures = [] + if (!db) { + throw new Error('Database not initialized') } - export async function setHLMigrationStatus( - migrationHash: string, - status: MigrationStatus - ) { + for (const migration of migrations) { try { - if (!db) { - throw new Error("Database not initialized"); - } + console.info(`Setting HL migration for ${migration.originalTransactionHash}`) await db .update(schema.fractalityTokenMigrations) - .set({ status: status }) + .set({ + status: MigrationStatus.SENT_TO_HL, + migratedAt: new Date().toISOString(), + migratedAmount: migration.hlTokenAmount + }) .where( - eq(schema.fractalityTokenMigrations.transactionHash, migrationHash) - ); + eq(schema.fractalityTokenMigrations.transactionHash, migration.originalTransactionHash) + ) + successes.push(migration) } catch (error) { - console.error(`ERROR setting HL migration status. Error: ${error}`); - throw error; + failures.push(migration) + //THhis needs to shut down the service, as the next run could cause a double send. + console.error(`ERROR updating HL ${migration} migrations. Error: ${error}`) } } + return { successes, failures } +} - export async function finalizeHlMigrations(migrations: HLMigration[]) { - const successes = []; - const failures = []; - if (!db) { - throw new Error("Database not initialized"); - } - - for (const migration of migrations) { - try { - console.log( - `Setting HL migration for ${migration.originalTransactionHash}` - ); - await db - .update(schema.fractalityTokenMigrations) - .set({ - status: MigrationStatus.SENT_TO_HL, - migratedAt: new Date().toISOString(), - migratedAmount: migration.hlTokenAmount, - }) - .where( - eq( - schema.fractalityTokenMigrations.transactionHash, - migration.originalTransactionHash - ) - ); - successes.push(migration); - } catch (error) { - failures.push(migration); - //THhis needs to shut down the service, as the next run could cause a double send. - console.error(`ERROR updating HL ${migration} migrations. Error: ${error}`); - } - } - return { successes, failures }; - } - - export type TokenMigration = - typeof schema.fractalityTokenMigrations.$inferSelect; +export type TokenMigration = typeof schema.fractalityTokenMigrations.$inferSelect diff --git a/libs/HyperliquidManager.ts b/libs/HyperliquidManager.ts index 4db65fc..9ae5436 100644 --- a/libs/HyperliquidManager.ts +++ b/libs/HyperliquidManager.ts @@ -1,59 +1,56 @@ -const { Hyperliquid } = require("hyperliquid"); -import { setHLMigrationStatus } from "../database"; -import { env } from "../env"; -import { HyperliquidError } from "../errors"; -import { HLMigration, MigrationStatus } from "../interfaces"; -import { DecimalConversion } from "./DecimalConversion"; +const { Hyperliquid } = require('hyperliquid') +import { setHLMigrationStatus } from '../database' +import { env } from '../env' +import { HyperliquidError } from '../errors' +import { HLMigration, MigrationStatus } from '../interfaces' +import { DecimalConversion } from './DecimalConversion' export class HyperliquidManager { - private hlSdk: any; - public hlTokenAddress: string; - public tokenInfo: any; - public decimalConversion: DecimalConversion | null = null; + private hlSdk: any + public hlTokenAddress: string + public tokenInfo: any + public decimalConversion: DecimalConversion | null = null constructor(enableWs: boolean, testnet: boolean, privateKey: string) { this.hlSdk = new Hyperliquid({ enableWs, // boolean (OPTIONAL) - Enable/disable WebSocket functionality, defaults to true privateKey: privateKey, testnet, - walletAddress: env.PUBLIC_ADDRESS, - }); - this.hlTokenAddress = env.TOKEN_ADDRESS; + walletAddress: env.PUBLIC_ADDRESS + }) + this.hlTokenAddress = env.TOKEN_ADDRESS } async init(arbitrumTokenDecimals: bigint) { - await this.hlSdk.connect(); - this.tokenInfo = await this.getTokenInfo(env.TOKEN_ADDRESS); - console.log( + await this.hlSdk.connect() + this.tokenInfo = await this.getTokenInfo(env.TOKEN_ADDRESS) + console.info( `Using HL token with name ${this.tokenInfo.name} wei decimals ${this.tokenInfo.weiDecimals}` - ); + ) this.decimalConversion = new DecimalConversion( this.tokenInfo.weiDecimals, arbitrumTokenDecimals - ); + ) } async getUserTokenBalances(userAddress: string) { try { - const balances = await this.hlSdk.info.spot.getSpotClearinghouseState( - userAddress, - false - ); - return balances; + const balances = await this.hlSdk.info.spot.getSpotClearinghouseState(userAddress, false) + return balances } catch (error) { - throw new HyperliquidError("Error getting user token balances: " + error); + throw new HyperliquidError('Error getting user token balances: ' + error) } } getTokenDecimals() { - return this.tokenInfo.weiDecimals; + return this.tokenInfo.weiDecimals } async getTokenInfo(tokenAddress: string) { try { - return this.hlSdk.info.spot.getTokenDetails(tokenAddress); + return this.hlSdk.info.spot.getTokenDetails(tokenAddress) } catch (error) { - throw new HyperliquidError("Error getting token info: " + error); + throw new HyperliquidError('Error getting token info: ' + error) } } @@ -62,43 +59,43 @@ export class HyperliquidManager { async sendToken(amount: string, destination: string) { const result = await this.hlSdk.exchange.spotTransfer( destination, - this.tokenInfo.name + ":" + this.hlTokenAddress, + this.tokenInfo.name + ':' + this.hlTokenAddress, amount - ); - if (result.status === "ok") { - console.log("Transfer successful"); + ) + if (result.status === 'ok') { + console.info('Transfer successful') } else { - console.log("Transfer failed", result.response); - throw new HyperliquidError("Transfer failed: " + result.response); + console.info('Transfer failed', result.response) + throw new HyperliquidError('Transfer failed: ' + result.response) } } //4000 migrations will take aroud 1000$ USDC! async sendHLMigrations(hlMigrations: HLMigration[]) { - let successes: HLMigration[] = []; - let failures: HLMigration[] = []; + let successes: HLMigration[] = [] + let failures: HLMigration[] = [] for (const migration of hlMigrations) { if (Number(migration.hlTokenAmount) == 0) { console.info( `migration with hash ${migration.originalTransactionHash} has a HL amount of 0 due to truncation, skipping sending.` - ); - successes.push(migration); - continue; + ) + successes.push(migration) + continue } try { - await this.sendToken(migration.hlTokenAmount, migration.sendToAddress); - successes.push(migration); + await this.sendToken(migration.hlTokenAmount, migration.sendToAddress) + successes.push(migration) } catch (error) { console.error( `Error sending token to hyperliquid for migration ${migration.originalTransactionHash}`, error - ); + ) await setHLMigrationStatus( migration.originalTransactionHash, MigrationStatus.ERRORED_IN_SENDING_TO_HL - ); - failures.push(migration); + ) + failures.push(migration) } } - return { successes, failures }; + return { successes, failures } } } diff --git a/migrationService.ts b/migrationService.ts index 8489e20..2b944de 100644 --- a/migrationService.ts +++ b/migrationService.ts @@ -21,7 +21,7 @@ import { Slack } from './libs/Slack' export async function main(runWithCron: boolean) { if (env.SLACK_TOKEN) { - Slack.initialize(process.env.SLACK_TOKEN!, process.env.SLACK_CHANNEL_ID!) + Slack.initialize(env.SLACK_TOKEN!, env.SLACK_CHANNEL_ID!) } await privateKeyManager.init() @@ -47,7 +47,7 @@ export async function main(runWithCron: boolean) { ) if (runWithCron) { - console.log('starting cron job for migrations, running every 5 minutes') + console.info('starting cron job for migrations, running every 5 minutes') const scheduledTask = cron.schedule('* * * * *', async () => { try { await coreMigrationService(blockManager, blockchainConnectionProvider, hlManager) @@ -55,7 +55,7 @@ export async function main(runWithCron: boolean) { if (error instanceof FatalFinalizationError) { scheduledTask.stop() } else { - console.log('Error in core migration service, this run will be skipped', error) + console.info('Error in core migration service, this run will be skipped', error) } } }) @@ -64,14 +64,14 @@ export async function main(runWithCron: boolean) { if (await redisOperations.shouldRunAccordingToStopRunningFlag()) { await coreMigrationService(blockManager, blockchainConnectionProvider, hlManager) } else { - console.log('stopRunning flag is set, not running core migration service') + console.info('stopRunning flag is set, not running core migration service') return } } catch (error) { if (error instanceof FatalFinalizationError) { await redisOperations.setStopRunningFlag() } else { - console.log('Error in core migration service, this run will be skipped', error) + console.info('Error in core migration service, this run will be skipped', error) } throw error } @@ -88,7 +88,7 @@ export async function coreMigrationService( //This gets the current block and sets it in redis. If fails, will bubble up and this run will be skipped. const toBlock = await blockManager.setFromBlockForScanToCurrentBlock() - console.log(`looking for migrations from block ${fromBlock} to block ${toBlock}`) + console.info(`looking for migrations from block ${fromBlock} to block ${toBlock}`) //Gets logs from the blockchain. If fails, will bubble up and this run will be skipped. const y2kMigrations = await blockchainConnectionProvider.scanMigrations( @@ -113,12 +113,12 @@ export async function coreMigrationService( //If all the migrations are not able to be prepped, we will skip this run and try again next time. //However, I don't see this failing as it's just doing some math. const hlMigrations = await prepForHLMigration(hlManager, unmigratedMigrations) - console.log('hlMigrations', hlMigrations) + console.info('hlMigrations', hlMigrations) //This fails gracefully, the ones we could not send are in the faulures array. const { successes, failures } = await hlManager.sendHLMigrations(hlMigrations) - console.log('successes', successes) - console.log('failures', failures) + console.info('successes', successes) + console.info('failures', failures) let finalizationMaxRetries = 3 let migrationsToFinalize = successes @@ -167,7 +167,7 @@ async function prepForHLMigration( async function addMigrationsToDatabase(migrations: MigrationRegisteredEvent[]) { const result = await filterAndaddNewFractalityTokenMigrations(migrations) //TODO: make this batch - console.log( + console.info( `Inserted ${result.newMigrations.length} new migrations and found ${result.existingTxs.length} existing migrations` ) console.info(`existing migrations that already exist in the database`, result.existingTxs) From 0bc52ebb681889ed12cd5c3f8ee8c04114f03659 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Tue, 27 May 2025 16:50:03 +1000 Subject: [PATCH 30/33] Update test params --- terraform/environments/test.tfvars | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/terraform/environments/test.tfvars b/terraform/environments/test.tfvars index b650337..0f18cc2 100644 --- a/terraform/environments/test.tfvars +++ b/terraform/environments/test.tfvars @@ -10,9 +10,9 @@ testnet = false public_address = "0x5B5fe168C17A74Cd32B2A2b5dfB30aDA3edF94d6" token_address = "0xbdeaa95bd62d96a76f2511fcd8ac810f" -y2k_token_migration_address = "0xb8b47E61188Cc197F36C48B2298cb05afE4332E1" -frct_r_migration_address = "0x06a4F1CAa90d22a4b461fB970D4C22Ef63987a5c" -block_start_number = "335048846" +y2k_token_migration_address = "0xEf2b53D2BF605a46bF7F4695Ac7Ba7bC47055271" +frct_r_migration_address = "0xE90D7ddb803Fc02F9122D67cDe3069745bf64f65" +block_start_number = "340965674" safety_cushion_number_of_blocks = "5" slack_channel_id = "C08TGV7UG8Z" From e3a68348b16feceedc9733d7d2ee0a799f56f38e Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Tue, 27 May 2025 02:15:54 -0600 Subject: [PATCH 31/33] new error types --- errors.ts | 14 ++++++++++++++ libs/HyperliquidManager.ts | 13 +++++++++++-- package-lock.json | 9 ++++----- package.json | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/errors.ts b/errors.ts index 33984b3..e017f21 100644 --- a/errors.ts +++ b/errors.ts @@ -38,6 +38,20 @@ export class DecimalConversionError extends Error { } } + export class HyperLiquidInsufficientHlTokenError extends Error { + constructor(message: string) { + super(message); + this.name = "HyperLiquidInsufficientHlTokenError"; + } + } + + export class HyperLiquidInsufficientGasTokenError extends Error { + constructor(message: string) { + super(message); + this.name = "HyperLiquidInsufficientGasTokenError"; + } + } + export class BlockchainConnectionError extends Error { constructor(message: string) { super(message); diff --git a/libs/HyperliquidManager.ts b/libs/HyperliquidManager.ts index 9ae5436..eb62e16 100644 --- a/libs/HyperliquidManager.ts +++ b/libs/HyperliquidManager.ts @@ -1,7 +1,7 @@ const { Hyperliquid } = require('hyperliquid') import { setHLMigrationStatus } from '../database' import { env } from '../env' -import { HyperliquidError } from '../errors' +import { HyperliquidError, HyperLiquidInsufficientGasTokenError, HyperLiquidInsufficientHlTokenError } from '../errors' import { HLMigration, MigrationStatus } from '../interfaces' import { DecimalConversion } from './DecimalConversion' @@ -66,7 +66,16 @@ export class HyperliquidManager { console.info('Transfer successful') } else { console.info('Transfer failed', result.response) - throw new HyperliquidError('Transfer failed: ' + result.response) + + if(result.response.toLowerCase().includes('insufficient balance for token transfer')) { + throw new HyperLiquidInsufficientHlTokenError('NEED TO TOP UP HL TOKEN BALANCE - ' + result.response) + } + + if(result.response.toLowerCase().includes('Insufficient USDC balance for token transfer gas')) { + throw new HyperLiquidInsufficientGasTokenError('NEED TO TOP UP USDC GAS TOKEN BALANCE - ' + result.response) + } + + throw new HyperliquidError('Transfer failed for a reason not related to hl or gas balance - ' + result.response) } } //4000 migrations will take aroud 1000$ USDC! diff --git a/package-lock.json b/package-lock.json index d009c43..b455aa2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", - "@slack/web-api": "^7.9.1", + "@slack/web-api": "^7.9.2", "@types/aws-lambda": "^8.10.147", "archiver": "^7.0.1", "aws-lambda": "^1.0.7", @@ -971,10 +971,9 @@ } }, "node_modules/@slack/web-api": { - "version": "7.9.1", - "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.9.1.tgz", - "integrity": "sha512-qMcb1oWw3Y/KlUIVJhkI8+NcQXq1lNymwf+ewk93ggZsGd6iuz9ObQsOEbvlqlx1J+wd8DmIm3DORGKs0fcKdg==", - "license": "MIT", + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.9.2.tgz", + "integrity": "sha512-3HoDwV6+ZSTfV+DsbnUd82GlZY0a+DPXuHQHpxWTqgxjM3JWZyGiwR+ov3d2M16pWiMzA+l58UJ5lm1znGq0yA==", "dependencies": { "@slack/logger": "^4.0.0", "@slack/types": "^2.9.0", diff --git a/package.json b/package.json index fa503e5..c76c459 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "@aws-sdk/client-secrets-manager": "^3.692.0", "@aws-sdk/credential-providers": "^3.692.0", - "@slack/web-api": "^7.9.1", + "@slack/web-api": "^7.9.2", "@types/aws-lambda": "^8.10.147", "archiver": "^7.0.1", "aws-lambda": "^1.0.7", From b7e50a24573c0761da67b16f9e9eb1aee140cabf Mon Sep 17 00:00:00 2001 From: Jose Herrera Date: Wed, 28 May 2025 20:22:43 -0600 Subject: [PATCH 32/33] more error checking but for the initialization --- database/index.ts | 12 ++-- errors.ts | 42 +++++++++++++- libs/BlockchainConnectionProvider.ts | 16 ++++-- libs/HyperliquidManager.ts | 20 ++++--- libs/PrivateKeyManager.ts | 15 +++-- migrationService.ts | 21 +++++-- package-lock.json | 85 +++++++++++++++------------- package.json | 4 +- redisOperations/redisOperations.ts | 8 ++- 9 files changed, 151 insertions(+), 72 deletions(-) diff --git a/database/index.ts b/database/index.ts index f7ab494..0a01e8a 100644 --- a/database/index.ts +++ b/database/index.ts @@ -5,14 +5,15 @@ import { eq, asc, and, gt, sql, inArray, not } from 'drizzle-orm' import * as schema from './schema' import { env } from '../env' import { HLMigration, MigrationRegisteredEvent, MigrationStatus } from '../interfaces' -import { DatabaseError } from '../errors' +import { DatabaseError, DatabaseInitError } from '../errors' let db: PostgresJsDatabase | null = null export async function initializeDatabaseConnection(): Promise> { - if (db) { - return db - } + try { + if (db) { + return db + } let connection: postgres.Sql<{}> | null = null if (env.NODE_ENV === 'local') { //local defaults for a local db instance @@ -34,6 +35,9 @@ export async function initializeDatabaseConnection(): Promise constructor(opts: BlockchainConnectionProviderOptions) { - this.y2kTokenMigrationAddress = opts.y2kTokenMigrationAddress - this.frctRTokenMigrationAddress = opts.frctRTokenMigrationAddress - this._viemClient = this._init(opts) - console.log('Connected to blockchain', this._viemClient.chain!.name) + try { + this.y2kTokenMigrationAddress = opts.y2kTokenMigrationAddress + this.frctRTokenMigrationAddress = opts.frctRTokenMigrationAddress + this._viemClient = this._init(opts) + console.log('Connected to blockchain', this._viemClient.chain!.name) + } catch (error) { + throw new BlockchainConnectionInitError('Error initializing blockchain connection provider: ' + error) + } } private _init = (opts: BlockchainConnectionProviderOptions) => { @@ -74,7 +78,7 @@ export class BlockchainConnectionProvider { try { return this._viemClient.getBlockNumber() } catch (error) { - throw new BlockchainConnectionError('Error getting current block number: ' + error) + throw new BlockchainGetCurrentBlockError('Error getting current block number: ' + error) } } diff --git a/libs/HyperliquidManager.ts b/libs/HyperliquidManager.ts index eb62e16..6bb6a6c 100644 --- a/libs/HyperliquidManager.ts +++ b/libs/HyperliquidManager.ts @@ -1,7 +1,7 @@ const { Hyperliquid } = require('hyperliquid') import { setHLMigrationStatus } from '../database' import { env } from '../env' -import { HyperliquidError, HyperLiquidInsufficientGasTokenError, HyperLiquidInsufficientHlTokenError } from '../errors' +import { HyperliquidError, HyperliquidInitError, HyperLiquidInsufficientGasTokenError, HyperLiquidInsufficientHlTokenError } from '../errors' import { HLMigration, MigrationStatus } from '../interfaces' import { DecimalConversion } from './DecimalConversion' @@ -22,15 +22,19 @@ export class HyperliquidManager { } async init(arbitrumTokenDecimals: bigint) { - await this.hlSdk.connect() - this.tokenInfo = await this.getTokenInfo(env.TOKEN_ADDRESS) - console.info( + try { + await this.hlSdk.connect() + this.tokenInfo = await this.getTokenInfo(env.TOKEN_ADDRESS) + console.info( `Using HL token with name ${this.tokenInfo.name} wei decimals ${this.tokenInfo.weiDecimals}` ) - this.decimalConversion = new DecimalConversion( - this.tokenInfo.weiDecimals, - arbitrumTokenDecimals - ) + this.decimalConversion = new DecimalConversion( + this.tokenInfo.weiDecimals, + arbitrumTokenDecimals + ) + } catch (error) { + throw new HyperliquidInitError('Error initializing hyperliquid manager: ' + error) + } } async getUserTokenBalances(userAddress: string) { diff --git a/libs/PrivateKeyManager.ts b/libs/PrivateKeyManager.ts index 58dc33d..5742e61 100644 --- a/libs/PrivateKeyManager.ts +++ b/libs/PrivateKeyManager.ts @@ -1,16 +1,21 @@ import getSecret from "./SecretManager"; import { env } from "../env"; +import { PrivateKeyManagerInitError } from "../errors"; export class PrivateKeyManager { private privateKey: string | null = null; async init() { - const secret = await getSecret(); - if (secret) { - this.privateKey = JSON.parse(secret).HL_PRIVATE_KEY; + try { + const secret = await getSecret(); + if (secret) { + this.privateKey = JSON.parse(secret).HL_PRIVATE_KEY; console.log("private key set from secret manager"); } else if (env.PRIVATE_KEY) { - this.privateKey = env.PRIVATE_KEY; - console.log("private key set from env var"); + this.privateKey = env.PRIVATE_KEY; + console.log("private key set from env var"); + } + } catch (error) { + throw new PrivateKeyManagerInitError("Error initializing private key manager: " + error); } } getPrivateKey() { diff --git a/migrationService.ts b/migrationService.ts index 2b944de..8e893a6 100644 --- a/migrationService.ts +++ b/migrationService.ts @@ -24,15 +24,21 @@ export async function main(runWithCron: boolean) { Slack.initialize(env.SLACK_TOKEN!, env.SLACK_CHANNEL_ID!) } + let blockManager: PreviousBlockManager | null = null + let blockchainConnectionProvider: BlockchainConnectionProvider | null = null + let hlManager: HyperliquidManager | null = null + let redisOperations: RedisOperations | null = null + try{ + await privateKeyManager.init() await initializeDatabaseConnection() - const redisOperations = new RedisOperations() + redisOperations = new RedisOperations() await redisOperations.initialize() - const hlManager = new HyperliquidManager(true, true, privateKeyManager.getPrivateKey()) + hlManager = new HyperliquidManager(true, true, privateKeyManager.getPrivateKey()) - const blockchainConnectionProvider = new BlockchainConnectionProvider({ + blockchainConnectionProvider = new BlockchainConnectionProvider({ providerUrl: env.PROVIDER_URL, y2kTokenMigrationAddress: env.Y2K_TOKEN_MIGRATION_ADDRESS as Address, frctRTokenMigrationAddress: env.FRCT_R_MIGRATION_ADDRESS as Address @@ -40,12 +46,17 @@ export async function main(runWithCron: boolean) { await hlManager.init(await blockchainConnectionProvider.getArbitrumTokenDecimals()) - const blockManager = new PreviousBlockManager( + blockManager = new PreviousBlockManager( redisOperations, BigInt(env.SAFETY_CUSHION_NUMBER_OF_BLOCKS), - () => blockchainConnectionProvider.getCurrentBlockNumber() + () => blockchainConnectionProvider!.getCurrentBlockNumber() ) + } catch (error) { + console.error("Error initializing migration service due to the following error, skipping this run", error); + throw error; + } + if (runWithCron) { console.info('starting cron job for migrations, running every 5 minutes') const scheduledTask = cron.schedule('* * * * *', async () => { diff --git a/package-lock.json b/package-lock.json index b455aa2..6150c29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "dotenv": "^16.4.5", "drizzle-orm": "^0.36.1", "ethers": "^6.13.4", - "hyperliquid": "^1.5.6", + "hyperliquid": "^1.7.5", "ioredis": "^5.4.2", "node-cron": "^3.0.3", "postgres": "^3.4.5", @@ -32,11 +32,13 @@ "viem": "^2.21.44", "webpack": "^5.99.8", "webpack-cli": "^6.0.1", + "ws": "^8.18.2", "zod": "^3.23.8" }, "devDependencies": { "@typechain/ethers-v6": "^0.5.1", "@types/node-cron": "^3.0.11", + "husky": "^9.1.7", "nodemon": "^3.1.7", "typechain": "^8.3.2" } @@ -1622,14 +1624,6 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==" }, - "node_modules/@types/ws": { - "version": "8.5.13", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", - "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -3061,6 +3055,26 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "node_modules/ethers/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -3429,42 +3443,35 @@ "node": ">= 0.4" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/hyperliquid": { - "version": "1.5.6", - "resolved": "https://registry.npmjs.org/hyperliquid/-/hyperliquid-1.5.6.tgz", - "integrity": "sha512-0amApm9Y2TOxg7bgqyPT8BMPXRtcII2cDEk18i1jzlsV+PTg+AwLSENWT6UUfl6UYgfbHvgvYLn/NvLy2dROUg==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/hyperliquid/-/hyperliquid-1.7.5.tgz", + "integrity": "sha512-aBKz1J075cA7Annn4qd1m2Hg/NELFB9fvE6uwbfNL3+3+j3dK2ntuBXiIQigXl3fR3F3yWIdZFl3TxsuUVz9kQ==", + "hasInstallScript": true, "dependencies": { "@msgpack/msgpack": "^3.0.0-beta2", - "@types/ws": "^8.5.11", "axios": "^1.7.2", - "ethers": "^6.13.2", - "typescript": "^5.5.4", - "ws": "^8.18.0" + "ethers": "^6.13.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/hyperliquid/node_modules/ws": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", - "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/ieee754": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", @@ -5765,9 +5772,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index c76c459..73a5106 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "dotenv": "^16.4.5", "drizzle-orm": "^0.36.1", "ethers": "^6.13.4", - "hyperliquid": "^1.5.6", + "hyperliquid": "^1.7.5", "ioredis": "^5.4.2", "node-cron": "^3.0.3", "postgres": "^3.4.5", @@ -35,11 +35,13 @@ "viem": "^2.21.44", "webpack": "^5.99.8", "webpack-cli": "^6.0.1", + "ws": "^8.18.2", "zod": "^3.23.8" }, "devDependencies": { "@typechain/ethers-v6": "^0.5.1", "@types/node-cron": "^3.0.11", + "husky": "^9.1.7", "nodemon": "^3.1.7", "typechain": "^8.3.2" } diff --git a/redisOperations/redisOperations.ts b/redisOperations/redisOperations.ts index 061ffb2..6a06d0d 100644 --- a/redisOperations/redisOperations.ts +++ b/redisOperations/redisOperations.ts @@ -1,13 +1,17 @@ import { env } from "../env"; import IORedis from "ioredis"; -import { RedisError } from "../errors"; +import { RedisError, RedisInitError } from "../errors"; export class RedisOperations { private redisConnection: IORedis | null = null; async initialize() { - this.redisConnection = await this.initRedisConnection(); + try { + this.redisConnection = await this.initRedisConnection(); + } catch (error) { + throw new RedisInitError("Error initializing redis connection: " + error); + } } From a8c9ffe4b6432c281a76aa1c1f18eea8cc791030 Mon Sep 17 00:00:00 2001 From: dylmanning Date: Thu, 29 May 2025 18:04:13 +1000 Subject: [PATCH 33/33] Add production variables --- terraform/environments/main.tfvars | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/terraform/environments/main.tfvars b/terraform/environments/main.tfvars index e54ef3b..f151ffc 100644 --- a/terraform/environments/main.tfvars +++ b/terraform/environments/main.tfvars @@ -1,15 +1,15 @@ name = "migration-service" project = "fractality" environment = "main" -vpc_id = "" -subnet_ids = [] +vpc_id = "vpc-08ae44a5cd755d8b0" +subnet_ids = ["subnet-05fe54f7cba0f2fd5", "subnet-07452d48590bce532"] db_name = "fractality" db_instance_id = "fund-data-pipeline-db" -public_address = "" -token_address = "" -y2k_token_migration_address = "" -frct_r_migration_address = "" -block_start_number = "" -safety_cushion_number_of_blocks = "250" -slack_channel_id = "" +public_address = "0xccc85d270a63c6cf3673a04163ace4c80cc2eaa5" +token_address = "0xfebf76468dd13f240281e4a8ef11932d" +y2k_token_migration_address = "0xb3625a0013D3CAD0013eaF6F7Cf4cBC5BA8b9df4" +frct_r_migration_address = "0x556A24BACd8f219c28e1658580BdF552c186C893" +block_start_number = "340592531" +safety_cushion_number_of_blocks = "30" +slack_channel_id = "C08UDS1EE5A"