@merkl/api 0.20.91 → 0.20.93

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.
@@ -43,6 +43,7 @@ const redisConfig = {
43
43
  keepAlive: 5_000,
44
44
  },
45
45
  password: REDISPASSWORD,
46
+ pingInterval: 10_000,
46
47
  };
47
48
  export const redisClient = createClient(redisConfig);
48
49
  redisClient.on("error", error => {
@@ -1,9 +1,11 @@
1
1
  import { Campaign } from "@sdk";
2
2
  import { AjnaCampaignTVLBuilder } from "./implementations/Ajna";
3
+ import { AmbiantCampaignTVLBuilder } from "./implementations/Ambiant";
3
4
  // @dev Casts are made to enforce type safety
4
5
  // @dev A type error must be thrown if a new campaign type is added and the corresponding builder is not implemented
5
6
  const map = {
6
7
  [Campaign.AJNA]: new AjnaCampaignTVLBuilder(),
8
+ [Campaign.AMBIENTPROCESSOR]: new AmbiantCampaignTVLBuilder(),
7
9
  };
8
10
  export const campaignDynamicDataBuilderFactory = (campaignType) => {
9
11
  if (!map[campaignType]) {
@@ -1,7 +1,7 @@
1
1
  import { type Campaign, type CampaignParameters, type MerklChainId } from "@sdk";
2
- import type { DynamicDataBuilder } from "../interface";
2
+ import type { CampaignTVLBuilder, TVLData } from "../interface";
3
3
  type campaignType = Campaign.AMBIENTPROCESSOR;
4
- export declare class AmbiantDynamicData implements DynamicDataBuilder<campaignType> {
5
- build(chainId: MerklChainId, campaigns: CampaignParameters<campaignType>[]): Promise<AmbientCampaignDynamicData[]>;
4
+ export declare class AmbiantCampaignTVLBuilder implements CampaignTVLBuilder<campaignType> {
5
+ build(chainId: MerklChainId, campaigns: CampaignParameters<campaignType>[]): Promise<TVLData>;
6
6
  }
7
7
  export {};
@@ -0,0 +1,178 @@
1
+ import { TokenService } from "@/modules/v4/token/token.service";
2
+ import { TvlType } from "@db/api";
3
+ import { AmbientAddresses, AmbientLens, BN2Number, ChainInteractionService, NETWORK_LABELS, subgraphAmbientEndpoints, withRetry, } from "@sdk";
4
+ import request, { gql } from "graphql-request";
5
+ import { log } from "../../../utils/logger";
6
+ var AmbiantPositionType;
7
+ (function (AmbiantPositionType) {
8
+ AmbiantPositionType["Concentrated"] = "concentrated";
9
+ AmbiantPositionType["Ambient"] = "ambient";
10
+ AmbiantPositionType["Knockout"] = "knockout";
11
+ })(AmbiantPositionType || (AmbiantPositionType = {}));
12
+ function encodeCall(chainId, holder) {
13
+ if (holder.positionType === AmbiantPositionType.Concentrated) {
14
+ return [
15
+ {
16
+ allowFailure: true,
17
+ callData: AmbientLens.encodeFunctionData("queryRangeTokens", [
18
+ holder.owner,
19
+ holder.base,
20
+ holder.quote,
21
+ holder.poolIdx,
22
+ holder.lowerTick,
23
+ holder.upperTick,
24
+ ]),
25
+ target: AmbientAddresses[chainId].CrocQuery,
26
+ },
27
+ ];
28
+ }
29
+ return [
30
+ {
31
+ allowFailure: true,
32
+ callData: AmbientLens.encodeFunctionData("queryAmbientTokens", [
33
+ holder.owner,
34
+ holder.base,
35
+ holder.quote,
36
+ holder.poolIdx,
37
+ ]),
38
+ target: AmbientAddresses[chainId].CrocQuery,
39
+ },
40
+ ];
41
+ }
42
+ async function decodeCall(result, holder) {
43
+ let amount0 = BigInt(0n);
44
+ let amount1 = BigInt(0n);
45
+ if (holder.positionType === AmbiantPositionType.Concentrated) {
46
+ const resTok = AmbientLens.decodeFunctionResult("queryRangeTokens", result);
47
+ amount0 = BigInt(resTok[1]);
48
+ amount1 = BigInt(resTok[2]);
49
+ }
50
+ if (holder.positionType === AmbiantPositionType.Ambient) {
51
+ const resTok = AmbientLens.decodeFunctionResult("queryAmbientTokens", result);
52
+ amount0 = BigInt(resTok[1]);
53
+ amount1 = BigInt(resTok[2]);
54
+ }
55
+ return { amount0, amount1 };
56
+ }
57
+ async function poolFromCampaign(campaign) {
58
+ return {
59
+ id: campaign.campaignParameters.poolId,
60
+ mainParameter: campaign.mainParameter, // main parameter containes info of poolAddress + AMM (in case its a priority AMM)
61
+ baseToken: campaign.campaignParameters.baseToken,
62
+ quoteToken: campaign.campaignParameters.quoteToken,
63
+ poolIdx: campaign.campaignParameters.poolIdx,
64
+ potentialHolders: await fetchAmbientPotentialPositions(campaign.campaignParameters.poolId, campaign.computeChainId),
65
+ };
66
+ }
67
+ const BATCH_NUMBER = 1000;
68
+ const holdersQuery = gql `
69
+ query LiquidityChanges($poolId: String!, $minId: String!) {
70
+ liquidityChanges(
71
+ where: { pool_: { id: $poolId }, id_gt: $minId , positionType_not: "knockout"},
72
+ first: ${BATCH_NUMBER},
73
+ orderBy: pool__id
74
+ ) {
75
+ id
76
+ user
77
+ positionType
78
+ bidTick
79
+ askTick
80
+ pool {
81
+ base
82
+ quote
83
+ poolIdx
84
+ }
85
+ }
86
+ }
87
+ `;
88
+ async function fetchAmbientPotentialPositions(poolId, chainId) {
89
+ let isFullyFetched = false;
90
+ let holders = [];
91
+ let minId = "";
92
+ while (!isFullyFetched) {
93
+ const data = await withRetry(request, [
94
+ subgraphAmbientEndpoints[chainId],
95
+ holdersQuery,
96
+ {
97
+ poolId: poolId,
98
+ minId: minId,
99
+ },
100
+ ]);
101
+ const fetchedHolders = data.liquidityChanges?.map(entry => {
102
+ return {
103
+ owner: entry.user,
104
+ base: entry.pool.base,
105
+ quote: entry.pool.quote,
106
+ poolIdx: Number(entry.pool.poolIdx),
107
+ lowerTick: Number(entry.bidTick),
108
+ upperTick: Number(entry.askTick),
109
+ positionType: entry.positionType,
110
+ };
111
+ });
112
+ if (fetchedHolders.length < BATCH_NUMBER) {
113
+ isFullyFetched = true;
114
+ }
115
+ else {
116
+ minId = data.liquidityChanges[fetchedHolders.length - 1].id;
117
+ }
118
+ holders = holders.concat(fetchedHolders);
119
+ }
120
+ // Only keep unique positions
121
+ holders = Array.from(new Set(holders.map(h => JSON.stringify(h)))).map(h => JSON.parse(h));
122
+ return holders;
123
+ }
124
+ export class AmbiantCampaignTVLBuilder {
125
+ async build(chainId, campaigns) {
126
+ const tvls = [];
127
+ let calls = [];
128
+ for (const campaign of campaigns) {
129
+ const pool = await poolFromCampaign(campaign);
130
+ calls = calls.concat(...pool.potentialHolders.map(holder => encodeCall(chainId, holder)));
131
+ }
132
+ const result = await ChainInteractionService(chainId).fetchState(calls);
133
+ let i = 0;
134
+ for (const campaign of campaigns) {
135
+ const pool = await poolFromCampaign(campaign);
136
+ let baseTokenBalance = 0n;
137
+ let quoteTokenBalance = 0n;
138
+ const prevI = i;
139
+ try {
140
+ for (let index = 0; index < pool.potentialHolders.length; index++) {
141
+ const res = await decodeCall(result[i++].returnData, pool.potentialHolders[index]);
142
+ baseTokenBalance += BigInt(res.amount0);
143
+ quoteTokenBalance += BigInt(res.amount1);
144
+ }
145
+ }
146
+ catch {
147
+ log.warn(`merklDynamic data - failed to decode state of pool ${pool.id} on ${NETWORK_LABELS[chainId]}`);
148
+ i = prevI + pool.potentialHolders.length;
149
+ }
150
+ const baseTokenId = TokenService.hashId({
151
+ chainId,
152
+ address: pool.baseToken,
153
+ });
154
+ const quoteTokenId = TokenService.hashId({
155
+ chainId,
156
+ address: pool.quoteToken,
157
+ });
158
+ tvls.push({
159
+ campaign,
160
+ tvl: (await TokenService.getValueByTokenId(baseTokenId, baseTokenBalance)) +
161
+ (await TokenService.getValueByTokenId(quoteTokenId, quoteTokenBalance)),
162
+ tvlBreakdown: [
163
+ {
164
+ identifier: baseTokenId,
165
+ type: TvlType.TOKEN,
166
+ value: BN2Number(baseTokenBalance, campaign.campaignParameters.decimalsBaseToken),
167
+ },
168
+ {
169
+ identifier: quoteTokenId,
170
+ type: TvlType.TOKEN,
171
+ value: BN2Number(quoteTokenBalance, campaign.campaignParameters.decimalsQuoteToken),
172
+ },
173
+ ],
174
+ });
175
+ }
176
+ return tvls;
177
+ }
178
+ }
@@ -1,6 +1,5 @@
1
1
  import { Campaign } from "@sdk";
2
2
  import { AjnaDynamicData } from "./implementations/Ajna";
3
- import { AmbiantDynamicData } from "./implementations/Ambiant";
4
3
  import { BadgerDynamicData } from "./implementations/Badger";
5
4
  import { ClammDynamicData } from "./implementations/Clamm";
6
5
  import { CompoundDynamicData } from "./implementations/Compound";
@@ -26,7 +25,7 @@ const map = {
26
25
  [Campaign.INVALID]: new DefaultDynamicData(),
27
26
  [Campaign.JSON_AIRDROP]: new DefaultDynamicData(),
28
27
  [Campaign.AJNA]: new AjnaDynamicData(),
29
- [Campaign.AMBIENTPROCESSOR]: new AmbiantDynamicData(),
28
+ [Campaign.AMBIENTPROCESSOR]: new DefaultDynamicData(),
30
29
  [Campaign.BADGER]: new BadgerDynamicData(),
31
30
  [Campaign.CLAMM]: new ClammDynamicData(),
32
31
  [Campaign.COMPOUND]: new CompoundDynamicData(),
@@ -13,7 +13,7 @@ const CAMPAIGNS_BY_CHAINS_DATABASE_ID = "1cbcfed0d48c8001b1ccf5c0900669ea";
13
13
  const CAMPAIGNS_BY_PROTOCOLS_DATABASE_ID = "1cecfed0d48c8066b016f580e54a6b33";
14
14
  const CAMPAIGNS_BY_TYPES_DATABASE_ID = "1cecfed0d48c80dcb6bccf3ac3889b5b";
15
15
  const getCurrentMonthPages = async (databaseId, firstDayOfMonth) => {
16
- const pagesToModify = await notion.databases.query({
16
+ const currentMonthPages = await notion.databases.query({
17
17
  database_id: databaseId,
18
18
  filter: {
19
19
  property: "From",
@@ -22,57 +22,82 @@ const getCurrentMonthPages = async (databaseId, firstDayOfMonth) => {
22
22
  },
23
23
  },
24
24
  });
25
- return pagesToModify.results;
25
+ return currentMonthPages.results;
26
26
  };
27
27
  const main = async () => {
28
28
  const chains = (await ChainService.findMany({})).reduce((prev, curr) => {
29
29
  return Object.assign(prev, { [curr.id]: curr.name });
30
30
  }, {});
31
31
  const today = new Date();
32
- const firstDayOfMonth = new Date(today.getFullYear(), today.getMonth(), 1).getTime() / 1000;
32
+ const firstDayOfMonth = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), 1, // Day 1 of the month
33
+ 0, // 0 hours
34
+ 0, // 0 minutes
35
+ 0, // 0 seconds
36
+ 0 // 0 milliseconds
37
+ )).getTime() / 1000;
33
38
  const promises = [
39
+ // ─── Rewards By Types ────────────────────────────────────────
34
40
  CacheService.set(TTLPresets.DAY_1, RewardService.getTotalDistributedByType, firstDayOfMonth).then(async (result) => {
35
- const pagesToModify = await getCurrentMonthPages(REWARDS_BY_TYPES_DATABASE_ID, firstDayOfMonth);
41
+ const currentMonthPages = await getCurrentMonthPages(REWARDS_BY_TYPES_DATABASE_ID, firstDayOfMonth);
36
42
  const promises = [];
37
- if (today.getDate() === 1 || pagesToModify.length === 0) {
38
- for (const [type, monthlyTotalRewards] of Object.entries(result)) {
43
+ // ─── Check If Page Already Exists ────────────────────
44
+ for (const type of Object.keys(result)) {
45
+ const page = currentMonthPages.find(page => "properties" in page &&
46
+ page.properties.Types.title[0].text.content === type);
47
+ // ─── If Page Exists, Update It ───────────────
48
+ if (page) {
49
+ promises.push(notion.pages.update({
50
+ page_id: page.id,
51
+ properties: {
52
+ Rewards: {
53
+ type: "number",
54
+ number: result[type],
55
+ },
56
+ To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
57
+ },
58
+ }));
59
+ // ─── Else, Create It ─────────────────
60
+ }
61
+ else {
39
62
  promises.push(notion.pages.create({
40
63
  parent: { type: "database_id", database_id: REWARDS_BY_TYPES_DATABASE_ID },
41
64
  properties: {
42
- Type: {
65
+ Types: {
43
66
  type: "title",
44
67
  title: [{ type: "text", text: { content: type } }],
45
68
  },
46
- Rewards: { type: "number", number: monthlyTotalRewards },
69
+ Rewards: { type: "number", number: result[type] },
47
70
  From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
48
71
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
49
72
  },
50
73
  }));
51
74
  }
52
75
  }
53
- else {
54
- for (const page of pagesToModify) {
76
+ // ─── Run All The Promises In Parallel ────────────────
77
+ await Promise.all(promises);
78
+ log.info("Total Distributed by Types data pushed to Notion successfully");
79
+ }),
80
+ // ─── Rewards By Chains ───────────────────────────────────────
81
+ CacheService.set(TTLPresets.DAY_1, RewardService.getTotalDistributedByChains, firstDayOfMonth).then(async (result) => {
82
+ const currentMonthPages = await getCurrentMonthPages(REWARDS_BY_CHAINS_DATABASE_ID, firstDayOfMonth);
83
+ const promises = [];
84
+ for (const chain of Object.keys(result)) {
85
+ const page = currentMonthPages.find(page => "properties" in page &&
86
+ page.properties.Chains.title[0].text.content ===
87
+ chains[chain]);
88
+ if (page) {
55
89
  promises.push(notion.pages.update({
56
90
  page_id: page.id,
57
91
  properties: {
58
92
  Rewards: {
59
93
  type: "number",
60
- number: result[page.properties.Type
61
- .title[0].text.content],
94
+ number: result[chain],
62
95
  },
63
96
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
64
97
  },
65
98
  }));
66
99
  }
67
- await Promise.all(promises);
68
- log.info("Total Distributed by Type data pushed to Notion successfully");
69
- }
70
- }),
71
- CacheService.set(TTLPresets.DAY_1, RewardService.getTotalDistributedByChains, firstDayOfMonth).then(async (result) => {
72
- const pagesToModify = await getCurrentMonthPages(REWARDS_BY_CHAINS_DATABASE_ID, firstDayOfMonth);
73
- const promises = [];
74
- if (today.getDate() === 1 || pagesToModify.length === 0) {
75
- for (const [chain, monthlyTotalRewards] of Object.entries(result)) {
100
+ else {
76
101
  promises.push(notion.pages.create({
77
102
  parent: { type: "database_id", database_id: REWARDS_BY_CHAINS_DATABASE_ID },
78
103
  properties: {
@@ -80,54 +105,45 @@ const main = async () => {
80
105
  type: "title",
81
106
  title: [{ type: "text", text: { content: chains[chain] } }],
82
107
  },
83
- Rewards: { type: "number", number: monthlyTotalRewards },
108
+ Rewards: { type: "number", number: result[chain] },
84
109
  From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
85
110
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
86
111
  },
87
112
  }));
88
113
  }
89
114
  }
90
- else {
91
- for (const page of pagesToModify) {
115
+ await Promise.all(promises);
116
+ log.info("Total Distributed by Chains data pushed to Notion successfully");
117
+ }),
118
+ // ─── Rewards By Protocols ────────────────────────────────────
119
+ CacheService.set(TTLPresets.DAY_1, RewardService.getTotalDistributedByProtocol, firstDayOfMonth).then(async (result) => {
120
+ const currentMonthPages = await getCurrentMonthPages(REWARDS_BY_PROTOCOLS_DATABASE_ID, firstDayOfMonth);
121
+ const promises = [];
122
+ for (const protocol of Object.keys(result)) {
123
+ const page = currentMonthPages.find(page => "properties" in page &&
124
+ page.properties.Protocols.title[0].text.content ===
125
+ protocol);
126
+ if (page) {
92
127
  promises.push(notion.pages.update({
93
128
  page_id: page.id,
94
129
  properties: {
95
130
  Rewards: {
96
131
  type: "number",
97
- number: result[Object.keys(chains).find(key => page.properties.Chains.title[0].text.content === chains[key])],
132
+ number: result[page.properties.Protocols.title[0].text.content],
98
133
  },
99
134
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
100
135
  },
101
136
  }));
102
137
  }
103
- await Promise.all(promises);
104
- log.info("Total Distributed by Chains data pushed to Notion successfully");
105
- }
106
- }),
107
- CacheService.set(TTLPresets.DAY_1, RewardService.getTotalDistributedByProtocol, firstDayOfMonth).then(async (result) => {
108
- const pagesToModify = await getCurrentMonthPages(REWARDS_BY_PROTOCOLS_DATABASE_ID, firstDayOfMonth);
109
- const promises = [];
110
- if (today.getDate() === 1 || pagesToModify.length === 0) {
111
- for (const [protocol, monthlyTotalRewards] of Object.entries(result)) {
138
+ else {
112
139
  promises.push(notion.pages.create({
113
140
  parent: { type: "database_id", database_id: REWARDS_BY_PROTOCOLS_DATABASE_ID },
114
141
  properties: {
115
142
  Protocols: { type: "title", title: [{ type: "text", text: { content: protocol } }] },
116
- Rewards: { type: "number", number: monthlyTotalRewards },
117
- From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
118
- To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
119
- },
120
- }));
121
- }
122
- }
123
- else {
124
- for (const page of pagesToModify) {
125
- promises.push(notion.pages.update({
126
- page_id: page.id,
127
- properties: {
128
- Rewards: {
129
- type: "number",
130
- number: result[page.properties.Protocols.title[0].text.content],
143
+ Rewards: { type: "number", number: result[protocol] },
144
+ From: {
145
+ type: "date",
146
+ date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] },
131
147
  },
132
148
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
133
149
  },
@@ -141,10 +157,25 @@ const main = async () => {
141
157
  CacheService.set(TTLPresets.DAY_1, CampaignService.countByChains, {
142
158
  createdAfter: new Date(firstDayOfMonth * 1000),
143
159
  }).then(async (result) => {
144
- const pagesToModify = await getCurrentMonthPages(CAMPAIGNS_BY_CHAINS_DATABASE_ID, firstDayOfMonth);
160
+ const currentMonthPages = await getCurrentMonthPages(CAMPAIGNS_BY_CHAINS_DATABASE_ID, firstDayOfMonth);
145
161
  const promises = [];
146
- if (today.getDate() === 1 || pagesToModify.length === 0) {
147
- for (const [chain, monthlyTotalCampaigns] of Object.entries(result)) {
162
+ for (const chain of Object.keys(result)) {
163
+ const page = currentMonthPages.find(page => "properties" in page &&
164
+ page.properties.Chains.title[0].text.content ===
165
+ chains[chain]);
166
+ if (page) {
167
+ promises.push(notion.pages.update({
168
+ page_id: page.id,
169
+ properties: {
170
+ Campaigns: {
171
+ type: "number",
172
+ number: result[Object.keys(chains).find(key => page.properties.Chains.title[0].text.content === chains[key])],
173
+ },
174
+ To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
175
+ },
176
+ }));
177
+ }
178
+ else {
148
179
  promises.push(notion.pages.create({
149
180
  parent: { type: "database_id", database_id: CAMPAIGNS_BY_CHAINS_DATABASE_ID },
150
181
  properties: {
@@ -152,37 +183,41 @@ const main = async () => {
152
183
  type: "title",
153
184
  title: [{ type: "text", text: { content: chains[chain] } }],
154
185
  },
155
- Campaigns: { type: "number", number: monthlyTotalCampaigns },
156
- From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
186
+ Campaigns: { type: "number", number: result[chain] },
187
+ From: {
188
+ type: "date",
189
+ date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] },
190
+ },
157
191
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
158
192
  },
159
193
  }));
160
194
  }
161
195
  }
162
- else {
163
- for (const page of pagesToModify) {
196
+ await Promise.all(promises);
197
+ log.info("Campaigns by Chains data pushed to Notion successfully");
198
+ }),
199
+ // ─── Campaigns By Protocols ──────────────────────────────────
200
+ CacheService.set(TTLPresets.DAY_1, CampaignService.countByProtocols, {
201
+ createdAfter: new Date(firstDayOfMonth * 1000),
202
+ }).then(async (result) => {
203
+ const currentMonthPages = await getCurrentMonthPages(CAMPAIGNS_BY_PROTOCOLS_DATABASE_ID, firstDayOfMonth);
204
+ const promises = [];
205
+ for (const protocol of Object.keys(result)) {
206
+ const page = currentMonthPages.find(page => "properties" in page &&
207
+ page.properties.Protocols.title[0].text.content === protocol);
208
+ if (page) {
164
209
  promises.push(notion.pages.update({
165
210
  page_id: page.id,
166
211
  properties: {
167
212
  Campaigns: {
168
213
  type: "number",
169
- number: result[Object.keys(chains).find(key => page.properties.Chains.title[0].text.content === chains[key])],
214
+ number: result[page.properties.Protocols.title[0].text.content],
170
215
  },
171
216
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
172
217
  },
173
218
  }));
174
219
  }
175
- }
176
- await Promise.all(promises);
177
- log.info("Campaigns by Chains data pushed to Notion successfully");
178
- }),
179
- CacheService.set(TTLPresets.DAY_1, CampaignService.countByProtocols, {
180
- createdAfter: new Date(firstDayOfMonth * 1000),
181
- }).then(async (result) => {
182
- const pagesToModify = await getCurrentMonthPages(CAMPAIGNS_BY_PROTOCOLS_DATABASE_ID, firstDayOfMonth);
183
- const promises = [];
184
- if (today.getDate() === 1 || pagesToModify.length === 0) {
185
- for (const [protocol, monthlyTotalCampaigns] of Object.entries(result)) {
220
+ else {
186
221
  promises.push(notion.pages.create({
187
222
  parent: { type: "database_id", database_id: CAMPAIGNS_BY_PROTOCOLS_DATABASE_ID },
188
223
  properties: {
@@ -190,37 +225,38 @@ const main = async () => {
190
225
  type: "title",
191
226
  title: [{ type: "text", text: { content: protocol } }],
192
227
  },
193
- Campaigns: { type: "number", number: monthlyTotalCampaigns },
228
+ Campaigns: { type: "number", number: result[protocol] },
194
229
  From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
195
230
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
196
231
  },
197
232
  }));
198
233
  }
199
234
  }
200
- else {
201
- for (const page of pagesToModify) {
235
+ await Promise.all(promises);
236
+ log.info("Campaigns by Protocols data pushed to Notion successfully");
237
+ }),
238
+ // ─── Campaigns By Types ──────────────────────────────────────
239
+ CacheService.set(TTLPresets.DAY_1, CampaignService.countByTypes, {
240
+ createdAfter: new Date(firstDayOfMonth * 1000),
241
+ }).then(async (result) => {
242
+ const currentMonthPages = await getCurrentMonthPages(CAMPAIGNS_BY_TYPES_DATABASE_ID, firstDayOfMonth);
243
+ const promises = [];
244
+ for (const type of Object.keys(result)) {
245
+ const page = currentMonthPages.find(page => "properties" in page &&
246
+ page.properties.Types.title[0].text.content === type);
247
+ if (page) {
202
248
  promises.push(notion.pages.update({
203
249
  page_id: page.id,
204
250
  properties: {
205
251
  Campaigns: {
206
252
  type: "number",
207
- number: result[page.properties.Protocols.title[0].text.content],
253
+ number: result[page.properties.Types.title[0].text.content],
208
254
  },
209
255
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
210
256
  },
211
257
  }));
212
258
  }
213
- }
214
- await Promise.all(promises);
215
- log.info("Campaigns by Protocols data pushed to Notion successfully");
216
- }),
217
- CacheService.set(TTLPresets.DAY_1, CampaignService.countByTypes, {
218
- createdAfter: new Date(firstDayOfMonth * 1000),
219
- }).then(async (result) => {
220
- const pagesToModify = await getCurrentMonthPages(CAMPAIGNS_BY_TYPES_DATABASE_ID, firstDayOfMonth);
221
- const promises = [];
222
- if (today.getDate() === 1 || pagesToModify.length === 0) {
223
- for (const [type, monthlyTotalCampaigns] of Object.entries(result)) {
259
+ else {
224
260
  promises.push(notion.pages.create({
225
261
  parent: { type: "database_id", database_id: CAMPAIGNS_BY_TYPES_DATABASE_ID },
226
262
  properties: {
@@ -228,22 +264,10 @@ const main = async () => {
228
264
  type: "title",
229
265
  title: [{ type: "text", text: { content: type } }],
230
266
  },
231
- Campaigns: { type: "number", number: monthlyTotalCampaigns },
232
- From: { type: "date", date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] } },
233
- To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
234
- },
235
- }));
236
- }
237
- }
238
- else {
239
- for (const page of pagesToModify) {
240
- promises.push(notion.pages.update({
241
- page_id: page.id,
242
- properties: {
243
- Campaigns: {
244
- type: "number",
245
- number: result[page.properties.Types
246
- .title[0].text.content],
267
+ Campaigns: { type: "number", number: result[type] },
268
+ From: {
269
+ type: "date",
270
+ date: { start: new Date(firstDayOfMonth * 1000).toISOString().split("T")[0] },
247
271
  },
248
272
  To: { type: "date", date: { start: today.toISOString().split("T")[0] } },
249
273
  },
@@ -27,6 +27,7 @@ import bigintToString from "@/utils/bigintToString";
27
27
  import { opentelemetry } from "@elysiajs/opentelemetry";
28
28
  import { getNodeAutoInstrumentations } from "@opentelemetry/auto-instrumentations-node";
29
29
  import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
30
+ import { RedisInstrumentation } from "@opentelemetry/instrumentation-redis-4";
30
31
  import { AlwaysOffSampler, BatchSpanProcessor } from "@opentelemetry/sdk-trace-node";
31
32
  import { PrismaInstrumentation } from "@prisma/instrumentation";
32
33
  import Elysia from "elysia";
@@ -47,9 +48,15 @@ export const v4 = new Elysia({ tags: ["v4"], prefix: "/v4" })
47
48
  instrumentations: [
48
49
  getNodeAutoInstrumentations({
49
50
  "@opentelemetry/instrumentation-redis": { enabled: false },
50
- "@opentelemetry/instrumentation-redis-4": { enabled: true },
51
51
  }),
52
52
  new PrismaInstrumentation(),
53
+ new RedisInstrumentation({
54
+ enabled: true,
55
+ requireParentSpan: false,
56
+ dbStatementSerializer: (cmdName, cmdArgs) => {
57
+ return [cmdName, ...cmdArgs].join(" ");
58
+ },
59
+ }),
53
60
  ],
54
61
  serviceName: "merkl-api",
55
62
  }))