Skip to content

Commit 45ebe2e

Browse files
committed
cloud_functions: add alarmFastTransfer
1 parent 4b033ba commit 45ebe2e

File tree

2 files changed

+140
-0
lines changed

2 files changed

+140
-0
lines changed

cloud_functions/scripts/deploy.sh

+11
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,16 @@ if [ -z "$PG_HOST" ] || [ "$PG_HOST" == "localhost" ] || [ "$PG_HOST" == "127.0.
177177
exit 1
178178
fi
179179

180+
if [ -z "$PG_FT_DATABASE" ]; then
181+
echo "PG_FT_DATABASE must be specified"
182+
exit 1
183+
fi
184+
185+
if [ -z "$FT_MISSING_VAA_SLACK_CHANNEL_ID" ]; then
186+
echo "FT_MISSING_VAA_SLACK_CHANNEL_ID must be specified"
187+
exit 1
188+
fi
189+
180190
if [ -z "$PG_TOKEN_TRANSFER_TABLE" ]; then
181191
echo "PG_TOKEN_TRANSFER_TABLE must be specified"
182192
exit 1
@@ -232,6 +242,7 @@ if [ -z "$SOLANA_RPC" ]; then
232242
exit 1
233243
fi
234244

245+
gcloud functions --project "$GCP_PROJECT" deploy alarm-fast-transfer --entry-point alarmFastTransfer --gen2 --runtime nodejs22 --trigger-http --allow-unauthenticated --timeout 300 --memory 512MB --region europe-west3 --set-env-vars FT_MISSING_VAA_SLACK_CHANNEL_ID=$FT_MISSING_VAA_SLACK_CHANNEL_ID,MISSING_VAA_SLACK_POST_URL=$MISSING_VAA_SLACK_POST_URL,MISSING_VAA_SLACK_BOT_TOKEN=$MISSING_VAA_SLACK_BOT_TOKEN,PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_FT_DATABASE=$PG_FT_DATABASE,PG_HOST=$PG_HOST,NETWORK=$NETWORK,FUNCTION=alarmFastTransfer
235246
gcloud functions --project "$GCP_PROJECT" deploy compute-guardian-set-info --entry-point computeGuardianSetInfo --gen2 --runtime nodejs22 --trigger-http --allow-unauthenticated --timeout 300 --memory 512MB --region europe-west3 --set-env-vars NETWORK=$NETWORK,FIRESTORE_GUARDIAN_SET_INFO_COLLECTION=$FIRESTORE_GUARDIAN_SET_INFO_COLLECTION,FUNCTION=computeGuardianSetInfo
236247
gcloud functions --project "$GCP_PROJECT" deploy compute-tvl --entry-point computeTVL --gen2 --runtime nodejs22 --trigger-http --no-allow-unauthenticated --timeout 300 --memory 1GB --region europe-west3 --set-env-vars PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_DATABASE=$PG_DATABASE,PG_HOST=$PG_HOST,PG_ATTEST_MESSAGE_TABLE=$PG_ATTEST_MESSAGE_TABLE,PG_TOKEN_METADATA_TABLE=$PG_TOKEN_METADATA_TABLE,PG_TOKEN_TRANSFER_TABLE=$PG_TOKEN_TRANSFER_TABLE,FIRESTORE_TVL_COLLECTION=$FIRESTORE_TVL_COLLECTION,NETWORK=$NETWORK,FUNCTION=computeTVL
237248
gcloud functions --project "$GCP_PROJECT" deploy compute-tvl-history --entry-point computeTVLHistory --gen2 --runtime nodejs22 --trigger-http --no-allow-unauthenticated --timeout 540 --memory 1GB --region europe-west3 --set-env-vars PG_USER=$PG_USER,PG_PASSWORD=$PG_PASSWORD,PG_DATABASE=$PG_DATABASE,PG_HOST=$PG_HOST,PG_ATTEST_MESSAGE_TABLE=$PG_ATTEST_MESSAGE_TABLE,PG_TOKEN_METADATA_TABLE=$PG_TOKEN_METADATA_TABLE,PG_TOKEN_TRANSFER_TABLE=$PG_TOKEN_TRANSFER_TABLE,FIRESTORE_TVL_HISTORY_COLLECTION=$FIRESTORE_TVL_HISTORY_COLLECTION,PG_TOKEN_PRICE_HISTORY_TABLE=$PG_TOKEN_PRICE_HISTORY_TABLE,NETWORK=$NETWORK,FUNCTION=computeTVLHistory
+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import {
2+
assertEnvironmentVariable,
3+
formatAndSendToSlack,
4+
SlackInfo,
5+
} from '@wormhole-foundation/wormhole-monitor-common';
6+
import knex, { Knex } from 'knex';
7+
8+
let alarmSlackInfo: SlackInfo;
9+
let initialized = false;
10+
let pg: Knex;
11+
12+
function initialize() {
13+
pg = knex({
14+
client: 'pg',
15+
connection: {
16+
user: assertEnvironmentVariable('PG_USER'),
17+
password: assertEnvironmentVariable('PG_PASSWORD'),
18+
database: assertEnvironmentVariable('PG_FT_DATABASE'),
19+
host: assertEnvironmentVariable('PG_HOST'),
20+
},
21+
});
22+
console.log(`database = ${assertEnvironmentVariable('PG_FT_DATABASE')}`);
23+
24+
alarmSlackInfo = {
25+
channelId: assertEnvironmentVariable('FT_MISSING_VAA_SLACK_CHANNEL_ID'),
26+
postUrl: assertEnvironmentVariable('MISSING_VAA_SLACK_POST_URL'),
27+
botToken: assertEnvironmentVariable('MISSING_VAA_SLACK_BOT_TOKEN'),
28+
bannerTxt: 'Wormhole Fast Transfer Alarm',
29+
msg: '',
30+
};
31+
console.log(`channelId = ${assertEnvironmentVariable('FT_MISSING_VAA_SLACK_CHANNEL_ID')}`);
32+
console.log('initialized global variables');
33+
initialized = true;
34+
}
35+
36+
export async function alarmFastTransfer(req: any, res: any) {
37+
res.set('Access-Control-Allow-Origin', '*');
38+
if (req.method === 'OPTIONS') {
39+
// Send response to OPTIONS requests
40+
res.set('Access-Control-Allow-Methods', 'GET');
41+
res.set('Access-Control-Allow-Headers', 'Content-Type');
42+
res.set('Access-Control-Max-Age', '3600');
43+
res.status(204).send('');
44+
return;
45+
}
46+
47+
try {
48+
if (!initialized) {
49+
initialize();
50+
}
51+
52+
// Get the last 30 minutes of delayed orders.
53+
// In this case, delayed means the execution time is greater than 20 seconds.
54+
// This cloud function is scheduled to run every 30 minutes.
55+
const alertOrders: DisplayRow[] = await getDelayedOrders();
56+
for (const order of alertOrders) {
57+
const formattedAmountIn = formatBigInt(order.amountIn);
58+
const formattedAmountOut = formatBigInt(order.amountOut);
59+
alarmSlackInfo.msg =
60+
`🚨 Delayed Order Alert!\n` +
61+
`Source Chain: ${order.sourceChain}\n` +
62+
`Sequence: ${order.sequence}\n` +
63+
`Status: ${order.status}\n` +
64+
`Order Timestamp: ${order.market_order_timestamp.toISOString()}\n` +
65+
`Destination Chain: ${order.destinationChain}\n` +
66+
`Execution Time: ${order.executionTime} Sec\n` +
67+
`Amount In: ${formattedAmountIn}\n` +
68+
`Amount Out: ${formattedAmountOut}`;
69+
console.log(alarmSlackInfo.msg);
70+
await formatAndSendToSlack(alarmSlackInfo);
71+
}
72+
} catch (e) {
73+
console.error(e);
74+
res.sendStatus(500);
75+
}
76+
res.status(200).send('successfully alarmed delayed fast transfers');
77+
return;
78+
}
79+
80+
// Format the amount in bigint to a string with 2 decimal places.
81+
// We know that the bigint input is fixed 6 decimal places and we only need to show 2 decimal places.
82+
const formatBigInt = (amt: bigint) => {
83+
const str = (amt / 10_000n).toString().padStart(3, '0');
84+
return str.substring(0, str.length - 2) + '.' + str.substring(str.length - 2);
85+
};
86+
87+
async function getDelayedOrders(): Promise<DisplayRow[]> {
88+
console.log('getDelayedOrders');
89+
const result = await pg
90+
.select(
91+
'mo.fast_vaa_id',
92+
'mo.status',
93+
'mo.market_order_timestamp',
94+
'mo.dst_chain AS destinationChain',
95+
pg.raw(
96+
'EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) AS "executionTime"'
97+
),
98+
'mo.amount_in AS amountIn',
99+
'fte.user_amount'
100+
)
101+
.from('market_orders AS mo')
102+
.join('fast_transfer_executions AS fte', 'mo.fast_vaa_hash', '=', 'fte.fast_vaa_hash')
103+
.where('mo.market_order_timestamp', '>=', pg.raw("NOW() - INTERVAL '30 MINUTES'")) // Get orders from last 30 minutes
104+
.andWhereRaw('EXTRACT(EPOCH FROM (fte.execution_time - mo.market_order_timestamp)) > 20')
105+
.orderBy('mo.market_order_timestamp', 'desc');
106+
107+
console.log('result', result);
108+
return result.map((row) => ({
109+
sourceChain: row.fast_vaa_id.split('/')[0],
110+
sequence: row.fast_vaa_id.split('/')[2],
111+
status: row.status,
112+
market_order_timestamp: row.market_order_timestamp,
113+
destinationChain: row.destinationChain,
114+
executionTime: row.executionTime,
115+
amountIn: BigInt(row.amountIn),
116+
amountOut: BigInt(row.user_amount),
117+
}));
118+
}
119+
120+
type DisplayRow = {
121+
sourceChain: number; // from fast_vaa_id
122+
sequence: bigint; // from fast_vaa_id
123+
status: string; // from MarketOrder
124+
market_order_timestamp: Date; // from MarketOrder
125+
destinationChain: number; // from MarketOrder
126+
executionTime: number; // execution_time - market_order_timestamp
127+
amountIn: bigint; // from MarketOrder
128+
amountOut: bigint; // from FastTransferExecutions
129+
};

0 commit comments

Comments
 (0)