@dubsdotapp/expo 0.2.33 → 0.2.35
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.d.mts +26 -1
- package/dist/index.d.ts +26 -1
- package/dist/index.js +179 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +132 -51
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/errors.ts +13 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useClaim.ts +29 -1
- package/src/hooks/useHasClaimed.ts +85 -0
- package/src/index.ts +2 -0
- package/src/types.ts +2 -0
package/package.json
CHANGED
package/src/errors.ts
CHANGED
|
@@ -66,6 +66,14 @@ export const SOLANA_PROGRAM_ERRORS: Record<number, SolanaErrorCode> = {
|
|
|
66
66
|
6049: { code: 'too_many_survivors', message: 'Too many survivors (max 50 per batch)' },
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
/** Known wallet/MWA error patterns that don't contain Solana error codes */
|
|
70
|
+
const WALLET_ERROR_PATTERNS: Array<{ pattern: RegExp; error: ParsedError }> = [
|
|
71
|
+
{ pattern: /CancellationException/i, error: { code: 'transaction_rejected', message: 'Transaction was rejected by the wallet' } },
|
|
72
|
+
{ pattern: /declined/i, error: { code: 'user_declined', message: 'Transaction was declined' } },
|
|
73
|
+
{ pattern: /timeout/i, error: { code: 'transaction_timeout', message: 'Transaction timed out' } },
|
|
74
|
+
{ pattern: /not connected/i, error: { code: 'wallet_not_connected', message: 'Wallet is not connected' } },
|
|
75
|
+
];
|
|
76
|
+
|
|
69
77
|
/** Known Solana built-in instruction errors */
|
|
70
78
|
const SOLANA_BUILTIN_ERRORS: Record<number, SolanaErrorCode> = {
|
|
71
79
|
0: { code: 'generic_error', message: 'Generic instruction error' },
|
|
@@ -90,6 +98,11 @@ export function parseSolanaError(err: unknown): ParsedError {
|
|
|
90
98
|
let parsed = err;
|
|
91
99
|
|
|
92
100
|
if (typeof parsed === 'string') {
|
|
101
|
+
// Check for known wallet/MWA error patterns before attempting JSON parse
|
|
102
|
+
for (const { pattern, error } of WALLET_ERROR_PATTERNS) {
|
|
103
|
+
if (pattern.test(parsed)) return error;
|
|
104
|
+
}
|
|
105
|
+
|
|
93
106
|
try {
|
|
94
107
|
const jsonMatch = parsed.match(/\{.*\}/s);
|
|
95
108
|
if (jsonMatch) parsed = JSON.parse(jsonMatch[0]);
|
package/src/hooks/index.ts
CHANGED
|
@@ -10,5 +10,7 @@ export { useClaim } from './useClaim';
|
|
|
10
10
|
export type { ClaimMutationResult } from './useClaim';
|
|
11
11
|
export { useCreateCustomGame } from './useCreateCustomGame';
|
|
12
12
|
export type { CreateCustomGameMutationResult } from './useCreateCustomGame';
|
|
13
|
+
export { useHasClaimed } from './useHasClaimed';
|
|
14
|
+
export type { ClaimStatus } from './useHasClaimed';
|
|
13
15
|
export { useAuth } from './useAuth';
|
|
14
16
|
export type { UseAuthResult } from './useAuth';
|
package/src/hooks/useClaim.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback } from 'react';
|
|
2
2
|
import { useDubs } from '../provider';
|
|
3
3
|
import { signAndSendBase64Transaction } from '../utils/transaction';
|
|
4
|
+
import { parseSolanaError } from '../errors';
|
|
4
5
|
import type { BuildClaimParams, MutationStatus } from '../types';
|
|
5
6
|
|
|
6
7
|
export interface ClaimMutationResult {
|
|
@@ -64,7 +65,34 @@ export function useClaim() {
|
|
|
64
65
|
return result;
|
|
65
66
|
} catch (err) {
|
|
66
67
|
console.error('[useClaim] FAILED:', err);
|
|
67
|
-
|
|
68
|
+
// Parse Solana program errors into human-readable messages
|
|
69
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
70
|
+
const parsed = parseSolanaError(raw);
|
|
71
|
+
|
|
72
|
+
// If the error is generic (wallet rejected / unrecognizable), check if it's
|
|
73
|
+
// actually an "already claimed" situation by querying the game state.
|
|
74
|
+
if (parsed.code === 'transaction_rejected' || parsed.code === 'transaction_failed') {
|
|
75
|
+
try {
|
|
76
|
+
const game = await client.getGame(params.gameId);
|
|
77
|
+
const myBet = game.bettors?.find(
|
|
78
|
+
(b: { wallet: string }) => b.wallet === params.playerWallet,
|
|
79
|
+
);
|
|
80
|
+
if (myBet?.amountClaimed != null && myBet.amountClaimed > 0) {
|
|
81
|
+
const claimedErr = new Error('Player has already claimed their winnings');
|
|
82
|
+
(claimedErr as any).code = 'already_claimed';
|
|
83
|
+
setError(claimedErr);
|
|
84
|
+
setStatus('error');
|
|
85
|
+
throw claimedErr;
|
|
86
|
+
}
|
|
87
|
+
} catch (checkErr) {
|
|
88
|
+
// If the post-hoc check itself identified "already claimed", rethrow it
|
|
89
|
+
if ((checkErr as any)?.code === 'already_claimed') throw checkErr;
|
|
90
|
+
// Otherwise fall through to the generic parsed error
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const error = new Error(parsed.message);
|
|
95
|
+
(error as any).code = parsed.code;
|
|
68
96
|
setError(error);
|
|
69
97
|
setStatus('error');
|
|
70
98
|
throw error;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useDubs } from '../provider';
|
|
3
|
+
|
|
4
|
+
export interface ClaimStatus {
|
|
5
|
+
/** Whether the current wallet has claimed this game's prize */
|
|
6
|
+
hasClaimed: boolean;
|
|
7
|
+
/** Amount claimed (null if not claimed) */
|
|
8
|
+
amountClaimed: number | null;
|
|
9
|
+
/** Claim transaction signature (null if not claimed) */
|
|
10
|
+
claimSignature: string | null;
|
|
11
|
+
/** Whether the game has been resolved */
|
|
12
|
+
isResolved: boolean;
|
|
13
|
+
/** Loading state */
|
|
14
|
+
loading: boolean;
|
|
15
|
+
/** Error if fetch failed */
|
|
16
|
+
error: Error | null;
|
|
17
|
+
/** Re-fetch claim status */
|
|
18
|
+
refetch: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Check whether the current wallet has already claimed a prize for a game.
|
|
23
|
+
*
|
|
24
|
+
* Uses the game detail endpoint which includes per-bettor claim data.
|
|
25
|
+
*/
|
|
26
|
+
export function useHasClaimed(gameId: string | null): ClaimStatus {
|
|
27
|
+
const { client, wallet } = useDubs();
|
|
28
|
+
const [hasClaimed, setHasClaimed] = useState(false);
|
|
29
|
+
const [amountClaimed, setAmountClaimed] = useState<number | null>(null);
|
|
30
|
+
const [claimSignature, setClaimSignature] = useState<string | null>(null);
|
|
31
|
+
const [isResolved, setIsResolved] = useState(false);
|
|
32
|
+
const [loading, setLoading] = useState(false);
|
|
33
|
+
const [error, setError] = useState<Error | null>(null);
|
|
34
|
+
const [trigger, setTrigger] = useState(0);
|
|
35
|
+
|
|
36
|
+
const refetch = () => setTrigger(t => t + 1);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!gameId || !wallet.publicKey) {
|
|
40
|
+
setHasClaimed(false);
|
|
41
|
+
setAmountClaimed(null);
|
|
42
|
+
setClaimSignature(null);
|
|
43
|
+
setIsResolved(false);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
setLoading(true);
|
|
49
|
+
setError(null);
|
|
50
|
+
|
|
51
|
+
client
|
|
52
|
+
.getGame(gameId)
|
|
53
|
+
.then(game => {
|
|
54
|
+
if (cancelled) return;
|
|
55
|
+
setIsResolved(game.isResolved);
|
|
56
|
+
|
|
57
|
+
const myWallet = wallet.publicKey!.toBase58();
|
|
58
|
+
const myBet = game.bettors?.find(b => b.wallet === myWallet);
|
|
59
|
+
|
|
60
|
+
if (myBet?.amountClaimed != null && myBet.amountClaimed > 0) {
|
|
61
|
+
setHasClaimed(true);
|
|
62
|
+
setAmountClaimed(myBet.amountClaimed);
|
|
63
|
+
setClaimSignature(myBet.claimSignature ?? null);
|
|
64
|
+
} else {
|
|
65
|
+
setHasClaimed(false);
|
|
66
|
+
setAmountClaimed(null);
|
|
67
|
+
setClaimSignature(null);
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.catch(err => {
|
|
71
|
+
if (!cancelled) {
|
|
72
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
.finally(() => {
|
|
76
|
+
if (!cancelled) setLoading(false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return () => {
|
|
80
|
+
cancelled = true;
|
|
81
|
+
};
|
|
82
|
+
}, [gameId, wallet.publicKey, client, trigger]);
|
|
83
|
+
|
|
84
|
+
return { hasClaimed, amountClaimed, claimSignature, isResolved, loading, error, refetch };
|
|
85
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -82,6 +82,7 @@ export {
|
|
|
82
82
|
useCreateCustomGame,
|
|
83
83
|
useJoinGame,
|
|
84
84
|
useClaim,
|
|
85
|
+
useHasClaimed,
|
|
85
86
|
useAuth,
|
|
86
87
|
} from './hooks';
|
|
87
88
|
export type {
|
|
@@ -89,6 +90,7 @@ export type {
|
|
|
89
90
|
CreateCustomGameMutationResult,
|
|
90
91
|
JoinGameMutationResult,
|
|
91
92
|
ClaimMutationResult,
|
|
93
|
+
ClaimStatus,
|
|
92
94
|
UseAuthResult,
|
|
93
95
|
} from './hooks';
|
|
94
96
|
|