@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.
- package/dist/src/backgroundJobs/index.js +0 -4
- package/dist/src/jobs/set-dungeon-keeper.js +82 -0
- package/dist/src/jobs/update-dynamic-data.d.ts +1 -0
- package/dist/src/jobs/update-uniswap-v4-pools.d.ts +1 -0
- package/dist/src/jobs/update-uniswap-v4-pools.js +14 -0
- package/dist/src/modules/v4/uniswapV4/uniswapV4.model.d.ts +9 -0
- package/dist/src/modules/v4/uniswapV4/uniswapV4.repository.d.ts +11 -0
- package/dist/src/modules/v4/uniswapV4/uniswapV4.repository.js +10 -0
- package/dist/src/modules/v4/uniswapV4/uniswapV4.service.d.ts +2 -0
- package/dist/src/modules/v4/uniswapV4/uniswapV4.service.js +175 -1
- package/dist/tsconfig.package.tsbuildinfo +1 -1
- package/package.json +10 -8
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.controller.d.ts +0 -34
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.controller.js +0 -8
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.model.d.ts +0 -0
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.model.js +0 -1
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.repository.d.ts +0 -5
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.repository.js +0 -71
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.service.d.ts +0 -3
- package/dist/src/modules/v4/dungeonKeeper/dungeonKeeper.service.js +0 -15
- /package/dist/src/jobs/{etl/dynamic-data.d.ts → dynamic-data.d.ts} +0 -0
- /package/dist/src/jobs/{etl/dynamic-data.js → dynamic-data.js} +0 -0
- /package/dist/src/jobs/{etl/pendings.d.ts → pendings.d.ts} +0 -0
- /package/dist/src/jobs/{etl/pendings.js → pendings.js} +0 -0
- /package/dist/src/jobs/{etl/prices.d.ts → prices.d.ts} +0 -0
- /package/dist/src/jobs/{etl/prices.js → prices.js} +0 -0
- /package/dist/src/jobs/{etl/reward-breakdowns.d.ts → reward-breakdowns.d.ts} +0 -0
- /package/dist/src/jobs/{etl/reward-breakdowns.js → reward-breakdowns.js} +0 -0
- /package/dist/src/jobs/{etl/rewards.d.ts → rewards.d.ts} +0 -0
- /package/dist/src/jobs/{etl/rewards.js → rewards.js} +0 -0
- /package/dist/src/jobs/{etl/update-dynamic-data.d.ts → set-dungeon-keeper.d.ts} +0 -0
- /package/dist/src/jobs/{etl/update-dynamic-data.js → update-dynamic-data.js} +0 -0
- /package/dist/src/jobs/{etl/update-euler-vaults.d.ts → update-euler-vaults.d.ts} +0 -0
- /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 {
|
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
|
}
|