@stackedapp/utils 0.26.1
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/blockchain_utils.d.ts +6 -0
- package/dist/blockchain_utils.js +13 -0
- package/dist/conditions.d.ts +133 -0
- package/dist/conditions.js +1295 -0
- package/dist/dynamic.d.ts +3 -0
- package/dist/dynamic.js +12 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/template.d.ts +21 -0
- package/dist/template.js +54 -0
- package/package.json +24 -0
|
@@ -0,0 +1,1295 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.meetsCompletionConditionsBeforeExpiry = exports.meetsCompletionConditions = exports.offerMeetsCompletionConditions = exports.meetsLinkedEntityOffersCondition = exports.hasCompletionConditions = exports.meetsBaseConditions = exports.DEFAULT_ENTITY_KIND = void 0;
|
|
4
|
+
exports.getMaxClaimsForDynamicCondition = getMaxClaimsForDynamicCondition;
|
|
5
|
+
exports.getMaxClaimsForDynamicGroup = getMaxClaimsForDynamicGroup;
|
|
6
|
+
exports.meetsDynamicConditions = meetsDynamicConditions;
|
|
7
|
+
exports.meetsClaimableConditions = meetsClaimableConditions;
|
|
8
|
+
exports.aggregateTokenBalances = aggregateTokenBalances;
|
|
9
|
+
const template_1 = require("./template");
|
|
10
|
+
const dynamic_1 = require("./dynamic");
|
|
11
|
+
const blockchain_utils_1 = require("./blockchain_utils");
|
|
12
|
+
exports.DEFAULT_ENTITY_KIND = '_default';
|
|
13
|
+
const meetsBaseConditions = ({ conditions, playerSnap, addDetails,
|
|
14
|
+
/** this exists if calling meetsBaseConditions from meetsCompletionConditions. but surfacing
|
|
15
|
+
* check doesn't use this since we don't have a playerOffer at surfacing time
|
|
16
|
+
*/
|
|
17
|
+
playerOffer,
|
|
18
|
+
/** Additional data like fetched token balances that isn't part of playerSnap */
|
|
19
|
+
additionalData, }) => {
|
|
20
|
+
const conditionData = [];
|
|
21
|
+
let isValid = true;
|
|
22
|
+
if (conditions?.minDaysInGame) {
|
|
23
|
+
const isDisqualify = (playerSnap.daysInGame || 0) < conditions.minDaysInGame;
|
|
24
|
+
if (addDetails) {
|
|
25
|
+
conditionData.push({
|
|
26
|
+
isMet: !isDisqualify,
|
|
27
|
+
kind: 'minDaysInGame',
|
|
28
|
+
trackerAmount: playerSnap.daysInGame || 0,
|
|
29
|
+
trackerGoal: conditions.minDaysInGame,
|
|
30
|
+
text: `More than ${conditions.minDaysInGame} Days in Game`,
|
|
31
|
+
});
|
|
32
|
+
if (isDisqualify)
|
|
33
|
+
isValid = false;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
if (isDisqualify)
|
|
37
|
+
return { isValid: false };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (conditions?.minTrustScore) {
|
|
41
|
+
const isDisqualify = (playerSnap.trustScore || 0) < conditions.minTrustScore;
|
|
42
|
+
if (addDetails) {
|
|
43
|
+
conditionData.push({
|
|
44
|
+
isMet: !isDisqualify,
|
|
45
|
+
kind: 'minTrustScore',
|
|
46
|
+
trackerAmount: playerSnap.trustScore || 0,
|
|
47
|
+
trackerGoal: conditions.minTrustScore,
|
|
48
|
+
text: `More than ${conditions.minTrustScore} Rep`,
|
|
49
|
+
});
|
|
50
|
+
if (isDisqualify)
|
|
51
|
+
isValid = false;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
if (isDisqualify)
|
|
55
|
+
return { isValid: false };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (conditions?.maxTrustScore) {
|
|
59
|
+
const isDisqualify = (playerSnap.trustScore || 0) > conditions.maxTrustScore;
|
|
60
|
+
if (addDetails) {
|
|
61
|
+
conditionData.push({
|
|
62
|
+
isMet: !isDisqualify,
|
|
63
|
+
kind: 'maxTrustScore',
|
|
64
|
+
trackerAmount: playerSnap.trustScore || 0,
|
|
65
|
+
text: `Less than ${conditions.maxTrustScore} Rep`,
|
|
66
|
+
});
|
|
67
|
+
if (isDisqualify)
|
|
68
|
+
isValid = false;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
if (isDisqualify)
|
|
72
|
+
return { isValid: false };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const key in conditions?.achievements) {
|
|
76
|
+
const a = conditions.achievements[key];
|
|
77
|
+
const playerAchData = playerSnap.achievements?.[key];
|
|
78
|
+
if (!playerAchData) {
|
|
79
|
+
const isDisqualify = true;
|
|
80
|
+
if (addDetails) {
|
|
81
|
+
conditionData.push({
|
|
82
|
+
isMet: !isDisqualify,
|
|
83
|
+
kind: 'achievements',
|
|
84
|
+
trackerAmount: 0,
|
|
85
|
+
trackerGoal: 1,
|
|
86
|
+
text: `Have the achievement ${a.name}`,
|
|
87
|
+
});
|
|
88
|
+
if (isDisqualify)
|
|
89
|
+
isValid = false;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
if (isDisqualify)
|
|
93
|
+
return { isValid: false };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (a.minCount) {
|
|
97
|
+
const isDisqualify = (playerAchData?.count || 0) < a.minCount;
|
|
98
|
+
if (addDetails) {
|
|
99
|
+
conditionData.push({
|
|
100
|
+
isMet: !isDisqualify,
|
|
101
|
+
kind: 'achievements',
|
|
102
|
+
trackerAmount: playerAchData?.count || 0,
|
|
103
|
+
trackerGoal: a.minCount,
|
|
104
|
+
text: `Have the achievement ${a.name} more than ${a.minCount} times`,
|
|
105
|
+
});
|
|
106
|
+
if (isDisqualify)
|
|
107
|
+
isValid = false;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (isDisqualify)
|
|
111
|
+
return { isValid: false };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
for (const key in conditions?.currencies) {
|
|
116
|
+
const c = conditions.currencies[key];
|
|
117
|
+
const playerCurrencyData = playerSnap.currencies?.[key];
|
|
118
|
+
if (c.min) {
|
|
119
|
+
const isDisqualify = (playerCurrencyData?.balance || 0) < c.min;
|
|
120
|
+
if (addDetails) {
|
|
121
|
+
conditionData.push({
|
|
122
|
+
isMet: !isDisqualify,
|
|
123
|
+
kind: 'currencies',
|
|
124
|
+
trackerAmount: playerCurrencyData?.balance || 0,
|
|
125
|
+
trackerGoal: c.min,
|
|
126
|
+
text: `Have more than ${c.min} ${c.name}`,
|
|
127
|
+
});
|
|
128
|
+
if (isDisqualify)
|
|
129
|
+
isValid = false;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
if (isDisqualify)
|
|
133
|
+
return { isValid: false };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (c.max) {
|
|
137
|
+
const isDisqualify = (playerCurrencyData?.balance || 0) > c.max;
|
|
138
|
+
if (addDetails) {
|
|
139
|
+
conditionData.push({
|
|
140
|
+
isMet: !isDisqualify,
|
|
141
|
+
kind: 'currencies',
|
|
142
|
+
trackerAmount: playerCurrencyData?.balance || 0,
|
|
143
|
+
text: `Have less than ${c.max} ${c.name}`,
|
|
144
|
+
});
|
|
145
|
+
if (isDisqualify)
|
|
146
|
+
isValid = false;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
if (isDisqualify)
|
|
150
|
+
return { isValid: false };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (c.in) {
|
|
154
|
+
const isDisqualify = (playerCurrencyData?.in || 0) < c.in;
|
|
155
|
+
if (addDetails) {
|
|
156
|
+
conditionData.push({
|
|
157
|
+
isMet: !isDisqualify,
|
|
158
|
+
kind: 'currencies',
|
|
159
|
+
trackerAmount: playerCurrencyData?.in || 0,
|
|
160
|
+
trackerGoal: c.in,
|
|
161
|
+
text: `Deposit at least ${c.in} ${c.name}`,
|
|
162
|
+
});
|
|
163
|
+
if (isDisqualify)
|
|
164
|
+
isValid = false;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
if (isDisqualify)
|
|
168
|
+
return { isValid: false };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (c.out) {
|
|
172
|
+
const isDisqualify = (playerCurrencyData?.out || 0) < c.out;
|
|
173
|
+
if (addDetails) {
|
|
174
|
+
conditionData.push({
|
|
175
|
+
isMet: !isDisqualify,
|
|
176
|
+
kind: 'currencies',
|
|
177
|
+
trackerAmount: playerCurrencyData?.out || 0,
|
|
178
|
+
trackerGoal: c.out,
|
|
179
|
+
text: `Withdraw at least ${c.out} ${c.name}`,
|
|
180
|
+
});
|
|
181
|
+
if (isDisqualify)
|
|
182
|
+
isValid = false;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
if (isDisqualify)
|
|
186
|
+
return { isValid: false };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
for (const key in conditions?.levels) {
|
|
191
|
+
const l = conditions.levels[key];
|
|
192
|
+
const playerLevelData = playerSnap.levels?.[key];
|
|
193
|
+
if (l.min) {
|
|
194
|
+
const isDisqualify = (playerLevelData?.level || 0) < l.min;
|
|
195
|
+
if (addDetails) {
|
|
196
|
+
conditionData.push({
|
|
197
|
+
isMet: !isDisqualify,
|
|
198
|
+
kind: 'levels',
|
|
199
|
+
trackerAmount: playerLevelData?.level || 0,
|
|
200
|
+
trackerGoal: l.min,
|
|
201
|
+
text: `Be above level ${l.min} ${l.name}`,
|
|
202
|
+
});
|
|
203
|
+
if (isDisqualify)
|
|
204
|
+
isValid = false;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
if (isDisqualify)
|
|
208
|
+
return { isValid: false };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (l.max) {
|
|
212
|
+
const isDisqualify = (playerLevelData?.level || 0) > l.max;
|
|
213
|
+
if (addDetails) {
|
|
214
|
+
conditionData.push({
|
|
215
|
+
isMet: !isDisqualify,
|
|
216
|
+
kind: 'levels',
|
|
217
|
+
trackerAmount: playerLevelData?.level || 0,
|
|
218
|
+
text: `Be under level ${l.max} ${l.name}`,
|
|
219
|
+
});
|
|
220
|
+
if (isDisqualify)
|
|
221
|
+
isValid = false;
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
if (isDisqualify)
|
|
225
|
+
return { isValid: false };
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (conditions?.quests) {
|
|
230
|
+
for (const questId in conditions.quests) {
|
|
231
|
+
const quest = conditions.quests[questId];
|
|
232
|
+
const playerQuestData = playerSnap.quests?.[questId];
|
|
233
|
+
const isDisqualify = playerQuestData ? (playerQuestData?.completions || 0) < (quest.completions || 0) : true; // if player has no data for this quest, they haven't completed it
|
|
234
|
+
if (addDetails) {
|
|
235
|
+
conditionData.push({
|
|
236
|
+
isMet: !isDisqualify,
|
|
237
|
+
kind: 'quests',
|
|
238
|
+
trackerAmount: playerQuestData?.completions || 0,
|
|
239
|
+
trackerGoal: quest.completions || 1,
|
|
240
|
+
text: quest.completions === 1
|
|
241
|
+
? `Complete the quest ${quest.name}`
|
|
242
|
+
: (quest.completions || 0) < 1
|
|
243
|
+
? `Start the quest ${quest.name}`
|
|
244
|
+
: `Complete the quest ${quest.name} ${quest.completions} times`,
|
|
245
|
+
});
|
|
246
|
+
if (isDisqualify)
|
|
247
|
+
isValid = false;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
if (isDisqualify)
|
|
251
|
+
return { isValid: false };
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const key in conditions?.memberships) {
|
|
256
|
+
const m = conditions.memberships[key];
|
|
257
|
+
const playerMembershipsData = playerSnap.memberships?.[key];
|
|
258
|
+
if (m.minCount) {
|
|
259
|
+
const isDisqualify = (playerMembershipsData?.count || 0) < m.minCount;
|
|
260
|
+
if (addDetails) {
|
|
261
|
+
conditionData.push({
|
|
262
|
+
isMet: !isDisqualify,
|
|
263
|
+
kind: 'memberships',
|
|
264
|
+
trackerAmount: playerMembershipsData?.count || 0,
|
|
265
|
+
trackerGoal: m.minCount,
|
|
266
|
+
text: m.minCount > 1 ? `Have at least ${m.minCount} ${m.name} memberships` : `Have a ${m.name} membership`,
|
|
267
|
+
});
|
|
268
|
+
if (isDisqualify)
|
|
269
|
+
isValid = false;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
if (isDisqualify)
|
|
273
|
+
return { isValid: false };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (m.maxCount) {
|
|
277
|
+
const isDisqualify = (playerMembershipsData?.count || 0) > m.maxCount;
|
|
278
|
+
if (addDetails) {
|
|
279
|
+
conditionData.push({
|
|
280
|
+
isMet: !isDisqualify,
|
|
281
|
+
kind: 'memberships',
|
|
282
|
+
trackerAmount: playerMembershipsData?.count || 0,
|
|
283
|
+
text: `Have less than ${m.maxCount} ${m.name} memberships`,
|
|
284
|
+
});
|
|
285
|
+
if (isDisqualify)
|
|
286
|
+
isValid = false;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
if (isDisqualify)
|
|
290
|
+
return { isValid: false };
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
const timeOwned = (playerMembershipsData?.expiresAt || 0) - Date.now();
|
|
294
|
+
if (m.minMs) {
|
|
295
|
+
const isDisqualify = timeOwned < m.minMs;
|
|
296
|
+
if (addDetails) {
|
|
297
|
+
conditionData.push({
|
|
298
|
+
isMet: !isDisqualify,
|
|
299
|
+
kind: 'memberships',
|
|
300
|
+
trackerAmount: Number((timeOwned / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
301
|
+
trackerGoal: Number((m.minMs / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
302
|
+
text: `Own ${m.name} membership for at least ${(m.minMs / (1000 * 60 * 60 * 24)).toFixed(1)} days`,
|
|
303
|
+
});
|
|
304
|
+
if (isDisqualify)
|
|
305
|
+
isValid = false;
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
if (isDisqualify)
|
|
309
|
+
return { isValid: false };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (m.maxMs) {
|
|
313
|
+
const isDisqualify = timeOwned > m.maxMs;
|
|
314
|
+
if (addDetails) {
|
|
315
|
+
conditionData.push({
|
|
316
|
+
isMet: !isDisqualify,
|
|
317
|
+
kind: 'memberships',
|
|
318
|
+
trackerAmount: Number((timeOwned / (1000 * 60 * 60 * 24)).toFixed(1)),
|
|
319
|
+
text: `Own ${m.name} membership for less than ${(m.maxMs / (1000 * 60 * 60 * 24)).toFixed(1)} days`,
|
|
320
|
+
});
|
|
321
|
+
if (isDisqualify)
|
|
322
|
+
isValid = false;
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
if (isDisqualify)
|
|
326
|
+
return { isValid: false };
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
for (const key in conditions?.stakedTokens) {
|
|
331
|
+
const s = conditions.stakedTokens[key];
|
|
332
|
+
const playerStakedData = playerSnap.stakedTokens?.[key];
|
|
333
|
+
if (s.min) {
|
|
334
|
+
const isDisqualify = (playerStakedData?.balance || 0) < s.min;
|
|
335
|
+
if (addDetails) {
|
|
336
|
+
conditionData.push({
|
|
337
|
+
isMet: !isDisqualify,
|
|
338
|
+
kind: 'stakedTokens',
|
|
339
|
+
trackerAmount: playerStakedData?.balance || 0,
|
|
340
|
+
trackerGoal: s.min,
|
|
341
|
+
text: `Have at least ${s.min} ${s.name} staked`,
|
|
342
|
+
});
|
|
343
|
+
if (isDisqualify)
|
|
344
|
+
isValid = false;
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
if (isDisqualify)
|
|
348
|
+
return { isValid: false };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// Validate link count conditions
|
|
353
|
+
if (conditions?.links && 'entityLinks' in playerSnap) {
|
|
354
|
+
for (const [linkType, constraint] of Object.entries(conditions.links)) {
|
|
355
|
+
// linkType should always exist. and be default is none was specified
|
|
356
|
+
const linkCount = playerSnap.entityLinks?.filter((link) => (link.kind || exports.DEFAULT_ENTITY_KIND) === linkType).length || 0;
|
|
357
|
+
if (constraint.min !== undefined) {
|
|
358
|
+
const isDisqualify = linkCount < constraint.min;
|
|
359
|
+
if (addDetails) {
|
|
360
|
+
conditionData.push({
|
|
361
|
+
isMet: !isDisqualify,
|
|
362
|
+
kind: 'links',
|
|
363
|
+
trackerAmount: linkCount,
|
|
364
|
+
trackerGoal: constraint.min,
|
|
365
|
+
text: constraint.template
|
|
366
|
+
? (0, template_1.renderTemplate)(constraint.template, {
|
|
367
|
+
current: linkCount,
|
|
368
|
+
min: constraint.min ?? 0,
|
|
369
|
+
max: constraint.max ?? 0,
|
|
370
|
+
type: linkType,
|
|
371
|
+
})
|
|
372
|
+
: `At least ${constraint.min} ${linkType} link(s)`,
|
|
373
|
+
});
|
|
374
|
+
if (isDisqualify)
|
|
375
|
+
isValid = false;
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
if (isDisqualify)
|
|
379
|
+
return { isValid: false };
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (constraint.max !== undefined) {
|
|
383
|
+
const isDisqualify = linkCount > constraint.max;
|
|
384
|
+
if (addDetails) {
|
|
385
|
+
conditionData.push({
|
|
386
|
+
isMet: !isDisqualify,
|
|
387
|
+
kind: 'links',
|
|
388
|
+
trackerAmount: linkCount,
|
|
389
|
+
text: constraint.template
|
|
390
|
+
? (0, template_1.renderTemplate)(constraint.template, {
|
|
391
|
+
current: linkCount,
|
|
392
|
+
min: constraint.min ?? 0,
|
|
393
|
+
max: constraint.max ?? 0,
|
|
394
|
+
type: linkType,
|
|
395
|
+
})
|
|
396
|
+
: `At most ${constraint.max} ${linkType} link(s)`,
|
|
397
|
+
});
|
|
398
|
+
if (isDisqualify)
|
|
399
|
+
isValid = false;
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
if (isDisqualify)
|
|
403
|
+
return { isValid: false };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
// Evaluate dynamic conditions
|
|
409
|
+
if (conditions?.dynamic?.conditions?.length) {
|
|
410
|
+
const resolvedConditions = (0, template_1.replaceDynamicConditionKeys)(conditions.dynamic.conditions, playerOffer?.trackers || {});
|
|
411
|
+
const dynamicResult = meetsDynamicConditions(playerSnap.dynamic, {
|
|
412
|
+
...conditions.dynamic,
|
|
413
|
+
conditions: resolvedConditions,
|
|
414
|
+
});
|
|
415
|
+
if (addDetails) {
|
|
416
|
+
conditionData.push({
|
|
417
|
+
isMet: dynamicResult,
|
|
418
|
+
kind: 'dynamic',
|
|
419
|
+
text: (0, template_1.renderTemplate)(conditions.dynamic.template, playerSnap.dynamic) || 'Dynamic conditions',
|
|
420
|
+
});
|
|
421
|
+
if (!dynamicResult)
|
|
422
|
+
isValid = false;
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
if (!dynamicResult)
|
|
426
|
+
return { isValid: false };
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (conditions?.identifiers?.platforms?.length && 'identifiers' in playerSnap) {
|
|
430
|
+
const playerPlatforms = new Set(playerSnap.identifiers?.map((i) => i.platform.toLowerCase()) || []);
|
|
431
|
+
const isAndBehaviour = conditions.identifiers.behaviour === 'AND';
|
|
432
|
+
const platformsToCheck = conditions.identifiers.platforms;
|
|
433
|
+
let isMet;
|
|
434
|
+
let displayText;
|
|
435
|
+
if (isAndBehaviour) {
|
|
436
|
+
isMet = platformsToCheck.every((platform) => playerPlatforms.has(platform.toLowerCase()));
|
|
437
|
+
displayText = `Link all: ${platformsToCheck.join(', ')}`;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
isMet = platformsToCheck.some((platform) => playerPlatforms.has(platform.toLowerCase()));
|
|
441
|
+
displayText = `Link any: ${platformsToCheck.join(', ')}`;
|
|
442
|
+
}
|
|
443
|
+
if (addDetails) {
|
|
444
|
+
conditionData.push({
|
|
445
|
+
isMet,
|
|
446
|
+
kind: 'identifiers',
|
|
447
|
+
trackerAmount: isMet ? 1 : 0,
|
|
448
|
+
trackerGoal: 1,
|
|
449
|
+
text: displayText,
|
|
450
|
+
});
|
|
451
|
+
if (!isMet)
|
|
452
|
+
isValid = false;
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
if (!isMet)
|
|
456
|
+
return { isValid: false };
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Evaluate token balance conditions
|
|
460
|
+
for (const tokenCond of conditions?.tokenBalances || []) {
|
|
461
|
+
const contracts = tokenCond.contracts || [];
|
|
462
|
+
let totalBalance = 0;
|
|
463
|
+
const fetchedBalances = aggregateTokenBalances(additionalData);
|
|
464
|
+
for (const contract of contracts) {
|
|
465
|
+
const balanceKey = (0, blockchain_utils_1.addressNetworkId)(contract.contractAddress, contract.network);
|
|
466
|
+
totalBalance += fetchedBalances[balanceKey] || 0;
|
|
467
|
+
}
|
|
468
|
+
if (tokenCond.min !== undefined) {
|
|
469
|
+
const isDisqualify = totalBalance < tokenCond.min;
|
|
470
|
+
if (addDetails) {
|
|
471
|
+
conditionData.push({
|
|
472
|
+
isMet: !isDisqualify,
|
|
473
|
+
kind: 'tokenBalances',
|
|
474
|
+
trackerAmount: totalBalance,
|
|
475
|
+
trackerGoal: tokenCond.min,
|
|
476
|
+
text: `Have at least ${tokenCond.min} ${tokenCond.name || 'tokens'}`,
|
|
477
|
+
});
|
|
478
|
+
if (isDisqualify)
|
|
479
|
+
isValid = false;
|
|
480
|
+
}
|
|
481
|
+
else {
|
|
482
|
+
if (isDisqualify)
|
|
483
|
+
return { isValid: false };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (tokenCond.max !== undefined) {
|
|
487
|
+
const isDisqualify = totalBalance > tokenCond.max;
|
|
488
|
+
if (addDetails) {
|
|
489
|
+
conditionData.push({
|
|
490
|
+
isMet: !isDisqualify,
|
|
491
|
+
kind: 'tokenBalances',
|
|
492
|
+
trackerAmount: totalBalance,
|
|
493
|
+
text: `Have at most ${tokenCond.max} ${tokenCond.name || 'tokens'}`,
|
|
494
|
+
});
|
|
495
|
+
if (isDisqualify)
|
|
496
|
+
isValid = false;
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
if (isDisqualify)
|
|
500
|
+
return { isValid: false };
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return { isValid, conditionData: addDetails ? conditionData : undefined };
|
|
505
|
+
};
|
|
506
|
+
exports.meetsBaseConditions = meetsBaseConditions;
|
|
507
|
+
const hasCompletionConditions = (conditions) => {
|
|
508
|
+
if (!conditions)
|
|
509
|
+
return false;
|
|
510
|
+
if (Object.keys(conditions.currencies || {}).length > 0)
|
|
511
|
+
return true;
|
|
512
|
+
if (Object.keys(conditions.levels || {}).length > 0)
|
|
513
|
+
return true;
|
|
514
|
+
if (Object.keys(conditions.stakedTokens || {}).length > 0)
|
|
515
|
+
return true;
|
|
516
|
+
if (Object.keys(conditions.memberships || {}).length > 0)
|
|
517
|
+
return true;
|
|
518
|
+
if (Object.keys(conditions.quests || {}).length > 0)
|
|
519
|
+
return true;
|
|
520
|
+
if (conditions.minTrustScore)
|
|
521
|
+
return true;
|
|
522
|
+
if (conditions.maxTrustScore)
|
|
523
|
+
return true;
|
|
524
|
+
if (conditions.achievements)
|
|
525
|
+
return true;
|
|
526
|
+
if (conditions.minDaysInGame)
|
|
527
|
+
return true;
|
|
528
|
+
if (conditions.dynamic?.conditions?.length)
|
|
529
|
+
return true;
|
|
530
|
+
if (conditions.identifiers?.platforms?.length)
|
|
531
|
+
return true;
|
|
532
|
+
const compCond = conditions;
|
|
533
|
+
if (compCond.context)
|
|
534
|
+
return true;
|
|
535
|
+
if (compCond.buyItem)
|
|
536
|
+
return true;
|
|
537
|
+
if (compCond.spendCurrency)
|
|
538
|
+
return true;
|
|
539
|
+
if (compCond.depositCurrency)
|
|
540
|
+
return true;
|
|
541
|
+
if (compCond.social)
|
|
542
|
+
return true;
|
|
543
|
+
if (compCond.login)
|
|
544
|
+
return true;
|
|
545
|
+
if (compCond.loginStreak)
|
|
546
|
+
return true;
|
|
547
|
+
if (compCond.linkedCompletions)
|
|
548
|
+
return true;
|
|
549
|
+
if (compCond.dynamicTracker?.conditions?.length)
|
|
550
|
+
return true;
|
|
551
|
+
if (conditions.tokenBalances?.length)
|
|
552
|
+
return true;
|
|
553
|
+
if (Object.keys(compCond.contractInteractions || {}).length > 0)
|
|
554
|
+
return true;
|
|
555
|
+
return false;
|
|
556
|
+
};
|
|
557
|
+
exports.hasCompletionConditions = hasCompletionConditions;
|
|
558
|
+
const meetsLinkedEntityOffersCondition = ({ linkedEntityOffers, matchingLinks, linkedPOfferMap, }) => {
|
|
559
|
+
if (!linkedPOfferMap)
|
|
560
|
+
return { isValid: false };
|
|
561
|
+
const linkedPlayerOffer_ids = [];
|
|
562
|
+
for (const link of matchingLinks) {
|
|
563
|
+
const key = `${link.playerId}:${linkedEntityOffers.offer_id}`;
|
|
564
|
+
const po = linkedPOfferMap.get(key);
|
|
565
|
+
if (po) {
|
|
566
|
+
linkedPlayerOffer_ids.push(po.instanceId.toString());
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (linkedPlayerOffer_ids.length > 0) {
|
|
570
|
+
return { isValid: true, linkedPlayerOffer_ids };
|
|
571
|
+
}
|
|
572
|
+
return { isValid: false };
|
|
573
|
+
};
|
|
574
|
+
exports.meetsLinkedEntityOffersCondition = meetsLinkedEntityOffersCondition;
|
|
575
|
+
const offerMeetsCompletionConditions = (offer, snapshot, additionalData) => {
|
|
576
|
+
return (0, exports.meetsCompletionConditions)({
|
|
577
|
+
completionConditions: offer.completionConditions || {},
|
|
578
|
+
completionTrackers: offer.completionTrackers,
|
|
579
|
+
playerSnap: snapshot,
|
|
580
|
+
playerOffer: offer,
|
|
581
|
+
addDetails: true,
|
|
582
|
+
maxClaimCount: offer.maxClaimCount,
|
|
583
|
+
additionalData,
|
|
584
|
+
});
|
|
585
|
+
};
|
|
586
|
+
exports.offerMeetsCompletionConditions = offerMeetsCompletionConditions;
|
|
587
|
+
const meetsCompletionConditions = ({ completionConditions, completionTrackers, playerSnap, playerOffer, addDetails = false, maxClaimCount, additionalData, }) => {
|
|
588
|
+
if (completionConditions) {
|
|
589
|
+
const conditions = completionConditions;
|
|
590
|
+
// For multi-claim offers, scale cumulative requirements by (claimedCount + 1)
|
|
591
|
+
const shouldScale = maxClaimCount === -1 || (maxClaimCount && maxClaimCount > 1);
|
|
592
|
+
const claimMultiplier = shouldScale ? (playerOffer.trackers?.claimedCount || 0) + 1 : 1;
|
|
593
|
+
const conditionData = [];
|
|
594
|
+
let isValid = true;
|
|
595
|
+
let maxTotalClaimsFromScaling = Infinity;
|
|
596
|
+
const updateMax = (limit) => (maxTotalClaimsFromScaling = Math.min(maxTotalClaimsFromScaling, limit));
|
|
597
|
+
if (completionConditions?.context?.id) {
|
|
598
|
+
const hasTrackedContext = completionTrackers?.context && completionConditions.context.id === completionTrackers.context;
|
|
599
|
+
const isDisqualify = !hasTrackedContext;
|
|
600
|
+
if (addDetails) {
|
|
601
|
+
conditionData.push({
|
|
602
|
+
isMet: !isDisqualify,
|
|
603
|
+
kind: 'context',
|
|
604
|
+
trackerAmount: hasTrackedContext ? 1 : 0,
|
|
605
|
+
trackerGoal: 1,
|
|
606
|
+
text: completionConditions.context.name,
|
|
607
|
+
});
|
|
608
|
+
if (isDisqualify)
|
|
609
|
+
isValid = false;
|
|
610
|
+
}
|
|
611
|
+
else {
|
|
612
|
+
if (isDisqualify)
|
|
613
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
if (conditions?.buyItem) {
|
|
617
|
+
const baseAmount = conditions.buyItem.amount || 1;
|
|
618
|
+
const scaledAmount = baseAmount * claimMultiplier;
|
|
619
|
+
const trackerValue = completionTrackers?.buyItem || 0;
|
|
620
|
+
const isDisqualify = trackerValue < scaledAmount;
|
|
621
|
+
if (shouldScale && baseAmount > 0) {
|
|
622
|
+
updateMax(Math.floor(trackerValue / baseAmount));
|
|
623
|
+
}
|
|
624
|
+
if (addDetails) {
|
|
625
|
+
conditionData.push({
|
|
626
|
+
isMet: !isDisqualify,
|
|
627
|
+
kind: 'buyItem',
|
|
628
|
+
trackerAmount: trackerValue,
|
|
629
|
+
trackerGoal: scaledAmount,
|
|
630
|
+
text: `Buy ${scaledAmount} ${conditions.buyItem.name}`,
|
|
631
|
+
});
|
|
632
|
+
if (isDisqualify)
|
|
633
|
+
isValid = false;
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
if (isDisqualify)
|
|
637
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (conditions?.spendCurrency) {
|
|
641
|
+
const baseAmount = conditions.spendCurrency.amount || 1;
|
|
642
|
+
const scaledAmount = baseAmount * claimMultiplier;
|
|
643
|
+
const trackerValue = completionTrackers?.spendCurrency || 0;
|
|
644
|
+
const isDisqualify = trackerValue < scaledAmount;
|
|
645
|
+
if (shouldScale && baseAmount > 0) {
|
|
646
|
+
updateMax(Math.floor(trackerValue / baseAmount));
|
|
647
|
+
}
|
|
648
|
+
if (addDetails) {
|
|
649
|
+
conditionData.push({
|
|
650
|
+
isMet: !isDisqualify,
|
|
651
|
+
kind: 'spendCurrency',
|
|
652
|
+
trackerAmount: trackerValue,
|
|
653
|
+
trackerGoal: scaledAmount,
|
|
654
|
+
text: `Spend ${scaledAmount} ${conditions.spendCurrency.name}`,
|
|
655
|
+
});
|
|
656
|
+
if (isDisqualify)
|
|
657
|
+
isValid = false;
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
if (isDisqualify)
|
|
661
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (conditions?.depositCurrency) {
|
|
665
|
+
const baseAmount = conditions.depositCurrency.amount || 1;
|
|
666
|
+
const scaledAmount = baseAmount * claimMultiplier;
|
|
667
|
+
const trackerValue = completionTrackers?.depositCurrency || 0;
|
|
668
|
+
const isDisqualify = trackerValue < scaledAmount;
|
|
669
|
+
if (shouldScale && baseAmount > 0) {
|
|
670
|
+
updateMax(Math.floor(trackerValue / baseAmount));
|
|
671
|
+
}
|
|
672
|
+
if (addDetails) {
|
|
673
|
+
conditionData.push({
|
|
674
|
+
isMet: !isDisqualify,
|
|
675
|
+
kind: 'depositCurrency',
|
|
676
|
+
trackerAmount: trackerValue,
|
|
677
|
+
trackerGoal: scaledAmount,
|
|
678
|
+
text: `Deposit ${scaledAmount} ${conditions.depositCurrency.name}`,
|
|
679
|
+
});
|
|
680
|
+
if (isDisqualify)
|
|
681
|
+
isValid = false;
|
|
682
|
+
}
|
|
683
|
+
else {
|
|
684
|
+
if (isDisqualify)
|
|
685
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (conditions?.login) {
|
|
689
|
+
const isMet = new Date(playerSnap.snapshotLastUpdated || 0).getTime() > new Date(playerOffer.createdAt || 0).getTime();
|
|
690
|
+
if (addDetails) {
|
|
691
|
+
conditionData.push({
|
|
692
|
+
isMet,
|
|
693
|
+
kind: 'login',
|
|
694
|
+
trackerAmount: isMet ? 1 : 0,
|
|
695
|
+
trackerGoal: 1,
|
|
696
|
+
text: `Login to the game`,
|
|
697
|
+
});
|
|
698
|
+
if (!isMet)
|
|
699
|
+
isValid = false;
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
if (!isMet)
|
|
703
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (conditions?.loginStreak) {
|
|
707
|
+
// player's login streak snapshot right now - their login streak when offer was surfaced = their login streak since the offer was surfaced
|
|
708
|
+
// if their login streak since the offer was surfaced is less than the required login streak, then they are not yet eligible for the offer
|
|
709
|
+
const streakSinceOffer = (playerSnap.loginStreak || 0) - (completionTrackers?.currentLoginStreak || 0);
|
|
710
|
+
const isDisqualify = streakSinceOffer + 1 < conditions.loginStreak;
|
|
711
|
+
if (addDetails) {
|
|
712
|
+
conditionData.push({
|
|
713
|
+
isMet: !isDisqualify,
|
|
714
|
+
kind: 'loginStreak',
|
|
715
|
+
trackerAmount: streakSinceOffer + 1,
|
|
716
|
+
trackerGoal: conditions.loginStreak,
|
|
717
|
+
text: `Login streak of ${conditions.loginStreak || 0} days`,
|
|
718
|
+
});
|
|
719
|
+
if (isDisqualify)
|
|
720
|
+
isValid = false;
|
|
721
|
+
}
|
|
722
|
+
else {
|
|
723
|
+
if (isDisqualify)
|
|
724
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (conditions?.social) {
|
|
728
|
+
const tSocialAccumulate = completionTrackers?.social;
|
|
729
|
+
const tSocialAttach = completionTrackers?.social;
|
|
730
|
+
const cSocial = completionConditions.social;
|
|
731
|
+
const mode = cSocial?.mode || 'attach';
|
|
732
|
+
const tSocial = mode === 'accumulate' ? tSocialAccumulate : tSocialAttach;
|
|
733
|
+
const hasContent = Boolean(mode === 'accumulate' ? tSocialAccumulate?.matchCount > 0 : tSocialAttach?.videoId);
|
|
734
|
+
// Only scale social metrics in accumulate mode (attach mode is single content)
|
|
735
|
+
const socialMultiplier = mode === 'accumulate' ? claimMultiplier : 1;
|
|
736
|
+
const minLikes = (cSocial?.minLikes || 0) * socialMultiplier;
|
|
737
|
+
const minViews = (cSocial?.minViews || 0) * socialMultiplier;
|
|
738
|
+
const minComments = (cSocial?.minComments || 0) * socialMultiplier;
|
|
739
|
+
const likes = tSocial?.likes || 0;
|
|
740
|
+
const views = tSocial?.views || 0;
|
|
741
|
+
const comments = tSocial?.comments || 0;
|
|
742
|
+
let isDisqualify = !hasContent;
|
|
743
|
+
if (likes < minLikes || views < minViews || comments < minComments) {
|
|
744
|
+
isDisqualify = true;
|
|
745
|
+
}
|
|
746
|
+
if (shouldScale && mode === 'accumulate' && hasContent) {
|
|
747
|
+
const baseLikes = cSocial?.minLikes || 0;
|
|
748
|
+
const baseViews = cSocial?.minViews || 0;
|
|
749
|
+
const baseComments = cSocial?.minComments || 0;
|
|
750
|
+
if (baseLikes > 0)
|
|
751
|
+
updateMax(Math.floor(likes / baseLikes));
|
|
752
|
+
if (baseViews > 0)
|
|
753
|
+
updateMax(Math.floor(views / baseViews));
|
|
754
|
+
if (baseComments > 0)
|
|
755
|
+
updateMax(Math.floor(comments / baseComments));
|
|
756
|
+
}
|
|
757
|
+
if (addDetails) {
|
|
758
|
+
const platformMap = {
|
|
759
|
+
tiktok: 'TikTok',
|
|
760
|
+
instagram: 'Instagram',
|
|
761
|
+
youtube: 'YouTube',
|
|
762
|
+
};
|
|
763
|
+
const platformText = conditions.social.platforms.map((platform) => platformMap[platform]).join(' | ');
|
|
764
|
+
const requiredWords = cSocial?.requiredWords ?? [];
|
|
765
|
+
if (mode === 'accumulate') {
|
|
766
|
+
const matchCount = tSocialAccumulate?.matchCount || 0;
|
|
767
|
+
conditionData.push({
|
|
768
|
+
isMet: hasContent,
|
|
769
|
+
kind: 'social',
|
|
770
|
+
trackerAmount: matchCount,
|
|
771
|
+
trackerGoal: 1,
|
|
772
|
+
text: hasContent
|
|
773
|
+
? `Found ${matchCount} matching ${platformText} post${matchCount !== 1 ? 's' : ''}`
|
|
774
|
+
: requiredWords.length > 0
|
|
775
|
+
? `Post ${platformText} content with ${requiredWords.map((w) => `"${w}"`).join(', ')}`
|
|
776
|
+
: `Post ${platformText} content`,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
const title = tSocialAttach?.title;
|
|
781
|
+
conditionData.push({
|
|
782
|
+
isMet: hasContent,
|
|
783
|
+
kind: 'social',
|
|
784
|
+
trackerAmount: hasContent ? 1 : 0,
|
|
785
|
+
trackerGoal: 1,
|
|
786
|
+
text: !hasContent
|
|
787
|
+
? requiredWords.length > 0
|
|
788
|
+
? `Attach a ${platformText} post with ${requiredWords.map((w) => `"${w}"`).join(', ')} in the title`
|
|
789
|
+
: `Attach a ${platformText} post`
|
|
790
|
+
: `Attached: ${title}`,
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
if (minLikes > 0) {
|
|
794
|
+
conditionData.push({
|
|
795
|
+
isMet: hasContent && likes >= minLikes,
|
|
796
|
+
kind: 'social',
|
|
797
|
+
trackerAmount: likes,
|
|
798
|
+
trackerGoal: minLikes,
|
|
799
|
+
text: mode === 'accumulate' ? `Combined ${minLikes} Likes` : `Reach ${minLikes} Likes`,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
if (minViews > 0) {
|
|
803
|
+
conditionData.push({
|
|
804
|
+
isMet: hasContent && views >= minViews,
|
|
805
|
+
kind: 'social',
|
|
806
|
+
trackerAmount: views,
|
|
807
|
+
trackerGoal: minViews,
|
|
808
|
+
text: mode === 'accumulate' ? `Combined ${minViews} Views` : `Reach ${minViews} Views`,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
if (minComments > 0) {
|
|
812
|
+
conditionData.push({
|
|
813
|
+
isMet: hasContent && comments >= minComments,
|
|
814
|
+
kind: 'social',
|
|
815
|
+
trackerAmount: comments,
|
|
816
|
+
trackerGoal: minComments,
|
|
817
|
+
text: mode === 'accumulate' ? `Combined ${minComments} Comments` : `Reach ${minComments} Comments`,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
if (isDisqualify)
|
|
821
|
+
isValid = false;
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
if (isDisqualify)
|
|
825
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
// Linked completions - wait for N linked entities to complete
|
|
829
|
+
if (conditions?.linkedCompletions?.min) {
|
|
830
|
+
const baseMin = conditions.linkedCompletions.min;
|
|
831
|
+
const currentCount = completionTrackers?.linkedCompletions || 0;
|
|
832
|
+
const scaledMin = baseMin * claimMultiplier;
|
|
833
|
+
const isDisqualify = currentCount < scaledMin;
|
|
834
|
+
if (shouldScale && baseMin > 0) {
|
|
835
|
+
updateMax(Math.floor(currentCount / baseMin));
|
|
836
|
+
}
|
|
837
|
+
if (addDetails) {
|
|
838
|
+
conditionData.push({
|
|
839
|
+
isMet: !isDisqualify,
|
|
840
|
+
kind: 'linkedCompletions',
|
|
841
|
+
trackerAmount: currentCount,
|
|
842
|
+
trackerGoal: scaledMin,
|
|
843
|
+
text: conditions.linkedCompletions.template
|
|
844
|
+
? (0, template_1.renderTemplate)(conditions.linkedCompletions.template, {
|
|
845
|
+
current: currentCount,
|
|
846
|
+
required: scaledMin,
|
|
847
|
+
})
|
|
848
|
+
: `Wait for ${scaledMin} linked ${scaledMin === 1 ? 'entity' : 'entities'} to complete`,
|
|
849
|
+
});
|
|
850
|
+
if (isDisqualify)
|
|
851
|
+
isValid = false;
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
if (isDisqualify)
|
|
855
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (conditions?.dynamicTracker?.conditions?.length) {
|
|
859
|
+
const resolvedConditions = (0, template_1.replaceDynamicConditionKeys)(conditions.dynamicTracker.conditions, playerOffer?.trackers || {});
|
|
860
|
+
// now we have the game-defined conditions with {{}} keys populated. feed these conditions into evaluator
|
|
861
|
+
const dynamicResult = meetsDynamicConditions((0, dynamic_1.dynamicTrackerToPrimitive)(completionTrackers?.dynamicTracker || {}), {
|
|
862
|
+
...conditions.dynamicTracker,
|
|
863
|
+
conditions: resolvedConditions,
|
|
864
|
+
}, claimMultiplier);
|
|
865
|
+
if (shouldScale) {
|
|
866
|
+
const dynamicMax = getMaxClaimsForDynamicGroup((0, dynamic_1.dynamicTrackerToPrimitive)(completionTrackers?.dynamicTracker || {}), {
|
|
867
|
+
...conditions.dynamicTracker,
|
|
868
|
+
conditions: resolvedConditions,
|
|
869
|
+
}, playerOffer?.trackers?.claimedCount || 0);
|
|
870
|
+
updateMax(dynamicMax);
|
|
871
|
+
}
|
|
872
|
+
if (addDetails) {
|
|
873
|
+
conditionData.push({
|
|
874
|
+
isMet: dynamicResult,
|
|
875
|
+
kind: 'dynamicTracker',
|
|
876
|
+
text: (0, template_1.renderTemplate)(conditions.dynamicTracker.template, (0, dynamic_1.dynamicTrackerToPrimitive)(completionTrackers?.dynamicTracker || {})) || 'Dynamic conditions',
|
|
877
|
+
});
|
|
878
|
+
if (!dynamicResult)
|
|
879
|
+
isValid = false;
|
|
880
|
+
}
|
|
881
|
+
else {
|
|
882
|
+
if (!dynamicResult)
|
|
883
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
// Evaluate contractInteractions completion trackers
|
|
887
|
+
if (conditions?.contractInteractions) {
|
|
888
|
+
for (const [conditionId, condition] of Object.entries(conditions.contractInteractions)) {
|
|
889
|
+
const baseAmount = condition.amount || 0;
|
|
890
|
+
const scaledAmount = baseAmount * claimMultiplier;
|
|
891
|
+
const trackerValue = completionTrackers?.contractInteractions?.[conditionId] || 0;
|
|
892
|
+
const isDisqualify = trackerValue < scaledAmount;
|
|
893
|
+
if (shouldScale && baseAmount > 0) {
|
|
894
|
+
updateMax(Math.floor(trackerValue / baseAmount));
|
|
895
|
+
}
|
|
896
|
+
if (addDetails) {
|
|
897
|
+
const displayText = (0, template_1.renderTemplate)(condition.template, {
|
|
898
|
+
current: trackerValue,
|
|
899
|
+
amount: scaledAmount,
|
|
900
|
+
});
|
|
901
|
+
conditionData.push({
|
|
902
|
+
isMet: !isDisqualify,
|
|
903
|
+
kind: 'contractInteractions',
|
|
904
|
+
trackerAmount: trackerValue,
|
|
905
|
+
trackerGoal: scaledAmount,
|
|
906
|
+
text: displayText,
|
|
907
|
+
});
|
|
908
|
+
if (isDisqualify)
|
|
909
|
+
isValid = false;
|
|
910
|
+
}
|
|
911
|
+
else {
|
|
912
|
+
if (isDisqualify)
|
|
913
|
+
return { isValid: false, availableClaimsNow: 0 };
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
const r = (0, exports.meetsBaseConditions)({
|
|
918
|
+
conditions,
|
|
919
|
+
playerSnap,
|
|
920
|
+
addDetails: true,
|
|
921
|
+
playerOffer,
|
|
922
|
+
additionalData,
|
|
923
|
+
});
|
|
924
|
+
isValid = isValid && r.isValid;
|
|
925
|
+
conditionData.push(...(r.conditionData || []));
|
|
926
|
+
if (maxClaimCount && maxClaimCount > 0) {
|
|
927
|
+
updateMax(maxClaimCount);
|
|
928
|
+
}
|
|
929
|
+
const claimedCount = playerOffer?.trackers?.claimedCount || 0;
|
|
930
|
+
const availableClaimsNow = !isValid
|
|
931
|
+
? 0
|
|
932
|
+
: maxTotalClaimsFromScaling === Infinity
|
|
933
|
+
? -1
|
|
934
|
+
: Math.max(0, maxTotalClaimsFromScaling - claimedCount);
|
|
935
|
+
return { isValid, conditionData, availableClaimsNow };
|
|
936
|
+
}
|
|
937
|
+
return { isValid: true, conditionData: [], availableClaimsNow: -1 };
|
|
938
|
+
};
|
|
939
|
+
exports.meetsCompletionConditions = meetsCompletionConditions;
|
|
940
|
+
/**
|
|
941
|
+
* Checks if completion conditions were met before a specific expiry time.
|
|
942
|
+
* Returns true if all relevant condition fields were updated before expiryTime.
|
|
943
|
+
*
|
|
944
|
+
* @param completionConditions - The completion conditions to check
|
|
945
|
+
* @param completionTrackers - The completion trackers (for buyItem, spendCurrency, etc.)
|
|
946
|
+
* @param playerSnap - The player snapshot with field timestamps
|
|
947
|
+
* @returns true if all conditions were met before expiry, false otherwise
|
|
948
|
+
*/
|
|
949
|
+
const meetsCompletionConditionsBeforeExpiry = ({ completionConditions, completionTrackers, playerSnap, playerOffer, maxClaimCount, }) => {
|
|
950
|
+
if (!completionConditions)
|
|
951
|
+
return false;
|
|
952
|
+
// Check if there are actually any conditions to evaluate
|
|
953
|
+
if (!(0, exports.hasCompletionConditions)(completionConditions))
|
|
954
|
+
return false;
|
|
955
|
+
// First check if conditions are actually met
|
|
956
|
+
const conditionsMet = (0, exports.meetsCompletionConditions)({
|
|
957
|
+
completionConditions,
|
|
958
|
+
completionTrackers,
|
|
959
|
+
playerOffer,
|
|
960
|
+
playerSnap,
|
|
961
|
+
maxClaimCount,
|
|
962
|
+
});
|
|
963
|
+
if (!conditionsMet.isValid)
|
|
964
|
+
return false;
|
|
965
|
+
if (!playerOffer.expiresAt)
|
|
966
|
+
return true;
|
|
967
|
+
const expiryTime = new Date(playerOffer.expiresAt).getTime();
|
|
968
|
+
const lastSnapshotUpdate = new Date(playerSnap.snapshotLastUpdated ?? new Date()).getTime();
|
|
969
|
+
/**
|
|
970
|
+
* Checks if a field was updated after the expiry time.
|
|
971
|
+
* Returns true if updated AFTER or AT expiry (violates grace period).
|
|
972
|
+
* Returns false if updated BEFORE expiry (allows grace period).
|
|
973
|
+
*/
|
|
974
|
+
function wasUpdatedAfterExpiry(data) {
|
|
975
|
+
let lastUpdated;
|
|
976
|
+
if (typeof data === 'object' && data !== null && !(data instanceof Date)) {
|
|
977
|
+
// Object with optional lastUpdated field
|
|
978
|
+
lastUpdated = data.lastUpdated ? new Date(data.lastUpdated).getTime() : lastSnapshotUpdate;
|
|
979
|
+
}
|
|
980
|
+
else if (data instanceof Date) {
|
|
981
|
+
lastUpdated = data.getTime();
|
|
982
|
+
}
|
|
983
|
+
else if (typeof data === 'string' || typeof data === 'number') {
|
|
984
|
+
lastUpdated = new Date(data).getTime();
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
// No data provided, use snapshot timestamp
|
|
988
|
+
lastUpdated = lastSnapshotUpdate;
|
|
989
|
+
}
|
|
990
|
+
return lastUpdated >= expiryTime;
|
|
991
|
+
}
|
|
992
|
+
if (completionConditions.currencies) {
|
|
993
|
+
for (const currencyId in completionConditions.currencies) {
|
|
994
|
+
const currency = playerSnap.currencies?.[currencyId];
|
|
995
|
+
if (!currency)
|
|
996
|
+
continue;
|
|
997
|
+
if (wasUpdatedAfterExpiry(currency))
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
if (completionConditions.levels) {
|
|
1002
|
+
for (const skillId in completionConditions.levels) {
|
|
1003
|
+
const level = playerSnap.levels?.[skillId];
|
|
1004
|
+
if (!level)
|
|
1005
|
+
continue;
|
|
1006
|
+
if (wasUpdatedAfterExpiry(level))
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
if (completionConditions.quests) {
|
|
1011
|
+
for (const questId in completionConditions.quests) {
|
|
1012
|
+
const quest = playerSnap.quests?.[questId];
|
|
1013
|
+
if (!quest)
|
|
1014
|
+
continue;
|
|
1015
|
+
if (wasUpdatedAfterExpiry(quest))
|
|
1016
|
+
return false;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (completionConditions.memberships) {
|
|
1020
|
+
for (const membershipId in completionConditions.memberships) {
|
|
1021
|
+
const membership = playerSnap.memberships?.[membershipId];
|
|
1022
|
+
if (!membership)
|
|
1023
|
+
continue;
|
|
1024
|
+
if (wasUpdatedAfterExpiry(membership))
|
|
1025
|
+
return false;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (completionConditions.achievements) {
|
|
1029
|
+
for (const achievementId in completionConditions.achievements) {
|
|
1030
|
+
const achievement = playerSnap.achievements?.[achievementId];
|
|
1031
|
+
if (!achievement)
|
|
1032
|
+
continue;
|
|
1033
|
+
if (wasUpdatedAfterExpiry(achievement))
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (completionConditions.stakedTokens) {
|
|
1038
|
+
for (const tokenId in completionConditions.stakedTokens) {
|
|
1039
|
+
const stakedToken = playerSnap.stakedTokens?.[tokenId];
|
|
1040
|
+
if (!stakedToken)
|
|
1041
|
+
continue;
|
|
1042
|
+
const lastStakeTime = new Date(stakedToken.lastStake ?? 0).getTime();
|
|
1043
|
+
const lastUnstakeTime = new Date(stakedToken.lastUnstake ?? 0).getTime();
|
|
1044
|
+
const lastUpdated = Math.max(lastStakeTime, lastUnstakeTime);
|
|
1045
|
+
if (lastUpdated >= expiryTime)
|
|
1046
|
+
return false;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
if (completionConditions.minTrustScore !== undefined || completionConditions.maxTrustScore !== undefined) {
|
|
1050
|
+
if (wasUpdatedAfterExpiry(playerSnap.trustLastUpdated))
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
if (completionConditions.minDaysInGame !== undefined) {
|
|
1054
|
+
if (wasUpdatedAfterExpiry(playerSnap.daysInGameLastUpdated))
|
|
1055
|
+
return false;
|
|
1056
|
+
}
|
|
1057
|
+
if (completionConditions.login || completionConditions.loginStreak) {
|
|
1058
|
+
if (wasUpdatedAfterExpiry())
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
if (completionConditions.social) {
|
|
1062
|
+
// Check if social content was attached/validated after expiry
|
|
1063
|
+
if (completionTrackers?.social?.lastChecked) {
|
|
1064
|
+
if (wasUpdatedAfterExpiry(completionTrackers.social.lastChecked))
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
// All conditions were met before expiry
|
|
1069
|
+
return true;
|
|
1070
|
+
};
|
|
1071
|
+
exports.meetsCompletionConditionsBeforeExpiry = meetsCompletionConditionsBeforeExpiry;
|
|
1072
|
+
/**
|
|
1073
|
+
* Checks if a dynamic object meets a set of dynamic field conditions.
|
|
1074
|
+
* @param dynamicObj - The object with any key and string or number value.
|
|
1075
|
+
* @param conditions - Array of conditions to check.
|
|
1076
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
1077
|
+
* @returns true if all conditions are met, false otherwise.
|
|
1078
|
+
*/
|
|
1079
|
+
/**
|
|
1080
|
+
* Evaluates a single dynamic condition against the dynamic object.
|
|
1081
|
+
*/
|
|
1082
|
+
function evaluateDynamicCondition(dynamicObj, cond, claimMultiplier = 1) {
|
|
1083
|
+
if (!dynamicObj)
|
|
1084
|
+
return false;
|
|
1085
|
+
const val = dynamicObj[cond.key];
|
|
1086
|
+
if (val == undefined)
|
|
1087
|
+
return false;
|
|
1088
|
+
const isNumber = typeof val === 'number';
|
|
1089
|
+
const isBoolean = typeof val === 'boolean';
|
|
1090
|
+
if (isBoolean) {
|
|
1091
|
+
switch (cond.operator) {
|
|
1092
|
+
case '==':
|
|
1093
|
+
return val === Boolean(cond.compareTo);
|
|
1094
|
+
case '!=':
|
|
1095
|
+
return val !== Boolean(cond.compareTo);
|
|
1096
|
+
default:
|
|
1097
|
+
return false;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
const compareTo = isNumber ? Number(cond.compareTo) : String(cond.compareTo);
|
|
1101
|
+
if (isNumber && typeof compareTo === 'number') {
|
|
1102
|
+
const skipMultiplier = cond.operator === '==' || cond.operator === '!=';
|
|
1103
|
+
const scaledCompareTo = skipMultiplier ? compareTo : compareTo * claimMultiplier;
|
|
1104
|
+
switch (cond.operator) {
|
|
1105
|
+
case '==':
|
|
1106
|
+
return val === scaledCompareTo;
|
|
1107
|
+
case '!=':
|
|
1108
|
+
return val !== scaledCompareTo;
|
|
1109
|
+
case '>':
|
|
1110
|
+
return val > scaledCompareTo;
|
|
1111
|
+
case '>=':
|
|
1112
|
+
return val >= scaledCompareTo;
|
|
1113
|
+
case '<':
|
|
1114
|
+
return val < scaledCompareTo;
|
|
1115
|
+
case '<=':
|
|
1116
|
+
return val <= scaledCompareTo;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
else if (!isNumber && typeof compareTo === 'string') {
|
|
1120
|
+
switch (cond.operator) {
|
|
1121
|
+
case '==':
|
|
1122
|
+
return val === compareTo;
|
|
1123
|
+
case '!=':
|
|
1124
|
+
return val !== compareTo;
|
|
1125
|
+
case 'has':
|
|
1126
|
+
return val.includes(compareTo);
|
|
1127
|
+
case 'not_has':
|
|
1128
|
+
return !val.includes(compareTo);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return false;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Calculates the maximum number of claims supported by a single dynamic condition.
|
|
1135
|
+
*/
|
|
1136
|
+
function getMaxClaimsForDynamicCondition(dynamicObj, cond) {
|
|
1137
|
+
if (!dynamicObj)
|
|
1138
|
+
return 0;
|
|
1139
|
+
const val = dynamicObj[cond.key];
|
|
1140
|
+
if (val === undefined)
|
|
1141
|
+
return 0;
|
|
1142
|
+
if (typeof val === 'number') {
|
|
1143
|
+
const base = Number(cond.compareTo);
|
|
1144
|
+
if (isNaN(base)) {
|
|
1145
|
+
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
1146
|
+
}
|
|
1147
|
+
switch (cond.operator) {
|
|
1148
|
+
case '>=':
|
|
1149
|
+
if (base === 0)
|
|
1150
|
+
return val >= 0 ? Infinity : 0;
|
|
1151
|
+
if (base < 0)
|
|
1152
|
+
return val >= base ? Infinity : 0;
|
|
1153
|
+
return Math.max(0, Math.floor(val / base));
|
|
1154
|
+
case '>':
|
|
1155
|
+
if (base === 0)
|
|
1156
|
+
return val > 0 ? Infinity : 0;
|
|
1157
|
+
if (base < 0)
|
|
1158
|
+
return val > base ? Infinity : 0;
|
|
1159
|
+
if (val <= 0)
|
|
1160
|
+
return 0;
|
|
1161
|
+
return Math.max(0, Math.ceil(val / base) - 1);
|
|
1162
|
+
case '==':
|
|
1163
|
+
return val === base ? Infinity : 0;
|
|
1164
|
+
case '!=':
|
|
1165
|
+
return val !== base ? Infinity : 0;
|
|
1166
|
+
case '<=':
|
|
1167
|
+
if (base === 0)
|
|
1168
|
+
return val <= 0 ? Infinity : 0;
|
|
1169
|
+
if (base > 0)
|
|
1170
|
+
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
1171
|
+
if (val >= 0)
|
|
1172
|
+
return 0;
|
|
1173
|
+
return Math.max(0, Math.floor(val / base));
|
|
1174
|
+
case '<':
|
|
1175
|
+
if (base === 0)
|
|
1176
|
+
return val < 0 ? Infinity : 0;
|
|
1177
|
+
if (base > 0)
|
|
1178
|
+
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
1179
|
+
if (val >= 0)
|
|
1180
|
+
return 0;
|
|
1181
|
+
return Math.max(0, Math.ceil(val / base) - 1);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
// we don't scale the rest, they are always true or always false
|
|
1185
|
+
return evaluateDynamicCondition(dynamicObj, cond, 1) ? Infinity : 0;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Calculates the maximum number of claims supported by a group of dynamic conditions.
|
|
1189
|
+
*/
|
|
1190
|
+
function getMaxClaimsForDynamicGroup(dynamicObj, dynamicGroup, currentClaimCount = 0) {
|
|
1191
|
+
const { conditions, links } = dynamicGroup;
|
|
1192
|
+
if (!conditions || conditions.length === 0)
|
|
1193
|
+
return Infinity;
|
|
1194
|
+
// AND only
|
|
1195
|
+
if (!links || links.length === 0 || links.every((l) => l === 'AND')) {
|
|
1196
|
+
let minClaims = Infinity;
|
|
1197
|
+
for (const cond of conditions) {
|
|
1198
|
+
const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
|
|
1199
|
+
if (max === 0)
|
|
1200
|
+
return 0;
|
|
1201
|
+
minClaims = Math.min(minClaims, max);
|
|
1202
|
+
}
|
|
1203
|
+
return minClaims;
|
|
1204
|
+
}
|
|
1205
|
+
// OR only
|
|
1206
|
+
if (links.every((l) => l === 'OR')) {
|
|
1207
|
+
let maxClaims = 0;
|
|
1208
|
+
for (const cond of conditions) {
|
|
1209
|
+
const max = getMaxClaimsForDynamicCondition(dynamicObj, cond);
|
|
1210
|
+
if (max === Infinity)
|
|
1211
|
+
return Infinity;
|
|
1212
|
+
maxClaims = Math.max(maxClaims, max);
|
|
1213
|
+
}
|
|
1214
|
+
return maxClaims;
|
|
1215
|
+
}
|
|
1216
|
+
// mixed:
|
|
1217
|
+
const maxIterations = 100;
|
|
1218
|
+
for (let n = currentClaimCount + 1; n <= currentClaimCount + maxIterations; n++) {
|
|
1219
|
+
if (!meetsDynamicConditions(dynamicObj, dynamicGroup, n)) {
|
|
1220
|
+
return n - 1;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return currentClaimCount + maxIterations;
|
|
1224
|
+
}
|
|
1225
|
+
/**
|
|
1226
|
+
* Evaluates a group of dynamic conditions with logical links (AND, OR, AND NOT).
|
|
1227
|
+
* @param dynamicObj - The player's dynamic object with any key and string or number value.
|
|
1228
|
+
* @param dynamicGroup - The group of conditions and links to check.
|
|
1229
|
+
* @param claimMultiplier - Multiplier to scale conditions (used for numeric comparisons).
|
|
1230
|
+
* @returns true if the group evaluates to true, false otherwise.
|
|
1231
|
+
*/
|
|
1232
|
+
function meetsDynamicConditions(dynamicObj, dynamicGroup, claimMultiplier = 1) {
|
|
1233
|
+
const { conditions, links } = dynamicGroup;
|
|
1234
|
+
if (!conditions || conditions.length === 0)
|
|
1235
|
+
return true;
|
|
1236
|
+
if (!dynamicObj)
|
|
1237
|
+
return false;
|
|
1238
|
+
// If no links, treat as AND between all conditions
|
|
1239
|
+
if (!links || links.length === 0) {
|
|
1240
|
+
return conditions.every((cond) => evaluateDynamicCondition(dynamicObj, cond, claimMultiplier));
|
|
1241
|
+
}
|
|
1242
|
+
// Evaluate the first condition
|
|
1243
|
+
let result = evaluateDynamicCondition(dynamicObj, conditions[0], claimMultiplier);
|
|
1244
|
+
for (let i = 0; i < links.length; i++) {
|
|
1245
|
+
const nextCond = evaluateDynamicCondition(dynamicObj, conditions[i + 1], claimMultiplier);
|
|
1246
|
+
const link = links[i];
|
|
1247
|
+
if (link === 'AND') {
|
|
1248
|
+
result = result && nextCond;
|
|
1249
|
+
}
|
|
1250
|
+
else if (link === 'OR') {
|
|
1251
|
+
result = result || nextCond;
|
|
1252
|
+
}
|
|
1253
|
+
else if (link === 'AND NOT') {
|
|
1254
|
+
result = result && !nextCond;
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return result;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Checks if a PlayerOffer meets its claimable conditions (completed -> claimable transition).
|
|
1261
|
+
* @param claimableConditions - The offer's claimableConditions (from IOffer)
|
|
1262
|
+
* @param claimableTrackers - The player offer's claimableTrackers
|
|
1263
|
+
*/
|
|
1264
|
+
function meetsClaimableConditions({ claimableConditions, playerOfferTrackers, claimableTrackers, }) {
|
|
1265
|
+
if (!claimableConditions) {
|
|
1266
|
+
return { isValid: true };
|
|
1267
|
+
}
|
|
1268
|
+
if (claimableConditions.siblingCompletions) {
|
|
1269
|
+
const siblingCount = playerOfferTrackers?.siblingPlayerOffer_ids?.length ?? 0;
|
|
1270
|
+
let completedCount = claimableTrackers?.siblingCompletions ?? 0;
|
|
1271
|
+
if (completedCount == -1)
|
|
1272
|
+
completedCount = siblingCount; // treat -1 as all completed
|
|
1273
|
+
// if siblings exist but not all are completed, return false
|
|
1274
|
+
if (siblingCount > 0 && completedCount < siblingCount) {
|
|
1275
|
+
return { isValid: false };
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
return { isValid: true };
|
|
1279
|
+
}
|
|
1280
|
+
// returns contractAddress:network -> balance
|
|
1281
|
+
function aggregateTokenBalances(data) {
|
|
1282
|
+
const aggregatedBalances = {};
|
|
1283
|
+
for (const { balances } of data?.cryptoWallets || []) {
|
|
1284
|
+
if (!balances)
|
|
1285
|
+
continue;
|
|
1286
|
+
for (const [key, balance] of Object.entries(balances)) {
|
|
1287
|
+
if (!aggregatedBalances[key]) {
|
|
1288
|
+
aggregatedBalances[key] = 0;
|
|
1289
|
+
}
|
|
1290
|
+
aggregatedBalances[key] += balance;
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return aggregatedBalances;
|
|
1294
|
+
}
|
|
1295
|
+
//# sourceMappingURL=conditions.js.map
|