delegate-framework
Version:
A TypeScript framework for building robust, production-ready blockchain workflows with comprehensive error handling, logging, and testing. Maintained by delegate.fun
282 lines • 11.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Liquidator = void 0;
const web3_js_1 = require("@solana/web3.js");
const spl_token_1 = require("@solana/spl-token");
const base_delegate_1 = require("./base-delegate");
const jupiter_1 = require("./swap/jupiter");
const raydium_1 = require("./swap/raydium");
class Liquidator extends base_delegate_1.BaseDelegate {
constructor(connection, signerKeypair, heliusClient, feeTakerKeypair) {
super(connection, signerKeypair, feeTakerKeypair);
this.heliusClient = heliusClient;
// Initialize swap protocols
this.jupiterSwap = new jupiter_1.JupiterSwap(signerKeypair, connection, { heliusClient });
this.raydiumSwap = new raydium_1.RaydiumSwap(signerKeypair, { heliusClient });
}
async executeDelegate(delegateOptions) {
const requestId = this.generateRequestId();
try {
this.logOperation('liquidator_execution_started', { requestId });
this.validateOptions(delegateOptions);
const { delegateAddress, tokenAddress, minUsdValue = 1 } = delegateOptions;
this.logOperation('liquidator_setup', {
requestId,
delegateAddress,
targetToken: tokenAddress,
minUsdValue
});
// Get all token accounts for the delegate address
const tokenAccounts = await this.getTokenAccounts(delegateAddress);
if (!tokenAccounts || tokenAccounts.length === 0) {
this.logOperation('no_token_accounts_found', { requestId, delegateAddress });
return {
success: true,
signatures: [],
liquidatedTokens: [],
totalLiquidated: 0
};
}
const signatures = [];
const liquidatedTokens = [];
let totalLiquidated = 0;
// Process each token account
for (let i = 0; i < tokenAccounts.length; i++) {
const tokenAccount = tokenAccounts[i];
if (!tokenAccount) {
this.logOperation('token_account_skipped', {
requestId,
reason: 'undefined_account',
index: i
});
continue;
}
// Skip frozen accounts
if (tokenAccount.frozen) {
this.logOperation('token_account_skipped', {
requestId,
mint: tokenAccount.mint,
reason: 'frozen_account'
});
continue;
}
// Skip USDC (common stablecoin)
if (tokenAccount.mint === "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") {
this.logOperation('token_account_skipped', {
requestId,
mint: tokenAccount.mint,
reason: 'usdc_excluded'
});
continue;
}
try {
const result = await this.processTokenLiquidation(tokenAccount, tokenAddress, minUsdValue, i + 1, tokenAccounts.length, requestId);
if (result.success && result.signature) {
signatures.push(result.signature);
liquidatedTokens.push({
mint: tokenAccount.mint,
amount: tokenAccount.amount,
signature: result.signature
});
totalLiquidated++;
}
this.logOperation('token_liquidation_processed', {
requestId,
mint: tokenAccount.mint,
amount: tokenAccount.amount,
success: result.success,
signature: result.signature
});
}
catch (error) {
await this.handleError(error instanceof Error ? error : new Error(String(error)), {
requestId,
mint: tokenAccount.mint
});
// Continue with next token account instead of failing completely
this.logOperation('token_liquidation_skipped', {
requestId,
mint: tokenAccount.mint,
error: error instanceof Error ? error.message : String(error)
});
continue;
}
}
this.logOperation('liquidator_execution_completed', {
requestId,
signatures,
totalLiquidated
});
return {
success: true,
signatures,
liquidatedTokens,
totalLiquidated
};
}
catch (error) {
await this.handleError(error instanceof Error ? error : new Error(String(error)), { requestId });
throw error;
}
}
validateOptions(delegateOptions) {
this.validateRequiredField(delegateOptions.delegateAddress, 'delegateAddress');
this.validatePublicKey(delegateOptions.delegateAddress, 'delegateAddress');
this.validateRequiredField(delegateOptions.tokenAddress, 'tokenAddress');
this.validatePublicKey(delegateOptions.tokenAddress, 'tokenAddress');
if (delegateOptions.minUsdValue !== undefined) {
this.validateNumberField(delegateOptions.minUsdValue, 'minUsdValue', 0);
}
}
async getTokenAccounts(delegateAddress) {
try {
const response = await this.heliusClient.getTokenAccounts(new web3_js_1.PublicKey(delegateAddress));
return response.value || [];
}
catch (error) {
this.logOperation('get_token_accounts_failed', {
delegateAddress,
error: error instanceof Error ? error.message : String(error)
});
return [];
}
}
async processTokenLiquidation(tokenAccount, targetTokenAddress, minUsdValue, currentIndex, totalAccounts, requestId) {
const { mint: tokenMint, amount: tokenAmount } = tokenAccount;
this.logOperation('liquidation_progress', {
requestId,
currentIndex,
totalAccounts,
mint: tokenMint,
amount: tokenAmount
});
// Get token decimals
const decimals = await this.getTokenDecimals(tokenMint);
if (decimals === -1) {
throw new Error(`Failed to get decimals for token ${tokenMint}`);
}
// Calculate token amount in human readable format
const tokenAmountInUnits = tokenAmount / Math.pow(10, decimals);
// Try to get quote from Jupiter first
let quote = null;
try {
quote = await this.jupiterSwap.getQuote(tokenMint, targetTokenAddress, tokenAmountInUnits, 0.5);
}
catch (error) {
this.logOperation('jupiter_quote_failed', {
requestId,
fromToken: tokenMint,
toToken: targetTokenAddress,
amount: tokenAmountInUnits,
error: error instanceof Error ? error.message : String(error)
});
}
// If Jupiter quote failed or price is too low, try Raydium
if (!quote || (quote.swapUsdValue && quote.swapUsdValue < minUsdValue)) {
try {
quote = await this.raydiumSwap.getQuote(tokenMint, targetTokenAddress, tokenAmountInUnits, 0.5);
}
catch (error) {
this.logOperation('raydium_quote_failed', {
requestId,
fromToken: tokenMint,
toToken: targetTokenAddress,
amount: tokenAmountInUnits,
error: error instanceof Error ? error.message : String(error)
});
}
}
// If no quote available or price is too low, skip
if (!quote || (quote.swapUsdValue && quote.swapUsdValue < minUsdValue)) {
this.logOperation('liquidation_skipped_low_value', {
requestId,
mint: tokenMint,
usdValue: quote?.swapUsdValue || 0,
minUsdValue
});
return { success: false, error: 'Price too low or no quote available' };
}
// Execute swap with fallback
const swapResult = await this.executeSwapWithFallback(tokenMint, targetTokenAddress, tokenAmountInUnits, 0.5 // 0.5% slippage
);
return swapResult;
}
async executeSwapWithFallback(fromAsset, toAsset, amount, slippage) {
// Try Jupiter first
try {
const result = await this.retryOperation(() => this.executeJupiterSwap(fromAsset, toAsset, amount, slippage), 3);
if (result.success) {
return { ...result, signature: result.signature };
}
}
catch (error) {
this.logOperation('jupiter_swap_failed', {
fromAsset,
toAsset,
amount,
error: error instanceof Error ? error.message : String(error)
});
}
// Fallback to Raydium
try {
const result = await this.retryOperation(() => this.executeRaydiumSwap(fromAsset, toAsset, amount, slippage), 3);
if (result.success) {
return { ...result, signature: result.signature };
}
}
catch (error) {
this.logOperation('raydium_swap_failed', {
fromAsset,
toAsset,
amount,
error: error instanceof Error ? error.message : String(error)
});
}
return {
success: false,
error: "Both Jupiter and Raydium swaps failed"
};
}
async executeJupiterSwap(fromAsset, toAsset, amount, slippage) {
// Get quote
const quote = await this.jupiterSwap.getQuote(fromAsset, toAsset, amount, slippage);
if (!quote) {
throw new Error("Failed to get Jupiter quote");
}
// Create transaction
const transaction = await this.jupiterSwap.createSwapTransaction(quote);
// Execute swap
const result = await this.jupiterSwap.executeSwap(transaction);
return result;
}
async executeRaydiumSwap(fromAsset, toAsset, amount, slippage) {
// Get quote
const quote = await this.raydiumSwap.getQuote(fromAsset, toAsset, amount, slippage);
if (!quote) {
throw new Error("Failed to get Raydium quote");
}
// Create transaction
const transaction = await this.raydiumSwap.createSwapTransaction(quote);
// Execute swap
const result = await this.raydiumSwap.executeSwap(transaction);
return result;
}
async getTokenDecimals(tokenAddress) {
try {
const mint = new web3_js_1.PublicKey(tokenAddress);
const mintInfo = await this.retryOperation(async () => {
return await (0, spl_token_1.getMint)(this.connection, mint);
}, 3);
return mintInfo.decimals;
}
catch (error) {
this.logOperation('get_token_decimals_failed', {
tokenAddress,
error: error instanceof Error ? error.message : String(error)
});
return -1;
}
}
}
exports.Liquidator = Liquidator;
//# sourceMappingURL=liquidator.js.map