@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
215 lines (214 loc) • 9.8 kB
JavaScript
;
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;