UNPKG

@drift-labs/sdk-browser

Version:
215 lines (214 loc) 9.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TitanClient = exports.SwapMode = void 0; const web3_js_1 = require("@solana/web3.js"); const msgpack_1 = require("@msgpack/msgpack"); var SwapMode; (function (SwapMode) { SwapMode["ExactIn"] = "ExactIn"; SwapMode["ExactOut"] = "ExactOut"; })(SwapMode || (exports.SwapMode = SwapMode = {})); const TITAN_API_URL = 'https://api.titan.exchange'; class TitanClient { constructor({ connection, authToken, url, }) { this.connection = connection; this.authToken = authToken; this.url = url !== null && url !== void 0 ? url : TITAN_API_URL; } /** * Get routes for a swap */ async getQuote({ inputMint, outputMint, amount, userPublicKey, maxAccounts = 50, // 50 is an estimated amount with buffer slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) { var _a; const params = new URLSearchParams({ inputMint: inputMint.toString(), outputMint: outputMint.toString(), amount: amount.toString(), userPublicKey: userPublicKey.toString(), ...(slippageBps && { slippageBps: slippageBps.toString() }), ...(swapMode && { swapMode: swapMode === 'ExactOut' ? SwapMode.ExactOut : SwapMode.ExactIn, }), ...(onlyDirectRoutes && { onlyDirectRoutes: onlyDirectRoutes.toString(), }), ...(maxAccounts && { accountsLimitTotal: maxAccounts.toString() }), ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }), ...(sizeConstraint && { sizeConstraint: sizeConstraint.toString() }), ...(accountsLimitWritable && { accountsLimitWritable: accountsLimitWritable.toString(), }), }); const response = await fetch(`${this.url}/api/v1/quote/swap?${params.toString()}`, { headers: { Accept: 'application/vnd.msgpack', 'Accept-Encoding': 'gzip, deflate, br', Authorization: `Bearer ${this.authToken}`, }, }); if (!response.ok) { throw new Error(`Titan API error: ${response.status} ${response.statusText}`); } const buffer = await response.arrayBuffer(); const data = (0, msgpack_1.decode)(buffer); const route = data.quotes[Object.keys(data.quotes).find((key) => key.toLowerCase() === 'titan') || '']; if (!route) { throw new Error('No routes available'); } return { inputMint: inputMint.toString(), inAmount: amount.toString(), outputMint: outputMint.toString(), outAmount: route.outAmount.toString(), swapMode: data.swapMode, slippageBps: route.slippageBps, platformFee: route.platformFee ? { amount: route.platformFee.amount.toString(), feeBps: route.platformFee.fee_bps, } : undefined, routePlan: ((_a = route.steps) === null || _a === void 0 ? void 0 : _a.map((step) => { var _a; return ({ swapInfo: { ammKey: new web3_js_1.PublicKey(step.ammKey).toString(), label: step.label, inputMint: new web3_js_1.PublicKey(step.inputMint).toString(), outputMint: new web3_js_1.PublicKey(step.outputMint).toString(), inAmount: step.inAmount.toString(), outAmount: step.outAmount.toString(), feeAmount: ((_a = step.feeAmount) === null || _a === void 0 ? void 0 : _a.toString()) || '0', feeMint: step.feeMint ? new web3_js_1.PublicKey(step.feeMint).toString() : '', }, percent: 100, }); })) || [], contextSlot: route.contextSlot, timeTaken: route.timeTaken, }; } /** * Get a swap transaction for quote */ async getSwap({ inputMint, outputMint, amount, userPublicKey, maxAccounts = 50, // 50 is an estimated amount with buffer slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) { const params = new URLSearchParams({ inputMint: inputMint.toString(), outputMint: outputMint.toString(), amount: amount.toString(), userPublicKey: userPublicKey.toString(), ...(slippageBps && { slippageBps: slippageBps.toString() }), ...(swapMode && { swapMode: swapMode }), ...(maxAccounts && { accountsLimitTotal: maxAccounts.toString() }), ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }), ...(onlyDirectRoutes && { onlyDirectRoutes: onlyDirectRoutes.toString(), }), ...(sizeConstraint && { sizeConstraint: sizeConstraint.toString() }), ...(accountsLimitWritable && { accountsLimitWritable: accountsLimitWritable.toString(), }), }); const response = await fetch(`${this.url}/api/v1/quote/swap?${params.toString()}`, { headers: { Accept: 'application/vnd.msgpack', 'Accept-Encoding': 'gzip, deflate, br', Authorization: `Bearer ${this.authToken}`, }, }); if (!response.ok) { if (response.status === 404) { throw new Error('No routes available'); } throw new Error(`Titan API error: ${response.status} ${response.statusText}`); } const buffer = await response.arrayBuffer(); const data = (0, msgpack_1.decode)(buffer); const route = data.quotes[Object.keys(data.quotes).find((key) => key.toLowerCase() === 'titan') || '']; if (!route) { throw new Error('No routes available'); } if (route.instructions && route.instructions.length > 0) { try { const { transactionMessage, lookupTables } = await this.getTransactionMessageAndLookupTables(route, userPublicKey); return { transactionMessage, lookupTables }; } catch (err) { throw new Error('Something went wrong with creating the Titan swap transaction. Please try again.'); } } throw new Error('No instructions provided in the route'); } /** * Get the titan instructions from transaction by filtering out instructions to compute budget and associated token programs * @param transactionMessage the transaction message * @param inputMint the input mint * @param outputMint the output mint */ getTitanInstructions({ transactionMessage, inputMint, outputMint, }) { // Filter out common system instructions that can be handled by DriftClient const filteredInstructions = transactionMessage.instructions.filter((instruction) => { const programId = instruction.programId.toString(); // Filter out system programs if (programId === 'ComputeBudget111111111111111111111111111111') { return false; } if (programId === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') { return false; } if (programId === '11111111111111111111111111111111') { return false; } // Filter out Associated Token Account creation for input/output mints if (programId === 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') { if (instruction.keys.length > 3) { const mint = instruction.keys[3].pubkey; if (mint.equals(inputMint) || mint.equals(outputMint)) { return false; } } } return true; }); return filteredInstructions; } async getTransactionMessageAndLookupTables(route, userPublicKey) { const solanaInstructions = route.instructions.map((instruction) => ({ programId: new web3_js_1.PublicKey(instruction.p), keys: instruction.a.map((meta) => ({ pubkey: new web3_js_1.PublicKey(meta.p), isSigner: meta.s, isWritable: meta.w, })), data: Buffer.from(instruction.d), })); // Get recent blockhash const { blockhash } = await this.connection.getLatestBlockhash(); // Build address lookup tables if provided const addressLookupTables = []; if (route.addressLookupTables && route.addressLookupTables.length > 0) { for (const altPubkey of route.addressLookupTables) { try { const altAccount = await this.connection.getAddressLookupTable(new web3_js_1.PublicKey(altPubkey)); if (altAccount.value) { addressLookupTables.push(altAccount.value); } } catch (err) { console.warn(`Failed to fetch address lookup table:`, err); } } } const transactionMessage = new web3_js_1.TransactionMessage({ payerKey: userPublicKey, recentBlockhash: blockhash, instructions: solanaInstructions, }); return { transactionMessage, lookupTables: addressLookupTables }; } } exports.TitanClient = TitanClient;