Skip to content

Commit cd14e43

Browse files
committed
feat(fallbacks): add fallbacks for topic0 hash
1 parent e3c8c40 commit cd14e43

File tree

5 files changed

+205
-18
lines changed

5 files changed

+205
-18
lines changed

services/decoder/decoder.ts

+58-18
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,24 @@ import {
1212
type DecodingFunctions,
1313
type EventType,
1414
type DecoderConfig,
15+
type Fallbacks,
1516
} from "./decoder.types";
1617
import { encodeEventTopics, type Abi } from "viem";
1718

1819
export class GoldRushDecoder {
1920
private static configs: DecoderConfig = {};
2021
private static decoders: Decoders = {};
22+
private static fallbacks: Fallbacks = {};
2123
private static decoding_functions: DecodingFunctions = [];
2224

2325
public static initDecoder = () => {
24-
const directoryPath: string = join(__dirname, "/protocols");
25-
const protocols = readdirSync(directoryPath);
26+
console.info("Initializing GoldrushDecoder Service...");
27+
28+
const protocolsDirectoryPath: string = join(__dirname, "/protocols");
29+
const protocols = readdirSync(protocolsDirectoryPath);
2630
let protocolsCount: number = 0;
2731
for (const protocol of protocols) {
28-
const protocolPath = join(directoryPath, protocol);
32+
const protocolPath = join(protocolsDirectoryPath, protocol);
2933
const files = readdirSync(protocolPath);
3034
let configFile: string | null = null,
3135
decodersFile: string | null = null;
@@ -56,6 +60,27 @@ export class GoldRushDecoder {
5660
}
5761
}
5862

63+
const fallbacksDirectoryPath: string = join(__dirname, "/fallbacks");
64+
const fallbacks = readdirSync(fallbacksDirectoryPath);
65+
let fallbacksCount: number = 0;
66+
for (const fallback of fallbacks) {
67+
const fallbackPath = join(fallbacksDirectoryPath, fallback);
68+
const files = readdirSync(fallbackPath);
69+
let fallbackFile: string | null = null;
70+
files.forEach((file) => {
71+
const fileExtension =
72+
process.env.NODE_ENV !== "test" ? "js" : "ts";
73+
if (file.endsWith(`.fallback.${fileExtension}`)) {
74+
fallbackFile = file;
75+
}
76+
});
77+
if (fallbackFile) {
78+
fallbacksCount++;
79+
require(join(fallbackPath, fallbackFile));
80+
}
81+
}
82+
83+
const decodersCount = Object.keys(this.decoding_functions).length;
5984
const configsCount = Object.values(this.configs).reduce(
6085
(networkCount, network) => {
6186
return (
@@ -68,11 +93,10 @@ export class GoldRushDecoder {
6893
0
6994
);
7095

71-
const decodersCount = Object.keys(this.decoding_functions).length;
72-
73-
console.info(
74-
`Created ${configsCount} configs and ${decodersCount} decoders for ${protocolsCount} protocols!`
75-
);
96+
console.info(`${protocolsCount.toLocaleString()} protocols found`);
97+
console.info(`${configsCount.toLocaleString()} configs generated`);
98+
console.info(`${decodersCount.toLocaleString()} decoders generated`);
99+
console.info(`${fallbacksCount.toLocaleString()} fallbacks generated`);
76100
};
77101

78102
public static on = (
@@ -82,7 +106,7 @@ export class GoldRushDecoder {
82106
decoding_function: DecodingFunction
83107
) => {
84108
const [protocol, event_name] = event_id.split(":");
85-
const [topic0] = encodeEventTopics({
109+
const [topic0_hash] = encodeEventTopics({
86110
abi: abi,
87111
eventName: event_name,
88112
});
@@ -101,12 +125,27 @@ export class GoldRushDecoder {
101125
Object.keys(this.configs[network][protocol]).forEach((address) => {
102126
this.decoders[network] ??= {};
103127
this.decoders[network][address] ??= {};
104-
this.decoders[network][address][topic0] =
128+
this.decoders[network][address][topic0_hash] =
105129
decoding_function_index;
106130
});
107131
});
108132
};
109133

134+
public static fallback = (
135+
event_name: string,
136+
abi: Abi,
137+
decoding_function: DecodingFunction
138+
) => {
139+
const [topic0_hash] = encodeEventTopics({
140+
abi: abi,
141+
eventName: event_name,
142+
});
143+
this.decoding_functions.push(decoding_function);
144+
const decoding_function_index: number =
145+
this.decoding_functions.length - 1;
146+
this.fallbacks[topic0_hash] = decoding_function_index;
147+
};
148+
110149
public static decode = async (
111150
network: Chain,
112151
tx: Transaction,
@@ -123,17 +162,18 @@ export class GoldRushDecoder {
123162
// !ERROR: add factory_contract_address in the log_event(s)
124163
// factory_contract_address,
125164
} = log;
126-
const function_index =
165+
const decoding_index =
127166
// !ERROR: add factory_contract_address in the log_event(s)
128167
// factory_contract_address ||
129168
this.decoders[network][contract_address]?.[topic0_hash];
130-
if (function_index !== undefined) {
131-
const event = await this.decoding_functions[function_index](
132-
log,
133-
tx,
134-
network,
135-
covalent_client
136-
);
169+
const fallback_index = this.fallbacks[topic0_hash];
170+
if (
171+
decoding_index !== undefined ||
172+
fallback_index !== undefined
173+
) {
174+
const event = await this.decoding_functions[
175+
decoding_index ?? fallback_index
176+
](log, tx, network, covalent_client);
137177
events.push(event);
138178
}
139179
}

services/decoder/decoder.types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,10 @@ export type Decoders =
8686
}
8787
| Record<string, never>;
8888

89+
export type Fallbacks =
90+
| {
91+
[topic0_hash: string]: number;
92+
}
93+
| Record<string, never>;
94+
8995
export type DecodingFunctions = DecodingFunction[];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
[
2+
{
3+
"anonymous": false,
4+
"inputs": [
5+
{
6+
"indexed": true,
7+
"internalType": "address",
8+
"name": "from",
9+
"type": "address"
10+
},
11+
{
12+
"indexed": true,
13+
"internalType": "address",
14+
"name": "to",
15+
"type": "address"
16+
},
17+
{
18+
"indexed": false,
19+
"internalType": "uint256",
20+
"name": "value",
21+
"type": "uint256"
22+
}
23+
],
24+
"name": "Transfer",
25+
"type": "event"
26+
}
27+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { GoldRushDecoder } from "../../decoder";
2+
import { type EventType } from "../../decoder.types";
3+
import {
4+
DECODED_ACTION,
5+
DECODED_EVENT_CATEGORY,
6+
} from "../../decoder.constants";
7+
import { decodeEventLog, type Abi } from "viem";
8+
import ABI from "./abis/transfer.abi.json";
9+
import { TimestampParser } from "../../../../utils/functions";
10+
import { prettifyCurrency } from "@covalenthq/client-sdk";
11+
12+
GoldRushDecoder.fallback(
13+
"Transfer",
14+
ABI as Abi,
15+
async (log_event, tx, chain_name, covalent_client): Promise<EventType> => {
16+
const { raw_log_data, raw_log_topics } = log_event;
17+
18+
const { args: decoded } = decodeEventLog({
19+
abi: ABI,
20+
topics: raw_log_topics as [],
21+
data: raw_log_data as `0x${string}`,
22+
eventName: "Transfer",
23+
}) as {
24+
eventName: "Transfer";
25+
args: {
26+
from: string;
27+
to: string;
28+
value: bigint;
29+
};
30+
};
31+
32+
const date = TimestampParser(log_event.block_signed_at, "YYYY-MM-DD");
33+
const { data } = await covalent_client.PricingService.getTokenPrices(
34+
chain_name,
35+
"USD",
36+
log_event.sender_address,
37+
{
38+
from: date,
39+
to: date,
40+
}
41+
);
42+
43+
const pretty_quote =
44+
data?.[0]?.items?.[0]?.price *
45+
(Number(decoded.value) /
46+
Math.pow(
47+
10,
48+
data?.[0]?.items?.[0]?.contract_metadata
49+
?.contract_decimals ?? 18
50+
));
51+
52+
return {
53+
action: DECODED_ACTION.SWAPPED,
54+
category: DECODED_EVENT_CATEGORY.DEX,
55+
name: "Transfer",
56+
protocol: {
57+
logo: log_event.sender_logo_url as string,
58+
name: log_event.sender_name as string,
59+
},
60+
details: [
61+
{
62+
title: "From",
63+
value: decoded.from,
64+
type: "address",
65+
},
66+
{
67+
title: "To",
68+
value: decoded.to,
69+
type: "address",
70+
},
71+
],
72+
tokens: [
73+
{
74+
decimals:
75+
data?.[0]?.items?.[0]?.contract_metadata
76+
?.contract_decimals ?? 18,
77+
heading: "Token Amount",
78+
pretty_quote: pretty_quote
79+
? prettifyCurrency(pretty_quote)
80+
: "",
81+
ticker_logo:
82+
data?.[0]?.items?.[0]?.contract_metadata?.logo_url,
83+
ticker_symbol:
84+
data?.[0]?.items?.[0]?.contract_metadata
85+
?.contract_ticker_symbol,
86+
value: decoded.value.toString(),
87+
},
88+
],
89+
};
90+
}
91+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import request from "supertest";
2+
import app from "../../../../api";
3+
import { type EventType } from "../../decoder.types";
4+
5+
describe("fallback", () => {
6+
test("Transfer", async () => {
7+
const res = await request(app)
8+
.post("/api/v1/tx/decode")
9+
.set({ "x-covalent-api-key": process.env.TEST_COVALENT_API_KEY })
10+
.send({
11+
network: "eth-mainnet",
12+
tx_hash:
13+
"0xe7b894fdac8c037fa69bbabe168fe7984033226e1b1871bd9f70c861b6f6a35d",
14+
});
15+
const { events } = res.body as { events: EventType[] };
16+
const event = events.find(({ name }) => name === "Transfer");
17+
if (!event) {
18+
throw Error("Event not found");
19+
}
20+
expect(event.details?.length).toEqual(2);
21+
expect(event.tokens?.length).toEqual(1);
22+
});
23+
});

0 commit comments

Comments
 (0)