Skip to content

Commit f3be78d

Browse files
committed
reprice sus transfers
1 parent dc39c69 commit f3be78d

1 file changed

Lines changed: 145 additions & 1 deletion

File tree

src/adapters/wormhole/index.ts

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import dayjs from "dayjs";
22
import { queryAllium } from "../../helpers/allium";
3+
import { getSingleLlamaPrice } from "../../utils/prices";
4+
35
type WormholeBridgeEvent = {
46
block_timestamp: string;
57
transaction_hash: string;
@@ -9,7 +11,145 @@ type WormholeBridgeEvent = {
911
token_usd_amount: string;
1012
token_amount: string;
1113
source_chain: string;
14+
source_token_address: string | null;
15+
source_token_amount: string | null;
1216
destination_chain: string;
17+
application_protocol_ids: string[] | string | null;
18+
};
19+
20+
const WORMHOLE_REPRICE_USD_THRESHOLD = Number(process.env.WORMHOLE_REPRICE_USD_THRESHOLD ?? 1_000_000);
21+
22+
const priceChainMapping: Record<string, string> = {
23+
avalanche: "avax",
24+
avax: "avax",
25+
bnb_smart_chain: "bsc",
26+
BNB_Smart_Chain: "bsc",
27+
bsc: "bsc",
28+
ethereum: "ethereum",
29+
polygon: "polygon",
30+
arbitrum: "arbitrum",
31+
optimism: "optimism",
32+
base: "base",
33+
solana: "solana",
34+
fantom: "fantom",
35+
celo: "celo",
36+
mantle: "mantle",
37+
scroll: "scroll",
38+
};
39+
40+
const normalizeProtocolIds = (protocolIds: string[] | string | null | undefined): string[] => {
41+
if (!protocolIds) return [];
42+
if (Array.isArray(protocolIds)) return protocolIds.map(String);
43+
return String(protocolIds)
44+
.replace(/[{}\\[\\]\"]/g, "")
45+
.split(",")
46+
.map((protocolId) => protocolId.trim())
47+
.filter(Boolean);
48+
};
49+
50+
const shouldRepriceWormholeEvent = (event: WormholeBridgeEvent): boolean => {
51+
const tokenUsdAmount = Number(event.token_usd_amount || 0);
52+
if (!Number.isFinite(tokenUsdAmount) || tokenUsdAmount < WORMHOLE_REPRICE_USD_THRESHOLD) return false;
53+
if (!event.source_token_address || !event.source_token_amount) return false;
54+
55+
const protocolIds = normalizeProtocolIds(event.application_protocol_ids);
56+
return protocolIds.includes("MAYAN") && protocolIds.some((protocolId) => protocolId.startsWith("SWIFT"));
57+
};
58+
59+
const getSourcePriceChainAndToken = (event: WormholeBridgeEvent) => {
60+
const sourceChain = event.source_chain;
61+
const priceChain = priceChainMapping[sourceChain] || priceChainMapping[sourceChain?.toLowerCase?.()];
62+
if (!priceChain || !event.source_token_address) return null;
63+
64+
const token = priceChain === "solana" ? event.source_token_address : event.source_token_address.toLowerCase();
65+
66+
return { priceChain, token };
67+
};
68+
69+
const getRepricedUsdAmount = async (
70+
event: WormholeBridgeEvent,
71+
priceCache: Map<string, Promise<any>>
72+
): Promise<number | null> => {
73+
const sourceTokenAmount = Number(event.source_token_amount || 0);
74+
if (!Number.isFinite(sourceTokenAmount) || sourceTokenAmount <= 0) return null;
75+
76+
const priceKey = getSourcePriceChainAndToken(event);
77+
if (!priceKey) return null;
78+
79+
const hourTimestamp = Math.floor(Number(event.block_timestamp) / 3600) * 3600;
80+
const cacheKey = `${priceKey.priceChain}:${priceKey.token}:${hourTimestamp}`;
81+
if (!priceCache.has(cacheKey)) {
82+
priceCache.set(cacheKey, getSingleLlamaPrice(priceKey.priceChain, priceKey.token, hourTimestamp));
83+
}
84+
85+
const priceData = await priceCache.get(cacheKey);
86+
const price = Number(priceData?.price);
87+
if (!Number.isFinite(price) || price <= 0) return null;
88+
89+
const alliumUsdAmount = Number(event.token_usd_amount || 0);
90+
const directUsdAmount = sourceTokenAmount * price;
91+
const decimals = Number(priceData?.decimals);
92+
93+
if (Number.isFinite(decimals) && decimals >= 0 && directUsdAmount > alliumUsdAmount * 100) {
94+
const decimalAdjustedUsdAmount = (sourceTokenAmount / 10 ** decimals) * price;
95+
if (Number.isFinite(decimalAdjustedUsdAmount) && decimalAdjustedUsdAmount <= alliumUsdAmount * 10) {
96+
return decimalAdjustedUsdAmount;
97+
}
98+
}
99+
100+
return directUsdAmount;
101+
};
102+
103+
const repriceSuspiciousWormholeEvents = async (events: WormholeBridgeEvent[]): Promise<WormholeBridgeEvent[]> => {
104+
const priceCache = new Map<string, Promise<any>>();
105+
const safeEvents = [] as WormholeBridgeEvent[];
106+
let corrected = 0;
107+
let skipped = 0;
108+
109+
for (const event of events) {
110+
if (!shouldRepriceWormholeEvent(event)) {
111+
safeEvents.push(event);
112+
continue;
113+
}
114+
115+
const originalUsdAmount = Number(event.token_usd_amount || 0);
116+
let repricedUsdAmount: number | null = null;
117+
try {
118+
repricedUsdAmount = await getRepricedUsdAmount(event, priceCache);
119+
} catch (e: any) {
120+
console.warn(
121+
`[Wormhole] Skipping suspicious event ${event.transaction_hash}; price lookup failed for ` +
122+
`${event.source_chain}:${event.source_token_address}: ${e?.message}`
123+
);
124+
}
125+
126+
if (repricedUsdAmount === null || !Number.isFinite(repricedUsdAmount)) {
127+
skipped++;
128+
console.warn(
129+
`[Wormhole] Skipping suspicious event ${event.transaction_hash}; no DefiLlama price for ` +
130+
`${event.source_chain}:${event.source_token_address}`
131+
);
132+
continue;
133+
}
134+
135+
corrected++;
136+
safeEvents.push({
137+
...event,
138+
token_usd_amount: String(repricedUsdAmount),
139+
});
140+
141+
if (originalUsdAmount / Math.max(repricedUsdAmount, 1) > 100) {
142+
console.warn(
143+
`[Wormhole] Repriced suspicious event ${event.transaction_hash} from ${originalUsdAmount} to ${repricedUsdAmount}`
144+
);
145+
}
146+
}
147+
148+
if (corrected || skipped) {
149+
console.log(`[Wormhole] Repriced ${corrected} suspicious events; skipped ${skipped} without prices.`);
150+
}
151+
152+
return safeEvents;
13153
};
14154

15155
const chains = [
@@ -110,7 +250,10 @@ export const fetchWormholeEvents = async (
110250
TOKEN_USD_AMOUNT,
111251
TOKEN_AMOUNT,
112252
SOURCE_CHAIN,
253+
SOURCE_TOKEN_ADDRESS,
254+
SOURCE_TOKEN_AMOUNT,
113255
DESTINATION_CHAIN,
256+
APPLICATION_PROTOCOL_IDS,
114257
UNIQUE_ID AS transaction_hash
115258
from org_db__defillama.default.wormhole_token_transfers
116259
where
@@ -127,8 +270,9 @@ export const fetchWormholeEvents = async (
127270
token_usd_amount: String(parseFloat(row.token_usd_amount || "0") || 0),
128271
block_timestamp: dayjs(row.block_timestamp).unix(),
129272
}));
273+
const repricedBatch = await repriceSuspiciousWormholeEvents(normalizedBatch);
130274

131-
allResults = [...allResults, ...normalizedBatch];
275+
allResults = [...allResults, ...repricedBatch];
132276
console.log(`Fetched ${allResults.length} Wormhole events.`);
133277

134278
currentTimestamp = normalizedBatch[normalizedBatch.length - 1].block_timestamp + 1;

0 commit comments

Comments
 (0)