@merkl/api 0.19.34 → 0.19.36

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.
@@ -157,3 +157,9 @@ export const RewardV3Dto = t.Record(t.String({ title: "Chain" }), t.Object({
157
157
  symbol: t.String(),
158
158
  })),
159
159
  }), { $id: "Rewards", additionalProperties: true });
160
+ export const QueryTotalDailyRewardsSinceDto = t.Object({
161
+ since: t.Date(),
162
+ chainId: t.Optional(t.Numeric()),
163
+ protocol: t.Optional(t.String()),
164
+ type: t.Optional(t.String()),
165
+ });
@@ -1,4 +1,4 @@
1
- import type { ChainId } from "@sdk";
1
+ import { type ChainId } from "@sdk";
2
2
  import type { BreakdownForCampaignsRaw, CampaignIdModel, CampaignIdWithoutPageModel, CreateManyBreakdownModel, CreateManyRewardModel, TokenIdModel } from "./reward.model";
3
3
  export declare abstract class RewardRepository {
4
4
  static createManyReward(rewards: CreateManyRewardModel): Promise<import("database/api/.generated/runtime/library").GetBatchResult>;
@@ -153,4 +153,12 @@ export declare abstract class RewardRepository {
153
153
  amount: string;
154
154
  claimed: string;
155
155
  }[]>;
156
+ /**
157
+ * Calculates the sum of daily average rewards for a given opportunity since a specified timestamp.
158
+ *
159
+ * @param since - The timestamp (in seconds) from which to start calculating the rewards.
160
+ * @param opportunityId - The ID of the opportunity for which to calculate the rewards.
161
+ * @returns The sum of daily average rewards for the specified opportunity since the given timestamp.
162
+ */
163
+ static sumDailyRewardsAvgByOpportunity(since: number, opportunityId: string): Promise<number>;
156
164
  }
@@ -2,6 +2,7 @@ import { Campaign, Reward, RewardBreakdown } from "@db/api:drizzle";
2
2
  import { TokenService } from "@/modules/v4/token/token.service";
3
3
  import { UserService } from "@/modules/v4/user/user.service";
4
4
  import { apiDbClient } from "@db";
5
+ import { DAY } from "@sdk";
5
6
  import { and, eq, exists, inArray, sql } from "drizzle-orm";
6
7
  import { CampaignService } from "../campaign";
7
8
  import { RewardService } from "./reward.service";
@@ -302,4 +303,30 @@ export class RewardRepository {
302
303
  },
303
304
  });
304
305
  }
306
+ /**
307
+ * Calculates the sum of daily average rewards for a given opportunity since a specified timestamp.
308
+ *
309
+ * @param since - The timestamp (in seconds) from which to start calculating the rewards.
310
+ * @param opportunityId - The ID of the opportunity for which to calculate the rewards.
311
+ * @returns The sum of daily average rewards for the specified opportunity since the given timestamp.
312
+ */
313
+ static async sumDailyRewardsAvgByOpportunity(since, opportunityId) {
314
+ const dateToRewards = new Map();
315
+ const dailyAvg = [];
316
+ const records = await apiDbClient.dailyRewardsRecord.findMany({
317
+ where: { opportunityId, timestamp: { gte: since }, total: { gt: 0 } },
318
+ select: { timestamp: true, total: true },
319
+ });
320
+ if (records.length === 0)
321
+ return 0;
322
+ for (const record of records) {
323
+ const date = Math.floor(Number(record.timestamp) / DAY).toString();
324
+ const rewards = dateToRewards.get(date) ?? [];
325
+ rewards.push(record.total);
326
+ dateToRewards.set(date, rewards);
327
+ }
328
+ for (const rewards of dateToRewards.values())
329
+ dailyAvg.push(rewards.reduce((prev, curr) => prev + curr, 0) / rewards.length);
330
+ return dailyAvg.reduce((prev, curr) => prev + curr, 0);
331
+ }
305
332
  }
@@ -8,6 +8,171 @@ export declare abstract class RewardService {
8
8
  static hashId(root: string, recipient: string, rewardTokenId: string): string;
9
9
  static hashDailyRewardsRecordId(opportunityId: string, timestamp: bigint): string;
10
10
  static createManyReward(rewards: CreateManyRewardModel): Promise<import("database/api/.generated/runtime/library").GetBatchResult>;
11
+ static getTotalDistributedByOpportunities(since: number): Promise<Map<{
12
+ apr: number;
13
+ aprRecord: {
14
+ cumulated: number;
15
+ timestamp: bigint;
16
+ breakdowns: {
17
+ id: number;
18
+ type: import("@db/api").$Enums.AprType;
19
+ identifier: string;
20
+ value: number;
21
+ aprRecordId: string;
22
+ }[];
23
+ };
24
+ tvlRecord: {
25
+ id: string;
26
+ total: number;
27
+ timestamp: bigint;
28
+ breakdowns: {
29
+ id: number;
30
+ type: import("@db/api").$Enums.TvlType;
31
+ identifier: string;
32
+ value: number;
33
+ tvlRecordId: string;
34
+ }[];
35
+ };
36
+ rewardsRecord: {
37
+ id: string;
38
+ total: number;
39
+ timestamp: bigint;
40
+ breakdowns: {
41
+ id: number;
42
+ value: number;
43
+ campaignId: string;
44
+ dailyRewardsRecordId: string;
45
+ token: {
46
+ symbol: string;
47
+ id: string;
48
+ name: string | null;
49
+ icon: string;
50
+ address: string;
51
+ chainId: number;
52
+ decimals: number;
53
+ displaySymbol: string;
54
+ verified: boolean;
55
+ isTest: boolean;
56
+ isPoint: boolean;
57
+ isNative: boolean;
58
+ price: number | null;
59
+ };
60
+ amount: bigint;
61
+ }[];
62
+ };
63
+ campaigns: {
64
+ params: any;
65
+ chain: {
66
+ id: number;
67
+ name: string;
68
+ icon: string;
69
+ };
70
+ endTimestamp: number;
71
+ startTimestamp: number;
72
+ rewardToken: {
73
+ symbol: string;
74
+ id: string;
75
+ name: string | null;
76
+ icon: string;
77
+ address: string;
78
+ chainId: number;
79
+ decimals: number;
80
+ verified: boolean;
81
+ isTest: boolean;
82
+ isPoint: boolean;
83
+ isNative: boolean;
84
+ } & {
85
+ price?: number | null | undefined;
86
+ };
87
+ distributionChain: {
88
+ id: number;
89
+ name: string;
90
+ icon: string;
91
+ } | undefined;
92
+ campaignStatus: {
93
+ computedUntil: number;
94
+ processingStarted: number;
95
+ error: string;
96
+ status: import("@db/api").$Enums.RunStatus;
97
+ details: import("database/api/.generated/runtime/library").JsonValue;
98
+ campaignId: string;
99
+ } | undefined;
100
+ creatorAddress: string;
101
+ creator: {
102
+ tags: string[];
103
+ address: string;
104
+ creatorId: string | null;
105
+ };
106
+ createdAt: string;
107
+ Opportunity: {
108
+ id: string;
109
+ name: string;
110
+ type: string;
111
+ status: import("@db/api").$Enums.Status;
112
+ tags: string[];
113
+ identifier: string;
114
+ action: import("@db/api").$Enums.OpportunityAction;
115
+ chainId: number;
116
+ depositUrl: string | null;
117
+ mainProtocolId: string | null;
118
+ tvl: number;
119
+ apr: number;
120
+ dailyRewards: number;
121
+ };
122
+ id: string;
123
+ type: string;
124
+ subType: number | null;
125
+ computeChainId: number;
126
+ distributionChainId: number;
127
+ campaignId: string;
128
+ rewardTokenId: string;
129
+ amount: string;
130
+ opportunityId: string;
131
+ }[] | undefined;
132
+ id: string;
133
+ depositUrl: string | undefined;
134
+ tokens: ({
135
+ symbol: string;
136
+ id: string;
137
+ name: string | null;
138
+ icon: string;
139
+ address: string;
140
+ chainId: number;
141
+ decimals: number;
142
+ verified: boolean;
143
+ isTest: boolean;
144
+ isPoint: boolean;
145
+ isNative: boolean;
146
+ } & {
147
+ price?: number | null | undefined;
148
+ })[];
149
+ chain: {
150
+ id: number;
151
+ name: string;
152
+ icon: string;
153
+ };
154
+ protocol: {
155
+ id: string;
156
+ name: string;
157
+ url: string;
158
+ description: string;
159
+ tags: string[];
160
+ icon: string;
161
+ } | undefined;
162
+ name: string;
163
+ type: string;
164
+ status: import("@db/api").$Enums.Status;
165
+ tags: string[];
166
+ identifier: string;
167
+ action: import("@db/api").$Enums.OpportunityAction;
168
+ chainId: number;
169
+ tvl: number;
170
+ dailyRewards: number;
171
+ }, number>>;
172
+ static getTotalDistributed(since: number): Promise<number>;
173
+ static getTotalDistributedByChains(since: number): Promise<any>;
174
+ static getTotalDistributedByProtocol(since: number): Promise<any>;
175
+ static getTotalDistributedByType(since: number): Promise<any>;
11
176
  /**
12
177
  * Format the reward breakdown to conform to its resource model declaration
13
178
  * @param breakdown straight from db
@@ -20,6 +20,56 @@ export class RewardService {
20
20
  static async createManyReward(rewards) {
21
21
  return await RewardRepository.createManyReward(rewards);
22
22
  }
23
+ static async getTotalDistributedByOpportunities(since) {
24
+ const opportunities = await OpportunityService.findMany({ items: 0 });
25
+ const promiseArray = [];
26
+ let i = 0;
27
+ const oppToDailyRewards = new Map();
28
+ for (i = 0; i < opportunities.length; i++) {
29
+ promiseArray.push(RewardRepository.sumDailyRewardsAvgByOpportunity(since, opportunities[i].id));
30
+ }
31
+ const result = await Promise.all(promiseArray);
32
+ i = 0;
33
+ for (i = 0; i < result.length; i++) {
34
+ // need an upper threshold bc certain opportunities have crazy daily rewards (cf. vicuna)
35
+ if (result[i] !== null && result[i] > 0 && result[i] <= 1_000_000_000)
36
+ oppToDailyRewards.set(opportunities[i], result[i]);
37
+ }
38
+ return oppToDailyRewards;
39
+ }
40
+ static async getTotalDistributed(since) {
41
+ const oppToDailyRewards = await RewardService.getTotalDistributedByOpportunities(since);
42
+ return oppToDailyRewards.values().reduce((prev, curr) => prev + curr, 0);
43
+ }
44
+ static async getTotalDistributedByChains(since) {
45
+ const oppToDailyRewards = await RewardService.getTotalDistributedByOpportunities(since);
46
+ const chainsToDailyRewards = new Map();
47
+ for (const [opp, dr] of oppToDailyRewards.entries()) {
48
+ const dailyRewards = chainsToDailyRewards.get(opp.chainId) === undefined ? dr : chainsToDailyRewards.get(opp.chainId) + dr;
49
+ chainsToDailyRewards.set(opp.chainId, dailyRewards);
50
+ }
51
+ return Object.fromEntries(chainsToDailyRewards);
52
+ }
53
+ static async getTotalDistributedByProtocol(since) {
54
+ const oppToDailyRewards = await RewardService.getTotalDistributedByOpportunities(since);
55
+ const protocolToDailyRewards = new Map();
56
+ for (const [opp, dr] of oppToDailyRewards.entries()) {
57
+ const dailyRewards = protocolToDailyRewards.get(opp.protocol?.id) === undefined
58
+ ? dr
59
+ : protocolToDailyRewards.get(opp.protocol?.id) + dr;
60
+ protocolToDailyRewards.set(opp.protocol?.id, dailyRewards);
61
+ }
62
+ return Object.fromEntries(protocolToDailyRewards);
63
+ }
64
+ static async getTotalDistributedByType(since) {
65
+ const oppToDailyRewards = await RewardService.getTotalDistributedByOpportunities(since);
66
+ const typeToDailyRewards = new Map();
67
+ for (const [opp, dr] of oppToDailyRewards.entries()) {
68
+ const dailyRewards = typeToDailyRewards.get(opp.type) === undefined ? dr : typeToDailyRewards.get(opp.type) + dr;
69
+ typeToDailyRewards.set(opp.type, dailyRewards);
70
+ }
71
+ return Object.fromEntries(typeToDailyRewards);
72
+ }
23
73
  /**
24
74
  * Format the reward breakdown to conform to its resource model declaration
25
75
  * @param breakdown straight from db
@@ -1143,48 +1143,44 @@ export declare const v4: Elysia<"/v4", false, {
1143
1143
  };
1144
1144
  } & {
1145
1145
  "dry-run": {
1146
- ":campaignId": {
1147
- metadata: {
1148
- get: {
1149
- body: unknown;
1150
- params: {
1151
- campaignId: string;
1152
- };
1153
- query: {
1154
- campaignId: string;
1155
- distributionChain: number;
1156
- };
1157
- headers: {
1158
- authorization: string;
1159
- };
1160
- response: {
1161
- 200: {
1146
+ metadata: {
1147
+ get: {
1148
+ body: unknown;
1149
+ params: {};
1150
+ query: {
1151
+ campaignId: string;
1152
+ distributionChain: number;
1153
+ };
1154
+ headers: {
1155
+ authorization: string;
1156
+ };
1157
+ response: {
1158
+ 200: {
1159
+ id: string;
1160
+ chainId: number;
1161
+ type: string;
1162
+ identifier: string;
1163
+ name: string;
1164
+ status: "PAST" | "LIVE" | "SOON";
1165
+ action: any;
1166
+ tokens: ({
1167
+ symbol: string;
1162
1168
  id: string;
1169
+ name: string | null;
1170
+ icon: string;
1171
+ address: string;
1163
1172
  chainId: number;
1164
- type: string;
1165
- identifier: string;
1166
- name: string;
1167
- status: "PAST" | "LIVE" | "SOON";
1168
- action: any;
1169
- tokens: ({
1170
- symbol: string;
1171
- id: string;
1172
- name: string | null;
1173
- icon: string;
1174
- address: string;
1175
- chainId: number;
1176
- decimals: number;
1177
- verified: boolean;
1178
- isTest: boolean;
1179
- isPoint: boolean;
1180
- isNative: boolean;
1181
- } & {
1182
- price?: number | null | undefined;
1183
- })[];
1184
- mainProtocol: "splice" | "morpho" | "euler" | "ambient" | "uniswap" | "arthswap" | "base-swap" | "camelot" | "crust" | "fenix" | "horiza" | "izumi" | "kim" | "pancake-swap" | "quick-swap" | "ramses" | "retro" | "stryke" | "sushiswap" | "swapr" | "thruster" | "voltage" | "zero" | "koi" | "supswap" | "zk-swap" | "thirdtrade" | "swap-x" | "velodrome" | "aerodrome" | "balancer" | "curve" | "cross_curve" | "curveNPool" | "aura" | "akron" | "beefy" | "dragonswap" | "poolside" | "syncswap" | "neptune" | "zkSwapThreePool" | "rfx" | "ra" | "maverick" | "trader-joe" | "hanji" | "radiant" | "aave" | "fraxlend" | "ironclad" | "gearbox" | "compound" | "sturdy" | "frax" | "ionic" | "moonwell" | "fluid" | "silo" | "dolomite" | "badger" | "ajna" | "layerbank" | "ion" | "venus" | "woofi" | "reactor_fusion" | "eigenlayer" | "vest" | "zerolend" | "hyperdrive" | "gamma" | "oku" | "hourglass" | "veda" | "kyo" | "sonex" | "lendle" | "tako-tako" | "equalizer" | "spectra" | "beraborrow" | "superlend" | "avalon" | "angles" | "enzyme" | "toros" | "vicuna" | "bunni" | "beratrax" | "concrete" | "cian" | "pendle" | "yei" | "filament" | "gammaswap" | "maha" | "tempest" | "uranium" | "holdstation" | "katana" | "satlayer" | undefined;
1185
- depositUrl: any;
1186
- tags: string[];
1187
- };
1173
+ decimals: number;
1174
+ verified: boolean;
1175
+ isTest: boolean;
1176
+ isPoint: boolean;
1177
+ isNative: boolean;
1178
+ } & {
1179
+ price?: number | null | undefined;
1180
+ })[];
1181
+ mainProtocol: "splice" | "morpho" | "euler" | "ambient" | "uniswap" | "arthswap" | "base-swap" | "camelot" | "crust" | "fenix" | "horiza" | "izumi" | "kim" | "pancake-swap" | "quick-swap" | "ramses" | "retro" | "stryke" | "sushiswap" | "swapr" | "thruster" | "voltage" | "zero" | "koi" | "supswap" | "zk-swap" | "thirdtrade" | "swap-x" | "velodrome" | "aerodrome" | "balancer" | "curve" | "cross_curve" | "curveNPool" | "aura" | "akron" | "beefy" | "dragonswap" | "poolside" | "syncswap" | "neptune" | "zkSwapThreePool" | "rfx" | "ra" | "maverick" | "trader-joe" | "hanji" | "radiant" | "aave" | "fraxlend" | "ironclad" | "gearbox" | "compound" | "sturdy" | "frax" | "ionic" | "moonwell" | "fluid" | "silo" | "dolomite" | "badger" | "ajna" | "layerbank" | "ion" | "venus" | "woofi" | "reactor_fusion" | "eigenlayer" | "vest" | "zerolend" | "hyperdrive" | "gamma" | "oku" | "hourglass" | "veda" | "kyo" | "sonex" | "lendle" | "tako-tako" | "equalizer" | "spectra" | "beraborrow" | "superlend" | "avalon" | "angles" | "enzyme" | "toros" | "vicuna" | "bunni" | "beratrax" | "concrete" | "cian" | "pendle" | "yei" | "filament" | "gammaswap" | "maha" | "tempest" | "uranium" | "holdstation" | "katana" | "satlayer" | undefined;
1182
+ depositUrl: any;
1183
+ tags: string[];
1188
1184
  };
1189
1185
  };
1190
1186
  };
@@ -1626,7 +1622,9 @@ export declare const v4: Elysia<"/v4", false, {
1626
1622
  withOpportunity?: boolean | undefined;
1627
1623
  createdAfter?: Date | null | undefined;
1628
1624
  };
1629
- headers: unknown;
1625
+ headers: {
1626
+ authorization: string;
1627
+ };
1630
1628
  response: {
1631
1629
  200: {
1632
1630
  [x: string]: number;
@@ -1638,7 +1636,7 @@ export declare const v4: Elysia<"/v4", false, {
1638
1636
  } & {
1639
1637
  campaigns: {
1640
1638
  count: {
1641
- "by-type": {
1639
+ "by-types": {
1642
1640
  get: {
1643
1641
  body: unknown;
1644
1642
  params: {};
@@ -1666,7 +1664,9 @@ export declare const v4: Elysia<"/v4", false, {
1666
1664
  withOpportunity?: boolean | undefined;
1667
1665
  createdAfter?: Date | null | undefined;
1668
1666
  };
1669
- headers: unknown;
1667
+ headers: {
1668
+ authorization: string;
1669
+ };
1670
1670
  response: {
1671
1671
  200: {
1672
1672
  [x: string]: number;
@@ -1679,7 +1679,7 @@ export declare const v4: Elysia<"/v4", false, {
1679
1679
  } & {
1680
1680
  campaigns: {
1681
1681
  count: {
1682
- "by-protocol": {
1682
+ "by-protocols": {
1683
1683
  get: {
1684
1684
  body: unknown;
1685
1685
  params: {};
@@ -1707,7 +1707,9 @@ export declare const v4: Elysia<"/v4", false, {
1707
1707
  withOpportunity?: boolean | undefined;
1708
1708
  createdAfter?: Date | null | undefined;
1709
1709
  };
1710
- headers: unknown;
1710
+ headers: {
1711
+ authorization: string;
1712
+ };
1711
1713
  response: {
1712
1714
  200: {
1713
1715
  [x: string]: number;
@@ -2535,6 +2537,117 @@ export declare const v4: Elysia<"/v4", false, {
2535
2537
  };
2536
2538
  };
2537
2539
  };
2540
+ } & {
2541
+ rewards: {
2542
+ total: {
2543
+ distributed: {
2544
+ get: {
2545
+ body: unknown;
2546
+ params: {};
2547
+ query: {
2548
+ since: Date;
2549
+ };
2550
+ headers: {
2551
+ authorization: string;
2552
+ };
2553
+ response: {
2554
+ 200: number;
2555
+ };
2556
+ };
2557
+ };
2558
+ };
2559
+ };
2560
+ } & {
2561
+ rewards: {
2562
+ total: {
2563
+ distributed: {
2564
+ "by-opportunities": {
2565
+ get: {
2566
+ body: unknown;
2567
+ params: {};
2568
+ query: {
2569
+ since: Date;
2570
+ };
2571
+ headers: {
2572
+ authorization: string;
2573
+ };
2574
+ response: {
2575
+ 200: string;
2576
+ };
2577
+ };
2578
+ };
2579
+ };
2580
+ };
2581
+ };
2582
+ } & {
2583
+ rewards: {
2584
+ total: {
2585
+ distributed: {
2586
+ "by-chains": {
2587
+ get: {
2588
+ body: unknown;
2589
+ params: {};
2590
+ query: {
2591
+ since: Date;
2592
+ };
2593
+ headers: {
2594
+ authorization: string;
2595
+ };
2596
+ response: {
2597
+ [x: string]: any;
2598
+ 200: any;
2599
+ };
2600
+ };
2601
+ };
2602
+ };
2603
+ };
2604
+ };
2605
+ } & {
2606
+ rewards: {
2607
+ total: {
2608
+ distributed: {
2609
+ "by-types": {
2610
+ get: {
2611
+ body: unknown;
2612
+ params: {};
2613
+ query: {
2614
+ since: Date;
2615
+ };
2616
+ headers: {
2617
+ authorization: string;
2618
+ };
2619
+ response: {
2620
+ [x: string]: any;
2621
+ 200: any;
2622
+ };
2623
+ };
2624
+ };
2625
+ };
2626
+ };
2627
+ };
2628
+ } & {
2629
+ rewards: {
2630
+ total: {
2631
+ distributed: {
2632
+ "by-protocols": {
2633
+ get: {
2634
+ body: unknown;
2635
+ params: {};
2636
+ query: {
2637
+ since: Date;
2638
+ };
2639
+ headers: {
2640
+ authorization: string;
2641
+ };
2642
+ response: {
2643
+ [x: string]: any;
2644
+ 200: any;
2645
+ };
2646
+ };
2647
+ };
2648
+ };
2649
+ };
2650
+ };
2538
2651
  };
2539
2652
  } & {
2540
2653
  v4: {
@@ -70,6 +70,15 @@ export class TokenService {
70
70
  }
71
71
  static async fetchOnChain(token) {
72
72
  const onchainData = await TokenRepository.getTokenInfo(token);
73
+ try {
74
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
75
+ onchainData.name = Buffer.from(onchainData.name.replace(/\u0000/g, ""), "utf-8").toString("utf-8");
76
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: <explanation>
77
+ onchainData.symbol = Buffer.from(onchainData.symbol.replace(/\u0000/g, ""), "utf-8").toString("utf-8");
78
+ }
79
+ catch (e) {
80
+ console.error(e);
81
+ }
73
82
  return {
74
83
  chainId: token.chainId,
75
84
  address: token.address,