@b3dotfun/sdk 0.0.9-alpha.5 → 0.0.9-alpha.7
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.
|
@@ -13,6 +13,8 @@ const utils_1 = require("../../../../shared/utils");
|
|
|
13
13
|
const centerTruncate_1 = __importDefault(require("../../../../shared/utils/centerTruncate"));
|
|
14
14
|
const number_1 = require("../../../../shared/utils/number");
|
|
15
15
|
const react_2 = require("@chakra-ui/react");
|
|
16
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
17
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
16
18
|
const react_3 = require("@web3icons/react");
|
|
17
19
|
const framer_motion_1 = require("framer-motion");
|
|
18
20
|
const lucide_react_1 = require("lucide-react");
|
|
@@ -177,7 +179,12 @@ exports.OrderDetails = (0, react_4.memo)(function OrderDetails({ isMainnet, mode
|
|
|
177
179
|
// Main payment handler that triggers chain switch and payment
|
|
178
180
|
const handlePayment = async () => {
|
|
179
181
|
console.log("Initiating payment process. Target chain:", order.srcChain, "Current chain:", walletClient?.chain?.id);
|
|
180
|
-
|
|
182
|
+
if (order.srcChain === anyspend_1.RELAY_SOLANA_MAINNET_CHAIN_ID) {
|
|
183
|
+
await initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
await switchChainAndExecute(order.srcChain, handlePaymentProcess);
|
|
187
|
+
}
|
|
181
188
|
};
|
|
182
189
|
// When waitingForDeposit is true, we show a message to the user to wait for the deposit to be processed.
|
|
183
190
|
const setWaitingForDeposit = (0, react_4.useCallback)(() => {
|
|
@@ -212,6 +219,16 @@ exports.OrderDetails = (0, react_4.memo)(function OrderDetails({ isMainnet, mode
|
|
|
212
219
|
}
|
|
213
220
|
}, [setWaitingForDeposit, txSuccess]);
|
|
214
221
|
const [showOrderDetails, setShowOrderDetails] = (0, react_4.useState)(false);
|
|
222
|
+
const isPhantomMobile = (0, react_4.useMemo)(() => navigator.userAgent.includes("Phantom"), []);
|
|
223
|
+
const isPhantomBrowser = (0, react_4.useMemo)(() => window.phantom?.solana?.isPhantom, []);
|
|
224
|
+
// Get connected Phantom wallet address if available
|
|
225
|
+
const phantomWalletAddress = (0, react_4.useMemo)(() => {
|
|
226
|
+
const phantom = window.phantom?.solana;
|
|
227
|
+
if (phantom?.isConnected && phantom?.publicKey) {
|
|
228
|
+
return phantom.publicKey.toString();
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}, []);
|
|
215
232
|
if (!srcToken || !dstToken) {
|
|
216
233
|
return (0, jsx_runtime_1.jsx)("div", { children: "Loading..." });
|
|
217
234
|
}
|
|
@@ -245,9 +262,136 @@ exports.OrderDetails = (0, react_4.memo)(function OrderDetails({ isMainnet, mode
|
|
|
245
262
|
const coinbaseUrl = `https://go.cb-w.com/dapp?cb_url=${encodeURIComponent(permalink)}`;
|
|
246
263
|
return coinbaseUrl;
|
|
247
264
|
};
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
const initiatePhantomTransfer = async (amountLamports, tokenAddress, recipientAddress) => {
|
|
266
|
+
try {
|
|
267
|
+
if (!isPhantomBrowser && !isPhantomMobile) {
|
|
268
|
+
sonner_1.toast.error("Phantom wallet not installed. Please install Phantom wallet to continue.");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Step 2: Ensure Phantom is connected/unlocked
|
|
272
|
+
const phantom = window.phantom?.solana;
|
|
273
|
+
if (!phantom) {
|
|
274
|
+
sonner_1.toast.error("Phantom wallet not accessible");
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Connect and unlock wallet if needed
|
|
278
|
+
let publicKey;
|
|
279
|
+
try {
|
|
280
|
+
const connection = await phantom.connect();
|
|
281
|
+
publicKey = connection.publicKey;
|
|
282
|
+
}
|
|
283
|
+
catch (connectError) {
|
|
284
|
+
sonner_1.toast.error("Failed to connect to Phantom wallet");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// Step 3: Create transaction with priority fees
|
|
288
|
+
const connection = new web3_js_1.Connection("https://mainnet.helius-rpc.com/?api-key=efafd9b3-1807-4cf8-8aa4-3d984f56d8fb");
|
|
289
|
+
const fromPubkey = new web3_js_1.PublicKey(publicKey.toString());
|
|
290
|
+
const toPubkey = new web3_js_1.PublicKey(recipientAddress);
|
|
291
|
+
const amount = BigInt(amountLamports);
|
|
292
|
+
// Step 4: Get recent priority fees to determine optimal pricing
|
|
293
|
+
let priorityFee = 10000; // Default fallback (10,000 micro-lamports)
|
|
294
|
+
try {
|
|
295
|
+
const recentFees = await connection.getRecentPrioritizationFees({
|
|
296
|
+
lockedWritableAccounts: [fromPubkey],
|
|
297
|
+
});
|
|
298
|
+
if (recentFees && recentFees.length > 0) {
|
|
299
|
+
// Use 75th percentile of recent fees for good priority
|
|
300
|
+
const sortedFees = recentFees.map(fee => fee.prioritizationFee).sort((a, b) => a - b);
|
|
301
|
+
const percentile75Index = Math.floor(sortedFees.length * 0.75);
|
|
302
|
+
priorityFee = Math.max(sortedFees[percentile75Index] || 10000, 10000);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
catch (feeError) {
|
|
306
|
+
console.warn("Failed to fetch recent priority fees, using default:", feeError);
|
|
307
|
+
}
|
|
308
|
+
let transaction;
|
|
309
|
+
// Check if this is native SOL transfer
|
|
310
|
+
if (tokenAddress === "11111111111111111111111111111111") {
|
|
311
|
+
// Native SOL transfer with priority fees
|
|
312
|
+
const computeUnitLimit = 1000; // SOL transfer + compute budget instructions need ~600-800 CU
|
|
313
|
+
const computeUnitPrice = Math.min(priorityFee, 100000); // Cap at 100k micro-lamports for safety
|
|
314
|
+
transaction = new web3_js_1.Transaction()
|
|
315
|
+
.add(
|
|
316
|
+
// Set compute unit limit first (must come before other instructions)
|
|
317
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({
|
|
318
|
+
units: computeUnitLimit,
|
|
319
|
+
}))
|
|
320
|
+
.add(
|
|
321
|
+
// Set priority fee
|
|
322
|
+
web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({
|
|
323
|
+
microLamports: computeUnitPrice,
|
|
324
|
+
}))
|
|
325
|
+
.add(
|
|
326
|
+
// Actual transfer instruction
|
|
327
|
+
web3_js_1.SystemProgram.transfer({
|
|
328
|
+
fromPubkey,
|
|
329
|
+
toPubkey,
|
|
330
|
+
lamports: Number(amount),
|
|
331
|
+
}));
|
|
332
|
+
console.log(`Using priority fee: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU`);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
// SPL Token transfer with priority fees
|
|
336
|
+
const mintPubkey = new web3_js_1.PublicKey(tokenAddress);
|
|
337
|
+
// Get associated token accounts
|
|
338
|
+
const fromTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mintPubkey, fromPubkey);
|
|
339
|
+
const toTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mintPubkey, toPubkey);
|
|
340
|
+
// Check if destination token account exists
|
|
341
|
+
const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
|
|
342
|
+
const needsDestinationAccount = !toTokenAccountInfo;
|
|
343
|
+
// Get mint info to determine decimals
|
|
344
|
+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
345
|
+
const decimals = mintInfo.value?.data?.parsed?.info?.decimals || 9;
|
|
346
|
+
// SPL transfers need more compute units than SOL transfers
|
|
347
|
+
// Add extra CU if we need to create destination account
|
|
348
|
+
const computeUnitLimit = needsDestinationAccount ? 40000 : 20000;
|
|
349
|
+
const computeUnitPrice = Math.min(priorityFee, 100000);
|
|
350
|
+
// Create transfer instruction
|
|
351
|
+
const transferInstruction = (0, spl_token_1.createTransferCheckedInstruction)(fromTokenAccount, mintPubkey, toTokenAccount, fromPubkey, Number(amount), decimals);
|
|
352
|
+
transaction = new web3_js_1.Transaction()
|
|
353
|
+
.add(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({
|
|
354
|
+
units: computeUnitLimit,
|
|
355
|
+
}))
|
|
356
|
+
.add(web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({
|
|
357
|
+
microLamports: computeUnitPrice,
|
|
358
|
+
}));
|
|
359
|
+
// Add create destination account instruction if needed
|
|
360
|
+
if (needsDestinationAccount) {
|
|
361
|
+
transaction.add((0, spl_token_1.createAssociatedTokenAccountInstruction)(fromPubkey, // payer
|
|
362
|
+
toTokenAccount, // ata
|
|
363
|
+
toPubkey, // owner
|
|
364
|
+
mintPubkey));
|
|
365
|
+
}
|
|
366
|
+
// Add the transfer instruction
|
|
367
|
+
transaction.add(transferInstruction);
|
|
368
|
+
console.log(`SPL Token transfer: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU, creating destination: ${needsDestinationAccount}`);
|
|
369
|
+
}
|
|
370
|
+
// Step 5: Get latest blockhash and simulate transaction to verify
|
|
371
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
372
|
+
transaction.recentBlockhash = blockhash;
|
|
373
|
+
transaction.feePayer = fromPubkey;
|
|
374
|
+
// Step 6: Sign and send transaction with priority fees
|
|
375
|
+
const signedTransaction = await phantom.signAndSendTransaction(transaction);
|
|
376
|
+
sonner_1.toast.success(`Transaction successful! Signature: ${signedTransaction.signature}`);
|
|
377
|
+
console.log("Transaction sent with priority fees. Signature:", signedTransaction.signature);
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
console.error("Transfer error:", error);
|
|
381
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
382
|
+
if (errorMessage.includes("User rejected")) {
|
|
383
|
+
sonner_1.toast.error("Transaction was cancelled by user");
|
|
384
|
+
}
|
|
385
|
+
else if (errorMessage.includes("insufficient")) {
|
|
386
|
+
sonner_1.toast.error("Insufficient balance for this transaction");
|
|
387
|
+
}
|
|
388
|
+
else if (errorMessage.includes("blockhash not found")) {
|
|
389
|
+
sonner_1.toast.error("Network congestion detected. Please try again in a moment.");
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
sonner_1.toast.error(`Transfer failed: ${errorMessage}`);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
251
395
|
};
|
|
252
396
|
if (refundTxs) {
|
|
253
397
|
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative mt-4 flex w-full flex-col gap-4", children: [(0, jsx_runtime_1.jsx)("div", { className: "bg-b3-react-background absolute bottom-2 left-4 top-2 z-[5] w-2", children: (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: "from-as-brand/50 absolute left-[2px] top-0 z-10 w-[3px] bg-gradient-to-b from-20% via-purple-500/50 via-80% to-transparent", initial: { height: "0%" }, animate: { height: "100%" }, transition: { duration: 1.5, ease: "easeInOut" } }) }), depositTxs
|
|
@@ -331,7 +475,9 @@ exports.OrderDetails = (0, react_4.memo)(function OrderDetails({ isMainnet, mode
|
|
|
331
475
|
sonner_1.toast.success("Copied to clipboard");
|
|
332
476
|
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsxs)("strong", { className: "border-as-brand text-as-primary border-b-2 pb-1 text-2xl font-semibold sm:text-xl", children: [roundedUpSrcAmount, " ", srcToken.symbol] }), (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "text-as-primary/50 hover:text-as-primary h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), (0, jsx_runtime_1.jsxs)(react_1.Badge, { variant: "outline", className: "flex h-10 items-center gap-2 px-3 py-1 pr-2 text-sm", children: ["on ", (0, anyspend_1.getChainName)(order.srcChain), (0, jsx_runtime_1.jsx)("img", { src: anyspend_1.ALL_CHAINS[order.srcChain].logoUrl, alt: (0, anyspend_1.getChainName)(order.srcChain), className: (0, utils_1.cn)("h-6 rounded-full", order.srcChain === chains_1.b3.id && "h-5 rounded-none") })] })] }), (0, jsx_runtime_1.jsx)("span", { className: "text-as-primary/50 mb-1 mt-2", children: " to the address:" })] }), (0, jsx_runtime_1.jsx)(react_1.CopyToClipboard, { text: order.globalAddress, onCopy: () => {
|
|
333
477
|
sonner_1.toast.success("Copied to clipboard");
|
|
334
|
-
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-react-background border-b3-react-border hover:border-as-brand group flex cursor-pointer items-center justify-between gap-4 rounded-lg border p-3 px-4 shadow-md transition-all duration-200", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary overflow-hidden text-ellipsis whitespace-nowrap text-sm", children: order.globalAddress }), (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "group-hover:text-as-brand text-as-primary/50 h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), account?.address && !showQRCode ? ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4 mt-8 flex w-full flex-col items-center gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full flex-col items-center gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.ShinyButton, { accentColor: "hsl(var(--as-brand))", textColor: "text-white", className: "flex w-5/6 items-center gap-2 sm:px-0", disabled: txLoading || isSwitchingOrExecuting, onClick: handlePayment, children: txLoading ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Transaction Pending", (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "ml-2 h-5 w-5 animate-spin" })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from Connected Wallet" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "h-4 w-4" })] })) }), (0, jsx_runtime_1.jsxs)("span", { className: "label-style text-as-primary/50 text-xs", children: ["Connected to:
|
|
478
|
+
}, children: (0, jsx_runtime_1.jsxs)("div", { className: "bg-b3-react-background border-b3-react-border hover:border-as-brand group flex cursor-pointer items-center justify-between gap-4 rounded-lg border p-3 px-4 shadow-md transition-all duration-200", children: [(0, jsx_runtime_1.jsx)("div", { className: "text-as-primary overflow-hidden text-ellipsis whitespace-nowrap text-sm", children: order.globalAddress }), (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "group-hover:text-as-brand text-as-primary/50 h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), (account?.address || phantomWalletAddress) && !showQRCode ? ((0, jsx_runtime_1.jsxs)("div", { className: "mb-4 mt-8 flex w-full flex-col items-center gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "relative flex w-full flex-col items-center gap-2", children: [(0, jsx_runtime_1.jsx)(react_1.ShinyButton, { accentColor: "hsl(var(--as-brand))", textColor: "text-white", className: "flex w-5/6 items-center gap-2 sm:px-0", disabled: txLoading || isSwitchingOrExecuting, onClick: handlePayment, children: txLoading ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Transaction Pending", (0, jsx_runtime_1.jsx)(lucide_react_1.Loader2, { className: "ml-2 h-5 w-5 animate-spin" })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from Connected Wallet" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "h-4 w-4" })] })) }), (0, jsx_runtime_1.jsxs)("span", { className: "label-style text-as-primary/50 text-xs", children: ["Connected to:", " ", order.srcChain === anyspend_1.RELAY_SOLANA_MAINNET_CHAIN_ID && phantomWalletAddress
|
|
479
|
+
? (0, centerTruncate_1.default)(phantomWalletAddress, 6)
|
|
480
|
+
: (0, centerTruncate_1.default)(account?.address || "", 6)] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex w-full flex-col items-center gap-2", children: [(0, jsx_runtime_1.jsxs)(react_1.ShinyButton, { accentColor: colorMode === "dark" ? "#ffffff" : "#000000", className: "flex w-5/6 items-center gap-2 sm:px-0", onClick: () => setShowQRCode(true), children: [(0, jsx_runtime_1.jsx)("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from a different wallet" }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "h-4 w-4" })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-2", children: [(0, jsx_runtime_1.jsx)(react_3.WalletMetamask, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletCoinbase, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletPhantom, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletTrust, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletWalletConnect, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)("span", { className: "label-style text-as-primary/30 text-xs", children: "& more" })] })] })] })) : ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { initial: { opacity: 0, filter: "blur(10px)" }, animate: { opacity: 1, filter: "blur(0px)" }, transition: { duration: 0.5, ease: "easeInOut" }, className: "flex w-full items-center justify-evenly gap-4", children: [(0, jsx_runtime_1.jsxs)("div", { className: "mt-8 flex flex-col items-center rounded-lg bg-white p-6 pb-3", children: [(0, jsx_runtime_1.jsx)(qrcode_react_1.QRCodeSVG, { value: (0, anyspend_1.getPaymentUrl)(order.globalAddress, BigInt(order.srcAmount), order.srcTokenAddress === anyspend_1.RELAY_ETH_ADDRESS ? "ETH" : order.srcTokenAddress), className: "max-w-[200px]" }), (0, jsx_runtime_1.jsxs)("div", { className: "mt-3 flex items-center justify-center gap-2 text-sm", children: [(0, jsx_runtime_1.jsx)("span", { className: "label-style text-as-brand/70 text-sm", children: "Scan with" }), (0, jsx_runtime_1.jsxs)(react_1.TextLoop, { interval: 3, children: [(0, jsx_runtime_1.jsx)(react_3.WalletMetamask, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletCoinbase, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletPhantom, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletTrust, { className: "h-5 w-5", variant: "branded" }), (0, jsx_runtime_1.jsx)(react_3.WalletWalletConnect, { className: "h-5 w-5", variant: "branded" })] })] })] }), (0, jsx_runtime_1.jsxs)("div", { className: "flex flex-col gap-2", children: [account && ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "ghost", className: "text-as-primary w-full", onClick: handlePayment, children: ["Send Transaction ", (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronRight, { className: "ml-2 h-4 w-4" })] })), anyspend_1.EVM_CHAINS[order.srcChain] ? ((0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", className: "w-full", onClick: handlePayment, children: ["Open Metamask", (0, jsx_runtime_1.jsx)(react_3.WalletMetamask, { className: "ml-2 h-5 w-5", variant: "branded" })] })) : null, (0, jsx_runtime_1.jsx)("a", { href: handleCoinbaseRedirect(), children: (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", className: "w-full", children: ["Open Coinbase", (0, jsx_runtime_1.jsx)(react_3.WalletCoinbase, { className: "ml-2 h-5 w-5", variant: "branded" })] }) }), (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", className: "w-full", onClick: () => initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress), children: ["Open Phantom", (0, jsx_runtime_1.jsx)(react_3.WalletPhantom, { className: "ml-2 h-5 w-5", variant: "branded" })] })] })] }))] })), (0, jsx_runtime_1.jsxs)("div", { className: "bg-as-light-brand/30 w-full rounded-lg p-4 sm:p-2 sm:px-4", children: [(0, jsx_runtime_1.jsx)("p", { className: "text-as-secondary mb-3 text-sm", children: "Continue on another device?" }), (0, jsx_runtime_1.jsxs)("div", { className: "flex items-center gap-4", children: [(0, jsx_runtime_1.jsx)(react_1.CopyToClipboard, { text: permalink, onCopy: () => {
|
|
335
481
|
sonner_1.toast.success("Copied to clipboard");
|
|
336
482
|
}, children: (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", className: "w-full", children: ["Copy Link", (0, jsx_runtime_1.jsx)(lucide_react_1.Copy, { className: "ml-2 h-3 w-3" })] }) }), (0, jsx_runtime_1.jsxs)(react_1.Button, { variant: "outline", className: "w-full", onClick: () => {
|
|
337
483
|
if (navigator.share) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { ALL_CHAINS, capitalizeFirstLetter, EVM_CHAINS, getChainName, getErrorDisplay, getExplorerTxUrl, getPaymentUrl, getStatusDisplay, isNativeToken, RELAY_ETH_ADDRESS, } from "../../../../anyspend/index.js";
|
|
3
|
+
import { ALL_CHAINS, capitalizeFirstLetter, EVM_CHAINS, getChainName, getErrorDisplay, getExplorerTxUrl, getPaymentUrl, getStatusDisplay, isNativeToken, RELAY_ETH_ADDRESS, RELAY_SOLANA_MAINNET_CHAIN_ID, } from "../../../../anyspend/index.js";
|
|
4
4
|
import { Badge, Button, CopyToClipboard, ShinyButton, Skeleton, TextLoop, TextShimmer, useAccountWallet, useChainSwitchWithAction, useModalStore, useOnchainName, } from "../../../../global-account/react/index.js";
|
|
5
5
|
import { useRouter, useSearchParams } from "../../../../shared/react/hooks/index.js";
|
|
6
6
|
import { cn } from "../../../../shared/utils/index.js";
|
|
7
7
|
import centerTruncate from "../../../../shared/utils/centerTruncate.js";
|
|
8
8
|
import { formatTokenAmount } from "../../../../shared/utils/number.js";
|
|
9
9
|
import { useColorMode } from "@chakra-ui/react";
|
|
10
|
+
import { createAssociatedTokenAccountInstruction, createTransferCheckedInstruction, getAssociatedTokenAddressSync, } from "@solana/spl-token";
|
|
11
|
+
import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
|
|
10
12
|
import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletTrust, WalletWalletConnect } from "@web3icons/react";
|
|
11
13
|
import { motion } from "framer-motion";
|
|
12
14
|
import { CheckIcon, ChevronDown, ChevronRight, Copy, ExternalLink, Home, Loader2, RefreshCcw, SquareArrowOutUpRight, } from "lucide-react";
|
|
@@ -171,7 +173,12 @@ export const OrderDetails = memo(function OrderDetails({ isMainnet, mode = "moda
|
|
|
171
173
|
// Main payment handler that triggers chain switch and payment
|
|
172
174
|
const handlePayment = async () => {
|
|
173
175
|
console.log("Initiating payment process. Target chain:", order.srcChain, "Current chain:", walletClient?.chain?.id);
|
|
174
|
-
|
|
176
|
+
if (order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID) {
|
|
177
|
+
await initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
await switchChainAndExecute(order.srcChain, handlePaymentProcess);
|
|
181
|
+
}
|
|
175
182
|
};
|
|
176
183
|
// When waitingForDeposit is true, we show a message to the user to wait for the deposit to be processed.
|
|
177
184
|
const setWaitingForDeposit = useCallback(() => {
|
|
@@ -206,6 +213,16 @@ export const OrderDetails = memo(function OrderDetails({ isMainnet, mode = "moda
|
|
|
206
213
|
}
|
|
207
214
|
}, [setWaitingForDeposit, txSuccess]);
|
|
208
215
|
const [showOrderDetails, setShowOrderDetails] = useState(false);
|
|
216
|
+
const isPhantomMobile = useMemo(() => navigator.userAgent.includes("Phantom"), []);
|
|
217
|
+
const isPhantomBrowser = useMemo(() => window.phantom?.solana?.isPhantom, []);
|
|
218
|
+
// Get connected Phantom wallet address if available
|
|
219
|
+
const phantomWalletAddress = useMemo(() => {
|
|
220
|
+
const phantom = window.phantom?.solana;
|
|
221
|
+
if (phantom?.isConnected && phantom?.publicKey) {
|
|
222
|
+
return phantom.publicKey.toString();
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}, []);
|
|
209
226
|
if (!srcToken || !dstToken) {
|
|
210
227
|
return _jsx("div", { children: "Loading..." });
|
|
211
228
|
}
|
|
@@ -239,9 +256,136 @@ export const OrderDetails = memo(function OrderDetails({ isMainnet, mode = "moda
|
|
|
239
256
|
const coinbaseUrl = `https://go.cb-w.com/dapp?cb_url=${encodeURIComponent(permalink)}`;
|
|
240
257
|
return coinbaseUrl;
|
|
241
258
|
};
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
259
|
+
const initiatePhantomTransfer = async (amountLamports, tokenAddress, recipientAddress) => {
|
|
260
|
+
try {
|
|
261
|
+
if (!isPhantomBrowser && !isPhantomMobile) {
|
|
262
|
+
toast.error("Phantom wallet not installed. Please install Phantom wallet to continue.");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Step 2: Ensure Phantom is connected/unlocked
|
|
266
|
+
const phantom = window.phantom?.solana;
|
|
267
|
+
if (!phantom) {
|
|
268
|
+
toast.error("Phantom wallet not accessible");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// Connect and unlock wallet if needed
|
|
272
|
+
let publicKey;
|
|
273
|
+
try {
|
|
274
|
+
const connection = await phantom.connect();
|
|
275
|
+
publicKey = connection.publicKey;
|
|
276
|
+
}
|
|
277
|
+
catch (connectError) {
|
|
278
|
+
toast.error("Failed to connect to Phantom wallet");
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// Step 3: Create transaction with priority fees
|
|
282
|
+
const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=efafd9b3-1807-4cf8-8aa4-3d984f56d8fb");
|
|
283
|
+
const fromPubkey = new PublicKey(publicKey.toString());
|
|
284
|
+
const toPubkey = new PublicKey(recipientAddress);
|
|
285
|
+
const amount = BigInt(amountLamports);
|
|
286
|
+
// Step 4: Get recent priority fees to determine optimal pricing
|
|
287
|
+
let priorityFee = 10000; // Default fallback (10,000 micro-lamports)
|
|
288
|
+
try {
|
|
289
|
+
const recentFees = await connection.getRecentPrioritizationFees({
|
|
290
|
+
lockedWritableAccounts: [fromPubkey],
|
|
291
|
+
});
|
|
292
|
+
if (recentFees && recentFees.length > 0) {
|
|
293
|
+
// Use 75th percentile of recent fees for good priority
|
|
294
|
+
const sortedFees = recentFees.map(fee => fee.prioritizationFee).sort((a, b) => a - b);
|
|
295
|
+
const percentile75Index = Math.floor(sortedFees.length * 0.75);
|
|
296
|
+
priorityFee = Math.max(sortedFees[percentile75Index] || 10000, 10000);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
catch (feeError) {
|
|
300
|
+
console.warn("Failed to fetch recent priority fees, using default:", feeError);
|
|
301
|
+
}
|
|
302
|
+
let transaction;
|
|
303
|
+
// Check if this is native SOL transfer
|
|
304
|
+
if (tokenAddress === "11111111111111111111111111111111") {
|
|
305
|
+
// Native SOL transfer with priority fees
|
|
306
|
+
const computeUnitLimit = 1000; // SOL transfer + compute budget instructions need ~600-800 CU
|
|
307
|
+
const computeUnitPrice = Math.min(priorityFee, 100000); // Cap at 100k micro-lamports for safety
|
|
308
|
+
transaction = new Transaction()
|
|
309
|
+
.add(
|
|
310
|
+
// Set compute unit limit first (must come before other instructions)
|
|
311
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
312
|
+
units: computeUnitLimit,
|
|
313
|
+
}))
|
|
314
|
+
.add(
|
|
315
|
+
// Set priority fee
|
|
316
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
317
|
+
microLamports: computeUnitPrice,
|
|
318
|
+
}))
|
|
319
|
+
.add(
|
|
320
|
+
// Actual transfer instruction
|
|
321
|
+
SystemProgram.transfer({
|
|
322
|
+
fromPubkey,
|
|
323
|
+
toPubkey,
|
|
324
|
+
lamports: Number(amount),
|
|
325
|
+
}));
|
|
326
|
+
console.log(`Using priority fee: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// SPL Token transfer with priority fees
|
|
330
|
+
const mintPubkey = new PublicKey(tokenAddress);
|
|
331
|
+
// Get associated token accounts
|
|
332
|
+
const fromTokenAccount = getAssociatedTokenAddressSync(mintPubkey, fromPubkey);
|
|
333
|
+
const toTokenAccount = getAssociatedTokenAddressSync(mintPubkey, toPubkey);
|
|
334
|
+
// Check if destination token account exists
|
|
335
|
+
const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
|
|
336
|
+
const needsDestinationAccount = !toTokenAccountInfo;
|
|
337
|
+
// Get mint info to determine decimals
|
|
338
|
+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
339
|
+
const decimals = mintInfo.value?.data?.parsed?.info?.decimals || 9;
|
|
340
|
+
// SPL transfers need more compute units than SOL transfers
|
|
341
|
+
// Add extra CU if we need to create destination account
|
|
342
|
+
const computeUnitLimit = needsDestinationAccount ? 40000 : 20000;
|
|
343
|
+
const computeUnitPrice = Math.min(priorityFee, 100000);
|
|
344
|
+
// Create transfer instruction
|
|
345
|
+
const transferInstruction = createTransferCheckedInstruction(fromTokenAccount, mintPubkey, toTokenAccount, fromPubkey, Number(amount), decimals);
|
|
346
|
+
transaction = new Transaction()
|
|
347
|
+
.add(ComputeBudgetProgram.setComputeUnitLimit({
|
|
348
|
+
units: computeUnitLimit,
|
|
349
|
+
}))
|
|
350
|
+
.add(ComputeBudgetProgram.setComputeUnitPrice({
|
|
351
|
+
microLamports: computeUnitPrice,
|
|
352
|
+
}));
|
|
353
|
+
// Add create destination account instruction if needed
|
|
354
|
+
if (needsDestinationAccount) {
|
|
355
|
+
transaction.add(createAssociatedTokenAccountInstruction(fromPubkey, // payer
|
|
356
|
+
toTokenAccount, // ata
|
|
357
|
+
toPubkey, // owner
|
|
358
|
+
mintPubkey));
|
|
359
|
+
}
|
|
360
|
+
// Add the transfer instruction
|
|
361
|
+
transaction.add(transferInstruction);
|
|
362
|
+
console.log(`SPL Token transfer: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU, creating destination: ${needsDestinationAccount}`);
|
|
363
|
+
}
|
|
364
|
+
// Step 5: Get latest blockhash and simulate transaction to verify
|
|
365
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
366
|
+
transaction.recentBlockhash = blockhash;
|
|
367
|
+
transaction.feePayer = fromPubkey;
|
|
368
|
+
// Step 6: Sign and send transaction with priority fees
|
|
369
|
+
const signedTransaction = await phantom.signAndSendTransaction(transaction);
|
|
370
|
+
toast.success(`Transaction successful! Signature: ${signedTransaction.signature}`);
|
|
371
|
+
console.log("Transaction sent with priority fees. Signature:", signedTransaction.signature);
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
console.error("Transfer error:", error);
|
|
375
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
376
|
+
if (errorMessage.includes("User rejected")) {
|
|
377
|
+
toast.error("Transaction was cancelled by user");
|
|
378
|
+
}
|
|
379
|
+
else if (errorMessage.includes("insufficient")) {
|
|
380
|
+
toast.error("Insufficient balance for this transaction");
|
|
381
|
+
}
|
|
382
|
+
else if (errorMessage.includes("blockhash not found")) {
|
|
383
|
+
toast.error("Network congestion detected. Please try again in a moment.");
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
toast.error(`Transfer failed: ${errorMessage}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
245
389
|
};
|
|
246
390
|
if (refundTxs) {
|
|
247
391
|
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "relative mt-4 flex w-full flex-col gap-4", children: [_jsx("div", { className: "bg-b3-react-background absolute bottom-2 left-4 top-2 z-[5] w-2", children: _jsx(motion.div, { className: "from-as-brand/50 absolute left-[2px] top-0 z-10 w-[3px] bg-gradient-to-b from-20% via-purple-500/50 via-80% to-transparent", initial: { height: "0%" }, animate: { height: "100%" }, transition: { duration: 1.5, ease: "easeInOut" } }) }), depositTxs
|
|
@@ -325,7 +469,9 @@ export const OrderDetails = memo(function OrderDetails({ isMainnet, mode = "moda
|
|
|
325
469
|
toast.success("Copied to clipboard");
|
|
326
470
|
}, children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("strong", { className: "border-as-brand text-as-primary border-b-2 pb-1 text-2xl font-semibold sm:text-xl", children: [roundedUpSrcAmount, " ", srcToken.symbol] }), _jsx(Copy, { className: "text-as-primary/50 hover:text-as-primary h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), _jsxs(Badge, { variant: "outline", className: "flex h-10 items-center gap-2 px-3 py-1 pr-2 text-sm", children: ["on ", getChainName(order.srcChain), _jsx("img", { src: ALL_CHAINS[order.srcChain].logoUrl, alt: getChainName(order.srcChain), className: cn("h-6 rounded-full", order.srcChain === b3.id && "h-5 rounded-none") })] })] }), _jsx("span", { className: "text-as-primary/50 mb-1 mt-2", children: " to the address:" })] }), _jsx(CopyToClipboard, { text: order.globalAddress, onCopy: () => {
|
|
327
471
|
toast.success("Copied to clipboard");
|
|
328
|
-
}, children: _jsxs("div", { className: "bg-b3-react-background border-b3-react-border hover:border-as-brand group flex cursor-pointer items-center justify-between gap-4 rounded-lg border p-3 px-4 shadow-md transition-all duration-200", children: [_jsx("div", { className: "text-as-primary overflow-hidden text-ellipsis whitespace-nowrap text-sm", children: order.globalAddress }), _jsx(Copy, { className: "group-hover:text-as-brand text-as-primary/50 h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), account?.address && !showQRCode ? (_jsxs("div", { className: "mb-4 mt-8 flex w-full flex-col items-center gap-4", children: [_jsxs("div", { className: "relative flex w-full flex-col items-center gap-2", children: [_jsx(ShinyButton, { accentColor: "hsl(var(--as-brand))", textColor: "text-white", className: "flex w-5/6 items-center gap-2 sm:px-0", disabled: txLoading || isSwitchingOrExecuting, onClick: handlePayment, children: txLoading ? (_jsxs(_Fragment, { children: ["Transaction Pending", _jsx(Loader2, { className: "ml-2 h-5 w-5 animate-spin" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from Connected Wallet" }), _jsx(ChevronRight, { className: "h-4 w-4" })] })) }), _jsxs("span", { className: "label-style text-as-primary/50 text-xs", children: ["Connected to:
|
|
472
|
+
}, children: _jsxs("div", { className: "bg-b3-react-background border-b3-react-border hover:border-as-brand group flex cursor-pointer items-center justify-between gap-4 rounded-lg border p-3 px-4 shadow-md transition-all duration-200", children: [_jsx("div", { className: "text-as-primary overflow-hidden text-ellipsis whitespace-nowrap text-sm", children: order.globalAddress }), _jsx(Copy, { className: "group-hover:text-as-brand text-as-primary/50 h-5 w-5 cursor-pointer transition-all duration-200" })] }) }), (account?.address || phantomWalletAddress) && !showQRCode ? (_jsxs("div", { className: "mb-4 mt-8 flex w-full flex-col items-center gap-4", children: [_jsxs("div", { className: "relative flex w-full flex-col items-center gap-2", children: [_jsx(ShinyButton, { accentColor: "hsl(var(--as-brand))", textColor: "text-white", className: "flex w-5/6 items-center gap-2 sm:px-0", disabled: txLoading || isSwitchingOrExecuting, onClick: handlePayment, children: txLoading ? (_jsxs(_Fragment, { children: ["Transaction Pending", _jsx(Loader2, { className: "ml-2 h-5 w-5 animate-spin" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from Connected Wallet" }), _jsx(ChevronRight, { className: "h-4 w-4" })] })) }), _jsxs("span", { className: "label-style text-as-primary/50 text-xs", children: ["Connected to:", " ", order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID && phantomWalletAddress
|
|
473
|
+
? centerTruncate(phantomWalletAddress, 6)
|
|
474
|
+
: centerTruncate(account?.address || "", 6)] })] }), _jsxs("div", { className: "flex w-full flex-col items-center gap-2", children: [_jsxs(ShinyButton, { accentColor: colorMode === "dark" ? "#ffffff" : "#000000", className: "flex w-5/6 items-center gap-2 sm:px-0", onClick: () => setShowQRCode(true), children: [_jsx("span", { className: "pl-4 text-lg md:text-sm", children: "Pay from a different wallet" }), _jsx(ChevronRight, { className: "h-4 w-4" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(WalletMetamask, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletCoinbase, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletPhantom, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletTrust, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletWalletConnect, { className: "h-5 w-5", variant: "branded" }), _jsx("span", { className: "label-style text-as-primary/30 text-xs", children: "& more" })] })] })] })) : (_jsxs(motion.div, { initial: { opacity: 0, filter: "blur(10px)" }, animate: { opacity: 1, filter: "blur(0px)" }, transition: { duration: 0.5, ease: "easeInOut" }, className: "flex w-full items-center justify-evenly gap-4", children: [_jsxs("div", { className: "mt-8 flex flex-col items-center rounded-lg bg-white p-6 pb-3", children: [_jsx(QRCodeSVG, { value: getPaymentUrl(order.globalAddress, BigInt(order.srcAmount), order.srcTokenAddress === RELAY_ETH_ADDRESS ? "ETH" : order.srcTokenAddress), className: "max-w-[200px]" }), _jsxs("div", { className: "mt-3 flex items-center justify-center gap-2 text-sm", children: [_jsx("span", { className: "label-style text-as-brand/70 text-sm", children: "Scan with" }), _jsxs(TextLoop, { interval: 3, children: [_jsx(WalletMetamask, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletCoinbase, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletPhantom, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletTrust, { className: "h-5 w-5", variant: "branded" }), _jsx(WalletWalletConnect, { className: "h-5 w-5", variant: "branded" })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [account && (_jsxs(Button, { variant: "ghost", className: "text-as-primary w-full", onClick: handlePayment, children: ["Send Transaction ", _jsx(ChevronRight, { className: "ml-2 h-4 w-4" })] })), EVM_CHAINS[order.srcChain] ? (_jsxs(Button, { variant: "outline", className: "w-full", onClick: handlePayment, children: ["Open Metamask", _jsx(WalletMetamask, { className: "ml-2 h-5 w-5", variant: "branded" })] })) : null, _jsx("a", { href: handleCoinbaseRedirect(), children: _jsxs(Button, { variant: "outline", className: "w-full", children: ["Open Coinbase", _jsx(WalletCoinbase, { className: "ml-2 h-5 w-5", variant: "branded" })] }) }), _jsxs(Button, { variant: "outline", className: "w-full", onClick: () => initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress), children: ["Open Phantom", _jsx(WalletPhantom, { className: "ml-2 h-5 w-5", variant: "branded" })] })] })] }))] })), _jsxs("div", { className: "bg-as-light-brand/30 w-full rounded-lg p-4 sm:p-2 sm:px-4", children: [_jsx("p", { className: "text-as-secondary mb-3 text-sm", children: "Continue on another device?" }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsx(CopyToClipboard, { text: permalink, onCopy: () => {
|
|
329
475
|
toast.success("Copied to clipboard");
|
|
330
476
|
}, children: _jsxs(Button, { variant: "outline", className: "w-full", children: ["Copy Link", _jsx(Copy, { className: "ml-2 h-3 w-3" })] }) }), _jsxs(Button, { variant: "outline", className: "w-full", onClick: () => {
|
|
331
477
|
if (navigator.share) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@b3dotfun/sdk",
|
|
3
|
-
"version": "0.0.9-alpha.
|
|
3
|
+
"version": "0.0.9-alpha.7",
|
|
4
4
|
"source": "src/index.ts",
|
|
5
5
|
"main": "./dist/cjs/index.js",
|
|
6
6
|
"react-native": "./dist/cjs/index.native.js",
|
|
@@ -190,10 +190,10 @@
|
|
|
190
190
|
"constants"
|
|
191
191
|
],
|
|
192
192
|
"dependencies": {
|
|
193
|
-
"@b3dotfun/bondkit": "^0.1.17",
|
|
194
193
|
"@amplitude/analytics-browser": "2.14.0",
|
|
195
194
|
"@b3dotfun/b3-api": "0.0.26",
|
|
196
195
|
"@b3dotfun/basement-api": "0.0.11",
|
|
196
|
+
"@b3dotfun/bondkit": "^0.1.17",
|
|
197
197
|
"@chakra-ui/react": "2.10.7",
|
|
198
198
|
"@feathersjs/authentication-client": "5.0.33",
|
|
199
199
|
"@feathersjs/feathers": "5.0.33",
|
|
@@ -202,8 +202,8 @@
|
|
|
202
202
|
"@hey-api/client-fetch": "0.8.3",
|
|
203
203
|
"@hey-api/openapi-ts": "0.64.13",
|
|
204
204
|
"@lottiefiles/dotlottie-react": "0.7.2",
|
|
205
|
-
"@radix-ui/react-dialog": "1.1.
|
|
206
|
-
"@radix-ui/react-popover": "1.1.
|
|
205
|
+
"@radix-ui/react-dialog": "1.1.7",
|
|
206
|
+
"@radix-ui/react-popover": "1.1.7",
|
|
207
207
|
"@radix-ui/react-scroll-area": "1.1.0",
|
|
208
208
|
"@radix-ui/react-slot": "1.1.2",
|
|
209
209
|
"@radix-ui/react-tabs": "1.1.3",
|
|
@@ -211,6 +211,8 @@
|
|
|
211
211
|
"@reservoir0x/relay-kit-ui": "2.15.4",
|
|
212
212
|
"@reservoir0x/relay-sdk": "2.3.0",
|
|
213
213
|
"@reservoir0x/reservoir-kit-ui": "2.8.7",
|
|
214
|
+
"@solana/spl-token": "^0.4.13",
|
|
215
|
+
"@solana/web3.js": "^1.98.2",
|
|
214
216
|
"@stripe/react-stripe-js": "^3.7.0",
|
|
215
217
|
"@stripe/stripe-js": "^7.3.1",
|
|
216
218
|
"@transak/transak-sdk": "3.1.3",
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
getStatusDisplay,
|
|
12
12
|
isNativeToken,
|
|
13
13
|
RELAY_ETH_ADDRESS,
|
|
14
|
+
RELAY_SOLANA_MAINNET_CHAIN_ID,
|
|
14
15
|
} from "@b3dotfun/sdk/anyspend";
|
|
15
16
|
import { components } from "@b3dotfun/sdk/anyspend/types/api";
|
|
16
17
|
import {
|
|
@@ -31,6 +32,12 @@ import { cn } from "@b3dotfun/sdk/shared/utils";
|
|
|
31
32
|
import centerTruncate from "@b3dotfun/sdk/shared/utils/centerTruncate";
|
|
32
33
|
import { formatTokenAmount } from "@b3dotfun/sdk/shared/utils/number";
|
|
33
34
|
import { useColorMode } from "@chakra-ui/react";
|
|
35
|
+
import {
|
|
36
|
+
createAssociatedTokenAccountInstruction,
|
|
37
|
+
createTransferCheckedInstruction,
|
|
38
|
+
getAssociatedTokenAddressSync,
|
|
39
|
+
} from "@solana/spl-token";
|
|
40
|
+
import { ComputeBudgetProgram, Connection, PublicKey, SystemProgram, Transaction } from "@solana/web3.js";
|
|
34
41
|
import { WalletCoinbase, WalletMetamask, WalletPhantom, WalletTrust, WalletWalletConnect } from "@web3icons/react";
|
|
35
42
|
import { motion } from "framer-motion";
|
|
36
43
|
import {
|
|
@@ -272,7 +279,11 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
272
279
|
// Main payment handler that triggers chain switch and payment
|
|
273
280
|
const handlePayment = async () => {
|
|
274
281
|
console.log("Initiating payment process. Target chain:", order.srcChain, "Current chain:", walletClient?.chain?.id);
|
|
275
|
-
|
|
282
|
+
if (order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID) {
|
|
283
|
+
await initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress);
|
|
284
|
+
} else {
|
|
285
|
+
await switchChainAndExecute(order.srcChain, handlePaymentProcess);
|
|
286
|
+
}
|
|
276
287
|
};
|
|
277
288
|
|
|
278
289
|
// When waitingForDeposit is true, we show a message to the user to wait for the deposit to be processed.
|
|
@@ -315,6 +326,18 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
315
326
|
|
|
316
327
|
const [showOrderDetails, setShowOrderDetails] = useState(false);
|
|
317
328
|
|
|
329
|
+
const isPhantomMobile = useMemo(() => navigator.userAgent.includes("Phantom"), []);
|
|
330
|
+
const isPhantomBrowser = useMemo(() => (window as any).phantom?.solana?.isPhantom, []);
|
|
331
|
+
|
|
332
|
+
// Get connected Phantom wallet address if available
|
|
333
|
+
const phantomWalletAddress = useMemo(() => {
|
|
334
|
+
const phantom = (window as any).phantom?.solana;
|
|
335
|
+
if (phantom?.isConnected && phantom?.publicKey) {
|
|
336
|
+
return phantom.publicKey.toString();
|
|
337
|
+
}
|
|
338
|
+
return null;
|
|
339
|
+
}, []);
|
|
340
|
+
|
|
318
341
|
if (!srcToken || !dstToken) {
|
|
319
342
|
return <div>Loading...</div>;
|
|
320
343
|
}
|
|
@@ -358,9 +381,171 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
358
381
|
return coinbaseUrl;
|
|
359
382
|
};
|
|
360
383
|
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
384
|
+
const initiatePhantomTransfer = async (amountLamports: string, tokenAddress: string, recipientAddress: string) => {
|
|
385
|
+
try {
|
|
386
|
+
if (!isPhantomBrowser && !isPhantomMobile) {
|
|
387
|
+
toast.error("Phantom wallet not installed. Please install Phantom wallet to continue.");
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Step 2: Ensure Phantom is connected/unlocked
|
|
392
|
+
const phantom = (window as any).phantom?.solana;
|
|
393
|
+
if (!phantom) {
|
|
394
|
+
toast.error("Phantom wallet not accessible");
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Connect and unlock wallet if needed
|
|
399
|
+
let publicKey;
|
|
400
|
+
try {
|
|
401
|
+
const connection = await phantom.connect();
|
|
402
|
+
publicKey = connection.publicKey;
|
|
403
|
+
} catch (connectError) {
|
|
404
|
+
toast.error("Failed to connect to Phantom wallet");
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Step 3: Create transaction with priority fees
|
|
409
|
+
const connection = new Connection("https://mainnet.helius-rpc.com/?api-key=efafd9b3-1807-4cf8-8aa4-3d984f56d8fb");
|
|
410
|
+
|
|
411
|
+
const fromPubkey = new PublicKey(publicKey.toString());
|
|
412
|
+
const toPubkey = new PublicKey(recipientAddress);
|
|
413
|
+
const amount = BigInt(amountLamports);
|
|
414
|
+
|
|
415
|
+
// Step 4: Get recent priority fees to determine optimal pricing
|
|
416
|
+
let priorityFee = 10000; // Default fallback (10,000 micro-lamports)
|
|
417
|
+
try {
|
|
418
|
+
const recentFees = await connection.getRecentPrioritizationFees({
|
|
419
|
+
lockedWritableAccounts: [fromPubkey],
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
if (recentFees && recentFees.length > 0) {
|
|
423
|
+
// Use 75th percentile of recent fees for good priority
|
|
424
|
+
const sortedFees = recentFees.map(fee => fee.prioritizationFee).sort((a, b) => a - b);
|
|
425
|
+
const percentile75Index = Math.floor(sortedFees.length * 0.75);
|
|
426
|
+
priorityFee = Math.max(sortedFees[percentile75Index] || 10000, 10000);
|
|
427
|
+
}
|
|
428
|
+
} catch (feeError) {
|
|
429
|
+
console.warn("Failed to fetch recent priority fees, using default:", feeError);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
let transaction: any;
|
|
433
|
+
|
|
434
|
+
// Check if this is native SOL transfer
|
|
435
|
+
if (tokenAddress === "11111111111111111111111111111111") {
|
|
436
|
+
// Native SOL transfer with priority fees
|
|
437
|
+
const computeUnitLimit = 1000; // SOL transfer + compute budget instructions need ~600-800 CU
|
|
438
|
+
const computeUnitPrice = Math.min(priorityFee, 100000); // Cap at 100k micro-lamports for safety
|
|
439
|
+
|
|
440
|
+
transaction = new Transaction()
|
|
441
|
+
.add(
|
|
442
|
+
// Set compute unit limit first (must come before other instructions)
|
|
443
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
444
|
+
units: computeUnitLimit,
|
|
445
|
+
}),
|
|
446
|
+
)
|
|
447
|
+
.add(
|
|
448
|
+
// Set priority fee
|
|
449
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
450
|
+
microLamports: computeUnitPrice,
|
|
451
|
+
}),
|
|
452
|
+
)
|
|
453
|
+
.add(
|
|
454
|
+
// Actual transfer instruction
|
|
455
|
+
SystemProgram.transfer({
|
|
456
|
+
fromPubkey,
|
|
457
|
+
toPubkey,
|
|
458
|
+
lamports: Number(amount),
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
console.log(`Using priority fee: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU`);
|
|
463
|
+
} else {
|
|
464
|
+
// SPL Token transfer with priority fees
|
|
465
|
+
const mintPubkey = new PublicKey(tokenAddress);
|
|
466
|
+
|
|
467
|
+
// Get associated token accounts
|
|
468
|
+
const fromTokenAccount = getAssociatedTokenAddressSync(mintPubkey, fromPubkey);
|
|
469
|
+
const toTokenAccount = getAssociatedTokenAddressSync(mintPubkey, toPubkey);
|
|
470
|
+
|
|
471
|
+
// Check if destination token account exists
|
|
472
|
+
const toTokenAccountInfo = await connection.getAccountInfo(toTokenAccount);
|
|
473
|
+
const needsDestinationAccount = !toTokenAccountInfo;
|
|
474
|
+
|
|
475
|
+
// Get mint info to determine decimals
|
|
476
|
+
const mintInfo = await connection.getParsedAccountInfo(mintPubkey);
|
|
477
|
+
const decimals = (mintInfo.value?.data as any)?.parsed?.info?.decimals || 9;
|
|
478
|
+
|
|
479
|
+
// SPL transfers need more compute units than SOL transfers
|
|
480
|
+
// Add extra CU if we need to create destination account
|
|
481
|
+
const computeUnitLimit = needsDestinationAccount ? 40000 : 20000;
|
|
482
|
+
const computeUnitPrice = Math.min(priorityFee, 100000);
|
|
483
|
+
|
|
484
|
+
// Create transfer instruction
|
|
485
|
+
const transferInstruction = createTransferCheckedInstruction(
|
|
486
|
+
fromTokenAccount,
|
|
487
|
+
mintPubkey,
|
|
488
|
+
toTokenAccount,
|
|
489
|
+
fromPubkey,
|
|
490
|
+
Number(amount),
|
|
491
|
+
decimals,
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
transaction = new Transaction()
|
|
495
|
+
.add(
|
|
496
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
497
|
+
units: computeUnitLimit,
|
|
498
|
+
}),
|
|
499
|
+
)
|
|
500
|
+
.add(
|
|
501
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
502
|
+
microLamports: computeUnitPrice,
|
|
503
|
+
}),
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// Add create destination account instruction if needed
|
|
507
|
+
if (needsDestinationAccount) {
|
|
508
|
+
transaction.add(
|
|
509
|
+
createAssociatedTokenAccountInstruction(
|
|
510
|
+
fromPubkey, // payer
|
|
511
|
+
toTokenAccount, // ata
|
|
512
|
+
toPubkey, // owner
|
|
513
|
+
mintPubkey, // mint
|
|
514
|
+
),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Add the transfer instruction
|
|
519
|
+
transaction.add(transferInstruction);
|
|
520
|
+
|
|
521
|
+
console.log(
|
|
522
|
+
`SPL Token transfer: ${computeUnitPrice} micro-lamports per CU, limit: ${computeUnitLimit} CU, creating destination: ${needsDestinationAccount}`,
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Step 5: Get latest blockhash and simulate transaction to verify
|
|
527
|
+
const { blockhash } = await connection.getLatestBlockhash("confirmed");
|
|
528
|
+
transaction.recentBlockhash = blockhash;
|
|
529
|
+
transaction.feePayer = fromPubkey;
|
|
530
|
+
|
|
531
|
+
// Step 6: Sign and send transaction with priority fees
|
|
532
|
+
const signedTransaction = await phantom.signAndSendTransaction(transaction);
|
|
533
|
+
|
|
534
|
+
toast.success(`Transaction successful! Signature: ${signedTransaction.signature}`);
|
|
535
|
+
console.log("Transaction sent with priority fees. Signature:", signedTransaction.signature);
|
|
536
|
+
} catch (error: unknown) {
|
|
537
|
+
console.error("Transfer error:", error);
|
|
538
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
539
|
+
if (errorMessage.includes("User rejected")) {
|
|
540
|
+
toast.error("Transaction was cancelled by user");
|
|
541
|
+
} else if (errorMessage.includes("insufficient")) {
|
|
542
|
+
toast.error("Insufficient balance for this transaction");
|
|
543
|
+
} else if (errorMessage.includes("blockhash not found")) {
|
|
544
|
+
toast.error("Network congestion detected. Please try again in a moment.");
|
|
545
|
+
} else {
|
|
546
|
+
toast.error(`Transfer failed: ${errorMessage}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
364
549
|
};
|
|
365
550
|
|
|
366
551
|
if (refundTxs) {
|
|
@@ -790,7 +975,7 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
790
975
|
</div>
|
|
791
976
|
</CopyToClipboard>
|
|
792
977
|
|
|
793
|
-
{account?.address && !showQRCode ? (
|
|
978
|
+
{(account?.address || phantomWalletAddress) && !showQRCode ? (
|
|
794
979
|
<div className="mb-4 mt-8 flex w-full flex-col items-center gap-4">
|
|
795
980
|
<div className="relative flex w-full flex-col items-center gap-2">
|
|
796
981
|
<ShinyButton
|
|
@@ -813,7 +998,10 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
813
998
|
)}
|
|
814
999
|
</ShinyButton>
|
|
815
1000
|
<span className="label-style text-as-primary/50 text-xs">
|
|
816
|
-
Connected to:
|
|
1001
|
+
Connected to:{" "}
|
|
1002
|
+
{order.srcChain === RELAY_SOLANA_MAINNET_CHAIN_ID && phantomWalletAddress
|
|
1003
|
+
? centerTruncate(phantomWalletAddress, 6)
|
|
1004
|
+
: centerTruncate(account?.address || "", 6)}
|
|
817
1005
|
</span>
|
|
818
1006
|
</div>
|
|
819
1007
|
|
|
@@ -883,12 +1071,16 @@ export const OrderDetails = memo(function OrderDetails({
|
|
|
883
1071
|
<WalletCoinbase className="ml-2 h-5 w-5" variant="branded" />
|
|
884
1072
|
</Button>
|
|
885
1073
|
</a>
|
|
886
|
-
<
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1074
|
+
<Button
|
|
1075
|
+
variant="outline"
|
|
1076
|
+
className="w-full"
|
|
1077
|
+
onClick={() =>
|
|
1078
|
+
initiatePhantomTransfer(order.srcAmount, order.srcTokenAddress, order.globalAddress)
|
|
1079
|
+
}
|
|
1080
|
+
>
|
|
1081
|
+
Open Phantom
|
|
1082
|
+
<WalletPhantom className="ml-2 h-5 w-5" variant="branded" />
|
|
1083
|
+
</Button>
|
|
892
1084
|
</div>
|
|
893
1085
|
</motion.div>
|
|
894
1086
|
)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Global window interface augmentations for AnySpend wallet providers
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface Window {
|
|
5
|
+
phantom?: {
|
|
6
|
+
solana?: {
|
|
7
|
+
isPhantom?: boolean;
|
|
8
|
+
connect: () => Promise<{ publicKey: { toString: () => string } }>;
|
|
9
|
+
signAndSendTransaction: (transaction: any) => Promise<{ signature: string }>;
|
|
10
|
+
};
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export {};
|