Files
web3-payment/src/index.ts
2026-05-14 00:57:42 +03:00

92 lines
3.2 KiB
TypeScript

import 'dotenv/config';
import type {Server} from 'node:http';
import {loadConfig,resolveEthereumConfig} from './config.js';
import {PostgresClient} from './db/PostgresClient.js';
import {EthersEthereumGateway} from './ethereum/EthersEthereumGateway.js';
import {createApp} from './http/app.js';
import {createLogger} from './logger.js';
import {AmqpClient} from './queue/AmqpClient.js';
import {VaultClient} from './secrets/VaultClient.js';
import {TransferOrchestrator} from './services/TransferOrchestrator.js';
async function main(): Promise<void> {
const config = loadConfig();
const logger = createLogger(config.logLevel);
logger.info({ event: 'bootstrap.start' }, 'starting usdt-transfer service');
const vault = new VaultClient({
addr: config.vaultAddr,
mountPoint: config.vaultMountPoint,
roleId: config.vaultRoleId,
secretId: config.vaultSecretId
});
const secrets = await vault.bootstrap();
logger.info({ event: 'bootstrap.vault_loaded' }, 'vault secrets loaded');
const ethereumConfig = resolveEthereumConfig(config, secrets.ethereum);
const db = new PostgresClient(secrets.database);
await db.ping();
logger.info({ event: 'bootstrap.pg_ready' }, 'postgres pool ready');
const ethereum = new EthersEthereumGateway({
rpcUrl: ethereumConfig.ethRpcUrl,
privateKey: ethereumConfig.hotWalletPrivateKey,
usdtContractAddress: ethereumConfig.usdtContractAddress
});
let triggerShutdown: (reason: string) => void = () => {};
const amqp = new AmqpClient({
secret: secrets.rabbitmq,
logger,
onConnectionLost: () => triggerShutdown('amqp_connection_lost')
});
await amqp.connect();
const orchestrator = new TransferOrchestrator(db, ethereum, amqp, {
maxTransferAmountUnits: config.maxTransferAmountUnits,
minEthBalanceWei: config.minEthBalanceWei,
balanceCheckAttempts: config.balanceCheckAttempts,
balanceCheckIntervalMs: config.balanceCheckIntervalMs,
staleWeb3ProcessingMs: config.staleWeb3ProcessingMs
});
await amqp.startConsumer((message, log) => orchestrator.handle(message, log));
const app = createApp({ ethereum, db, amqp, logger });
const server: Server = await new Promise((resolve) => {
const s = app.listen(config.port, () => resolve(s));
});
logger.info({ event: 'bootstrap.http_listening', port: config.port }, 'http listening');
let shuttingDown = false;
triggerShutdown = (reason: string) => {
if (shuttingDown) {
return;
}
shuttingDown = true;
logger.warn({ event: 'shutdown.start', reason }, 'shutting down');
void (async () => {
try {
await new Promise<void>((resolve) => server.close(() => resolve()));
await amqp.close();
await db.close();
logger.info({ event: 'shutdown.done' }, 'shutdown complete');
process.exit(reason === 'amqp_connection_lost' ? 1 : 0);
} catch (error) {
logger.error({ event: 'shutdown.error', err: error }, 'shutdown error');
process.exit(1);
}
})();
};
process.on('SIGINT', () => triggerShutdown('SIGINT'));
process.on('SIGTERM', () => triggerShutdown('SIGTERM'));
}
main().catch((error) => {
console.error('fatal bootstrap error:', error);
process.exit(1);
});