UNPKG

@shogun-sdk/one-shot

Version:

Shogun SDK - One Shot: React Components and hooks for cross-chain swaps

195 lines (167 loc) 5.18 kB
import { LEGO_API } from '@shogun-sdk/money-legos'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useShogunLego } from './../contexts/ShogunLegoContext.js'; /* ============================== 🔹 Type Definitions & Interfaces ============================== */ /** Response structure returned from the API */ interface ResponseData { steps: Step[]; details: Details; fees: Fees; } /** Transaction details including sender, recipient, and token info */ interface Details { sender: string; recipient: string; tokenIn: TokenIn; } /** Represents a token, including metadata and amount */ interface TokenIn { amountUsd: string; chainId: number; address: string; symbol: string; name: string; decimals: number; amount?: string; } /** Fee structure for transactions */ interface Fees { gas: TokenIn; } /** Step object representing a blockchain transaction */ interface Step { from: string; to: string; data: string; value: string; chainId: number; gas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; } /** Represents an NFT item with contract address and token ID */ interface NFTItem { address: string; tokenId: string; } /** Defines the token being used for the transaction */ interface Token { address: string; decimals: number; chainId: number; } /** Input parameters required for `useLego` */ interface UseLegoProps { items: NFTItem[]; token: Token; userAddress: string; } /** Output structure of `useLego` */ interface UseLegoResult { status: boolean; error?: string | null; data?: ResponseData | null; isLoading: boolean; refetch: () => void; } /* ============================== 🔹 useLego Hook ============================== */ /** * Hook to validate and prepare transaction data for purchasing NFTs. * * @param {UseLegoProps} props - The NFT items, token details, and user address. * @returns {UseLegoResult} - Status, error message (if any), response data, loading state, and a refetch function. */ export const useLego = ({ items, token, userAddress }: UseLegoProps): UseLegoResult => { const { API_KEY } = useShogunLego(); // ✅ State for handling API responses, errors, and loading status const [data, setData] = useState<ResponseData | null>(null); const [error, setError] = useState<string | null>(null); const [isLoading, setIsLoading] = useState<boolean>(false); // ✅ Ref to manage API request cancellation const abortControllerRef = useRef<AbortController | null>(null); // ✅ Memoized API request body to prevent unnecessary recalculations const requestBody = useMemo(() => { if (!items.length || !token.address || !userAddress) return null; return JSON.stringify({ tokenIn: { address: token.address, decimals: token.decimals, chainId: token.chainId, }, nft: items, address: userAddress, }); }, [items, token.address, token.decimals, token.chainId, userAddress]); // ✅ Function to fetch data from the API const fetchData = useCallback(async () => { if (!requestBody) { setError('Invalid parameters provided to useLego hook.'); return; } // Cancel any ongoing request before making a new one if (abortControllerRef.current) { abortControllerRef.current.abort(); } // Create a new AbortController for the current request const abortController = new AbortController(); abortControllerRef.current = abortController; setIsLoading(true); setError(null); try { const response = await fetch(`${LEGO_API}/nft/purchase`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY, }, body: requestBody, signal: abortController.signal, // ✅ Allows request cancellation }); // Prevent state updates if request was aborted if (abortController.signal.aborted) { return; } if (!response.ok) { throw new Error(`API Error: ${response.statusText}`); } const result = await response.json(); setData(result?.data?.execute); } catch (err) { if (err instanceof Error && err.name !== 'AbortError') { console.error('NFT Purchase API Error:', err); setError(err.message); } } finally { // Ensure loading state resets only if this is the latest request if (abortControllerRef.current === abortController) { setIsLoading(false); abortControllerRef.current = null; } } }, [API_KEY, requestBody]); // ✅ Auto-fetch data when dependencies change useEffect(() => { if (requestBody) { fetchData(); } // Cleanup function to abort any ongoing requests when dependencies change return () => { if (abortControllerRef.current) { abortControllerRef.current.abort(); abortControllerRef.current = null; } }; }, [fetchData, requestBody]); // ✅ Return hook state and refetch function return { status: !error && !!data, error, data, isLoading, refetch: fetchData, }; };