@dubsdotapp/expo 0.1.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/dist/index.js ADDED
@@ -0,0 +1,1364 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ ConnectWalletScreen: () => ConnectWalletScreen,
34
+ DEFAULT_BASE_URL: () => DEFAULT_BASE_URL,
35
+ DEFAULT_RPC_URL: () => DEFAULT_RPC_URL,
36
+ DubsApiError: () => DubsApiError,
37
+ DubsClient: () => DubsClient,
38
+ DubsProvider: () => DubsProvider,
39
+ MwaWalletAdapter: () => MwaWalletAdapter,
40
+ SOLANA_PROGRAM_ERRORS: () => SOLANA_PROGRAM_ERRORS,
41
+ SettingsSheet: () => SettingsSheet,
42
+ UserProfileCard: () => UserProfileCard,
43
+ parseSolanaError: () => parseSolanaError,
44
+ pollTransactionConfirmation: () => pollTransactionConfirmation,
45
+ signAndSendBase64Transaction: () => signAndSendBase64Transaction,
46
+ useAuth: () => useAuth,
47
+ useClaim: () => useClaim,
48
+ useCreateGame: () => useCreateGame,
49
+ useDubs: () => useDubs,
50
+ useDubsTheme: () => useDubsTheme,
51
+ useEvents: () => useEvents,
52
+ useGame: () => useGame,
53
+ useGames: () => useGames,
54
+ useJoinGame: () => useJoinGame,
55
+ useNetworkGames: () => useNetworkGames
56
+ });
57
+ module.exports = __toCommonJS(index_exports);
58
+
59
+ // src/constants.ts
60
+ var DEFAULT_BASE_URL = "https://dubs-server-prod-9c91d3f01199.herokuapp.com/api/developer/v1";
61
+ var DEFAULT_RPC_URL = "https://api.mainnet-beta.solana.com";
62
+
63
+ // src/errors.ts
64
+ var DubsApiError = class extends Error {
65
+ constructor(code, message, httpStatus) {
66
+ super(message);
67
+ this.name = "DubsApiError";
68
+ this.code = code;
69
+ this.httpStatus = httpStatus;
70
+ }
71
+ };
72
+ var SOLANA_PROGRAM_ERRORS = {
73
+ 6e3: { code: "invalid_amount", message: "Amount must be greater than 0" },
74
+ 6001: { code: "invalid_max_players", message: "Max players must be between 1 and 20" },
75
+ 6002: { code: "game_not_active", message: "Game is not active" },
76
+ 6003: { code: "game_full", message: "Game is full" },
77
+ 6004: { code: "player_already_joined", message: "Player has already joined this game" },
78
+ 6005: { code: "insufficient_funds", message: "Insufficient funds in pot" },
79
+ 6006: { code: "unauthorized", message: "Only the game creator can perform this action" },
80
+ 6007: { code: "invalid_winner_index", message: "Invalid winner index" },
81
+ 6008: { code: "game_still_active", message: "Game is still active \u2014 must close before resetting" },
82
+ 6009: { code: "pot_not_empty", message: "Pot must be empty before resetting \u2014 distribute winnings first" },
83
+ 6010: { code: "no_winners_specified", message: "At least one winner must be specified" },
84
+ 6011: { code: "mismatched_winners_percentages", message: "Number of winners must match number of percentages" },
85
+ 6012: { code: "percentages_invalid", message: "Percentages must sum to exactly 100" },
86
+ 6013: { code: "winner_account_mismatch", message: "Winner account does not match expected player" },
87
+ 6014: { code: "invalid_operator_fee", message: "Operator fee must be between 0 and 100" },
88
+ 6015: { code: "operator_wallet_mismatch", message: "Operator wallet does not match game settings" },
89
+ 6016: { code: "winner_not_in_game", message: "Winner address is not a player in this game" },
90
+ 6017: { code: "voting_not_enabled", message: "Voting is not enabled for this game" },
91
+ 6018: { code: "voter_not_in_game", message: "Voter is not a player in this game" },
92
+ 6019: { code: "voted_for_not_in_game", message: "Cannot vote for someone who is not in the game" },
93
+ 6020: { code: "voting_incomplete", message: "Voting incomplete \u2014 need majority of players to vote" },
94
+ 6021: { code: "invalid_referee_commission", message: "Referee commission must be between 0 and 100" },
95
+ 6022: { code: "total_fees_exceed_100", message: "Total fees (operator + referee) cannot exceed 100%" },
96
+ 6023: { code: "referee_mode_requires_referee", message: "Referee mode requires a referee address" },
97
+ 6024: { code: "referee_must_earn_commission", message: "Referee must earn commission" },
98
+ 6025: { code: "referee_cannot_be_player", message: "Referee cannot join as a player (conflict of interest)" },
99
+ 6026: { code: "only_referee_can_vote", message: "Only the referee can vote in Referee mode" },
100
+ 6027: { code: "referee_account_mismatch", message: "Referee account does not match game settings" },
101
+ 6028: { code: "invalid_lock_time", message: "Lock time must be in the future" },
102
+ 6029: { code: "game_locked", message: "Game is locked \u2014 no more players can join" },
103
+ 6030: { code: "lock_time_passed", message: "Lock time has passed \u2014 game is now locked" },
104
+ 6031: { code: "unauthorized_oracle", message: "Only authorized oracle can resolve this game" },
105
+ 6032: { code: "game_not_locked", message: "Game must be locked before it can be resolved" },
106
+ 6033: { code: "already_resolved", message: "Game has already been resolved" },
107
+ 6034: { code: "game_not_resolved", message: "Game has not been resolved yet" },
108
+ 6035: { code: "player_not_in_game", message: "Player is not in this game" },
109
+ 6036: { code: "not_a_winner", message: "Player did not win this game" },
110
+ 6037: { code: "invalid_game_mode", message: "Invalid game mode for this operation" },
111
+ 6038: { code: "already_claimed", message: "Player has already claimed their winnings" },
112
+ 6039: { code: "lock_time_too_soon", message: "Lock time must be at least 2 minutes in the future" },
113
+ 6040: { code: "operator_account_missing", message: "Operator account must be provided" },
114
+ 6041: { code: "no_winners_to_distribute", message: "No winners to distribute funds to" },
115
+ 6042: { code: "insufficient_funds_for_rent", message: "Insufficient funds to maintain rent exemption" },
116
+ 6043: { code: "cannot_resolve_before_lock", message: "Cannot resolve game before lock time has passed" },
117
+ 6044: { code: "emergency_refund_not_available", message: "Emergency refund not available yet \u2014 must wait 7 days after lock time" },
118
+ 6045: { code: "sponsor_account_missing", message: "Sponsor account must be provided for tie refund" },
119
+ 6046: { code: "sponsor_wallet_mismatch", message: "Sponsor wallet does not match stored sponsor" },
120
+ 6047: { code: "invalid_bet_amount", message: "Bet amount must be greater than zero" },
121
+ 6048: { code: "no_survivors_to_distribute", message: "No survivors to distribute winnings to" },
122
+ 6049: { code: "too_many_survivors", message: "Too many survivors (max 50 per batch)" }
123
+ };
124
+ var SOLANA_BUILTIN_ERRORS = {
125
+ 0: { code: "generic_error", message: "Generic instruction error" },
126
+ 1: { code: "invalid_argument", message: "Invalid argument passed to program" },
127
+ 2: { code: "invalid_instruction_data", message: "Invalid instruction data" },
128
+ 3: { code: "invalid_account_data", message: "Invalid account data" },
129
+ 4: { code: "account_data_too_small", message: "Account data too small" },
130
+ 5: { code: "insufficient_funds", message: "Insufficient funds for transaction" },
131
+ 6: { code: "incorrect_program_id", message: "Incorrect program ID" },
132
+ 7: { code: "missing_required_signature", message: "Missing required signature" },
133
+ 8: { code: "account_already_initialized", message: "Account already initialized" },
134
+ 9: { code: "uninitialized_account", message: "Attempt to operate on uninitialized account" }
135
+ };
136
+ function parseSolanaError(err) {
137
+ if (!err) return { code: "unknown_error", message: "Unknown transaction error" };
138
+ let parsed = err;
139
+ if (typeof parsed === "string") {
140
+ try {
141
+ const jsonMatch = parsed.match(/\{.*\}/s);
142
+ if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
143
+ else return { code: "transaction_failed", message: parsed };
144
+ } catch {
145
+ return { code: "transaction_failed", message: parsed };
146
+ }
147
+ }
148
+ if (typeof parsed === "object" && parsed !== null && "InstructionError" in parsed) {
149
+ const [ixIndex, details] = parsed.InstructionError;
150
+ if (details && typeof details === "object" && "Custom" in details) {
151
+ const customCode = details.Custom;
152
+ const known = SOLANA_PROGRAM_ERRORS[customCode];
153
+ if (known) return known;
154
+ return { code: `program_error_${customCode}`, message: `Program error code ${customCode} (instruction ${ixIndex})` };
155
+ }
156
+ if (typeof details === "string") {
157
+ const snake = details.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
158
+ return { code: snake, message: details.replace(/([a-z])([A-Z])/g, "$1 $2") };
159
+ }
160
+ if (typeof details === "number") {
161
+ const known = SOLANA_BUILTIN_ERRORS[details];
162
+ if (known) return known;
163
+ return { code: `instruction_error_${details}`, message: `Instruction error ${details}` };
164
+ }
165
+ return { code: "instruction_error", message: `Instruction ${ixIndex} failed: ${JSON.stringify(details)}` };
166
+ }
167
+ return { code: "transaction_failed", message: typeof parsed === "object" ? JSON.stringify(parsed) : String(parsed) };
168
+ }
169
+
170
+ // src/client.ts
171
+ var DubsClient = class {
172
+ constructor(config) {
173
+ this._token = null;
174
+ this.apiKey = config.apiKey;
175
+ this.baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, "");
176
+ }
177
+ /** Set the user JWT token for authenticated requests */
178
+ setToken(token) {
179
+ this._token = token;
180
+ }
181
+ /** Get the current user JWT token */
182
+ getToken() {
183
+ return this._token;
184
+ }
185
+ // ── Private HTTP helper ──
186
+ async request(method, path, body) {
187
+ const url = `${this.baseUrl}${path}`;
188
+ const headers = {
189
+ "X-API-Key": this.apiKey,
190
+ "Content-Type": "application/json"
191
+ };
192
+ if (this._token) {
193
+ headers["Authorization"] = `Bearer ${this._token}`;
194
+ }
195
+ const res = await fetch(url, {
196
+ method,
197
+ headers,
198
+ body: body ? JSON.stringify(body) : void 0
199
+ });
200
+ const json = await res.json();
201
+ if (!json.success) {
202
+ const err = json.error;
203
+ if (typeof err === "object" && err !== null) {
204
+ throw new DubsApiError(err.code || "unknown", err.message || "Unknown error", res.status);
205
+ }
206
+ throw new DubsApiError("unknown", typeof err === "string" ? err : "Request failed", res.status);
207
+ }
208
+ return json;
209
+ }
210
+ // ── Events ──
211
+ async getUpcomingEvents(params) {
212
+ const qs = new URLSearchParams();
213
+ if (params?.type) qs.set("type", params.type);
214
+ if (params?.game) qs.set("game", params.game);
215
+ if (params?.page) qs.set("page", String(params.page));
216
+ if (params?.per_page) qs.set("per_page", String(params.per_page));
217
+ const query = qs.toString();
218
+ const res = await this.request(
219
+ "GET",
220
+ `/events/upcoming${query ? `?${query}` : ""}`
221
+ );
222
+ return { events: res.events, pagination: res.pagination };
223
+ }
224
+ async getSportsEvents(league) {
225
+ const res = await this.request(
226
+ "GET",
227
+ `/sports/events/${encodeURIComponent(league)}`
228
+ );
229
+ return res.events;
230
+ }
231
+ async getEsportsMatches(videogame) {
232
+ const qs = videogame ? `?videogame=${encodeURIComponent(videogame)}` : "";
233
+ const res = await this.request(
234
+ "GET",
235
+ `/esports/matches/upcoming${qs}`
236
+ );
237
+ return res.matches;
238
+ }
239
+ async getEsportsMatchDetail(matchId) {
240
+ const res = await this.request(
241
+ "GET",
242
+ `/esports/matches/${encodeURIComponent(String(matchId))}`
243
+ );
244
+ return res.match;
245
+ }
246
+ // ── Game Lifecycle ──
247
+ async validateEvent(id) {
248
+ const res = await this.request(
249
+ "POST",
250
+ "/games/validate",
251
+ { id }
252
+ );
253
+ return {
254
+ bettable: res.bettable,
255
+ gameMode: res.gameMode,
256
+ lockTimestamp: res.lockTimestamp,
257
+ startTime: res.startTime,
258
+ status: res.status
259
+ };
260
+ }
261
+ async createGame(params) {
262
+ const res = await this.request(
263
+ "POST",
264
+ "/games/create",
265
+ params
266
+ );
267
+ return {
268
+ gameId: res.gameId,
269
+ gameAddress: res.gameAddress,
270
+ transaction: res.transaction,
271
+ lockTimestamp: res.lockTimestamp,
272
+ event: res.event
273
+ };
274
+ }
275
+ async joinGame(params) {
276
+ const res = await this.request(
277
+ "POST",
278
+ "/games/join",
279
+ params
280
+ );
281
+ return {
282
+ gameId: res.gameId,
283
+ transaction: res.transaction,
284
+ gameAddress: res.gameAddress
285
+ };
286
+ }
287
+ async confirmGame(params) {
288
+ const res = await this.request(
289
+ "POST",
290
+ "/games/confirm",
291
+ params
292
+ );
293
+ return {
294
+ gameId: res.gameId,
295
+ signature: res.signature,
296
+ explorerUrl: res.explorerUrl,
297
+ message: res.message
298
+ };
299
+ }
300
+ async buildClaimTransaction(params) {
301
+ const res = await this.request(
302
+ "POST",
303
+ "/transactions/build/claim",
304
+ params
305
+ );
306
+ return {
307
+ transaction: res.transaction,
308
+ gameAddress: res.gameAddress,
309
+ message: res.message
310
+ };
311
+ }
312
+ // ── Game Queries ──
313
+ async getGame(gameId) {
314
+ const res = await this.request(
315
+ "GET",
316
+ `/games/${encodeURIComponent(gameId)}`
317
+ );
318
+ return res.game;
319
+ }
320
+ async getGames(params) {
321
+ const qs = new URLSearchParams();
322
+ if (params?.wallet) qs.set("wallet", params.wallet);
323
+ if (params?.status) qs.set("status", params.status);
324
+ if (params?.limit != null) qs.set("limit", String(params.limit));
325
+ if (params?.offset != null) qs.set("offset", String(params.offset));
326
+ const query = qs.toString();
327
+ const res = await this.request(
328
+ "GET",
329
+ `/games${query ? `?${query}` : ""}`
330
+ );
331
+ return res.games;
332
+ }
333
+ // ── Network ──
334
+ async getNetworkGames(params) {
335
+ const qs = new URLSearchParams();
336
+ if (params?.league) qs.set("league", params.league);
337
+ if (params?.limit != null) qs.set("limit", String(params.limit));
338
+ if (params?.offset != null) qs.set("offset", String(params.offset));
339
+ const query = qs.toString();
340
+ const res = await this.request(
341
+ "GET",
342
+ `/network/games${query ? `?${query}` : ""}`
343
+ );
344
+ return { games: res.games, pagination: res.pagination };
345
+ }
346
+ // ── User Auth ──
347
+ async getNonce(walletAddress) {
348
+ const res = await this.request(
349
+ "POST",
350
+ "/auth/nonce",
351
+ { walletAddress }
352
+ );
353
+ return res.data;
354
+ }
355
+ async authenticate(params) {
356
+ const res = await this.request(
357
+ "POST",
358
+ "/auth/authenticate",
359
+ params
360
+ );
361
+ if (res.data.token) {
362
+ this._token = res.data.token;
363
+ }
364
+ return res.data;
365
+ }
366
+ async register(params) {
367
+ const res = await this.request(
368
+ "POST",
369
+ "/auth/register",
370
+ params
371
+ );
372
+ if (res.data.token) {
373
+ this._token = res.data.token;
374
+ }
375
+ return res.data;
376
+ }
377
+ async getMe() {
378
+ const res = await this.request(
379
+ "GET",
380
+ "/auth/me"
381
+ );
382
+ return res.data.user;
383
+ }
384
+ async getUser(walletAddress) {
385
+ const res = await this.request(
386
+ "GET",
387
+ `/users/${encodeURIComponent(walletAddress)}`
388
+ );
389
+ return res.data.user;
390
+ }
391
+ async getAppUsers(params) {
392
+ const qs = new URLSearchParams();
393
+ if (params?.limit != null) qs.set("limit", String(params.limit));
394
+ if (params?.offset != null) qs.set("offset", String(params.offset));
395
+ const query = qs.toString();
396
+ const res = await this.request(
397
+ "GET",
398
+ `/users${query ? `?${query}` : ""}`
399
+ );
400
+ return res.data;
401
+ }
402
+ async checkUsername(username) {
403
+ const res = await this.request(
404
+ "GET",
405
+ `/auth/check-username/${encodeURIComponent(username)}`
406
+ );
407
+ return res.data;
408
+ }
409
+ async logout() {
410
+ await this.request(
411
+ "POST",
412
+ "/auth/logout"
413
+ );
414
+ this._token = null;
415
+ }
416
+ // ── Error Utilities ──
417
+ async parseError(error) {
418
+ const res = await this.request(
419
+ "POST",
420
+ "/errors/parse",
421
+ { error }
422
+ );
423
+ return res.error;
424
+ }
425
+ async getErrorCodes() {
426
+ const res = await this.request(
427
+ "GET",
428
+ "/errors/codes"
429
+ );
430
+ return res.errors;
431
+ }
432
+ /**
433
+ * Parse a Solana error locally without an API call.
434
+ * Useful for instant feedback in the UI.
435
+ */
436
+ parseErrorLocal(error) {
437
+ return parseSolanaError(error);
438
+ }
439
+ /** Get the full local error code map */
440
+ getErrorCodesLocal() {
441
+ return { ...SOLANA_PROGRAM_ERRORS };
442
+ }
443
+ };
444
+
445
+ // src/provider.tsx
446
+ var import_react = require("react");
447
+ var import_web3 = require("@solana/web3.js");
448
+ var import_jsx_runtime = require("react/jsx-runtime");
449
+ var DubsContext = (0, import_react.createContext)(null);
450
+ function DubsProvider({ apiKey, baseUrl, rpcUrl, wallet, children }) {
451
+ const value = (0, import_react.useMemo)(() => {
452
+ const client = new DubsClient({ apiKey, baseUrl });
453
+ const connection = new import_web3.Connection(rpcUrl || DEFAULT_RPC_URL, { commitment: "confirmed" });
454
+ return { client, wallet, connection };
455
+ }, [apiKey, baseUrl, rpcUrl, wallet]);
456
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DubsContext.Provider, { value, children });
457
+ }
458
+ function useDubs() {
459
+ const ctx = (0, import_react.useContext)(DubsContext);
460
+ if (!ctx) {
461
+ throw new Error("useDubs must be used within a <DubsProvider>");
462
+ }
463
+ return ctx;
464
+ }
465
+
466
+ // src/wallet/mwa-adapter.ts
467
+ var import_web32 = require("@solana/web3.js");
468
+ function toPublicKey(address) {
469
+ if (address instanceof Uint8Array) {
470
+ return new import_web32.PublicKey(address);
471
+ }
472
+ const bytes = Uint8Array.from(atob(address), (c) => c.charCodeAt(0));
473
+ return new import_web32.PublicKey(bytes);
474
+ }
475
+ var MwaWalletAdapter = class {
476
+ constructor(config) {
477
+ this._publicKey = null;
478
+ this._connected = false;
479
+ this._authToken = null;
480
+ this.config = config;
481
+ this.transact = config.transact;
482
+ }
483
+ get publicKey() {
484
+ return this._publicKey;
485
+ }
486
+ get connected() {
487
+ return this._connected;
488
+ }
489
+ /**
490
+ * Connect to a mobile wallet. Call this before any signing.
491
+ */
492
+ async connect() {
493
+ await this.transact(async (wallet) => {
494
+ const authResult = this._authToken ? await wallet.reauthorize({ auth_token: this._authToken }) : await wallet.authorize({
495
+ identity: this.config.appIdentity,
496
+ cluster: this.config.cluster || "mainnet-beta"
497
+ });
498
+ this._publicKey = toPublicKey(authResult.accounts[0].address);
499
+ this._authToken = authResult.auth_token;
500
+ this._connected = true;
501
+ this.config.onAuthTokenChange?.(this._authToken);
502
+ });
503
+ }
504
+ /**
505
+ * Disconnect and clear auth token.
506
+ */
507
+ disconnect() {
508
+ this._publicKey = null;
509
+ this._connected = false;
510
+ this._authToken = null;
511
+ this.config.onAuthTokenChange?.(null);
512
+ }
513
+ async signTransaction(transaction) {
514
+ if (!this._connected) throw new Error("Wallet not connected");
515
+ const signed = await this.transact(async (wallet) => {
516
+ const reauth = await wallet.reauthorize({ auth_token: this._authToken });
517
+ this._authToken = reauth.auth_token;
518
+ this.config.onAuthTokenChange?.(this._authToken);
519
+ const result = await wallet.signTransactions({
520
+ transactions: [transaction]
521
+ });
522
+ return result[0];
523
+ });
524
+ return signed;
525
+ }
526
+ async signMessage(message) {
527
+ if (!this._connected || !this._publicKey) throw new Error("Wallet not connected");
528
+ const sig = await this.transact(async (wallet) => {
529
+ const reauth = await wallet.reauthorize({ auth_token: this._authToken });
530
+ this._authToken = reauth.auth_token;
531
+ this.config.onAuthTokenChange?.(this._authToken);
532
+ const result = await wallet.signMessages({
533
+ addresses: [this._publicKey.toBytes()],
534
+ payloads: [message]
535
+ });
536
+ return result[0];
537
+ });
538
+ return sig instanceof Uint8Array ? sig : new Uint8Array(sig);
539
+ }
540
+ async signAndSendTransaction(transaction) {
541
+ if (!this._connected) throw new Error("Wallet not connected");
542
+ const signature = await this.transact(async (wallet) => {
543
+ const reauth = await wallet.reauthorize({ auth_token: this._authToken });
544
+ this._authToken = reauth.auth_token;
545
+ this.config.onAuthTokenChange?.(this._authToken);
546
+ const result = await wallet.signAndSendTransactions({
547
+ transactions: [transaction]
548
+ });
549
+ return result[0];
550
+ });
551
+ if (signature instanceof Uint8Array) {
552
+ const bs582 = await import("@solana/web3.js").then(() => {
553
+ return new import_web32.PublicKey(signature).toBase58();
554
+ }).catch(() => {
555
+ return Buffer.from(signature).toString("base64");
556
+ });
557
+ return bs582;
558
+ }
559
+ return String(signature);
560
+ }
561
+ };
562
+
563
+ // src/hooks/useEvents.ts
564
+ var import_react2 = require("react");
565
+ function useEvents(params) {
566
+ const { client } = useDubs();
567
+ const [data, setData] = (0, import_react2.useState)(null);
568
+ const [loading, setLoading] = (0, import_react2.useState)(true);
569
+ const [error, setError] = (0, import_react2.useState)(null);
570
+ const paramKey = JSON.stringify(params ?? {});
571
+ const fetchData = (0, import_react2.useCallback)(async () => {
572
+ setLoading(true);
573
+ setError(null);
574
+ try {
575
+ const result = await client.getUpcomingEvents(params);
576
+ setData(result);
577
+ } catch (err) {
578
+ setError(err instanceof Error ? err : new Error(String(err)));
579
+ } finally {
580
+ setLoading(false);
581
+ }
582
+ }, [client, paramKey]);
583
+ (0, import_react2.useEffect)(() => {
584
+ fetchData();
585
+ }, [fetchData]);
586
+ return { data, loading, error, refetch: fetchData };
587
+ }
588
+
589
+ // src/hooks/useGame.ts
590
+ var import_react3 = require("react");
591
+ function useGame(gameId) {
592
+ const { client } = useDubs();
593
+ const [data, setData] = (0, import_react3.useState)(null);
594
+ const [loading, setLoading] = (0, import_react3.useState)(!!gameId);
595
+ const [error, setError] = (0, import_react3.useState)(null);
596
+ const fetchData = (0, import_react3.useCallback)(async () => {
597
+ if (!gameId) return;
598
+ setLoading(true);
599
+ setError(null);
600
+ try {
601
+ const game = await client.getGame(gameId);
602
+ setData(game);
603
+ } catch (err) {
604
+ setError(err instanceof Error ? err : new Error(String(err)));
605
+ } finally {
606
+ setLoading(false);
607
+ }
608
+ }, [client, gameId]);
609
+ (0, import_react3.useEffect)(() => {
610
+ fetchData();
611
+ }, [fetchData]);
612
+ return { data, loading, error, refetch: fetchData };
613
+ }
614
+
615
+ // src/hooks/useGames.ts
616
+ var import_react4 = require("react");
617
+ function useGames(params) {
618
+ const { client } = useDubs();
619
+ const [data, setData] = (0, import_react4.useState)(null);
620
+ const [loading, setLoading] = (0, import_react4.useState)(true);
621
+ const [error, setError] = (0, import_react4.useState)(null);
622
+ const paramKey = JSON.stringify(params ?? {});
623
+ const fetchData = (0, import_react4.useCallback)(async () => {
624
+ setLoading(true);
625
+ setError(null);
626
+ try {
627
+ const games = await client.getGames(params);
628
+ setData(games);
629
+ } catch (err) {
630
+ setError(err instanceof Error ? err : new Error(String(err)));
631
+ } finally {
632
+ setLoading(false);
633
+ }
634
+ }, [client, paramKey]);
635
+ (0, import_react4.useEffect)(() => {
636
+ fetchData();
637
+ }, [fetchData]);
638
+ return { data, loading, error, refetch: fetchData };
639
+ }
640
+
641
+ // src/hooks/useNetworkGames.ts
642
+ var import_react5 = require("react");
643
+ function useNetworkGames(params) {
644
+ const { client } = useDubs();
645
+ const [data, setData] = (0, import_react5.useState)(null);
646
+ const [loading, setLoading] = (0, import_react5.useState)(true);
647
+ const [error, setError] = (0, import_react5.useState)(null);
648
+ const paramKey = JSON.stringify(params ?? {});
649
+ const fetchData = (0, import_react5.useCallback)(async () => {
650
+ setLoading(true);
651
+ setError(null);
652
+ try {
653
+ const result = await client.getNetworkGames(params);
654
+ setData(result);
655
+ } catch (err) {
656
+ setError(err instanceof Error ? err : new Error(String(err)));
657
+ } finally {
658
+ setLoading(false);
659
+ }
660
+ }, [client, paramKey]);
661
+ (0, import_react5.useEffect)(() => {
662
+ fetchData();
663
+ }, [fetchData]);
664
+ return { data, loading, error, refetch: fetchData };
665
+ }
666
+
667
+ // src/hooks/useCreateGame.ts
668
+ var import_react6 = require("react");
669
+
670
+ // src/utils/transaction.ts
671
+ var import_web33 = require("@solana/web3.js");
672
+ async function signAndSendBase64Transaction(base64Tx, wallet, connection) {
673
+ if (!wallet.publicKey) throw new Error("Wallet not connected");
674
+ const txBuffer = Buffer.from(base64Tx, "base64");
675
+ const transaction = import_web33.Transaction.from(txBuffer);
676
+ if (wallet.signAndSendTransaction) {
677
+ return wallet.signAndSendTransaction(transaction);
678
+ }
679
+ const signed = await wallet.signTransaction(transaction);
680
+ const signature = await connection.sendRawTransaction(signed.serialize(), {
681
+ skipPreflight: true
682
+ });
683
+ return signature;
684
+ }
685
+ async function pollTransactionConfirmation(signature, connection, commitment = "confirmed", timeout = 6e4, interval = 1500) {
686
+ const start = Date.now();
687
+ const confirmationOrder = ["processed", "confirmed", "finalized"];
688
+ const targetIndex = confirmationOrder.indexOf(commitment);
689
+ while (Date.now() - start < timeout) {
690
+ const statuses = await connection.getSignatureStatuses([signature]);
691
+ const status = statuses?.value?.[0];
692
+ if (status?.err) {
693
+ throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
694
+ }
695
+ if (status?.confirmationStatus) {
696
+ const currentIndex = confirmationOrder.indexOf(status.confirmationStatus);
697
+ if (currentIndex >= targetIndex) {
698
+ return;
699
+ }
700
+ }
701
+ await new Promise((resolve) => setTimeout(resolve, interval));
702
+ }
703
+ throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
704
+ }
705
+
706
+ // src/hooks/useCreateGame.ts
707
+ function useCreateGame() {
708
+ const { client, wallet, connection } = useDubs();
709
+ const [status, setStatus] = (0, import_react6.useState)("idle");
710
+ const [error, setError] = (0, import_react6.useState)(null);
711
+ const [data, setData] = (0, import_react6.useState)(null);
712
+ const reset = (0, import_react6.useCallback)(() => {
713
+ setStatus("idle");
714
+ setError(null);
715
+ setData(null);
716
+ }, []);
717
+ const execute = (0, import_react6.useCallback)(async (params) => {
718
+ setStatus("building");
719
+ setError(null);
720
+ setData(null);
721
+ try {
722
+ const createResult = await client.createGame(params);
723
+ setStatus("signing");
724
+ const signature = await signAndSendBase64Transaction(
725
+ createResult.transaction,
726
+ wallet,
727
+ connection
728
+ );
729
+ setStatus("confirming");
730
+ await pollTransactionConfirmation(signature, connection);
731
+ setStatus("saving");
732
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
733
+ await client.confirmGame({
734
+ gameId: createResult.gameId,
735
+ playerWallet: params.playerWallet,
736
+ signature,
737
+ teamChoice: params.teamChoice,
738
+ wagerAmount: params.wagerAmount,
739
+ role: "creator",
740
+ gameAddress: createResult.gameAddress
741
+ });
742
+ const result = {
743
+ gameId: createResult.gameId,
744
+ gameAddress: createResult.gameAddress,
745
+ signature,
746
+ explorerUrl
747
+ };
748
+ setData(result);
749
+ setStatus("success");
750
+ return result;
751
+ } catch (err) {
752
+ const error2 = err instanceof Error ? err : new Error(String(err));
753
+ setError(error2);
754
+ setStatus("error");
755
+ throw error2;
756
+ }
757
+ }, [client, wallet, connection]);
758
+ return { execute, status, error, data, reset };
759
+ }
760
+
761
+ // src/hooks/useJoinGame.ts
762
+ var import_react7 = require("react");
763
+ function useJoinGame() {
764
+ const { client, wallet, connection } = useDubs();
765
+ const [status, setStatus] = (0, import_react7.useState)("idle");
766
+ const [error, setError] = (0, import_react7.useState)(null);
767
+ const [data, setData] = (0, import_react7.useState)(null);
768
+ const reset = (0, import_react7.useCallback)(() => {
769
+ setStatus("idle");
770
+ setError(null);
771
+ setData(null);
772
+ }, []);
773
+ const execute = (0, import_react7.useCallback)(async (params) => {
774
+ setStatus("building");
775
+ setError(null);
776
+ setData(null);
777
+ try {
778
+ const joinResult = await client.joinGame(params);
779
+ setStatus("signing");
780
+ const signature = await signAndSendBase64Transaction(
781
+ joinResult.transaction,
782
+ wallet,
783
+ connection
784
+ );
785
+ setStatus("confirming");
786
+ await pollTransactionConfirmation(signature, connection);
787
+ setStatus("saving");
788
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
789
+ await client.confirmGame({
790
+ gameId: params.gameId,
791
+ playerWallet: params.playerWallet,
792
+ signature,
793
+ teamChoice: params.teamChoice,
794
+ wagerAmount: params.amount,
795
+ role: "joiner",
796
+ gameAddress: joinResult.gameAddress
797
+ });
798
+ const result = {
799
+ gameId: params.gameId,
800
+ gameAddress: joinResult.gameAddress,
801
+ signature,
802
+ explorerUrl
803
+ };
804
+ setData(result);
805
+ setStatus("success");
806
+ return result;
807
+ } catch (err) {
808
+ const error2 = err instanceof Error ? err : new Error(String(err));
809
+ setError(error2);
810
+ setStatus("error");
811
+ throw error2;
812
+ }
813
+ }, [client, wallet, connection]);
814
+ return { execute, status, error, data, reset };
815
+ }
816
+
817
+ // src/hooks/useClaim.ts
818
+ var import_react8 = require("react");
819
+ function useClaim() {
820
+ const { client, wallet, connection } = useDubs();
821
+ const [status, setStatus] = (0, import_react8.useState)("idle");
822
+ const [error, setError] = (0, import_react8.useState)(null);
823
+ const [data, setData] = (0, import_react8.useState)(null);
824
+ const reset = (0, import_react8.useCallback)(() => {
825
+ setStatus("idle");
826
+ setError(null);
827
+ setData(null);
828
+ }, []);
829
+ const execute = (0, import_react8.useCallback)(async (params) => {
830
+ setStatus("building");
831
+ setError(null);
832
+ setData(null);
833
+ try {
834
+ const claimResult = await client.buildClaimTransaction(params);
835
+ setStatus("signing");
836
+ const signature = await signAndSendBase64Transaction(
837
+ claimResult.transaction,
838
+ wallet,
839
+ connection
840
+ );
841
+ setStatus("confirming");
842
+ await pollTransactionConfirmation(signature, connection);
843
+ const explorerUrl = `https://solscan.io/tx/${signature}`;
844
+ const result = {
845
+ gameId: params.gameId,
846
+ signature,
847
+ explorerUrl
848
+ };
849
+ setData(result);
850
+ setStatus("success");
851
+ return result;
852
+ } catch (err) {
853
+ const error2 = err instanceof Error ? err : new Error(String(err));
854
+ setError(error2);
855
+ setStatus("error");
856
+ throw error2;
857
+ }
858
+ }, [client, wallet, connection]);
859
+ return { execute, status, error, data, reset };
860
+ }
861
+
862
+ // src/hooks/useAuth.ts
863
+ var import_react9 = require("react");
864
+ var import_bs58 = __toESM(require("bs58"));
865
+ function useAuth() {
866
+ const { client, wallet } = useDubs();
867
+ const [status, setStatus] = (0, import_react9.useState)("idle");
868
+ const [user, setUser] = (0, import_react9.useState)(null);
869
+ const [token, setToken] = (0, import_react9.useState)(null);
870
+ const [error, setError] = (0, import_react9.useState)(null);
871
+ const pendingAuth = (0, import_react9.useRef)(null);
872
+ const reset = (0, import_react9.useCallback)(() => {
873
+ setStatus("idle");
874
+ setUser(null);
875
+ setToken(null);
876
+ setError(null);
877
+ pendingAuth.current = null;
878
+ client.setToken(null);
879
+ }, [client]);
880
+ const authenticate = (0, import_react9.useCallback)(async () => {
881
+ try {
882
+ if (!wallet.publicKey) {
883
+ throw new Error("Wallet not connected");
884
+ }
885
+ if (!wallet.signMessage) {
886
+ throw new Error("Wallet does not support signMessage");
887
+ }
888
+ const walletAddress = wallet.publicKey.toBase58();
889
+ setStatus("authenticating");
890
+ setError(null);
891
+ const { nonce, message } = await client.getNonce(walletAddress);
892
+ setStatus("signing");
893
+ const messageBytes = new TextEncoder().encode(message);
894
+ const signatureBytes = await wallet.signMessage(messageBytes);
895
+ const signature = import_bs58.default.encode(signatureBytes);
896
+ setStatus("verifying");
897
+ const result = await client.authenticate({ walletAddress, signature, nonce });
898
+ if (result.needsRegistration) {
899
+ pendingAuth.current = { walletAddress, nonce, signature };
900
+ setStatus("needsRegistration");
901
+ return;
902
+ }
903
+ setUser(result.user);
904
+ setToken(result.token);
905
+ setStatus("authenticated");
906
+ } catch (err) {
907
+ setError(err instanceof Error ? err : new Error(String(err)));
908
+ setStatus("error");
909
+ }
910
+ }, [client, wallet]);
911
+ const register = (0, import_react9.useCallback)(async (username, referralCode) => {
912
+ try {
913
+ const pending = pendingAuth.current;
914
+ if (!pending) {
915
+ throw new Error("No pending authentication \u2014 call authenticate() first");
916
+ }
917
+ setStatus("registering");
918
+ setError(null);
919
+ const result = await client.register({
920
+ walletAddress: pending.walletAddress,
921
+ signature: pending.signature,
922
+ nonce: pending.nonce,
923
+ username,
924
+ referralCode
925
+ });
926
+ pendingAuth.current = null;
927
+ setUser(result.user);
928
+ setToken(result.token);
929
+ setStatus("authenticated");
930
+ } catch (err) {
931
+ setError(err instanceof Error ? err : new Error(String(err)));
932
+ setStatus("error");
933
+ }
934
+ }, [client]);
935
+ const logout = (0, import_react9.useCallback)(async () => {
936
+ try {
937
+ await client.logout();
938
+ } catch {
939
+ }
940
+ setUser(null);
941
+ setToken(null);
942
+ setStatus("idle");
943
+ setError(null);
944
+ pendingAuth.current = null;
945
+ }, [client]);
946
+ const restoreSession = (0, import_react9.useCallback)(async (savedToken) => {
947
+ try {
948
+ client.setToken(savedToken);
949
+ const me = await client.getMe();
950
+ setUser(me);
951
+ setToken(savedToken);
952
+ setStatus("authenticated");
953
+ return true;
954
+ } catch {
955
+ client.setToken(null);
956
+ setUser(null);
957
+ setToken(null);
958
+ setStatus("idle");
959
+ return false;
960
+ }
961
+ }, [client]);
962
+ return {
963
+ status,
964
+ user,
965
+ token,
966
+ isAuthenticated: status === "authenticated",
967
+ error,
968
+ authenticate,
969
+ register,
970
+ logout,
971
+ restoreSession,
972
+ reset
973
+ };
974
+ }
975
+
976
+ // src/ui/ConnectWalletScreen.tsx
977
+ var import_react_native2 = require("react-native");
978
+
979
+ // src/ui/theme.ts
980
+ var import_react_native = require("react-native");
981
+ var dark = {
982
+ background: "#08080D",
983
+ surface: "#111118",
984
+ surfaceActive: "#7C3AED",
985
+ border: "#1A1A24",
986
+ text: "#FFFFFF",
987
+ textSecondary: "#E0E0EE",
988
+ textMuted: "#666666",
989
+ textDim: "#555555",
990
+ accent: "#7C3AED",
991
+ success: "#22C55E",
992
+ live: "#EF4444",
993
+ errorText: "#F87171",
994
+ errorBg: "#1A0A0A",
995
+ errorBorder: "#3A1515"
996
+ };
997
+ var light = {
998
+ background: "#FFFFFF",
999
+ surface: "#F0F0F5",
1000
+ surfaceActive: "#7C3AED",
1001
+ border: "#E0E0E8",
1002
+ text: "#111118",
1003
+ textSecondary: "#333333",
1004
+ textMuted: "#888888",
1005
+ textDim: "#999999",
1006
+ accent: "#7C3AED",
1007
+ success: "#16A34A",
1008
+ live: "#DC2626",
1009
+ errorText: "#DC2626",
1010
+ errorBg: "#FEF2F2",
1011
+ errorBorder: "#FECACA"
1012
+ };
1013
+ function useDubsTheme() {
1014
+ const scheme = (0, import_react_native.useColorScheme)();
1015
+ return scheme === "light" ? light : dark;
1016
+ }
1017
+
1018
+ // src/ui/ConnectWalletScreen.tsx
1019
+ var import_jsx_runtime2 = require("react/jsx-runtime");
1020
+ function ConnectWalletScreen({
1021
+ onConnect,
1022
+ connecting = false,
1023
+ error = null,
1024
+ appName = "Dubs"
1025
+ }) {
1026
+ const t = useDubsTheme();
1027
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles.container, { backgroundColor: t.background }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles.content, children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles.brandingSection, children: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: [styles.logoCircle, { backgroundColor: t.accent }], children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles.logoText, children: "D" }) }),
1030
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.appName, { color: t.text }], children: appName }),
1031
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.subtitle, { color: t.textMuted }], children: "Connect your Solana wallet to get started" })
1032
+ ] }),
1033
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles.actionSection, children: [
1034
+ error ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1035
+ import_react_native2.View,
1036
+ {
1037
+ style: [
1038
+ styles.errorBox,
1039
+ { backgroundColor: t.errorBg, borderColor: t.errorBorder }
1040
+ ],
1041
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.errorText, { color: t.errorText }], children: error })
1042
+ }
1043
+ ) : null,
1044
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1045
+ import_react_native2.TouchableOpacity,
1046
+ {
1047
+ style: [styles.connectButton, { backgroundColor: t.accent }],
1048
+ onPress: onConnect,
1049
+ disabled: connecting,
1050
+ activeOpacity: 0.8,
1051
+ children: connecting ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { color: "#FFFFFF", size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles.connectButtonText, children: "Connect Wallet" })
1052
+ }
1053
+ ),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: [styles.hint, { color: t.textDim }], children: "Phantom, Solflare, or any Solana wallet" })
1055
+ ] })
1056
+ ] }) });
1057
+ }
1058
+ var styles = import_react_native2.StyleSheet.create({
1059
+ container: {
1060
+ flex: 1,
1061
+ justifyContent: "center"
1062
+ },
1063
+ content: {
1064
+ flex: 1,
1065
+ justifyContent: "space-between",
1066
+ paddingHorizontal: 32,
1067
+ paddingTop: 120,
1068
+ paddingBottom: 80
1069
+ },
1070
+ brandingSection: {
1071
+ alignItems: "center",
1072
+ gap: 12
1073
+ },
1074
+ logoCircle: {
1075
+ width: 80,
1076
+ height: 80,
1077
+ borderRadius: 40,
1078
+ justifyContent: "center",
1079
+ alignItems: "center",
1080
+ marginBottom: 8
1081
+ },
1082
+ logoText: {
1083
+ fontSize: 36,
1084
+ fontWeight: "800",
1085
+ color: "#FFFFFF"
1086
+ },
1087
+ appName: {
1088
+ fontSize: 32,
1089
+ fontWeight: "800"
1090
+ },
1091
+ subtitle: {
1092
+ fontSize: 16,
1093
+ textAlign: "center",
1094
+ lineHeight: 22
1095
+ },
1096
+ actionSection: {
1097
+ gap: 16
1098
+ },
1099
+ errorBox: {
1100
+ borderWidth: 1,
1101
+ borderRadius: 12,
1102
+ paddingHorizontal: 16,
1103
+ paddingVertical: 12
1104
+ },
1105
+ errorText: {
1106
+ fontSize: 14,
1107
+ textAlign: "center"
1108
+ },
1109
+ connectButton: {
1110
+ height: 56,
1111
+ borderRadius: 16,
1112
+ justifyContent: "center",
1113
+ alignItems: "center"
1114
+ },
1115
+ connectButtonText: {
1116
+ color: "#FFFFFF",
1117
+ fontSize: 18,
1118
+ fontWeight: "700"
1119
+ },
1120
+ hint: {
1121
+ fontSize: 13,
1122
+ textAlign: "center"
1123
+ }
1124
+ });
1125
+
1126
+ // src/ui/UserProfileCard.tsx
1127
+ var import_react10 = require("react");
1128
+ var import_react_native3 = require("react-native");
1129
+ var import_jsx_runtime3 = require("react/jsx-runtime");
1130
+ function truncateAddress(address, chars = 4) {
1131
+ if (address.length <= chars * 2 + 3) return address;
1132
+ return `${address.slice(0, chars)}...${address.slice(-chars)}`;
1133
+ }
1134
+ function formatMemberSince(iso) {
1135
+ const date = new Date(iso);
1136
+ const month = date.toLocaleString("en-US", { month: "short" });
1137
+ const year = date.getFullYear();
1138
+ return `Member since ${month} ${year}`;
1139
+ }
1140
+ function UserProfileCard({
1141
+ walletAddress,
1142
+ username,
1143
+ avatarUrl,
1144
+ memberSince
1145
+ }) {
1146
+ const t = useDubsTheme();
1147
+ const imageUri = (0, import_react10.useMemo)(
1148
+ () => avatarUrl || `https://api.dicebear.com/9.x/avataaars/png?seed=${walletAddress}&size=128`,
1149
+ [avatarUrl, walletAddress]
1150
+ );
1151
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: [styles2.card, { backgroundColor: t.surface, borderColor: t.border }], children: [
1152
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Image, { source: { uri: imageUri }, style: styles2.avatar }),
1153
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles2.info, children: [
1154
+ username ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles2.username, { color: t.text }], children: username }) : null,
1155
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles2.address, { color: t.textMuted }], children: truncateAddress(walletAddress) }),
1156
+ memberSince ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: [styles2.memberSince, { color: t.textDim }], children: formatMemberSince(memberSince) }) : null
1157
+ ] })
1158
+ ] });
1159
+ }
1160
+ var styles2 = import_react_native3.StyleSheet.create({
1161
+ card: {
1162
+ flexDirection: "row",
1163
+ alignItems: "center",
1164
+ padding: 16,
1165
+ borderRadius: 16,
1166
+ borderWidth: 1,
1167
+ gap: 14
1168
+ },
1169
+ avatar: {
1170
+ width: 64,
1171
+ height: 64,
1172
+ borderRadius: 32,
1173
+ backgroundColor: "#1A1A24"
1174
+ },
1175
+ info: {
1176
+ flex: 1,
1177
+ gap: 2
1178
+ },
1179
+ username: {
1180
+ fontSize: 18,
1181
+ fontWeight: "700"
1182
+ },
1183
+ address: {
1184
+ fontSize: 14,
1185
+ fontFamily: "monospace"
1186
+ },
1187
+ memberSince: {
1188
+ fontSize: 12,
1189
+ marginTop: 2
1190
+ }
1191
+ });
1192
+
1193
+ // src/ui/SettingsSheet.tsx
1194
+ var import_react_native4 = require("react-native");
1195
+ var import_jsx_runtime4 = require("react/jsx-runtime");
1196
+ function truncateAddress2(address, chars = 4) {
1197
+ if (address.length <= chars * 2 + 3) return address;
1198
+ return `${address.slice(0, chars)}...${address.slice(-chars)}`;
1199
+ }
1200
+ function SettingsSheet({
1201
+ walletAddress,
1202
+ username,
1203
+ avatarUrl,
1204
+ memberSince,
1205
+ appVersion,
1206
+ onCopyAddress,
1207
+ onSupport,
1208
+ onLogout,
1209
+ loggingOut = false
1210
+ }) {
1211
+ const t = useDubsTheme();
1212
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1213
+ import_react_native4.ScrollView,
1214
+ {
1215
+ style: [styles3.container, { backgroundColor: t.background }],
1216
+ contentContainerStyle: styles3.content,
1217
+ children: [
1218
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1219
+ UserProfileCard,
1220
+ {
1221
+ walletAddress,
1222
+ username,
1223
+ avatarUrl,
1224
+ memberSince
1225
+ }
1226
+ ),
1227
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles3.actionsCard, { backgroundColor: t.surface, borderColor: t.border }], children: [
1228
+ onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1229
+ import_react_native4.TouchableOpacity,
1230
+ {
1231
+ style: styles3.actionRow,
1232
+ onPress: onCopyAddress,
1233
+ activeOpacity: 0.7,
1234
+ children: [
1235
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles3.actionRowLeft, children: [
1236
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Wallet Address" }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.actionValue, { color: t.textMuted }], children: truncateAddress2(walletAddress) })
1238
+ ] }),
1239
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.copyLabel, { color: t.accent }], children: "Copy" })
1240
+ ]
1241
+ }
1242
+ ) : null,
1243
+ onSupport ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1244
+ onCopyAddress ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: [styles3.separator, { backgroundColor: t.border }] }) : null,
1245
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
1246
+ import_react_native4.TouchableOpacity,
1247
+ {
1248
+ style: styles3.actionRow,
1249
+ onPress: onSupport,
1250
+ activeOpacity: 0.7,
1251
+ children: [
1252
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.actionLabel, { color: t.text }], children: "Help & Support" }),
1253
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.chevron, { color: t.textMuted }], children: "\u203A" })
1254
+ ]
1255
+ }
1256
+ )
1257
+ ] }) : null
1258
+ ] }),
1259
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1260
+ import_react_native4.TouchableOpacity,
1261
+ {
1262
+ style: [styles3.logoutButton, { borderColor: t.live }],
1263
+ onPress: onLogout,
1264
+ disabled: loggingOut,
1265
+ activeOpacity: 0.7,
1266
+ children: loggingOut ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { color: t.live, size: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles3.logoutText, { color: t.live }], children: "Log Out" })
1267
+ }
1268
+ ),
1269
+ appVersion ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Text, { style: [styles3.version, { color: t.textDim }], children: [
1270
+ "v",
1271
+ appVersion
1272
+ ] }) : null
1273
+ ]
1274
+ }
1275
+ );
1276
+ }
1277
+ var styles3 = import_react_native4.StyleSheet.create({
1278
+ container: {
1279
+ flex: 1
1280
+ },
1281
+ content: {
1282
+ padding: 20,
1283
+ paddingTop: 24,
1284
+ gap: 20
1285
+ },
1286
+ actionsCard: {
1287
+ borderRadius: 16,
1288
+ borderWidth: 1,
1289
+ overflow: "hidden"
1290
+ },
1291
+ actionRow: {
1292
+ flexDirection: "row",
1293
+ alignItems: "center",
1294
+ justifyContent: "space-between",
1295
+ paddingHorizontal: 16,
1296
+ paddingVertical: 14
1297
+ },
1298
+ actionRowLeft: {
1299
+ flex: 1,
1300
+ gap: 2
1301
+ },
1302
+ actionLabel: {
1303
+ fontSize: 15,
1304
+ fontWeight: "600"
1305
+ },
1306
+ actionValue: {
1307
+ fontSize: 13,
1308
+ fontFamily: "monospace"
1309
+ },
1310
+ copyLabel: {
1311
+ fontSize: 14,
1312
+ fontWeight: "600"
1313
+ },
1314
+ chevron: {
1315
+ fontSize: 22,
1316
+ fontWeight: "300"
1317
+ },
1318
+ separator: {
1319
+ height: 1,
1320
+ marginHorizontal: 16
1321
+ },
1322
+ logoutButton: {
1323
+ height: 52,
1324
+ borderRadius: 16,
1325
+ borderWidth: 1.5,
1326
+ justifyContent: "center",
1327
+ alignItems: "center"
1328
+ },
1329
+ logoutText: {
1330
+ fontSize: 16,
1331
+ fontWeight: "700"
1332
+ },
1333
+ version: {
1334
+ fontSize: 12,
1335
+ textAlign: "center"
1336
+ }
1337
+ });
1338
+ // Annotate the CommonJS export names for ESM import in node:
1339
+ 0 && (module.exports = {
1340
+ ConnectWalletScreen,
1341
+ DEFAULT_BASE_URL,
1342
+ DEFAULT_RPC_URL,
1343
+ DubsApiError,
1344
+ DubsClient,
1345
+ DubsProvider,
1346
+ MwaWalletAdapter,
1347
+ SOLANA_PROGRAM_ERRORS,
1348
+ SettingsSheet,
1349
+ UserProfileCard,
1350
+ parseSolanaError,
1351
+ pollTransactionConfirmation,
1352
+ signAndSendBase64Transaction,
1353
+ useAuth,
1354
+ useClaim,
1355
+ useCreateGame,
1356
+ useDubs,
1357
+ useDubsTheme,
1358
+ useEvents,
1359
+ useGame,
1360
+ useGames,
1361
+ useJoinGame,
1362
+ useNetworkGames
1363
+ });
1364
+ //# sourceMappingURL=index.js.map