92 lines
3.2 KiB
TypeScript
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);
|
|
});
|