@wkalidev/trivia-quest-sdk 2.0.0 → 3.0.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/sdk/src/index.ts CHANGED
@@ -1,91 +1,443 @@
1
- export const TRIVQ_TOKEN_ADDRESS = "0xe65fc5cacaf9a5aebbc0e151dee08a53f24a05c5" as const;
2
- export const TRIVIA_QUEST_ADDRESS = "0xffe22d3d1b63866ac9da8ac92fdb9ceddeadb0bb" as const;
3
- export const DAILY_CHECKIN_ADDRESS = "0x8650e6c477f8ae3933dc6d61d85e65c90cf71828" as const;
4
- export const REFERRAL_ADDRESS = "0xa0fcd85a25ecb71ca1ea9d63da058c832c27c62e" as const;
1
+ // TriviaQuest SDK v2.0.0
2
+ // Blockchain quiz game on Celo & Base
3
+ // https://trivia-quest-eight.vercel.app
5
4
 
6
- export const TRIVQ_ABI = [
5
+ export const SDK_VERSION = "2.0.0";
6
+
7
+ // ── Contract Addresses ─────────────────────────────────────
8
+
9
+ // Celo Mainnet
10
+ export const TRIVIA_QUEST_ADDRESS_CELO = "0xffe22d3d1b63866ac9da8ac92fdb9ceddeadb0bb" as const;
11
+ export const TRIVQ_TOKEN_ADDRESS_CELO = "0xe65fc5cacaf9a5aebbc0e151dee08a53f24a05c5" as const;
12
+ export const CHECKIN_ADDRESS_CELO = "0x8650e6c477f8ae3933dc6d61d85e65c90cf71828" as const;
13
+ export const REFERRAL_ADDRESS_CELO = "0xa0fcd85a25ecb71ca1ea9d63da058c832c27c62e" as const;
14
+ export const DUEL_ADDRESS_CELO = "0xee7be00cd5454b9bea56d864d82076b8b5de5ca1" as const;
15
+
16
+ // Base Mainnet
17
+ export const TRIVIA_QUEST_ADDRESS_BASE = "0x1e2c209412ec30915ccf922654f0593faf61fcfb" as const;
18
+ export const TRIVQ_TOKEN_ADDRESS_BASE = "0x8ecc1dc70f3bc5be941b61b42707eb7dbddb54c3" as const;
19
+ export const CHECKIN_ADDRESS_BASE = "0x0f19851d5cd905d110c000a7d26d74a2f21f8ff9" as const;
20
+ export const REFERRAL_ADDRESS_BASE = "0x4fb5285263354e1e75f044c65166ab22c3840074" as const;
21
+
22
+ // Legacy exports (backwards compatibility)
23
+ export const CONTRACT_ADDRESS_MAINNET = TRIVIA_QUEST_ADDRESS_CELO;
24
+ export const CONTRACT_ADDRESS_TESTNET = "0x50b20728ba0ad803679b5428f267c89aede9a378" as const;
25
+
26
+ // ── Network Config ─────────────────────────────────────────
27
+
28
+ export const CELO_MAINNET = {
29
+ id: 42220,
30
+ name: "Celo Mainnet",
31
+ rpcUrl: "https://forno.celo.org",
32
+ explorerUrl: "https://celoscan.io",
33
+ contracts: {
34
+ game: TRIVIA_QUEST_ADDRESS_CELO,
35
+ token: TRIVQ_TOKEN_ADDRESS_CELO,
36
+ checkin: CHECKIN_ADDRESS_CELO,
37
+ referral:REFERRAL_ADDRESS_CELO,
38
+ duel: DUEL_ADDRESS_CELO,
39
+ },
40
+ };
41
+
42
+ export const BASE_MAINNET = {
43
+ id: 8453,
44
+ name: "Base Mainnet",
45
+ rpcUrl: "https://mainnet.base.org",
46
+ explorerUrl: "https://basescan.org",
47
+ contracts: {
48
+ game: TRIVIA_QUEST_ADDRESS_BASE,
49
+ token: TRIVQ_TOKEN_ADDRESS_BASE,
50
+ checkin: CHECKIN_ADDRESS_BASE,
51
+ referral:REFERRAL_ADDRESS_BASE,
52
+ duel: "" as const,
53
+ },
54
+ };
55
+
56
+ export const CELO_TESTNET = {
57
+ id: 11142220,
58
+ name: "Celo Sepolia",
59
+ rpcUrl: "https://forno.celo-sepolia.celo-testnet.org",
60
+ explorerUrl: "https://sepolia.celoscan.io",
61
+ contracts: {
62
+ game: CONTRACT_ADDRESS_TESTNET,
63
+ token: "" as const,
64
+ checkin: "" as const,
65
+ referral:"" as const,
66
+ duel: "0xd9456518d7acbe6bcab494aa5894ce4cdf7c5ad7" as const,
67
+ },
68
+ };
69
+
70
+ // ── Helper: get contract address by chainId ────────────────
71
+ export function getAddress(
72
+ chainId: number,
73
+ contract: "game" | "token" | "checkin" | "referral" | "duel"
74
+ ): `0x${string}` {
75
+ if (chainId === 42220) return CELO_MAINNET.contracts[contract] as `0x${string}`;
76
+ if (chainId === 8453) return BASE_MAINNET.contracts[contract] as `0x${string}`;
77
+ return CELO_TESTNET.contracts[contract] as `0x${string}`;
78
+ }
79
+
80
+ // ── TriviaQuest ABI ────────────────────────────────────────
81
+ export const CONTRACT_ABI = [
7
82
  {
8
- name: "balanceOf",
83
+ name: "joinRound",
84
+ type: "function",
85
+ stateMutability: "payable",
86
+ inputs: [],
87
+ outputs: [],
88
+ },
89
+ {
90
+ name: "getCurrentRound",
9
91
  type: "function",
10
92
  stateMutability: "view",
11
- inputs: [{ name: "account", type: "address" }],
12
- outputs: [{ name: "", type: "uint256" }],
93
+ inputs: [],
94
+ outputs: [
95
+ {
96
+ type: "tuple",
97
+ components: [
98
+ { name: "id", type: "uint256" },
99
+ { name: "prizePool", type: "uint256" },
100
+ { name: "startTime", type: "uint256" },
101
+ { name: "endTime", type: "uint256" },
102
+ { name: "topWinners", type: "address[]" },
103
+ { name: "finished", type: "bool" },
104
+ ],
105
+ },
106
+ ],
13
107
  },
14
108
  {
15
- name: "mintReward",
109
+ name: "entryFee",
16
110
  type: "function",
17
- stateMutability: "nonpayable",
18
- inputs: [
19
- { name: "player", type: "address" },
20
- { name: "amount", type: "uint256" },
111
+ stateMutability: "view",
112
+ inputs: [],
113
+ outputs: [{ type: "uint256" }],
114
+ },
115
+ {
116
+ name: "getLeaderboard",
117
+ type: "function",
118
+ stateMutability: "view",
119
+ inputs: [],
120
+ outputs: [
121
+ {
122
+ type: "tuple[]",
123
+ components: [
124
+ { name: "player", type: "address" },
125
+ { name: "totalPoints", type: "uint256" },
126
+ { name: "bestScore", type: "uint256" },
127
+ { name: "gamesPlayed", type: "uint256" },
128
+ ],
129
+ },
130
+ ],
131
+ },
132
+ {
133
+ name: "getPlayerStats",
134
+ type: "function",
135
+ stateMutability: "view",
136
+ inputs: [{ name: "player", type: "address" }],
137
+ outputs: [
138
+ {
139
+ type: "tuple",
140
+ components: [
141
+ { name: "score", type: "uint256" },
142
+ { name: "totalWinnings", type: "uint256" },
143
+ { name: "totalPoints", type: "uint256" },
144
+ { name: "gamesPlayed", type: "uint256" },
145
+ { name: "bestScore", type: "uint256" },
146
+ { name: "exists", type: "bool" },
147
+ ],
148
+ },
21
149
  ],
22
- outputs: [],
23
150
  },
24
151
  {
25
- name: "rewardsRemaining",
152
+ name: "getTotalPlayers",
26
153
  type: "function",
27
154
  stateMutability: "view",
28
155
  inputs: [],
29
156
  outputs: [{ type: "uint256" }],
30
157
  },
158
+ {
159
+ name: "submitScore",
160
+ type: "function",
161
+ stateMutability: "nonpayable",
162
+ inputs: [
163
+ { name: "player", type: "address" },
164
+ { name: "score", type: "uint256" },
165
+ { name: "points", type: "uint256" },
166
+ ],
167
+ outputs: [],
168
+ },
31
169
  ] as const;
32
170
 
33
- export const TRIVIA_QUEST_ABI = [
171
+ // ── TriviaDuel ABI ─────────────────────────────────────────
172
+ export const DUEL_ABI = [
34
173
  {
35
- name: "joinRound",
174
+ name: "createDuel",
36
175
  type: "function",
37
176
  stateMutability: "payable",
38
177
  inputs: [],
178
+ outputs: [{ type: "uint256" }],
179
+ },
180
+ {
181
+ name: "joinDuel",
182
+ type: "function",
183
+ stateMutability: "payable",
184
+ inputs: [{ name: "duelId", type: "uint256" }],
39
185
  outputs: [],
40
186
  },
41
187
  {
42
- name: "getCurrentRound",
188
+ name: "cancelExpiredDuel",
189
+ type: "function",
190
+ stateMutability: "nonpayable",
191
+ inputs: [{ name: "duelId", type: "uint256" }],
192
+ outputs: [],
193
+ },
194
+ {
195
+ name: "getDuel",
43
196
  type: "function",
44
197
  stateMutability: "view",
45
- inputs: [],
198
+ inputs: [{ name: "duelId", type: "uint256" }],
46
199
  outputs: [
47
- { name: "id", type: "uint256" },
48
- { name: "prizePool", type: "uint256" },
49
- { name: "startTime", type: "uint256" },
50
- { name: "endTime", type: "uint256" },
51
- { name: "topWinners", type: "address[]" },
52
- { name: "finished", type: "bool" },
200
+ {
201
+ type: "tuple",
202
+ components: [
203
+ { name: "id", type: "uint256" },
204
+ { name: "playerA", type: "address" },
205
+ { name: "playerB", type: "address" },
206
+ { name: "wager", type: "uint256" },
207
+ { name: "scoreA", type: "uint256" },
208
+ { name: "scoreB", type: "uint256" },
209
+ { name: "scoreASubmitted", type: "bool" },
210
+ { name: "scoreBSubmitted", type: "bool" },
211
+ { name: "winner", type: "address" },
212
+ { name: "status", type: "uint8" },
213
+ { name: "createdAt", type: "uint256" },
214
+ { name: "expiresAt", type: "uint256" },
215
+ ],
216
+ },
53
217
  ],
54
218
  },
55
219
  {
56
- name: "getLeaderboard",
220
+ name: "getOpenDuels",
57
221
  type: "function",
58
222
  stateMutability: "view",
59
- inputs: [],
223
+ inputs: [{ name: "limit", type: "uint256" }],
60
224
  outputs: [
61
225
  {
62
226
  type: "tuple[]",
63
227
  components: [
64
- { name: "player", type: "address" },
65
- { name: "totalPoints", type: "uint256" },
66
- { name: "bestScore", type: "uint256" },
67
- { name: "gamesPlayed", type: "uint256" },
228
+ { name: "id", type: "uint256" },
229
+ { name: "playerA", type: "address" },
230
+ { name: "playerB", type: "address" },
231
+ { name: "wager", type: "uint256" },
232
+ { name: "scoreA", type: "uint256" },
233
+ { name: "scoreB", type: "uint256" },
234
+ { name: "scoreASubmitted", type: "bool" },
235
+ { name: "scoreBSubmitted", type: "bool" },
236
+ { name: "winner", type: "address" },
237
+ { name: "status", type: "uint8" },
238
+ { name: "createdAt", type: "uint256" },
239
+ { name: "expiresAt", type: "uint256" },
68
240
  ],
69
241
  },
70
242
  ],
71
243
  },
72
244
  {
73
- name: "getTotalPlayers",
245
+ name: "getPlayerDuels",
246
+ type: "function",
247
+ stateMutability: "view",
248
+ inputs: [{ name: "player", type: "address" }],
249
+ outputs: [{ type: "uint256[]" }],
250
+ },
251
+ {
252
+ name: "duelCounter",
74
253
  type: "function",
75
254
  stateMutability: "view",
76
255
  inputs: [],
77
256
  outputs: [{ type: "uint256" }],
78
257
  },
258
+ ] as const;
259
+
260
+ // ── TRIVQ Token ABI ────────────────────────────────────────
261
+ export const TRIVQ_ABI = [
79
262
  {
80
- name: "entryFee",
263
+ name: "balanceOf",
264
+ type: "function",
265
+ stateMutability: "view",
266
+ inputs: [{ name: "account", type: "address" }],
267
+ outputs: [{ type: "uint256" }],
268
+ },
269
+ {
270
+ name: "transfer",
271
+ type: "function",
272
+ stateMutability: "nonpayable",
273
+ inputs: [
274
+ { name: "to", type: "address" },
275
+ { name: "amount", type: "uint256" },
276
+ ],
277
+ outputs: [{ type: "bool" }],
278
+ },
279
+ {
280
+ name: "totalSupply",
81
281
  type: "function",
82
282
  stateMutability: "view",
83
283
  inputs: [],
84
284
  outputs: [{ type: "uint256" }],
85
285
  },
286
+ {
287
+ name: "decimals",
288
+ type: "function",
289
+ stateMutability: "view",
290
+ inputs: [],
291
+ outputs: [{ type: "uint8" }],
292
+ },
86
293
  ] as const;
87
294
 
88
- export const DAILY_CHECKIN_ABI = [
295
+ // ── Types ──────────────────────────────────────────────────
296
+
297
+ export type LeaderboardEntry = {
298
+ player: string;
299
+ totalPoints: bigint;
300
+ bestScore: bigint;
301
+ gamesPlayed: bigint;
302
+ };
303
+
304
+ export type PlayerStats = {
305
+ score: bigint;
306
+ totalWinnings: bigint;
307
+ totalPoints: bigint;
308
+ gamesPlayed: bigint;
309
+ bestScore: bigint;
310
+ exists: boolean;
311
+ };
312
+
313
+ export type Round = {
314
+ id: bigint;
315
+ prizePool: bigint;
316
+ startTime: bigint;
317
+ endTime: bigint;
318
+ topWinners: string[];
319
+ finished: boolean;
320
+ };
321
+
322
+ export enum DuelStatus {
323
+ Open = 0,
324
+ Active = 1,
325
+ Finished = 2,
326
+ Cancelled = 3,
327
+ }
328
+
329
+ export type Duel = {
330
+ id: bigint;
331
+ playerA: string;
332
+ playerB: string;
333
+ wager: bigint;
334
+ scoreA: bigint;
335
+ scoreB: bigint;
336
+ scoreASubmitted: boolean;
337
+ scoreBSubmitted: boolean;
338
+ winner: string;
339
+ status: DuelStatus;
340
+ createdAt: bigint;
341
+ expiresAt: bigint;
342
+ };
343
+
344
+ // ── Score & Streak Utils ───────────────────────────────────
345
+
346
+ export function getMultiplier(streak: number): number {
347
+ if (streak >= 5) return 3;
348
+ if (streak >= 3) return 2;
349
+ return 1;
350
+ }
351
+
352
+ export function calculatePoints(correct: boolean, streak: number): number {
353
+ if (!correct) return 0;
354
+ return 100 * getMultiplier(streak + 1);
355
+ }
356
+
357
+ export function getStreakLabel(streak: number): string {
358
+ if (streak >= 5) return "🔥🔥🔥 x3 MEGA";
359
+ if (streak >= 3) return "🔥🔥 x2 HOT";
360
+ if (streak >= 1) return "🔥 Streak";
361
+ return "";
362
+ }
363
+
364
+ // ── Duel Utils ─────────────────────────────────────────────
365
+
366
+ export function getDuelStatusLabel(status: DuelStatus): string {
367
+ switch (status) {
368
+ case DuelStatus.Open: return "Open";
369
+ case DuelStatus.Active: return "Active";
370
+ case DuelStatus.Finished: return "Finished";
371
+ case DuelStatus.Cancelled: return "Cancelled";
372
+ }
373
+ }
374
+
375
+ export function formatWager(wager: bigint): string {
376
+ const n = Number(wager) / 1e18;
377
+ if (n < 0.001) return "<0.001 CELO";
378
+ return `${n.toFixed(3)} CELO`;
379
+ }
380
+
381
+ export function getDuelNetPrize(wager: bigint, feeBps = 1000): bigint {
382
+ const total = wager * BigInt(2);
383
+ const fee = (total * BigInt(feeBps)) / BigInt(10000);
384
+ return total - fee;
385
+ }
386
+
387
+ export function isDuelExpired(expiresAt: bigint): boolean {
388
+ return Date.now() / 1000 > Number(expiresAt);
389
+ }
390
+
391
+ // ── MiniPay Utils ──────────────────────────────────────────
392
+
393
+ export function isMiniPay(): boolean {
394
+ if (typeof window === "undefined") return false;
395
+ return window.navigator.userAgent.includes("MiniPay");
396
+ }
397
+
398
+ type EthereumProvider = {
399
+ request: (args: { method: string }) => Promise<string[]>;
400
+ };
401
+
402
+ type WindowWithEthereum = Window & {
403
+ ethereum?: EthereumProvider;
404
+ };
405
+
406
+ export async function getMiniPayAccount(): Promise<string | null> {
407
+ if (!isMiniPay()) return null;
408
+ try {
409
+ const ethereum = (window as WindowWithEthereum).ethereum;
410
+ if (!ethereum) return null;
411
+ const accounts = await ethereum.request({ method: "eth_requestAccounts" });
412
+ return accounts[0] ?? null;
413
+ } catch {
414
+ return null;
415
+ }
416
+ }
417
+
418
+ // ── Format Utils ───────────────────────────────────────────
419
+
420
+ export function formatAddress(address: string): string {
421
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
422
+ }
423
+
424
+ export function formatTrivq(raw: bigint): string {
425
+ const n = Number(raw) / 1e18;
426
+ if (n >= 1_000_000_000) return `${(n / 1_000_000_000).toFixed(1)}B`;
427
+ if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
428
+ if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
429
+ return n.toFixed(0);
430
+ }
431
+
432
+ export function formatCelo(wei: bigint): string {
433
+ const n = Number(wei) / 1e18;
434
+ if (n === 0) return "0";
435
+ if (n < 0.001) return "<0.001";
436
+ return n.toFixed(3);
437
+ }
438
+
439
+ // ── CheckIn ABI ────────────────────────────────────────────
440
+ export const CHECKIN_ABI = [
89
441
  {
90
442
  name: "checkIn",
91
443
  type: "function",
@@ -107,14 +459,15 @@ export const DAILY_CHECKIN_ABI = [
107
459
  ],
108
460
  },
109
461
  {
110
- name: "isCheckInAvailable",
462
+ name: "totalCheckIns",
111
463
  type: "function",
112
464
  stateMutability: "view",
113
- inputs: [{ name: "player", type: "address" }],
114
- outputs: [{ type: "bool" }],
465
+ inputs: [],
466
+ outputs: [{ type: "uint256" }],
115
467
  },
116
468
  ] as const;
117
469
 
470
+ // ── Referral ABI ───────────────────────────────────────────
118
471
  export const REFERRAL_ABI = [
119
472
  {
120
473
  name: "registerReferral",
@@ -124,25 +477,105 @@ export const REFERRAL_ABI = [
124
477
  outputs: [],
125
478
  },
126
479
  {
127
- name: "getReferralStats",
480
+ name: "getReferrer",
128
481
  type: "function",
129
482
  stateMutability: "view",
130
483
  inputs: [{ name: "user", type: "address" }],
131
- outputs: [
132
- { name: "refs", type: "uint256" },
133
- { name: "earned", type: "uint256" },
134
- { name: "referred", type: "bool" },
135
- { name: "referrer", type: "address" },
136
- ],
484
+ outputs: [{ type: "address" }],
137
485
  },
486
+ {
487
+ name: "getReferralCount",
488
+ type: "function",
489
+ stateMutability: "view",
490
+ inputs: [{ name: "referrer", type: "address" }],
491
+ outputs: [{ type: "uint256" }],
492
+ },
493
+ ] as const;
494
+
495
+ // ── Types ──────────────────────────────────────────────────
496
+
497
+ export type CheckInData = {
498
+ lastCheckIn: bigint;
499
+ streak: bigint;
500
+ totalCheckIns: bigint;
501
+ checkInAvailable: boolean;
502
+ secondsUntilNext: bigint;
503
+ };
504
+
505
+ export type NetworkStats = {
506
+ players: number;
507
+ roundId: number;
508
+ prizePool: string;
509
+ totalCheckins: number;
510
+ };
511
+
512
+ // ── Network Stats Fetcher ──────────────────────────────────
513
+
514
+ export async function fetchNetworkStats(): Promise<NetworkStats> {
515
+ const res = await fetch("https://trivia-quest-eight.vercel.app/api/stats");
516
+ const data = await res.json();
517
+ return {
518
+ players: data.live_stats?.players ?? 0,
519
+ roundId: data.live_stats?.round_id ?? 0,
520
+ prizePool: data.live_stats?.prize_pool ?? "0",
521
+ totalCheckins: data.live_stats?.total_checkins ?? 0,
522
+ };
523
+ }
524
+
525
+ // ── Streak Utils ───────────────────────────────────────────
526
+
527
+ export function getStreakBonus(streak: number): number {
528
+ return streak > 0 && streak % 7 === 0 ? 2000 : 0;
529
+ }
530
+
531
+ export function getNextStreakMilestone(streak: number): number {
532
+ return 7 - (streak % 7);
533
+ }
534
+
535
+ export function formatCountdown(seconds: number): string {
536
+ const h = Math.floor(seconds / 3600);
537
+ const m = Math.floor((seconds % 3600) / 60);
538
+ const s = seconds % 60;
539
+ return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
540
+ }
541
+
542
+ // ── Category Utils ─────────────────────────────────────────
543
+
544
+ export const CATEGORIES = [
545
+ { id: 1, name: "Africa Explorer", emoji: "🌍", description: "African Geography" },
546
+ { id: 2, name: "Crypto Master", emoji: "⛓", description: "Web3 & Crypto" },
547
+ { id: 3, name: "Culture Keeper", emoji: "📜", description: "History & Culture" },
548
+ { id: 4, name: "Tech Wizard", emoji: "⚡", description: "Science & Tech" },
549
+ { id: 5, name: "Sport Champion", emoji: "🏆", description: "Sports" },
550
+ { id: 6, name: "Trivia Legend", emoji: "✨", description: "General Knowledge" },
138
551
  ] as const;
139
552
 
140
- // Chain info
141
- export const CELO_CHAIN_ID = 42220;
142
- export const CELO_RPC = "https://forno.celo.org";
553
+ export function getCategoryById(id: number) {
554
+ return CATEGORIES.find(c => c.id === id) ?? null;
555
+ }
556
+
557
+ // ── Reward Calculator ──────────────────────────────────────
558
+
559
+ export function calculateRewards(params: {
560
+ score: number;
561
+ streak: number;
562
+ isCheckIn?: boolean;
563
+ isDuelWinner?: boolean;
564
+ wager?: bigint;
565
+ }): { trivq: number; celoWin: bigint } {
566
+ let trivq = 0;
567
+ let celoWin = BigInt(0);
568
+
569
+ if (params.score > 0) {
570
+ trivq += params.score * 100 * getMultiplier(params.streak);
571
+ }
572
+ if (params.isCheckIn) {
573
+ trivq += 100;
574
+ trivq += getStreakBonus(params.streak);
575
+ }
576
+ if (params.isDuelWinner && params.wager) {
577
+ celoWin = getDuelNetPrize(params.wager);
578
+ }
143
579
 
144
- // Reward rates
145
- export const DAILY_REWARD_TRIVQ = 100;
146
- export const WEEK_BONUS_TRIVQ = 2000;
147
- export const REFERRAL_REWARD_TRIVQ = 500;
148
- export const TRIVQ_PER_POINT = 100;
580
+ return { trivq, celoWin };
581
+ }