@drift-labs/sdk 2.145.0-beta.0 → 2.145.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/lib/browser/driftClient.d.ts +25 -3
- package/lib/browser/driftClient.js +77 -2
- package/lib/browser/index.d.ts +1 -1
- package/lib/browser/index.js +3 -3
- package/lib/browser/jupiter/jupiterClient.d.ts +1 -1
- package/lib/browser/swap/UnifiedSwapClient.d.ts +86 -0
- package/lib/browser/swap/UnifiedSwapClient.js +179 -0
- package/lib/node/driftClient.d.ts +25 -3
- package/lib/node/driftClient.d.ts.map +1 -1
- package/lib/node/driftClient.js +77 -2
- package/lib/node/index.d.ts +1 -1
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -3
- package/lib/node/jupiter/jupiterClient.d.ts +1 -1
- package/lib/node/jupiter/jupiterClient.d.ts.map +1 -1
- package/lib/node/swap/UnifiedSwapClient.d.ts +87 -0
- package/lib/node/swap/UnifiedSwapClient.d.ts.map +1 -0
- package/lib/node/swap/UnifiedSwapClient.js +179 -0
- package/package.json +1 -1
- package/src/driftClient.ts +152 -9
- package/src/index.ts +2 -1
- package/src/jupiter/jupiterClient.ts +1 -2
- package/src/swap/UnifiedSwapClient.ts +293 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnifiedSwapClient = void 0;
|
|
4
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
5
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
6
|
+
const jupiterClient_1 = require("../jupiter/jupiterClient");
|
|
7
|
+
const titanClient_1 = require("../titan/titanClient");
|
|
8
|
+
class UnifiedSwapClient {
|
|
9
|
+
constructor({ clientType, connection, authToken, url, }) {
|
|
10
|
+
this.clientType = clientType;
|
|
11
|
+
if (clientType === 'jupiter') {
|
|
12
|
+
this.client = new jupiterClient_1.JupiterClient({
|
|
13
|
+
connection,
|
|
14
|
+
url,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else if (clientType === 'titan') {
|
|
18
|
+
if (!authToken) {
|
|
19
|
+
throw new Error('authToken is required for Titan client');
|
|
20
|
+
}
|
|
21
|
+
this.client = new titanClient_1.TitanClient({
|
|
22
|
+
connection,
|
|
23
|
+
authToken,
|
|
24
|
+
url,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
throw new Error(`Unsupported client type: ${clientType}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get a swap quote from the underlying client
|
|
33
|
+
*/
|
|
34
|
+
async getQuote(params) {
|
|
35
|
+
if (this.clientType === 'jupiter') {
|
|
36
|
+
const jupiterClient = this.client;
|
|
37
|
+
const { userPublicKey: _userPublicKey, // Not needed for Jupiter
|
|
38
|
+
sizeConstraint: _sizeConstraint, // Jupiter-specific params to exclude
|
|
39
|
+
accountsLimitWritable: _accountsLimitWritable, ...jupiterParams } = params;
|
|
40
|
+
return await jupiterClient.getQuote(jupiterParams);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const titanClient = this.client;
|
|
44
|
+
const { autoSlippage: _autoSlippage, // Titan-specific params to exclude
|
|
45
|
+
maxAutoSlippageBps: _maxAutoSlippageBps, usdEstimate: _usdEstimate, ...titanParams } = params;
|
|
46
|
+
if (!titanParams.userPublicKey) {
|
|
47
|
+
throw new Error('userPublicKey is required for Titan quotes');
|
|
48
|
+
}
|
|
49
|
+
// Cast to ensure TypeScript knows userPublicKey is defined
|
|
50
|
+
const titanParamsWithUser = {
|
|
51
|
+
...titanParams,
|
|
52
|
+
userPublicKey: titanParams.userPublicKey,
|
|
53
|
+
swapMode: titanParams.swapMode, // Titan expects string
|
|
54
|
+
};
|
|
55
|
+
return await titanClient.getQuote(titanParamsWithUser);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get a swap transaction from the underlying client
|
|
60
|
+
*/
|
|
61
|
+
async getSwap(params) {
|
|
62
|
+
if (this.clientType === 'jupiter') {
|
|
63
|
+
const jupiterClient = this.client;
|
|
64
|
+
// Cast the quote to Jupiter's QuoteResponse type
|
|
65
|
+
const jupiterParams = {
|
|
66
|
+
...params,
|
|
67
|
+
quote: params.quote,
|
|
68
|
+
};
|
|
69
|
+
const transaction = await jupiterClient.getSwap(jupiterParams);
|
|
70
|
+
return { transaction };
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const titanClient = this.client;
|
|
74
|
+
const { quote, userPublicKey, slippageBps } = params;
|
|
75
|
+
// For Titan, we need to reconstruct the parameters from the quote
|
|
76
|
+
const titanQuote = quote;
|
|
77
|
+
const result = await titanClient.getSwap({
|
|
78
|
+
inputMint: new web3_js_1.PublicKey(titanQuote.inputMint),
|
|
79
|
+
outputMint: new web3_js_1.PublicKey(titanQuote.outputMint),
|
|
80
|
+
amount: new anchor_1.BN(titanQuote.inAmount),
|
|
81
|
+
userPublicKey,
|
|
82
|
+
slippageBps: slippageBps || titanQuote.slippageBps,
|
|
83
|
+
swapMode: titanQuote.swapMode,
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
transactionMessage: result.transactionMessage,
|
|
87
|
+
lookupTables: result.lookupTables,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get swap instructions from the underlying client (Jupiter or Titan)
|
|
93
|
+
* This is the core swap logic without any context preparation
|
|
94
|
+
*/
|
|
95
|
+
async getSwapInstructions({ inputMint, outputMint, amount, userPublicKey, slippageBps, swapMode = 'ExactIn', onlyDirectRoutes = false, quote, sizeConstraint, }) {
|
|
96
|
+
const isExactOut = swapMode === 'ExactOut';
|
|
97
|
+
let swapInstructions;
|
|
98
|
+
let lookupTables;
|
|
99
|
+
if (this.clientType === 'jupiter') {
|
|
100
|
+
const jupiterClient = this.client;
|
|
101
|
+
// Get quote if not provided
|
|
102
|
+
let finalQuote = quote;
|
|
103
|
+
if (!finalQuote) {
|
|
104
|
+
finalQuote = await jupiterClient.getQuote({
|
|
105
|
+
inputMint,
|
|
106
|
+
outputMint,
|
|
107
|
+
amount,
|
|
108
|
+
slippageBps,
|
|
109
|
+
swapMode,
|
|
110
|
+
onlyDirectRoutes,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (!finalQuote) {
|
|
114
|
+
throw new Error("Could not fetch Jupiter's quote. Please try again.");
|
|
115
|
+
}
|
|
116
|
+
// Get swap transaction and extract instructions
|
|
117
|
+
const transaction = await jupiterClient.getSwap({
|
|
118
|
+
quote: finalQuote,
|
|
119
|
+
userPublicKey,
|
|
120
|
+
slippageBps,
|
|
121
|
+
});
|
|
122
|
+
const { transactionMessage, lookupTables: jupiterLookupTables } = await jupiterClient.getTransactionMessageAndLookupTables({
|
|
123
|
+
transaction,
|
|
124
|
+
});
|
|
125
|
+
swapInstructions = jupiterClient.getJupiterInstructions({
|
|
126
|
+
transactionMessage,
|
|
127
|
+
inputMint,
|
|
128
|
+
outputMint,
|
|
129
|
+
});
|
|
130
|
+
lookupTables = jupiterLookupTables;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const titanClient = this.client;
|
|
134
|
+
// For Titan, get swap directly (it handles quote internally)
|
|
135
|
+
const { transactionMessage, lookupTables: titanLookupTables } = await titanClient.getSwap({
|
|
136
|
+
inputMint,
|
|
137
|
+
outputMint,
|
|
138
|
+
amount,
|
|
139
|
+
userPublicKey,
|
|
140
|
+
slippageBps,
|
|
141
|
+
swapMode: isExactOut ? titanClient_1.SwapMode.ExactOut : titanClient_1.SwapMode.ExactIn,
|
|
142
|
+
onlyDirectRoutes,
|
|
143
|
+
sizeConstraint: sizeConstraint || 1280 - 375, // MAX_TX_BYTE_SIZE - buffer for drift instructions
|
|
144
|
+
});
|
|
145
|
+
swapInstructions = titanClient.getTitanInstructions({
|
|
146
|
+
transactionMessage,
|
|
147
|
+
inputMint,
|
|
148
|
+
outputMint,
|
|
149
|
+
});
|
|
150
|
+
lookupTables = titanLookupTables;
|
|
151
|
+
}
|
|
152
|
+
return { instructions: swapInstructions, lookupTables };
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Get the underlying client instance
|
|
156
|
+
*/
|
|
157
|
+
getClient() {
|
|
158
|
+
return this.client;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get the client type
|
|
162
|
+
*/
|
|
163
|
+
getClientType() {
|
|
164
|
+
return this.clientType;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Check if this is a Jupiter client
|
|
168
|
+
*/
|
|
169
|
+
isJupiter() {
|
|
170
|
+
return this.clientType === 'jupiter';
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if this is a Titan client
|
|
174
|
+
*/
|
|
175
|
+
isTitan() {
|
|
176
|
+
return this.clientType === 'titan';
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
exports.UnifiedSwapClient = UnifiedSwapClient;
|
package/package.json
CHANGED
package/src/driftClient.ts
CHANGED
|
@@ -157,11 +157,8 @@ import { isSpotPositionAvailable } from './math/spotPosition';
|
|
|
157
157
|
import { calculateMarketMaxAvailableInsurance } from './math/market';
|
|
158
158
|
import { fetchUserStatsAccount } from './accounts/fetch';
|
|
159
159
|
import { castNumberToSpotPrecision } from './math/spotMarket';
|
|
160
|
-
import {
|
|
161
|
-
|
|
162
|
-
QuoteResponse,
|
|
163
|
-
SwapMode,
|
|
164
|
-
} from './jupiter/jupiterClient';
|
|
160
|
+
import { JupiterClient, QuoteResponse } from './jupiter/jupiterClient';
|
|
161
|
+
import { SwapMode } from './swap/UnifiedSwapClient';
|
|
165
162
|
import { getNonIdleUserFilter } from './memcmp';
|
|
166
163
|
import { UserStatsSubscriptionConfig } from './userStatsConfig';
|
|
167
164
|
import { getMarinadeDepositIx, getMarinadeFinanceProgram } from './marinade';
|
|
@@ -210,9 +207,11 @@ import {
|
|
|
210
207
|
isBuilderOrderCompleted,
|
|
211
208
|
} from './math/builder';
|
|
212
209
|
import { TitanClient, SwapMode as TitanSwapMode } from './titan/titanClient';
|
|
210
|
+
import { UnifiedSwapClient } from './swap/UnifiedSwapClient';
|
|
213
211
|
|
|
214
212
|
/**
|
|
215
|
-
* Union type for swap clients (Titan and Jupiter)
|
|
213
|
+
* Union type for swap clients (Titan and Jupiter) - Legacy type
|
|
214
|
+
* @deprecated Use UnifiedSwapClient class instead
|
|
216
215
|
*/
|
|
217
216
|
export type SwapClient = TitanClient | JupiterClient;
|
|
218
217
|
|
|
@@ -5772,7 +5771,7 @@ export class DriftClient {
|
|
|
5772
5771
|
quote,
|
|
5773
5772
|
onlyDirectRoutes = false,
|
|
5774
5773
|
}: {
|
|
5775
|
-
swapClient: SwapClient;
|
|
5774
|
+
swapClient: UnifiedSwapClient | SwapClient;
|
|
5776
5775
|
outMarketIndex: number;
|
|
5777
5776
|
inMarketIndex: number;
|
|
5778
5777
|
outAssociatedTokenAccount?: PublicKey;
|
|
@@ -5793,7 +5792,23 @@ export class DriftClient {
|
|
|
5793
5792
|
lookupTables: AddressLookupTableAccount[];
|
|
5794
5793
|
};
|
|
5795
5794
|
|
|
5796
|
-
|
|
5795
|
+
// Use unified SwapClient if available
|
|
5796
|
+
if (swapClient instanceof UnifiedSwapClient) {
|
|
5797
|
+
res = await this.getSwapIxV2({
|
|
5798
|
+
swapClient,
|
|
5799
|
+
outMarketIndex,
|
|
5800
|
+
inMarketIndex,
|
|
5801
|
+
outAssociatedTokenAccount,
|
|
5802
|
+
inAssociatedTokenAccount,
|
|
5803
|
+
amount,
|
|
5804
|
+
slippageBps,
|
|
5805
|
+
swapMode,
|
|
5806
|
+
onlyDirectRoutes,
|
|
5807
|
+
reduceOnly,
|
|
5808
|
+
quote,
|
|
5809
|
+
v6,
|
|
5810
|
+
});
|
|
5811
|
+
} else if (swapClient instanceof TitanClient) {
|
|
5797
5812
|
res = await this.getTitanSwapIx({
|
|
5798
5813
|
titanClient: swapClient,
|
|
5799
5814
|
outMarketIndex,
|
|
@@ -5823,7 +5838,7 @@ export class DriftClient {
|
|
|
5823
5838
|
});
|
|
5824
5839
|
} else {
|
|
5825
5840
|
throw new Error(
|
|
5826
|
-
'Invalid swap client type. Must be TitanClient or JupiterClient.'
|
|
5841
|
+
'Invalid swap client type. Must be SwapClient, TitanClient, or JupiterClient.'
|
|
5827
5842
|
);
|
|
5828
5843
|
}
|
|
5829
5844
|
|
|
@@ -6243,6 +6258,134 @@ export class DriftClient {
|
|
|
6243
6258
|
return { beginSwapIx, endSwapIx };
|
|
6244
6259
|
}
|
|
6245
6260
|
|
|
6261
|
+
public async getSwapIxV2({
|
|
6262
|
+
swapClient,
|
|
6263
|
+
outMarketIndex,
|
|
6264
|
+
inMarketIndex,
|
|
6265
|
+
outAssociatedTokenAccount,
|
|
6266
|
+
inAssociatedTokenAccount,
|
|
6267
|
+
amount,
|
|
6268
|
+
slippageBps,
|
|
6269
|
+
swapMode,
|
|
6270
|
+
onlyDirectRoutes,
|
|
6271
|
+
reduceOnly,
|
|
6272
|
+
quote,
|
|
6273
|
+
v6,
|
|
6274
|
+
}: {
|
|
6275
|
+
swapClient: UnifiedSwapClient;
|
|
6276
|
+
outMarketIndex: number;
|
|
6277
|
+
inMarketIndex: number;
|
|
6278
|
+
outAssociatedTokenAccount?: PublicKey;
|
|
6279
|
+
inAssociatedTokenAccount?: PublicKey;
|
|
6280
|
+
amount: BN;
|
|
6281
|
+
slippageBps?: number;
|
|
6282
|
+
swapMode?: SwapMode;
|
|
6283
|
+
onlyDirectRoutes?: boolean;
|
|
6284
|
+
reduceOnly?: SwapReduceOnly;
|
|
6285
|
+
quote?: QuoteResponse;
|
|
6286
|
+
v6?: {
|
|
6287
|
+
quote?: QuoteResponse;
|
|
6288
|
+
};
|
|
6289
|
+
}): Promise<{
|
|
6290
|
+
ixs: TransactionInstruction[];
|
|
6291
|
+
lookupTables: AddressLookupTableAccount[];
|
|
6292
|
+
}> {
|
|
6293
|
+
// Get market accounts to determine mints
|
|
6294
|
+
const outMarket = this.getSpotMarketAccount(outMarketIndex);
|
|
6295
|
+
const inMarket = this.getSpotMarketAccount(inMarketIndex);
|
|
6296
|
+
|
|
6297
|
+
const isExactOut = swapMode === 'ExactOut';
|
|
6298
|
+
const exactOutBufferedAmountIn = amount.muln(1001).divn(1000); // Add 10bp buffer
|
|
6299
|
+
|
|
6300
|
+
const preInstructions: TransactionInstruction[] = [];
|
|
6301
|
+
|
|
6302
|
+
// Handle token accounts if not provided
|
|
6303
|
+
let finalOutAssociatedTokenAccount = outAssociatedTokenAccount;
|
|
6304
|
+
let finalInAssociatedTokenAccount = inAssociatedTokenAccount;
|
|
6305
|
+
|
|
6306
|
+
if (!finalOutAssociatedTokenAccount) {
|
|
6307
|
+
const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
|
|
6308
|
+
finalOutAssociatedTokenAccount = await this.getAssociatedTokenAccount(
|
|
6309
|
+
outMarket.marketIndex,
|
|
6310
|
+
false,
|
|
6311
|
+
tokenProgram
|
|
6312
|
+
);
|
|
6313
|
+
|
|
6314
|
+
const accountInfo = await this.connection.getAccountInfo(
|
|
6315
|
+
finalOutAssociatedTokenAccount
|
|
6316
|
+
);
|
|
6317
|
+
if (!accountInfo) {
|
|
6318
|
+
preInstructions.push(
|
|
6319
|
+
this.createAssociatedTokenAccountIdempotentInstruction(
|
|
6320
|
+
finalOutAssociatedTokenAccount,
|
|
6321
|
+
this.provider.wallet.publicKey,
|
|
6322
|
+
this.provider.wallet.publicKey,
|
|
6323
|
+
outMarket.mint,
|
|
6324
|
+
tokenProgram
|
|
6325
|
+
)
|
|
6326
|
+
);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
|
|
6330
|
+
if (!finalInAssociatedTokenAccount) {
|
|
6331
|
+
const tokenProgram = this.getTokenProgramForSpotMarket(inMarket);
|
|
6332
|
+
finalInAssociatedTokenAccount = await this.getAssociatedTokenAccount(
|
|
6333
|
+
inMarket.marketIndex,
|
|
6334
|
+
false,
|
|
6335
|
+
tokenProgram
|
|
6336
|
+
);
|
|
6337
|
+
|
|
6338
|
+
const accountInfo = await this.connection.getAccountInfo(
|
|
6339
|
+
finalInAssociatedTokenAccount
|
|
6340
|
+
);
|
|
6341
|
+
if (!accountInfo) {
|
|
6342
|
+
preInstructions.push(
|
|
6343
|
+
this.createAssociatedTokenAccountIdempotentInstruction(
|
|
6344
|
+
finalInAssociatedTokenAccount,
|
|
6345
|
+
this.provider.wallet.publicKey,
|
|
6346
|
+
this.provider.wallet.publicKey,
|
|
6347
|
+
inMarket.mint,
|
|
6348
|
+
tokenProgram
|
|
6349
|
+
)
|
|
6350
|
+
);
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6354
|
+
// Get drift swap instructions for begin and end
|
|
6355
|
+
const { beginSwapIx, endSwapIx } = await this.getSwapIx({
|
|
6356
|
+
outMarketIndex,
|
|
6357
|
+
inMarketIndex,
|
|
6358
|
+
amountIn: isExactOut ? exactOutBufferedAmountIn : amount,
|
|
6359
|
+
inTokenAccount: finalInAssociatedTokenAccount,
|
|
6360
|
+
outTokenAccount: finalOutAssociatedTokenAccount,
|
|
6361
|
+
reduceOnly,
|
|
6362
|
+
});
|
|
6363
|
+
|
|
6364
|
+
// Get core swap instructions from SwapClient
|
|
6365
|
+
const swapResult = await swapClient.getSwapInstructions({
|
|
6366
|
+
inputMint: inMarket.mint,
|
|
6367
|
+
outputMint: outMarket.mint,
|
|
6368
|
+
amount,
|
|
6369
|
+
userPublicKey: this.provider.wallet.publicKey,
|
|
6370
|
+
slippageBps,
|
|
6371
|
+
swapMode,
|
|
6372
|
+
onlyDirectRoutes,
|
|
6373
|
+
quote: quote ?? v6?.quote,
|
|
6374
|
+
});
|
|
6375
|
+
|
|
6376
|
+
const allInstructions = [
|
|
6377
|
+
...preInstructions,
|
|
6378
|
+
beginSwapIx,
|
|
6379
|
+
...swapResult.instructions,
|
|
6380
|
+
endSwapIx,
|
|
6381
|
+
];
|
|
6382
|
+
|
|
6383
|
+
return {
|
|
6384
|
+
ixs: allInstructions,
|
|
6385
|
+
lookupTables: swapResult.lookupTables,
|
|
6386
|
+
};
|
|
6387
|
+
}
|
|
6388
|
+
|
|
6246
6389
|
public async stakeForMSOL({ amount }: { amount: BN }): Promise<TxSigAndSlot> {
|
|
6247
6390
|
const ixs = await this.getStakeForMSOLIx({ amount });
|
|
6248
6391
|
const tx = await this.buildTransaction(ixs);
|
package/src/index.ts
CHANGED
|
@@ -53,7 +53,8 @@ export * from './events/webSocketLogProvider';
|
|
|
53
53
|
export * from './events/parse';
|
|
54
54
|
export * from './events/pollingLogProvider';
|
|
55
55
|
export * from './jupiter/jupiterClient';
|
|
56
|
-
|
|
56
|
+
// Primary swap client interface - use this for all swap operations
|
|
57
|
+
export * from './swap/UnifiedSwapClient';
|
|
57
58
|
export * from './math/auction';
|
|
58
59
|
export * from './math/builder';
|
|
59
60
|
export * from './math/spotMarket';
|
|
@@ -8,8 +8,7 @@ import {
|
|
|
8
8
|
} from '@solana/web3.js';
|
|
9
9
|
import fetch from 'node-fetch';
|
|
10
10
|
import { BN } from '@coral-xyz/anchor';
|
|
11
|
-
|
|
12
|
-
export type SwapMode = 'ExactIn' | 'ExactOut';
|
|
11
|
+
import { SwapMode } from '../swap/UnifiedSwapClient';
|
|
13
12
|
|
|
14
13
|
export interface MarketInfo {
|
|
15
14
|
id: string;
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection,
|
|
3
|
+
PublicKey,
|
|
4
|
+
TransactionMessage,
|
|
5
|
+
AddressLookupTableAccount,
|
|
6
|
+
VersionedTransaction,
|
|
7
|
+
TransactionInstruction,
|
|
8
|
+
} from '@solana/web3.js';
|
|
9
|
+
import { BN } from '@coral-xyz/anchor';
|
|
10
|
+
import {
|
|
11
|
+
JupiterClient,
|
|
12
|
+
QuoteResponse as JupiterQuoteResponse,
|
|
13
|
+
} from '../jupiter/jupiterClient';
|
|
14
|
+
import {
|
|
15
|
+
TitanClient,
|
|
16
|
+
QuoteResponse as TitanQuoteResponse,
|
|
17
|
+
SwapMode as TitanSwapMode,
|
|
18
|
+
} from '../titan/titanClient';
|
|
19
|
+
|
|
20
|
+
export type SwapMode = 'ExactIn' | 'ExactOut';
|
|
21
|
+
export type SwapClientType = 'jupiter' | 'titan';
|
|
22
|
+
|
|
23
|
+
export type UnifiedQuoteResponse = JupiterQuoteResponse | TitanQuoteResponse;
|
|
24
|
+
|
|
25
|
+
export interface SwapQuoteParams {
|
|
26
|
+
inputMint: PublicKey;
|
|
27
|
+
outputMint: PublicKey;
|
|
28
|
+
amount: BN;
|
|
29
|
+
userPublicKey?: PublicKey; // Required for Titan, optional for Jupiter
|
|
30
|
+
maxAccounts?: number;
|
|
31
|
+
slippageBps?: number;
|
|
32
|
+
swapMode?: SwapMode;
|
|
33
|
+
onlyDirectRoutes?: boolean;
|
|
34
|
+
excludeDexes?: string[];
|
|
35
|
+
sizeConstraint?: number; // Titan-specific
|
|
36
|
+
accountsLimitWritable?: number; // Titan-specific
|
|
37
|
+
autoSlippage?: boolean; // Jupiter-specific
|
|
38
|
+
maxAutoSlippageBps?: number; // Jupiter-specific
|
|
39
|
+
usdEstimate?: number; // Jupiter-specific
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface SwapTransactionParams {
|
|
43
|
+
quote: UnifiedQuoteResponse;
|
|
44
|
+
userPublicKey: PublicKey;
|
|
45
|
+
slippageBps?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface SwapTransactionResult {
|
|
49
|
+
transaction?: VersionedTransaction; // Jupiter returns this
|
|
50
|
+
transactionMessage?: TransactionMessage; // Titan returns this
|
|
51
|
+
lookupTables?: AddressLookupTableAccount[]; // Titan returns this
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export class UnifiedSwapClient {
|
|
55
|
+
private client: JupiterClient | TitanClient;
|
|
56
|
+
private clientType: SwapClientType;
|
|
57
|
+
|
|
58
|
+
constructor({
|
|
59
|
+
clientType,
|
|
60
|
+
connection,
|
|
61
|
+
authToken,
|
|
62
|
+
url,
|
|
63
|
+
}: {
|
|
64
|
+
clientType: SwapClientType;
|
|
65
|
+
connection: Connection;
|
|
66
|
+
authToken?: string; // Required for Titan, optional for Jupiter
|
|
67
|
+
url?: string; // Optional custom URL
|
|
68
|
+
}) {
|
|
69
|
+
this.clientType = clientType;
|
|
70
|
+
|
|
71
|
+
if (clientType === 'jupiter') {
|
|
72
|
+
this.client = new JupiterClient({
|
|
73
|
+
connection,
|
|
74
|
+
url,
|
|
75
|
+
});
|
|
76
|
+
} else if (clientType === 'titan') {
|
|
77
|
+
if (!authToken) {
|
|
78
|
+
throw new Error('authToken is required for Titan client');
|
|
79
|
+
}
|
|
80
|
+
this.client = new TitanClient({
|
|
81
|
+
connection,
|
|
82
|
+
authToken,
|
|
83
|
+
url,
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
throw new Error(`Unsupported client type: ${clientType}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get a swap quote from the underlying client
|
|
92
|
+
*/
|
|
93
|
+
public async getQuote(
|
|
94
|
+
params: SwapQuoteParams
|
|
95
|
+
): Promise<UnifiedQuoteResponse> {
|
|
96
|
+
if (this.clientType === 'jupiter') {
|
|
97
|
+
const jupiterClient = this.client as JupiterClient;
|
|
98
|
+
const {
|
|
99
|
+
userPublicKey: _userPublicKey, // Not needed for Jupiter
|
|
100
|
+
sizeConstraint: _sizeConstraint, // Jupiter-specific params to exclude
|
|
101
|
+
accountsLimitWritable: _accountsLimitWritable,
|
|
102
|
+
...jupiterParams
|
|
103
|
+
} = params;
|
|
104
|
+
|
|
105
|
+
return await jupiterClient.getQuote(jupiterParams);
|
|
106
|
+
} else {
|
|
107
|
+
const titanClient = this.client as TitanClient;
|
|
108
|
+
const {
|
|
109
|
+
autoSlippage: _autoSlippage, // Titan-specific params to exclude
|
|
110
|
+
maxAutoSlippageBps: _maxAutoSlippageBps,
|
|
111
|
+
usdEstimate: _usdEstimate,
|
|
112
|
+
...titanParams
|
|
113
|
+
} = params;
|
|
114
|
+
|
|
115
|
+
if (!titanParams.userPublicKey) {
|
|
116
|
+
throw new Error('userPublicKey is required for Titan quotes');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Cast to ensure TypeScript knows userPublicKey is defined
|
|
120
|
+
const titanParamsWithUser = {
|
|
121
|
+
...titanParams,
|
|
122
|
+
userPublicKey: titanParams.userPublicKey,
|
|
123
|
+
swapMode: titanParams.swapMode as string, // Titan expects string
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
return await titanClient.getQuote(titanParamsWithUser);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Get a swap transaction from the underlying client
|
|
132
|
+
*/
|
|
133
|
+
public async getSwap(
|
|
134
|
+
params: SwapTransactionParams
|
|
135
|
+
): Promise<SwapTransactionResult> {
|
|
136
|
+
if (this.clientType === 'jupiter') {
|
|
137
|
+
const jupiterClient = this.client as JupiterClient;
|
|
138
|
+
// Cast the quote to Jupiter's QuoteResponse type
|
|
139
|
+
const jupiterParams = {
|
|
140
|
+
...params,
|
|
141
|
+
quote: params.quote as JupiterQuoteResponse,
|
|
142
|
+
};
|
|
143
|
+
const transaction = await jupiterClient.getSwap(jupiterParams);
|
|
144
|
+
return { transaction };
|
|
145
|
+
} else {
|
|
146
|
+
const titanClient = this.client as TitanClient;
|
|
147
|
+
const { quote, userPublicKey, slippageBps } = params;
|
|
148
|
+
|
|
149
|
+
// For Titan, we need to reconstruct the parameters from the quote
|
|
150
|
+
const titanQuote = quote as TitanQuoteResponse;
|
|
151
|
+
const result = await titanClient.getSwap({
|
|
152
|
+
inputMint: new PublicKey(titanQuote.inputMint),
|
|
153
|
+
outputMint: new PublicKey(titanQuote.outputMint),
|
|
154
|
+
amount: new BN(titanQuote.inAmount),
|
|
155
|
+
userPublicKey,
|
|
156
|
+
slippageBps: slippageBps || titanQuote.slippageBps,
|
|
157
|
+
swapMode: titanQuote.swapMode,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
transactionMessage: result.transactionMessage,
|
|
162
|
+
lookupTables: result.lookupTables,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get swap instructions from the underlying client (Jupiter or Titan)
|
|
169
|
+
* This is the core swap logic without any context preparation
|
|
170
|
+
*/
|
|
171
|
+
public async getSwapInstructions({
|
|
172
|
+
inputMint,
|
|
173
|
+
outputMint,
|
|
174
|
+
amount,
|
|
175
|
+
userPublicKey,
|
|
176
|
+
slippageBps,
|
|
177
|
+
swapMode = 'ExactIn',
|
|
178
|
+
onlyDirectRoutes = false,
|
|
179
|
+
quote,
|
|
180
|
+
sizeConstraint,
|
|
181
|
+
}: {
|
|
182
|
+
inputMint: PublicKey;
|
|
183
|
+
outputMint: PublicKey;
|
|
184
|
+
amount: BN;
|
|
185
|
+
userPublicKey: PublicKey;
|
|
186
|
+
slippageBps?: number;
|
|
187
|
+
swapMode?: SwapMode;
|
|
188
|
+
onlyDirectRoutes?: boolean;
|
|
189
|
+
quote?: UnifiedQuoteResponse;
|
|
190
|
+
sizeConstraint?: number;
|
|
191
|
+
}): Promise<{
|
|
192
|
+
instructions: TransactionInstruction[];
|
|
193
|
+
lookupTables: AddressLookupTableAccount[];
|
|
194
|
+
}> {
|
|
195
|
+
const isExactOut = swapMode === 'ExactOut';
|
|
196
|
+
let swapInstructions: TransactionInstruction[];
|
|
197
|
+
let lookupTables: AddressLookupTableAccount[];
|
|
198
|
+
|
|
199
|
+
if (this.clientType === 'jupiter') {
|
|
200
|
+
const jupiterClient = this.client as JupiterClient;
|
|
201
|
+
|
|
202
|
+
// Get quote if not provided
|
|
203
|
+
let finalQuote = quote as JupiterQuoteResponse;
|
|
204
|
+
if (!finalQuote) {
|
|
205
|
+
finalQuote = await jupiterClient.getQuote({
|
|
206
|
+
inputMint,
|
|
207
|
+
outputMint,
|
|
208
|
+
amount,
|
|
209
|
+
slippageBps,
|
|
210
|
+
swapMode,
|
|
211
|
+
onlyDirectRoutes,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!finalQuote) {
|
|
216
|
+
throw new Error("Could not fetch Jupiter's quote. Please try again.");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Get swap transaction and extract instructions
|
|
220
|
+
const transaction = await jupiterClient.getSwap({
|
|
221
|
+
quote: finalQuote,
|
|
222
|
+
userPublicKey,
|
|
223
|
+
slippageBps,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const { transactionMessage, lookupTables: jupiterLookupTables } =
|
|
227
|
+
await jupiterClient.getTransactionMessageAndLookupTables({
|
|
228
|
+
transaction,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
swapInstructions = jupiterClient.getJupiterInstructions({
|
|
232
|
+
transactionMessage,
|
|
233
|
+
inputMint,
|
|
234
|
+
outputMint,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
lookupTables = jupiterLookupTables;
|
|
238
|
+
} else {
|
|
239
|
+
const titanClient = this.client as TitanClient;
|
|
240
|
+
|
|
241
|
+
// For Titan, get swap directly (it handles quote internally)
|
|
242
|
+
const { transactionMessage, lookupTables: titanLookupTables } =
|
|
243
|
+
await titanClient.getSwap({
|
|
244
|
+
inputMint,
|
|
245
|
+
outputMint,
|
|
246
|
+
amount,
|
|
247
|
+
userPublicKey,
|
|
248
|
+
slippageBps,
|
|
249
|
+
swapMode: isExactOut ? TitanSwapMode.ExactOut : TitanSwapMode.ExactIn,
|
|
250
|
+
onlyDirectRoutes,
|
|
251
|
+
sizeConstraint: sizeConstraint || 1280 - 375, // MAX_TX_BYTE_SIZE - buffer for drift instructions
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
swapInstructions = titanClient.getTitanInstructions({
|
|
255
|
+
transactionMessage,
|
|
256
|
+
inputMint,
|
|
257
|
+
outputMint,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
lookupTables = titanLookupTables;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { instructions: swapInstructions, lookupTables };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get the underlying client instance
|
|
268
|
+
*/
|
|
269
|
+
public getClient(): JupiterClient | TitanClient {
|
|
270
|
+
return this.client;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the client type
|
|
275
|
+
*/
|
|
276
|
+
public getClientType(): SwapClientType {
|
|
277
|
+
return this.clientType;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Check if this is a Jupiter client
|
|
282
|
+
*/
|
|
283
|
+
public isJupiter(): boolean {
|
|
284
|
+
return this.clientType === 'jupiter';
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Check if this is a Titan client
|
|
289
|
+
*/
|
|
290
|
+
public isTitan(): boolean {
|
|
291
|
+
return this.clientType === 'titan';
|
|
292
|
+
}
|
|
293
|
+
}
|