diff --git a/package.json b/package.json index a293f4c..6186cb2 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "build": "lerna run build --concurrency 1", "build-tsc": "tsc -p", "deploy": "yarn lerna publish", + "build:watch": "lerna run build --stream", "prepare": "husky install", "build:docker": "lerna run build --concurrency 1" }, diff --git a/packages/cw-to-ton/src/PacketProcessor.ts b/packages/cw-to-ton/src/PacketProcessor.ts index dedf75b..12d2dac 100644 --- a/packages/cw-to-ton/src/PacketProcessor.ts +++ b/packages/cw-to-ton/src/PacketProcessor.ts @@ -8,6 +8,9 @@ import { sleep } from "./utils"; import { ACK } from "./dtos/packets/AckPacket"; import { TonHandler } from "./services"; import { TransferPacket } from "./dtos/packets/TransferPacket"; +import { Logger } from "winston"; +import { getExistenceProofSnakeCell } from "@oraichain/ton-bridge-contracts"; +import { ExistenceProof } from "cosmjs-types/cosmos/ics23/v1/proofs"; //@ts-ignore BigInt.prototype.toJSON = function () { @@ -19,6 +22,7 @@ export class PacketProcessorArgs { cosmosProofHandler: CosmosProofHandler; tonHandler: TonHandler; pollingInterval: number; + logger: Logger; } export class PacketProcessor { @@ -26,6 +30,7 @@ export class PacketProcessor { cosmosProofHandler: CosmosProofHandler; tonHandler: TonHandler; pollingInterval: number; + logger: Logger; // Memory packets lock: boolean = false; pendingRelayPackets: ( @@ -41,6 +46,7 @@ export class PacketProcessor { this.cosmosProofHandler = args.cosmosProofHandler; this.tonHandler = args.tonHandler; this.pollingInterval = args.pollingInterval || 5000; + this.logger = args.logger; } addPendingTransferPackets(transferPackets: TransferPacketWithBasicInfo[]) { @@ -48,6 +54,9 @@ export class PacketProcessor { return; } this.pendingRelayPackets.push(...transferPackets); + this.logger.info( + `PacketProcessor:Added ${transferPackets.length} TransferPackets` + ); } addPendingAckPackets(ackPackets: AckPacketWithBasicInfo[]) { @@ -60,25 +69,26 @@ export class PacketProcessor { this.pendingRelayPackets.push(packet); } }); + this.logger.info(`PacketProcessor:Added ${ackPackets.length} AckPackets`); } async run() { - logger.info("PacketProcessor:Start running"); + this.logger.info("PacketProcessor:Start running"); while (true) { try { this.lock = true; - logger.debug( + this.logger.debug( `PacketProcessor:Before pop all pending packets, ${JSON.stringify(this.getPendingRelayPackets())}` ); - logger.debug( + this.logger.debug( `PacketProcessor:Before pop all pending packets, ${JSON.stringify(this.getPendingAckSuccessPackets())}` ); const pendingPackets = this._popAllPendingRelayPackets(); const pendingAckSuccessPacket = this._popAllPendingAckSuccessPackets(); - logger.debug( + this.logger.debug( `PacketProcessor:After pop all pending packets, ${JSON.stringify(this.getPendingRelayPackets())}` ); - logger.debug( + this.logger.debug( `PacketProcessor:After pop all pending packets, ${JSON.stringify(this.getPendingAckSuccessPackets())}` ); @@ -89,10 +99,11 @@ export class PacketProcessor { ]; if (pendingPackets.length === 0) { this.addPendingAckPackets(pendingAckSuccessPacket); + this.logger.info("PacketProcessor:No pending packets"); await sleep(this.pollingInterval); continue; } - logger.info( + this.logger.info( `PacketProcessor:Processing ${this.processingPackets.length} packets` ); let heightForQueryProof = this.getHeightLatestPackets([ @@ -106,17 +117,23 @@ export class PacketProcessor { latestLightClientHeight, neededUpdateHeight ); - logger.debug( + this.logger.debug( `PacketProcessor:heightForQueryProof ${heightForQueryProof}` ); - logger.debug( + this.logger.debug( `PacketProcessor:neededUpdateHeight ${neededUpdateHeight}` ); - logger.debug( + this.logger.debug( `PacketProcessor:latestLightClientHeight ${latestLightClientHeight}` ); - if (finalUpdateHeight === neededUpdateHeight) { + if ( + finalUpdateHeight === neededUpdateHeight && + finalUpdateHeight !== latestLightClientHeight + ) { + this.logger.info( + `PacketProcessor:Update light client to ${finalUpdateHeight}` + ); const clientData = await this.cosmosProofHandler.createUpdateClientData( finalUpdateHeight @@ -124,9 +141,18 @@ export class PacketProcessor { await this.tonHandler.updateLightClient(clientData); } else if (finalUpdateHeight == latestLightClientHeight) { heightForQueryProof = latestLightClientHeight - 1; + this.logger.info( + `PacketProcessor:Light client height is larger than neededUpdateHeight. Update heightForQueryProof ${heightForQueryProof}` + ); } // FIXME: This may reach rate limit if the number of packets is too large - const packetProof = await Promise.all( + this.logger.info( + `PacketProcessor:Get proofs at ${heightForQueryProof}` + ); + this.logger.debug( + "PacketProcessor:packet.data" + JSON.stringify(this.processingPackets) + ); + const serializedProofs = await Promise.allSettled( this.processingPackets.map((packet) => { if (packet.data instanceof TransferPacket) { return this.cosmosProofHandler.getPacketProofs( @@ -141,30 +167,45 @@ export class PacketProcessor { }) ); - if (packetProof.length !== this.processingPackets.length) { + const packetProofs = serializedProofs.map((proofs) => { + if (proofs.status === "rejected") { + return []; + } + return proofs.value.map((proof) => { + if (proof) return ExistenceProof.fromJSON(proof); + }); + }); + + if (packetProofs.length !== this.processingPackets.length) { throw new Error( - "`PacketProcessor:Packet proof length not match with processing packets length" + "PacketProcessor:Packet proof length not match with processing packets length" ); } // Get proof from minProvenHeight - while (this.processingPackets.length > 1) { + while (this.processingPackets.length > 0) { const packet = this.processingPackets.shift(); - logger.debug(`PacketProcessor:packet.data ${packet.data}`); - const proof = packetProof.shift(); + this.logger.debug(`PacketProcessor:packet.data ${packet.data}`); + const proof = packetProofs.shift(); const data = packet.data; // TODO: should change to highload_wallet contract + if (proof.length === 0) { + this.logger.error( + `PacketProcessor:NotFound proof for ${data.getName()} at ${packet.hash}` + ); + continue; + } await this.tonHandler.sendPacket(finalUpdateHeight, data, proof); - logger.info( + this.logger.info( `PacketProcessor:Send packet ${data.getName()} to TON successfully` ); } await this.cosmosBlockOffset.updateBlockOffset(finalUpdateHeight); - logger.info( + this.logger.info( `PacketProcessor:Update block offset to ${finalUpdateHeight}` ); } catch (error) { - logger.error(`PacketProcessor:Error when run:${error}`); + this.logger.error(`PacketProcessor:Error when run`, error); throw new Error(`PacketProcessor:Error when run:${error}`); } } diff --git a/packages/cw-to-ton/src/config/index.ts b/packages/cw-to-ton/src/config/index.ts index 280ebb8..6c25a9c 100644 --- a/packages/cw-to-ton/src/config/index.ts +++ b/packages/cw-to-ton/src/config/index.ts @@ -1,6 +1,6 @@ import * as dotenv from "dotenv"; -dotenv.config(); +dotenv.config(); export type Config = { tonMnemonic: string; cosmosRpcUrl: string; diff --git a/packages/cw-to-ton/src/index.ts b/packages/cw-to-ton/src/index.ts index adbefa5..8c1bd7e 100644 --- a/packages/cw-to-ton/src/index.ts +++ b/packages/cw-to-ton/src/index.ts @@ -7,7 +7,6 @@ import { } from "@oraichain/ton-bridge-contracts"; import { Address } from "@ton/core"; import { PacketProcessor } from "./PacketProcessor"; - import { CosmosProofHandler, CosmwasmWatcherEvent, @@ -17,14 +16,23 @@ import { } from "./services"; import { Packets } from "./@types"; import { CosmosBlockOffset } from "./models"; +import { Logger } from "winston"; -export async function createCwToTonRelayerWithConfig(config: Config) { +export async function createCwToTonRelayerWithConfig( + config: Config, + injectLogger: Logger +) { + const logger = injectLogger; const duckDb = await DuckDb.getInstance(config.connectionString); const cosmosBlockOffset = new CosmosBlockOffset(duckDb); + await cosmosBlockOffset.createTable(); + const startOffset = await cosmosBlockOffset.mayLoadBlockOffset( + config.syncBlockOffSet + ); + logger.info(`CW_TO_TON start at: ${startOffset}`); if (config.wasmBridge === "") { throw new Error("WASM_BRIDGE is required"); } - const { walletContract, client: tonClient, @@ -35,7 +43,6 @@ export async function createCwToTonRelayerWithConfig(config: Config) { config.tonCenter, config.tonApiKey ); - const lightClientMaster = tonClient.open( LightClientMaster.createFromAddress( Address.parse(TonDefaultConfig.cosmosLightClientMaster) @@ -48,7 +55,6 @@ export async function createCwToTonRelayerWithConfig(config: Config) { config.cosmosRpcUrl, config.wasmBridge ); - const tonHandler = new TonHandler( walletContract, tonClient, @@ -57,25 +63,31 @@ export async function createCwToTonRelayerWithConfig(config: Config) { bridgeAdapter, config.syncInterval ); - const packetProcessor = new PacketProcessor({ cosmosBlockOffset, cosmosProofHandler, tonHandler, pollingInterval: config.syncInterval, + logger, }); - const watcher = await createCosmosBridgeWatcher(config); packetProcessor.run(); + watcher.on( CosmwasmWatcherEvent.DATA, async (data: Packets & { offset: number }) => { - const { transferPackets, ackPackets } = data; - packetProcessor.addPendingTransferPackets(transferPackets); - packetProcessor.addPendingAckPackets(ackPackets); + const { transferPackets, ackPackets, offset } = data; + logger.info(`CosmosWatcher synced at block: ${offset}`); + if (transferPackets && transferPackets.length > 0) { + logger.info(`Found ${transferPackets.length} TransferPackets`); + packetProcessor.addPendingTransferPackets(transferPackets); + } + if (ackPackets && ackPackets.length > 0) { + logger.info(`Found ${ackPackets.length} AckPackets`); + packetProcessor.addPendingAckPackets(ackPackets); + } } ); - return watcher; } diff --git a/packages/cw-to-ton/src/services/cosmos.service.ts b/packages/cw-to-ton/src/services/cosmos.service.ts index a6e3b61..cbe3ec1 100644 --- a/packages/cw-to-ton/src/services/cosmos.service.ts +++ b/packages/cw-to-ton/src/services/cosmos.service.ts @@ -138,7 +138,9 @@ export class CosmwasmWatcher extends EventEmitter { (parsedData.transferPackets.length > 0 || parsedData.ackPackets.length > 0) ) { - this.emit(CosmwasmWatcherEvent.DATA, { parsedData, offset }); + this.emit(CosmwasmWatcherEvent.DATA, { ...parsedData, offset }); + } else { + this.emit(CosmwasmWatcherEvent.DATA, { offset }); } } catch (e) { this.emit("error", `CosmwasmWatcher:Error when parsing data:${e}`); diff --git a/packages/cw-to-ton/src/services/ton.service.ts b/packages/cw-to-ton/src/services/ton.service.ts index d9cfc2e..ccc0380 100644 --- a/packages/cw-to-ton/src/services/ton.service.ts +++ b/packages/cw-to-ton/src/services/ton.service.ts @@ -75,7 +75,6 @@ export class TonHandler { }; async updateLightClient(clientData: LightClientData) { - logger.debug(`TonHandler:updateLightClient:${JSON.stringify(clientData)}`); const header = deserializeHeader(clientData.header); const height = BigInt(header.height); await retry( @@ -125,7 +124,6 @@ export class TonHandler { packet: IntoCell, proofs: ExistenceProof[] ) { - logger.debug(`TonHandler:sendPacket:${JSON.stringify(packet)}`); try { const seqno = await retry(async () => { try { diff --git a/packages/orchestrator/.env.example b/packages/orchestrator/.env.example index 248903e..05e1849 100644 --- a/packages/orchestrator/.env.example +++ b/packages/orchestrator/.env.example @@ -1,10 +1,8 @@ NODE_ENV=mainnet -REDIS_HOST="localhost" -REDIS_PORT=6379 TON_MNEMONIC= COSMOS_MNEMONIC= -COSMOS_RPC_URL= -SYNC_BLOCK_OFFSET= +COSMOS_RPC_URL="https://rpc.orai.io/" +SYNC_BLOCK_OFFSET=31548100 SYNC_LIMIT=100 SYNC_THREADS=4 SYNC_INTERVAL=5000 @@ -12,7 +10,13 @@ TON_CENTER="https://toncenter.orai.io/jsonRPC" TON_API_KEY= TON_LITE_CLIENT_LIST="https://ton.org/global.config.json" CONNECTION_STRING="relayer.duckdb" -WASM_BRIDGE= -WASM_VALIDATORS= -COSMOS_LIGHT_CLIENT_MASTER= -TON_BRIDGE= \ No newline at end of file +WASM_VALIDATORS=orai16crw7g2rcvuga7vlnyxgwtdxtan46k8qqjjwhjqdjvjgk96n95es35q8vm +COSMOS_LIGHT_CLIENT_MASTER=EQDzy_POlimFDyzrHd3OQsb9sZCngyG3O7Za4GRFzM-rrO93 +TON_BRIDGE=EQC-aFP0rJXwTgKZQJPbPfTSpBFc8wxOgKHWD9cPvOl_DnaY +WASM_BRIDGE=orai159l8l9c5ckhqpuwdfgs9p4v599nqt3cjlfahalmtrhfuncnec2ms5mz60e +# App configuration +LOG_LEVEL="info" +HEALTH_CHECK_PORT=3001 +WEBHOOK_URL= + + diff --git a/packages/orchestrator/package.json b/packages/orchestrator/package.json index 01f9a12..04c2c4e 100644 --- a/packages/orchestrator/package.json +++ b/packages/orchestrator/package.json @@ -14,7 +14,8 @@ "@oraichain/tonbridge-relayer-to-cw": "^1.0.9", "@oraichain/tonbridge-relayer-to-ton": "^1.1.0", "bullmq": "^5.8.7", - "express": "^4.19.2" + "express": "^4.19.2", + "winston-transport-discord": "^1.0.3" }, "devDependencies": { "@types/express": "^4", diff --git a/packages/orchestrator/src/@types/global.d.ts b/packages/orchestrator/src/@types/global.d.ts index 0467cc7..15ffa51 100644 --- a/packages/orchestrator/src/@types/global.d.ts +++ b/packages/orchestrator/src/@types/global.d.ts @@ -1,9 +1,11 @@ +import { Logger } from "winston"; + declare global { + // eslint-disable-next-line no-var + var logger: Logger; namespace NodeJS { interface ProcessEnv { NODE_ENV: string; - REDIS_HOST: string; - REDIS_PORT: number; TON_MNEMONIC: string; COSMOS_MNEMONIC: string; // SYNC_OPTS @@ -23,6 +25,10 @@ declare global { WASM_VALIDATORS: string; TON_BRIDGE: string; COSMOS_LIGHT_CLIENT_MASTER: string; + + // APP CONFIG + WEBHOOK_URL: string; + HEALTH_CHECK_PORT: number; } interface BigInt { toJSON(): string; diff --git a/packages/orchestrator/src/config/index.ts b/packages/orchestrator/src/config/index.ts index e5e7dc9..db19349 100644 --- a/packages/orchestrator/src/config/index.ts +++ b/packages/orchestrator/src/config/index.ts @@ -1,17 +1,22 @@ import { Config as CwToTonConfig } from "@oraichain/tonbridge-relayer-to-ton"; import type { Config as TonToCwConfig } from "@oraichain/tonbridge-relayer-to-cw"; + import * as dotenv from "dotenv"; + dotenv.config(); export type Config = { + appConfig: { + webhookUrl: string; + heathCheckPort: number; + loglevel: string; + }; cwToTon: CwToTonConfig; tonToCw: TonToCwConfig; }; export function loadConfig(): Config { const cwToTon: CwToTonConfig = { - redisHost: process.env.REDIS_HOST || "http://localhost", - redisPort: Number(process.env.REDIS_PORT || 6379), tonMnemonic: process.env.TON_MNEMONIC || "", cosmosRpcUrl: process.env.COSMOS_RPC_URL || "https://rpc.orai.io/", syncBlockOffSet: Number(process.env.SYNC_BLOCK_OFFSET || 20000000), @@ -38,7 +43,14 @@ export function loadConfig(): Config { }; return { + appConfig: { + webhookUrl: process.env.WEBHOOK_URL || "", + heathCheckPort: Number(process.env.HEALTH_CHECK_PORT || 3000), + loglevel: process.env.LOG_LEVEL || "info", + }, cwToTon, tonToCw, }; } + +export * from "./logger"; diff --git a/packages/orchestrator/src/config/logger.ts b/packages/orchestrator/src/config/logger.ts new file mode 100644 index 0000000..279c2e0 --- /dev/null +++ b/packages/orchestrator/src/config/logger.ts @@ -0,0 +1,24 @@ +import { createLogger, format, transports } from "winston"; +import { DiscordTransport } from "winston-transport-discord"; +export const logger = (label: string, webhookUrl: string, loglevel?: string) => + createLogger({ + level: loglevel || "info", + format: format.combine( + format.label({ label }), + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf((info) => { + return `[${info.label}] ${info.timestamp} ${info.level}: ${info.message}`; + }) + ), + transports: [ + new transports.Console(), + new DiscordTransport({ + discord: { + webhook: { + url: webhookUrl, + }, + }, + level: "error", + }), + ], + }); diff --git a/packages/orchestrator/src/index.ts b/packages/orchestrator/src/index.ts index 32adbda..7c38d1e 100644 --- a/packages/orchestrator/src/index.ts +++ b/packages/orchestrator/src/index.ts @@ -2,15 +2,27 @@ import express, { Request, Response } from "express"; import dotenv from "dotenv"; import { createTonToCwRelayerWithConfig } from "@oraichain/tonbridge-relayer-to-cw"; import { createCwToTonRelayerWithConfig } from "@oraichain/tonbridge-relayer-to-ton"; +import { loadConfig, logger as createServiceLogger } from "./config"; +import { Logger } from "winston"; -import { loadConfig } from "./config"; dotenv.config(); const config = loadConfig(); - -console.log({ config }); +// eslint-disable-next-line prefer-const +let appLogger: Logger; (async () => { try { + appLogger = createServiceLogger( + "Orchestrator", + config.appConfig.webhookUrl, + config.appConfig.loglevel + ); + appLogger.debug(JSON.stringify(config)); + const cwToTonLogger = createServiceLogger( + "CwToTonRelayer", + config.appConfig.webhookUrl, + config.appConfig.loglevel + ); const app = express(); const port = process.env.HEALTH_CHECK_PORT ? Number(process.env.HEALTH_CHECK_PORT) @@ -21,13 +33,16 @@ console.log({ config }); }); app.listen(port, "0.0.0.0", async () => { - console.log(`Server is running at http://0.0.0.0:${port}`); + appLogger.info(`Server is running at http://0.0.0.0:${port}`); const [tonToCwRelayer, cwToTonRelayer] = await Promise.all([ createTonToCwRelayerWithConfig(config.tonToCw), - createCwToTonRelayerWithConfig(config.cwToTon), + createCwToTonRelayerWithConfig(config.cwToTon, cwToTonLogger), ]); - tonToCwRelayer.relay(); + // tonToCwRelayer.relay(); cwToTonRelayer.start(); + cwToTonRelayer.on("error", (error) => { + appLogger.error(`cwToTonRelayer`, error); + }); }); } catch (error) { console.error("error is: ", error); @@ -35,7 +50,7 @@ console.log({ config }); } process.on("unhandledRejection", (reason) => { - console.log(reason); + console.log("unhandledRejection", reason); process.exit(1); }); @@ -43,7 +58,6 @@ console.log({ config }); console.log("SIGINT received"); process.exit(0); }); - process.on("SIGTERM", () => { console.log("SIGTERM received"); process.exit(0); diff --git a/tsconfig.json b/tsconfig.json index 8d897e1..0d64a43 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,8 @@ "sourceMap": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, - "declaration": false, + "declaration": true, + "declarationMap": true, "resolveJsonModule": true, "outDir": "dist", "typeRoots": ["node_modules/@types"], diff --git a/yarn.lock b/yarn.lock index 44ada70..299a3ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -923,6 +923,34 @@ enabled "2.0.x" kuler "^2.0.0" +"@discordjs/builders@^0.16.0": + version "0.16.0" + resolved "https://registry.npmjs.org/@discordjs/builders/-/builders-0.16.0.tgz#3201f57fa57c4dd77aebb480cf47da77b7ba2e8c" + integrity sha512-9/NCiZrLivgRub2/kBc0Vm5pMBE5AUdYbdXsLu/yg9ANgvnaJ0bZKTY8yYnLbsEc/LYUP79lEIdC73qEYhWq7A== + dependencies: + "@sapphire/shapeshift" "^3.5.1" + discord-api-types "^0.36.2" + fast-deep-equal "^3.1.3" + ts-mixer "^6.0.1" + tslib "^2.4.0" + +"@discordjs/collection@^0.7.0": + version "0.7.0" + resolved "https://registry.npmjs.org/@discordjs/collection/-/collection-0.7.0.tgz#1a6c00198b744ba2b73a64442145da637ac073b8" + integrity sha512-R5i8Wb8kIcBAFEPLLf7LVBQKBDYUL+ekb23sOgpkpyGT+V4P7V83wTxcsqmX+PbqHt4cEHn053uMWfRqh/Z/nA== + +"@discordjs/rest@^0.5.0": + version "0.5.0" + resolved "https://registry.npmjs.org/@discordjs/rest/-/rest-0.5.0.tgz#dc5bd68e33bf8dd1cf08cf3c55b2901deb92eb5d" + integrity sha512-S4E1YNz1UxgUfMPpMeqzPPkCfXE877zOsvKM5WEmwIhcpz1PQV7lzqlEOuz194UuwOJLLjQFBgQELnQfCX9UfA== + dependencies: + "@discordjs/collection" "^0.7.0" + "@sapphire/async-queue" "^1.3.1" + "@sapphire/snowflake" "^3.2.2" + discord-api-types "^0.33.3" + tslib "^2.4.0" + undici "^5.4.0" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1313,6 +1341,11 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@fastify/busboy@^2.0.0": + version "2.1.1" + resolved "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz#b9da6a878a371829a0502c9b6c1c143ef6663f4d" + integrity sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA== + "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -2656,6 +2689,24 @@ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== +"@sapphire/async-queue@^1.3.1", "@sapphire/async-queue@^1.5.0": + version "1.5.3" + resolved "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.3.tgz#03cd2a2f3665068f314736bdc56eee2025352422" + integrity sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w== + +"@sapphire/shapeshift@^3.5.1": + version "3.9.7" + resolved "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-3.9.7.tgz#43e23243cac8a0c046bf1e73baf3dbf407d33a0c" + integrity sha512-4It2mxPSr4OGn4HSQWGmhFMsNFGfFVhWeRPCRwbH972Ek2pzfGRZtb0pJ4Ze6oIzcyh2jw7nUDa6qGlWofgd9g== + dependencies: + fast-deep-equal "^3.1.3" + lodash "^4.17.21" + +"@sapphire/snowflake@^3.2.2": + version "3.5.3" + resolved "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz#0c102aa2ec5b34f806e9bc8625fc6a5e1d0a0c6a" + integrity sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ== + "@scure/base@~1.1.6": version "1.1.7" resolved "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" @@ -3122,6 +3173,14 @@ resolved "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== +"@types/node-fetch@^2.6.3": + version "2.6.11" + resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" + integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + "@types/node@*", "@types/node@>=13.7.0", "@types/node@^20.11.30": version "20.14.10" resolved "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a" @@ -3212,6 +3271,13 @@ dependencies: "@types/node" "*" +"@types/ws@^8.5.4": + version "8.5.12" + resolved "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -4988,6 +5054,31 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discord-api-types@^0.33.3, discord-api-types@^0.33.5: + version "0.33.5" + resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.33.5.tgz#6548b70520f7b944c60984dca4ab58654d664a12" + integrity sha512-dvO5M52v7m7Dy96+XUnzXNsQ/0npsYpU6dL205kAtEDueswoz3aU3bh1UMoK4cQmcGtB1YRyLKqp+DXi05lzFg== + +discord-api-types@^0.36.1, discord-api-types@^0.36.2: + version "0.36.3" + resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.36.3.tgz#a931b7e57473a5c971d6937fa5f392eb30047579" + integrity sha512-bz/NDyG0KBo/tY14vSkrwQ/n3HKPf87a0WFW/1M9+tXYK+vp5Z5EksawfCWo2zkAc6o7CClc0eff1Pjrqznlwg== + +discord.js@^13.8.1: + version "13.17.1" + resolved "https://registry.npmjs.org/discord.js/-/discord.js-13.17.1.tgz#df171c8aacb351b1532d0562dd7db56df666094d" + integrity sha512-h13kUf+7ZaP5ZWggzooCxFutvJJvugcAO54oTEIdVr3zQWi0Sf/61S1kETtuY9nVAyYebXR/Ey4C+oWbsgEkew== + dependencies: + "@discordjs/builders" "^0.16.0" + "@discordjs/collection" "^0.7.0" + "@sapphire/async-queue" "^1.5.0" + "@types/node-fetch" "^2.6.3" + "@types/ws" "^8.5.4" + discord-api-types "^0.33.5" + form-data "^4.0.0" + node-fetch "^2.6.7" + ws "^8.13.0" + doctrine@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -10703,6 +10794,11 @@ ts-jest@^29.1.4: semver "^7.5.3" yargs-parser "^21.0.1" +ts-mixer@^6.0.1: + version "6.0.4" + resolved "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz#1da39ceabc09d947a82140d9f09db0f84919ca28" + integrity sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA== + ts-node-dev@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" @@ -10962,6 +11058,13 @@ undici-types@~5.26.4: resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici@^5.4.0: + version "5.28.4" + resolved "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz#6b280408edb6a1a604a9b20340f45b422e373068" + integrity sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g== + dependencies: + "@fastify/busboy" "^2.0.0" + unique-filename@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz#e785f8675a9a7589e0ac77e0b5c34d2eaeac6da2" @@ -11226,7 +11329,19 @@ wide-align@1.1.5, wide-align@^1.1.2, wide-align@^1.1.5: dependencies: string-width "^1.0.2 || 2 || 3 || 4" -winston-transport@^4.7.0: +winston-transport-discord@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/winston-transport-discord/-/winston-transport-discord-1.0.3.tgz#8cf82b1f2a6481b7ddf5a11a67c6af3c19c0556c" + integrity sha512-pKF3Sie6haNk+MjyF60nkex1AdskJ6Ep2Oxym1oh1Te1Vgs162qoVg91W9Tb4doHivYBmPJZ90z7gHXarRxd2A== + dependencies: + "@discordjs/rest" "^0.5.0" + discord-api-types "^0.36.1" + discord.js "^13.8.1" + triple-beam "^1.3.0" + tslib "^2.4.0" + winston-transport "^4.5.0" + +winston-transport@^4.5.0, winston-transport@^4.7.0: version "4.7.1" resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz#52ff1bcfe452ad89991a0aaff9c3b18e7f392569" integrity sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA== @@ -11355,7 +11470,7 @@ ws@^7: resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.8.1: +ws@^8.13.0, ws@^8.8.1: version "8.18.0" resolved "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==