@merkl/api 0.18.14 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/src/backgroundJobs/index.js +0 -4
  2. package/dist/src/jobs/set-dungeon-keeper.js +82 -0
  3. package/dist/src/jobs/update-dynamic-data.d.ts +1 -0
  4. package/dist/src/jobs/update-uniswap-v4-pools.d.ts +1 -0
  5. package/dist/src/jobs/update-uniswap-v4-pools.js +14 -0
  6. package/dist/src/modules/v4/uniswapV4/uniswapV4.model.d.ts +9 -0
  7. package/dist/src/modules/v4/uniswapV4/uniswapV4.repository.d.ts +11 -0
  8. package/dist/src/modules/v4/uniswapV4/uniswapV4.repository.js +10 -0
  9. package/dist/src/modules/v4/uniswapV4/uniswapV4.service.d.ts +2 -0
  10. package/dist/src/modules/v4/uniswapV4/uniswapV4.service.js +175 -1
  11. package/dist/tsconfig.package.tsbuildinfo +1 -1
  12. package/package.json +10 -8
  13. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.controller.d.ts +0 -34
  14. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.controller.js +0 -8
  15. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.model.d.ts +0 -0
  16. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.model.js +0 -1
  17. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.repository.d.ts +0 -5
  18. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.repository.js +0 -71
  19. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.service.d.ts +0 -3
  20. package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.service.js +0 -15
  21. /package/dist/src/jobs/{etl/dynamic-data.d.ts → dynamic-data.d.ts} +0 -0
  22. /package/dist/src/jobs/{etl/dynamic-data.js → dynamic-data.js} +0 -0
  23. /package/dist/src/jobs/{etl/pendings.d.ts → pendings.d.ts} +0 -0
  24. /package/dist/src/jobs/{etl/pendings.js → pendings.js} +0 -0
  25. /package/dist/src/jobs/{etl/prices.d.ts → prices.d.ts} +0 -0
  26. /package/dist/src/jobs/{etl/prices.js → prices.js} +0 -0
  27. /package/dist/src/jobs/{etl/reward-breakdowns.d.ts → reward-breakdowns.d.ts} +0 -0
  28. /package/dist/src/jobs/{etl/reward-breakdowns.js → reward-breakdowns.js} +0 -0
  29. /package/dist/src/jobs/{etl/rewards.d.ts → rewards.d.ts} +0 -0
  30. /package/dist/src/jobs/{etl/rewards.js → rewards.js} +0 -0
  31. /package/dist/src/jobs/{etl/update-dynamic-data.d.ts → set-dungeon-keeper.d.ts} +0 -0
  32. /package/dist/src/jobs/{etl/update-dynamic-data.js → update-dynamic-data.js} +0 -0
  33. /package/dist/src/jobs/{etl/update-euler-vaults.d.ts → update-euler-vaults.d.ts} +0 -0
  34. /package/dist/src/jobs/{etl/update-euler-vaults.js → update-euler-vaults.js} +0 -0
@@ -3,7 +3,6 @@ import { Redis } from "@/cache";
3
3
  import { redisClient } from "@/cache/redis";
4
4
  import { getEulerV2Vaults, updateEulerVaultsCollatInDatabase } from "@/libs/campaigns/utils/getEulerV2Vaults";
5
5
  import { getUniswapV4Pools } from "@/libs/campaigns/utils/getUniswapV4Pools";
6
- import { DungeonKeeperController } from "@/modules/v4/dungeonKeeper/dungeonKeeper.controller";
7
6
  import { log } from "@/utils/logger";
8
7
  import { engineDbClient } from "@db";
9
8
  import { swagger } from "@elysiajs/swagger";
@@ -33,9 +32,6 @@ new Elysia({
33
32
  .get("/uniswapv4Update", async () => {
34
33
  log.info("🔃 updating UniswapV4 pools...");
35
34
  await Redis.safeSet("UniswapV4Pools", await getUniswapV4Pools());
36
- })
37
- .group("/v4", app => {
38
- return app.use(DungeonKeeperController);
39
35
  })
40
36
  .onError(ctx => {
41
37
  console.error(ctx.error.message);
@@ -0,0 +1,82 @@
1
+ import { Client } from "@notionhq/client";
2
+ import { Client as DiscordClient, GatewayIntentBits } from "discord.js";
3
+ import moment from "moment";
4
+ const notion = new Client({ auth: process.env.NOTION_TOKEN });
5
+ const discord = new DiscordClient({ intents: [GatewayIntentBits.GuildMembers] });
6
+ const NOTION_EMAIL_TO_DISCORD_ID = {
7
+ picodes: "thepicodes",
8
+ hugo: "ugolxt",
9
+ alex: "wombomango",
10
+ nileco: ".nileco",
11
+ thibaudb: ".greedythib",
12
+ gnervo: "gs8nrv",
13
+ vincent: "vince_merkl",
14
+ hicham: "lamicham_93854",
15
+ pveyrat: "sogipec",
16
+ baptiste: "baptistg",
17
+ clement: "clmntngl",
18
+ };
19
+ async function fetchEntriesForDateWithTag(databaseId, date, tag) {
20
+ const response = await notion.databases.query({
21
+ database_id: databaseId,
22
+ filter: {
23
+ and: [
24
+ {
25
+ property: "Date",
26
+ date: {
27
+ equals: date,
28
+ },
29
+ },
30
+ {
31
+ property: "Database",
32
+ select: {
33
+ equals: tag,
34
+ },
35
+ },
36
+ ],
37
+ },
38
+ });
39
+ return response.results;
40
+ }
41
+ async function assignRole(notionId) {
42
+ const discordId = NOTION_EMAIL_TO_DISCORD_ID[notionId];
43
+ if (!discordId)
44
+ throw "Discord ID not found.";
45
+ await discord.login(process.env.DISCORD_TOKEN);
46
+ const guild = await discord.guilds.cache.get("862708408711643136");
47
+ if (!guild)
48
+ throw "Guild not found.";
49
+ const roles = await guild.roles.fetch();
50
+ const teamRole = roles.find(r => r.name === "Team");
51
+ const dkRole = roles.find(r => r.name === "Dungeon Keeper");
52
+ if (!teamRole || !dkRole)
53
+ throw "Roles not found.";
54
+ const users = await guild.members.fetch();
55
+ for (const [_id, dkUser] of users.filter(member => member.roles.cache.has(dkRole.id))) {
56
+ await dkUser.roles.remove(dkRole);
57
+ }
58
+ const user = users
59
+ .filter(member => member.roles.cache.has(teamRole.id))
60
+ .find(member => member.user.tag === discordId);
61
+ if (!user)
62
+ throw "User not found";
63
+ await user.roles.add(dkRole);
64
+ const channel = await discord.channels.fetch("1328383110957633630");
65
+ if (!channel)
66
+ throw "Channel not found";
67
+ if (channel.isSendable()) {
68
+ await channel.send(`Lucky you <@${user.id}>! You're the <@&${dkRole.id}> today!`);
69
+ }
70
+ }
71
+ async function main() {
72
+ const entries = await fetchEntriesForDateWithTag("8546f6a84641406bafb2358762281e81", moment().format("YYYY-MM-DD"), "Dungeon Keeper");
73
+ const user = entries?.[0];
74
+ if (!user) {
75
+ throw "No dk found for today";
76
+ }
77
+ const notionId = (user?.properties["Qui ?"]).people[0].person.email
78
+ .replace("@angle.money", "")
79
+ .replace("@merkl.xyz", "");
80
+ await assignRole(notionId);
81
+ }
82
+ await main();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import { Redis } from "@/cache";
2
+ import { UniswapV4Service } from "@/modules/v4/uniswapV4";
3
+ async function main() {
4
+ try {
5
+ await Redis.safeSet("UniswapV4Pools", await UniswapV4Service.getPools());
6
+ process.exit(0);
7
+ }
8
+ catch (err) {
9
+ console.error("Failed to update Uniswap V4 pools cache.");
10
+ console.error(err);
11
+ process.exit(1);
12
+ }
13
+ }
14
+ await main();
@@ -1,3 +1,6 @@
1
+ import type { apiDbClient } from "@db";
2
+ import type { Prisma } from "@db/api";
3
+ import type { MerklChainId, UniswapV4PoolType } from "@sdk";
1
4
  export declare const UniswapV4PoolsDto: import("@sinclair/typebox").TRecord<import("@sinclair/typebox").TString, import("@sinclair/typebox").TObject<{
2
5
  poolKey: import("@sinclair/typebox").TObject<{
3
6
  currency0: import("@sinclair/typebox").TString;
@@ -12,4 +15,10 @@ export declare const UniswapV4PoolsDto: import("@sinclair/typebox").TRecord<impo
12
15
  symbolCurrency0: import("@sinclair/typebox").TString;
13
16
  symbolCurrency1: import("@sinclair/typebox").TString;
14
17
  }>>;
18
+ export type UniswapV4PoolsReturnType = {
19
+ [chainId in MerklChainId]?: {
20
+ [poolId: string]: UniswapV4PoolType;
21
+ };
22
+ };
15
23
  export type UniswapV4PoolsEntity = typeof UniswapV4PoolsDto.static;
24
+ export type LoggedCreateBody = Prisma.Args<typeof apiDbClient.logged, "createMany">["data"];
@@ -1,2 +1,13 @@
1
+ import type { LoggedCreateBody } from "./uniswapV4.model";
1
2
  export declare abstract class UniswapV4Repository {
3
+ static getStoredPools(): Promise<{
4
+ id: string;
5
+ type: import("@db/api").$Enums.LoggedEntityType;
6
+ address: string | null;
7
+ chainId: number;
8
+ fetchAtBlock: number;
9
+ caughtFromAddress: string;
10
+ entityData: import("database/api/.generated/runtime/library").JsonValue;
11
+ }[]>;
12
+ static createMany(data: LoggedCreateBody): Promise<import("database/api/.generated/runtime/library").GetBatchResult>;
2
13
  }
@@ -1,2 +1,12 @@
1
+ import { apiDbClient } from "@db";
2
+ import { LoggedEntityType } from "@db/api";
1
3
  export class UniswapV4Repository {
4
+ static async getStoredPools() {
5
+ return await apiDbClient.logged.findMany({
6
+ where: { type: LoggedEntityType.UNISWAP_V4 },
7
+ });
8
+ }
9
+ static async createMany(data) {
10
+ return await apiDbClient.logged.createMany({ data });
11
+ }
2
12
  }
@@ -1,4 +1,5 @@
1
1
  import { type MerklChainId } from "@sdk";
2
+ import type { UniswapV4PoolsReturnType } from "./uniswapV4.model";
2
3
  export declare const PoolManagerInterface: any;
3
4
  export declare abstract class UniswapV4Service {
4
5
  static getPoolsByChain(chainId: MerklChainId): Promise<{
@@ -17,4 +18,5 @@ export declare abstract class UniswapV4Service {
17
18
  symbolCurrency1: string;
18
19
  };
19
20
  }>;
21
+ static getPools(): Promise<UniswapV4PoolsReturnType>;
20
22
  }
@@ -1,8 +1,11 @@
1
1
  import { safeFetchLogs } from "@/libs/campaigns/utils/fetchLogs";
2
2
  import { batchMulticallCallWithRetry } from "@/utils/generic";
3
+ import { log } from "@/utils/logger";
3
4
  import { providers } from "@/utils/providers";
4
- import { ERC20Interface, UniswapV4PoolManager__factory } from "@sdk";
5
+ import { LoggedEntityType } from "@db/api";
6
+ import { ChainInteractionService, ERC20Interface, NETWORK_LABELS, NULL_ADDRESS, UniswapV4Addresses, UniswapV4PoolManagerInterface, UniswapV4PoolManager__factory, getContractCreationBlock, } from "@sdk";
5
7
  import { utils } from "ethers";
8
+ import { UniswapV4Repository } from "./uniswapV4.repository";
6
9
  export const PoolManagerInterface = UniswapV4PoolManager__factory.createInterface();
7
10
  export class UniswapV4Service {
8
11
  static async getPoolsByChain(chainId) {
@@ -70,4 +73,175 @@ export class UniswapV4Service {
70
73
  }
71
74
  return res;
72
75
  }
76
+ static async getPools() {
77
+ const UNIV4_CHAINIDS = Object.keys(UniswapV4Addresses).map((x) => Number(x));
78
+ const pools = {};
79
+ // 0_ Fetch all euler vaults from database
80
+ const storedPools = await UniswapV4Repository.getStoredPools();
81
+ const res = await Promise.all(UNIV4_CHAINIDS.map(async (chainId) => {
82
+ chainId = chainId;
83
+ const perChainIdRes = {};
84
+ const poolManagerAddress = UniswapV4Addresses[chainId]?.PoolManager ?? NULL_ADDRESS;
85
+ const jsonRPCprovider = providers[chainId];
86
+ try {
87
+ // 1_ Get latest euler vaults from chain
88
+ const storedPoolsPerChain = storedPools.filter(pool => pool.chainId === chainId);
89
+ log.info(`found ${storedPoolsPerChain.length} already stored pools on ${NETWORK_LABELS[chainId]}`);
90
+ let fromBlock;
91
+ if (storedPoolsPerChain.length > 0) {
92
+ fromBlock = Math.max(...storedPoolsPerChain.map(x => x.fetchAtBlock)) + 1;
93
+ }
94
+ else {
95
+ fromBlock = await getContractCreationBlock(poolManagerAddress, jsonRPCprovider);
96
+ }
97
+ const toBlock = await jsonRPCprovider.getBlockNumber();
98
+ const logs = await safeFetchLogs(chainId, // TODO: rm type enforcing
99
+ [UniswapV4PoolManagerInterface.getEventTopic("Initialize")], [poolManagerAddress], fromBlock, toBlock
100
+ // fromBlock + 10_000
101
+ );
102
+ const decodedPools = await Promise.all(logs.map(async (log) => {
103
+ const [id, currency0, currency1, fee, tickSpacing, hooks] = UniswapV4PoolManagerInterface.decodeEventLog("Initialize", log.data, log.topics);
104
+ // Respect typing
105
+ return {
106
+ poolId: id,
107
+ chainId: chainId,
108
+ currency0: currency0,
109
+ currency1: currency1,
110
+ tickSpacing: tickSpacing,
111
+ lpFee: fee,
112
+ hooks: hooks,
113
+ fetchedAtBlock: Number(log.blockNumber),
114
+ };
115
+ }));
116
+ log.local(`fetched ${decodedPools.length} pool(s) on ${NETWORK_LABELS[chainId] ?? "Sepolia"} between blocks ${fromBlock} and ${toBlock}`);
117
+ /** Extra calls batch to get the collateral addresses */
118
+ const resCurrencies = await ChainInteractionService(chainId).fetchState(decodedPools.flatMap(pool => {
119
+ return [
120
+ {
121
+ allowFailure: true,
122
+ callData: ERC20Interface.encodeFunctionData("symbol"),
123
+ target: pool.currency0,
124
+ },
125
+ {
126
+ allowFailure: true,
127
+ callData: ERC20Interface.encodeFunctionData("decimals"),
128
+ target: pool.currency0,
129
+ },
130
+ {
131
+ allowFailure: true,
132
+ callData: ERC20Interface.encodeFunctionData("symbol"),
133
+ target: pool.currency1,
134
+ },
135
+ {
136
+ allowFailure: true,
137
+ callData: ERC20Interface.encodeFunctionData("decimals"),
138
+ target: pool.currency1,
139
+ },
140
+ ].filter(x => x.target !== NULL_ADDRESS);
141
+ }));
142
+ let index = 0;
143
+ for (const pool of decodedPools) {
144
+ let symbolCurrency0 = "UNKNOWN";
145
+ let decimalsCurrency0 = 18;
146
+ if (pool.currency0 !== NULL_ADDRESS) {
147
+ try {
148
+ symbolCurrency0 = ERC20Interface.decodeFunctionResult("symbol", resCurrencies[index].returnData)[0];
149
+ if (symbolCurrency0.includes("/") || symbolCurrency0.includes("\u0000")) {
150
+ symbolCurrency0 = "INVALID";
151
+ }
152
+ decimalsCurrency0 = ERC20Interface.decodeFunctionResult("decimals", resCurrencies[index + 1].returnData)[0];
153
+ }
154
+ catch {
155
+ log.error("getUniswapV4Pools", `issue when fetching symbol / decimals for currency0 ${pool.currency0} of pool ${pool.poolId} on ${NETWORK_LABELS[chainId]}`);
156
+ }
157
+ }
158
+ else {
159
+ symbolCurrency0 = "ETH";
160
+ decimalsCurrency0 = 18;
161
+ index -= 2;
162
+ }
163
+ let symbolCurrency1 = "UNKNOWN";
164
+ let decimalsCurrency1 = 18;
165
+ if (pool.currency1 !== NULL_ADDRESS) {
166
+ try {
167
+ symbolCurrency1 = ERC20Interface.decodeFunctionResult("symbol", resCurrencies[index + 2].returnData)[0];
168
+ if (symbolCurrency1.includes("/") || symbolCurrency1.includes("\u0000")) {
169
+ symbolCurrency1 = "INVALID";
170
+ }
171
+ decimalsCurrency1 = ERC20Interface.decodeFunctionResult("decimals", resCurrencies[index + 3].returnData)[0];
172
+ }
173
+ catch {
174
+ log.error("getUniswapV4Pools", `issue when fetching symbol / decimals for currency1 ${pool.currency1} of pool ${pool.poolId} on ${NETWORK_LABELS[chainId]}`);
175
+ }
176
+ }
177
+ else {
178
+ symbolCurrency1 = "ETH";
179
+ decimalsCurrency1 = 18;
180
+ index -= 2;
181
+ }
182
+ const id = pool.poolId;
183
+ perChainIdRes[id ?? "unknownKey"] = {
184
+ chainId: pool.chainId,
185
+ currency0: pool.currency0,
186
+ currency1: pool.currency1,
187
+ decimalsCurrency0,
188
+ decimalsCurrency1,
189
+ hooks: pool.hooks,
190
+ fetchedAtBlock: pool.fetchedAtBlock,
191
+ lpFee: pool.lpFee,
192
+ poolId: id,
193
+ poolKey: pool.poolKey,
194
+ symbolCurrency0,
195
+ symbolCurrency1,
196
+ tickSpacing: pool.tickSpacing,
197
+ };
198
+ index += 4;
199
+ }
200
+ }
201
+ catch (e) {
202
+ log.error(`issue when fetching UniswapV4 pools on ${NETWORK_LABELS[chainId]}`, e);
203
+ }
204
+ return perChainIdRes;
205
+ }));
206
+ UNIV4_CHAINIDS.forEach((chainId, i) => {
207
+ if (!!res[i])
208
+ pools[chainId] = res[i];
209
+ });
210
+ // Update the API database
211
+ const tableData = Object.values(pools).flatMap(pools => Object.values(pools));
212
+ for (const chainId of UNIV4_CHAINIDS) {
213
+ if (tableData.filter(p => p.chainId === chainId).length > 0) {
214
+ try {
215
+ await UniswapV4Repository.createMany(tableData
216
+ .filter(point => point.chainId === chainId)
217
+ .map(pool => ({
218
+ fetchAtBlock: pool.fetchedAtBlock,
219
+ caughtFromAddress: UniswapV4Addresses[pool.chainId]?.PoolManager ?? NULL_ADDRESS,
220
+ chainId: pool.chainId,
221
+ entityData: pool,
222
+ id: Bun.hash(`${pool.poolId}-${pool.chainId}`).toString(),
223
+ type: LoggedEntityType.UNISWAP_V4,
224
+ })));
225
+ log.info(`✅ successfully saved vaults to API database ('Logged' table) on ${NETWORK_LABELS[chainId]}`);
226
+ }
227
+ catch (e) {
228
+ log.error("getUniswapV4Pools/LoggedTableUpdate", e);
229
+ throw new Error(`Error while saving UniV4 pools to API database ('Logged' table) on ${NETWORK_LABELS[chainId]}`);
230
+ }
231
+ }
232
+ }
233
+ log.info(`✅ successfully fetched ${tableData.length} new pool(s) on UniswapV4`);
234
+ // _ Merge previoulsy stored pools with newly fetched ones
235
+ // TODO optimize this part
236
+ if (storedPools.length > 0) {
237
+ for (const pool of storedPools) {
238
+ const chainId = pool.chainId;
239
+ if (!pools[chainId])
240
+ pools[chainId] = {};
241
+ pools[chainId][pool.entityData.poolId] = pool.entityData;
242
+ }
243
+ }
244
+ log.info("👋 exiting getUniswapV4Pools");
245
+ return pools;
246
+ }
73
247
  }