UNPKG

wgm-swap-components

Version:
292 lines (291 loc) 18.8 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useState, useEffect } from 'react'; import { CHAIN_ID, DEFAULT_PROTOCOLS, DEFAULT_EXCHANGE_RATE, RELOAD_COUNTDOWN_MAX } from './constants'; import { TokenInput } from './TokenInput'; import { SwapButton } from './SwapButton'; import { TokenSelectModal } from './TokenSelectModal'; import { useWagmiIntegration } from './hooks/useWagmiIntegration'; import { useFetchQuote } from './hooks/useFetchQuote'; import { useTokens } from './hooks/useTokens'; import { formatUnits } from 'viem'; import { waitForTransactionReceipt } from '@wagmi/core'; import toast, { Toaster } from 'react-hot-toast'; /** * Complete Swap Form Component */ export var SwapForm = function (_a) { var config = _a.config; var _b = useTokens(''), fetchedTokens = _b.tokens, isLoadingTokens = _b.isLoading; var slippageTolerance = 0.5; var _c = useState(null), fromToken = _c[0], setFromToken = _c[1]; var _d = useState(null), toToken = _d[0], setToToken = _d[1]; useEffect(function () { if (fetchedTokens && fetchedTokens.length > 0) { if (!fromToken && fetchedTokens[0]) { setFromToken(fetchedTokens[0]); } if (!toToken && fetchedTokens[1]) { setToToken(fetchedTokens[1]); } } }, [fetchedTokens, fromToken, toToken]); var _e = useState(''), fromAmount = _e[0], setFromAmount = _e[1]; var _f = useState(''), toAmount = _f[0], setToAmount = _f[1]; var _g = useState(false), isLoading = _g[0], setIsLoading = _g[1]; var _h = useState(null), exchangeRate = _h[0], setExchangeRate = _h[1]; var _j = useState(false), isModalOpen = _j[0], setIsModalOpen = _j[1]; var _k = useState('from'), modalType = _k[0], setModalType = _k[1]; var _l = useState(DEFAULT_PROTOCOLS), protocols = _l[0], setProtocols = _l[1]; var _m = useState(false), priceImpactWarning = _m[0], setPriceImpactWarning = _m[1]; var _o = useState(RELOAD_COUNTDOWN_MAX), reloadCountdown = _o[0], setReloadCountdown = _o[1]; // Wagmi integration for balance and swap functionality var _p = useWagmiIntegration({ token: fromToken, config: config }), fromTokenBalance = _p.balance, isWagmiLoading = _p.isLoading, isConnected = _p.isConnected, address = _p.address, chainId = _p.chainId, isWrongNetwork = _p.isWrongNetwork, switchToCorrectNetwork = _p.switchToCorrectNetwork, executeSwap = _p.executeSwap, wrapIP = _p.wrapIP, unwrapWIP = _p.unwrapWIP, checkAllowance = _p.checkAllowance, approveToken = _p.approveToken; // Fetch quote for regular swaps (not wrap/unwrap) var isWrapOrUnwrap = ((fromToken === null || fromToken === void 0 ? void 0 : fromToken.symbol) === 'IP' && (toToken === null || toToken === void 0 ? void 0 : toToken.symbol) === 'WIP') || ((fromToken === null || fromToken === void 0 ? void 0 : fromToken.symbol) === 'WIP' && (toToken === null || toToken === void 0 ? void 0 : toToken.symbol) === 'IP'); var _q = useFetchQuote({ tokenIn: fromToken, tokenOut: toToken, amountIn: fromAmount, chainId: chainId || CHAIN_ID, protocols: protocols.join(','), enabled: !isWrapOrUnwrap, }), quoteData = _q.data, isQuoteLoading = _q.isLoading, quoteError = _q.error, refetchQuote = _q.refetch; var getTokenExchangeRate = function (amountIn, amountOut) { try { if (!amountIn || !amountOut || parseFloat(amountIn) <= 0 || parseFloat(amountOut) <= 0) { return null; } return parseFloat(amountOut) / parseFloat(amountIn); } catch (_a) { return null; } }; // Handle quote data and calculate amounts useEffect(function () { if (!fromToken || !toToken || !fromAmount || parseFloat(fromAmount) <= 0) { setToAmount(''); setExchangeRate(null); setPriceImpactWarning(false); return; } // Handle wrap/unwrap operations (1:1 ratio) if (fromToken.symbol === 'IP' && toToken.symbol === 'WIP') { setToAmount(fromAmount); setExchangeRate(DEFAULT_EXCHANGE_RATE); setPriceImpactWarning(false); return; } if (fromToken.symbol === 'WIP' && toToken.symbol === 'IP') { setToAmount(fromAmount); setExchangeRate(DEFAULT_EXCHANGE_RATE); setPriceImpactWarning(false); return; } // Handle regular swap with quote data if ((quoteData === null || quoteData === void 0 ? void 0 : quoteData.quote) && (toToken === null || toToken === void 0 ? void 0 : toToken.decimals)) { var quote = quoteData.quote; // Calculate total amountOut from all routes var totalAmountOut = quote.route.reduce(function (total, path) { var lastStep = path[path.length - 1]; if (lastStep) { var amount = parseFloat(formatUnits(BigInt(lastStep.amountOut || '0'), toToken.decimals || 18)); return total + amount; } return total; }, 0); var formattedTotalAmountOut = totalAmountOut.toFixed(4); setToAmount(formattedTotalAmountOut); var rate = getTokenExchangeRate(fromAmount, formattedTotalAmountOut); setExchangeRate(rate); // Check price impact warning var priceImpact = parseFloat(quote.priceImpact || '0'); setPriceImpactWarning(priceImpact > 5); // 5% threshold } else { // Clear amounts if no quote available setToAmount(''); setExchangeRate(null); } }, [fromToken, toToken, fromAmount, quoteData, isQuoteLoading, quoteError]); // Auto-reload quote useEffect(function () { if (!fromToken || !toToken || !fromAmount || parseFloat(fromAmount) <= 0) { return; } var interval = setInterval(function () { setReloadCountdown(function (prev) { if (prev === 1) { refetchQuote(); return RELOAD_COUNTDOWN_MAX; } return prev - 1; }); }, 1000); return function () { return clearInterval(interval); }; }, [fromToken, toToken, fromAmount, refetchQuote]); var handleReloadClick = function () { refetchQuote(); setReloadCountdown(RELOAD_COUNTDOWN_MAX); }; var handleMaxClick = function () { // Use wagmi balance if available, otherwise fallback to token balance var availableBalance = fromTokenBalance || (fromToken === null || fromToken === void 0 ? void 0 : fromToken.balance) || '0'; setFromAmount(availableBalance); }; var handleTokenSelect = function (tokenType) { setModalType(tokenType); setIsModalOpen(true); }; var handleModalTokenSelect = function (token) { if (modalType === 'from') { if ((toToken === null || toToken === void 0 ? void 0 : toToken.address) === token.address) { setToToken(fromToken); // Swap them } setFromToken(token); } else { // modalType is 'to' if ((fromToken === null || fromToken === void 0 ? void 0 : fromToken.address) === token.address) { setFromToken(toToken); // Swap them } setToToken(token); } // Reset amounts after any selection setFromAmount(''); setToAmount(''); setIsModalOpen(false); }; var handleCloseModal = function () { setIsModalOpen(false); }; var handleSwap = function () { return __awaiter(void 0, void 0, void 0, function () { var switchNetwork, userBalance, swapAmount, hash, receipt, error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!fromToken || !toToken || !fromAmount || parseFloat(fromAmount) <= 0) { return [2 /*return*/]; } // Check if wallet is connected if (!isConnected || !address) { alert('Please connect your wallet first'); return [2 /*return*/]; } if (!isWrongNetwork) return [3 /*break*/, 2]; switchNetwork = window.confirm('You are on the wrong network. Switch to the correct network?'); if (!switchNetwork) return [3 /*break*/, 2]; return [4 /*yield*/, switchToCorrectNetwork()]; case 1: _a.sent(); return [2 /*return*/]; case 2: userBalance = parseFloat(fromTokenBalance || '0'); swapAmount = parseFloat(fromAmount); if (swapAmount > userBalance) { alert("Insufficient balance. You have ".concat(userBalance, " ").concat(fromToken.symbol)); return [2 /*return*/]; } setIsLoading(true); _a.label = 3; case 3: _a.trys.push([3, 13, 14, 15]); hash = null; if (!(fromToken.symbol === 'IP' && toToken.symbol === 'WIP')) return [3 /*break*/, 5]; return [4 /*yield*/, wrapIP(fromAmount)]; case 4: hash = _a.sent(); return [3 /*break*/, 9]; case 5: if (!(fromToken.symbol === 'WIP' && toToken.symbol === 'IP')) return [3 /*break*/, 7]; return [4 /*yield*/, unwrapWIP(fromAmount)]; case 6: hash = _a.sent(); return [3 /*break*/, 9]; case 7: return [4 /*yield*/, executeSwap(fromToken, toToken, fromAmount, quoteData === null || quoteData === void 0 ? void 0 : quoteData.quote)]; case 8: // Regular swap with quote data hash = _a.sent(); _a.label = 9; case 9: if (!(hash && config)) return [3 /*break*/, 11]; return [4 /*yield*/, waitForTransactionReceipt(config, { hash: hash })]; case 10: receipt = _a.sent(); if (receipt.status === 'success') { toast.success('Swap successful'); setFromAmount(''); setToAmount(''); } else { toast.error('Swap failed'); } return [3 /*break*/, 12]; case 11: toast.error('Swap failed. Please try again.'); _a.label = 12; case 12: return [3 /*break*/, 15]; case 13: error_1 = _a.sent(); console.error('Swap failed:', error_1); toast.error('Swap failed. Please check the console for details.'); return [3 /*break*/, 15]; case 14: setIsLoading(false); return [7 /*endfinally*/]; case 15: return [2 /*return*/]; } }); }); }; var canSwap = fromToken && toToken && fromAmount && parseFloat(fromAmount) > 0 && !isLoading && isConnected && (isWrapOrUnwrap || !!(quoteData === null || quoteData === void 0 ? void 0 : quoteData.quote)); var formClasses = "bg-white rounded-2xl shadow-xl border border-gray-200 p-6 max-w-md mx-auto".trim(); return (_jsxs(_Fragment, { children: [_jsx(Toaster, {}), _jsxs("div", { className: formClasses, children: [_jsx("div", { className: "flex justify-end mb-4", children: _jsx("a", { href: "https://ag.mimboku.com/?tab=SWAP", target: "_blank", rel: "noopener noreferrer", className: "flex gap-2 items-center text-blue-600 hover:underline font-medium", children: "Mimboku" }) }), _jsx("div", { className: "", children: _jsx(TokenInput, { label: "From", backgroundColor: "#1F1F23", textColor: "white", symbol: (fromToken === null || fromToken === void 0 ? void 0 : fromToken.symbol) || 'Select Token', name: (fromToken === null || fromToken === void 0 ? void 0 : fromToken.name) || 'Choose a token', logoUrl: fromToken === null || fromToken === void 0 ? void 0 : fromToken.logoUrl, value: fromAmount, balance: fromTokenBalance || (fromToken === null || fromToken === void 0 ? void 0 : fromToken.balance), onChange: setFromAmount, onMaxClick: handleMaxClick, onTokenSelect: function () { return handleTokenSelect('from'); } }) }), _jsx("div", { className: "mb-6", children: _jsx(TokenInput, { label: "To", backgroundColor: "#D4D4D4", textColor: "black", symbol: (toToken === null || toToken === void 0 ? void 0 : toToken.symbol) || 'Select Token', name: (toToken === null || toToken === void 0 ? void 0 : toToken.name) || 'Choose a token', logoUrl: toToken === null || toToken === void 0 ? void 0 : toToken.logoUrl, value: toAmount, onChange: setToAmount, onTokenSelect: function () { return handleTokenSelect('to'); }, disabled: true }) }), exchangeRate && fromToken && toToken && (_jsxs("div", { className: "mb-4 p-3 bg-gray-50 rounded-lg", children: [_jsxs("div", { className: "flex justify-between items-center text-sm", children: [_jsxs("div", { className: "flex items-center", children: [_jsx("span", { className: "text-gray-600 mr-2", children: "Rate" }), _jsxs("span", { className: "font-medium", children: ["1 ", fromToken.symbol, " = ", exchangeRate.toFixed(6), " ", toToken.symbol] })] }), _jsxs("div", { className: "flex items-center", children: [_jsx("button", { onClick: handleReloadClick, className: "mr-2 p-1 hover:bg-gray-200 rounded-full", children: _jsx("svg", { className: "w-4 h-4 text-gray-600 ".concat(isQuoteLoading ? 'animate-spin' : ''), fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "M4 4v5h5M20 20v-5h-5M4 4l16 16" }) }) }), _jsxs("span", { className: "text-xs text-gray-500", children: [reloadCountdown, "s"] })] })] }), _jsxs("div", { className: "flex justify-between items-center text-sm mt-1", children: [_jsx("span", { className: "text-gray-600", children: "Slippage Tolerance" }), _jsxs("span", { className: "font-medium", children: [slippageTolerance, "%"] })] }), priceImpactWarning && (_jsx("div", { className: "text-red-500 text-xs mt-1", children: "High price impact warning!" }))] })), _jsx(SwapButton, { variant: "primary", size: "medium", fullWidth: true, disabled: !canSwap, loading: isLoading || isWagmiLoading || isQuoteLoading, onClick: handleSwap, children: !isConnected ? 'Connect Wallet' : isWrongNetwork ? 'Wrong Network' : isQuoteLoading ? 'Fetching Quote...' : isLoading || isWagmiLoading ? 'Processing...' : (fromToken === null || fromToken === void 0 ? void 0 : fromToken.symbol) === 'IP' && (toToken === null || toToken === void 0 ? void 0 : toToken.symbol) === 'WIP' ? 'Wrap IP' : (fromToken === null || fromToken === void 0 ? void 0 : fromToken.symbol) === 'WIP' && (toToken === null || toToken === void 0 ? void 0 : toToken.symbol) === 'IP' ? 'Unwrap WIP' : 'Swap Tokens' }), _jsx(TokenSelectModal, { isOpen: isModalOpen, onClose: handleCloseModal, onSelectToken: handleModalTokenSelect, selectedToken: modalType === 'from' ? fromToken : toToken, excludeToken: modalType === 'from' ? toToken : fromToken })] })] })); }; export default SwapForm;