feat(token-aggregation): reports, PMM quotes, config; Engine X flash vaults
- Expand token-aggregation API (report routes), canonical tokens, pools - Add flash vault contracts + tests (indexed, DODO cwUSDC, XAUT borrow) - PMM pools JSON, deploy/export scripts, metamask verified list Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,6 +26,7 @@ export function getDatabasePool(): Pool {
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
min: parseInt(process.env.DATABASE_POOL_MIN || '2', 10),
|
||||
max: parseInt(process.env.DATABASE_POOL_MAX || '10', 10),
|
||||
connectionTimeoutMillis: parseInt(process.env.DATABASE_CONNECTION_TIMEOUT_MS || '3000', 10),
|
||||
};
|
||||
|
||||
// If connectionString is not provided, use individual config
|
||||
|
||||
@@ -19,7 +19,16 @@ export class MarketDataRepository {
|
||||
return code === '42P01' || (message.includes('relation "') && message.includes('" does not exist'));
|
||||
}
|
||||
|
||||
private shouldUseReadFallback(error: unknown): boolean {
|
||||
if (this.isMissingRelationError(error)) return true;
|
||||
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
|
||||
const message = (error as { message?: string })?.message || '';
|
||||
const code = (error as { code?: string })?.code || '';
|
||||
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
|
||||
}
|
||||
|
||||
async getMarketData(chainId: number, tokenAddress: string): Promise<TokenMarketData | null> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
|
||||
@@ -49,7 +58,7 @@ export class MarketDataRepository {
|
||||
lastUpdated: row.last_updated,
|
||||
};
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
@@ -99,6 +108,7 @@ export class MarketDataRepository {
|
||||
}
|
||||
|
||||
async getTopTokensByVolume(chainId: number, limit: number = 50): Promise<TokenMarketData[]> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
|
||||
@@ -125,7 +135,7 @@ export class MarketDataRepository {
|
||||
lastUpdated: row.last_updated,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
@@ -133,6 +143,7 @@ export class MarketDataRepository {
|
||||
}
|
||||
|
||||
async getTopTokensByLiquidity(chainId: number, limit: number = 50): Promise<TokenMarketData[]> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT chain_id, token_address, price_usd, price_change_24h, volume_24h, volume_7d, volume_30d,
|
||||
@@ -159,7 +170,7 @@ export class MarketDataRepository {
|
||||
lastUpdated: row.last_updated,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -53,7 +53,16 @@ export class PoolRepository {
|
||||
return code === '42P01' || message.includes('relation "') && message.includes('" does not exist');
|
||||
}
|
||||
|
||||
private shouldUseReadFallback(error: unknown): boolean {
|
||||
if (this.isMissingRelationError(error)) return true;
|
||||
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
|
||||
const message = (error as { message?: string })?.message || '';
|
||||
const code = (error as { code?: string })?.code || '';
|
||||
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
|
||||
}
|
||||
|
||||
async getPool(chainId: number, poolAddress: string): Promise<LiquidityPool | null> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
|
||||
@@ -70,7 +79,7 @@ export class PoolRepository {
|
||||
|
||||
return this.mapRowToPool(result.rows[0]);
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
@@ -78,6 +87,7 @@ export class PoolRepository {
|
||||
}
|
||||
|
||||
async getPoolsByChain(chainId: number, limit: number = 500): Promise<LiquidityPool[]> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
|
||||
@@ -91,7 +101,7 @@ export class PoolRepository {
|
||||
);
|
||||
return result.rows.map((row) => this.mapRowToPool(row));
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
@@ -99,6 +109,7 @@ export class PoolRepository {
|
||||
}
|
||||
|
||||
async getPoolsByToken(chainId: number, tokenAddress: string): Promise<LiquidityPool[]> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT id, chain_id, pool_address, token0_address, token1_address, dex_type,
|
||||
@@ -112,7 +123,7 @@ export class PoolRepository {
|
||||
|
||||
return result.rows.map((row) => this.mapRowToPool(row));
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
|
||||
@@ -46,7 +46,16 @@ export class TokenRepository {
|
||||
return code === '42P01' || (message.includes('relation "') && message.includes('" does not exist'));
|
||||
}
|
||||
|
||||
private shouldUseReadFallback(error: unknown): boolean {
|
||||
if (this.isMissingRelationError(error)) return true;
|
||||
if (String(process.env.TOKEN_AGGREGATION_DB_READ_FALLBACK ?? '1').toLowerCase() === '0') return false;
|
||||
const message = (error as { message?: string })?.message || '';
|
||||
const code = (error as { code?: string })?.code || '';
|
||||
return ['ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'].includes(code) || /timeout|connect/i.test(message);
|
||||
}
|
||||
|
||||
async getToken(chainId: number, address: string): Promise<Token | null> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return null;
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT chain_id, address, name, symbol, decimals, total_supply, logo_url, website_url, description, verified
|
||||
@@ -73,7 +82,7 @@ export class TokenRepository {
|
||||
verified: row.verified,
|
||||
};
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
@@ -81,6 +90,7 @@ export class TokenRepository {
|
||||
}
|
||||
|
||||
async getTokens(chainId: number, limit: number = 50, offset: number = 0): Promise<Token[]> {
|
||||
if (String(process.env.TOKEN_AGGREGATION_SKIP_DB_READS ?? '0').toLowerCase() === '1') return [];
|
||||
try {
|
||||
const result = await this.pool.query(
|
||||
`SELECT chain_id, address, name, symbol, decimals, total_supply, logo_url, website_url, description, verified
|
||||
@@ -104,7 +114,7 @@ export class TokenRepository {
|
||||
verified: row.verified,
|
||||
}));
|
||||
} catch (error) {
|
||||
if (this.isMissingRelationError(error)) {
|
||||
if (this.shouldUseReadFallback(error)) {
|
||||
return [];
|
||||
}
|
||||
throw error;
|
||||
|
||||
Reference in New Issue
Block a user