@dubsdotapp/expo 0.5.33 → 0.5.34
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 +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +62 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +31 -0
- package/src/hooks/useCreateGame.ts +10 -3
- package/src/types.ts +13 -0
- package/src/ui/game/CreateGameSheet.tsx +49 -4
- package/src/ui/game/JoinGameSheet.tsx +6 -1
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -200,6 +200,37 @@ export class DubsClient {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
async createGame(params: CreateGameParams): Promise<CreateGameResult> {
|
|
203
|
+
// Sponsored path: spend an active streak credit instead of paying
|
|
204
|
+
// rent + buy-in. Server picks oldest FIFO credit, returns the
|
|
205
|
+
// partial-signed compound tx + promoCode so confirmGame can mark
|
|
206
|
+
// the credit as used after the on-chain confirm.
|
|
207
|
+
if (params.useCredit) {
|
|
208
|
+
const res = await this.request<{
|
|
209
|
+
success: true;
|
|
210
|
+
gameId: string;
|
|
211
|
+
gameAddress: string;
|
|
212
|
+
transaction: string;
|
|
213
|
+
lockTimestamp: number;
|
|
214
|
+
event: UnifiedEvent;
|
|
215
|
+
promoCode: string;
|
|
216
|
+
sponsorWallet?: string;
|
|
217
|
+
wagerAmount: number;
|
|
218
|
+
}>('POST', '/games/create-sponsored', {
|
|
219
|
+
id: params.id,
|
|
220
|
+
teamChoice: params.teamChoice,
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
gameId: res.gameId,
|
|
224
|
+
gameAddress: res.gameAddress,
|
|
225
|
+
transaction: res.transaction,
|
|
226
|
+
lockTimestamp: res.lockTimestamp,
|
|
227
|
+
event: res.event,
|
|
228
|
+
promoCode: res.promoCode,
|
|
229
|
+
sponsorWallet: res.sponsorWallet,
|
|
230
|
+
wagerAmount: res.wagerAmount,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
203
234
|
const res = await this.request<{ success: true } & CreateGameResult>(
|
|
204
235
|
'POST',
|
|
205
236
|
'/games/create',
|
|
@@ -58,14 +58,21 @@ export function useCreateGame() {
|
|
|
58
58
|
// 3. Confirm with backend (server handles on-chain verification)
|
|
59
59
|
setStatus('confirming');
|
|
60
60
|
console.log('[useCreateGame] Step 3: Confirming with backend...');
|
|
61
|
+
// For sponsored creates, the server forces wager to the credit
|
|
62
|
+
// amount — use that, not whatever was in params, so the saved
|
|
63
|
+
// game record is consistent with the on-chain bet.
|
|
64
|
+
const wagerAmount = createResult.wagerAmount ?? params.wagerAmount;
|
|
61
65
|
const confirmResult = await client.confirmGame({
|
|
62
66
|
gameId: createResult.gameId,
|
|
63
67
|
playerWallet: params.playerWallet,
|
|
64
68
|
signature,
|
|
65
69
|
teamChoice: params.teamChoice,
|
|
66
|
-
wagerAmount
|
|
70
|
+
wagerAmount,
|
|
67
71
|
role: 'creator',
|
|
68
72
|
gameAddress: createResult.gameAddress,
|
|
73
|
+
// Echo the credit code through so the server marks it as used
|
|
74
|
+
// after the on-chain confirm succeeds.
|
|
75
|
+
promoCode: createResult.promoCode,
|
|
69
76
|
});
|
|
70
77
|
console.log('[useCreateGame] Step 3 done.');
|
|
71
78
|
|
|
@@ -94,14 +101,14 @@ export function useCreateGame() {
|
|
|
94
101
|
params.teamChoice === 'away' ? teamNickname(away) :
|
|
95
102
|
'Draw';
|
|
96
103
|
|
|
97
|
-
const message = `I just placed a ${
|
|
104
|
+
const message = `I just placed a ${wagerAmount} SOL bet on ${teamLabel} - Join me!`;
|
|
98
105
|
const gameInvite = {
|
|
99
106
|
gameId: createResult.gameId,
|
|
100
107
|
gameAddress: createResult.gameAddress,
|
|
101
108
|
title: event?.title,
|
|
102
109
|
league: event?.league,
|
|
103
110
|
gameType: 'sports',
|
|
104
|
-
buyIn:
|
|
111
|
+
buyIn: wagerAmount,
|
|
105
112
|
status: 'waiting',
|
|
106
113
|
homeTeam: home,
|
|
107
114
|
awayTeam: away,
|
package/src/types.ts
CHANGED
|
@@ -95,6 +95,14 @@ export interface CreateGameParams {
|
|
|
95
95
|
playerWallet: string;
|
|
96
96
|
teamChoice: 'home' | 'away' | 'draw';
|
|
97
97
|
wagerAmount: number;
|
|
98
|
+
/**
|
|
99
|
+
* When true, fund create+join with one of the user's active 0.01 SOL
|
|
100
|
+
* streak credits instead of paying rent + buy-in from their own
|
|
101
|
+
* wallet. SDK picks the oldest credit (FIFO). Treasury covers rent
|
|
102
|
+
* + buy-in; player only signs to consent. Buy-in is forced to the
|
|
103
|
+
* credit amount.
|
|
104
|
+
*/
|
|
105
|
+
useCredit?: boolean;
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
export interface CreateGameResult {
|
|
@@ -103,6 +111,11 @@ export interface CreateGameResult {
|
|
|
103
111
|
transaction: string;
|
|
104
112
|
lockTimestamp: number;
|
|
105
113
|
event: UnifiedEvent;
|
|
114
|
+
/** Set when sponsored — echoed back to confirmGame for mark-as-used. */
|
|
115
|
+
promoCode?: string;
|
|
116
|
+
sponsorWallet?: string;
|
|
117
|
+
/** Server-confirmed wager amount (== credit amount when sponsored). */
|
|
118
|
+
wagerAmount?: number;
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
// ── Custom Game (game_mode=6) ──
|
|
@@ -15,6 +15,7 @@ import { useDubsTheme } from '../theme';
|
|
|
15
15
|
import { useDubs } from '../../provider';
|
|
16
16
|
import { useCreateGame } from '../../hooks/useCreateGame';
|
|
17
17
|
import type { CreateGameMutationResult } from '../../hooks/useCreateGame';
|
|
18
|
+
import { useCredits } from '../../hooks/useCredits';
|
|
18
19
|
import type { UnifiedEvent } from '../../types';
|
|
19
20
|
import { SolSlider } from './SolSlider';
|
|
20
21
|
import { TeamButton } from './TeamButton';
|
|
@@ -74,10 +75,19 @@ export function CreateGameSheet({
|
|
|
74
75
|
const t = useDubsTheme();
|
|
75
76
|
const { wallet } = useDubs();
|
|
76
77
|
const mutation = useCreateGame();
|
|
78
|
+
const { credits, refetch: refetchCredits } = useCredits();
|
|
77
79
|
|
|
78
80
|
const [selectedTeam, setSelectedTeam] = useState<'home' | 'away' | null>(null);
|
|
79
81
|
const [wager, setWager] = useState(0.01);
|
|
80
82
|
const [showSuccess, setShowSuccess] = useState(false);
|
|
83
|
+
const [useCredit, setUseCredit] = useState(false);
|
|
84
|
+
|
|
85
|
+
const oldestCredit = credits.length > 0 ? credits[0] : null;
|
|
86
|
+
// Compare in lamports to avoid float drift between credit.amountSOL and
|
|
87
|
+
// the slider's wager value. The on-chain instruction requires exact
|
|
88
|
+
// match anyway — if either side has a sub-lamport rounding error we'd
|
|
89
|
+
// get a confusing on-chain failure instead of a clean toggle gate.
|
|
90
|
+
const canUseCredit = !!oldestCredit; // Any 0.01 SOL credit covers the 0.01 SOL minimum buy-in
|
|
81
91
|
|
|
82
92
|
const overlayOpacity = useRef(new Animated.Value(0)).current;
|
|
83
93
|
const successScale = useRef(new Animated.Value(0)).current;
|
|
@@ -144,12 +154,17 @@ export function CreateGameSheet({
|
|
|
144
154
|
id: event.id,
|
|
145
155
|
playerWallet: wallet.publicKey.toBase58(),
|
|
146
156
|
teamChoice: selectedTeam,
|
|
147
|
-
|
|
157
|
+
// When sponsoring, the on-chain instruction forces buy-in to
|
|
158
|
+
// the credit's amount; pass that so client-side wager state
|
|
159
|
+
// matches what gets recorded.
|
|
160
|
+
wagerAmount: useCredit && oldestCredit ? oldestCredit.amountSOL : wager,
|
|
161
|
+
useCredit: useCredit && canUseCredit ? true : undefined,
|
|
148
162
|
});
|
|
163
|
+
if (useCredit) refetchCredits();
|
|
149
164
|
} catch {
|
|
150
165
|
// Error captured in mutation state
|
|
151
166
|
}
|
|
152
|
-
}, [selectedTeam, wallet.publicKey, mutation.execute, event.id, wager]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
167
|
+
}, [selectedTeam, wallet.publicKey, mutation.execute, event.id, wager, useCredit, oldestCredit, canUseCredit, refetchCredits]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
153
168
|
|
|
154
169
|
const statusLabel = STATUS_LABELS[mutation.status] || '';
|
|
155
170
|
|
|
@@ -239,8 +254,31 @@ export function CreateGameSheet({
|
|
|
239
254
|
</View>
|
|
240
255
|
</View>
|
|
241
256
|
|
|
242
|
-
{/*
|
|
243
|
-
{selectedTeam && (
|
|
257
|
+
{/* Credit toggle — only when user has at least one credit */}
|
|
258
|
+
{selectedTeam && canUseCredit && oldestCredit && (
|
|
259
|
+
<TouchableOpacity
|
|
260
|
+
style={[styles.creditRow, { backgroundColor: useCredit ? '#22C55E18' : t.surface, borderColor: useCredit ? '#22C55E' : t.border }]}
|
|
261
|
+
activeOpacity={0.8}
|
|
262
|
+
onPress={() => setUseCredit(v => !v)}
|
|
263
|
+
>
|
|
264
|
+
<View style={styles.creditRowText}>
|
|
265
|
+
<Text style={[styles.creditTitle, { color: useCredit ? '#22C55E' : t.text }]}>
|
|
266
|
+
🎁 Use my {formatSol(oldestCredit.amountSOL)} SOL credit
|
|
267
|
+
</Text>
|
|
268
|
+
<Text style={[styles.creditSub, { color: t.textMuted }]}>
|
|
269
|
+
{useCredit
|
|
270
|
+
? 'Treasury covers rent + buy-in'
|
|
271
|
+
: `${credits.length} credit${credits.length === 1 ? '' : 's'} from your streak`}
|
|
272
|
+
</Text>
|
|
273
|
+
</View>
|
|
274
|
+
<View style={[styles.creditCheckbox, useCredit && { backgroundColor: '#22C55E', borderColor: '#22C55E' }]}>
|
|
275
|
+
{useCredit && <Text style={styles.creditCheck}>✓</Text>}
|
|
276
|
+
</View>
|
|
277
|
+
</TouchableOpacity>
|
|
278
|
+
)}
|
|
279
|
+
|
|
280
|
+
{/* SOL Slider — hidden when sponsoring (wager locked to credit) */}
|
|
281
|
+
{selectedTeam && !useCredit && (
|
|
244
282
|
<SolSlider
|
|
245
283
|
value={wager}
|
|
246
284
|
min={0.01}
|
|
@@ -318,6 +356,13 @@ const styles = StyleSheet.create({
|
|
|
318
356
|
ctaText: { color: '#FFFFFF', fontSize: 16, fontWeight: '700' },
|
|
319
357
|
ctaLoading: { flexDirection: 'row', alignItems: 'center', gap: 10 },
|
|
320
358
|
|
|
359
|
+
creditRow: { flexDirection: 'row', alignItems: 'center', gap: 12, marginTop: 12, padding: 12, borderRadius: 12, borderWidth: 1.5 },
|
|
360
|
+
creditRowText: { flex: 1, gap: 2 },
|
|
361
|
+
creditTitle: { fontSize: 14, fontWeight: '700' },
|
|
362
|
+
creditSub: { fontSize: 12, fontWeight: '500' },
|
|
363
|
+
creditCheckbox: { width: 22, height: 22, borderRadius: 11, borderWidth: 2, borderColor: '#3A3A3C', alignItems: 'center', justifyContent: 'center' },
|
|
364
|
+
creditCheck: { color: '#FFFFFF', fontSize: 14, fontWeight: '900' },
|
|
365
|
+
|
|
321
366
|
successOverlay: { ...StyleSheet.absoluteFillObject, zIndex: 100, alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(0,0,0,0.85)' },
|
|
322
367
|
successContent: { alignItems: 'center', gap: 12 },
|
|
323
368
|
successEmoji: { fontSize: 64 },
|
|
@@ -97,9 +97,14 @@ export function JoinGameSheet({
|
|
|
97
97
|
// least one active credit AND the credit's amount covers buy-in. The
|
|
98
98
|
// sponsored on-chain instruction transfers exactly the credit amount,
|
|
99
99
|
// so when this is on we lock wager to the credit value.
|
|
100
|
+
//
|
|
101
|
+
// Compare in lamports to dodge float drift between credit.amountSOL
|
|
102
|
+
// (server-rounded to 5dp) and game.buyIn — a 0.01 SOL credit must
|
|
103
|
+
// visibly cover a 0.01 SOL buy-in, which `>=` on raw floats can miss.
|
|
100
104
|
const [useCredit, setUseCredit] = useState(false);
|
|
101
105
|
const oldestCredit = credits.length > 0 ? credits[0] : null;
|
|
102
|
-
const canUseCredit = !!oldestCredit
|
|
106
|
+
const canUseCredit = !!oldestCredit
|
|
107
|
+
&& Math.round(oldestCredit.amountSOL * 1e9) >= Math.round(game.buyIn * 1e9);
|
|
103
108
|
|
|
104
109
|
const overlayOpacity = useRef(new Animated.Value(0)).current;
|
|
105
110
|
const successScale = useRef(new Animated.Value(0)).current;
|