UNPKG

@shogun-sdk/money-legos

Version:

Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.

266 lines (236 loc) 6.67 kB
import { LEGO_API, SOLANA_CHAIN_ID } from '@shogun-sdk/money-legos'; /** * Response data structure returned by the Lego API */ interface IResponseData { steps: IStep[]; details: IDetails; fees: IFees; } /** * Transaction details including sender, recipient and token information */ interface IDetails { sender: string; recipient: string; tokenIn: ITokenIn; } /** * Token information including amount, chain, and metadata */ interface ITokenIn { amountUsd: string; chainId: number; address: string; symbol: string; name: string; decimals: number; amount?: string; } /** * Fee information for transactions */ interface IFees { gas: ITokenIn; } /** * Individual transaction step with blockchain interaction details */ interface IStep { from: string; to: string; data: string; value: string; chainId: number; gas: string; maxFeePerGas: string; maxPriorityFeePerGas: string; } /** * Represents a single NFT item with its contract address and token ID */ interface INFTItem { address: string; tokenId: string; } /** * Represents the token being used for the purchase */ interface IToken { address: string; decimals: number; chainId: number; } /** * Input parameters required for fetching Lego data */ interface IFetchLegoProps { items: INFTItem[]; token: IToken; userAddress: string; recipientAddress?: string; } /** * Output structure returned by the MagicEden.SwapForNFT function */ interface ILegoResult { status: boolean; error?: string | null; data?: IResponseData | null; refetch: () => Promise<ILegoResult>; } /** * Error structure returned by the API */ interface IApiError { statusCode: number; message: string; error: string; } /** * Configuration options for the LegoClient */ interface ILegoClientConfig { /** * API key for authentication */ apiKey: string; } /** * Client for interacting with the Lego API * * @example * ```typescript * const legoClient = new LegoClient({ apiKey: 'your-api-key' }); * const result = await legoClient.MagicEden.SwapForNFT({ items, token, userAddress, recipientAddress }); * const updatedResult = await result.refetch(); * ``` */ export class LegoClient { private apiKey: string; private activeControllers: Map<string, AbortController> = new Map(); /** * Creates a new LegoClient instance * * @param {LegoClientConfig} config - Configuration options including API key */ constructor(config: ILegoClientConfig) { this.apiKey = config.apiKey; } /** * Generates a unique request ID for tracking purposes * * @param {IFetchLegoProps} props - The fetch parameters * @returns {string} - A unique identifier for this request * @private */ private generateRequestId(props: IFetchLegoProps): string { const { items, token, userAddress } = props; const itemsStr = items.map((item) => `${item.address}-${item.tokenId}`).join('|'); return `${userAddress}-${token.address}-${itemsStr}`; } /** * MagicEden marketplace operations */ MagicEden = { /** * Swaps tokens for NFTs on MagicEden with support for refetching * * @param {IFetchLegoProps} props - Input parameters containing NFT items, token, and user address * @returns {Promise<ILegoResult>} - Result containing status, data, error message, and refetch function */ SwapForNFT: async (props: IFetchLegoProps): Promise<ILegoResult> => { const { items, token, userAddress, recipientAddress } = props; const requestId = this.generateRequestId(props); // Create refetch function that will be returned with the result const refetch = async (): Promise<ILegoResult> => { return this.MagicEden.SwapForNFT(props); }; // ✅ Validate required parameters if (!items.length || !token.address || !userAddress) { return { status: false, error: 'Invalid parameters provided', data: null, refetch, }; } const isSolanaTokenIn = token.chainId === SOLANA_CHAIN_ID; // ✅ Solana requires recipient address if (isSolanaTokenIn && !recipientAddress) { return { status: false, error: 'Recipient address is required for Solana transactions.', data: null, refetch, }; } // Cancel any ongoing request with the same ID if (this.activeControllers.has(requestId)) { this.activeControllers.get(requestId)?.abort(); this.activeControllers.delete(requestId); } // Create a new AbortController for this request const controller = new AbortController(); this.activeControllers.set(requestId, controller); try { // ✅ Construct the request payload conditionally const requestBody = JSON.stringify({ tokenIn: { address: token.address, decimals: token.decimals, chainId: token.chainId, }, nft: items, address: userAddress, ...(isSolanaTokenIn || recipientAddress ? { recipient: recipientAddress } : {}), }); // ✅ Send API request const response = await fetch(`${LEGO_API}/nft/purchase`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey, }, body: requestBody, signal: controller.signal, }); // Clean up controller reference after request this.activeControllers.delete(requestId); // ✅ Handle non-200 responses if (!response.ok) { const errorData: IApiError = await response.json(); throw new Error(errorData.message || response.statusText); } // ✅ Parse response const result = await response.json(); // ✅ Return successful result return { status: !!result?.data?.execute, error: null, data: result?.data?.execute || null, refetch, }; } catch (err) { // Clean up controller reference this.activeControllers.delete(requestId); // ✅ Ignore abort errors if (err instanceof DOMException && err.name === 'AbortError') { return { status: false, error: 'Request aborted', data: null, refetch, }; } // ✅ Return error result return { status: false, error: err instanceof Error ? err.message : 'Unknown error', data: null, refetch, }; } }, }; }