@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
text/typescript
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,
};
}
},
};
}