@ak--47/dungeon-master 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +51 -13
  3. package/dungeons/technical/ad-spend.js +2 -2
  4. package/dungeons/technical/anonymous-users.js +2 -2
  5. package/dungeons/technical/array-of-object-lookup.js +2 -2
  6. package/dungeons/technical/experiments.js +2 -2
  7. package/dungeons/technical/foobar.js +2 -2
  8. package/dungeons/technical/group-analytics.js +2 -2
  9. package/dungeons/technical/mirror-strategies.js +2 -2
  10. package/dungeons/technical/nested-objects.js +2 -2
  11. package/dungeons/technical/retention-cadence.js +2 -3
  12. package/dungeons/technical/sanity.js +2 -2
  13. package/dungeons/technical/scale-test.js +2 -2
  14. package/dungeons/technical/scd.js +2 -2
  15. package/dungeons/technical/simple.js +2 -2
  16. package/dungeons/technical/simplest.js +2 -2
  17. package/dungeons/technical/text-generation.js +2 -2
  18. package/dungeons/vertical/ai-platform-schema.json +617 -0
  19. package/dungeons/vertical/ai-platform.js +799 -0
  20. package/dungeons/vertical/community.js +40 -26
  21. package/dungeons/vertical/crypto-schema.json +546 -0
  22. package/dungeons/vertical/crypto.js +721 -0
  23. package/dungeons/vertical/dating-schema.json +401 -0
  24. package/dungeons/vertical/dating.js +798 -0
  25. package/dungeons/vertical/devtools.js +13 -9
  26. package/dungeons/vertical/ecommerce.js +2 -3
  27. package/dungeons/vertical/education.js +4 -5
  28. package/dungeons/vertical/fintech.js +32 -29
  29. package/dungeons/vertical/fitness.js +2 -3
  30. package/dungeons/vertical/food-delivery.js +37 -43
  31. package/dungeons/vertical/gaming-schema.json +2495 -230
  32. package/dungeons/vertical/gaming.js +771 -388
  33. package/dungeons/vertical/healthcare.js +2 -3
  34. package/dungeons/vertical/insurance-application.js +2 -3
  35. package/dungeons/vertical/logistics.js +20 -14
  36. package/dungeons/vertical/marketplace.js +22 -14
  37. package/dungeons/vertical/media.js +39 -30
  38. package/dungeons/vertical/real-estate-schema.json +527 -0
  39. package/dungeons/vertical/real-estate.js +774 -0
  40. package/dungeons/vertical/sass.js +2 -3
  41. package/dungeons/vertical/social.js +15 -13
  42. package/dungeons/vertical/travel.js +59 -26
  43. package/lib/core/config-validator.js +71 -15
  44. package/lib/core/storage.js +14 -4
  45. package/lib/orchestrators/user-loop.js +39 -5
  46. package/lib/templates/macro-presets.js +111 -0
  47. package/lib/templates/soup-presets.js +19 -36
  48. package/package.json +8 -2
  49. package/types.d.ts +219 -42
  50. package/dungeons/user/.gitkeep +0 -0
  51. package/dungeons/vertical/rpg-schema.json +0 -2491
  52. package/dungeons/vertical/rpg.js +0 -976
@@ -0,0 +1,721 @@
1
+ // ── TWEAK THESE ──
2
+ const SEED = "coinnest";
3
+ const num_days = 120;
4
+ const num_users = 6_000;
5
+ const avg_events_per_user_per_day = 0.83;
6
+ let token = "your-mixpanel-token";
7
+
8
+ // ── env overrides ──
9
+ if (process.env.MP_TOKEN) token = process.env.MP_TOKEN;
10
+
11
+ import dayjs from "dayjs";
12
+ import utc from "dayjs/plugin/utc.js";
13
+ import "dotenv/config";
14
+ import * as u from "../../lib/utils/utils.js";
15
+ import * as v from "ak-tools";
16
+
17
+ dayjs.extend(utc);
18
+ const chance = u.initChance(SEED);
19
+
20
+ /** @typedef {import("../../types").Dungeon} Config */
21
+
22
+ /**
23
+ * ===================================================================
24
+ * DATASET OVERVIEW
25
+ * ===================================================================
26
+ *
27
+ * CoinNest — a DeFi cryptocurrency exchange platform. Users connect
28
+ * wallets, swap tokens, stake for yield, mint NFTs, claim airdrops,
29
+ * and trade. Features KYC verification, pro trading tiers, and
30
+ * portfolio tracking.
31
+ *
32
+ * Scale: 6,000 users · 600K events · 120 days · 16 event types
33
+ *
34
+ * Core loop: wallet connected → KYC → deposit → swap → stake/unstake
35
+ * → portfolio viewed → withdrawal
36
+ *
37
+ * Funnels:
38
+ * - Onboarding: wallet connected → kyc started → deposit (60%)
39
+ * - Trading: deposit → swap → withdrawal (70%)
40
+ * - DeFi: swap → stake → claim airdrop (35%)
41
+ *
42
+ * Tiers: Standard (75%) / Pro (25%)
43
+ * Chains: Ethereum, Solana, Base, Arbitrum, Polygon
44
+ * Wallets: MetaMask, Phantom, Coinbase Wallet, Rainbow, WalletConnect
45
+ */
46
+
47
+ /**
48
+ * ===================================================================
49
+ * ANALYTICS HOOKS (8 architected patterns)
50
+ * ===================================================================
51
+ *
52
+ * 1. WHALE WALLETS (everything hook — purchase value)
53
+ * 2% of wallets do 60% of trade volume. Deterministic via
54
+ * charCodeAt(0) % 50 === 0. Whale swaps are 50x bigger with
55
+ * is_whale=true.
56
+ * → Insights: "swap" total trade_amount_usd, breakdown is_whale
57
+ * → Insights: "swap" count of unique users, filter is_whale = true
58
+ * (expect ~2% of users, ~60% of total volume)
59
+ *
60
+ * 2. GAS PRICE SPIKE (event + everything hook — time-based)
61
+ * Days 35-37, network congestion. Gas fees 10x on swap/transfer,
62
+ * gas_spike=true. 40% of swaps in this window fail.
63
+ * → Insights: "swap" avg gas_fee_usd by day
64
+ * (expect massive spike at days 35-37)
65
+ * → Insights: "swap" count, breakdown swap_status, filter gas_spike=true
66
+ * (expect ~40% failed during spike)
67
+ *
68
+ * 3. TOKEN LAUNCH SURGE (event + everything hook — timed release)
69
+ * Day 50, "MOON" token lists. After day 50, 25% of swaps become
70
+ * MOON trades. MOON traders get 5x event volume via cloned swaps.
71
+ * → Insights: "swap" total by day, breakdown moon_trade
72
+ * (expect large surge after day 50)
73
+ * → Insights: "swap" total trade_amount_usd, filter moon_trade=true
74
+ *
75
+ * 4. AIRDROP HUNTER CHURN (everything hook — churn)
76
+ * Users who claim airdrops but never swap are bots. 95% of their
77
+ * events after the airdrop claim are removed.
78
+ * → Retention: any event, segment by users who did "claim airdrop"
79
+ * (expect abysmal retention for airdrop-only users)
80
+ * → Insights: "claim airdrop" unique users vs "swap" unique users
81
+ *
82
+ * 5. KYC FUNNEL COMPLETION (everything hook — conversion)
83
+ * KYC-verified users deposit 4x more and swap 8x more (cloned
84
+ * events). kyc_verified=true flagged on post-KYC events.
85
+ * → Insights: "deposit" avg deposit_amount_usd, breakdown kyc_verified
86
+ * (expect ~4x for verified users)
87
+ * → Insights: "swap" count per user, breakdown kyc_verified
88
+ *
89
+ * 6. STAKE-TO-RETAIN (everything hook — retention)
90
+ * Users who stake within first 14 days get 70% D60 retention
91
+ * (events injected). Non-stakers lose events after day 60 to
92
+ * simulate 15% retention.
93
+ * → Retention: any event, segment staked_early=true vs false
94
+ * (expect dramatic retention gap at D60)
95
+ * → Insights: event count by day, breakdown staked_early
96
+ *
97
+ * 7. PRO TIER MAKER FEES (everything hook — subscription tier)
98
+ * Pro users pay 0.05% maker fees (vs 0.30%). Pro users trade 6x
99
+ * volume. pro_advantage=true on Pro swap events.
100
+ * → Insights: "swap" avg maker_fee_pct, breakdown trading_tier
101
+ * (expect Pro ~0.05, Standard ~0.30)
102
+ * → Insights: "swap" total trade_amount_usd, breakdown pro_advantage
103
+ *
104
+ * 8. RUG-PULL AFTERMATH (everything hook — time-based churn)
105
+ * Day 70, "SCAM" token rugs. Users who held SCAM (swapped it
106
+ * before day 70) lose 80% of events after day 70. Flagged
107
+ * rug_pull_victim=true.
108
+ * → Retention: any event, segment rug_pull_victim=true vs others
109
+ * (expect massive drop-off at day 70 for victims)
110
+ * → Insights: any event total by day, filter rug_pull_victim=true
111
+ *
112
+ * ===================================================================
113
+ * ADVANCED ANALYSIS IDEAS
114
+ * ===================================================================
115
+ *
116
+ * Cross-hook patterns:
117
+ * - Whale + MOON: Do whales drive the MOON token surge?
118
+ * - Gas Spike + Rug Pull: Do SCAM holders panic-sell during gas spikes?
119
+ * - KYC + Pro Tier: Do verified users upgrade to Pro faster?
120
+ * - Stakers + Whales: Do whales stake more, or just trade?
121
+ * - Airdrop Hunters + Rug Pull: Overlap between bots and victims?
122
+ *
123
+ * Cohort analysis:
124
+ * - By preferred_chain: Ethereum vs Solana engagement
125
+ * - By wallet_type: MetaMask vs Phantom retention
126
+ * - By trading_tier: Standard→Pro upgrade path
127
+ * - By kyc_status: verified vs pending conversion rates
128
+ *
129
+ * ===================================================================
130
+ * EXPECTED METRICS SUMMARY
131
+ * ===================================================================
132
+ *
133
+ * Hook | Metric | Baseline | Effect | Ratio
134
+ * ----------------------|-----------------------|----------|---------|------
135
+ * Whale Wallets | Avg trade_amount_usd | $200 | $10,000 | 50x
136
+ * Gas Price Spike | Avg gas_fee_usd | $5 | $50 | 10x
137
+ * Token Launch Surge | Swap count post-D50 | 1x | 5x | 5x
138
+ * Airdrop Hunter Churn | Post-airdrop events | 100% | 5% | 0.05x
139
+ * KYC Completion | Avg deposit_amount_usd| $500 | $2,000 | 4x
140
+ * Stake-to-Retain | D60 retention | 15% | 70% | ~5x
141
+ * Pro Tier Fees | Avg maker_fee_pct | 0.30 | 0.05 | 6x
142
+ * Rug-Pull Aftermath | Post-D70 events | 100% | 20% | 0.2x
143
+ */
144
+
145
+ // Token pairs used in swap events
146
+ const TOKEN_PAIRS = [
147
+ "ETH/USDC", "ETH/USDT", "BTC/USDC", "SOL/USDC",
148
+ "MATIC/ETH", "ARB/ETH", "OP/USDC", "AVAX/USDC",
149
+ "LINK/ETH", "UNI/USDC", "AAVE/ETH", "CRV/USDC",
150
+ "DOGE/USDT", "PEPE/ETH", "APE/USDC"
151
+ ];
152
+
153
+ const AIRDROP_NAMES = [
154
+ "LayerZero Season 1", "zkSync Drop", "Starknet STRK",
155
+ "Arbitrum ARB", "Optimism OP", "Blur Season 3",
156
+ "EigenLayer Points", "Celestia TIA", "Jupiter JUP",
157
+ "Wormhole W"
158
+ ];
159
+
160
+ const NFT_COLLECTIONS = [
161
+ "Bored Apes", "Azuki", "DeGods", "Pudgy Penguins",
162
+ "Milady", "Doodles", "CloneX", "Moonbirds",
163
+ "CryptoPunks", "Art Blocks"
164
+ ];
165
+
166
+ /** @type {Config} */
167
+ const config = {
168
+ token,
169
+ seed: SEED,
170
+ numDays: num_days,
171
+ avgEventsPerUserPerDay: avg_events_per_user_per_day,
172
+ numUsers: num_users,
173
+ hasAnonIds: false,
174
+ hasSessionIds: true,
175
+ format: "json",
176
+ gzip: true,
177
+ alsoInferFunnels: false,
178
+ hasLocation: true,
179
+ hasAndroidDevices: false,
180
+ hasIOSDevices: true,
181
+ hasDesktopDevices: true,
182
+ hasBrowser: true,
183
+ hasCampaigns: false,
184
+ isAnonymous: false,
185
+ hasAdSpend: false,
186
+ hasAvatar: true,
187
+
188
+ concurrency: 1,
189
+ writeToDisk: false,
190
+
191
+ soup: "growth",
192
+
193
+ scdProps: {
194
+ trading_tier: {
195
+ values: ["Standard", "Pro"],
196
+ frequency: "month",
197
+ timing: "fuzzy",
198
+ max: 4
199
+ }
200
+ },
201
+
202
+ funnels: [
203
+ {
204
+ sequence: ["wallet connected", "kyc started", "deposit"],
205
+ isFirstFunnel: true,
206
+ conversionRate: 60,
207
+ timeToConvert: 1,
208
+ },
209
+ {
210
+ sequence: ["deposit", "swap", "withdrawal"],
211
+ conversionRate: 70,
212
+ timeToConvert: 2,
213
+ weight: 5,
214
+ },
215
+ {
216
+ sequence: ["swap", "stake", "claim airdrop"],
217
+ conversionRate: 35,
218
+ timeToConvert: 7,
219
+ weight: 3,
220
+ },
221
+ ],
222
+
223
+ events: [
224
+ {
225
+ event: "wallet connected",
226
+ weight: 1,
227
+ isFirstEvent: true,
228
+ properties: {
229
+ wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
230
+ chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
231
+ }
232
+ },
233
+ {
234
+ event: "kyc started",
235
+ weight: 2,
236
+ properties: {
237
+ document_type: ["passport", "drivers_license", "national_id"],
238
+ verification_method: ["selfie", "video", "document_scan"],
239
+ }
240
+ },
241
+ {
242
+ event: "kyc completed",
243
+ weight: 1,
244
+ properties: {
245
+ document_type: ["passport", "drivers_license", "national_id"],
246
+ verification_method: ["selfie", "video", "document_scan"],
247
+ }
248
+ },
249
+ {
250
+ event: "deposit",
251
+ weight: 5,
252
+ properties: {
253
+ token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
254
+ deposit_amount_usd: u.weighNumRange(10, 5000, 0.3, 500),
255
+ chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
256
+ deposit_method: ["wallet_transfer", "bridge", "on_ramp", "exchange_transfer"],
257
+ kyc_verified: [false],
258
+ }
259
+ },
260
+ {
261
+ event: "withdrawal",
262
+ weight: 3,
263
+ properties: {
264
+ token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
265
+ amount_usd: u.weighNumRange(10, 5000, 0.3, 400),
266
+ chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
267
+ withdrawal_method: ["wallet_transfer", "bridge", "off_ramp", "exchange_transfer"],
268
+ }
269
+ },
270
+ {
271
+ event: "swap",
272
+ weight: 10,
273
+ properties: {
274
+ token_pair: TOKEN_PAIRS,
275
+ trade_amount_usd: u.weighNumRange(10, 10000, 0.3, 200),
276
+ gas_fee_usd: u.weighNumRange(0.5, 30, 0.3, 5),
277
+ swap_status: ["completed", "completed", "completed", "completed", "pending", "failed"],
278
+ slippage_pct: u.weighNumRange(0.01, 5, 0.3, 0.5),
279
+ maker_fee_pct: [0.30],
280
+ is_whale: [false],
281
+ kyc_verified: [false],
282
+ gas_spike: [false],
283
+ pro_advantage: [false],
284
+ rug_pull_victim: [false],
285
+ moon_trade: [false],
286
+ staked_early: [false],
287
+ }
288
+ },
289
+ {
290
+ event: "stake",
291
+ weight: 4,
292
+ properties: {
293
+ token: ["ETH", "SOL", "MATIC", "AVAX", "ATOM", "DOT"],
294
+ amount_usd: u.weighNumRange(50, 10000, 0.3, 500),
295
+ apy_pct: u.weighNumRange(2, 25, 0.5, 8),
296
+ lock_period_days: [7, 14, 30, 60, 90, 180],
297
+ }
298
+ },
299
+ {
300
+ event: "unstake",
301
+ weight: 2,
302
+ properties: {
303
+ token: ["ETH", "SOL", "MATIC", "AVAX", "ATOM", "DOT"],
304
+ amount_usd: u.weighNumRange(50, 10000, 0.3, 500),
305
+ apy_pct: u.weighNumRange(2, 25, 0.5, 8),
306
+ lock_period_days: [7, 14, 30, 60, 90, 180],
307
+ }
308
+ },
309
+ {
310
+ event: "claim airdrop",
311
+ weight: 2,
312
+ properties: {
313
+ airdrop_name: AIRDROP_NAMES,
314
+ token_amount: u.weighNumRange(10, 10000, 0.3, 500),
315
+ airdrop_value_usd: u.weighNumRange(5, 2000, 0.3, 100),
316
+ }
317
+ },
318
+ {
319
+ event: "nft mint",
320
+ weight: 2,
321
+ properties: {
322
+ collection: NFT_COLLECTIONS,
323
+ mint_price_usd: u.weighNumRange(10, 2000, 0.3, 150),
324
+ chain: ["Ethereum", "Solana", "Base", "Polygon"],
325
+ }
326
+ },
327
+ {
328
+ event: "portfolio viewed",
329
+ weight: 8,
330
+ properties: {
331
+ total_value_usd: u.weighNumRange(100, 100000, 0.3, 5000),
332
+ }
333
+ },
334
+ {
335
+ event: "price alert set",
336
+ weight: 3,
337
+ properties: {
338
+ token: ["ETH", "BTC", "SOL", "MATIC", "AVAX", "LINK", "UNI", "AAVE"],
339
+ alert_type: ["above", "below", "percent_change"],
340
+ }
341
+ },
342
+ {
343
+ event: "referral sent",
344
+ weight: 2,
345
+ properties: {
346
+ referral_method: ["link", "email", "twitter", "discord", "telegram"],
347
+ }
348
+ },
349
+ {
350
+ event: "limit order placed",
351
+ weight: 3,
352
+ properties: {
353
+ token_pair: TOKEN_PAIRS,
354
+ order_type: ["limit_buy", "limit_sell", "stop_loss", "take_profit"],
355
+ price_usd: u.weighNumRange(10, 50000, 0.3, 1000),
356
+ }
357
+ },
358
+ {
359
+ event: "transfer",
360
+ weight: 3,
361
+ properties: {
362
+ token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
363
+ amount_usd: u.weighNumRange(10, 5000, 0.3, 300),
364
+ gas_fee_usd: u.weighNumRange(0.5, 30, 0.3, 5),
365
+ destination_type: ["wallet", "exchange", "contract", "ens_name"],
366
+ gas_spike: [false],
367
+ }
368
+ },
369
+ ],
370
+
371
+ superProps: {
372
+ trading_tier: ["Standard", "Standard", "Standard", "Pro"],
373
+ preferred_chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
374
+ wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
375
+ staked_early: [false],
376
+ },
377
+
378
+ userProps: {
379
+ trading_tier: ["Standard", "Standard", "Standard", "Pro"],
380
+ preferred_chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
381
+ wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
382
+ total_trade_volume: u.weighNumRange(0, 500000, 0.3, 5000),
383
+ portfolio_value: u.weighNumRange(0, 200000, 0.3, 3000),
384
+ kyc_status: ["pending"],
385
+ has_staked: [false],
386
+ staked_early: [false],
387
+ },
388
+
389
+ groupKeys: [],
390
+ groupProps: {},
391
+ lookupTables: [],
392
+
393
+ /**
394
+ * ARCHITECTED ANALYTICS HOOKS
395
+ *
396
+ * This hook function creates 8 deliberate patterns in the data:
397
+ *
398
+ * 1. WHALE WALLETS: 2% of users (deterministic) trade 50x volume, is_whale=true
399
+ * 2. GAS PRICE SPIKE: Days 35-37 gas fees 10x, 40% swaps fail, gas_spike=true
400
+ * 3. TOKEN LAUNCH SURGE: Day 50+ MOON token, 25% swaps become MOON, 5x volume for MOON traders
401
+ * 4. AIRDROP HUNTER CHURN: Airdrop claimers with no swaps lose 95% of events post-claim
402
+ * 5. KYC COMPLETION: Verified users deposit 4x more, swap 8x more, kyc_verified=true
403
+ * 6. STAKE-TO-RETAIN: Early stakers (first 14 days) get 70% D60 retention vs 15%
404
+ * 7. PRO TIER FEES: Pro users pay 0.05% fees (vs 0.30%), trade 6x volume, pro_advantage=true
405
+ * 8. RUG-PULL AFTERMATH: Day 70 SCAM token rugs, holders lose 80% of post-D70 events
406
+ */
407
+ hook: function (record, type, meta) {
408
+ const NOW = dayjs();
409
+ const DATASET_START = NOW.subtract(num_days, "days");
410
+
411
+ // ===============================================================
412
+ // Hook #2 (partial): GAS PRICE SPIKE (event)
413
+ // Days 35-37, network congestion. Gas fees 10x on swap/transfer
414
+ // events and gas_spike=true.
415
+ // ===============================================================
416
+ if (type === "event") {
417
+ const EVENT_TIME = dayjs(record.time);
418
+ const dayInDataset = EVENT_TIME.diff(DATASET_START, "day");
419
+
420
+ // Gas price spike: days 35-37
421
+ if (dayInDataset >= 35 && dayInDataset <= 37) {
422
+ if (record.event === "swap") {
423
+ record.gas_fee_usd = Math.round((record.gas_fee_usd || 5) * 10);
424
+ record.gas_spike = true;
425
+ }
426
+ if (record.event === "transfer") {
427
+ record.gas_fee_usd = Math.round((record.gas_fee_usd || 5) * 10);
428
+ record.gas_spike = true;
429
+ }
430
+ }
431
+
432
+ // ===============================================================
433
+ // Hook #3 (partial): TOKEN LAUNCH SURGE (event)
434
+ // After day 50, 25% of swap events get token_pair changed to
435
+ // include "MOON". Tagged moon_trade=true.
436
+ // ===============================================================
437
+ if (record.event === "swap" && dayInDataset >= 50) {
438
+ if (chance.bool({ likelihood: 25 })) {
439
+ record.token_pair = chance.pickone(["MOON/USDC", "MOON/ETH", "ETH/MOON"]);
440
+ record.moon_trade = true;
441
+ }
442
+ }
443
+ }
444
+
445
+ // ===============================================================
446
+ // EVERYTHING HOOK — all complex behavioral patterns
447
+ // ===============================================================
448
+ if (type === "everything") {
449
+ const userEvents = record;
450
+ const profile = meta.profile;
451
+
452
+ // Stamp superProps from profile for consistency
453
+ userEvents.forEach(e => {
454
+ e.trading_tier = profile.trading_tier;
455
+ e.preferred_chain = profile.preferred_chain;
456
+ e.wallet_type = profile.wallet_type;
457
+ e.staked_early = profile.staked_early;
458
+ });
459
+
460
+ const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
461
+
462
+ // -----------------------------------------------------------
463
+ // Hook #1: WHALE WALLETS
464
+ // 2% of wallets do massive volume. Deterministic selection via
465
+ // charCodeAt(0) % 50 === 0. Whales get 50x trade_amount_usd
466
+ // on all swap events with is_whale=true.
467
+ // -----------------------------------------------------------
468
+ const userId = userEvents.length > 0 ? (userEvents[0].user_id || userEvents[0].distinct_id || "") : "";
469
+ const isWhale = userId.length > 0 && userId.charCodeAt(0) % 50 === 0;
470
+
471
+ if (isWhale) {
472
+ userEvents.forEach(e => {
473
+ if (e.event === "swap") {
474
+ e.trade_amount_usd = Math.round((e.trade_amount_usd || 200) * 50);
475
+ e.is_whale = true;
476
+ }
477
+ });
478
+ }
479
+
480
+ // -----------------------------------------------------------
481
+ // Hook #2 (continued): GAS PRICE SPIKE — failed swaps
482
+ // During days 35-37, 40% of swaps get swap_status changed to
483
+ // "failed".
484
+ // -----------------------------------------------------------
485
+ userEvents.forEach(e => {
486
+ if (e.event === "swap" && e.gas_spike === true) {
487
+ if (chance.bool({ likelihood: 40 })) {
488
+ e.swap_status = "failed";
489
+ }
490
+ }
491
+ });
492
+
493
+ // -----------------------------------------------------------
494
+ // Hook #3 (continued): TOKEN LAUNCH SURGE — volume boost
495
+ // Users who traded MOON get 5x swap event volume via cloned
496
+ // events spread over days after day 50.
497
+ // -----------------------------------------------------------
498
+ const moonSwaps = userEvents.filter(e => e.event === "swap" && e.moon_trade === true);
499
+ if (moonSwaps.length > 0) {
500
+ const clonedMoonEvents = [];
501
+ moonSwaps.forEach(moonEvt => {
502
+ const moonTime = dayjs(moonEvt.time);
503
+ for (let i = 0; i < 4; i++) {
504
+ clonedMoonEvents.push({
505
+ ...moonEvt,
506
+ time: moonTime.add(chance.integer({ min: 1, max: 72 }), "hours").toISOString(),
507
+ user_id: moonEvt.user_id,
508
+ trade_amount_usd: Math.round((moonEvt.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 2.0 })),
509
+ });
510
+ }
511
+ });
512
+ userEvents.push(...clonedMoonEvents);
513
+ }
514
+
515
+ // -----------------------------------------------------------
516
+ // Hook #4: AIRDROP HUNTER CHURN
517
+ // Users who have "claim airdrop" events but zero "swap"
518
+ // events are bots/hunters. Remove 95% of events after the
519
+ // first airdrop claim.
520
+ // -----------------------------------------------------------
521
+ const hasAirdropClaim = userEvents.some(e => e.event === "claim airdrop");
522
+ const hasSwap = userEvents.some(e => e.event === "swap");
523
+
524
+ if (hasAirdropClaim && !hasSwap) {
525
+ const firstClaim = userEvents.find(e => e.event === "claim airdrop");
526
+ if (firstClaim) {
527
+ const claimTime = dayjs(firstClaim.time);
528
+ for (let i = userEvents.length - 1; i >= 0; i--) {
529
+ const evt = userEvents[i];
530
+ if (dayjs(evt.time).isAfter(claimTime) && evt.event !== "claim airdrop") {
531
+ if (chance.bool({ likelihood: 95 })) {
532
+ userEvents.splice(i, 1);
533
+ }
534
+ }
535
+ }
536
+ }
537
+ }
538
+
539
+ // -----------------------------------------------------------
540
+ // Hook #5: KYC FUNNEL COMPLETION
541
+ // Users with "kyc completed" events get 4x deposit_amount_usd
542
+ // and 8x more swap events (cloned). Flag kyc_verified=true on
543
+ // post-KYC events.
544
+ // -----------------------------------------------------------
545
+ const kycCompleted = userEvents.find(e => e.event === "kyc completed");
546
+ if (kycCompleted) {
547
+ const kycTime = dayjs(kycCompleted.time);
548
+
549
+ // Boost deposits and flag post-KYC events
550
+ userEvents.forEach(e => {
551
+ if (dayjs(e.time).isAfter(kycTime)) {
552
+ if (e.event === "deposit") {
553
+ e.deposit_amount_usd = Math.round((e.deposit_amount_usd || 500) * 4);
554
+ e.kyc_verified = true;
555
+ }
556
+ if (e.event === "swap") {
557
+ e.kyc_verified = true;
558
+ }
559
+ }
560
+ });
561
+
562
+ // Clone swap events for 8x volume
563
+ const postKycSwaps = userEvents.filter(e => e.event === "swap" && e.kyc_verified === true);
564
+ const clonedKycSwaps = [];
565
+ postKycSwaps.forEach(swapEvt => {
566
+ const swapTime = dayjs(swapEvt.time);
567
+ for (let i = 0; i < 7; i++) {
568
+ clonedKycSwaps.push({
569
+ ...swapEvt,
570
+ time: swapTime.add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
571
+ user_id: swapEvt.user_id,
572
+ trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.8, max: 1.5 })),
573
+ kyc_verified: true,
574
+ });
575
+ }
576
+ });
577
+ userEvents.push(...clonedKycSwaps);
578
+ }
579
+
580
+ // -----------------------------------------------------------
581
+ // Hook #6: STAKE-TO-RETAIN
582
+ // Users who stake any token within the first 14 days of the
583
+ // dataset have 70% D60 retention (events injected beyond D60).
584
+ // Non-stakers lose events after day 60 to simulate 15% retention.
585
+ // -----------------------------------------------------------
586
+ const day14 = DATASET_START.add(14, "days");
587
+ const day60 = DATASET_START.add(60, "days");
588
+
589
+ const stakedEarly = userEvents.some(e =>
590
+ e.event === "stake" && dayjs(e.time).isBefore(day14)
591
+ );
592
+
593
+ if (stakedEarly) {
594
+ // Mark all events for this user
595
+ userEvents.forEach(e => { e.staked_early = true; });
596
+
597
+ // 70% retention: inject late-stage events for stakers
598
+ // (only inject if they don't already have many post-D60 events)
599
+ const postD60Events = userEvents.filter(e => dayjs(e.time).isAfter(day60));
600
+ if (postD60Events.length < 5) {
601
+ const swapTemplate = userEvents.find(e => e.event === "swap");
602
+ const portfolioTemplate = userEvents.find(e => e.event === "portfolio viewed");
603
+ if (swapTemplate) {
604
+ for (let i = 0; i < 8; i++) {
605
+ userEvents.push({
606
+ ...swapTemplate,
607
+ time: day60.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
608
+ user_id: swapTemplate.user_id,
609
+ trade_amount_usd: Math.round((swapTemplate.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 2.0 })),
610
+ staked_early: true,
611
+ });
612
+ }
613
+ }
614
+ if (portfolioTemplate) {
615
+ for (let i = 0; i < 4; i++) {
616
+ userEvents.push({
617
+ ...portfolioTemplate,
618
+ time: day60.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
619
+ user_id: portfolioTemplate.user_id,
620
+ staked_early: true,
621
+ });
622
+ }
623
+ }
624
+ }
625
+ } else {
626
+ // Non-stakers: 15% retention past D60 — remove 85% of post-D60 events
627
+ for (let i = userEvents.length - 1; i >= 0; i--) {
628
+ const evt = userEvents[i];
629
+ if (dayjs(evt.time).isAfter(day60)) {
630
+ if (chance.bool({ likelihood: 85 })) {
631
+ userEvents.splice(i, 1);
632
+ }
633
+ }
634
+ }
635
+ }
636
+
637
+ // -----------------------------------------------------------
638
+ // Hook #7: PRO TIER MAKER FEES
639
+ // Pro users (from profile.trading_tier) pay maker_fee_pct=0.05
640
+ // vs standard 0.30. Pro users also trade 6x volume.
641
+ // pro_advantage=true on Pro swap events.
642
+ // -----------------------------------------------------------
643
+ if (profile.trading_tier === "Pro") {
644
+ userEvents.forEach(e => {
645
+ if (e.event === "swap") {
646
+ e.maker_fee_pct = 0.05;
647
+ e.pro_advantage = true;
648
+ }
649
+ });
650
+
651
+ // Clone swap events for 6x volume
652
+ const proSwaps = userEvents.filter(e => e.event === "swap");
653
+ const clonedProSwaps = [];
654
+ proSwaps.forEach(swapEvt => {
655
+ const swapTime = dayjs(swapEvt.time);
656
+ for (let i = 0; i < 5; i++) {
657
+ clonedProSwaps.push({
658
+ ...swapEvt,
659
+ time: swapTime.add(chance.integer({ min: 1, max: 96 }), "hours").toISOString(),
660
+ user_id: swapEvt.user_id,
661
+ trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 3.0 })),
662
+ maker_fee_pct: 0.05,
663
+ pro_advantage: true,
664
+ });
665
+ }
666
+ });
667
+ userEvents.push(...clonedProSwaps);
668
+ } else {
669
+ // Ensure standard fee on non-Pro swap events
670
+ userEvents.forEach(e => {
671
+ if (e.event === "swap") {
672
+ e.maker_fee_pct = 0.30;
673
+ }
674
+ });
675
+ }
676
+
677
+ // -----------------------------------------------------------
678
+ // Hook #8: RUG-PULL AFTERMATH
679
+ // Day 70, "SCAM" token rugs. Users who swapped SCAM token
680
+ // before day 70 lose 80% of events after day 70.
681
+ // rug_pull_victim=true on remaining events.
682
+ // -----------------------------------------------------------
683
+ const day70 = DATASET_START.add(70, "days");
684
+
685
+ // Check if user traded SCAM before day 70
686
+ // ~10% of pre-day-70 swaps naturally get SCAM token pair
687
+ // (injected here since SCAM isn't in the default TOKEN_PAIRS list)
688
+ let hadScam = false;
689
+ userEvents.forEach(e => {
690
+ if (e.event === "swap") {
691
+ const swapDay = dayjs(e.time).diff(DATASET_START, "day");
692
+ // Before day 70, ~8% of swaps randomly become SCAM trades
693
+ if (swapDay >= 10 && swapDay < 70 && chance.bool({ likelihood: 8 })) {
694
+ e.token_pair = chance.pickone(["SCAM/USDC", "SCAM/ETH", "ETH/SCAM"]);
695
+ hadScam = true;
696
+ }
697
+ }
698
+ });
699
+
700
+ if (hadScam) {
701
+ for (let i = userEvents.length - 1; i >= 0; i--) {
702
+ const evt = userEvents[i];
703
+ if (dayjs(evt.time).isAfter(day70)) {
704
+ if (chance.bool({ likelihood: 80 })) {
705
+ userEvents.splice(i, 1);
706
+ } else {
707
+ evt.rug_pull_victim = true;
708
+ }
709
+ }
710
+ }
711
+ }
712
+
713
+ // Sort by time after all mutations
714
+ userEvents.sort((a, b) => new Date(a.time) - new Date(b.time));
715
+ }
716
+
717
+ return record;
718
+ }
719
+ };
720
+
721
+ export default config;