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