delegate-framework
Version:
A TypeScript framework for building robust, production-ready blockchain workflows with comprehensive error handling, logging, and testing. Maintained by delegate.fun
158 lines • 6.97 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JupiterClient = void 0;
const web3_js_1 = require("@solana/web3.js");
const error_handling_1 = require("../../utils/error-handling");
class JupiterClient {
constructor(config) {
this.requestId = 0;
this.config = {
quoteApiUrl: JupiterClient.DEFAULT_QUOTE_API_URL,
timeout: JupiterClient.DEFAULT_TIMEOUT,
retries: JupiterClient.DEFAULT_RETRIES,
...config,
};
this.logger = this.config.logger;
}
/**
* Get a quote for swapping tokens
* @param fromMint - The input token mint
* @param toMint - The output token mint
* @param amount - The amount to swap (in smallest unit)
* @param slippageBps - Slippage tolerance in basis points (default: 100 = 1%)
* @returns Quote response from Jupiter
*/
async getQuote(fromMint, toMint, amount, slippageBps = 100) {
// Validate inputs
if (!fromMint || !toMint) {
(0, error_handling_1.throwError)('Invalid mint addresses provided', 'Jupiter Quote Error');
}
if (!amount || Number(amount) <= 0) {
(0, error_handling_1.throwError)('Invalid amount provided', 'Jupiter Quote Error');
}
if (slippageBps < 0 || slippageBps > 10000) {
(0, error_handling_1.throwError)('Slippage must be between 0 and 10000 basis points', 'Jupiter Quote Error');
}
return this.makeRequest(async () => {
const url = `${this.config['quoteApiUrl']}/quote?inputMint=${fromMint.toBase58()}&outputMint=${toMint.toBase58()}&amount=${amount}&slippageBps=${slippageBps}`;
this.logger?.debug('Requesting Jupiter quote', {
fromMint: fromMint.toBase58(),
toMint: toMint.toBase58(),
amount,
slippageBps,
url,
});
const response = await fetch(url);
if (!response || typeof response.ok !== 'boolean') {
throw new Error('No response received from Jupiter');
}
if (!response.ok) {
(0, error_handling_1.throwError)(`HTTP ${response.status}: ${response.statusText}`, 'Jupiter API Error');
}
const quoteResponse = await response.json();
this.logger?.debug('Jupiter quote received', {
inAmount: quoteResponse.inAmount,
outAmount: quoteResponse.outAmount,
priceImpactPct: quoteResponse.priceImpactPct,
otherAmountThreshold: quoteResponse.otherAmountThreshold,
});
return quoteResponse;
}, 'getQuote');
}
/**
* Get a swap transaction from Jupiter
* @param quoteResponse - The quote response from getQuote
* @param swapParams - Additional swap parameters
* @returns Versioned transaction ready for signing
*/
async getSwapTransaction(quoteResponse, swapParams) {
if (!quoteResponse) {
(0, error_handling_1.throwError)('Quote response is required', 'Jupiter Swap Error');
}
if (!swapParams) {
(0, error_handling_1.throwError)('Swap parameters are required', 'Jupiter Swap Error');
}
return this.makeRequest(async () => {
this.logger?.debug('Requesting Jupiter swap transaction', {
userPublicKey: swapParams.userPublicKey,
});
const response = await fetch(`${this.config['quoteApiUrl']}/swap`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(swapParams),
});
if (!response || typeof response.ok !== 'boolean') {
throw new Error('No response received from Jupiter');
}
if (!response.ok) {
const errorText = await response.text();
(0, error_handling_1.throwError)(`HTTP ${response.status}: ${errorText}`, 'Jupiter Swap Error');
}
const { swapTransaction } = await response.json();
if (!swapTransaction) {
(0, error_handling_1.throwError)('No swap transaction received from Jupiter', 'Jupiter Swap Error');
}
this.logger?.debug('Jupiter swap transaction received', {
transactionSize: swapTransaction.length,
});
return web3_js_1.VersionedTransaction.deserialize(Buffer.from(swapTransaction, 'base64'));
}, 'getSwapTransaction');
}
/**
* Make a request with retry logic and error handling
* @param operation - The operation to perform
* @param operationName - Name of the operation for logging
* @returns Result of the operation
*/
async makeRequest(operation, operationName) {
const requestId = ++this.requestId;
this.logger?.debug(`Request ${requestId} started: ${operationName}`);
let lastError;
for (let attempt = 1; attempt <= this.config['retries']; attempt++) {
try {
const result = await Promise.race([
operation(),
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`Operation timed out after ${this.config['timeout']}ms`));
}, this.config['timeout']);
}),
]);
this.logger?.debug(`Request ${requestId} completed: ${operationName}`, { result });
return result;
}
catch (error) {
// If error is not an Error instance, wrap it
lastError = error instanceof Error ? error : new Error(String(error));
this.logger?.warn(`Request ${requestId} attempt ${attempt} failed: ${operationName}`, lastError);
if (attempt === this.config['retries']) {
this.logger?.error(`Request ${requestId} failed after ${attempt} attempts: ${operationName}`, lastError);
(0, error_handling_1.throwError)(lastError, `Jupiter API Request Failed (${operationName})`);
}
await this.delay(Math.pow(2, attempt - 1) * 1000);
}
}
throw lastError;
}
/**
* Utility method for delays
* @param ms - Milliseconds to delay
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* Get the current configuration
* @returns Current client configuration
*/
getConfig() {
return this.config;
}
}
exports.JupiterClient = JupiterClient;
JupiterClient.DEFAULT_TIMEOUT = 30000;
JupiterClient.DEFAULT_RETRIES = 3;
JupiterClient.DEFAULT_QUOTE_API_URL = "https://quote-api.jup.ag/v6";
//# sourceMappingURL=jupiter.js.map