@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.
- package/CHANGELOG.md +42 -0
- package/README.md +51 -13
- package/dungeons/technical/ad-spend.js +2 -2
- package/dungeons/technical/anonymous-users.js +2 -2
- package/dungeons/technical/array-of-object-lookup.js +2 -2
- package/dungeons/technical/experiments.js +2 -2
- package/dungeons/technical/foobar.js +2 -2
- package/dungeons/technical/group-analytics.js +2 -2
- package/dungeons/technical/mirror-strategies.js +2 -2
- package/dungeons/technical/nested-objects.js +2 -2
- package/dungeons/technical/retention-cadence.js +2 -3
- package/dungeons/technical/sanity.js +2 -2
- package/dungeons/technical/scale-test.js +2 -2
- package/dungeons/technical/scd.js +2 -2
- package/dungeons/technical/simple.js +2 -2
- package/dungeons/technical/simplest.js +2 -2
- package/dungeons/technical/text-generation.js +2 -2
- package/dungeons/vertical/ai-platform-schema.json +617 -0
- package/dungeons/vertical/ai-platform.js +799 -0
- package/dungeons/vertical/community.js +40 -26
- package/dungeons/vertical/crypto-schema.json +546 -0
- package/dungeons/vertical/crypto.js +721 -0
- package/dungeons/vertical/dating-schema.json +401 -0
- package/dungeons/vertical/dating.js +798 -0
- package/dungeons/vertical/devtools.js +13 -9
- package/dungeons/vertical/ecommerce.js +2 -3
- package/dungeons/vertical/education.js +4 -5
- package/dungeons/vertical/fintech.js +32 -29
- package/dungeons/vertical/fitness.js +2 -3
- package/dungeons/vertical/food-delivery.js +37 -43
- package/dungeons/vertical/gaming-schema.json +2495 -230
- package/dungeons/vertical/gaming.js +771 -388
- package/dungeons/vertical/healthcare.js +2 -3
- package/dungeons/vertical/insurance-application.js +2 -3
- package/dungeons/vertical/logistics.js +20 -14
- package/dungeons/vertical/marketplace.js +22 -14
- package/dungeons/vertical/media.js +39 -30
- package/dungeons/vertical/real-estate-schema.json +527 -0
- package/dungeons/vertical/real-estate.js +774 -0
- package/dungeons/vertical/sass.js +2 -3
- package/dungeons/vertical/social.js +15 -13
- package/dungeons/vertical/travel.js +59 -26
- package/lib/core/config-validator.js +71 -15
- package/lib/core/storage.js +14 -4
- package/lib/orchestrators/user-loop.js +39 -5
- package/lib/templates/macro-presets.js +111 -0
- package/lib/templates/soup-presets.js +19 -36
- package/package.json +8 -2
- package/types.d.ts +219 -42
- package/dungeons/user/.gitkeep +0 -0
- package/dungeons/vertical/rpg-schema.json +0 -2491
- 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;
|