@b3dotfun/sdk 0.0.62-alpha.2 → 0.0.62-alpha.4

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 (129) hide show
  1. package/dist/cjs/anyspend/react/components/AnySpend.js +61 -23
  2. package/dist/cjs/anyspend/react/components/AnySpendCustom.js +3 -0
  3. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.d.ts +34 -0
  4. package/dist/cjs/anyspend/react/components/AnySpendCustomExactIn.js +276 -0
  5. package/dist/cjs/anyspend/react/components/AnySpendStakeB3ExactIn.d.ts +7 -0
  6. package/dist/cjs/anyspend/react/components/AnySpendStakeB3ExactIn.js +288 -0
  7. package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +9 -0
  8. package/dist/cjs/anyspend/react/components/AnySpendStakeUpsideExactIn.js +33 -0
  9. package/dist/cjs/anyspend/react/components/AnyspendDepositHype.js +4 -4
  10. package/dist/cjs/anyspend/react/components/common/CryptoPaySection.js +4 -6
  11. package/dist/cjs/anyspend/react/components/common/CryptoPaymentMethod.js +9 -17
  12. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  13. package/dist/cjs/anyspend/react/components/common/CryptoReceiveSection.js +11 -1
  14. package/dist/cjs/anyspend/react/components/common/OrderDetails.js +66 -147
  15. package/dist/cjs/anyspend/react/components/common/OrderDetailsCollapsible.js +2 -3
  16. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  17. package/dist/cjs/anyspend/react/components/common/OrderTokenAmount.js +39 -15
  18. package/dist/cjs/anyspend/react/components/common/PaySection.js +1 -1
  19. package/dist/cjs/anyspend/react/components/common/TokenBalance.js +1 -1
  20. package/dist/cjs/anyspend/react/components/index.d.ts +5 -1
  21. package/dist/cjs/anyspend/react/components/index.js +11 -3
  22. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.d.ts +24 -2
  23. package/dist/cjs/anyspend/react/hooks/useAnyspendFlow.js +41 -18
  24. package/dist/cjs/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +116 -0
  25. package/dist/cjs/anyspend/react/hooks/useAnyspendQuote.js +1 -1
  26. package/dist/cjs/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  27. package/dist/cjs/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.js +56 -0
  28. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  29. package/dist/cjs/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +73 -0
  30. package/dist/cjs/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  31. package/dist/cjs/anyspend/react/hooks/useConnectedWalletDisplay.js +57 -0
  32. package/dist/cjs/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  33. package/dist/cjs/anyspend/react/hooks/usePhantomTransfer.js +211 -0
  34. package/dist/cjs/anyspend/types/api.d.ts +665 -3
  35. package/dist/cjs/anyspend/utils/orderPayload.js +4 -0
  36. package/dist/cjs/global-account/react/components/B3DynamicModal.js +10 -1
  37. package/dist/cjs/global-account/react/hooks/index.d.ts +2 -1
  38. package/dist/cjs/global-account/react/hooks/index.js +5 -3
  39. package/dist/cjs/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  40. package/dist/cjs/global-account/react/hooks/useTokenBalanceDirect.js +62 -0
  41. package/dist/cjs/global-account/react/hooks/useTokenFromUrl.js +4 -3
  42. package/dist/cjs/global-account/react/stores/useModalStore.d.ts +31 -1
  43. package/dist/esm/anyspend/react/components/AnySpend.js +62 -24
  44. package/dist/esm/anyspend/react/components/AnySpendCustom.js +3 -0
  45. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.d.ts +34 -0
  46. package/dist/esm/anyspend/react/components/AnySpendCustomExactIn.js +270 -0
  47. package/dist/esm/anyspend/react/components/AnySpendStakeB3ExactIn.d.ts +7 -0
  48. package/dist/esm/anyspend/react/components/AnySpendStakeB3ExactIn.js +285 -0
  49. package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +9 -0
  50. package/dist/esm/anyspend/react/components/AnySpendStakeUpsideExactIn.js +30 -0
  51. package/dist/esm/anyspend/react/components/AnyspendDepositHype.js +4 -4
  52. package/dist/esm/anyspend/react/components/common/CryptoPaySection.js +5 -7
  53. package/dist/esm/anyspend/react/components/common/CryptoPaymentMethod.js +9 -17
  54. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  55. package/dist/esm/anyspend/react/components/common/CryptoReceiveSection.js +11 -1
  56. package/dist/esm/anyspend/react/components/common/OrderDetails.js +67 -148
  57. package/dist/esm/anyspend/react/components/common/OrderDetailsCollapsible.js +2 -3
  58. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  59. package/dist/esm/anyspend/react/components/common/OrderTokenAmount.js +40 -16
  60. package/dist/esm/anyspend/react/components/common/PaySection.js +1 -1
  61. package/dist/esm/anyspend/react/components/common/TokenBalance.js +2 -2
  62. package/dist/esm/anyspend/react/components/index.d.ts +5 -1
  63. package/dist/esm/anyspend/react/components/index.js +5 -1
  64. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.d.ts +24 -2
  65. package/dist/esm/anyspend/react/hooks/useAnyspendFlow.js +41 -18
  66. package/dist/esm/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +116 -0
  67. package/dist/esm/anyspend/react/hooks/useAnyspendQuote.js +1 -1
  68. package/dist/esm/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  69. package/dist/esm/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.js +53 -0
  70. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  71. package/dist/esm/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.js +70 -0
  72. package/dist/esm/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  73. package/dist/esm/anyspend/react/hooks/useConnectedWalletDisplay.js +54 -0
  74. package/dist/esm/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  75. package/dist/esm/anyspend/react/hooks/usePhantomTransfer.js +208 -0
  76. package/dist/esm/anyspend/types/api.d.ts +665 -3
  77. package/dist/esm/anyspend/utils/orderPayload.js +4 -0
  78. package/dist/esm/global-account/react/components/B3DynamicModal.js +11 -2
  79. package/dist/esm/global-account/react/hooks/index.d.ts +2 -1
  80. package/dist/esm/global-account/react/hooks/index.js +2 -1
  81. package/dist/esm/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  82. package/dist/esm/global-account/react/hooks/useTokenBalanceDirect.js +59 -0
  83. package/dist/esm/global-account/react/hooks/useTokenFromUrl.js +4 -3
  84. package/dist/esm/global-account/react/stores/useModalStore.d.ts +31 -1
  85. package/dist/types/anyspend/react/components/AnySpendCustomExactIn.d.ts +34 -0
  86. package/dist/types/anyspend/react/components/AnySpendStakeB3ExactIn.d.ts +7 -0
  87. package/dist/types/anyspend/react/components/AnySpendStakeUpsideExactIn.d.ts +9 -0
  88. package/dist/types/anyspend/react/components/common/CryptoReceiveSection.d.ts +6 -1
  89. package/dist/types/anyspend/react/components/common/OrderTokenAmount.d.ts +2 -1
  90. package/dist/types/anyspend/react/components/index.d.ts +5 -1
  91. package/dist/types/anyspend/react/hooks/useAnyspendFlow.d.ts +24 -2
  92. package/dist/types/anyspend/react/hooks/useAnyspendOrderHistory.d.ts +116 -0
  93. package/dist/types/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.d.ts +26 -0
  94. package/dist/types/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.d.ts +10 -0
  95. package/dist/types/anyspend/react/hooks/useConnectedWalletDisplay.d.ts +14 -0
  96. package/dist/types/anyspend/react/hooks/usePhantomTransfer.d.ts +36 -0
  97. package/dist/types/anyspend/types/api.d.ts +665 -3
  98. package/dist/types/global-account/react/hooks/index.d.ts +2 -1
  99. package/dist/types/global-account/react/hooks/useTokenBalanceDirect.d.ts +12 -0
  100. package/dist/types/global-account/react/stores/useModalStore.d.ts +31 -1
  101. package/package.json +1 -1
  102. package/src/anyspend/react/components/AnySpend.tsx +73 -22
  103. package/src/anyspend/react/components/AnySpendCustom.tsx +4 -0
  104. package/src/anyspend/react/components/AnySpendCustomExactIn.tsx +596 -0
  105. package/src/anyspend/react/components/AnySpendStakeB3ExactIn.tsx +516 -0
  106. package/src/anyspend/react/components/AnySpendStakeUpsideExactIn.tsx +67 -0
  107. package/src/anyspend/react/components/AnyspendDepositHype.tsx +7 -3
  108. package/src/anyspend/react/components/common/CryptoPaySection.tsx +5 -7
  109. package/src/anyspend/react/components/common/CryptoPaymentMethod.tsx +9 -18
  110. package/src/anyspend/react/components/common/CryptoReceiveSection.tsx +22 -0
  111. package/src/anyspend/react/components/common/OrderDetails.tsx +76 -190
  112. package/src/anyspend/react/components/common/OrderDetailsCollapsible.tsx +2 -3
  113. package/src/anyspend/react/components/common/OrderTokenAmount.tsx +48 -17
  114. package/src/anyspend/react/components/common/PaySection.tsx +1 -0
  115. package/src/anyspend/react/components/common/TokenBalance.tsx +2 -2
  116. package/src/anyspend/react/components/index.ts +5 -1
  117. package/src/anyspend/react/hooks/useAnyspendFlow.ts +49 -16
  118. package/src/anyspend/react/hooks/useAnyspendQuote.ts +1 -1
  119. package/src/anyspend/react/hooks/useAutoSelectCryptoPaymentMethod.ts +72 -0
  120. package/src/anyspend/react/hooks/useAutoSetActiveWalletFromWagmi.ts +80 -0
  121. package/src/anyspend/react/hooks/useConnectedWalletDisplay.ts +69 -0
  122. package/src/anyspend/react/hooks/usePhantomTransfer.ts +301 -0
  123. package/src/anyspend/types/api.ts +669 -1
  124. package/src/anyspend/utils/orderPayload.ts +5 -1
  125. package/src/global-account/react/components/B3DynamicModal.tsx +11 -1
  126. package/src/global-account/react/hooks/index.ts +2 -1
  127. package/src/global-account/react/hooks/useTokenBalanceDirect.tsx +84 -0
  128. package/src/global-account/react/hooks/useTokenFromUrl.tsx +6 -5
  129. package/src/global-account/react/stores/useModalStore.ts +34 -0
@@ -0,0 +1,516 @@
1
+ import { ABI_ERC20_STAKING, B3_TOKEN, eqci } from "@b3dotfun/sdk/anyspend";
2
+ import { normalizeAddress } from "@b3dotfun/sdk/anyspend/utils";
3
+ import {
4
+ Button,
5
+ GlareCardRounded,
6
+ Input,
7
+ StyleRoot,
8
+ TextLoop,
9
+ useHasMounted,
10
+ useModalStore,
11
+ useSimBalance,
12
+ useUnifiedChainSwitchAndExecute,
13
+ } from "@b3dotfun/sdk/global-account/react";
14
+ import { PUBLIC_BASE_RPC_URL } from "@b3dotfun/sdk/shared/constants";
15
+ import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
16
+ import { ArrowRight, Loader2 } from "lucide-react";
17
+ import { motion } from "motion/react";
18
+ import { useEffect, useState } from "react";
19
+ import { toast } from "sonner";
20
+ import { createPublicClient, encodeFunctionData, erc20Abi, http } from "viem";
21
+ import { base } from "viem/chains";
22
+ import { useAccount, useWaitForTransactionReceipt } from "wagmi";
23
+ import { AnySpendCustomExactIn } from "./AnySpendCustomExactIn";
24
+ import { EthIcon } from "./icons/EthIcon";
25
+ import { SolIcon } from "./icons/SolIcon";
26
+ import { UsdcIcon } from "./icons/USDCIcon";
27
+
28
+ const basePublicClient = createPublicClient({
29
+ chain: base,
30
+ transport: http(PUBLIC_BASE_RPC_URL),
31
+ });
32
+
33
+ const ERC20Staking = "0xbf04200be3cbf371467a539706393c81c470f523";
34
+
35
+ const STAKE_FUNCTION_ABI = JSON.stringify([
36
+ {
37
+ name: "stake",
38
+ type: "function",
39
+ stateMutability: "nonpayable",
40
+ inputs: [
41
+ { name: "amount", type: "uint256" },
42
+ { name: "beneficiary", type: "address" },
43
+ ],
44
+ outputs: [],
45
+ },
46
+ ]);
47
+
48
+ export function AnySpendStakeB3ExactIn({
49
+ loadOrder,
50
+ mode = "modal",
51
+ recipientAddress,
52
+ stakeAmount,
53
+ onSuccess,
54
+ }: {
55
+ loadOrder?: string;
56
+ mode?: "modal" | "page";
57
+ recipientAddress: string;
58
+ stakeAmount?: string;
59
+ onSuccess?: () => void;
60
+ }) {
61
+ const hasMounted = useHasMounted();
62
+ const { setB3ModalOpen } = useModalStore();
63
+
64
+ // Wagmi hooks for direct staking
65
+ const { address } = useAccount();
66
+ const { switchChainAndExecute, isSwitchingOrExecuting } = useUnifiedChainSwitchAndExecute();
67
+
68
+ // Fetch B3 token balance
69
+ const { data: simBalance, isLoading: isBalanceLoading } = useSimBalance(address, [base.id]);
70
+ const b3RawBalanceStr = simBalance?.balances.find(b => eqci(b.address, B3_TOKEN.address))?.amount || "0";
71
+ const b3RawBalance = BigInt(b3RawBalanceStr);
72
+ const b3Balance = formatTokenAmount(b3RawBalance, B3_TOKEN.decimals);
73
+
74
+ // State for direct staking flow
75
+ const [isStaking, setIsStaking] = useState(false);
76
+ const [stakingTxHash, setStakingTxHash] = useState<string>("");
77
+ const [showSuccessModal, setShowSuccessModal] = useState(false);
78
+
79
+ // Wait for transaction confirmation
80
+ const { isLoading: isTxPending, isSuccess: isTxSuccess } = useWaitForTransactionReceipt({
81
+ hash: stakingTxHash as `0x${string}`,
82
+ query: {
83
+ structuralSharing: false, // Disable to avoid BigInt serialization issues
84
+ },
85
+ });
86
+
87
+ // Show success modal when transaction is confirmed
88
+ useEffect(() => {
89
+ if (isTxSuccess && stakingTxHash) {
90
+ setShowAmountPrompt(false);
91
+ setShowSuccessModal(true);
92
+ setIsStaking(false);
93
+ }
94
+ }, [isTxSuccess, stakingTxHash]);
95
+
96
+ const [userStakeAmount, setUserStakeAmount] = useState<string>(stakeAmount || "");
97
+ const [showAmountPrompt, setShowAmountPrompt] = useState<boolean>(!stakeAmount);
98
+ const [isAmountValid, setIsAmountValid] = useState<boolean>(!!stakeAmount);
99
+ const [validationError, setValidationError] = useState<string>("");
100
+ // Store display amount for UI
101
+ const [displayAmount, setDisplayAmount] = useState<string>("");
102
+ // Debounced state for balance checks and messaging
103
+ const [debouncedAmount, setDebouncedAmount] = useState<string>("");
104
+ const [debouncedUserStakeAmount, setDebouncedUserStakeAmount] = useState<string>("");
105
+
106
+ // Debounce the amount for balance checks
107
+ useEffect(() => {
108
+ const timer = setTimeout(() => {
109
+ setDebouncedAmount(displayAmount);
110
+ setDebouncedUserStakeAmount(userStakeAmount);
111
+ }, 500);
112
+
113
+ return () => clearTimeout(timer);
114
+ }, [displayAmount, userStakeAmount]);
115
+
116
+ useEffect(() => {
117
+ if (stakeAmount) {
118
+ setUserStakeAmount(stakeAmount);
119
+ setShowAmountPrompt(false);
120
+ setIsAmountValid(true);
121
+ }
122
+ }, [stakeAmount]);
123
+
124
+ if (!recipientAddress) return null;
125
+
126
+ const validateAndSetAmount = (value: string) => {
127
+ // Allow decimal input by validating against a pattern
128
+ // This regex allows numbers with up to 18 decimal places
129
+ const isValidFormat = /^(\d+\.?\d{0,18}|\.\d{1,18})$/.test(value) || value === "";
130
+
131
+ if (!isValidFormat && value !== "") {
132
+ return;
133
+ }
134
+
135
+ setDisplayAmount(value);
136
+
137
+ try {
138
+ if (value === "" || value === ".") {
139
+ setUserStakeAmount("");
140
+ setIsAmountValid(false);
141
+ setValidationError("");
142
+ return;
143
+ }
144
+
145
+ // For UI validation - check if it's a positive number
146
+ const numValue = parseFloat(value);
147
+ if (isNaN(numValue) || numValue <= 0) {
148
+ setIsAmountValid(false);
149
+ setUserStakeAmount("");
150
+ setValidationError("Please enter a valid positive number");
151
+ return;
152
+ }
153
+
154
+ // Check minimum stake amount (50 B3)
155
+ if (numValue < 50) {
156
+ setIsAmountValid(false);
157
+ setUserStakeAmount("");
158
+ setValidationError("Minimum stake amount is 50 B3");
159
+ return;
160
+ }
161
+
162
+ // Convert to wei (multiply by 10^18)
163
+ // Handle decimal places correctly by removing the decimal point
164
+ let fullAmount;
165
+ if (value.includes(".")) {
166
+ const [whole, fraction = ""] = value.split(".");
167
+ // Pad with zeros to 18 decimal places
168
+ const paddedFraction = fraction.padEnd(18, "0");
169
+ fullAmount = whole + paddedFraction;
170
+ } else {
171
+ fullAmount = value + "000000000000000000"; // Add 18 zeros
172
+ }
173
+
174
+ // Remove leading zeros
175
+ fullAmount = fullAmount.replace(/^0+/, "") || "0";
176
+
177
+ // Set the full amount for the actual transaction
178
+ setUserStakeAmount(fullAmount);
179
+ setIsAmountValid(true);
180
+ setValidationError("");
181
+ } catch (error) {
182
+ setIsAmountValid(false);
183
+ setUserStakeAmount("");
184
+ setValidationError("Please enter a valid amount");
185
+ }
186
+ };
187
+
188
+ const handleDirectStaking = async () => {
189
+ if (!address || !basePublicClient || !userStakeAmount) return;
190
+
191
+ try {
192
+ setIsStaking(true);
193
+
194
+ // Check current allowance
195
+ const allowance = await basePublicClient.readContract({
196
+ address: B3_TOKEN.address as `0x${string}`,
197
+ abi: erc20Abi,
198
+ functionName: "allowance",
199
+ args: [address, ERC20Staking as `0x${string}`],
200
+ });
201
+
202
+ // If allowance is insufficient, request approval first
203
+ if (allowance < BigInt(userStakeAmount)) {
204
+ toast.info("Approving B3 spending...");
205
+
206
+ const approvalData = encodeFunctionData({
207
+ abi: erc20Abi,
208
+ functionName: "approve",
209
+ args: [ERC20Staking as `0x${string}`, BigInt(userStakeAmount)],
210
+ });
211
+
212
+ const approvalHash = await switchChainAndExecute(base.id, {
213
+ to: B3_TOKEN.address as `0x${string}`,
214
+ data: approvalData,
215
+ value: BigInt(0),
216
+ });
217
+
218
+ if (!approvalHash) {
219
+ toast.error("Approval failed. Please try again.");
220
+ return;
221
+ }
222
+
223
+ const approvalReceipt = await basePublicClient.waitForTransactionReceipt({
224
+ hash: approvalHash as `0x${string}`,
225
+ confirmations: 1,
226
+ });
227
+
228
+ if (approvalReceipt?.status !== "success") {
229
+ toast.error("Approval failed. Please try again.");
230
+ return;
231
+ }
232
+ }
233
+
234
+ // Execute the stake
235
+ toast.info("Staking B3...");
236
+
237
+ const stakeData = encodeFunctionData({
238
+ abi: ABI_ERC20_STAKING,
239
+ functionName: "stake",
240
+ args: [BigInt(userStakeAmount), recipientAddress as `0x${string}`],
241
+ });
242
+
243
+ const stakeHash = await switchChainAndExecute(base.id, {
244
+ to: ERC20Staking as `0x${string}`,
245
+ data: stakeData,
246
+ value: BigInt(0),
247
+ });
248
+
249
+ if (stakeHash) {
250
+ setStakingTxHash(stakeHash);
251
+ toast.success("Staking transaction submitted!");
252
+ }
253
+ } catch (error) {
254
+ console.error("@@b3-stake-custom-exact-in:error:", error);
255
+ toast.error("Staking failed. Please try again.");
256
+ setShowSuccessModal(false); // Ensure modal doesn't show on error
257
+ } finally {
258
+ setIsStaking(false);
259
+ }
260
+ };
261
+
262
+ const confirmAmount = () => {
263
+ if (!isAmountValid) {
264
+ toast.error("Please enter a valid amount to stake");
265
+ return;
266
+ }
267
+
268
+ // Check if user has sufficient B3 balance for direct staking
269
+ const hasEnoughBalance = b3RawBalance && BigInt(userStakeAmount) <= b3RawBalance;
270
+
271
+ if (hasEnoughBalance) {
272
+ // User has enough B3, proceed with direct staking
273
+ handleDirectStaking();
274
+ } else {
275
+ // User needs more B3, proceed to AnySpend conversion flow
276
+ setShowAmountPrompt(false);
277
+ }
278
+ };
279
+
280
+ const header = () => (
281
+ <>
282
+ <div className="relative mx-auto size-32">
283
+ <img alt="b3 coin" className="size-full" src="https://cdn.b3.fun/b3-coin-3d.png" />
284
+ </div>
285
+ <div className="from-b3-react-background to-as-on-surface-1 mt-[-60px] w-full rounded-t-lg bg-gradient-to-t">
286
+ <div className="h-[60px] w-full" />
287
+ <div className="mb-1 flex w-full flex-col items-center gap-2 p-5">
288
+ <span className="font-sf-rounded text-2xl font-semibold">
289
+ Swap & Stake {userStakeAmount ? formatTokenAmount(BigInt(userStakeAmount), 18) : ""} B3 (Exact In)
290
+ </span>
291
+ </div>
292
+ </div>
293
+ </>
294
+ );
295
+
296
+ const onFocusStakeAmountInput = () => {
297
+ window.scrollTo(0, 0);
298
+ document.body.scrollTop = 0;
299
+ };
300
+
301
+ const customExactInConfig = {
302
+ functionAbi: STAKE_FUNCTION_ABI,
303
+ functionName: "stake",
304
+ functionArgs: ["{{amount_out}}", normalizeAddress(recipientAddress)],
305
+ to: ERC20Staking,
306
+ spenderAddress: ERC20Staking,
307
+ action: "stake B3",
308
+ };
309
+
310
+ // Render amount input prompt if no stake amount is provided
311
+ if (showAmountPrompt) {
312
+ return (
313
+ <StyleRoot>
314
+ <div className="bg-b3-react-background flex w-full flex-col items-center">
315
+ <div className="w-full px-4">
316
+ <motion.div
317
+ initial={false}
318
+ animate={{
319
+ opacity: hasMounted ? 1 : 0,
320
+ y: hasMounted ? 0 : 20,
321
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
322
+ }}
323
+ transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
324
+ className="relative mx-auto size-48"
325
+ >
326
+ <video autoPlay muted playsInline className="size-full" src="https://cdn.b3.fun/b3-sphere-to-coin.mp4" />
327
+ </motion.div>
328
+ <motion.div
329
+ initial={false}
330
+ animate={{
331
+ opacity: hasMounted ? 1 : 0,
332
+ y: hasMounted ? 0 : 20,
333
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
334
+ }}
335
+ transition={{ duration: 0.3, delay: 0.1, ease: "easeInOut" }}
336
+ >
337
+ <h2 className="font-sf-rounded font-neue-montreal-medium mb-1 text-center text-2xl font-semibold">
338
+ {(() => {
339
+ const hasEnoughBalance = b3RawBalance && BigInt(debouncedUserStakeAmount || "0") <= b3RawBalance;
340
+ return hasEnoughBalance || !debouncedAmount ? "Stake B3" : "Swap & Stake B3";
341
+ })()}
342
+ </h2>
343
+ </motion.div>
344
+ </div>
345
+
346
+ <motion.div
347
+ initial={false}
348
+ animate={{
349
+ opacity: hasMounted ? 1 : 0,
350
+ y: hasMounted ? 0 : 20,
351
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
352
+ }}
353
+ transition={{ duration: 0.3, delay: 0.2, ease: "easeInOut" }}
354
+ className="bg-b3-react-background w-full p-6"
355
+ >
356
+ <div className="mb-2">
357
+ <div className="flex items-center justify-between">
358
+ <p className="text-as-primary/70 text-sm font-medium">I want to stake</p>
359
+ <span className="text-as-primary/50 flex items-center gap-1 text-sm">
360
+ Available: {isBalanceLoading ? <Loader2 className="h-3 w-3 animate-spin" /> : `${b3Balance} B3`}
361
+ </span>
362
+ </div>
363
+ </div>
364
+
365
+ <div className="relative">
366
+ <Input
367
+ onFocus={onFocusStakeAmountInput}
368
+ type="text"
369
+ placeholder="0.00"
370
+ value={displayAmount}
371
+ onChange={e => validateAndSetAmount(e.target.value)}
372
+ className={`h-14 px-4 text-lg ${!isAmountValid && displayAmount ? "border-as-red" : "border-b3-react-border"}`}
373
+ />
374
+ <div className="font-pack absolute right-4 top-1/2 -translate-y-1/2 text-lg font-medium text-blue-500/70">
375
+ B3
376
+ </div>
377
+ </div>
378
+
379
+ {!isAmountValid && displayAmount && <p className="text-as-red mt-2 text-sm">{validationError}</p>}
380
+
381
+ <div className="mt-4">
382
+ {(() => {
383
+ const hasEnoughBalance = b3RawBalance && BigInt(debouncedUserStakeAmount || "0") <= b3RawBalance;
384
+
385
+ if (!hasEnoughBalance || !debouncedAmount) {
386
+ return (
387
+ <div className="bg-as-brand/10 flex flex-col items-center gap-2 rounded-lg p-4 pb-5">
388
+ <div className="flex items-center justify-center gap-2">
389
+ <span className="text-as-primary text-sm font-semibold">Swap & stake from any token</span>
390
+ <TextLoop>
391
+ <EthIcon className="h-8 w-8" />
392
+ <SolIcon className="h-8 w-8" />
393
+ <UsdcIcon className="h-8 w-8" />
394
+ </TextLoop>
395
+ <ArrowRight className="text-as-primary h-4 w-4" />
396
+ <img src="https://cdn.b3.fun/b3-coin-3d.png" className="h-7 w-7" alt="B3 Token" />
397
+ </div>
398
+ <p className="text-as-primary/50 text-sm font-medium">
399
+ {debouncedAmount
400
+ ? `No problem, we'll help you swap to ${debouncedAmount} B3!`
401
+ : "Not enough B3? We'll help you swap from other coins."}
402
+ </p>
403
+ </div>
404
+ );
405
+ }
406
+ })()}
407
+ </div>
408
+
409
+ <Button
410
+ onClick={confirmAmount}
411
+ disabled={!isAmountValid || !displayAmount || isStaking || isTxPending || isSwitchingOrExecuting}
412
+ className="bg-as-brand hover:bg-as-brand/90 text-as-primary mt-4 h-14 w-full rounded-xl text-lg font-medium"
413
+ >
414
+ {isStaking || isSwitchingOrExecuting ? "Staking..." : isTxPending ? "Confirming..." : "Continue"}
415
+ </Button>
416
+ </motion.div>
417
+ </div>
418
+ </StyleRoot>
419
+ );
420
+ }
421
+
422
+ // Success Modal for Direct Staking
423
+ if (showSuccessModal) {
424
+ return (
425
+ <StyleRoot>
426
+ <div className="bg-b3-react-background flex w-full flex-col items-center">
427
+ <div className="w-full p-4">
428
+ <motion.div
429
+ initial={false}
430
+ animate={{
431
+ opacity: hasMounted ? 1 : 0,
432
+ y: hasMounted ? 0 : 20,
433
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
434
+ }}
435
+ transition={{ duration: 0.3, delay: 0, ease: "easeInOut" }}
436
+ className="relative mx-auto mb-4 size-[120px]"
437
+ >
438
+ <div className="absolute inset-0 scale-95 rounded-[50%] bg-black/30 blur-md"></div>
439
+ <GlareCardRounded className="overflow-hidden rounded-full border-none">
440
+ <img
441
+ alt="b3 coin"
442
+ loading="lazy"
443
+ width="64"
444
+ height="64"
445
+ decoding="async"
446
+ data-nimg="1"
447
+ className="size-full shrink-0 bg-transparent text-transparent"
448
+ src="https://cdn.b3.fun/b3-coin-3d.png"
449
+ />
450
+ <div className="absolute inset-0 rounded-[50%] border border-white/10"></div>
451
+ </GlareCardRounded>
452
+ </motion.div>
453
+ <motion.div
454
+ initial={false}
455
+ animate={{
456
+ opacity: hasMounted ? 1 : 0,
457
+ y: hasMounted ? 0 : 20,
458
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
459
+ }}
460
+ transition={{ duration: 0.3, delay: 0.1, ease: "easeInOut" }}
461
+ >
462
+ <h2 className="font-sf-rounded mb-1 text-center text-2xl font-semibold">
463
+ Staked {formatTokenAmount(BigInt(userStakeAmount), 18)} B3
464
+ </h2>
465
+ </motion.div>
466
+ </div>
467
+
468
+ <motion.div
469
+ initial={false}
470
+ animate={{
471
+ opacity: hasMounted ? 1 : 0,
472
+ y: hasMounted ? 0 : 20,
473
+ filter: hasMounted ? "blur(0px)" : "blur(10px)",
474
+ }}
475
+ transition={{ duration: 0.3, delay: 0.2, ease: "easeInOut" }}
476
+ className="bg-b3-react-background w-full p-6"
477
+ >
478
+ <div className="mb-6">
479
+ <a
480
+ href={`https://basescan.org/tx/${stakingTxHash}`}
481
+ target="_blank"
482
+ rel="noopener noreferrer"
483
+ className="text-as-primary/70 hover:text-as-primary block break-all text-center font-mono text-sm underline transition-colors"
484
+ >
485
+ View transaction
486
+ </a>
487
+ </div>
488
+
489
+ <Button
490
+ onClick={() => {
491
+ setB3ModalOpen(false);
492
+ onSuccess?.();
493
+ }}
494
+ className="bg-as-brand hover:bg-as-brand/90 text-as-primary h-14 w-full rounded-xl text-lg font-medium"
495
+ >
496
+ Done
497
+ </Button>
498
+ </motion.div>
499
+ </div>
500
+ </StyleRoot>
501
+ );
502
+ }
503
+
504
+ return (
505
+ <AnySpendCustomExactIn
506
+ loadOrder={loadOrder}
507
+ mode={mode}
508
+ recipientAddress={recipientAddress}
509
+ destinationToken={B3_TOKEN}
510
+ destinationChainId={base.id}
511
+ customExactInConfig={customExactInConfig}
512
+ header={header}
513
+ onSuccess={onSuccess}
514
+ />
515
+ );
516
+ }
@@ -0,0 +1,67 @@
1
+ import { components } from "@b3dotfun/sdk/anyspend/types/api";
2
+ import { normalizeAddress } from "@b3dotfun/sdk/anyspend/utils";
3
+ import { base } from "viem/chains";
4
+ import { AnySpendCustomExactIn } from "./AnySpendCustomExactIn";
5
+
6
+ const STAKE_FOR_FUNCTION_ABI = JSON.stringify([
7
+ {
8
+ name: "stakeFor",
9
+ type: "function",
10
+ stateMutability: "nonpayable",
11
+ inputs: [
12
+ { name: "user", type: "address" },
13
+ { name: "amount", type: "uint256" },
14
+ ],
15
+ outputs: [],
16
+ },
17
+ ]);
18
+
19
+ export function AnySpendStakeUpsideExactIn({
20
+ loadOrder,
21
+ mode = "modal",
22
+ recipientAddress,
23
+ stakingContractAddress,
24
+ token,
25
+ onSuccess,
26
+ }: {
27
+ loadOrder?: string;
28
+ mode?: "modal" | "page";
29
+ recipientAddress: string;
30
+ stakingContractAddress: string;
31
+ token: components["schemas"]["Token"];
32
+ onSuccess?: () => void;
33
+ }) {
34
+ if (!recipientAddress) return null;
35
+
36
+ const header = () => (
37
+ <>
38
+ <div className="from-b3-react-background to-as-on-surface-1 w-full rounded-t-lg bg-gradient-to-t">
39
+ <div className="mb-1 flex w-full flex-col items-center gap-2">
40
+ <span className="font-sf-rounded text-2xl font-semibold">Swap & Stake {token.symbol} (Exact In)</span>
41
+ </div>
42
+ </div>
43
+ </>
44
+ );
45
+
46
+ const customExactInConfig = {
47
+ functionAbi: STAKE_FOR_FUNCTION_ABI,
48
+ functionName: "stakeFor",
49
+ functionArgs: [normalizeAddress(recipientAddress), "{{amount_out}}"],
50
+ to: stakingContractAddress,
51
+ spenderAddress: stakingContractAddress,
52
+ action: `stake ${token.symbol}`,
53
+ };
54
+
55
+ return (
56
+ <AnySpendCustomExactIn
57
+ loadOrder={loadOrder}
58
+ mode={mode}
59
+ recipientAddress={recipientAddress}
60
+ destinationToken={token}
61
+ destinationChainId={base.id}
62
+ customExactInConfig={customExactInConfig}
63
+ header={header}
64
+ onSuccess={onSuccess}
65
+ />
66
+ );
67
+ }
@@ -6,6 +6,7 @@ import invariant from "invariant";
6
6
  import { motion } from "motion/react";
7
7
  import { useEffect, useMemo, useRef } from "react";
8
8
  import { toast } from "sonner";
9
+ import { useActiveWallet, useSetActiveWallet } from "thirdweb/react";
9
10
  import { base } from "viem/chains";
10
11
  import { PanelView, useAnyspendFlow } from "../hooks/useAnyspendFlow";
11
12
  import { AnySpendFingerprintWrapper, getFingerprintConfig } from "./AnySpendFingerprintWrapper";
@@ -17,11 +18,10 @@ import { FiatPaymentMethod, FiatPaymentMethodComponent } from "./common/FiatPaym
17
18
  import { OrderDetails } from "./common/OrderDetails";
18
19
  import { PointsDetailPanel } from "./common/PointsDetailPanel";
19
20
  import { RecipientSelection } from "./common/RecipientSelection";
20
- import { useActiveWallet, useSetActiveWallet } from "thirdweb/react";
21
21
 
22
22
  import { ArrowDown, Loader2 } from "lucide-react";
23
- import { PanelOnramp } from "./common/PanelOnramp";
24
23
  import { useGlobalWalletState } from "../../utils";
24
+ import { PanelOnramp } from "./common/PanelOnramp";
25
25
 
26
26
  const SLIPPAGE_PERCENT = 3;
27
27
 
@@ -286,9 +286,12 @@ function AnySpendDepositHypeInner({
286
286
  <CryptoReceiveSection
287
287
  isDepositMode={false}
288
288
  isBuyMode={true}
289
- selectedRecipientAddress={recipientAddress}
289
+ selectedRecipientAddress={selectedRecipientAddress}
290
290
  recipientName={recipientName || undefined}
291
291
  onSelectRecipient={() => setActivePanel(PanelView.RECIPIENT_SELECTION)}
292
+ setRecipientAddress={setSelectedRecipientAddress}
293
+ recipientAddressFromProps={recipientAddress}
294
+ globalAddress={globalAddress}
292
295
  dstAmount={dstAmount}
293
296
  dstToken={B3_TOKEN}
294
297
  dstTokenSymbol={HYPE_TOKEN_DETAILS.SYMBOL}
@@ -304,6 +307,7 @@ function AnySpendDepositHypeInner({
304
307
  anyspendQuote={anyspendQuote}
305
308
  onShowPointsDetail={() => setActivePanel(PanelView.POINTS_DETAIL)}
306
309
  onShowFeeDetail={() => setActivePanel(PanelView.FEE_DETAIL)}
310
+ selectedCryptoPaymentMethod={selectedCryptoPaymentMethod}
307
311
  />
308
312
  )}
309
313
  </div>
@@ -1,4 +1,4 @@
1
- import { useAccountWallet, useProfile, useTokenData } from "@b3dotfun/sdk/global-account/react";
1
+ import { useProfile, useTokenData } from "@b3dotfun/sdk/global-account/react";
2
2
  import { formatUsername } from "@b3dotfun/sdk/shared/utils";
3
3
  import { shortenAddress } from "@b3dotfun/sdk/shared/utils/formatAddress";
4
4
  import { formatDisplayNumber } from "@b3dotfun/sdk/shared/utils/number";
@@ -6,6 +6,7 @@ import { ChevronRight, Info } from "lucide-react";
6
6
  import { motion } from "motion/react";
7
7
  import { useEffect, useRef } from "react";
8
8
  import { components } from "../../../types/api";
9
+ import { useConnectedWalletDisplay } from "../../hooks/useConnectedWalletDisplay";
9
10
  import { CryptoPaymentMethodType } from "./CryptoPaymentMethod";
10
11
  import { OrderTokenAmount } from "./OrderTokenAmount";
11
12
  import { TokenBalance } from "./TokenBalance";
@@ -46,14 +47,10 @@ export function CryptoPaySection({
46
47
  onTokenSelect,
47
48
  onShowFeeDetail,
48
49
  }: CryptoPaySectionProps) {
49
- const { connectedSmartWallet, connectedEOAWallet } = useAccountWallet();
50
50
  const { data: srcTokenMetadata } = useTokenData(selectedSrcToken?.chainId, selectedSrcToken?.address);
51
51
 
52
- // Determine which address to use based on payment method
53
- const walletAddress =
54
- selectedCryptoPaymentMethod === CryptoPaymentMethodType.GLOBAL_WALLET
55
- ? connectedSmartWallet?.getAccount()?.address
56
- : connectedEOAWallet?.getAccount()?.address;
52
+ // Use custom hook to determine wallet address based on payment method
53
+ const { walletAddress } = useConnectedWalletDisplay(selectedCryptoPaymentMethod);
57
54
 
58
55
  const { data: profileData } = useProfile({ address: walletAddress });
59
56
  const connectedName = profileData?.displayName;
@@ -137,6 +134,7 @@ export function CryptoPaySection({
137
134
  </div>
138
135
  <OrderTokenAmount
139
136
  address={walletAddress}
137
+ walletAddress={walletAddress}
140
138
  context="from"
141
139
  inputValue={srcAmount}
142
140
  onChangeInput={value => {