@ak--47/dungeon-master 1.3.0 → 1.4.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.
- package/CHANGELOG.md +58 -0
- package/dungeons/technical/array-of-object-lookup.js +0 -2
- package/dungeons/technical/hook-helpers-verify.js +89 -0
- package/dungeons/technical/identity-model-verify.js +47 -0
- package/dungeons/technical/pattern-aggregate-by-bin.js +41 -0
- package/dungeons/technical/pattern-attributed-by-source.js +42 -0
- package/dungeons/technical/pattern-frequency-by-frequency.js +40 -0
- package/dungeons/technical/pattern-funnel-frequency.js +54 -0
- package/dungeons/technical/pattern-ttc-by-segment.js +45 -0
- package/dungeons/technical/simple.js +3 -4
- package/dungeons/technical/simplest-schema.json +5 -0
- package/dungeons/technical/text-generation.js +1 -1
- package/dungeons/vertical/ai-platform.js +216 -164
- package/dungeons/vertical/community.js +74 -41
- package/dungeons/vertical/crypto.js +343 -233
- package/dungeons/vertical/dating.js +246 -292
- package/dungeons/vertical/devtools.js +179 -70
- package/dungeons/vertical/ecommerce.js +275 -122
- package/dungeons/vertical/education.js +345 -312
- package/dungeons/vertical/fintech.js +445 -314
- package/dungeons/vertical/fitness.js +199 -165
- package/dungeons/vertical/food-delivery.js +309 -330
- package/dungeons/vertical/gaming.js +387 -241
- package/dungeons/vertical/healthcare.js +149 -66
- package/dungeons/vertical/insurance-application.js +173 -75
- package/dungeons/vertical/logistics.js +107 -12
- package/dungeons/vertical/marketplace.js +171 -76
- package/dungeons/vertical/media.js +259 -402
- package/dungeons/vertical/real-estate.js +336 -318
- package/dungeons/vertical/sass.js +294 -288
- package/dungeons/vertical/social.js +289 -234
- package/dungeons/vertical/travel.js +66 -20
- package/index.js +17 -17
- package/lib/core/config-validator.js +226 -175
- package/lib/core/context.js +10 -24
- package/lib/core/storage.js +6 -1
- package/lib/generators/events.js +57 -104
- package/lib/generators/funnels.js +212 -93
- package/lib/generators/mirror.js +3 -2
- package/lib/generators/product-names.js +1 -1
- package/lib/generators/scd.js +2 -1
- package/lib/generators/text.js +1 -1
- package/lib/hook-helpers/_internal.js +23 -0
- package/lib/hook-helpers/cohort.js +124 -0
- package/lib/hook-helpers/identity.js +56 -0
- package/lib/hook-helpers/index.js +44 -0
- package/lib/hook-helpers/inject.js +99 -0
- package/lib/hook-helpers/mutate.js +151 -0
- package/lib/hook-helpers/timing.js +99 -0
- package/lib/hook-patterns/aggregate-per-user-by-bin.js +38 -0
- package/lib/hook-patterns/attributed-by-source.js +72 -0
- package/lib/hook-patterns/frequency-by-frequency.js +46 -0
- package/lib/hook-patterns/funnel-frequency-breakdown.js +73 -0
- package/lib/hook-patterns/index.js +14 -0
- package/lib/hook-patterns/time-to-convert-by-segment.js +41 -0
- package/lib/orchestrators/user-loop.js +143 -293
- package/lib/utils/utils.js +97 -52
- package/lib/verify/emulate-breakdown.js +281 -0
- package/lib/verify/index.js +12 -0
- package/lib/verify/verify-dungeon.js +61 -0
- package/package.json +6 -4
- package/scripts/smoke-test-all.mjs +162 -0
- package/scripts/verify-runner.mjs +72 -24
- package/types.d.ts +434 -225
- package/dungeons/technical/ad-spend-schema.json +0 -128
- package/dungeons/technical/anonymous-users-schema.json +0 -92
- package/dungeons/technical/array-of-object-lookup-schema.json +0 -191
- package/dungeons/technical/experiments-schema.json +0 -203
- package/dungeons/technical/foobar-schema.json +0 -362
- package/dungeons/technical/group-analytics-schema.json +0 -241
- package/dungeons/technical/mirror-strategies-schema.json +0 -84
- package/dungeons/technical/nested-objects-schema.json +0 -145
- package/dungeons/technical/retention-cadence-schema.json +0 -37
- package/dungeons/technical/sanity-schema.json +0 -185
- package/dungeons/technical/scale-test-schema.json +0 -70
- package/dungeons/technical/scd-schema.json +0 -467
- package/dungeons/technical/simple-schema.json +0 -362
- package/dungeons/technical/text-generation-schema.json +0 -1062
- package/dungeons/vertical/ai-platform-schema.json +0 -617
- package/dungeons/vertical/community-schema.json +0 -579
- package/dungeons/vertical/crypto-schema.json +0 -546
- package/dungeons/vertical/dating-schema.json +0 -401
- package/dungeons/vertical/devtools-schema.json +0 -601
- package/dungeons/vertical/ecommerce-schema.json +0 -604
- package/dungeons/vertical/education-schema.json +0 -5686
- package/dungeons/vertical/fintech-schema.json +0 -630
- package/dungeons/vertical/fitness-schema.json +0 -530
- package/dungeons/vertical/food-delivery-schema.json +0 -36728
- package/dungeons/vertical/gaming-schema.json +0 -2703
- package/dungeons/vertical/healthcare-schema.json +0 -549
- package/dungeons/vertical/insurance-application-schema.json +0 -485
- package/dungeons/vertical/logistics-schema.json +0 -574
- package/dungeons/vertical/marketplace-schema.json +0 -533
- package/dungeons/vertical/media-schema.json +0 -4749
- package/dungeons/vertical/real-estate-schema.json +0 -527
- package/dungeons/vertical/sass-schema.json +0 -3128
- package/dungeons/vertical/social-schema.json +0 -620
- package/dungeons/vertical/travel-schema.json +0 -580
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// ── TWEAK THESE ──
|
|
2
2
|
const SEED = "coinnest";
|
|
3
3
|
const num_days = 120;
|
|
4
|
-
const num_users =
|
|
4
|
+
const num_users = 10_000;
|
|
5
5
|
const avg_events_per_user_per_day = 0.83;
|
|
6
6
|
let token = "your-mixpanel-token";
|
|
7
7
|
|
|
@@ -46,85 +46,224 @@ const chance = u.initChance(SEED);
|
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* ===================================================================
|
|
49
|
-
* ANALYTICS HOOKS (
|
|
49
|
+
* ANALYTICS HOOKS (10 hooks)
|
|
50
50
|
* ===================================================================
|
|
51
51
|
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
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
|
|
52
|
+
* NOTE: All cohort effects are HIDDEN — no flag stamping. Discoverable
|
|
53
|
+
* via behavioral cohorts (count event per user, observe trade_amount
|
|
54
|
+
* distribution) or raw-prop breakdowns (token_pair, day, trading_tier).
|
|
111
55
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
56
|
+
* 1. WHALE WALLETS (everything)
|
|
57
|
+
*
|
|
58
|
+
* PATTERN: 2% of wallets (deterministic via id hash) get swap
|
|
59
|
+
* trade_amount_usd boosted 50x. No flag — analyst sees long-tail
|
|
60
|
+
* trade-amount distribution.
|
|
61
|
+
*
|
|
62
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
63
|
+
*
|
|
64
|
+
* Report 1: Trade Amount Distribution
|
|
65
|
+
* - Report type: Insights
|
|
66
|
+
* - Event: "swap"
|
|
67
|
+
* - Measure: Distribution of "trade_amount_usd"
|
|
68
|
+
* - Expected: heavy long tail; top 2% of users dominate volume
|
|
69
|
+
*
|
|
70
|
+
* Report 2: Volume Share by Top Traders Cohort
|
|
71
|
+
* - Cohort A: top 5% by total swap volume per user
|
|
72
|
+
* - Event: "swap"
|
|
73
|
+
* - Measure: Total of "trade_amount_usd"
|
|
74
|
+
* - Expected: cohort A drives ~ 60%+ of total volume
|
|
75
|
+
*
|
|
76
|
+
* REAL-WORLD ANALOGUE: Whale wallets dominate exchange volume.
|
|
77
|
+
*
|
|
78
|
+
* ---------------------------------------------------------------
|
|
79
|
+
* 2. GAS PRICE SPIKE (event + everything)
|
|
80
|
+
*
|
|
81
|
+
* PATTERN: Days 35-37, gas_fee_usd 10x on swap + transfer events.
|
|
82
|
+
* In window, 40% of swaps get swap_status flipped to "failed". No flag.
|
|
83
|
+
*
|
|
84
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
85
|
+
*
|
|
86
|
+
* Report 1: Avg Gas Fee Over Time
|
|
87
|
+
* - Report type: Insights
|
|
88
|
+
* - Event: "swap"
|
|
89
|
+
* - Measure: Average of "gas_fee_usd"
|
|
90
|
+
* - Line chart by day
|
|
91
|
+
* - Expected: spike days 35-37 (~10x baseline)
|
|
92
|
+
*
|
|
93
|
+
* Report 2: Swap Failure Rate Over Time
|
|
94
|
+
* - Report type: Insights
|
|
95
|
+
* - Event: "swap"
|
|
96
|
+
* - Measure: Total
|
|
97
|
+
* - Filter: swap_status = "failed"
|
|
98
|
+
* - Line chart by day
|
|
99
|
+
* - Expected: failure rate spikes during gas window
|
|
100
|
+
*
|
|
101
|
+
* REAL-WORLD ANALOGUE: Network congestion breaks transactions.
|
|
102
|
+
*
|
|
103
|
+
* ---------------------------------------------------------------
|
|
104
|
+
* 3. TOKEN LAUNCH SURGE (event + everything)
|
|
105
|
+
*
|
|
106
|
+
* PATTERN: Day 50+, 25% of swaps get token_pair flipped to a MOON
|
|
107
|
+
* pair. Each MOON swap clones 4 extra swap events with unique offset.
|
|
108
|
+
* No flag — discover via token_pair breakdown over time.
|
|
109
|
+
*
|
|
110
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
111
|
+
*
|
|
112
|
+
* Report 1: Swap Volume by Token Pair Over Time
|
|
113
|
+
* - Report type: Insights
|
|
114
|
+
* - Event: "swap"
|
|
115
|
+
* - Measure: Total
|
|
116
|
+
* - Filter: token_pair contains "MOON"
|
|
117
|
+
* - Line chart by day
|
|
118
|
+
* - Expected: appears at day 50, surges thereafter
|
|
119
|
+
*
|
|
120
|
+
* REAL-WORLD ANALOGUE: Meme/altcoin listings drive frenzied volume.
|
|
121
|
+
*
|
|
122
|
+
* ---------------------------------------------------------------
|
|
123
|
+
* 4. AIRDROP HUNTER CHURN (everything)
|
|
124
|
+
*
|
|
125
|
+
* PATTERN: 4% of users (deterministic via charCodeAt(1) % 25 === 0)
|
|
126
|
+
* are airdrop-farming bots. After their first "claim airdrop" event,
|
|
127
|
+
* 95% of subsequent events are removed.
|
|
128
|
+
*
|
|
129
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
130
|
+
*
|
|
131
|
+
* Report 1: Retention by Airdrop Claim
|
|
132
|
+
* - Report type: Retention
|
|
133
|
+
* - Event A: "claim airdrop"
|
|
134
|
+
* - Event B: any event
|
|
135
|
+
* - Expected: claimers retain dramatically worse than overall users
|
|
136
|
+
*
|
|
137
|
+
* Report 2: Airdrop Hunters vs Real Traders
|
|
138
|
+
* - Report type: Insights
|
|
139
|
+
* - Events: "claim airdrop" unique users vs "swap" unique users
|
|
140
|
+
* - Expected: large overlap gap — many claimers never swap again
|
|
141
|
+
*
|
|
142
|
+
* REAL-WORLD ANALOGUE: Airdrop farms are a well-known DeFi growth
|
|
143
|
+
* tax — bots claim free tokens and immediately abandon the platform.
|
|
144
|
+
*
|
|
145
|
+
* ---------------------------------------------------------------
|
|
146
|
+
* 5. KYC FUNNEL COMPLETION (everything)
|
|
147
|
+
*
|
|
148
|
+
* PATTERN: Users with kyc-completed get post-KYC deposit_amount_usd
|
|
149
|
+
* boosted 4x and 7 extra cloned swaps per existing post-KYC swap.
|
|
150
|
+
* No flag — discover via cohort builder.
|
|
151
|
+
*
|
|
152
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
153
|
+
*
|
|
154
|
+
* Report 1: Avg Deposit by KYC Cohort
|
|
155
|
+
* - Report type: Insights (with cohort)
|
|
156
|
+
* - Cohort A: users with >= 1 "kyc completed"
|
|
157
|
+
* - Cohort B: rest
|
|
158
|
+
* - Event: "deposit"
|
|
159
|
+
* - Measure: Average of "deposit_amount_usd"
|
|
160
|
+
* - Expected: A ~ 4x B
|
|
161
|
+
*
|
|
162
|
+
* REAL-WORLD ANALOGUE: KYC unlocks higher limits.
|
|
163
|
+
*
|
|
164
|
+
* ---------------------------------------------------------------
|
|
165
|
+
* 6. STAKE-TO-RETAIN (everything)
|
|
166
|
+
*
|
|
167
|
+
* PATTERN: Users who stake any token within first 14 days get extra
|
|
168
|
+
* cloned swap + portfolio events past day 60. Non-stakers lose 85% of
|
|
169
|
+
* post-day-60 events. No flag — discover via retention cohort.
|
|
170
|
+
*
|
|
171
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
172
|
+
*
|
|
173
|
+
* Report 1: Retention by Early Stake Cohort
|
|
174
|
+
* - Report type: Retention
|
|
175
|
+
* - Cohort A: users with >= 1 "stake" in first 14 days
|
|
176
|
+
* - Cohort B: rest
|
|
177
|
+
* - Expected: A ~ 70% D60 vs B ~ 15%
|
|
178
|
+
*
|
|
179
|
+
* REAL-WORLD ANALOGUE: Staking creates skin in the game.
|
|
180
|
+
*
|
|
181
|
+
* ---------------------------------------------------------------
|
|
182
|
+
* 7. PRO TIER MAKER FEES (everything)
|
|
183
|
+
*
|
|
184
|
+
* PATTERN: Pro-tier users (profile.trading_tier) get maker_fee_pct
|
|
185
|
+
* pinned to 0.05 (vs Standard 0.30) on swap events plus 5 extra
|
|
186
|
+
* cloned swaps per existing. Mutates raw prop. Discover via
|
|
187
|
+
* trading_tier breakdown on maker_fee_pct.
|
|
188
|
+
*
|
|
189
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
190
|
+
*
|
|
191
|
+
* Report 1: Avg Maker Fee by Tier
|
|
192
|
+
* - Report type: Insights
|
|
193
|
+
* - Event: "swap"
|
|
194
|
+
* - Measure: Average of "maker_fee_pct"
|
|
195
|
+
* - Breakdown: "trading_tier"
|
|
196
|
+
* - Expected: Pro ~ 0.05, Standard ~ 0.30
|
|
197
|
+
*
|
|
198
|
+
* REAL-WORLD ANALOGUE: Tiered fees retain volume traders.
|
|
199
|
+
*
|
|
200
|
+
* ---------------------------------------------------------------
|
|
201
|
+
* 8. RUG-PULL AFTERMATH (everything)
|
|
202
|
+
*
|
|
203
|
+
* PATTERN: ~2% of pre-day-70 swaps get token_pair flipped to a SCAM
|
|
204
|
+
* pair. Users who held SCAM lose 80% of post-day-70 events. No flag.
|
|
205
|
+
*
|
|
206
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
207
|
+
*
|
|
208
|
+
* Report 1: Retention by SCAM Holder Cohort
|
|
209
|
+
* - Report type: Retention
|
|
210
|
+
* - Cohort A: users with >= 1 swap where token_pair contains "SCAM"
|
|
211
|
+
* - Cohort B: rest
|
|
212
|
+
* - Expected: A retention drops sharply at day 70
|
|
213
|
+
*
|
|
214
|
+
* REAL-WORLD ANALOGUE: Rug-pulls collapse trust.
|
|
215
|
+
*
|
|
216
|
+
* ---------------------------------------------------------------
|
|
217
|
+
* 9. TRADING FUNNEL TIME-TO-CONVERT (funnel-post)
|
|
218
|
+
*
|
|
219
|
+
* PATTERN: Pro tier users complete deposit→swap→withdrawal funnel
|
|
220
|
+
* 1.4x faster than baseline (factor 0.7); Standard 1.3x slower
|
|
221
|
+
* (factor 1.3). Mutates funnel event timestamps.
|
|
222
|
+
*
|
|
223
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
224
|
+
*
|
|
225
|
+
* Report 1: Trading Funnel Median Time-to-Convert by Tier
|
|
226
|
+
* - Report type: Funnels
|
|
227
|
+
* - Steps: "deposit" -> "swap" -> "withdrawal"
|
|
228
|
+
* - Measure: Median time to convert
|
|
229
|
+
* - Breakdown: "trading_tier"
|
|
230
|
+
* - Expected: Pro ~ 0.7x; Standard ~ 1.3x
|
|
115
231
|
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* - Stakers + Whales: Do whales stake more, or just trade?
|
|
121
|
-
* - Airdrop Hunters + Rug Pull: Overlap between bots and victims?
|
|
232
|
+
* NOTE (funnel-post measurement): visible only via Mixpanel funnel
|
|
233
|
+
* median TTC. Cross-event MIN→MIN SQL queries on raw events do NOT
|
|
234
|
+
* show this — funnel-post adjusts gaps within funnel instances, not
|
|
235
|
+
* across the user's full event history.
|
|
122
236
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
237
|
+
* REAL-WORLD ANALOGUE: Pro traders execute faster end-to-end.
|
|
238
|
+
*
|
|
239
|
+
* ---------------------------------------------------------------
|
|
240
|
+
* 10. SWAP-COUNT MAGIC NUMBER (everything)
|
|
241
|
+
*
|
|
242
|
+
* PATTERN: Users with 8-20 swaps in dataset get +30% on stake
|
|
243
|
+
* amount_usd plus 1-2 extra cloned stake events per existing.
|
|
244
|
+
* Users with 21+ swaps drop 40% of portfolio-viewed events
|
|
245
|
+
* (over-active traders ignore portfolio review). No flag.
|
|
246
|
+
*
|
|
247
|
+
* HOW TO FIND IT IN MIXPANEL:
|
|
248
|
+
*
|
|
249
|
+
* Report 1: Avg Stake Amount by Swap-Count Bucket
|
|
250
|
+
* - Report type: Insights (with cohort)
|
|
251
|
+
* - Cohort A: users with 8-20 "swap"
|
|
252
|
+
* - Cohort B: users with 0-7
|
|
253
|
+
* - Event: "stake"
|
|
254
|
+
* - Measure: Average of "amount_usd"
|
|
255
|
+
* - Expected: A ~ 1.3x B
|
|
256
|
+
*
|
|
257
|
+
* Report 2: Portfolio Views per User on Heavy Swappers
|
|
258
|
+
* - Report type: Insights (with cohort)
|
|
259
|
+
* - Cohort C: users with >= 21 "swap"
|
|
260
|
+
* - Cohort A: users with 8-20
|
|
261
|
+
* - Event: "portfolio viewed"
|
|
262
|
+
* - Measure: Total per user
|
|
263
|
+
* - Expected: C ~ 40% fewer portfolio views per user
|
|
264
|
+
*
|
|
265
|
+
* REAL-WORLD ANALOGUE: Engaged swappers grow stake; over-active
|
|
266
|
+
* day-traders ignore long-term portfolio review.
|
|
128
267
|
*
|
|
129
268
|
* ===================================================================
|
|
130
269
|
* EXPECTED METRICS SUMMARY
|
|
@@ -132,14 +271,17 @@ const chance = u.initChance(SEED);
|
|
|
132
271
|
*
|
|
133
272
|
* Hook | Metric | Baseline | Effect | Ratio
|
|
134
273
|
* ----------------------|-----------------------|----------|---------|------
|
|
135
|
-
* Whale Wallets |
|
|
136
|
-
* Gas Price Spike |
|
|
137
|
-
* Token Launch Surge |
|
|
138
|
-
* Airdrop Hunter Churn |
|
|
139
|
-
* KYC Completion |
|
|
140
|
-
* Stake-to-Retain | D60 retention | 15% | 70% | ~5x
|
|
141
|
-
* Pro Tier Fees |
|
|
142
|
-
* Rug-Pull Aftermath |
|
|
274
|
+
* Whale Wallets | Top 5% trade share | 5% | ~ 60% | n/a
|
|
275
|
+
* Gas Price Spike | gas_fee_usd days 35-7 | 1x | 10x | 10x
|
|
276
|
+
* Token Launch Surge | MOON pair share post-D50 | 0% | ~ 25%+ | new
|
|
277
|
+
* Airdrop Hunter Churn | post-airdrop events | 1x | 0.05x | -95%
|
|
278
|
+
* KYC Completion | deposit_amount_usd | 1x | 4x | 4x
|
|
279
|
+
* Stake-to-Retain | D60 retention | 15% | 70% | ~ 5x
|
|
280
|
+
* Pro Tier Fees | maker_fee_pct | 0.30 | 0.05 | -83%
|
|
281
|
+
* Rug-Pull Aftermath | victim post-D70 | 1x | 0.2x | -80%
|
|
282
|
+
* Trading T2C | median min by tier | 1x | 0.7x/1.3x | 1.86x range
|
|
283
|
+
* Swap Magic Number | sweet stake amount | 1x | 1.3x | 1.3x
|
|
284
|
+
* Swap Magic Number | over portfolio/user | 1x | 0.6x | -40%
|
|
143
285
|
*/
|
|
144
286
|
|
|
145
287
|
// Token pairs used in swap events
|
|
@@ -165,12 +307,16 @@ const NFT_COLLECTIONS = [
|
|
|
165
307
|
|
|
166
308
|
/** @type {Config} */
|
|
167
309
|
const config = {
|
|
310
|
+
version: 2,
|
|
168
311
|
token,
|
|
169
312
|
seed: SEED,
|
|
170
|
-
|
|
313
|
+
datasetStart: "2026-01-01T00:00:00Z",
|
|
314
|
+
datasetEnd: "2026-05-01T23:59:59Z",
|
|
315
|
+
// numDays: num_days,
|
|
171
316
|
avgEventsPerUserPerDay: avg_events_per_user_per_day,
|
|
172
317
|
numUsers: num_users,
|
|
173
|
-
hasAnonIds:
|
|
318
|
+
hasAnonIds: true,
|
|
319
|
+
avgDevicePerUser: 2,
|
|
174
320
|
hasSessionIds: true,
|
|
175
321
|
format: "json",
|
|
176
322
|
gzip: true,
|
|
@@ -225,6 +371,7 @@ const config = {
|
|
|
225
371
|
event: "wallet connected",
|
|
226
372
|
weight: 1,
|
|
227
373
|
isFirstEvent: true,
|
|
374
|
+
isAuthEvent: true,
|
|
228
375
|
properties: {
|
|
229
376
|
wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
|
|
230
377
|
chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
|
|
@@ -254,7 +401,6 @@ const config = {
|
|
|
254
401
|
deposit_amount_usd: u.weighNumRange(10, 5000, 0.3, 500),
|
|
255
402
|
chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
|
|
256
403
|
deposit_method: ["wallet_transfer", "bridge", "on_ramp", "exchange_transfer"],
|
|
257
|
-
kyc_verified: [false],
|
|
258
404
|
}
|
|
259
405
|
},
|
|
260
406
|
{
|
|
@@ -277,13 +423,6 @@ const config = {
|
|
|
277
423
|
swap_status: ["completed", "completed", "completed", "completed", "pending", "failed"],
|
|
278
424
|
slippage_pct: u.weighNumRange(0.01, 5, 0.3, 0.5),
|
|
279
425
|
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
426
|
}
|
|
288
427
|
},
|
|
289
428
|
{
|
|
@@ -363,7 +502,6 @@ const config = {
|
|
|
363
502
|
amount_usd: u.weighNumRange(10, 5000, 0.3, 300),
|
|
364
503
|
gas_fee_usd: u.weighNumRange(0.5, 30, 0.3, 5),
|
|
365
504
|
destination_type: ["wallet", "exchange", "contract", "ens_name"],
|
|
366
|
-
gas_spike: [false],
|
|
367
505
|
}
|
|
368
506
|
},
|
|
369
507
|
],
|
|
@@ -372,7 +510,6 @@ const config = {
|
|
|
372
510
|
trading_tier: ["Standard", "Standard", "Standard", "Pro"],
|
|
373
511
|
preferred_chain: ["Ethereum", "Solana", "Base", "Arbitrum", "Polygon"],
|
|
374
512
|
wallet_type: ["MetaMask", "Phantom", "Coinbase Wallet", "Rainbow", "WalletConnect"],
|
|
375
|
-
staked_early: [false],
|
|
376
513
|
},
|
|
377
514
|
|
|
378
515
|
userProps: {
|
|
@@ -382,70 +519,40 @@ const config = {
|
|
|
382
519
|
total_trade_volume: u.weighNumRange(0, 500000, 0.3, 5000),
|
|
383
520
|
portfolio_value: u.weighNumRange(0, 200000, 0.3, 3000),
|
|
384
521
|
kyc_status: ["pending"],
|
|
385
|
-
has_staked: [false],
|
|
386
|
-
staked_early: [false],
|
|
387
522
|
},
|
|
388
523
|
|
|
389
524
|
groupKeys: [],
|
|
390
525
|
groupProps: {},
|
|
391
526
|
lookupTables: [],
|
|
392
527
|
|
|
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
528
|
hook: function (record, type, meta) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
//
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
record.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
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;
|
|
529
|
+
// HOOKS 2 + 3: GAS PRICE SPIKE and TOKEN LAUNCH SURGE moved to everything
|
|
530
|
+
// hook below — event hook fires before bunchIntoSessions reshuffles timestamps,
|
|
531
|
+
// causing day-window tags to leak across the boundary.
|
|
532
|
+
|
|
533
|
+
// HOOK 9 (T2C): TRADING FUNNEL TIME-TO-CONVERT (funnel-post)
|
|
534
|
+
// Pro tier users complete deposit→swap→withdrawal funnel 1.4x faster
|
|
535
|
+
// (factor 0.7 on inter-event gaps); Standard 1.3x slower (factor 1.3).
|
|
536
|
+
if (type === "funnel-post") {
|
|
537
|
+
const segment = meta?.profile?.trading_tier;
|
|
538
|
+
if (Array.isArray(record) && record.length > 1) {
|
|
539
|
+
const factor = (
|
|
540
|
+
segment === "Pro" ? 0.7 :
|
|
541
|
+
segment === "Standard" ? 1.3 :
|
|
542
|
+
1.0
|
|
543
|
+
);
|
|
544
|
+
if (factor !== 1.0) {
|
|
545
|
+
for (let i = 1; i < record.length; i++) {
|
|
546
|
+
const prev = dayjs(record[i - 1].time);
|
|
547
|
+
const newGap = Math.round(dayjs(record[i].time).diff(prev) * factor);
|
|
548
|
+
record[i].time = prev.add(newGap, "milliseconds").toISOString();
|
|
549
|
+
}
|
|
441
550
|
}
|
|
442
551
|
}
|
|
443
552
|
}
|
|
444
553
|
|
|
445
|
-
// ===============================================================
|
|
446
|
-
// EVERYTHING HOOK — all complex behavioral patterns
|
|
447
|
-
// ===============================================================
|
|
448
554
|
if (type === "everything") {
|
|
555
|
+
const datasetStart = dayjs.unix(meta.datasetStart);
|
|
449
556
|
const userEvents = record;
|
|
450
557
|
const profile = meta.profile;
|
|
451
558
|
|
|
@@ -454,17 +561,12 @@ const config = {
|
|
|
454
561
|
e.trading_tier = profile.trading_tier;
|
|
455
562
|
e.preferred_chain = profile.preferred_chain;
|
|
456
563
|
e.wallet_type = profile.wallet_type;
|
|
457
|
-
e.staked_early = profile.staked_early;
|
|
458
564
|
});
|
|
459
565
|
|
|
460
566
|
const firstEventTime = userEvents.length > 0 ? dayjs(userEvents[0].time) : null;
|
|
461
567
|
|
|
462
|
-
//
|
|
463
|
-
//
|
|
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
|
-
// -----------------------------------------------------------
|
|
568
|
+
// HOOK 1: WHALE WALLETS — 2% of users (charCodeAt(0) % 50 === 0)
|
|
569
|
+
// get swap trade_amount_usd boosted 50x. No flag.
|
|
468
570
|
const userId = userEvents.length > 0 ? (userEvents[0].user_id || userEvents[0].distinct_id || "") : "";
|
|
469
571
|
const isWhale = userId.length > 0 && userId.charCodeAt(0) % 50 === 0;
|
|
470
572
|
|
|
@@ -472,30 +574,27 @@ const config = {
|
|
|
472
574
|
userEvents.forEach(e => {
|
|
473
575
|
if (e.event === "swap") {
|
|
474
576
|
e.trade_amount_usd = Math.round((e.trade_amount_usd || 200) * 50);
|
|
475
|
-
e.is_whale = true;
|
|
476
577
|
}
|
|
477
578
|
});
|
|
478
579
|
}
|
|
479
580
|
|
|
480
|
-
//
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
|
|
484
|
-
// -----------------------------------------------------------
|
|
581
|
+
// HOOK 2 moved to end (after all cloning) for full coverage
|
|
582
|
+
|
|
583
|
+
// HOOK 3: TOKEN LAUNCH SURGE — after d50, 25% of swaps flip token_pair to MOON.
|
|
584
|
+
const day50 = datasetStart.add(50, "days");
|
|
485
585
|
userEvents.forEach(e => {
|
|
486
|
-
if (e.event === "swap" && e.
|
|
487
|
-
|
|
488
|
-
e.swap_status = "failed";
|
|
489
|
-
}
|
|
586
|
+
if (e.event === "swap" && dayjs(e.time).isAfter(day50) && chance.bool({ likelihood: 25 })) {
|
|
587
|
+
e.token_pair = chance.pickone(["MOON/USDC", "MOON/ETH", "ETH/MOON"]);
|
|
490
588
|
}
|
|
491
589
|
});
|
|
492
590
|
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
591
|
+
// HOOK 3 (cont): TOKEN LAUNCH SURGE — clone 4 extra MOON-pair
|
|
592
|
+
// swap events per existing MOON swap. Cloned with unique offset.
|
|
593
|
+
const moonSwaps = userEvents.filter(e =>
|
|
594
|
+
e.event === "swap" &&
|
|
595
|
+
typeof e.token_pair === "string" &&
|
|
596
|
+
e.token_pair.includes("MOON")
|
|
597
|
+
);
|
|
499
598
|
if (moonSwaps.length > 0) {
|
|
500
599
|
const clonedMoonEvents = [];
|
|
501
600
|
moonSwaps.forEach(moonEvt => {
|
|
@@ -514,14 +613,16 @@ const config = {
|
|
|
514
613
|
|
|
515
614
|
// -----------------------------------------------------------
|
|
516
615
|
// Hook #4: AIRDROP HUNTER CHURN
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
// first
|
|
616
|
+
// 4% of users are deterministically tagged as airdrop-farming bots
|
|
617
|
+
// via charCodeAt(1) % 25 === 0. Bots that have ever claimed an airdrop
|
|
618
|
+
// lose 95% of events after their first claim. (Original cohort
|
|
619
|
+
// "claimed-but-never-swapped" was empty because every claimer also
|
|
620
|
+
// has organic swap events at this scale.)
|
|
520
621
|
// -----------------------------------------------------------
|
|
622
|
+
const isAirdropBot = userId.length > 1 && userId.charCodeAt(1) % 25 === 0;
|
|
521
623
|
const hasAirdropClaim = userEvents.some(e => e.event === "claim airdrop");
|
|
522
|
-
const hasSwap = userEvents.some(e => e.event === "swap");
|
|
523
624
|
|
|
524
|
-
if (
|
|
625
|
+
if (isAirdropBot && hasAirdropClaim) {
|
|
525
626
|
const firstClaim = userEvents.find(e => e.event === "claim airdrop");
|
|
526
627
|
if (firstClaim) {
|
|
527
628
|
const claimTime = dayjs(firstClaim.time);
|
|
@@ -536,31 +637,22 @@ const config = {
|
|
|
536
637
|
}
|
|
537
638
|
}
|
|
538
639
|
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
//
|
|
542
|
-
// and 8x more swap events (cloned). Flag kyc_verified=true on
|
|
543
|
-
// post-KYC events.
|
|
544
|
-
// -----------------------------------------------------------
|
|
640
|
+
// HOOK 5: KYC FUNNEL COMPLETION — users with kyc-completed
|
|
641
|
+
// get post-KYC deposit_amount_usd boosted 4x and 7 extra cloned
|
|
642
|
+
// swaps per existing post-KYC swap. No flag.
|
|
545
643
|
const kycCompleted = userEvents.find(e => e.event === "kyc completed");
|
|
546
644
|
if (kycCompleted) {
|
|
547
645
|
const kycTime = dayjs(kycCompleted.time);
|
|
548
646
|
|
|
549
|
-
// Boost deposits and flag post-KYC events
|
|
550
647
|
userEvents.forEach(e => {
|
|
551
|
-
if (dayjs(e.time).isAfter(kycTime)) {
|
|
552
|
-
|
|
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
|
-
}
|
|
648
|
+
if (dayjs(e.time).isAfter(kycTime) && e.event === "deposit") {
|
|
649
|
+
e.deposit_amount_usd = Math.round((e.deposit_amount_usd || 500) * 4);
|
|
559
650
|
}
|
|
560
651
|
});
|
|
561
652
|
|
|
562
|
-
|
|
563
|
-
|
|
653
|
+
const postKycSwaps = userEvents.filter(e =>
|
|
654
|
+
e.event === "swap" && dayjs(e.time).isAfter(kycTime)
|
|
655
|
+
);
|
|
564
656
|
const clonedKycSwaps = [];
|
|
565
657
|
postKycSwaps.forEach(swapEvt => {
|
|
566
658
|
const swapTime = dayjs(swapEvt.time);
|
|
@@ -570,7 +662,6 @@ const config = {
|
|
|
570
662
|
time: swapTime.add(chance.integer({ min: 1, max: 48 }), "hours").toISOString(),
|
|
571
663
|
user_id: swapEvt.user_id,
|
|
572
664
|
trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.8, max: 1.5 })),
|
|
573
|
-
kyc_verified: true,
|
|
574
665
|
});
|
|
575
666
|
}
|
|
576
667
|
});
|
|
@@ -583,19 +674,15 @@ const config = {
|
|
|
583
674
|
// dataset have 70% D60 retention (events injected beyond D60).
|
|
584
675
|
// Non-stakers lose events after day 60 to simulate 15% retention.
|
|
585
676
|
// -----------------------------------------------------------
|
|
586
|
-
const day14 =
|
|
587
|
-
const day60 =
|
|
677
|
+
const day14 = datasetStart.add(14, "days");
|
|
678
|
+
const day60 = datasetStart.add(60, "days");
|
|
588
679
|
|
|
589
680
|
const stakedEarly = userEvents.some(e =>
|
|
590
681
|
e.event === "stake" && dayjs(e.time).isBefore(day14)
|
|
591
682
|
);
|
|
592
683
|
|
|
593
684
|
if (stakedEarly) {
|
|
594
|
-
//
|
|
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)
|
|
685
|
+
// 70% retention: inject late-stage events for stakers (no flag)
|
|
599
686
|
const postD60Events = userEvents.filter(e => dayjs(e.time).isAfter(day60));
|
|
600
687
|
if (postD60Events.length < 5) {
|
|
601
688
|
const swapTemplate = userEvents.find(e => e.event === "swap");
|
|
@@ -607,7 +694,6 @@ const config = {
|
|
|
607
694
|
time: day60.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
|
|
608
695
|
user_id: swapTemplate.user_id,
|
|
609
696
|
trade_amount_usd: Math.round((swapTemplate.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 2.0 })),
|
|
610
|
-
staked_early: true,
|
|
611
697
|
});
|
|
612
698
|
}
|
|
613
699
|
}
|
|
@@ -617,7 +703,6 @@ const config = {
|
|
|
617
703
|
...portfolioTemplate,
|
|
618
704
|
time: day60.add(chance.integer({ min: 1, max: 55 }), "days").toISOString(),
|
|
619
705
|
user_id: portfolioTemplate.user_id,
|
|
620
|
-
staked_early: true,
|
|
621
706
|
});
|
|
622
707
|
}
|
|
623
708
|
}
|
|
@@ -634,21 +719,14 @@ const config = {
|
|
|
634
719
|
}
|
|
635
720
|
}
|
|
636
721
|
|
|
637
|
-
//
|
|
638
|
-
//
|
|
639
|
-
//
|
|
640
|
-
// vs standard 0.30. Pro users also trade 6x volume.
|
|
641
|
-
// pro_advantage=true on Pro swap events.
|
|
642
|
-
// -----------------------------------------------------------
|
|
722
|
+
// HOOK 7: PRO TIER MAKER FEES — Pro users pay maker_fee_pct=0.05
|
|
723
|
+
// (vs Standard 0.30) and get 5 extra cloned swaps. Mutates raw
|
|
724
|
+
// maker_fee_pct prop. Reads trading_tier from profile.
|
|
643
725
|
if (profile.trading_tier === "Pro") {
|
|
644
726
|
userEvents.forEach(e => {
|
|
645
|
-
if (e.event === "swap")
|
|
646
|
-
e.maker_fee_pct = 0.05;
|
|
647
|
-
e.pro_advantage = true;
|
|
648
|
-
}
|
|
727
|
+
if (e.event === "swap") e.maker_fee_pct = 0.05;
|
|
649
728
|
});
|
|
650
729
|
|
|
651
|
-
// Clone swap events for 6x volume
|
|
652
730
|
const proSwaps = userEvents.filter(e => e.event === "swap");
|
|
653
731
|
const clonedProSwaps = [];
|
|
654
732
|
proSwaps.forEach(swapEvt => {
|
|
@@ -660,37 +738,29 @@ const config = {
|
|
|
660
738
|
user_id: swapEvt.user_id,
|
|
661
739
|
trade_amount_usd: Math.round((swapEvt.trade_amount_usd || 200) * chance.floating({ min: 0.5, max: 3.0 })),
|
|
662
740
|
maker_fee_pct: 0.05,
|
|
663
|
-
pro_advantage: true,
|
|
664
741
|
});
|
|
665
742
|
}
|
|
666
743
|
});
|
|
667
744
|
userEvents.push(...clonedProSwaps);
|
|
668
745
|
} else {
|
|
669
|
-
// Ensure standard fee on non-Pro swap events
|
|
670
746
|
userEvents.forEach(e => {
|
|
671
|
-
if (e.event === "swap")
|
|
672
|
-
e.maker_fee_pct = 0.30;
|
|
673
|
-
}
|
|
747
|
+
if (e.event === "swap") e.maker_fee_pct = 0.30;
|
|
674
748
|
});
|
|
675
749
|
}
|
|
676
750
|
|
|
677
751
|
// -----------------------------------------------------------
|
|
678
752
|
// Hook #8: RUG-PULL AFTERMATH
|
|
679
753
|
// Day 70, "SCAM" token rugs. Users who swapped SCAM token
|
|
680
|
-
// before day 70 lose 80% of events after day 70.
|
|
681
|
-
//
|
|
754
|
+
// before day 70 lose 80% of events after day 70. No flag —
|
|
755
|
+
// derive cohort by detecting users with token_pair containing "SCAM".
|
|
682
756
|
// -----------------------------------------------------------
|
|
683
|
-
const day70 =
|
|
757
|
+
const day70 = datasetStart.add(70, "days");
|
|
684
758
|
|
|
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
759
|
let hadScam = false;
|
|
689
760
|
userEvents.forEach(e => {
|
|
690
761
|
if (e.event === "swap") {
|
|
691
|
-
const swapDay = dayjs(e.time).diff(
|
|
692
|
-
|
|
693
|
-
if (swapDay >= 10 && swapDay < 70 && chance.bool({ likelihood: 8 })) {
|
|
762
|
+
const swapDay = dayjs(e.time).diff(datasetStart, "day");
|
|
763
|
+
if (swapDay >= 10 && swapDay < 70 && chance.bool({ likelihood: 2 })) {
|
|
694
764
|
e.token_pair = chance.pickone(["SCAM/USDC", "SCAM/ETH", "ETH/SCAM"]);
|
|
695
765
|
hadScam = true;
|
|
696
766
|
}
|
|
@@ -700,17 +770,57 @@ const config = {
|
|
|
700
770
|
if (hadScam) {
|
|
701
771
|
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
702
772
|
const evt = userEvents[i];
|
|
703
|
-
if (dayjs(evt.time).isAfter(day70)) {
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
773
|
+
if (dayjs(evt.time).isAfter(day70) && chance.bool({ likelihood: 80 })) {
|
|
774
|
+
userEvents.splice(i, 1);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// HOOK 10: SWAP-COUNT MAGIC NUMBER (no flags)
|
|
780
|
+
// Sweet 8-20 swaps → +30% on stake amount_usd; clone 1-2 extra
|
|
781
|
+
// stake events per existing. Over 21+ → drop 40% of portfolio
|
|
782
|
+
// viewed events (over-active traders ignore their portfolio).
|
|
783
|
+
const swapCount = userEvents.filter(e => e.event === "swap").length;
|
|
784
|
+
if (swapCount >= 8 && swapCount <= 20) {
|
|
785
|
+
const stakeTemplate = userEvents.find(e => e.event === "stake");
|
|
786
|
+
if (stakeTemplate) {
|
|
787
|
+
const stakes = userEvents.filter(e => e.event === "stake");
|
|
788
|
+
stakes.forEach(s => {
|
|
789
|
+
if (typeof s.amount_usd === "number") s.amount_usd = Math.round(s.amount_usd * 1.3);
|
|
790
|
+
const extras = chance.integer({ min: 1, max: 2 });
|
|
791
|
+
for (let k = 0; k < extras; k++) {
|
|
792
|
+
userEvents.push({
|
|
793
|
+
...s,
|
|
794
|
+
time: dayjs(s.time).add(chance.integer({ min: 5, max: 360 }), "minutes").toISOString(),
|
|
795
|
+
user_id: s.user_id,
|
|
796
|
+
});
|
|
708
797
|
}
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
} else if (swapCount >= 21) {
|
|
801
|
+
for (let i = userEvents.length - 1; i >= 0; i--) {
|
|
802
|
+
if (userEvents[i].event === "portfolio viewed" && chance.bool({ likelihood: 40 })) {
|
|
803
|
+
userEvents.splice(i, 1);
|
|
709
804
|
}
|
|
710
805
|
}
|
|
711
806
|
}
|
|
712
807
|
|
|
713
|
-
//
|
|
808
|
+
// HOOK 2: GAS PRICE SPIKE — days 35-37 swap+transfer gas_fee_usd 10x,
|
|
809
|
+
// 40% of swaps get swap_status=failed. Runs AFTER all cloning to
|
|
810
|
+
// catch events that land in the spike window from clone offsets.
|
|
811
|
+
const day35 = datasetStart.add(35, "days");
|
|
812
|
+
const day38 = datasetStart.add(38, "days");
|
|
813
|
+
userEvents.forEach(e => {
|
|
814
|
+
if (e.event !== "swap" && e.event !== "transfer") return;
|
|
815
|
+
const t = dayjs(e.time);
|
|
816
|
+
if (t.isAfter(day35) && t.isBefore(day38)) {
|
|
817
|
+
e.gas_fee_usd = Math.round((e.gas_fee_usd || 5) * 10);
|
|
818
|
+
if (e.event === "swap" && chance.bool({ likelihood: 40 })) {
|
|
819
|
+
e.swap_status = "failed";
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
});
|
|
823
|
+
|
|
714
824
|
userEvents.sort((a, b) => new Date(a.time) - new Date(b.time));
|
|
715
825
|
}
|
|
716
826
|
|