Skip to content

Commit 335c7b0

Browse files
committed
rebase to master
1 parent 0b7e206 commit 335c7b0

File tree

4 files changed

+271
-0
lines changed

4 files changed

+271
-0
lines changed

src/adapters/ccip/index.ts

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
export type CCIPEvent = {
2+
chain: string;
3+
tx_hash: string;
4+
ts: number;
5+
tx_from: string;
6+
tx_to: string;
7+
token: string;
8+
amount: string;
9+
is_deposit: boolean;
10+
is_usd_volume: boolean; // Per requirements, always false for CCIP
11+
};
12+
13+
interface CCIPTransaction {
14+
sourceChain: string;
15+
destChain: string;
16+
sourceTxHash: string;
17+
destTxHash: string;
18+
blockTimestamp: number; // In seconds from API
19+
messageID: string;
20+
tokenTransferFrom: string;
21+
tokenTransferTo: string;
22+
tokenAddressSource: string;
23+
tokenAddressDest: string;
24+
tokenAmount: number; // Number from API, converted to string for CCIPEvent
25+
}
26+
27+
interface ApiResponse {
28+
transactions: CCIPTransaction[];
29+
}
30+
31+
// --- Constants ---
32+
const API_BASE_URL = "https://dsa-metrics-api-dev-81875351881.europe-west2.run.app/v1/ccip_transactions";
33+
const API_KEY = ""; // API Key, currently an empty string
34+
35+
// --- Main function ---
36+
export async function fetchCCIPEvents(dateString: string): Promise<CCIPEvent[]> {
37+
console.log(`[fetchCCIPEvents] Starting for date: ${dateString}`);
38+
39+
// Basic validation for dateString format (optional but good practice)
40+
if (!/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
41+
const errorMsg = `[fetchCCIPEvents] Invalid date format: "${dateString}". Expected YYYY-MM-DD.`;
42+
console.error(errorMsg);
43+
throw new Error(errorMsg);
44+
}
45+
46+
const apiUrlWithDate = `${API_BASE_URL}?date=${dateString}`;
47+
console.log(`[fetchCCIPEvents] Constructed API URL: ${apiUrlWithDate}`);
48+
49+
const allEvents: CCIPEvent[] = [];
50+
51+
try {
52+
console.log(`[fetchCCIPEvents] Making API request to ${apiUrlWithDate}...`);
53+
const response = await fetch(apiUrlWithDate, {
54+
method: 'GET',
55+
headers: {
56+
'X-API-Key': API_KEY,
57+
'Accept': 'application/json',
58+
},
59+
});
60+
61+
console.log(`[fetchCCIPEvents] API response status: ${response.status}`);
62+
63+
if (!response.ok) {
64+
const errorBody = await response.text();
65+
const errorMsg = `[fetchCCIPEvents] API Error ${response.status}: ${response.statusText}. URL: ${apiUrlWithDate}. Body: ${errorBody}`;
66+
console.error(errorMsg);
67+
throw new Error(`Failed to fetch CCIP events: ${response.status} ${response.statusText}`);
68+
}
69+
70+
const apiData: ApiResponse = await response.json();
71+
console.log("[fetchCCIPEvents] Successfully parsed API response.");
72+
73+
const rawTransactions = apiData?.transactions;
74+
if (!Array.isArray(rawTransactions)) {
75+
const errorMsg = `[fetchCCIPEvents] Invalid data format: 'transactions' array not found or not an array. URL: ${apiUrlWithDate}. Response: ${JSON.stringify(apiData)}`;
76+
console.error(errorMsg);
77+
throw new Error(errorMsg);
78+
}
79+
console.log(`[fetchCCIPEvents] Received ${rawTransactions.length} raw transactions from API.`);
80+
81+
let processedCount = 0;
82+
for (const transaction of rawTransactions) {
83+
console.log(`[fetchCCIPEvents] Processing raw transaction with messageID: ${transaction.messageID}`);
84+
if (
85+
!transaction.sourceChain || !transaction.destChain ||
86+
!transaction.sourceTxHash || !transaction.destTxHash ||
87+
typeof transaction.blockTimestamp !== 'number' ||
88+
!transaction.tokenTransferFrom || !transaction.tokenTransferTo ||
89+
!transaction.tokenAddressSource || !transaction.tokenAddressDest ||
90+
typeof transaction.tokenAmount !== 'number'
91+
) {
92+
console.warn("[fetchCCIPEvents] Skipping incomplete raw transaction data:", transaction);
93+
continue;
94+
}
95+
96+
const timestampMs = transaction.blockTimestamp * 1000;
97+
const amountStr = String(transaction.tokenAmount);
98+
99+
const withdrawalEvent: CCIPEvent = {
100+
chain: transaction.sourceChain,
101+
tx_hash: transaction.sourceTxHash,
102+
ts: timestampMs,
103+
tx_from: transaction.tokenTransferFrom,
104+
tx_to: transaction.tokenTransferTo,
105+
token: transaction.tokenAddressSource,
106+
amount: amountStr,
107+
is_deposit: false,
108+
is_usd_volume: false,
109+
};
110+
allEvents.push(withdrawalEvent);
111+
112+
const depositEvent: CCIPEvent = {
113+
chain: transaction.destChain,
114+
tx_hash: transaction.destTxHash,
115+
ts: timestampMs,
116+
tx_from: transaction.tokenTransferFrom,
117+
tx_to: transaction.tokenTransferTo,
118+
token: transaction.tokenAddressDest,
119+
amount: amountStr,
120+
is_deposit: true,
121+
is_usd_volume: false,
122+
};
123+
allEvents.push(depositEvent);
124+
processedCount++;
125+
}
126+
console.log(`[fetchCCIPEvents] Successfully processed ${processedCount} raw transactions, created ${allEvents.length} CCIPEvent objects.`);
127+
128+
} catch (error) {
129+
// Ensure error is an instance of Error for consistent message access
130+
const errorMessage = error instanceof Error ? error.message : String(error);
131+
console.error(`[fetchCCIPEvents] Error during processing for date ${dateString}: ${errorMessage}`, error);
132+
throw error; // Re-throw the original error or a new error encapsulating it
133+
}
134+
135+
console.log(`[fetchCCIPEvents] Finished for date: ${dateString}. Returning ${allEvents.length} events.`);
136+
return allEvents;
137+
}

src/adapters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ import fuel from "./fuel";
8787
import lighter from "./lighter";
8888
import movement from "./movement";
8989
import intersoon from "./intersoon";
90+
import ccip from "./ccip";
9091

9192
export default {
9293
polygon,

src/data/bridgeNetworkData.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2158,4 +2158,13 @@ export default [
21582158
"Ink",
21592159
],
21602160
},
2161+
{
2162+
id: 92,
2163+
displayName: "Chainlink CCIP",
2164+
bridgeDbName: "ccip",
2165+
iconLink: "icons:ccip",
2166+
largeTxThreshold: 10000,
2167+
url: "https://ccip.chain.link/",
2168+
chains: ["Ethereum"],
2169+
},
21612170
] as BridgeNetwork[];

src/handlers/runCCIP.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// runCCIP.ts
2+
3+
import dayjs from 'dayjs';
4+
import _ from 'lodash'; // For batching transactions
5+
import { fetchCCIPEvents, CCIPEvent } from '../adapters/ccip';
6+
import { sql } from '../utils/db';
7+
import { insertTransactionRows } from '../utils/wrappa/postgres/write';
8+
import { getBridgeID } from '../utils/wrappa/postgres/query';
9+
// import { wrapScheduledLambda } from "../utils/wrap";
10+
11+
// --- Define TransactionRow interface locally ---
12+
interface TransactionRow {
13+
bridge_id: string;
14+
chain: string;
15+
tx_hash: string;
16+
ts: number; // Timestamp in milliseconds
17+
tx_from: string;
18+
tx_to: string;
19+
token: string; // Token address
20+
amount: string; // Amount as a string
21+
is_deposit: boolean;
22+
is_usd_volume: boolean;
23+
}
24+
25+
// --- Configuration ---
26+
const ADAPTER_NAME = "ccip";
27+
const DEFAULT_CHAIN_FOR_BRIDGE_ID = "Ethereum"; // Chain to use for fetching the global bridge_id
28+
const DAYS_TO_PROCESS = 3;
29+
const DB_BATCH_SIZE = 200;
30+
31+
/**
32+
* Formats a dayjs.Dayjs object into 'YYYY-MM-DD' string.
33+
*/
34+
const formatDateToYYYYMMDD = (date: dayjs.Dayjs): string => date.format('YYYY-MM-DD');
35+
36+
/**
37+
* Main handler function to fetch CCIP events for recent days and store them.
38+
*/
39+
export const handler = async (): Promise<void> => {
40+
console.log(`[runCCIP] Starting CCIP event processing task.`);
41+
42+
let globalBridgeId: string | null = null;
43+
44+
try {
45+
console.log(`[runCCIP] Fetching global bridge_id for adapter "${ADAPTER_NAME}" using chain "${DEFAULT_CHAIN_FOR_BRIDGE_ID}"...`);
46+
const bridgeEntry = await getBridgeID(ADAPTER_NAME, DEFAULT_CHAIN_FOR_BRIDGE_ID);
47+
globalBridgeId = bridgeEntry ? bridgeEntry.id : null;
48+
49+
if (!globalBridgeId) {
50+
console.error(`[runCCIP] CRITICAL: Failed to fetch global bridge_id using chain "${DEFAULT_CHAIN_FOR_BRIDGE_ID}". Terminating task.`);
51+
return; // Stop processing if the global bridge_id cannot be determined
52+
}
53+
console.log(`[runCCIP] Using global bridge_id: "${globalBridgeId}" for all CCIP events.`);
54+
} catch (err) {
55+
console.error(`[runCCIP] CRITICAL: Error fetching global bridge_id:`, err);
56+
return; // Stop processing on error
57+
}
58+
59+
for (let i = 0; i < DAYS_TO_PROCESS; i++) {
60+
const targetDate = dayjs().subtract(i + 1, 'day');
61+
const dateString = formatDateToYYYYMMDD(targetDate);
62+
63+
console.log(`[runCCIP] Starting processing for date: ${dateString}`);
64+
65+
try {
66+
const ccipEvents: CCIPEvent[] = await fetchCCIPEvents(dateString);
67+
68+
if (ccipEvents.length === 0) {
69+
console.log(`[runCCIP] No CCIP events found for ${dateString}.`);
70+
continue;
71+
}
72+
console.log(`[runCCIP] Fetched ${ccipEvents.length} CCIP events for ${dateString}.`);
73+
74+
// Step 2: Transform CCIPEvents to TransactionRows using the global bridge_id
75+
const transactionsToInsert: TransactionRow[] = ccipEvents.map(event => {
76+
return {
77+
...event, // Spreads all properties from CCIPEvent
78+
bridge_id: globalBridgeId, // Use the fetched global bridge_id
79+
};
80+
});
81+
82+
if (transactionsToInsert.length > 0) {
83+
console.log(`[runCCIP] Prepared ${transactionsToInsert.length} transaction rows for insertion for date ${dateString}.`);
84+
85+
await sql.begin(async (sqlClient) => {
86+
const transactionChunks = _.chunk(transactionsToInsert, DB_BATCH_SIZE);
87+
let chunkCount = 0;
88+
for (const batch of transactionChunks) {
89+
chunkCount++;
90+
console.log(`[runCCIP] Inserting batch ${chunkCount}/${transactionChunks.length} (${batch.length} rows) for ${dateString}...`);
91+
await insertTransactionRows(sqlClient, true, batch, "upsert");
92+
}
93+
});
94+
console.log(`[runCCIP] Successfully inserted/upserted ${transactionsToInsert.length} transaction rows for ${dateString}.`);
95+
} else {
96+
// This should ideally not be reached if ccipEvents.length > 0
97+
console.log(`[runCCIP] No transactions to insert for ${dateString} after processing.`);
98+
}
99+
100+
} catch (error) {
101+
const errorMessage = error instanceof Error ? error.message : String(error);
102+
console.error(`[runCCIP] Critical error processing data for ${dateString}: ${errorMessage}`, error);
103+
}
104+
console.log(`[runCCIP] Finished processing for date: ${dateString}.`);
105+
}
106+
console.log(`[runCCIP] CCIP event processing task completed for all ${DAYS_TO_PROCESS} day(s).`);
107+
};
108+
109+
// --- Example of how to run the handler (for local testing) ---
110+
// async function runLocalTest() {
111+
// console.log("[runCCIP - LocalTest] Starting local test run...");
112+
// try {
113+
// await handler();
114+
// console.log("[runCCIP - LocalTest] Local test run finished successfully.");
115+
// } catch (error) {
116+
// console.error("[runCCIP - LocalTest] Local test run failed:", error);
117+
// }
118+
// }
119+
120+
// If you want to run this script directly (e.g., `ts-node runCCIP.ts`):
121+
// runLocalTest();
122+
123+
// If this is a scheduled AWS Lambda, you might wrap it:
124+
// export default wrapScheduledLambda(handler);

0 commit comments

Comments
 (0)