@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/README.md +73 -40
- package/package.json +1 -1
- package/sdk/README.md +68 -3
- package/sdk/index.d.ts +191 -1
- package/sdk/index.d.ts.map +1 -1
- package/sdk/index.js +162 -3
- package/sdk/index.js.map +1 -1
- package/sdk/package.json +1 -1
- package/sdk/sdk/index.d.ts +449 -67
- package/sdk/sdk/index.js +408 -52
- package/sdk/src/index.ts +485 -52
package/sdk/src/index.ts
CHANGED
|
@@ -1,91 +1,443 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
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: "
|
|
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: [
|
|
12
|
-
outputs: [
|
|
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: "
|
|
109
|
+
name: "entryFee",
|
|
16
110
|
type: "function",
|
|
17
|
-
stateMutability: "
|
|
18
|
-
inputs: [
|
|
19
|
-
|
|
20
|
-
|
|
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: "
|
|
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
|
-
|
|
171
|
+
// ── TriviaDuel ABI ─────────────────────────────────────────
|
|
172
|
+
export const DUEL_ABI = [
|
|
34
173
|
{
|
|
35
|
-
name: "
|
|
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: "
|
|
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
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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: "
|
|
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: "
|
|
65
|
-
{ name: "
|
|
66
|
-
{ name: "
|
|
67
|
-
{ name: "
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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: "
|
|
462
|
+
name: "totalCheckIns",
|
|
111
463
|
type: "function",
|
|
112
464
|
stateMutability: "view",
|
|
113
|
-
inputs: [
|
|
114
|
-
outputs: [{ type: "
|
|
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: "
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
+
}
|