@dubsdotapp/expo 0.5.31 → 0.5.33

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dubsdotapp/expo",
3
- "version": "0.5.31",
3
+ "version": "0.5.33",
4
4
  "description": "React Native SDK for the Dubs betting platform",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
package/src/client.ts CHANGED
@@ -215,6 +215,31 @@ export class DubsClient {
215
215
  }
216
216
 
217
217
  async joinGame(params: JoinGameParams): Promise<JoinGameResult> {
218
+ // Sponsored path: spend an active streak credit instead of buy-in.
219
+ // Server picks the user's oldest active credit (FIFO); we just flag
220
+ // the intent. Result includes promoCode for the confirm step to
221
+ // mark as used.
222
+ if (params.useCredit) {
223
+ const res = await this.request<{
224
+ success: true;
225
+ gameId: string;
226
+ transaction: string;
227
+ gameAddress: string;
228
+ promoCode: string;
229
+ sponsorWallet?: string;
230
+ }>('POST', '/games/join-sponsored', {
231
+ gameId: params.gameId,
232
+ teamChoice: params.teamChoice,
233
+ });
234
+ return {
235
+ gameId: res.gameId,
236
+ transaction: res.transaction,
237
+ gameAddress: res.gameAddress,
238
+ promoCode: res.promoCode,
239
+ sponsorWallet: res.sponsorWallet,
240
+ };
241
+ }
242
+
218
243
  const res = await this.request<{ success: true } & JoinGameResult>(
219
244
  'POST',
220
245
  '/games/join',
@@ -53,6 +53,9 @@ export function useJoinGame() {
53
53
  wagerAmount: params.amount,
54
54
  role: 'joiner' as const,
55
55
  gameAddress: joinResult.gameAddress,
56
+ // Pass through when this was a sponsored join — server marks
57
+ // the credit as used after the on-chain confirm succeeds.
58
+ promoCode: joinResult.promoCode,
56
59
  };
57
60
  console.log('[useJoinGame] Step 3: Confirming with backend...', confirmParams);
58
61
  const confirmResult = await client.confirmGame(confirmParams);
package/src/index.ts CHANGED
@@ -124,6 +124,7 @@ export {
124
124
  useJackpot,
125
125
  useJackpotHistory,
126
126
  useEnterJackpot,
127
+ useCredits,
127
128
  } from './hooks';
128
129
  export type {
129
130
  CreateGameMutationResult,
@@ -145,6 +146,7 @@ export type {
145
146
  UseJackpotResult,
146
147
  UseJackpotHistoryResult,
147
148
  EnterJackpotMutationResult,
149
+ UseCreditsResult,
148
150
  } from './hooks';
149
151
 
150
152
  export { useHighlights, useShorts } from './hooks';
package/src/types.ts CHANGED
@@ -130,12 +130,26 @@ export interface JoinGameParams {
130
130
  gameId: string;
131
131
  teamChoice: 'home' | 'away' | 'draw';
132
132
  amount: number;
133
+ /**
134
+ * When true, spend one of the user's active 0.01 SOL streak credits
135
+ * instead of paying buy-in from the player's own wallet. The SDK
136
+ * picks the oldest credit (FIFO). The treasury covers buy-in + tx
137
+ * fee; the player only signs to consent. No-op if the user has no
138
+ * active credits — call useCredits() to check before enabling.
139
+ */
140
+ useCredit?: boolean;
133
141
  }
134
142
 
135
143
  export interface JoinGameResult {
136
144
  gameId: string;
137
145
  transaction: string;
138
146
  gameAddress: string;
147
+ /**
148
+ * Set when this join was sponsored. The mutation hook echoes this
149
+ * back to confirmGame so the server can mark the credit as used.
150
+ */
151
+ promoCode?: string;
152
+ sponsorWallet?: string;
139
153
  }
140
154
 
141
155
  // ── Confirm Game ──
@@ -148,6 +162,8 @@ export interface ConfirmGameParams {
148
162
  wagerAmount?: number;
149
163
  role?: 'creator' | 'joiner';
150
164
  gameAddress?: string;
165
+ /** Echoed back from a sponsored joinGame so the server marks the credit as used. */
166
+ promoCode?: string;
151
167
  }
152
168
 
153
169
  export interface ConfirmGameResult {
@@ -15,6 +15,7 @@ import { useDubsTheme } from '../theme';
15
15
  import { useDubs } from '../../provider';
16
16
  import { useJoinGame } from '../../hooks/useJoinGame';
17
17
  import type { JoinGameMutationResult } from '../../hooks/useJoinGame';
18
+ import { useCredits } from '../../hooks/useCredits';
18
19
  import type { GameDetail } from '../../types';
19
20
  import { SolSlider } from './SolSlider';
20
21
  import { TeamButton } from './TeamButton';
@@ -85,12 +86,20 @@ export function JoinGameSheet({
85
86
  const t = useDubsTheme();
86
87
  const { wallet } = useDubs();
87
88
  const mutation = useJoinGame();
89
+ const { credits, refetch: refetchCredits } = useCredits();
88
90
 
89
91
  const isCustomGame = game.gameMode === CUSTOM_GAME_MODE;
90
92
 
91
93
  const [selectedTeam, setSelectedTeam] = useState<'home' | 'away' | 'draw' | null>(null);
92
94
  const [wager, setWager] = useState(game.buyIn);
93
95
  const [showSuccess, setShowSuccess] = useState(false);
96
+ // "Use my streak credit" toggle. Only meaningful when the user has at
97
+ // least one active credit AND the credit's amount covers buy-in. The
98
+ // sponsored on-chain instruction transfers exactly the credit amount,
99
+ // so when this is on we lock wager to the credit value.
100
+ const [useCredit, setUseCredit] = useState(false);
101
+ const oldestCredit = credits.length > 0 ? credits[0] : null;
102
+ const canUseCredit = !!oldestCredit && oldestCredit.amountSOL >= game.buyIn;
94
103
 
95
104
  const overlayOpacity = useRef(new Animated.Value(0)).current;
96
105
  const successScale = useRef(new Animated.Value(0)).current;
@@ -213,12 +222,18 @@ export function JoinGameSheet({
213
222
  playerWallet: wallet.publicKey.toBase58(),
214
223
  gameId: game.gameId,
215
224
  teamChoice: selectedTeam,
216
- amount: wager,
225
+ // When useCredit is on, the on-chain instruction transfers
226
+ // exactly the credit's amount; pass that as the wager.
227
+ amount: useCredit && oldestCredit ? oldestCredit.amountSOL : wager,
228
+ useCredit: useCredit && canUseCredit ? true : undefined,
217
229
  });
230
+ // The credit was just spent — refresh the list so the toggle
231
+ // disappears (or rolls to the next FIFO credit).
232
+ if (useCredit) refetchCredits();
218
233
  } catch {
219
234
  // Error is already captured in mutation state
220
235
  }
221
- }, [selectedTeam, wallet.publicKey, mutation.execute, game.gameId, wager]); // eslint-disable-line react-hooks/exhaustive-deps
236
+ }, [selectedTeam, wallet.publicKey, mutation.execute, game.gameId, wager, useCredit, oldestCredit, canUseCredit, refetchCredits]); // eslint-disable-line react-hooks/exhaustive-deps
222
237
 
223
238
  const statusLabel = STATUS_LABELS[mutation.status] || '';
224
239
 
@@ -430,8 +445,31 @@ export function JoinGameSheet({
430
445
  )}
431
446
  </View>
432
447
 
433
- {/* SOL Slidersits right below summary card */}
434
- {selectedTeam && !isPoolModeEnabled && !alreadyJoined && (
448
+ {/* Credit toggleonly when user has a credit ≥ buy-in */}
449
+ {selectedTeam && !alreadyJoined && canUseCredit && oldestCredit && (
450
+ <TouchableOpacity
451
+ style={[styles.creditRow, { backgroundColor: useCredit ? '#22C55E18' : t.surface, borderColor: useCredit ? '#22C55E' : t.border }]}
452
+ activeOpacity={0.8}
453
+ onPress={() => setUseCredit(v => !v)}
454
+ >
455
+ <View style={styles.creditRowText}>
456
+ <Text style={[styles.creditTitle, { color: useCredit ? '#22C55E' : t.text }]}>
457
+ 🎁 Use my {formatSol(oldestCredit.amountSOL)} SOL credit
458
+ </Text>
459
+ <Text style={[styles.creditSub, { color: t.textMuted }]}>
460
+ {useCredit
461
+ ? 'Treasury covers this bet — wager locked to credit amount'
462
+ : `${credits.length} credit${credits.length === 1 ? '' : 's'} available from your streak`}
463
+ </Text>
464
+ </View>
465
+ <View style={[styles.creditCheckbox, useCredit && { backgroundColor: '#22C55E', borderColor: '#22C55E' }]}>
466
+ {useCredit && <Text style={styles.creditCheck}>✓</Text>}
467
+ </View>
468
+ </TouchableOpacity>
469
+ )}
470
+
471
+ {/* SOL Slider — hidden when sponsoring with a credit */}
472
+ {selectedTeam && !isPoolModeEnabled && !alreadyJoined && !useCredit && (
435
473
  <SolSlider
436
474
  value={wager}
437
475
  min={game.buyIn}
@@ -804,4 +842,39 @@ const styles = StyleSheet.create({
804
842
  alignItems: 'center',
805
843
  gap: 10,
806
844
  },
845
+ creditRow: {
846
+ flexDirection: 'row',
847
+ alignItems: 'center',
848
+ gap: 12,
849
+ marginTop: 12,
850
+ padding: 12,
851
+ borderRadius: 12,
852
+ borderWidth: 1.5,
853
+ },
854
+ creditRowText: {
855
+ flex: 1,
856
+ gap: 2,
857
+ },
858
+ creditTitle: {
859
+ fontSize: 14,
860
+ fontWeight: '700',
861
+ },
862
+ creditSub: {
863
+ fontSize: 12,
864
+ fontWeight: '500',
865
+ },
866
+ creditCheckbox: {
867
+ width: 22,
868
+ height: 22,
869
+ borderRadius: 11,
870
+ borderWidth: 2,
871
+ borderColor: '#3A3A3C',
872
+ alignItems: 'center',
873
+ justifyContent: 'center',
874
+ },
875
+ creditCheck: {
876
+ color: '#FFFFFF',
877
+ fontSize: 14,
878
+ fontWeight: '900',
879
+ },
807
880
  });