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 9ae5436..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 } 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) { @@ -66,7 +70,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/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 d009c43..6150c29 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", @@ -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" } @@ -971,10 +973,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", @@ -1623,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", @@ -3062,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", @@ -3430,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", @@ -5766,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 fa503e5..73a5106 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", @@ -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); + } }