@b3dotfun/sdk 0.0.1-alpha.9 → 0.0.3-alpha.1

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.
Files changed (75) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.d.ts +2 -1
  2. package/dist/cjs/anyspend/react/components/AnySpend.js +4 -4
  3. package/dist/cjs/anyspend/react/components/AnySpendBuySpin.d.ts +2 -1
  4. package/dist/cjs/anyspend/react/components/AnySpendBuySpin.js +124 -53
  5. package/dist/cjs/global-account/react/components/B3DynamicModal.js +2 -2
  6. package/dist/cjs/global-account/react/components/{B3Provider.d.ts → B3Provider/B3Provider.d.ts} +3 -29
  7. package/dist/cjs/global-account/react/components/{B3Provider.js → B3Provider/B3Provider.js} +6 -34
  8. package/dist/{esm/global-account/react/components → cjs/global-account/react/components/B3Provider}/B3Provider.native.d.ts +2 -25
  9. package/dist/cjs/global-account/react/components/{B3Provider.native.js → B3Provider/B3Provider.native.js} +5 -28
  10. package/dist/cjs/global-account/react/components/B3Provider/types.d.ts +25 -0
  11. package/dist/cjs/global-account/react/components/B3Provider/types.js +20 -0
  12. package/dist/cjs/global-account/react/components/B3Provider/useB3.d.ts +5 -0
  13. package/dist/cjs/global-account/react/components/B3Provider/useB3.js +17 -0
  14. package/dist/cjs/global-account/react/components/StyleRoot.js +2 -2
  15. package/dist/cjs/global-account/react/components/index.d.ts +8 -6
  16. package/dist/cjs/global-account/react/components/index.js +18 -16
  17. package/dist/cjs/global-account/react/index.native.d.ts +5 -4
  18. package/dist/cjs/global-account/react/index.native.js +10 -8
  19. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +2 -0
  20. package/dist/cjs/global-account/types/chain-networks.d.ts +34 -34
  21. package/dist/cjs/global-account/types/feature-flags.d.ts +5 -5
  22. package/dist/cjs/shared/constants/chains/b3Chain.d.ts +1 -1
  23. package/dist/cjs/shared/constants/chains/supported.d.ts +4 -3
  24. package/dist/cjs/shared/constants/chains/supported.js +3 -1
  25. package/dist/esm/anyspend/react/components/AnySpend.d.ts +2 -1
  26. package/dist/esm/anyspend/react/components/AnySpend.js +4 -4
  27. package/dist/esm/anyspend/react/components/AnySpendBuySpin.d.ts +2 -1
  28. package/dist/esm/anyspend/react/components/AnySpendBuySpin.js +124 -53
  29. package/dist/esm/global-account/react/components/B3DynamicModal.js +1 -1
  30. package/dist/esm/global-account/react/components/{B3Provider.d.ts → B3Provider/B3Provider.d.ts} +3 -29
  31. package/dist/esm/global-account/react/components/{B3Provider.js → B3Provider/B3Provider.js} +5 -32
  32. package/dist/{cjs/global-account/react/components → esm/global-account/react/components/B3Provider}/B3Provider.native.d.ts +2 -25
  33. package/dist/esm/global-account/react/components/{B3Provider.native.js → B3Provider/B3Provider.native.js} +5 -26
  34. package/dist/esm/global-account/react/components/B3Provider/types.d.ts +25 -0
  35. package/dist/esm/global-account/react/components/B3Provider/types.js +17 -0
  36. package/dist/esm/global-account/react/components/B3Provider/useB3.d.ts +5 -0
  37. package/dist/esm/global-account/react/components/B3Provider/useB3.js +14 -0
  38. package/dist/esm/global-account/react/components/StyleRoot.js +1 -1
  39. package/dist/esm/global-account/react/components/index.d.ts +8 -6
  40. package/dist/esm/global-account/react/components/index.js +7 -5
  41. package/dist/esm/global-account/react/index.native.d.ts +5 -4
  42. package/dist/esm/global-account/react/index.native.js +5 -4
  43. package/dist/esm/global-account/react/stores/useModalStore.d.ts +2 -0
  44. package/dist/esm/global-account/types/chain-networks.d.ts +34 -34
  45. package/dist/esm/global-account/types/feature-flags.d.ts +5 -5
  46. package/dist/esm/shared/constants/chains/b3Chain.d.ts +1 -1
  47. package/dist/esm/shared/constants/chains/supported.d.ts +4 -3
  48. package/dist/esm/shared/constants/chains/supported.js +2 -0
  49. package/dist/styles/index.css +1 -1
  50. package/dist/types/anyspend/react/components/AnySpend.d.ts +2 -1
  51. package/dist/types/anyspend/react/components/AnySpendBuySpin.d.ts +2 -1
  52. package/dist/types/global-account/react/components/{B3Provider.d.ts → B3Provider/B3Provider.d.ts} +2 -28
  53. package/dist/types/global-account/react/components/{B3Provider.native.d.ts → B3Provider/B3Provider.native.d.ts} +1 -24
  54. package/dist/types/global-account/react/components/B3Provider/types.d.ts +25 -0
  55. package/dist/types/global-account/react/components/B3Provider/useB3.d.ts +5 -0
  56. package/dist/types/global-account/react/components/index.d.ts +8 -6
  57. package/dist/types/global-account/react/index.native.d.ts +5 -4
  58. package/dist/types/global-account/react/stores/useModalStore.d.ts +2 -0
  59. package/dist/types/global-account/types/chain-networks.d.ts +34 -34
  60. package/dist/types/global-account/types/feature-flags.d.ts +5 -5
  61. package/dist/types/shared/constants/chains/b3Chain.d.ts +1 -1
  62. package/dist/types/shared/constants/chains/supported.d.ts +4 -3
  63. package/package.json +4 -3
  64. package/src/anyspend/react/components/AnySpend.tsx +6 -5
  65. package/src/anyspend/react/components/AnySpendBuySpin.tsx +233 -184
  66. package/src/global-account/react/components/B3DynamicModal.tsx +1 -1
  67. package/src/global-account/react/components/{B3Provider.native.tsx → B3Provider/B3Provider.native.tsx} +4 -45
  68. package/src/global-account/react/components/{B3Provider.tsx → B3Provider/B3Provider.tsx} +4 -53
  69. package/src/global-account/react/components/B3Provider/types.ts +40 -0
  70. package/src/global-account/react/components/B3Provider/useB3.ts +17 -0
  71. package/src/global-account/react/components/StyleRoot.tsx +1 -1
  72. package/src/global-account/react/components/index.ts +8 -6
  73. package/src/global-account/react/index.native.ts +5 -4
  74. package/src/global-account/react/stores/useModalStore.ts +2 -0
  75. package/src/shared/constants/chains/supported.ts +2 -0
@@ -1,4 +1,5 @@
1
1
  import { B3_TOKEN, OrderType } from "@b3dotfun/sdk/anyspend";
2
+ import { baseMainnet } from "@b3dotfun/sdk/shared/constants/chains/supported";
2
3
  import { EthIcon } from "./icons/EthIcon";
3
4
  import { SolIcon } from "./icons/SolIcon";
4
5
  import { UsdcIcon } from "./icons/USDCIcon";
@@ -19,7 +20,6 @@ import { ArrowRight, Loader2 } from "lucide-react";
19
20
  import { useCallback, useEffect, useState } from "react";
20
21
  import { toast } from "sonner";
21
22
  import { createPublicClient, encodeFunctionData, erc20Abi, formatUnits, http } from "viem";
22
- import { base } from "viem/chains";
23
23
  import { useAccount, useWaitForTransactionReceipt, useWriteContract } from "wagmi";
24
24
  import { AnySpendCustom } from "./AnySpendCustom";
25
25
 
@@ -52,6 +52,20 @@ const SPIN_WHEEL_ABI = [
52
52
  outputs: [],
53
53
  stateMutability: "payable",
54
54
  type: "function"
55
+ },
56
+ {
57
+ inputs: [],
58
+ name: "getWheelInfo",
59
+ outputs: [
60
+ { internalType: "address", name: "creator_", type: "address" },
61
+ { internalType: "uint256", name: "startTime_", type: "uint256" },
62
+ { internalType: "uint256", name: "endTime_", type: "uint256" },
63
+ { internalType: "uint256", name: "totalPrizesAvailable_", type: "uint256" },
64
+ { internalType: "uint256", name: "prizesRequestedCount_", type: "uint256" },
65
+ { internalType: "enum SpinWheelV2.WheelState", name: "state_", type: "uint8" }
66
+ ],
67
+ stateMutability: "view",
68
+ type: "function"
55
69
  }
56
70
  ] as const;
57
71
 
@@ -62,6 +76,37 @@ interface PaymentConfig {
62
76
  entryModule: string;
63
77
  }
64
78
 
79
+ interface WheelInfo {
80
+ creator_: string;
81
+ startTime_: bigint;
82
+ endTime_: bigint;
83
+ totalPrizesAvailable_: bigint;
84
+ prizesRequestedCount_: bigint;
85
+ state_: number;
86
+ }
87
+
88
+ type WheelStatus = "not_started" | "active" | "ended" | "sold_out";
89
+
90
+ function getWheelStatus(wheelInfo: WheelInfo): WheelStatus {
91
+ const now = BigInt(Math.floor(Date.now() / 1000));
92
+ console.log("@@anyspend-buy-spin:now:", now);
93
+ console.log("@@anyspend-buy-spin:wheelInfo:", wheelInfo);
94
+
95
+ if (now < wheelInfo.startTime_) {
96
+ return "not_started";
97
+ }
98
+
99
+ if (now > wheelInfo.endTime_) {
100
+ return "ended";
101
+ }
102
+
103
+ if (wheelInfo.totalPrizesAvailable_ <= wheelInfo.prizesRequestedCount_) {
104
+ return "sold_out";
105
+ }
106
+
107
+ return "active";
108
+ }
109
+
65
110
  function generateEncodedDataForBuyEntriesAndSpin(user: string, quantity: string): string {
66
111
  invariant(BigInt(quantity) > 0, "Quantity must be greater than zero");
67
112
  console.log("@@anyspend-buy-spin:encoded-data:", { user, quantity });
@@ -74,7 +119,7 @@ function generateEncodedDataForBuyEntriesAndSpin(user: string, quantity: string)
74
119
  }
75
120
 
76
121
  const basePublicClient = createPublicClient({
77
- chain: base,
122
+ chain: baseMainnet,
78
123
  transport: http()
79
124
  });
80
125
 
@@ -85,6 +130,7 @@ export function AnySpendBuySpin({
85
130
  spinwheelContractAddress,
86
131
  chainId,
87
132
  recipientAddress,
133
+ prefillQuantity,
88
134
  onSuccess
89
135
  }: {
90
136
  isMainnet?: boolean;
@@ -93,6 +139,7 @@ export function AnySpendBuySpin({
93
139
  spinwheelContractAddress: string;
94
140
  chainId: number;
95
141
  recipientAddress: string;
142
+ prefillQuantity?: string;
96
143
  onSuccess?: (txHash?: string) => void;
97
144
  }) {
98
145
  const hasMounted = useHasMounted();
@@ -102,6 +149,7 @@ export function AnySpendBuySpin({
102
149
  const [paymentConfig, setPaymentConfig] = useState<PaymentConfig | null>(null);
103
150
  const [isLoadingConfig, setIsLoadingConfig] = useState(true);
104
151
  const [configError, setConfigError] = useState<string>("");
152
+ const [wheelInfo, setWheelInfo] = useState<WheelInfo | null>(null);
105
153
 
106
154
  // Fetch B3 token balance
107
155
  const {
@@ -120,24 +168,34 @@ export function AnySpendBuySpin({
120
168
  // State for direct buying flow (when user has B3 tokens)
121
169
  const [isBuying, setIsBuying] = useState(false);
122
170
  const [buyingTxHash, setBuyingTxHash] = useState<string>("");
123
- const [showSuccessModal, setShowSuccessModal] = useState(false);
124
-
125
- // Wait for transaction confirmation
126
- const { isLoading: isTxPending, isSuccess: isTxSuccess } = useWaitForTransactionReceipt({
171
+ const {
172
+ isLoading: isTxPending,
173
+ isSuccess: isTxSuccess,
174
+ isError: isTxError,
175
+ error: txError
176
+ } = useWaitForTransactionReceipt({
127
177
  hash: buyingTxHash as `0x${string}`,
128
178
  query: {
129
179
  structuralSharing: false
130
180
  }
131
181
  });
132
182
 
133
- // Show success modal when transaction is confirmed
183
+ // Handle transaction status
134
184
  useEffect(() => {
135
- if (isTxSuccess && buyingTxHash) {
136
- setShowAmountPrompt(false);
137
- setShowSuccessModal(true);
185
+ if (!buyingTxHash) return;
186
+
187
+ if (isTxSuccess) {
188
+ setB3ModalOpen(false);
189
+ onSuccess?.(buyingTxHash);
190
+ toast.success("Spin purchase transaction confirmed!");
191
+ setIsBuying(false);
192
+ } else if (isTxError) {
193
+ console.error("@@anyspend-buy-spin:tx-error:", txError);
194
+ toast.error("Transaction failed. Please try again.");
195
+ setB3ModalOpen(false);
138
196
  setIsBuying(false);
139
197
  }
140
- }, [isTxSuccess, buyingTxHash]);
198
+ }, [isTxSuccess, isTxError, buyingTxHash, onSuccess, setB3ModalOpen, txError]);
141
199
 
142
200
  // Spin quantity state
143
201
  const [userSpinQuantity, setUserSpinQuantity] = useState<string>("");
@@ -146,12 +204,21 @@ export function AnySpendBuySpin({
146
204
  const [validationError, setValidationError] = useState<string>("");
147
205
  const [displayQuantity, setDisplayQuantity] = useState<string>("");
148
206
  const [debouncedQuantity, setDebouncedQuantity] = useState<string>("");
207
+ const [debouncedUserSpinQuantity, setDebouncedUserSpinQuantity] = useState<string>("");
208
+
209
+ useEffect(() => {
210
+ if (prefillQuantity && wheelInfo) {
211
+ const remainingSpins = wheelInfo.totalPrizesAvailable_ - wheelInfo.prizesRequestedCount_;
212
+ const adjustedQuantity = BigInt(prefillQuantity) > remainingSpins ? remainingSpins.toString() : prefillQuantity;
213
+ validateAndSetQuantity(adjustedQuantity);
214
+ }
215
+ }, [prefillQuantity, wheelInfo]);
149
216
 
150
217
  // Calculate total cost
151
218
  const totalCost =
152
219
  paymentConfig && userSpinQuantity ? paymentConfig.pricePerEntry * BigInt(userSpinQuantity) : BigInt(0);
153
220
 
154
- // Fetch payment configuration
221
+ // Fetch payment configuration and wheel info
155
222
  const fetchPaymentConfig = useCallback(async () => {
156
223
  if (!basePublicClient || !spinwheelContractAddress) return;
157
224
 
@@ -161,7 +228,7 @@ export function AnySpendBuySpin({
161
228
 
162
229
  console.log("@@anyspend-buy-spin:fetch-config:", { spinwheelContractAddress, chainId });
163
230
 
164
- const [config, entryModuleAddress] = await Promise.all([
231
+ const [config, entryModuleAddress, wheelInfo] = await Promise.all([
165
232
  basePublicClient.readContract({
166
233
  address: spinwheelContractAddress as `0x${string}`,
167
234
  abi: SPIN_WHEEL_ABI,
@@ -171,6 +238,11 @@ export function AnySpendBuySpin({
171
238
  address: spinwheelContractAddress as `0x${string}`,
172
239
  abi: SPIN_WHEEL_ABI,
173
240
  functionName: "entryModule"
241
+ }),
242
+ basePublicClient.readContract({
243
+ address: spinwheelContractAddress as `0x${string}`,
244
+ abi: SPIN_WHEEL_ABI,
245
+ functionName: "getWheelInfo"
174
246
  })
175
247
  ]);
176
248
 
@@ -181,13 +253,17 @@ export function AnySpendBuySpin({
181
253
  entryModule: entryModuleAddress
182
254
  };
183
255
 
184
- console.log("@@anyspend-buy-spin:config-fetched:", {
185
- pricePerEntry: paymentConfig.pricePerEntry.toString(),
186
- maxEntriesPerUser: paymentConfig.maxEntriesPerUser.toString(),
187
- paymentRecipient: paymentConfig.paymentRecipient,
188
- entryModule: paymentConfig.entryModule
189
- });
256
+ const wheelInfoData: WheelInfo = {
257
+ creator_: wheelInfo[0],
258
+ startTime_: wheelInfo[1],
259
+ endTime_: wheelInfo[2],
260
+ totalPrizesAvailable_: wheelInfo[3],
261
+ prizesRequestedCount_: wheelInfo[4],
262
+ state_: wheelInfo[5]
263
+ };
264
+
190
265
  setPaymentConfig(paymentConfig);
266
+ setWheelInfo(wheelInfoData);
191
267
  } catch (error) {
192
268
  console.error("@@anyspend-buy-spin:config-error:", error);
193
269
  setConfigError("Failed to load spin wheel configuration");
@@ -206,6 +282,7 @@ export function AnySpendBuySpin({
206
282
  useEffect(() => {
207
283
  const timer = setTimeout(() => {
208
284
  setDebouncedQuantity(displayQuantity);
285
+ setDebouncedUserSpinQuantity(userSpinQuantity);
209
286
  }, 500);
210
287
 
211
288
  return () => clearTimeout(timer);
@@ -238,17 +315,23 @@ export function AnySpendBuySpin({
238
315
  }
239
316
 
240
317
  // Check maximum entries per user (0 means no limit)
241
- if (
242
- paymentConfig &&
243
- paymentConfig.maxEntriesPerUser > BigInt(0) &&
244
- BigInt(numValue) > paymentConfig.maxEntriesPerUser
245
- ) {
318
+ if (paymentConfig && paymentConfig.maxEntriesPerUser > 0n && BigInt(numValue) > paymentConfig.maxEntriesPerUser) {
246
319
  setIsQuantityValid(false);
247
320
  setUserSpinQuantity("");
248
321
  setValidationError(`Maximum ${paymentConfig.maxEntriesPerUser.toString()} spins allowed`);
249
322
  return;
250
323
  }
251
324
 
325
+ // Check if quantity exceeds remaining entries
326
+ if (wheelInfo && BigInt(numValue) > wheelInfo.totalPrizesAvailable_ - wheelInfo.prizesRequestedCount_) {
327
+ setIsQuantityValid(false);
328
+ setUserSpinQuantity("");
329
+ setValidationError(
330
+ `Only ${(wheelInfo.totalPrizesAvailable_ - wheelInfo.prizesRequestedCount_).toString()} spins remaining`
331
+ );
332
+ return;
333
+ }
334
+
252
335
  setUserSpinQuantity(value);
253
336
  setIsQuantityValid(true);
254
337
  setValidationError("");
@@ -303,7 +386,7 @@ export function AnySpendBuySpin({
303
386
  } catch (error) {
304
387
  console.error("@@anyspend-buy-spin:error:", error);
305
388
  toast.error("Spin purchase failed. Please try again.");
306
- setShowSuccessModal(false);
389
+ setB3ModalOpen(false);
307
390
  } finally {
308
391
  setIsBuying(false);
309
392
  }
@@ -382,6 +465,40 @@ export function AnySpendBuySpin({
382
465
  // Render quantity input prompt
383
466
  if (showAmountPrompt) {
384
467
  const pricePerEntry = formatUnits(paymentConfig.pricePerEntry, 18);
468
+ const remainingEntries = wheelInfo ? wheelInfo.totalPrizesAvailable_ - wheelInfo.prizesRequestedCount_ : 0n;
469
+ const wheelStatus = wheelInfo ? getWheelStatus(wheelInfo) : null;
470
+ const isSoldOut = wheelStatus === "sold_out";
471
+ const isActive = wheelStatus === "active";
472
+
473
+ const getStatusMessage = () => {
474
+ if (!wheelInfo) return null;
475
+
476
+ const formatDate = (timestamp: bigint) => {
477
+ return new Date(Number(timestamp) * 1000).toLocaleString();
478
+ };
479
+
480
+ switch (wheelStatus) {
481
+ case "not_started":
482
+ return {
483
+ title: "Spin Wheel Not Started",
484
+ message: `Starts at ${formatDate(wheelInfo.startTime_)}`
485
+ };
486
+ case "ended":
487
+ return {
488
+ title: "Spin Wheel Ended",
489
+ message: `Ended at ${formatDate(wheelInfo.endTime_)}`
490
+ };
491
+ case "sold_out":
492
+ return {
493
+ title: "All Spins Have Been Claimed",
494
+ message: "Stay tuned for the next spin wheel event!"
495
+ };
496
+ default:
497
+ return null;
498
+ }
499
+ };
500
+
501
+ const statusInfo = getStatusMessage();
385
502
 
386
503
  return (
387
504
  <StyleRoot>
@@ -395,7 +512,7 @@ export function AnySpendBuySpin({
395
512
  filter: hasMounted ? "blur(0px)" : "blur(10px)"
396
513
  }}
397
514
  transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
398
- className="mb-4 flex justify-center"
515
+ className={`flex justify-center ${isActive ? "mb-4" : ""}`}
399
516
  >
400
517
  <img
401
518
  alt="B3 Token"
@@ -417,15 +534,33 @@ export function AnySpendBuySpin({
417
534
  transition={{ duration: 0.3, delay: 0.1, ease: "easeInOut" }}
418
535
  className="text-center"
419
536
  >
420
- <h2 className="font-sf-rounded text-as-primary mb-2 text-2xl font-bold">
421
- {(() => {
422
- const hasEnoughBalance = b3RawBalance && totalCost <= b3RawBalance;
423
- return hasEnoughBalance || !debouncedQuantity ? "Buy Spins" : `Swap & Buy Spins`;
424
- })()}
425
- </h2>
426
- <div className="bg-as-on-surface-2/50 inline-flex items-center gap-2 rounded-full border border-white/10 px-3 py-1 backdrop-blur-sm">
427
- <p className="text-as-primary/80 text-sm">{pricePerEntry} $B3 per spin</p>
428
- </div>
537
+ {isActive ? (
538
+ <>
539
+ <h2 className="font-sf-rounded text-as-primary mb-4 text-2xl font-bold">
540
+ {(() => {
541
+ const hasEnoughBalance = b3RawBalance && totalCost <= b3RawBalance;
542
+ return hasEnoughBalance || !debouncedQuantity ? "Buy Spins" : `Swap & Buy Spins`;
543
+ })()}
544
+ </h2>
545
+ {wheelInfo && (
546
+ <div className="inline-flex items-center gap-2">
547
+ <div className="bg-as-brand/10 border-as-brand/10 inline-flex items-center rounded-full border px-3 py-1">
548
+ <p className="text-as-brand text-sm font-medium">{pricePerEntry} $B3 per spin</p>
549
+ </div>
550
+ <div className="bg-as-brand/10 border-as-brand/10 inline-flex items-center rounded-full border px-3 py-1">
551
+ <p className="text-as-brand text-sm font-medium">{remainingEntries.toString()} remaining</p>
552
+ </div>
553
+ </div>
554
+ )}
555
+ </>
556
+ ) : (
557
+ statusInfo && (
558
+ <div className="text-center">
559
+ <p className="text-as-primary text-lg font-semibold">{statusInfo.title}</p>
560
+ <p className="text-as-primary/70 mt-2 text-sm">{statusInfo.message}</p>
561
+ </div>
562
+ )
563
+ )}
429
564
  </motion.div>
430
565
  </div>
431
566
 
@@ -439,163 +574,77 @@ export function AnySpendBuySpin({
439
574
  transition={{ duration: 0.3, delay: 0.2, ease: "easeInOut" }}
440
575
  className="bg-b3-react-background w-full p-6"
441
576
  >
442
- <div className="space-y-4">
443
- <div className="flex items-center justify-between">
444
- <p className="text-as-primary/70 text-sm font-medium">Number of spins</p>
445
- <span className="text-as-primary/50 flex items-center gap-1 text-sm">
446
- Available: {isBalanceLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : `${b3Balance} B3`}
447
- </span>
448
- </div>
577
+ {isActive ? (
578
+ <div className="space-y-4">
579
+ <div className="flex items-center justify-between">
580
+ <p className="text-as-primary/70 text-sm font-medium">Number of spins</p>
581
+ <span className="text-as-primary/50 flex items-center gap-1 text-sm">
582
+ Available: {isBalanceLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : `${b3Balance} B3`}
583
+ </span>
584
+ </div>
449
585
 
450
- <div className="relative">
451
- <Input
452
- onFocus={onFocusQuantityInput}
453
- type="text"
454
- placeholder="1"
455
- value={displayQuantity}
456
- onChange={e => validateAndSetQuantity(e.target.value)}
457
- className={`h-14 px-4 pr-20 text-lg ${!isQuantityValid && displayQuantity ? "border-as-red" : "border-b3-react-border"}`}
458
- />
459
- <div className="font-pack absolute right-4 top-1/2 -translate-y-1/2 text-lg font-medium text-blue-500/70">
460
- {displayQuantity === "1" ? "Spin" : "Spins"}
586
+ <div className="relative">
587
+ <Input
588
+ onFocus={onFocusQuantityInput}
589
+ type="text"
590
+ placeholder="1"
591
+ value={displayQuantity}
592
+ onChange={e => validateAndSetQuantity(e.target.value)}
593
+ className={`h-14 px-4 pr-20 text-lg ${!isQuantityValid && displayQuantity ? "border-as-red" : "border-b3-react-border"}`}
594
+ />
595
+ <div className="font-pack absolute right-4 top-1/2 -translate-y-1/2 text-lg font-medium text-blue-500/70">
596
+ {displayQuantity === "1" ? "Spin" : "Spins"}
597
+ </div>
461
598
  </div>
462
- </div>
463
599
 
464
- {!isQuantityValid && displayQuantity && <p className="text-as-red text-sm">{validationError}</p>}
600
+ {!isQuantityValid && displayQuantity && <p className="text-as-red text-sm">{validationError}</p>}
465
601
 
466
- <div className="bg-as-on-surface-2/30 rounded-lg border border-white/10 p-4 backdrop-blur-sm">
467
- <div className="flex items-center justify-between">
468
- <span className="text-as-primary/70 text-sm font-medium">Total Cost:</span>
469
- <div className="flex items-center gap-2">
470
- <span className="text-as-primary text-lg font-bold">
471
- {displayQuantity && isQuantityValid ? formatUnits(totalCost, 18) : "0"} B3
472
- </span>
602
+ <div className="bg-as-on-surface-2/30 rounded-lg border border-white/10 p-4 backdrop-blur-sm">
603
+ <div className="flex items-center justify-between">
604
+ <span className="text-as-primary/70 text-sm font-medium">Total Cost:</span>
605
+ <div className="flex items-center gap-2">
606
+ <span className="text-as-primary text-lg font-bold">
607
+ {displayQuantity && isQuantityValid ? formatUnits(totalCost, 18) : "0"} B3
608
+ </span>
609
+ </div>
473
610
  </div>
474
611
  </div>
475
- </div>
476
- </div>
477
-
478
- <div className="mt-4">
479
- {(() => {
480
- const hasEnoughBalance = b3RawBalance && totalCost <= b3RawBalance;
481
-
482
- if (!hasEnoughBalance && debouncedQuantity) {
483
- return (
484
- <div className="bg-as-brand/10 flex flex-col items-center gap-2 rounded-lg p-4 pb-5">
485
- <div className="flex items-center justify-center gap-2">
486
- <span className="text-as-primary text-sm font-semibold">Swap & buy from any token</span>
487
- <TextLoop>
488
- <EthIcon className="h-8 w-8" />
489
- <SolIcon className="h-8 w-8" />
490
- <UsdcIcon className="h-8 w-8" />
491
- </TextLoop>
492
- <ArrowRight className="text-as-primary h-4 w-4" />
493
- <img src="https://cdn.b3.fun/b3-coin-3d.png" className="h-7 w-7" alt="B3 Token" />
494
- </div>
495
- <p className="text-as-primary/50 text-sm font-medium">
496
- No problem, we'll help you swap to B3 for your spins!
497
- </p>
498
- </div>
499
- );
500
- }
501
- })()}
502
- </div>
503
-
504
- <Button
505
- onClick={confirmQuantity}
506
- disabled={!isQuantityValid || !displayQuantity || isBuying || isTxPending}
507
- className="bg-as-brand hover:bg-as-brand/90 text-as-primary mt-4 h-14 w-full rounded-xl text-lg font-medium"
508
- >
509
- {isBuying ? "Buying..." : isTxPending ? "Confirming..." : "Continue"}
510
- </Button>
511
- </motion.div>
512
- </div>
513
- </StyleRoot>
514
- );
515
- }
516
612
 
517
- // Success Modal for Direct Buying
518
- if (showSuccessModal) {
519
- return (
520
- <StyleRoot>
521
- <div className="bg-b3-react-background flex w-full flex-col items-center">
522
- <div className="w-full p-4">
523
- <motion.div
524
- initial={false}
525
- animate={{
526
- opacity: hasMounted ? 1 : 0,
527
- y: hasMounted ? 0 : 20,
528
- filter: hasMounted ? "blur(0px)" : "blur(10px)"
529
- }}
530
- transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
531
- className="relative mx-auto mb-4 size-[120px]"
532
- >
533
- <div className="absolute inset-0 scale-95 rounded-[50%] bg-gradient-to-br from-green-500/30 to-blue-500/30 blur-xl"></div>
534
- <GlareCardRounded className="overflow-hidden rounded-full border-none bg-gradient-to-br from-green-500/10 to-blue-500/10 backdrop-blur-sm">
535
- <img
536
- alt="B3 Token"
537
- loading="lazy"
538
- width="120"
539
- height="120"
540
- decoding="async"
541
- data-nimg="1"
542
- className="size-full shrink-0 bg-transparent text-transparent"
543
- src="https://cdn.b3.fun/b3-coin-3d.png"
544
- />
545
- <div className="absolute inset-0 rounded-[50%] border border-white/20"></div>
546
- </GlareCardRounded>
547
- </motion.div>
548
- <motion.div
549
- initial={false}
550
- animate={{
551
- opacity: hasMounted ? 1 : 0,
552
- y: hasMounted ? 0 : 20,
553
- filter: hasMounted ? "blur(0px)" : "blur(10px)"
554
- }}
555
- transition={{ duration: 0.3, delay: 0.1, ease: "easeInOut" }}
556
- className="text-center"
557
- >
558
- <h2 className="font-sf-rounded mb-3 bg-gradient-to-r from-green-400 to-blue-500 bg-clip-text text-3xl font-bold text-transparent">
559
- 🎉 Purchase Complete!
560
- </h2>
561
- <div className="bg-as-on-surface-2/50 inline-flex items-center gap-2 rounded-full border border-white/10 px-4 py-2 backdrop-blur-sm">
562
- <span className="text-as-primary/80 text-sm font-medium">
563
- {userSpinQuantity} Spin{userSpinQuantity !== "1" ? "s" : ""} • {formatUnits(totalCost, 18)} B3
564
- </span>
565
- </div>
566
- </motion.div>
567
- </div>
613
+ <div className="mt-4">
614
+ {(() => {
615
+ const hasEnoughBalance = b3RawBalance && totalCost <= b3RawBalance;
616
+
617
+ if (!hasEnoughBalance && debouncedQuantity) {
618
+ return (
619
+ <div className="bg-as-brand/10 flex flex-col items-center gap-2 rounded-lg p-4 pb-5">
620
+ <div className="flex items-center justify-center gap-2">
621
+ <span className="text-as-primary text-sm font-semibold">Swap & buy from any token</span>
622
+ <TextLoop>
623
+ <EthIcon className="h-8 w-8" />
624
+ <SolIcon className="h-8 w-8" />
625
+ <UsdcIcon className="h-8 w-8" />
626
+ </TextLoop>
627
+ <ArrowRight className="text-as-primary h-4 w-4" />
628
+ <img src="https://cdn.b3.fun/b3-coin-3d.png" className="h-7 w-7" alt="B3 Token" />
629
+ </div>
630
+ <p className="text-as-primary/50 text-sm font-medium">
631
+ No problem, we'll help you swap to B3 for your spins!
632
+ </p>
633
+ </div>
634
+ );
635
+ }
636
+ })()}
637
+ </div>
568
638
 
569
- <motion.div
570
- initial={false}
571
- animate={{
572
- opacity: hasMounted ? 1 : 0,
573
- y: hasMounted ? 0 : 20,
574
- filter: hasMounted ? "blur(0px)" : "blur(10px)"
575
- }}
576
- transition={{ duration: 0.3, delay: 0.2, ease: "easeInOut" }}
577
- className="bg-b3-react-background w-full p-6"
578
- >
579
- <div className="mb-6">
580
- <a
581
- href={`https://basescan.org/tx/${buyingTxHash}`}
582
- target="_blank"
583
- rel="noopener noreferrer"
584
- className="text-as-primary/70 hover:text-as-primary block break-all text-center font-mono text-sm underline transition-colors"
585
- >
586
- View transaction
587
- </a>
588
- </div>
589
-
590
- <Button
591
- onClick={() => {
592
- setB3ModalOpen(false);
593
- onSuccess?.(buyingTxHash);
594
- }}
595
- className="bg-as-brand hover:bg-as-brand/90 text-as-primary h-14 w-full rounded-xl text-lg font-medium"
596
- >
597
- Done
598
- </Button>
639
+ <Button
640
+ onClick={confirmQuantity}
641
+ disabled={!isQuantityValid || !displayQuantity || isBuying || isTxPending}
642
+ className="bg-as-brand hover:bg-as-brand/90 text-as-primary mt-4 h-14 w-full rounded-xl text-lg font-medium"
643
+ >
644
+ {isBuying ? "Buying..." : isTxPending ? "Confirming..." : "Continue"}
645
+ </Button>
646
+ </div>
647
+ ) : null}
599
648
  </motion.div>
600
649
  </div>
601
650
  </StyleRoot>
@@ -8,7 +8,7 @@ import {
8
8
  } from "@b3dotfun/sdk/anyspend/react";
9
9
  import { useIsMobile, useModalStore } from "@b3dotfun/sdk/global-account/react";
10
10
  import { debugB3React } from "@b3dotfun/sdk/shared/utils/debug";
11
- import { useB3 } from "./B3Provider";
11
+ import { useB3 } from "./B3Provider/useB3";
12
12
  import { ManageAccount } from "./ManageAccount/ManageAccount";
13
13
  import { RequestPermissions } from "./RequestPermissions/RequestPermissions";
14
14
  import { SignInWithB3Flow } from "./SignInWithB3/SignInWithB3Flow";
@@ -6,6 +6,7 @@ import { Account } from "thirdweb/wallets";
6
6
  // import { RelayKitProviderWrapper } from "./RelayKitProviderWrapper";
7
7
 
8
8
  import { User } from "@b3dotfun/sdk/global-account/types/b3-api.types";
9
+ import { B3Context, B3ContextType } from "./types";
9
10
 
10
11
  /**
11
12
  * Default permissions configuration for B3 provider
@@ -17,51 +18,6 @@ const DEFAULT_PERMISSIONS = {
17
18
  endDate: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365) // 1 year from now
18
19
  };
19
20
 
20
- /**
21
- * Context type for B3Provider
22
- */
23
-
24
- export interface B3ContextType {
25
- account?: Account;
26
- user?: User;
27
- setAccount: (account: Account) => void;
28
- setUser: (user?: User) => void;
29
- initialized: boolean;
30
- ready: boolean;
31
- environment?: "development" | "production";
32
- defaultPermissions?: PermissionsConfig;
33
- theme: "light" | "dark";
34
- }
35
-
36
- /**
37
- * Context for B3 provider
38
- */
39
- export const B3Context = createContext<B3ContextType>({
40
- account: undefined,
41
- user: undefined,
42
- setAccount: () => {},
43
- setUser: () => {},
44
- initialized: false,
45
- ready: false,
46
- environment: "development",
47
- theme: "light"
48
- });
49
-
50
- /**
51
- * Hook to access the B3 context
52
- * @throws Error if used outside a B3Provider
53
- */
54
- export function useB3() {
55
- const context = useContext(B3Context);
56
-
57
- if (!context.initialized) {
58
- throw new Error("useB3 must be used within a B3Provider");
59
- }
60
-
61
- // Return a stable reference
62
- return useMemo(() => context, [context]);
63
- }
64
-
65
21
  // Create queryClient instance
66
22
  const queryClient = new QueryClient();
67
23
 
@@ -125,6 +81,9 @@ export function InnerProvider({
125
81
  <B3Context.Provider
126
82
  value={{
127
83
  account: effectiveAccount,
84
+ automaticallySetFirstEoa: false,
85
+ setWallet: () => {},
86
+ wallet: undefined,
128
87
  setAccount,
129
88
  user,
130
89
  setUser,