feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs
- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
This commit is contained in:
19
services/token-aggregation/scripts/apply-lightweight-schema.sh
Executable file
19
services/token-aggregation/scripts/apply-lightweight-schema.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SERVICE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
SCHEMA_FILE="${SCRIPT_DIR}/bootstrap-lightweight-schema.sql"
|
||||
|
||||
if [ -f "${SERVICE_DIR}/.env" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
set -a && source "${SERVICE_DIR}/.env" && set +a
|
||||
fi
|
||||
|
||||
if [ -z "${DATABASE_URL:-}" ]; then
|
||||
echo "DATABASE_URL is required. Set it in ${SERVICE_DIR}/.env or export it before running." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Applying lightweight token-aggregation schema using ${SCHEMA_FILE}"
|
||||
psql "${DATABASE_URL}" -v ON_ERROR_STOP=1 -f "${SCHEMA_FILE}"
|
||||
@@ -0,0 +1,181 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
name TEXT,
|
||||
symbol TEXT,
|
||||
decimals INTEGER,
|
||||
total_supply NUMERIC(78, 0),
|
||||
logo_url TEXT,
|
||||
website_url TEXT,
|
||||
description TEXT,
|
||||
verified BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (chain_id, address)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tokens_chain_id ON tokens (chain_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tokens_chain_symbol ON tokens (chain_id, symbol);
|
||||
CREATE INDEX IF NOT EXISTS idx_tokens_chain_name ON tokens (chain_id, name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS token_market_data (
|
||||
chain_id INTEGER NOT NULL,
|
||||
token_address TEXT NOT NULL,
|
||||
price_usd NUMERIC(38, 18),
|
||||
price_change_24h NUMERIC(38, 18),
|
||||
volume_24h NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
volume_7d NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
volume_30d NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
market_cap_usd NUMERIC(38, 18),
|
||||
liquidity_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
holders_count INTEGER NOT NULL DEFAULT 0,
|
||||
transfers_24h INTEGER NOT NULL DEFAULT 0,
|
||||
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
PRIMARY KEY (chain_id, token_address)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_token_market_data_volume_24h
|
||||
ON token_market_data (chain_id, volume_24h DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_token_market_data_liquidity
|
||||
ON token_market_data (chain_id, liquidity_usd DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS liquidity_pools (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
pool_address TEXT NOT NULL,
|
||||
token0_address TEXT NOT NULL,
|
||||
token1_address TEXT NOT NULL,
|
||||
dex_type TEXT NOT NULL,
|
||||
factory_address TEXT,
|
||||
router_address TEXT,
|
||||
reserve0 NUMERIC(78, 0) NOT NULL DEFAULT 0,
|
||||
reserve1 NUMERIC(78, 0) NOT NULL DEFAULT 0,
|
||||
reserve0_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
reserve1_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
total_liquidity_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
volume_24h NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
fee_tier INTEGER,
|
||||
created_at_block BIGINT,
|
||||
created_at_timestamp TIMESTAMPTZ,
|
||||
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (chain_id, pool_address)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_liquidity_pools_chain_liquidity
|
||||
ON liquidity_pools (chain_id, total_liquidity_usd DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_liquidity_pools_chain_token0
|
||||
ON liquidity_pools (chain_id, token0_address);
|
||||
CREATE INDEX IF NOT EXISTS idx_liquidity_pools_chain_token1
|
||||
ON liquidity_pools (chain_id, token1_address);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pool_reserves_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
pool_address TEXT NOT NULL,
|
||||
reserve0 NUMERIC(78, 0) NOT NULL,
|
||||
reserve1 NUMERIC(78, 0) NOT NULL,
|
||||
reserve0_usd NUMERIC(38, 18),
|
||||
reserve1_usd NUMERIC(38, 18),
|
||||
total_liquidity_usd NUMERIC(38, 18),
|
||||
block_number BIGINT NOT NULL,
|
||||
timestamp TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pool_reserves_history_lookup
|
||||
ON pool_reserves_history (chain_id, pool_address, timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS swap_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
pool_address TEXT NOT NULL,
|
||||
token0_address TEXT NOT NULL,
|
||||
token1_address TEXT NOT NULL,
|
||||
amount_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
price_usd NUMERIC(38, 18),
|
||||
transaction_hash TEXT,
|
||||
log_index INTEGER,
|
||||
block_number BIGINT,
|
||||
timestamp TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_swap_events_pool_time
|
||||
ON swap_events (chain_id, pool_address, timestamp DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_swap_events_token_time
|
||||
ON swap_events (chain_id, token0_address, token1_address, timestamp DESC);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_swap_events_unique_log
|
||||
ON swap_events (
|
||||
chain_id,
|
||||
pool_address,
|
||||
COALESCE(transaction_hash, ''),
|
||||
COALESCE(log_index, -1)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS token_ohlcv (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
token_address TEXT NOT NULL,
|
||||
pool_address TEXT NOT NULL DEFAULT '',
|
||||
interval_type TEXT NOT NULL,
|
||||
open_price NUMERIC(38, 18) NOT NULL,
|
||||
high_price NUMERIC(38, 18) NOT NULL,
|
||||
low_price NUMERIC(38, 18) NOT NULL,
|
||||
close_price NUMERIC(38, 18) NOT NULL,
|
||||
volume NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
volume_usd NUMERIC(38, 18) NOT NULL DEFAULT 0,
|
||||
timestamp TIMESTAMPTZ NOT NULL,
|
||||
UNIQUE (chain_id, token_address, pool_address, interval_type, timestamp)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_token_ohlcv_lookup
|
||||
ON token_ohlcv (chain_id, token_address, interval_type, timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS provider_health_snapshots (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
chain_id INTEGER NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
supports_execution BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
supports_quote BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
captured_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_provider_health_snapshots_lookup
|
||||
ON provider_health_snapshots (chain_id, provider, captured_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS route_plan_cache (
|
||||
plan_id TEXT PRIMARY KEY,
|
||||
request_hash TEXT NOT NULL,
|
||||
chain_id INTEGER NOT NULL,
|
||||
destination_chain_id INTEGER NOT NULL,
|
||||
decision TEXT NOT NULL,
|
||||
response_json JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_route_plan_cache_lookup
|
||||
ON route_plan_cache (request_hash, expires_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS route_execution_metrics (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
plan_id TEXT NOT NULL,
|
||||
chain_id INTEGER NOT NULL,
|
||||
provider TEXT NOT NULL,
|
||||
hop_index INTEGER NOT NULL,
|
||||
token_in_address TEXT NOT NULL,
|
||||
token_out_address TEXT NOT NULL,
|
||||
estimated_amount_out NUMERIC(78, 0),
|
||||
actual_amount_out NUMERIC(78, 0),
|
||||
status TEXT NOT NULL,
|
||||
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_route_execution_metrics_lookup
|
||||
ON route_execution_metrics (plan_id, hop_index, created_at DESC);
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,41 @@
|
||||
import path from 'path';
|
||||
import { existsSync } from 'fs';
|
||||
import * as dotenv from 'dotenv';
|
||||
import { AggregatorRouteMatrixGenerator } from '../src/services/aggregator-route-matrix-generator';
|
||||
import { closeDatabasePool } from '../src/database/client';
|
||||
|
||||
const rootEnvCandidates = [
|
||||
path.resolve(__dirname, '../../.env'),
|
||||
path.resolve(__dirname, '../../../.env'),
|
||||
];
|
||||
|
||||
for (const candidate of rootEnvCandidates) {
|
||||
if (existsSync(candidate)) {
|
||||
dotenv.config({ path: candidate });
|
||||
break;
|
||||
}
|
||||
}
|
||||
dotenv.config();
|
||||
|
||||
async function main() {
|
||||
const outputPath = process.argv[2]
|
||||
? path.resolve(process.cwd(), process.argv[2])
|
||||
: path.resolve(__dirname, '../../../../config/aggregator-route-matrix.json');
|
||||
|
||||
const generator = new AggregatorRouteMatrixGenerator();
|
||||
try {
|
||||
const writtenPath = await generator.writeToFile(outputPath, 138);
|
||||
process.stdout.write(`${writtenPath}\n`);
|
||||
} finally {
|
||||
await closeDatabasePool();
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack || error.message : String(error)}\n`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,142 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { getProviderCapabilities } from '../src/config/provider-capabilities';
|
||||
import { InternalExecutionPlanV2Builder } from '../src/services/internal-execution-plan-v2';
|
||||
import { BestExecutionPlanner } from '../src/services/best-execution-planner';
|
||||
|
||||
const CHAIN_ID = 138;
|
||||
const WETH10 = '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f';
|
||||
const USDT = '0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1';
|
||||
const D3_PROXY = '0xc9a11abB7C63d88546Be24D58a6d95e3762cB843';
|
||||
const D3_POOL = '0x6550A3a59070061a262a893A1D6F3F490afFDBDA';
|
||||
const ROUTER_V2 = '0xF1c93F54A5C2fc0d7766Ccb0Ad8f157DFB4C99Ce';
|
||||
const AMOUNT_IN = '100000000000000000';
|
||||
|
||||
class MockPlannerMetricsRepository {
|
||||
async getCachedPlan(): Promise<null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async recordProviderSnapshots(): Promise<void> {}
|
||||
|
||||
async cachePlan(): Promise<void> {}
|
||||
|
||||
async recordPlannedRouteMetrics(): Promise<void> {}
|
||||
}
|
||||
|
||||
function assert(condition: unknown, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function loadMatrix(): Record<string, unknown> {
|
||||
const root = path.resolve(__dirname, '../../../../');
|
||||
const matrixPath = path.resolve(root, 'config/aggregator-route-matrix.json');
|
||||
const raw = fs.readFileSync(matrixPath, 'utf8');
|
||||
return JSON.parse(raw) as Record<string, unknown>;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const capabilities = getProviderCapabilities(CHAIN_ID);
|
||||
const dodoV3 = capabilities.find((capability) => capability.provider === 'dodo_v3');
|
||||
assert(dodoV3, 'Missing dodo_v3 provider capability for Chain 138.');
|
||||
assert(dodoV3.live === true, 'Expected dodo_v3 capability to be live.');
|
||||
assert(dodoV3.quoteLive === true, 'Expected dodo_v3 quoteLive=true.');
|
||||
assert(dodoV3.executionLive === true, 'Expected dodo_v3 executionLive=true with the live router-v2 D3 adapter.');
|
||||
|
||||
const pair = dodoV3.pairs.find(
|
||||
(entry) =>
|
||||
entry.status === 'live' &&
|
||||
entry.tokenInAddress === WETH10.toLowerCase() &&
|
||||
entry.tokenOutAddress === USDT.toLowerCase()
|
||||
);
|
||||
assert(pair, 'Missing live WETH10 -> USDT dodo_v3 capability.');
|
||||
assert(pair.target === D3_PROXY.toLowerCase(), `Expected D3 proxy target ${D3_PROXY}, got ${pair.target}.`);
|
||||
|
||||
const planner = new BestExecutionPlanner(undefined, new MockPlannerMetricsRepository() as never);
|
||||
const request = {
|
||||
sourceChainId: CHAIN_ID,
|
||||
destinationChainId: CHAIN_ID,
|
||||
tokenIn: WETH10,
|
||||
tokenOut: USDT,
|
||||
amountIn: AMOUNT_IN,
|
||||
};
|
||||
|
||||
const plan = await planner.plan(request);
|
||||
assert(plan.decision === 'direct-pool', `Expected direct-pool decision, got ${plan.decision}.`);
|
||||
assert(plan.legs.length === 1, `Expected 1 planner leg, got ${plan.legs.length}.`);
|
||||
assert(plan.legs[0].provider === 'dodo_v3', `Expected dodo_v3 provider, got ${plan.legs[0].provider}.`);
|
||||
assert(plan.routePlan !== undefined, 'Expected executable routePlan for dodo_v3 pilot routes.');
|
||||
assert(
|
||||
plan.riskFlags.includes('pilot-venue') && !plan.riskFlags.includes('manual-execution-only'),
|
||||
`Expected pilot-only risk flag set, got ${plan.riskFlags.join(', ')}.`
|
||||
);
|
||||
assert(BigInt(plan.estimatedAmountOut) > 0n, 'Expected positive DODO v3 quote output.');
|
||||
|
||||
const executionBuilder = new InternalExecutionPlanV2Builder(planner);
|
||||
const internalExecution = await executionBuilder.build(request);
|
||||
assert(!internalExecution.error, `Expected executable internal plan, got ${internalExecution.error || 'none'}.`);
|
||||
assert(internalExecution.execution?.kind === 'route', `Expected route execution plan, got ${internalExecution.execution?.kind || 'none'}.`);
|
||||
assert(
|
||||
internalExecution.execution?.contractAddress === ROUTER_V2.toLowerCase(),
|
||||
`Expected router-v2 contract ${ROUTER_V2}, got ${internalExecution.execution?.contractAddress || 'none'}.`
|
||||
);
|
||||
assert(
|
||||
internalExecution.execution?.encodedCalldata?.startsWith('0x434180a2'),
|
||||
'Expected executeRoute calldata for dodo_v3 pilot route.'
|
||||
);
|
||||
|
||||
const matrix = loadMatrix();
|
||||
const liveSwapRoutes = Array.isArray(matrix.liveSwapRoutes) ? matrix.liveSwapRoutes : [];
|
||||
const dodoV3Routes = liveSwapRoutes.filter((route) => {
|
||||
if (!route || typeof route !== 'object') return false;
|
||||
const legs = Array.isArray((route as { legs?: unknown[] }).legs) ? (route as { legs: unknown[] }).legs : [];
|
||||
return legs.some((leg) => leg && typeof leg === 'object' && (leg as { protocol?: string }).protocol === 'dodo_v3');
|
||||
}) as Array<Record<string, unknown>>;
|
||||
|
||||
assert(dodoV3Routes.length === 2, `Expected 2 dodo_v3 routes in route matrix, got ${dodoV3Routes.length}.`);
|
||||
for (const route of dodoV3Routes) {
|
||||
const legs = Array.isArray(route.legs) ? route.legs : [];
|
||||
const leg = legs[0] as Record<string, unknown>;
|
||||
assert(leg.poolAddress === D3_POOL.toLowerCase(), `Expected canonical D3 pool ${D3_POOL}, got ${String(leg.poolAddress || '')}.`);
|
||||
assert(leg.executorAddress === D3_PROXY.toLowerCase(), `Expected canonical D3 proxy ${D3_PROXY}, got ${String(leg.executorAddress || '')}.`);
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
verifiedAt: new Date().toISOString(),
|
||||
chainId: CHAIN_ID,
|
||||
capability: {
|
||||
provider: dodoV3.provider,
|
||||
live: dodoV3.live,
|
||||
quoteLive: dodoV3.quoteLive,
|
||||
executionLive: dodoV3.executionLive,
|
||||
},
|
||||
planner: {
|
||||
decision: plan.decision,
|
||||
provider: plan.legs[0].provider,
|
||||
estimatedAmountOut: plan.estimatedAmountOut,
|
||||
riskFlags: plan.riskFlags,
|
||||
routePlanPresent: plan.routePlan !== undefined,
|
||||
},
|
||||
internalExecutionPlan: {
|
||||
kind: internalExecution.execution?.kind,
|
||||
contractAddress: internalExecution.execution?.contractAddress,
|
||||
error: internalExecution.error,
|
||||
},
|
||||
routeMatrix: {
|
||||
dodoV3RouteIds: dodoV3Routes.map((route) => String(route.routeId || '')),
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[fail] ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user