Skip to content

Commit 445fe3e

Browse files
committed
ft_watcher: check for multiple ixs in tx and inline deconstructor
Signed-off-by: bingyuyap <bingyu.yap.21@gmail.com>
1 parent af5e302 commit 445fe3e

File tree

3 files changed

+57
-49
lines changed

3 files changed

+57
-49
lines changed

database/fast-transfer-schema.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ CREATE TABLE fast_transfer_executions (
6060
execution_slot BIGINT,
6161
execution_time TIMESTAMP,
6262
-- fill_id can be a vaa id (cctp) or solana account pubkey (local)
63-
fill_id VARCHAR(255),
63+
fill_id VARCHAR(255)
6464
);
6565

6666
-- Settlement is created when the settlement is created in the `settleFastTransfer`

watcher/src/fastTransfer/swapLayer/solParser.ts

+42-41
Original file line numberDiff line numberDiff line change
@@ -51,56 +51,60 @@ export class SwapLayerParser {
5151
*/
5252
private async processTransaction(
5353
transaction: VersionedTransactionResponse
54-
): Promise<TransferCompletion | null> {
54+
): Promise<TransferCompletion[]> {
5555
const sig = transaction.transaction.signatures[0];
5656
const programInstructions = this.getProgramInstructions(transaction);
5757

58-
for (const { ix } of programInstructions) {
59-
const decoded = this.swapLayerBorshCoder.instruction.decode(Buffer.from(ix.data));
60-
if (!decoded) continue;
61-
62-
try {
63-
switch (decoded.name) {
64-
case 'complete_swap_direct':
65-
case 'complete_swap_relay':
66-
case 'complete_swap_payload':
67-
return await this.parseSwapInstruction(transaction, ix, decoded.name);
68-
69-
case 'complete_transfer_direct':
70-
case 'complete_transfer_relay':
71-
case 'complete_transfer_payload':
72-
return await this.parseTransferInstruction(transaction, ix, decoded.name);
73-
74-
case 'release_inbound':
75-
return await this.parseReleaseInbound(transaction, ix, decoded.name);
76-
77-
default:
78-
// we will not log when there are unknown instructions to prevent log congestion
79-
continue;
58+
const results = await Promise.all(
59+
programInstructions.map(async ({ ix }) => {
60+
const decoded = this.swapLayerBorshCoder.instruction.decode(Buffer.from(ix.data));
61+
if (!decoded) return null;
62+
63+
try {
64+
switch (decoded.name) {
65+
case 'complete_swap_direct':
66+
case 'complete_swap_relay':
67+
case 'complete_swap_payload':
68+
return await this.parseSwapInstruction(transaction, ix, decoded.name);
69+
70+
case 'complete_transfer_direct':
71+
case 'complete_transfer_relay':
72+
case 'complete_transfer_payload':
73+
return await this.parseTransferInstruction(transaction, ix, decoded.name);
74+
75+
case 'release_inbound':
76+
return await this.parseReleaseInbound(transaction, ix, decoded.name);
77+
78+
default:
79+
// Skip unknown instructions
80+
// we will not log when there are unknown instructions to prevent log congestion
81+
return null;
82+
}
83+
} catch (error) {
84+
console.error(`Error processing ${decoded.name} in transaction ${sig}:`, error);
85+
// Continue to the next instruction if there's an error
86+
return null;
8087
}
81-
} catch (error) {
82-
console.error(`Error processing ${decoded.name} in transaction ${sig}:`, error);
83-
// Continue to the next instruction if there's an error
84-
continue;
85-
}
86-
}
88+
})
89+
);
8790

88-
return null;
91+
// Filter out any null results
92+
return results.filter((result): result is TransferCompletion => result !== null);
8993
}
9094

9195
/**
9296
* Fetches and processes a single transaction by its signature. This is only used for testing for now.
9397
*
9498
* @param signature - The signature of the transaction to fetch and process.
9599
*
96-
* @returns A `TransferCompletion` object containing parsed details from the transaction,
97-
* or `null` if no relevant instructions were found.
100+
* @returns An array of `TransferCompletion` objects containing parsed details from the transaction.
101+
* If no relevant instructions were found, an empty array is returned.
98102
*/
99-
async parseTransaction(signature: string): Promise<TransferCompletion | null> {
103+
async parseTransaction(signature: string): Promise<TransferCompletion[]> {
100104
const transaction = await this.connection.getTransaction(signature, {
101105
maxSupportedTransactionVersion: 0,
102106
});
103-
if (!transaction) return null;
107+
if (!transaction) return [];
104108

105109
return this.processTransaction(transaction);
106110
}
@@ -123,15 +127,14 @@ export class SwapLayerParser {
123127
(tx): tx is VersionedTransactionResponse => tx !== null
124128
);
125129

126-
// Process each transaction and filter out null results
130+
// Process each transaction and gather the results
127131
const promises = nonNullTransactions.map(async (tx) => await this.processTransaction(tx));
128132

129133
const results = await Promise.all(promises);
130134

131-
// Filter out null results from the processed transactions
132-
return results.filter((res): res is TransferCompletion => res !== null);
135+
// Flatten the array and filter out any null values
136+
return results.flat().filter((res): res is TransferCompletion => res !== null);
133137
}
134-
135138
// === parsing logic ===
136139

137140
/**
@@ -223,9 +226,7 @@ export class SwapLayerParser {
223226
throw new Error(`Transaction block time not found: ${sig}`);
224227
}
225228

226-
const instructionConfig = this.getInstructionConfig(instructionName);
227-
228-
const { fillAccountIndex, recipientIndex } = instructionConfig;
229+
const { fillAccountIndex, recipientIndex } = this.getInstructionConfig(instructionName);
229230

230231
if (ix.accountKeyIndexes.length <= recipientIndex) {
231232
throw new Error(`${INSUFFICIENT_ACCOUNTS} for ${instructionName} in ${sig}`);

watcher/src/watchers/__tests__/fastTransfer/SolanaSwapLayerParser.test.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ describe('SwapLayerParser', () => {
156156
const txHash =
157157
'32goGrEsPb6Kky65Z4wX6wswzjDbT9pBWs1HSZFsfWhxoA1fnSsoE9hJgtepPL8VyKQJUdRrfGWPrXCizDufArwR';
158158
const result = await parser.parseTransaction(txHash);
159+
expect(result.length).toBe(1);
159160
const expected = {
160161
fill_id: 'BkWHY4H2kEVevdUeiRmFYNtg5zURRbTEtjt29KWdbjzV',
161162
output_token: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
@@ -168,7 +169,7 @@ describe('SwapLayerParser', () => {
168169
relaying_fee: '0',
169170
};
170171

171-
const serializedRes = seralizedRes(result);
172+
const serializedRes = seralizedRes(result[0]);
172173
expect(serializedRes).toEqual(expected);
173174
});
174175

@@ -177,6 +178,7 @@ describe('SwapLayerParser', () => {
177178
'4EWH6ZetTTjdYSbxqXddKNKLKDpBctELAhqChmkey2jwunZaj1Digj1fQxBMxtw6uhDeqkX3ev2vucu7jrexhWka';
178179

179180
const result = await parser.parseTransaction(txHash);
181+
expect(result.length).toBe(1);
180182

181183
const expected = {
182184
recipient: 'FQ4PBuykgHqemPhqqktJL9y1L7oTbShYiwGkwgM1VceF',
@@ -190,7 +192,7 @@ describe('SwapLayerParser', () => {
190192
staged_inbound: undefined,
191193
};
192194

193-
const serializedRes = seralizedRes(result);
195+
const serializedRes = seralizedRes(result[0]);
194196
expect(serializedRes).toEqual(expected);
195197
});
196198

@@ -199,6 +201,7 @@ describe('SwapLayerParser', () => {
199201
'3Ufce773W4xgVsZiGBhSRPQssfaNdrEWeTBPLTnQSFZHsVx9ADaSN9yQBF6kcQMyDAoAnM3BVU88tQ2TbDZn1kUJ';
200202

201203
const result = await parser.parseTransaction(txHash);
204+
expect(result.length).toBe(1);
202205
const expected = {
203206
recipient: 'GcppBeM1UYGU4b7aX9uPAqL4ZEUThNHt5FpxPtzBE1xx',
204207
tx_hash:
@@ -210,7 +213,7 @@ describe('SwapLayerParser', () => {
210213
output_amount: '49564106',
211214
staged_inbound: undefined,
212215
};
213-
const serializedRes = seralizedRes(result);
216+
const serializedRes = seralizedRes(result[0]);
214217
expect(serializedRes).toEqual(expected);
215218
});
216219

@@ -219,6 +222,7 @@ describe('SwapLayerParser', () => {
219222
'39K8aHVDmyAjne6J4PBFkvmKZH9CQR9QpbmTFafeiTLxeWg5n5RgcRdX5AYhebLR9shiUHrDeqg4YSD1EhRZNpS1';
220223

221224
const result = await parser.parseTransaction(txHash);
225+
expect(result.length).toBe(1);
222226
const expected = {
223227
fill_id: 'Hru6CBfyXtG18zF33DnXEjmECjgj1eMjNfPRaESBqpUr',
224228
output_token: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
@@ -230,7 +234,7 @@ describe('SwapLayerParser', () => {
230234
'39K8aHVDmyAjne6J4PBFkvmKZH9CQR9QpbmTFafeiTLxeWg5n5RgcRdX5AYhebLR9shiUHrDeqg4YSD1EhRZNpS1',
231235
relaying_fee: '0',
232236
};
233-
const serializedRes = seralizedRes(result);
237+
const serializedRes = seralizedRes(result[0]);
234238
expect(serializedRes).toEqual(expected);
235239
});
236240

@@ -239,6 +243,7 @@ describe('SwapLayerParser', () => {
239243
'eo2CugBsJ9Efbtg9TAiYyBvvZZsbh93ZZcLDxxjbmbEpZojCF8BDphVVrCjXtMkSLaP2EGQE5zSrjU4r6fxsxRP';
240244

241245
const result = await parser.parseTransaction(txHash);
246+
expect(result.length).toBe(1);
242247
const expected = {
243248
recipient: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
244249
tx_hash:
@@ -250,7 +255,7 @@ describe('SwapLayerParser', () => {
250255
output_amount: '0',
251256
staged_inbound: 'ECiEWJndTfUJaEQ59gYgy6e4331mkrh1USQCmDcBwBvj',
252257
};
253-
const serializedRes = seralizedRes(result);
258+
const serializedRes = seralizedRes(result[0]);
254259
expect(serializedRes).toEqual(expected);
255260
});
256261

@@ -259,6 +264,7 @@ describe('SwapLayerParser', () => {
259264
'4yCcw8MJ1BokhPJM2fQC3BMfoezteM4MkaHLfjPrLG25AEW4EeNxcNsrgU3ECkwQ1sy3AKFseafxM2mfjdwbzo8x';
260265

261266
const result = await parser.parseTransaction(txHash);
267+
expect(result.length).toBe(1);
262268
const expected = {
263269
fill_id: 'ESccxJbedTgsu7kwK6uNWnMrg3GiD7pgexXfWeyZNK3J',
264270
output_token: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
@@ -270,7 +276,7 @@ describe('SwapLayerParser', () => {
270276
'4yCcw8MJ1BokhPJM2fQC3BMfoezteM4MkaHLfjPrLG25AEW4EeNxcNsrgU3ECkwQ1sy3AKFseafxM2mfjdwbzo8x',
271277
relaying_fee: '0',
272278
};
273-
const serializedRes = seralizedRes(result);
279+
const serializedRes = seralizedRes(result[0]);
274280
expect(serializedRes).toEqual(expected);
275281
});
276282

@@ -279,6 +285,7 @@ describe('SwapLayerParser', () => {
279285
'2EFLPdYpdJzeoe4HD4fNRWwphhy9HyEHFj3EQtY9agUPmQ5LjJkXFjEt5dnshS9sSTby9nN2QF9BaCbVyiBFGLxj';
280286

281287
const result = await parser.parseTransaction(txHash);
288+
expect(result.length).toBe(1);
282289
const expected = {
283290
tx_hash:
284291
'2EFLPdYpdJzeoe4HD4fNRWwphhy9HyEHFj3EQtY9agUPmQ5LjJkXFjEt5dnshS9sSTby9nN2QF9BaCbVyiBFGLxj',
@@ -290,7 +297,7 @@ describe('SwapLayerParser', () => {
290297
output_amount: '6900000000',
291298
staged_inbound: 'GFJ6699xu2BER8t98S4Vy6ZQam4mvr539AaqvHHBh9i3',
292299
};
293-
const serializedRes = seralizedRes(result);
300+
const serializedRes = seralizedRes(result[0]);
294301
expect(serializedRes).toEqual(expected);
295302
});
296303
});

0 commit comments

Comments
 (0)