@merkl/api 0.20.19 → 0.20.21

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.
@@ -1195,6 +1195,17 @@ declare const eden: {
1195
1195
  }>>;
1196
1196
  };
1197
1197
  }) & {
1198
+ "dynamic-data": {
1199
+ post: (body: string[], options: {
1200
+ headers: {
1201
+ authorization: string;
1202
+ };
1203
+ query?: Record<string, unknown> | undefined;
1204
+ fetch?: RequestInit | undefined;
1205
+ }) => Promise<import("@elysiajs/eden").Treaty.TreatyResponse<{
1206
+ 200: unknown[];
1207
+ }>>;
1208
+ };
1198
1209
  metadata: {
1199
1210
  get: (options: {
1200
1211
  headers: {
@@ -4946,6 +4957,17 @@ declare const eden: {
4946
4957
  }>>;
4947
4958
  };
4948
4959
  }) & {
4960
+ "dynamic-data": {
4961
+ post: (body: string[], options: {
4962
+ headers: {
4963
+ authorization: string;
4964
+ };
4965
+ query?: Record<string, unknown> | undefined;
4966
+ fetch?: RequestInit | undefined;
4967
+ }) => Promise<import("@elysiajs/eden").Treaty.TreatyResponse<{
4968
+ 200: unknown[];
4969
+ }>>;
4970
+ };
4949
4971
  metadata: {
4950
4972
  get: (options: {
4951
4973
  headers: {
@@ -9469,6 +9491,22 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
9469
9491
  };
9470
9492
  };
9471
9493
  };
9494
+ } & {
9495
+ "dry-run": {
9496
+ "dynamic-data": {
9497
+ post: {
9498
+ body: string[];
9499
+ params: {};
9500
+ query: unknown;
9501
+ headers: {
9502
+ authorization: string;
9503
+ };
9504
+ response: {
9505
+ 200: unknown[];
9506
+ };
9507
+ };
9508
+ };
9509
+ };
9472
9510
  } & {
9473
9511
  "dry-run": {
9474
9512
  metadata: {
@@ -14965,6 +15003,17 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
14965
15003
  }>>;
14966
15004
  };
14967
15005
  }) & {
15006
+ "dynamic-data": {
15007
+ post: (body: string[], options: {
15008
+ headers: {
15009
+ authorization: string;
15010
+ };
15011
+ query?: Record<string, unknown> | undefined;
15012
+ fetch?: RequestInit | undefined;
15013
+ }) => Promise<import("@elysiajs/eden").Treaty.TreatyResponse<{
15014
+ 200: unknown[];
15015
+ }>>;
15016
+ };
14968
15017
  metadata: {
14969
15018
  get: (options: {
14970
15019
  headers: {
@@ -18716,6 +18765,17 @@ export declare const MerklApi: (domain: string | import("elysia").default<"", fa
18716
18765
  }>>;
18717
18766
  };
18718
18767
  }) & {
18768
+ "dynamic-data": {
18769
+ post: (body: string[], options: {
18770
+ headers: {
18771
+ authorization: string;
18772
+ };
18773
+ query?: Record<string, unknown> | undefined;
18774
+ fetch?: RequestInit | undefined;
18775
+ }) => Promise<import("@elysiajs/eden").Treaty.TreatyResponse<{
18776
+ 200: unknown[];
18777
+ }>>;
18778
+ };
18719
18779
  metadata: {
18720
18780
  get: (options: {
18721
18781
  headers: {
@@ -39,8 +39,6 @@ async function computeEventBasedPoolTVLFromMostRecentStateSave(chainId, campaign
39
39
  // Bucket service
40
40
  let tvl = 0;
41
41
  try {
42
- if (bucketName === undefined)
43
- console.log("EVENT_BASED:", states);
44
42
  const bucket = new BucketService("merkl-production-states", "merkl-production");
45
43
  const storedStates = JSON.parse(await bucket.pull(fileName));
46
44
  for (const [_, { value, params: _params }] of Object.entries(storedStates)) {
@@ -32,19 +32,17 @@ async function computeUniV4PoolTVLFromMostRecentStateSave(chainId, poolID, price
32
32
  stateSave = mostRecentStateSave.state;
33
33
  blockNumber = mostRecentStateSave?.blockNumber;
34
34
  states = stateSave.states;
35
- // const globalState = stateSave.globalState as { tick: number; liquidity: string };
36
35
  }
37
- catch (e) {
36
+ catch {
38
37
  log.warn(`merklDynamic data - failed to read a recent state of pool ${poolID} on ${NETWORK_LABELS[chainId]}`);
38
+ return { tvl: 0, amount0: 0, amount1: 0, blockNumber: blockNumber ?? 0 };
39
39
  }
40
- const { fileName, bucketName } = states;
40
+ const { fileName } = states;
41
41
  // Bucket service
42
42
  let tvl = 0;
43
43
  let amount0 = 0;
44
44
  let amount1 = 0;
45
45
  try {
46
- if (bucketName === undefined)
47
- console.log("UNIV4", states);
48
46
  const bucket = new BucketService("merkl-production-states", "merkl-production");
49
47
  const storedStates = JSON.parse(await bucket.pull(fileName));
50
48
  for (const [_, { value, params: _params }] of Object.entries(storedStates)) {
@@ -53,8 +51,7 @@ async function computeUniV4PoolTVLFromMostRecentStateSave(chainId, poolID, price
53
51
  }
54
52
  tvl = amount0 * (priceCurrency0 ?? 0) + amount1 * (priceCurrency1 ?? 0);
55
53
  }
56
- catch (e) {
57
- console.log(e);
54
+ catch {
58
55
  log.warn(`merklDynamic data - failed to decode state of pool ${poolID} on ${NETWORK_LABELS[chainId]}`);
59
56
  }
60
57
  return { tvl, amount0, amount1, blockNumber: blockNumber };
@@ -156,7 +156,7 @@ export async function getEulerV2Vaults(chainId) {
156
156
  const aux = EulerEVKInterface.decodeEventLog("EVaultCreated", log.data, log.topics);
157
157
  const name = (await EulerVault__factory.connect(log.address, providers[chainId]).name()).split(" ");
158
158
  const vaultName = (await fetchEulerVaultName(getAddress(log.address), chainId)) ?? name[name.length - 1];
159
- /** Respect the previous typing */
159
+ /** Match previous typing */
160
160
  return {
161
161
  address: log.address.toString(),
162
162
  asset: aux[1].toString(),
@@ -167,8 +167,8 @@ export async function getEulerV2Vaults(chainId) {
167
167
  };
168
168
  }
169
169
  catch {
170
- logger.warn(`issue when fetching data on ${NETWORK_LABELS[chainId]} for vault ${log.address}`);
171
- process.exit(1);
170
+ logger.error(`issue when fetching data on ${NETWORK_LABELS[chainId]} for vault ${log.address}`);
171
+ return {};
172
172
  }
173
173
  }));
174
174
  log.info(`fetched ${decodedVaults.length} vaults(s) on ${NETWORK_LABELS[chainId]} between blocks ${fromBlock} and ${toBlock}`);
@@ -12,9 +12,14 @@ export class MorphoMetadata {
12
12
  { name: `Lend ${morphoParams.symbolBorrowToken} on ${market}`, action: OpportunityAction.LEND },
13
13
  ];
14
14
  const subtype = subtypes[subType];
15
+ const tokens = [{ chainId: computeChainId, address: params.targetToken }];
16
+ if (subType === MorphoSubCampaignType.META) {
17
+ const typedParams = params;
18
+ tokens.push({ chainId: computeChainId, address: typedParams.underlyingToken });
19
+ }
15
20
  return {
16
21
  action: subtype.action,
17
- tokens: [{ chainId: computeChainId, address: params.targetToken }],
22
+ tokens,
18
23
  name: subtype.name,
19
24
  mainProtocol: (computeChainId === ChainId.POLYGON ? "compound" : "morpho"),
20
25
  depositUrl: MorphoMetadata.generateUrl(computeChainId, params, morphoParams, subType),
@@ -1280,6 +1280,22 @@ declare const app: Elysia<"", false, {
1280
1280
  };
1281
1281
  };
1282
1282
  };
1283
+ } & {
1284
+ "dry-run": {
1285
+ "dynamic-data": {
1286
+ post: {
1287
+ body: string[];
1288
+ params: {};
1289
+ query: unknown;
1290
+ headers: {
1291
+ authorization: string;
1292
+ };
1293
+ response: {
1294
+ 200: unknown[];
1295
+ };
1296
+ };
1297
+ };
1298
+ };
1283
1299
  } & {
1284
1300
  "dry-run": {
1285
1301
  metadata: {
@@ -1,5 +1,6 @@
1
1
  import { Redis } from "@/cache";
2
2
  import { getEulerV2Vaults, updateEulerVaultsCollatInDatabase } from "@/engine/dynamicData/utils/getEulerV2Vaults";
3
+ import { logger } from "@/utils/logger";
3
4
  (async () => {
4
5
  await Redis.safeSet("EulerV2Vaults", await getEulerV2Vaults());
5
6
  try {
@@ -9,5 +10,6 @@ import { getEulerV2Vaults, updateEulerVaultsCollatInDatabase } from "@/engine/dy
9
10
  console.error(e);
10
11
  process.exit(1);
11
12
  }
13
+ logger.info("✅ Euler update exited successfully");
12
14
  process.exit(0);
13
15
  })();
@@ -183,6 +183,22 @@ export declare const CampaignController: Elysia<"/campaigns", false, {
183
183
  };
184
184
  };
185
185
  };
186
+ } & {
187
+ "dry-run": {
188
+ "dynamic-data": {
189
+ post: {
190
+ body: string[];
191
+ params: {};
192
+ query: unknown;
193
+ headers: {
194
+ authorization: string;
195
+ };
196
+ response: {
197
+ 200: unknown[];
198
+ };
199
+ };
200
+ };
201
+ };
186
202
  } & {
187
203
  "dry-run": {
188
204
  metadata: {
@@ -8,7 +8,7 @@ import { throwOnUnsupportedChainId } from "src/utils/throw";
8
8
  import { DynamicDataService } from "../dynamicData/dynamicData.service";
9
9
  import { OpportunityService } from "../opportunity";
10
10
  import { OpportunityConvertorService } from "../opportunity/opportunity.converter";
11
- import { CampaignResourceDto, CampaignUniqueDto, CreateCampaignDto, GetCampaignQueryDto, RemoveManualOverrideDto, UpdateCampaignCreatorDto, UpdateCampaignDto, UpdateMetaDataCampaignDto, } from "./campaign.model";
11
+ import { CampaignResourceDto, CampaignUniqueDto, CampaignsDto, CreateCampaignDto, GetCampaignQueryDto, RemoveManualOverrideDto, UpdateCampaignCreatorDto, UpdateCampaignDto, UpdateMetaDataCampaignDto, } from "./campaign.model";
12
12
  import { CampaignService } from "./campaign.service";
13
13
  // ─── Campaigns Controller ────────────────────────────────────────────────────
14
14
  export const CampaignController = new Elysia({ prefix: "/campaigns", detail: { tags: ["Campaigns"] } })
@@ -65,6 +65,19 @@ export const CampaignController = new Elysia({ prefix: "/campaigns", detail: { t
65
65
  const campaignV3 = OpportunityConvertorService.convertV4CampaignToV3(Campaign[campaign.type], CampaignService.format(campaign), campaign.Opportunity.identifier);
66
66
  return await DynamicDataService.updateForCampaigns([campaignV3], true);
67
67
  }, { beforeHandle: BackOfficeGuard, headers: AuthorizationHeadersDto, detail: { hide: true } })
68
+ // ─── Test Dynamic data computation with a list of campaignId ───────────────────────
69
+ .post("/dynamic-data", async ({ body }) => {
70
+ const listCampaigns = [];
71
+ for (const campaignId of body) {
72
+ const id = (await CampaignService.findMany({ campaignId: campaignId, test: true }))?.[0]?.id;
73
+ if (!id)
74
+ throw new NotFoundError();
75
+ const campaign = await CampaignService.findUniqueOrThrow(id, true);
76
+ const campaignV3 = OpportunityConvertorService.convertV4CampaignToV3(Campaign[campaign.type], CampaignService.format(campaign), campaign.Opportunity.identifier);
77
+ listCampaigns.push(campaignV3);
78
+ }
79
+ return await DynamicDataService.updateForCampaigns(listCampaigns, true);
80
+ }, { beforeHandle: BackOfficeGuard, body: CampaignsDto, headers: AuthorizationHeadersDto, detail: { hide: true } })
68
81
  // ─── Test Opportunity creation through a campaign Id and a chain ───────────────────────
69
82
  // @dev Starts from the engine db to debug opportunity creation failing and preventing the api db to be filled
70
83
  .get("/metadata", async ({ query }) => {
@@ -29,6 +29,7 @@ export declare const CampaignUniqueDto: import("@sinclair/typebox").TObject<{
29
29
  distributionChain: import("@sinclair/typebox").TNumber;
30
30
  campaignId: import("@sinclair/typebox").TString;
31
31
  }>;
32
+ export declare const CampaignsDto: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TString>;
32
33
  export declare const CampaignResourceDto: import("@sinclair/typebox").TObject<{
33
34
  id: import("@sinclair/typebox").TString;
34
35
  computeChainId: import("@sinclair/typebox").TNumber;
@@ -8,6 +8,7 @@ export const CampaignUniqueDto = t.Object({
8
8
  distributionChain: t.Numeric(),
9
9
  campaignId: t.String(),
10
10
  });
11
+ export const CampaignsDto = t.Array(t.String());
11
12
  export const CampaignResourceDto = t.Object({
12
13
  id: t.String(),
13
14
  computeChainId: t.Number(),
@@ -1,5 +1,4 @@
1
- import type { DynamicDataBuilder } from "@/engine/dynamicData/interface";
2
- import { Campaign, type CampaignParameters, type ChainId } from "@sdk";
1
+ import { Campaign, type CampaignDynamicData, type CampaignParameters, type ChainId } from "@sdk";
3
2
  import { Campaign as CampaignEnum } from "@sdk";
4
3
  export declare class DynamicDataService {
5
4
  static queryERC20DynamicData(chainId: ChainId, tokenAddress: string, decimals?: number): Promise<{
@@ -10,7 +9,7 @@ export declare class DynamicDataService {
10
9
  priceTargetToken: number;
11
10
  type: string;
12
11
  }>;
13
- static getDynamicData(campaigns: CampaignParameters<CampaignEnum>[], type: Campaign, chainId: number): Promise<DynamicDataBuilder<typeof type>[]>;
12
+ static getDynamicData(campaigns: CampaignParameters<CampaignEnum>[], type: Campaign, chainId: number): Promise<CampaignDynamicData<CampaignEnum>[]>;
14
13
  static updateForCampaignType(campaigns: CampaignParameters<CampaignEnum>[], type: Campaign, dryRun?: boolean): Promise<unknown[]>;
15
14
  static updateForCampaigns(campaigns: CampaignParameters<CampaignEnum>[], dryRun?: boolean): Promise<unknown[]>;
16
15
  }
@@ -53,36 +53,34 @@ export class DynamicDataService {
53
53
  };
54
54
  }
55
55
  static async getDynamicData(campaigns, type, chainId) {
56
+ // Base case: empty input
57
+ if (campaigns.length === 0)
58
+ return [];
56
59
  try {
57
- const dynamicData = await dynamicDataBuilderFactory(typeof type === "number" ? type : Campaign[type]).build(chainId, campaigns);
58
- return dynamicData;
60
+ const campaignType = typeof type === "number" ? type : Campaign[type];
61
+ return await dynamicDataBuilderFactory(campaignType).build(chainId, campaigns);
59
62
  }
60
- catch (_err) {
61
- console.log("Failed to get dynamic data");
62
- const firstHalf = campaigns.splice(0, Math.ceil(campaigns.length / 2));
63
- console.log("firstHalf", firstHalf);
64
- const secondHalf = campaigns;
65
- if (firstHalf.length > 1 || secondHalf.length > 1)
66
- return [
67
- ...(await DynamicDataService.getDynamicData(firstHalf, type, chainId)),
68
- ...(await DynamicDataService.getDynamicData(secondHalf, type, chainId)),
69
- ];
70
- if (firstHalf.length <= 1)
71
- console.error(`Failed to get dynamic data for ${firstHalf[0]}`);
72
- if (secondHalf.length <= 1)
73
- console.error(`Failed to get dynamic data for ${secondHalf[0]}`);
74
- return [];
63
+ catch (error) {
64
+ // Base case: single failing campaign
65
+ if (campaigns.length === 1) {
66
+ log.error(`Permanent failure for campaign ${campaigns[0].campaignId}`, error);
67
+ return [];
68
+ }
69
+ // Recursive binary split
70
+ const mid = Math.ceil(campaigns.length / 2);
71
+ const [firstResults, secondResults] = await Promise.all([
72
+ // Process first half with error propagation
73
+ DynamicDataService.getDynamicData(campaigns.slice(0, mid), type, chainId),
74
+ // Process second half with error propagation
75
+ DynamicDataService.getDynamicData(campaigns.slice(mid), type, chainId),
76
+ ]);
77
+ return [...firstResults, ...secondResults];
75
78
  }
76
79
  }
77
80
  static async updateForCampaignType(campaigns, type, dryRun = false) {
78
81
  const chainId = campaigns[0].computeChainId;
79
82
  const dynamicDataArray = [];
80
- const dynamicData = await dynamicDataBuilderFactory(typeof type === "number" ? type : Campaign[type]).build(chainId, campaigns);
81
- // const dynamicData = await DynamicDataService.getDynamicData(
82
- // campaigns,
83
- // typeof type === "number" ? type : Campaign[type as keyof typeof Campaign],
84
- // chainId
85
- // );
83
+ const dynamicData = await DynamicDataService.getDynamicData(campaigns, typeof type === "number" ? type : Campaign[type], chainId);
86
84
  const oppMap = {};
87
85
  for (const data of dynamicData) {
88
86
  if (!!data) {
@@ -123,6 +121,7 @@ export class DynamicDataService {
123
121
  }
124
122
  dynamicDataArray.push(bigintToString({ campaignId: Object.values(entry[1])[0].campaignId, apr, tvl, dailyRewards }));
125
123
  }
124
+ log.info(`[${CampaignEnum[type]}] Updated ${dynamicData.length}/${campaigns.length} campaigns`);
126
125
  return dynamicDataArray;
127
126
  }
128
127
  static async updateForCampaigns(campaigns, dryRun = false) {
@@ -136,7 +135,6 @@ export class DynamicDataService {
136
135
  const dynamicDataArray = [];
137
136
  for (const [campaignType, campaigns] of campaignTypeToCampaigns.entries()) {
138
137
  try {
139
- log.info(`updating dynamic data for ${campaigns.length} campaigns of type ${CampaignEnum[campaignType]}`);
140
138
  try {
141
139
  dynamicDataArray.push(await DynamicDataService.updateForCampaignType(campaigns, campaignType, dryRun));
142
140
  }
@@ -1150,6 +1150,22 @@ export declare const v4: Elysia<"/v4", false, {
1150
1150
  };
1151
1151
  };
1152
1152
  };
1153
+ } & {
1154
+ "dry-run": {
1155
+ "dynamic-data": {
1156
+ post: {
1157
+ body: string[];
1158
+ params: {};
1159
+ query: unknown;
1160
+ headers: {
1161
+ authorization: string;
1162
+ };
1163
+ response: {
1164
+ 200: unknown[];
1165
+ };
1166
+ };
1167
+ };
1168
+ };
1153
1169
  } & {
1154
1170
  "dry-run": {
1155
1171
  metadata: {