@ak--47/dungeon-master 1.5.4 → 1.6.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 (146) hide show
  1. package/.claude/skills/analyze-soup/SKILL.md +21 -12
  2. package/.claude/skills/create-dungeon/SKILL.md +65 -5
  3. package/.claude/skills/create-project/SKILL.md +1 -1
  4. package/.claude/skills/create-project/provision.mjs +57 -10
  5. package/.claude/skills/verify-dungeon/SKILL.md +56 -25
  6. package/.claude/skills/verify-dungeon/references/counting-semantics.md +26 -6
  7. package/.claude/skills/verify-dungeon/references/report-format.md +14 -8
  8. package/.claude/skills/write-hooks/SKILL.md +156 -18
  9. package/CHANGELOG.md +183 -0
  10. package/HOOKS.md +566 -112
  11. package/README.md +28 -9
  12. package/docs/guides/1.3.0-upgrade-guide.md +262 -0
  13. package/docs/guides/1.3.1-upgrade-guide.md +328 -0
  14. package/docs/guides/1.4.0-upgrade-guide.md +130 -0
  15. package/docs/guides/1.4.1-upgrade-guide.md +125 -0
  16. package/docs/guides/1.4.3-upgrade-guide.md +200 -0
  17. package/docs/guides/1.4.4-upgrade-guide.md +95 -0
  18. package/docs/guides/1.5.0-upgrade-guide.md +426 -0
  19. package/docs/guides/1.5.1-upgrade-guide.md +178 -0
  20. package/docs/guides/1.5.3-upgrade-guide.md +117 -0
  21. package/docs/guides/1.6.0-upgrade-guide.md +204 -0
  22. package/dungeons/technical/stories-verify.js +152 -0
  23. package/dungeons/vertical/README.md +154 -0
  24. package/dungeons/vertical/ai-platform/ai-platform.js +1424 -0
  25. package/dungeons/vertical/ai-platform/ai-platform.sql +184 -0
  26. package/dungeons/vertical/ai-platform/ai-platform.verify.mjs +71 -0
  27. package/dungeons/vertical/community/community.js +1589 -0
  28. package/dungeons/vertical/community/community.sql +216 -0
  29. package/dungeons/vertical/community/community.verify.mjs +72 -0
  30. package/dungeons/vertical/crypto/crypto.js +1483 -0
  31. package/dungeons/vertical/crypto/crypto.sql +194 -0
  32. package/dungeons/vertical/crypto/crypto.verify.mjs +78 -0
  33. package/dungeons/vertical/dating/dating.js +1540 -0
  34. package/dungeons/vertical/dating/dating.sql +288 -0
  35. package/dungeons/vertical/dating/dating.verify.mjs +72 -0
  36. package/dungeons/vertical/devtools/devtools.js +1524 -0
  37. package/dungeons/vertical/devtools/devtools.sql +161 -0
  38. package/dungeons/vertical/devtools/devtools.verify.mjs +73 -0
  39. package/dungeons/vertical/ecommerce/ecommerce.js +1480 -0
  40. package/dungeons/vertical/ecommerce/ecommerce.sql +218 -0
  41. package/dungeons/vertical/ecommerce/ecommerce.verify.mjs +70 -0
  42. package/dungeons/vertical/education/education.js +1763 -0
  43. package/dungeons/vertical/education/education.sql +243 -0
  44. package/dungeons/vertical/education/education.verify.mjs +75 -0
  45. package/dungeons/vertical/fintech/fintech.js +1410 -0
  46. package/dungeons/vertical/fintech/fintech.sql +175 -0
  47. package/dungeons/vertical/fintech/fintech.verify.mjs +70 -0
  48. package/dungeons/vertical/fitness/fitness.js +1495 -0
  49. package/dungeons/vertical/fitness/fitness.sql +206 -0
  50. package/dungeons/vertical/fitness/fitness.verify.mjs +71 -0
  51. package/dungeons/vertical/food-delivery/food-delivery.js +1641 -0
  52. package/dungeons/vertical/food-delivery/food-delivery.sql +183 -0
  53. package/dungeons/vertical/food-delivery/food-delivery.verify.mjs +75 -0
  54. package/dungeons/vertical/gaming/gaming.js +1741 -0
  55. package/dungeons/vertical/gaming/gaming.sql +249 -0
  56. package/dungeons/vertical/gaming/gaming.verify.mjs +70 -0
  57. package/dungeons/vertical/healthcare/healthcare.js +1488 -0
  58. package/dungeons/vertical/healthcare/healthcare.sql +193 -0
  59. package/dungeons/vertical/healthcare/healthcare.verify.mjs +72 -0
  60. package/dungeons/vertical/insurance-application/insurance-application.js +1852 -0
  61. package/dungeons/vertical/insurance-application/insurance-application.sql +174 -0
  62. package/dungeons/vertical/insurance-application/insurance-application.verify.mjs +74 -0
  63. package/dungeons/vertical/logistics/logistics.js +1541 -0
  64. package/dungeons/vertical/logistics/logistics.sql +205 -0
  65. package/dungeons/vertical/logistics/logistics.verify.mjs +73 -0
  66. package/dungeons/vertical/marketplace/marketplace.js +1438 -0
  67. package/dungeons/vertical/marketplace/marketplace.sql +148 -0
  68. package/dungeons/vertical/marketplace/marketplace.verify.mjs +74 -0
  69. package/dungeons/vertical/{media.js → media/media.js} +557 -58
  70. package/dungeons/vertical/media/media.sql +173 -0
  71. package/dungeons/vertical/media/media.verify.mjs +74 -0
  72. package/dungeons/vertical/real-estate/real-estate.js +1629 -0
  73. package/dungeons/vertical/real-estate/real-estate.sql +167 -0
  74. package/dungeons/vertical/real-estate/real-estate.verify.mjs +75 -0
  75. package/dungeons/vertical/sass/sass.js +1733 -0
  76. package/dungeons/vertical/sass/sass.sql +173 -0
  77. package/dungeons/vertical/sass/sass.verify.mjs +74 -0
  78. package/dungeons/vertical/social/social.js +1456 -0
  79. package/dungeons/vertical/social/social.sql +160 -0
  80. package/dungeons/vertical/social/social.verify.mjs +76 -0
  81. package/dungeons/vertical/streaming/streaming.js +677 -0
  82. package/dungeons/vertical/streaming/streaming.sql +101 -0
  83. package/dungeons/vertical/streaming/streaming.verify.mjs +75 -0
  84. package/dungeons/vertical/support-desk/support-desk.js +741 -0
  85. package/dungeons/vertical/support-desk/support-desk.sql +137 -0
  86. package/dungeons/vertical/support-desk/support-desk.verify.mjs +75 -0
  87. package/dungeons/vertical/travel/travel.js +1603 -0
  88. package/dungeons/vertical/travel/travel.sql +236 -0
  89. package/dungeons/vertical/travel/travel.verify.mjs +72 -0
  90. package/index.js +2 -2
  91. package/lib/core/config-validator.js +37 -2
  92. package/lib/core/context.js +4 -2
  93. package/lib/core/dungeon-loader.js +20 -15
  94. package/lib/core/dungeon-to-json.js +22 -4
  95. package/lib/core/storage.js +3 -3
  96. package/lib/generators/funnels.js +36 -9
  97. package/lib/hook-helpers/cohort.js +53 -0
  98. package/lib/hook-helpers/index.js +8 -0
  99. package/lib/hook-helpers/mutate.js +1 -1
  100. package/lib/hook-helpers/shape.js +319 -0
  101. package/lib/hook-patterns/aggregate-per-user-by-bin.js +13 -5
  102. package/lib/hook-patterns/attributed-by-source.js +60 -58
  103. package/lib/hook-patterns/frequency-by-frequency.js +14 -4
  104. package/lib/hook-patterns/funnel-frequency-breakdown.js +24 -12
  105. package/lib/hook-patterns/index.js +1 -1
  106. package/lib/hook-patterns/time-to-convert-by-segment.js +75 -13
  107. package/lib/orchestrators/user-loop.js +41 -0
  108. package/lib/templates/story-spec.schema.json +175 -0
  109. package/lib/utils/json-evaluator.js +1 -1
  110. package/lib/utils/utils.js +5 -4
  111. package/lib/verify/coerce.js +186 -0
  112. package/lib/verify/counting.js +270 -32
  113. package/lib/verify/emulate-breakdown.js +1285 -203
  114. package/lib/verify/first-time.js +89 -0
  115. package/lib/verify/flows.js +679 -0
  116. package/lib/verify/formula.js +259 -0
  117. package/lib/verify/funnel-engine.js +697 -113
  118. package/lib/verify/index.js +32 -4
  119. package/lib/verify/sessionize.js +275 -0
  120. package/lib/verify/story-runner.js +483 -0
  121. package/lib/verify/verify-dungeon.js +59 -37
  122. package/package.json +2 -1
  123. package/scripts/extract-dungeon-schema.mjs +2 -2
  124. package/scripts/run-many.mjs +2 -2
  125. package/scripts/verify-runner.mjs +2 -2
  126. package/scripts/verify-stories.mjs +244 -0
  127. package/types.d.ts +296 -30
  128. package/dungeons/vertical/ai-platform.js +0 -818
  129. package/dungeons/vertical/community.js +0 -966
  130. package/dungeons/vertical/crypto.js +0 -895
  131. package/dungeons/vertical/dating.js +0 -830
  132. package/dungeons/vertical/devtools.js +0 -1050
  133. package/dungeons/vertical/ecommerce.js +0 -811
  134. package/dungeons/vertical/education.js +0 -964
  135. package/dungeons/vertical/fintech.js +0 -946
  136. package/dungeons/vertical/fitness.js +0 -935
  137. package/dungeons/vertical/food-delivery.js +0 -934
  138. package/dungeons/vertical/gaming.js +0 -1120
  139. package/dungeons/vertical/healthcare.js +0 -945
  140. package/dungeons/vertical/insurance-application.js +0 -1039
  141. package/dungeons/vertical/logistics.js +0 -986
  142. package/dungeons/vertical/marketplace.js +0 -1019
  143. package/dungeons/vertical/real-estate.js +0 -872
  144. package/dungeons/vertical/sass.js +0 -995
  145. package/dungeons/vertical/social.js +0 -904
  146. package/dungeons/vertical/travel.js +0 -930
@@ -1,895 +0,0 @@
1
- // ── IMPORTS ──
2
- import dayjs from "dayjs";
3
- import utc from "dayjs/plugin/utc.js";
4
- dayjs.extend(utc);
5
- import "dotenv/config";
6
- import * as u from "../../lib/utils/utils.js";
7
- /** @typedef {import("../../types").Dungeon} Config */
8
-
9
- // ── OVERVIEW ──
10
- /*
11
- * NAME: CoinNest
12
- * APP: DeFi cryptocurrency exchange platform. Users connect wallets,
13
- * swap tokens, stake for yield, mint NFTs, claim airdrops, and
14
- * trade. Features KYC verification, pro trading tiers, and
15
- * portfolio tracking.
16
- * SCALE: 10,000 users, ~600K events, 121 days (2026-01-01 → 2026-05-01)
17
- * CORE LOOP: wallet connected → KYC → deposit → swap → stake/unstake → portfolio viewed → withdrawal
18
- *
19
- * EVENTS (16):
20
- * swap (10) > portfolio viewed (8) > deposit (5) > stake (4) > withdrawal (3)
21
- * > price alert set (3) > limit order placed (3) > transfer (3) > kyc started (2)
22
- * > unstake (2) > claim airdrop (2) > nft mint (2) > referral sent (2)
23
- * > wallet connected (1) > kyc completed (1)
24
- *
25
- * FUNNELS (3):
26
- * - Onboarding: wallet connected → kyc started → deposit (60%)
27
- * - Trading: deposit → swap → withdrawal (70%)
28
- * - DeFi: swap → stake → claim airdrop (35%)
29
- *
30
- * USER PROPS: trading_tier, preferred_chain, wallet_type, total_trade_volume, portfolio_value, kyc_status
31
- * SUPER PROPS: trading_tier, preferred_chain, wallet_type
32
- * SCD PROPS: trading_tier (Standard/Pro, monthly fuzzy, max 4)
33
- * GROUPS: none
34
- */
35
-
36
- // ── HOOK STORIES ──
37
- /*
38
- * ---------------------------------------------------------------
39
- * 1. WHALE WALLETS (everything)
40
- *
41
- * PATTERN: 2% of wallets (deterministic via id hash) get swap
42
- * trade_amount_usd boosted 50x. No flag — analyst sees long-tail
43
- * trade-amount distribution.
44
- *
45
- * HOW TO FIND IT IN MIXPANEL:
46
- *
47
- * Report 1: Trade Amount Distribution
48
- * - Report type: Insights
49
- * - Event: "swap"
50
- * - Measure: Distribution of "trade_amount_usd"
51
- * - Expected: heavy long tail; top 2% of users dominate volume
52
- *
53
- * Report 2: Volume Share by Top Traders Cohort
54
- * - Cohort A: top 5% by total swap volume per user
55
- * - Event: "swap"
56
- * - Measure: Total of "trade_amount_usd"
57
- * - Expected: cohort A drives ~ 60%+ of total volume
58
- *
59
- * REAL-WORLD ANALOGUE: Whale wallets dominate exchange volume.
60
- *
61
- * ---------------------------------------------------------------
62
- * 2. GAS PRICE SPIKE (event + everything)
63
- *
64
- * PATTERN: Days 35-37, gas_fee_usd 10x on swap + transfer events.
65
- * In window, 40% of swaps get swap_status flipped to "failed". No flag.
66
- *
67
- * HOW TO FIND IT IN MIXPANEL:
68
- *
69
- * Report 1: Avg Gas Fee Over Time
70
- * - Report type: Insights
71
- * - Event: "swap"
72
- * - Measure: Average of "gas_fee_usd"
73
- * - Line chart by day
74
- * - Expected: spike days 35-37 (~10x baseline)
75
- *
76
- * Report 2: Swap Failure Rate Over Time
77
- * - Report type: Insights
78
- * - Event: "swap"
79
- * - Measure: Total
80
- * - Filter: swap_status = "failed"
81
- * - Line chart by day
82
- * - Expected: failure rate spikes during gas window
83
- *
84
- * REAL-WORLD ANALOGUE: Network congestion breaks transactions.
85
- *
86
- * ---------------------------------------------------------------
87
- * 3. TOKEN LAUNCH SURGE (event + everything)
88
- *
89
- * PATTERN: Day 50+, 25% of swaps get token_pair flipped to a MOON
90
- * pair. Each MOON swap clones 4 extra swap events with unique offset.
91
- * No flag — discover via token_pair breakdown over time.
92
- *
93
- * HOW TO FIND IT IN MIXPANEL:
94
- *
95
- * Report 1: Swap Volume by Token Pair Over Time
96
- * - Report type: Insights
97
- * - Event: "swap"
98
- * - Measure: Total
99
- * - Filter: token_pair contains "MOON"
100
- * - Line chart by day
101
- * - Expected: appears at day 50, surges thereafter
102
- *
103
- * REAL-WORLD ANALOGUE: Meme/altcoin listings drive frenzied volume.
104
- *
105
- * ---------------------------------------------------------------
106
- * 4. AIRDROP HUNTER CHURN (everything)
107
- *
108
- * PATTERN: 4% of users (deterministic via charCodeAt(1) % 25 === 0)
109
- * are airdrop-farming bots. After their first "claim airdrop" event,
110
- * 95% of subsequent events are removed.
111
- *
112
- * HOW TO FIND IT IN MIXPANEL:
113
- *
114
- * Report 1: Retention by Airdrop Claim
115
- * - Report type: Retention
116
- * - Event A: "claim airdrop"
117
- * - Event B: any event
118
- * - Expected: claimers retain dramatically worse than overall users
119
- *
120
- * Report 2: Airdrop Hunters vs Real Traders
121
- * - Report type: Insights
122
- * - Events: "claim airdrop" unique users vs "swap" unique users
123
- * - Expected: large overlap gap — many claimers never swap again
124
- *
125
- * REAL-WORLD ANALOGUE: Airdrop farms are a well-known DeFi growth
126
- * tax — bots claim free tokens and immediately abandon the platform.
127
- *
128
- * ---------------------------------------------------------------
129
- * 5. KYC FUNNEL COMPLETION (everything)
130
- *
131
- * PATTERN: Users with kyc-completed get post-KYC deposit_amount_usd
132
- * boosted 4x and 7 extra cloned swaps per existing post-KYC swap.
133
- * No flag — discover via cohort builder.
134
- *
135
- * HOW TO FIND IT IN MIXPANEL:
136
- *
137
- * Report 1: Avg Deposit by KYC Cohort
138
- * - Report type: Insights (with cohort)
139
- * - Cohort A: users with >= 1 "kyc completed"
140
- * - Cohort B: rest
141
- * - Event: "deposit"
142
- * - Measure: Average of "deposit_amount_usd"
143
- * - Expected: A ~ 4x B
144
- *
145
- * REAL-WORLD ANALOGUE: KYC unlocks higher limits.
146
- *
147
- * ---------------------------------------------------------------
148
- * 6. STAKE-TO-RETAIN (everything)
149
- *
150
- * PATTERN: Users who stake any token within first 14 days get extra
151
- * cloned swap + portfolio events past day 60. Non-stakers lose 85% of
152
- * post-day-60 events. No flag — discover via retention cohort.
153
- *
154
- * HOW TO FIND IT IN MIXPANEL:
155
- *
156
- * Report 1: Retention by Early Stake Cohort
157
- * - Report type: Retention
158
- * - Cohort A: users with >= 1 "stake" in first 14 days
159
- * - Cohort B: rest
160
- * - Expected: A ~ 70% D60 vs B ~ 15%
161
- *
162
- * REAL-WORLD ANALOGUE: Staking creates skin in the game.
163
- *
164
- * ---------------------------------------------------------------
165
- * 7. PRO TIER MAKER FEES (everything)
166
- *
167
- * PATTERN: Pro-tier users (profile.trading_tier) get maker_fee_pct
168
- * pinned to 0.05 (vs Standard 0.30) on swap events plus 5 extra
169
- * cloned swaps per existing. Mutates raw prop. Discover via
170
- * trading_tier breakdown on maker_fee_pct.
171
- *
172
- * HOW TO FIND IT IN MIXPANEL:
173
- *
174
- * Report 1: Avg Maker Fee by Tier
175
- * - Report type: Insights
176
- * - Event: "swap"
177
- * - Measure: Average of "maker_fee_pct"
178
- * - Breakdown: "trading_tier"
179
- * - Expected: Pro ~ 0.05, Standard ~ 0.30
180
- *
181
- * REAL-WORLD ANALOGUE: Tiered fees retain volume traders.
182
- *
183
- * ---------------------------------------------------------------
184
- * 8. RUG-PULL AFTERMATH (everything)
185
- *
186
- * PATTERN: ~2% of pre-day-70 swaps get token_pair flipped to a SCAM
187
- * pair. Users who held SCAM lose 80% of post-day-70 events. No flag.
188
- *
189
- * HOW TO FIND IT IN MIXPANEL:
190
- *
191
- * Report 1: Retention by SCAM Holder Cohort
192
- * - Report type: Retention
193
- * - Cohort A: users with >= 1 swap where token_pair contains "SCAM"
194
- * - Cohort B: rest
195
- * - Expected: A retention drops sharply at day 70
196
- *
197
- * REAL-WORLD ANALOGUE: Rug-pulls collapse trust.
198
- *
199
- * ---------------------------------------------------------------
200
- * 9. TRADING FUNNEL TIME-TO-CONVERT (funnel-post)
201
- *
202
- * PATTERN: Pro tier users complete deposit→swap→withdrawal funnel
203
- * 1.4x faster than baseline (factor 0.7); Standard 1.3x slower
204
- * (factor 1.3). Mutates funnel event timestamps.
205
- *
206
- * HOW TO FIND IT IN MIXPANEL:
207
- *
208
- * Report 1: Trading Funnel Median Time-to-Convert by Tier
209
- * - Report type: Funnels
210
- * - Steps: "deposit" -> "swap" -> "withdrawal"
211
- * - Measure: Median time to convert
212
- * - Breakdown: "trading_tier"
213
- * - Expected: Pro ~ 0.7x; Standard ~ 1.3x
214
- *
215
- * NOTE (funnel-post measurement): visible only via Mixpanel funnel
216
- * median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
217
- * show this — funnel-post adjusts gaps within funnel instances, not
218
- * across the user's full event history.
219
- *
220
- * REAL-WORLD ANALOGUE: Pro traders execute faster end-to-end.
221
- *
222
- * ---------------------------------------------------------------
223
- * 10. SWAP-COUNT MAGIC NUMBER (everything)
224
- *
225
- * PATTERN: Users with 8-20 swaps in dataset get +30% on stake
226
- * amount_usd plus 1-2 extra cloned stake events per existing.
227
- * Users with 21+ swaps drop 75% of portfolio-viewed events
228
- * and halve total_value_usd on survivors (over-active traders
229
- * ignore portfolio review). No flag.
230
- *
231
- * HOW TO FIND IT IN MIXPANEL:
232
- *
233
- * Report 1: Avg Stake Amount by Swap-Count Bucket
234
- * - Report type: Insights (with cohort)
235
- * - Cohort A: users with 8-20 "swap"
236
- * - Cohort B: users with 0-7
237
- * - Event: "stake"
238
- * - Measure: Average of "amount_usd"
239
- * - Expected: A ~ 1.3x B
240
- *
241
- * Report 2: Portfolio Views per User on Heavy Swappers
242
- * - Report type: Insights (with cohort)
243
- * - Cohort C: users with >= 21 "swap"
244
- * - Cohort A: users with 8-20
245
- * - Event: "portfolio viewed"
246
- * - Measure: Total per user
247
- * - Expected: C ~ 40% fewer portfolio views per user
248
- *
249
- * REAL-WORLD ANALOGUE: Engaged swappers grow stake; over-active
250
- * day-traders ignore long-term portfolio review.
251
- *
252
- * ---------------------------------------------------------------
253
- * 11. EARLY STAKER RETENTION (everything — retention magic number)
254
- *
255
- * PATTERN: Born-in-dataset users with 2+ "stake" events in their
256
- * first 10 days retain normally. Those with fewer than 2 early
257
- * stakes lose 60% of post-day-40 events. No flag — discover via
258
- * behavioral cohort on early stake count.
259
- *
260
- * HOW TO FIND IT IN MIXPANEL:
261
- *
262
- * Report 1: Retention by Early Stakers
263
- * - Report type: Retention
264
- * - Cohort A: users with >= 2 "stake" in first 10 days
265
- * - Cohort B: users with 0-1 "stake" in first 10 days
266
- * - Expected: A retains strongly; B drops ~60% after day 40
267
- *
268
- * Report 2: Post-Day-40 Event Volume by Cohort
269
- * - Report type: Insights (with cohort)
270
- * - Cohort A vs B (as above)
271
- * - Event: any event
272
- * - Measure: Total per user
273
- * - Filter: date > day 40
274
- * - Expected: A ~ 2.5x B in post-d40 volume
275
- *
276
- * REAL-WORLD ANALOGUE: Users who stake early have skin in the game
277
- * and stick around; the "magic number" for retention is 2 stakes
278
- * in the first 10 days.
279
- *
280
- * ===================================================================
281
- * EXPECTED METRICS SUMMARY
282
- * ===================================================================
283
- *
284
- * Hook | Metric | Baseline | Effect | Ratio
285
- * ----------------------|-----------------------|----------|---------|------
286
- * Whale Wallets | Top 5% trade share | 5% | ~ 60% | n/a
287
- * Gas Price Spike | gas_fee_usd days 35-7 | 1x | 10x | 10x
288
- * Token Launch Surge | MOON pair share post-D50 | 0% | ~ 25%+ | new
289
- * Airdrop Hunter Churn | post-airdrop events | 1x | 0.05x | -95%
290
- * KYC Completion | deposit_amount_usd | 1x | 4x | 4x
291
- * Stake-to-Retain | D60 retention | 15% | 70% | ~ 5x
292
- * Pro Tier Fees | maker_fee_pct | 0.30 | 0.05 | -83%
293
- * Rug-Pull Aftermath | victim post-D70 | 1x | 0.2x | -80%
294
- * Trading T2C | median min by tier | 1x | 0.7x/1.3x | 1.86x range
295
- * Swap Magic Number | sweet stake amount | 1x | 1.3x | 1.3x
296
- * Swap Magic Number | over portfolio/user | 1x | 0.25x | -75%
297
- * Early Staker Retain | post-d40 events (non) | 1x | 0.4x | -60%
298
- */
299
-
300
- // ── SCALE ──
301
- const SEED = "coinnest";
302
- const NUM_USERS = 10_000;
303
- const DATASET_START = "2026-01-01T00:00:00Z";
304
- const DATASET_END = "2026-05-01T23:59:59Z";
305
- const EVENTS_PER_DAY = 0.83;
306
- const token = process.env.MP_TOKEN || "your-mixpanel-token";
307
-
308
- const chance = u.initChance(SEED);
309
-
310
- // ── KNOBS (tweak these to reshape stories) ──
311
- const WHALE_HASH_MOD = 50;
312
- const WHALE_AMOUNT_MULT = 50;
313
-
314
- const GAS_SPIKE_START_DAY = 35;
315
- const GAS_SPIKE_END_DAY = 38;
316
- const GAS_SPIKE_MULT = 10;
317
- const GAS_SPIKE_FAIL_LIKELIHOOD = 40;
318
-
319
- const MOON_LAUNCH_DAY = 50;
320
- const MOON_FLIP_LIKELIHOOD = 25;
321
- const MOON_CLONE_COUNT = 4;
322
-
323
- const AIRDROP_BOT_HASH_MOD = 25;
324
- const AIRDROP_DROP_LIKELIHOOD = 95;
325
-
326
- const KYC_DEPOSIT_MULT = 4;
327
- const KYC_SWAP_CLONE_COUNT = 7;
328
-
329
- const STAKE_EARLY_WINDOW_DAYS = 14;
330
- const STAKE_RETENTION_CUTOFF_DAYS = 60;
331
- const STAKE_NON_STAKER_DROP_LIKELIHOOD = 85;
332
- const STAKE_INJECT_SWAP_COUNT = 8;
333
- const STAKE_INJECT_PORTFOLIO_COUNT = 4;
334
-
335
- const PRO_MAKER_FEE = 0.05;
336
- const STANDARD_MAKER_FEE = 0.30;
337
- const PRO_SWAP_CLONE_COUNT = 5;
338
-
339
- const SCAM_WINDOW_START_DAY = 10;
340
- const SCAM_WINDOW_END_DAY = 70;
341
- const SCAM_FLIP_LIKELIHOOD = 2;
342
- const SCAM_DROP_LIKELIHOOD = 80;
343
-
344
- const FUNNEL_TTC_PRO = 0.7;
345
- const FUNNEL_TTC_STANDARD = 1.3;
346
-
347
- const SWAP_SWEET_MIN = 8;
348
- const SWAP_SWEET_MAX = 20;
349
- const SWAP_OVER_THRESHOLD = 21;
350
- const SWAP_STAKE_BOOST = 1.3;
351
- const SWAP_OVER_PORTFOLIO_DROP_LIKELIHOOD = 75;
352
- const SWAP_OVER_PORTFOLIO_VALUE_FACTOR = 0.5;
353
-
354
- const EARLY_STAKE_MIN = 2;
355
- const EARLY_STAKE_WINDOW_DAYS = 10;
356
- const EARLY_STAKE_CUTOFF_DAYS = 40;
357
- const EARLY_STAKE_DROP_LIKELIHOOD = 60;
358
-
359
- // ── DATA ARRAYS ──
360
- const TOKEN_PAIRS = [
361
- "ETH/USDC", "ETH/USDT", "BTC/USDC", "SOL/USDC",
362
- "MATIC/ETH", "ARB/ETH", "OP/USDC", "AVAX/USDC",
363
- "LINK/ETH", "UNI/USDC", "AAVE/ETH", "CRV/USDC",
364
- "DOGE/USDT", "PEPE/ETH", "APE/USDC"
365
- ];
366
-
367
- const AIRDROP_NAMES = [
368
- "LayerZero Season 1", "zkSync Drop", "Starknet STRK",
369
- "Arbitrum ARB", "Optimism OP", "Blur Season 3",
370
- "EigenLayer Points", "Celestia TIA", "Jupiter JUP",
371
- "Wormhole W"
372
- ];
373
-
374
- const NFT_COLLECTIONS = [
375
- "Bored Apes", "Azuki", "DeGods", "Pudgy Penguins",
376
- "Milady", "Doodles", "CloneX", "Moonbirds",
377
- "CryptoPunks", "Art Blocks"
378
- ];
379
-
380
- // ── HELPER FUNCTIONS ──
381
- function handleFunnelPostHooks(record, meta) {
382
- // H9: Trading funnel TTC scaled by tier (Pro faster, Standard slower)
383
- const segment = meta?.profile?.trading_tier;
384
- if (Array.isArray(record) && record.length > 1) {
385
- const factor = (
386
- segment === "Pro" ? FUNNEL_TTC_PRO :
387
- segment === "Standard" ? FUNNEL_TTC_STANDARD :
388
- 1.0
389
- );
390
- if (factor !== 1.0) {
391
- for (let i = 1; i < record.length; i++) {
392
- const prev = dayjs(record[i - 1].time);
393
- const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
394
- record[i].time = prev.add(newGap, "milliseconds").toISOString();
395
- }
396
- }
397
- }
398
- return record;
399
- }
400
-
401
- function handleEverythingHooks(record, meta) {
402
- const datasetStart = dayjs.unix(meta.datasetStart);
403
- const userEvents = record;
404
- const profile = meta.profile;
405
-
406
- // Stamp superProps from profile for consistency
407
- userEvents.forEach(e => {
408
- e.trading_tier = profile.trading_tier;
409
- e.preferred_chain = profile.preferred_chain;
410
- e.wallet_type = profile.wallet_type;
411
- });
412
-
413
- // H1: Whale wallets — 2% of users (charCodeAt(0) % 50 === 0) get 50x swap volume
414
- const userId = userEvents.length > 0 ? (userEvents[0].user_id || userEvents[0].distinct_id || "") : "";
415
- const isWhale = userId.length > 0 && userId.charCodeAt(0) % WHALE_HASH_MOD === 0;
416
- if (isWhale) {
417
- userEvents.forEach(e => {
418
- if (e.event === "swap") {
419
- e.trade_amount_usd = Math.round((e.trade_amount_usd || 200) * WHALE_AMOUNT_MULT);
420
- }
421
- });
422
- }
423
-
424
- // H3: Token launch surge — after d50, 25% of swaps flip token_pair to MOON
425
- const moonLaunchDay = datasetStart.add(MOON_LAUNCH_DAY, "days");
426
- userEvents.forEach(e => {
427
- if (e.event === "swap" && dayjs(e.time).isAfter(moonLaunchDay) && chance.bool({ likelihood: MOON_FLIP_LIKELIHOOD })) {
428
- e.token_pair = chance.pickone(["MOON/USDC", "MOON/ETH", "ETH/MOON"]);
429
- }
430
- });
431
-
432
- // H3 (cont): Clone 4 extra MOON-pair swaps per existing MOON swap with unique offset
433
- const moonSwaps = userEvents.filter(e =>
434
- e.event === "swap" &&
435
- typeof e.token_pair === "string" &&
436
- e.token_pair.includes("MOON")
437
- );
438
- if (moonSwaps.length > 0) {
439
- const clonedMoonEvents = [];
440
- moonSwaps.forEach(moonEvt => {
441
- const moonTime = dayjs(moonEvt.time);
442
- for (let i = 0; i < MOON_CLONE_COUNT; i++) {
443
- clonedMoonEvents.push({
444
- ...moonEvt,
445
- time: moonTime.add(chance.integer({ min: 1, max: 72 }), "hours").toISOString(),
446
- user_id: moonEvt.user_id,
447
- trade_amount_usd: Math.round((moonEvt.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 2.0 })),
448
- });
449
- }
450
- });
451
- userEvents.push(...clonedMoonEvents);
452
- }
453
-
454
- // H4: Airdrop hunter churn — 4% of users (charCodeAt(1) % 25 === 0) who claim
455
- // an airdrop lose 95% of subsequent events after first claim
456
- const isAirdropBot = userId.length > 1 && userId.charCodeAt(1) % AIRDROP_BOT_HASH_MOD === 0;
457
- const hasAirdropClaim = userEvents.some(e => e.event === "claim airdrop");
458
- if (isAirdropBot && hasAirdropClaim) {
459
- const firstClaim = userEvents.find(e => e.event === "claim airdrop");
460
- if (firstClaim) {
461
- const claimTime = dayjs(firstClaim.time);
462
- for (let i = userEvents.length - 1; i >= 0; i--) {
463
- const evt = userEvents[i];
464
- if (dayjs(evt.time).isAfter(claimTime) && evt.event !== "claim airdrop") {
465
- if (chance.bool({ likelihood: AIRDROP_DROP_LIKELIHOOD })) {
466
- userEvents.splice(i, 1);
467
- }
468
- }
469
- }
470
- }
471
- }
472
-
473
- // H5: KYC funnel completion — post-KYC deposits 4x boost + 7 extra cloned swaps
474
- const kycCompleted = userEvents.find(e => e.event === "kyc completed");
475
- if (kycCompleted) {
476
- const kycTime = dayjs(kycCompleted.time);
477
- userEvents.forEach(e => {
478
- if (dayjs(e.time).isAfter(kycTime) && e.event === "deposit") {
479
- e.deposit_amount_usd = Math.round((e.deposit_amount_usd || 500) * KYC_DEPOSIT_MULT);
480
- }
481
- });
482
-
483
- const postKycSwaps = userEvents.filter(e =>
484
- e.event === "swap" && dayjs(e.time).isAfter(kycTime)
485
- );
486
- const clonedKycSwaps = [];
487
- postKycSwaps.forEach(swapEvt => {
488
- const swapTime = dayjs(swapEvt.time);
489
- for (let i = 0; i < KYC_SWAP_CLONE_COUNT; i++) {
490
- clonedKycSwaps.push({
491
- ...swapEvt,
492
- time: swapTime.add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
493
- user_id: swapEvt.user_id,
494
- trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.8, max: 1.5 })),
495
- });
496
- }
497
- });
498
- userEvents.push(...clonedKycSwaps);
499
- }
500
-
501
- // H6: Stake-to-retain — early stakers get post-D60 events injected; non-stakers lose 85%
502
- const earlyStakeDay = datasetStart.add(STAKE_EARLY_WINDOW_DAYS, "days");
503
- const stakeRetentionCutoff = datasetStart.add(STAKE_RETENTION_CUTOFF_DAYS, "days");
504
- const stakedEarly = userEvents.some(e =>
505
- e.event === "stake" && dayjs(e.time).isBefore(earlyStakeDay)
506
- );
507
- if (stakedEarly) {
508
- const postCutoffEvents = userEvents.filter(e => dayjs(e.time).isAfter(stakeRetentionCutoff));
509
- if (postCutoffEvents.length < 5) {
510
- const swapTemplate = userEvents.find(e => e.event === "swap");
511
- const portfolioTemplate = userEvents.find(e => e.event === "portfolio viewed");
512
- if (swapTemplate) {
513
- for (let i = 0; i < STAKE_INJECT_SWAP_COUNT; i++) {
514
- userEvents.push({
515
- ...swapTemplate,
516
- time: stakeRetentionCutoff.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
517
- user_id: swapTemplate.user_id,
518
- trade_amount_usd: Math.round((swapTemplate.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 2.0 })),
519
- });
520
- }
521
- }
522
- if (portfolioTemplate) {
523
- for (let i = 0; i < STAKE_INJECT_PORTFOLIO_COUNT; i++) {
524
- userEvents.push({
525
- ...portfolioTemplate,
526
- time: stakeRetentionCutoff.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
527
- user_id: portfolioTemplate.user_id,
528
- });
529
- }
530
- }
531
- }
532
- } else {
533
- // Non-stakers: drop 85% of post-D60 events to simulate ~15% retention
534
- for (let i = userEvents.length - 1; i >= 0; i--) {
535
- const evt = userEvents[i];
536
- if (dayjs(evt.time).isAfter(stakeRetentionCutoff)) {
537
- if (chance.bool({ likelihood: STAKE_NON_STAKER_DROP_LIKELIHOOD })) {
538
- userEvents.splice(i, 1);
539
- }
540
- }
541
- }
542
- }
543
-
544
- // H7: Pro tier maker fees — Pro=0.05, Standard=0.30; Pro gets 5 extra cloned swaps
545
- if (profile.trading_tier === "Pro") {
546
- userEvents.forEach(e => {
547
- if (e.event === "swap") e.maker_fee_pct = PRO_MAKER_FEE;
548
- });
549
- const proSwaps = userEvents.filter(e => e.event === "swap");
550
- const clonedProSwaps = [];
551
- proSwaps.forEach(swapEvt => {
552
- const swapTime = dayjs(swapEvt.time);
553
- for (let i = 0; i < PRO_SWAP_CLONE_COUNT; i++) {
554
- clonedProSwaps.push({
555
- ...swapEvt,
556
- time: swapTime.add(chance.integer({ min: 1, max: 96 }), "hours").toISOString(),
557
- user_id: swapEvt.user_id,
558
- trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 3.0 })),
559
- maker_fee_pct: PRO_MAKER_FEE,
560
- });
561
- }
562
- });
563
- userEvents.push(...clonedProSwaps);
564
- } else {
565
- userEvents.forEach(e => {
566
- if (e.event === "swap") e.maker_fee_pct = STANDARD_MAKER_FEE;
567
- });
568
- }
569
-
570
- // H8: Rug-pull aftermath — ~2% of pre-day-70 swaps flipped to SCAM; victims lose 80% post-D70
571
- const scamCutoff = datasetStart.add(SCAM_WINDOW_END_DAY, "days");
572
- let hadScam = false;
573
- userEvents.forEach(e => {
574
- if (e.event === "swap") {
575
- const swapDay = dayjs(e.time).diff(datasetStart, "day");
576
- if (swapDay >= SCAM_WINDOW_START_DAY && swapDay < SCAM_WINDOW_END_DAY && chance.bool({ likelihood: SCAM_FLIP_LIKELIHOOD })) {
577
- e.token_pair = chance.pickone(["SCAM/USDC", "SCAM/ETH", "ETH/SCAM"]);
578
- hadScam = true;
579
- }
580
- }
581
- });
582
- if (hadScam) {
583
- for (let i = userEvents.length - 1; i >= 0; i--) {
584
- const evt = userEvents[i];
585
- if (dayjs(evt.time).isAfter(scamCutoff) && chance.bool({ likelihood: SCAM_DROP_LIKELIHOOD })) {
586
- userEvents.splice(i, 1);
587
- }
588
- }
589
- }
590
-
591
- // H10: Swap-count magic number — sweet 8-20 boosts stake; over 21+ drops portfolio views
592
- const swapCount = userEvents.filter(e => e.event === "swap").length;
593
- if (swapCount >= SWAP_SWEET_MIN && swapCount <= SWAP_SWEET_MAX) {
594
- const stakeTemplate = userEvents.find(e => e.event === "stake");
595
- if (stakeTemplate) {
596
- const stakes = userEvents.filter(e => e.event === "stake");
597
- stakes.forEach(s => {
598
- if (typeof s.amount_usd === "number") s.amount_usd = Math.round(s.amount_usd * SWAP_STAKE_BOOST);
599
- const extras = chance.integer({ min: 1, max: 2 });
600
- for (let k = 0; k < extras; k++) {
601
- userEvents.push({
602
- ...s,
603
- time: dayjs(s.time).add(chance.integer({ min: 5, max: 360 }), "minutes").toISOString(),
604
- user_id: s.user_id,
605
- });
606
- }
607
- });
608
- }
609
- } else if (swapCount >= SWAP_OVER_THRESHOLD) {
610
- // Over-active traders ignore portfolio review: drop 75%, halve total_value_usd on survivors
611
- for (let i = userEvents.length - 1; i >= 0; i--) {
612
- if (userEvents[i].event === "portfolio viewed") {
613
- if (chance.bool({ likelihood: SWAP_OVER_PORTFOLIO_DROP_LIKELIHOOD })) {
614
- userEvents.splice(i, 1);
615
- } else {
616
- userEvents[i].total_value_usd = Math.round((userEvents[i].total_value_usd || 5000) * SWAP_OVER_PORTFOLIO_VALUE_FACTOR);
617
- }
618
- }
619
- }
620
- }
621
-
622
- // H11: Early staker retention — born-in users with <2 early stakes lose 60% post-D40
623
- if (meta.userIsBornInDataset) {
624
- const firstT = userEvents[0]?.time;
625
- if (firstT) {
626
- const window10 = dayjs(firstT).add(EARLY_STAKE_WINDOW_DAYS, "days").toISOString();
627
- const earlyStakes = userEvents.filter(e => e.event === "stake" && e.time <= window10).length;
628
- if (earlyStakes < EARLY_STAKE_MIN) {
629
- const cutoff = dayjs(firstT).add(EARLY_STAKE_CUTOFF_DAYS, "days");
630
- for (let i = userEvents.length - 1; i >= 0; i--) {
631
- if (dayjs(userEvents[i].time).isAfter(cutoff) && chance.bool({ likelihood: EARLY_STAKE_DROP_LIKELIHOOD })) {
632
- userEvents.splice(i, 1);
633
- }
634
- }
635
- }
636
- }
637
- }
638
-
639
- // H2: Gas price spike — days 35-37 swap+transfer gas_fee_usd 10x; 40% of swaps fail.
640
- // Runs AFTER all cloning to catch events that land in the spike window from clone offsets.
641
- const gasSpikeStart = datasetStart.add(GAS_SPIKE_START_DAY, "days");
642
- const gasSpikeEnd = datasetStart.add(GAS_SPIKE_END_DAY, "days");
643
- userEvents.forEach(e => {
644
- if (e.event !== "swap" && e.event !== "transfer") return;
645
- const t = dayjs(e.time);
646
- if (t.isAfter(gasSpikeStart) && t.isBefore(gasSpikeEnd)) {
647
- e.gas_fee_usd = Math.round((e.gas_fee_usd || 5) * GAS_SPIKE_MULT);
648
- if (e.event === "swap" && chance.bool({ likelihood: GAS_SPIKE_FAIL_LIKELIHOOD })) {
649
- e.swap_status = "failed";
650
- }
651
- }
652
- });
653
-
654
- userEvents.sort((a, b) => new Date(a.time) - new Date(b.time));
655
- return record;
656
- }
657
-
658
- // ── CONFIG ──
659
- /** @type {Config} */
660
- const config = {
661
- version: 2,
662
- seed: SEED,
663
- datasetStart: DATASET_START,
664
- datasetEnd: DATASET_END,
665
- avgEventsPerUserPerDay: EVENTS_PER_DAY,
666
- numUsers: NUM_USERS,
667
- format: "json",
668
- gzip: true,
669
- credentials: {
670
- token,
671
- },
672
- switches: {
673
- hasSessionIds: true,
674
- alsoInferFunnels: false,
675
- hasLocation: true,
676
- hasAndroidDevices: false,
677
- hasIOSDevices: true,
678
- hasDesktopDevices: true,
679
- hasBrowser: true,
680
- hasCampaigns: false,
681
- isAnonymous: false,
682
- hasAdSpend: false,
683
- hasAvatar: true,
684
- },
685
- identity: {
686
- avgDevicePerUser: 2,
687
- },
688
-
689
- concurrency: 1,
690
- writeToDisk: false,
691
-
692
- soup: "growth",
693
-
694
- scdProps: {
695
- trading_tier: {
696
- values: ["Standard", "Pro"],
697
- frequency: "month",
698
- timing: "fuzzy",
699
- max: 4
700
- }
701
- },
702
-
703
- funnels: [
704
- {
705
- sequence: ["wallet connected", "kyc started", "deposit"],
706
- isFirstFunnel: true,
707
- conversionRate: 60,
708
- timeToConvert: 1,
709
- },
710
- {
711
- sequence: ["deposit", "swap", "withdrawal"],
712
- conversionRate: 70,
713
- timeToConvert: 2,
714
- weight: 5,
715
- },
716
- {
717
- sequence: ["swap", "stake", "claim airdrop"],
718
- conversionRate: 35,
719
- timeToConvert: 7,
720
- weight: 3,
721
- },
722
- ],
723
-
724
- events: [
725
- {
726
- event: "wallet connected",
727
- weight: 1,
728
- isFirstEvent: true,
729
- isAuthEvent: true,
730
- properties: {
731
- wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
732
- chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
733
- }
734
- },
735
- {
736
- event: "kyc started",
737
- weight: 2,
738
- properties: {
739
- document_type: ["passport", "drivers_license", "national_id"],
740
- verification_method: ["selfie", "video", "document_scan"],
741
- }
742
- },
743
- {
744
- event: "kyc completed",
745
- weight: 1,
746
- properties: {
747
- document_type: ["passport", "drivers_license", "national_id"],
748
- verification_method: ["selfie", "video", "document_scan"],
749
- }
750
- },
751
- {
752
- event: "deposit",
753
- weight: 5,
754
- isStrictEvent: false,
755
- properties: {
756
- token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
757
- deposit_amount_usd: u.weighNumRange(10, 5000, 0.3, 500),
758
- chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
759
- deposit_method: ["wallet_transfer", "bridge", "on_ramp", "exchange_transfer"],
760
- }
761
- },
762
- {
763
- event: "withdrawal",
764
- weight: 3,
765
- isStrictEvent: false,
766
- properties: {
767
- token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
768
- amount_usd: u.weighNumRange(10, 5000, 0.3, 400),
769
- chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
770
- withdrawal_method: ["wallet_transfer", "bridge", "off_ramp", "exchange_transfer"],
771
- }
772
- },
773
- {
774
- event: "swap",
775
- weight: 10,
776
- isStrictEvent: false,
777
- properties: {
778
- token_pair: TOKEN_PAIRS,
779
- trade_amount_usd: u.weighNumRange(10, 10000, 0.3, 200),
780
- gas_fee_usd: u.weighNumRange(0.5, 30, 0.3, 5),
781
- swap_status: ["completed", "completed", "completed", "completed", "pending", "failed"],
782
- slippage_pct: u.weighNumRange(0.01, 5, 0.3, 0.5),
783
- maker_fee_pct: [0.30],
784
- }
785
- },
786
- {
787
- event: "stake",
788
- weight: 4,
789
- isStrictEvent: false,
790
- properties: {
791
- token: ["ETH", "SOL", "MATIC", "AVAX", "ATOM", "DOT"],
792
- amount_usd: u.weighNumRange(50, 10000, 0.3, 500),
793
- apy_pct: u.weighNumRange(2, 25, 0.5, 8),
794
- lock_period_days: [7, 14, 30, 60, 90, 180],
795
- }
796
- },
797
- {
798
- event: "unstake",
799
- weight: 2,
800
- properties: {
801
- token: ["ETH", "SOL", "MATIC", "AVAX", "ATOM", "DOT"],
802
- amount_usd: u.weighNumRange(50, 10000, 0.3, 500),
803
- apy_pct: u.weighNumRange(2, 25, 0.5, 8),
804
- lock_period_days: [7, 14, 30, 60, 90, 180],
805
- }
806
- },
807
- {
808
- event: "claim airdrop",
809
- weight: 2,
810
- isStrictEvent: false,
811
- properties: {
812
- airdrop_name: AIRDROP_NAMES,
813
- token_amount: u.weighNumRange(10, 10000, 0.3, 500),
814
- airdrop_value_usd: u.weighNumRange(5, 2000, 0.3, 100),
815
- }
816
- },
817
- {
818
- event: "nft mint",
819
- weight: 2,
820
- properties: {
821
- collection: NFT_COLLECTIONS,
822
- mint_price_usd: u.weighNumRange(10, 2000, 0.3, 150),
823
- chain: ["Ethereum", "Solana", "Base", "Polygon"],
824
- }
825
- },
826
- {
827
- event: "portfolio viewed",
828
- weight: 8,
829
- properties: {
830
- total_value_usd: u.weighNumRange(100, 100000, 0.3, 5000),
831
- }
832
- },
833
- {
834
- event: "price alert set",
835
- weight: 3,
836
- properties: {
837
- token: ["ETH", "BTC", "SOL", "MATIC", "AVAX", "LINK", "UNI", "AAVE"],
838
- alert_type: ["above", "below", "percent_change"],
839
- }
840
- },
841
- {
842
- event: "referral sent",
843
- weight: 2,
844
- properties: {
845
- referral_method: ["link", "email", "twitter", "discord", "telegram"],
846
- }
847
- },
848
- {
849
- event: "limit order placed",
850
- weight: 3,
851
- properties: {
852
- token_pair: TOKEN_PAIRS,
853
- order_type: ["limit_buy", "limit_sell", "stop_loss", "take_profit"],
854
- price_usd: u.weighNumRange(10, 50000, 0.3, 1000),
855
- }
856
- },
857
- {
858
- event: "transfer",
859
- weight: 3,
860
- properties: {
861
- token: ["ETH", "USDC", "USDT", "SOL", "BTC"],
862
- amount_usd: u.weighNumRange(10, 5000, 0.3, 300),
863
- gas_fee_usd: u.weighNumRange(0.5, 30, 0.3, 5),
864
- destination_type: ["wallet", "exchange", "contract", "ens_name"],
865
- }
866
- },
867
- ],
868
-
869
- superProps: {
870
- trading_tier: ["Standard", "Standard", "Standard", "Pro"],
871
- preferred_chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
872
- wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
873
- },
874
-
875
- userProps: {
876
- trading_tier: ["Standard", "Standard", "Standard", "Pro"],
877
- preferred_chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
878
- wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
879
- total_trade_volume: u.weighNumRange(0, 500000, 0.3, 5000),
880
- portfolio_value: u.weighNumRange(0, 200000, 0.3, 3000),
881
- kyc_status: ["pending"],
882
- },
883
-
884
- groupKeys: [],
885
- groupProps: {},
886
- lookupTables: [],
887
-
888
- hook(record, type, meta) {
889
- if (type === "funnel-post") return handleFunnelPostHooks(record, meta);
890
- if (type === "everything") return handleEverythingHooks(record, meta);
891
- return record;
892
- }
893
- };
894
-
895
- export default config;