@drift-labs/sdk 2.144.0-beta.2 → 2.144.0-beta.3

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.
@@ -0,0 +1,214 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TitanClient = exports.SwapMode = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const msgpack_1 = require("@msgpack/msgpack");
6
+ var SwapMode;
7
+ (function (SwapMode) {
8
+ SwapMode["ExactIn"] = "ExactIn";
9
+ SwapMode["ExactOut"] = "ExactOut";
10
+ })(SwapMode || (exports.SwapMode = SwapMode = {}));
11
+ const TITAN_API_URL = 'https://api.titan.exchange';
12
+ class TitanClient {
13
+ constructor({ connection, authToken, url, }) {
14
+ this.connection = connection;
15
+ this.authToken = authToken;
16
+ this.url = url !== null && url !== void 0 ? url : TITAN_API_URL;
17
+ }
18
+ /**
19
+ * Get routes for a swap
20
+ */
21
+ async getQuote({ inputMint, outputMint, amount, userPublicKey, maxAccounts = 50, // 50 is an estimated amount with buffer
22
+ slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) {
23
+ var _a;
24
+ const params = new URLSearchParams({
25
+ inputMint: inputMint.toString(),
26
+ outputMint: outputMint.toString(),
27
+ amount: amount.toString(),
28
+ userPublicKey: userPublicKey.toString(),
29
+ ...(slippageBps && { slippageBps: slippageBps.toString() }),
30
+ ...(swapMode && {
31
+ swapMode: swapMode === 'ExactOut' ? SwapMode.ExactOut : SwapMode.ExactIn,
32
+ }),
33
+ ...(onlyDirectRoutes && {
34
+ onlyDirectRoutes: onlyDirectRoutes.toString(),
35
+ }),
36
+ ...(maxAccounts && { accountsLimitTotal: maxAccounts.toString() }),
37
+ ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }),
38
+ ...(sizeConstraint && { sizeConstraint: sizeConstraint.toString() }),
39
+ ...(accountsLimitWritable && {
40
+ accountsLimitWritable: accountsLimitWritable.toString(),
41
+ }),
42
+ });
43
+ const response = await fetch(`${this.url}/api/v1/quote/swap?${params.toString()}`, {
44
+ headers: {
45
+ Accept: 'application/vnd.msgpack',
46
+ 'Accept-Encoding': 'gzip, deflate, br',
47
+ Authorization: `Bearer ${this.authToken}`,
48
+ },
49
+ });
50
+ if (!response.ok) {
51
+ throw new Error(`Titan API error: ${response.status} ${response.statusText}`);
52
+ }
53
+ const buffer = await response.arrayBuffer();
54
+ const data = (0, msgpack_1.decode)(buffer);
55
+ const route = data.quotes[Object.keys(data.quotes).find((key) => key.toLowerCase() === 'titan') ||
56
+ ''];
57
+ if (!route) {
58
+ throw new Error('No routes available');
59
+ }
60
+ return {
61
+ inputMint: inputMint.toString(),
62
+ inAmount: amount.toString(),
63
+ outputMint: outputMint.toString(),
64
+ outAmount: route.outAmount.toString(),
65
+ swapMode: data.swapMode,
66
+ slippageBps: route.slippageBps,
67
+ platformFee: route.platformFee
68
+ ? {
69
+ amount: route.platformFee.amount.toString(),
70
+ feeBps: route.platformFee.fee_bps,
71
+ }
72
+ : undefined,
73
+ routePlan: ((_a = route.steps) === null || _a === void 0 ? void 0 : _a.map((step) => {
74
+ var _a;
75
+ return ({
76
+ swapInfo: {
77
+ ammKey: new web3_js_1.PublicKey(step.ammKey).toString(),
78
+ label: step.label,
79
+ inputMint: new web3_js_1.PublicKey(step.inputMint).toString(),
80
+ outputMint: new web3_js_1.PublicKey(step.outputMint).toString(),
81
+ inAmount: step.inAmount.toString(),
82
+ outAmount: step.outAmount.toString(),
83
+ feeAmount: ((_a = step.feeAmount) === null || _a === void 0 ? void 0 : _a.toString()) || '0',
84
+ feeMint: step.feeMint ? new web3_js_1.PublicKey(step.feeMint).toString() : '',
85
+ },
86
+ percent: 100,
87
+ });
88
+ })) || [],
89
+ contextSlot: route.contextSlot,
90
+ timeTaken: route.timeTaken,
91
+ };
92
+ }
93
+ /**
94
+ * Get a swap transaction for quote
95
+ */
96
+ async getSwap({ inputMint, outputMint, amount, userPublicKey, maxAccounts = 50, // 50 is an estimated amount with buffer
97
+ slippageBps, swapMode, onlyDirectRoutes, excludeDexes, sizeConstraint, accountsLimitWritable, }) {
98
+ const params = new URLSearchParams({
99
+ inputMint: inputMint.toString(),
100
+ outputMint: outputMint.toString(),
101
+ amount: amount.toString(),
102
+ userPublicKey: userPublicKey.toString(),
103
+ ...(slippageBps && { slippageBps: slippageBps.toString() }),
104
+ ...(swapMode && { swapMode: swapMode }),
105
+ ...(maxAccounts && { accountsLimitTotal: maxAccounts.toString() }),
106
+ ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }),
107
+ ...(onlyDirectRoutes && {
108
+ onlyDirectRoutes: onlyDirectRoutes.toString(),
109
+ }),
110
+ ...(sizeConstraint && { sizeConstraint: sizeConstraint.toString() }),
111
+ ...(accountsLimitWritable && {
112
+ accountsLimitWritable: accountsLimitWritable.toString(),
113
+ }),
114
+ });
115
+ const response = await fetch(`${this.url}/api/v1/quote/swap?${params.toString()}`, {
116
+ headers: {
117
+ Accept: 'application/vnd.msgpack',
118
+ 'Accept-Encoding': 'gzip, deflate, br',
119
+ Authorization: `Bearer ${this.authToken}`,
120
+ },
121
+ });
122
+ if (!response.ok) {
123
+ if (response.status === 404) {
124
+ throw new Error('No routes available');
125
+ }
126
+ throw new Error(`Titan API error: ${response.status} ${response.statusText}`);
127
+ }
128
+ const buffer = await response.arrayBuffer();
129
+ const data = (0, msgpack_1.decode)(buffer);
130
+ const route = data.quotes[Object.keys(data.quotes).find((key) => key.toLowerCase() === 'titan') ||
131
+ ''];
132
+ if (!route) {
133
+ throw new Error('No routes available');
134
+ }
135
+ if (route.instructions && route.instructions.length > 0) {
136
+ try {
137
+ const { transactionMessage, lookupTables } = await this.getTransactionMessageAndLookupTables(route, userPublicKey);
138
+ return { transactionMessage, lookupTables };
139
+ }
140
+ catch (err) {
141
+ throw new Error('Something went wrong with creating the Titan swap transaction. Please try again.');
142
+ }
143
+ }
144
+ throw new Error('No instructions provided in the route');
145
+ }
146
+ /**
147
+ * Get the titan instructions from transaction by filtering out instructions to compute budget and associated token programs
148
+ * @param transactionMessage the transaction message
149
+ * @param inputMint the input mint
150
+ * @param outputMint the output mint
151
+ */
152
+ getTitanInstructions({ transactionMessage, inputMint, outputMint, }) {
153
+ // Filter out common system instructions that can be handled by DriftClient
154
+ const filteredInstructions = transactionMessage.instructions.filter((instruction) => {
155
+ const programId = instruction.programId.toString();
156
+ // Filter out system programs
157
+ if (programId === 'ComputeBudget111111111111111111111111111111') {
158
+ return false;
159
+ }
160
+ if (programId === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
161
+ return false;
162
+ }
163
+ if (programId === '11111111111111111111111111111111') {
164
+ return false;
165
+ }
166
+ // Filter out Associated Token Account creation for input/output mints
167
+ if (programId === 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') {
168
+ if (instruction.keys.length > 3) {
169
+ const mint = instruction.keys[3].pubkey;
170
+ if (mint.equals(inputMint) || mint.equals(outputMint)) {
171
+ return false;
172
+ }
173
+ }
174
+ }
175
+ return true;
176
+ });
177
+ return filteredInstructions;
178
+ }
179
+ async getTransactionMessageAndLookupTables(route, userPublicKey) {
180
+ const solanaInstructions = route.instructions.map((instruction) => ({
181
+ programId: new web3_js_1.PublicKey(instruction.p),
182
+ keys: instruction.a.map((meta) => ({
183
+ pubkey: new web3_js_1.PublicKey(meta.p),
184
+ isSigner: meta.s,
185
+ isWritable: meta.w,
186
+ })),
187
+ data: Buffer.from(instruction.d),
188
+ }));
189
+ // Get recent blockhash
190
+ const { blockhash } = await this.connection.getLatestBlockhash();
191
+ // Build address lookup tables if provided
192
+ const addressLookupTables = [];
193
+ if (route.addressLookupTables && route.addressLookupTables.length > 0) {
194
+ for (const altPubkey of route.addressLookupTables) {
195
+ try {
196
+ const altAccount = await this.connection.getAddressLookupTable(new web3_js_1.PublicKey(altPubkey));
197
+ if (altAccount.value) {
198
+ addressLookupTables.push(altAccount.value);
199
+ }
200
+ }
201
+ catch (err) {
202
+ console.warn(`Failed to fetch address lookup table:`, err);
203
+ }
204
+ }
205
+ }
206
+ const transactionMessage = new web3_js_1.TransactionMessage({
207
+ payerKey: userPublicKey,
208
+ recentBlockhash: blockhash,
209
+ instructions: solanaInstructions,
210
+ });
211
+ return { transactionMessage, lookupTables: addressLookupTables };
212
+ }
213
+ }
214
+ exports.TitanClient = TitanClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.144.0-beta.2",
3
+ "version": "2.144.0-beta.3",
4
4
  "main": "lib/node/index.js",
5
5
  "types": "lib/node/index.d.ts",
6
6
  "module": "./lib/browser/index.js",
@@ -44,6 +44,7 @@
44
44
  "@coral-xyz/anchor-30": "npm:@coral-xyz/anchor@0.30.1",
45
45
  "@ellipsis-labs/phoenix-sdk": "1.4.5",
46
46
  "@grpc/grpc-js": "1.14.0",
47
+ "@msgpack/msgpack": "^3.1.2",
47
48
  "@openbook-dex/openbook-v2": "0.2.10",
48
49
  "@project-serum/serum": "0.13.65",
49
50
  "@pythnetwork/client": "2.5.3",
@@ -709,21 +709,6 @@ export class grpcDriftClientAccountSubscriberV2
709
709
  await this.perpMarketsSubscriber.removeAccounts(
710
710
  perpMarketPubkeysToRemove
711
711
  );
712
- // Clean up the mapping for removed perp markets
713
- for (const pubkey of perpMarketPubkeysToRemove) {
714
- const pubkeyString = pubkey.toBase58();
715
- for (const [
716
- marketIndex,
717
- accountPubkey,
718
- ] of this.perpMarketIndexToAccountPubkeyMap.entries()) {
719
- if (accountPubkey === pubkeyString) {
720
- this.perpMarketIndexToAccountPubkeyMap.delete(marketIndex);
721
- this.perpOracleMap.delete(marketIndex);
722
- this.perpOracleStringMap.delete(marketIndex);
723
- break;
724
- }
725
- }
726
- }
727
712
  }
728
713
 
729
714
  // Remove accounts in batches - oracles
@@ -190,6 +190,7 @@ import { createMinimalEd25519VerifyIx } from './util/ed25519Utils';
190
190
  import {
191
191
  createNativeInstructionDiscriminatorBuffer,
192
192
  isVersionedTransaction,
193
+ MAX_TX_BYTE_SIZE,
193
194
  } from './tx/utils';
194
195
  import pythSolanaReceiverIdl from './idl/pyth_solana_receiver.json';
195
196
  import { asV0Tx, PullFeed, AnchorUtils } from '@switchboard-xyz/on-demand';
@@ -208,6 +209,12 @@ import {
208
209
  isBuilderOrderReferral,
209
210
  isBuilderOrderCompleted,
210
211
  } from './math/builder';
212
+ import { TitanClient, SwapMode as TitanSwapMode } from './titan/titanClient';
213
+
214
+ /**
215
+ * Union type for swap clients (Titan and Jupiter)
216
+ */
217
+ export type SwapClient = TitanClient | JupiterClient;
211
218
 
212
219
  type RemainingAccountParams = {
213
220
  userAccounts: UserAccount[];
@@ -5733,23 +5740,23 @@ export class DriftClient {
5733
5740
  }
5734
5741
 
5735
5742
  /**
5736
- * Swap tokens in drift account using jupiter
5737
- * @param jupiterClient jupiter client to find routes and jupiter instructions
5743
+ * Swap tokens in drift account using titan or jupiter
5744
+ * @param swapClient swap client to find routes and instructions (Titan or Jupiter)
5738
5745
  * @param outMarketIndex the market index of the token you're buying
5739
5746
  * @param inMarketIndex the market index of the token you're selling
5740
- * @param outAssociatedTokenAccount the token account to receive the token being sold on jupiter
5747
+ * @param outAssociatedTokenAccount the token account to receive the token being sold on titan or jupiter
5741
5748
  * @param inAssociatedTokenAccount the token account to
5742
5749
  * @param amount the amount of TokenIn, regardless of swapMode
5743
- * @param slippageBps the max slippage passed to jupiter api
5744
- * @param swapMode jupiter swapMode (ExactIn or ExactOut), default is ExactIn
5745
- * @param route the jupiter route to use for the swap
5750
+ * @param slippageBps the max slippage passed to titan or jupiter api
5751
+ * @param swapMode titan or jupiter swapMode (ExactIn or ExactOut), default is ExactIn
5752
+ * @param route the titan or jupiter route to use for the swap
5746
5753
  * @param reduceOnly specify if In or Out token on the drift account must reduceOnly, checked at end of swap
5747
5754
  * @param v6 pass in the quote response from Jupiter quote's API (deprecated, use quote instead)
5748
5755
  * @param quote pass in the quote response from Jupiter quote's API
5749
5756
  * @param txParams
5750
5757
  */
5751
5758
  public async swap({
5752
- jupiterClient,
5759
+ swapClient,
5753
5760
  outMarketIndex,
5754
5761
  inMarketIndex,
5755
5762
  outAssociatedTokenAccount,
@@ -5763,7 +5770,7 @@ export class DriftClient {
5763
5770
  quote,
5764
5771
  onlyDirectRoutes = false,
5765
5772
  }: {
5766
- jupiterClient: JupiterClient;
5773
+ swapClient: SwapClient;
5767
5774
  outMarketIndex: number;
5768
5775
  inMarketIndex: number;
5769
5776
  outAssociatedTokenAccount?: PublicKey;
@@ -5779,21 +5786,45 @@ export class DriftClient {
5779
5786
  };
5780
5787
  quote?: QuoteResponse;
5781
5788
  }): Promise<TransactionSignature> {
5782
- const quoteToUse = quote ?? v6?.quote;
5789
+ let res: {
5790
+ ixs: TransactionInstruction[];
5791
+ lookupTables: AddressLookupTableAccount[];
5792
+ };
5793
+
5794
+ if (swapClient instanceof TitanClient) {
5795
+ res = await this.getTitanSwapIx({
5796
+ titanClient: swapClient,
5797
+ outMarketIndex,
5798
+ inMarketIndex,
5799
+ outAssociatedTokenAccount,
5800
+ inAssociatedTokenAccount,
5801
+ amount,
5802
+ slippageBps,
5803
+ swapMode,
5804
+ onlyDirectRoutes,
5805
+ reduceOnly,
5806
+ });
5807
+ } else if (swapClient instanceof JupiterClient) {
5808
+ const quoteToUse = quote ?? v6?.quote;
5809
+ res = await this.getJupiterSwapIxV6({
5810
+ jupiterClient: swapClient,
5811
+ outMarketIndex,
5812
+ inMarketIndex,
5813
+ outAssociatedTokenAccount,
5814
+ inAssociatedTokenAccount,
5815
+ amount,
5816
+ slippageBps,
5817
+ swapMode,
5818
+ quote: quoteToUse,
5819
+ reduceOnly,
5820
+ onlyDirectRoutes,
5821
+ });
5822
+ } else {
5823
+ throw new Error(
5824
+ 'Invalid swap client type. Must be TitanClient or JupiterClient.'
5825
+ );
5826
+ }
5783
5827
 
5784
- const res = await this.getJupiterSwapIxV6({
5785
- jupiterClient,
5786
- outMarketIndex,
5787
- inMarketIndex,
5788
- outAssociatedTokenAccount,
5789
- inAssociatedTokenAccount,
5790
- amount,
5791
- slippageBps,
5792
- swapMode,
5793
- quote: quoteToUse,
5794
- reduceOnly,
5795
- onlyDirectRoutes,
5796
- });
5797
5828
  const ixs = res.ixs;
5798
5829
  const lookupTables = res.lookupTables;
5799
5830
 
@@ -5811,6 +5842,126 @@ export class DriftClient {
5811
5842
  return txSig;
5812
5843
  }
5813
5844
 
5845
+ public async getTitanSwapIx({
5846
+ titanClient,
5847
+ outMarketIndex,
5848
+ inMarketIndex,
5849
+ outAssociatedTokenAccount,
5850
+ inAssociatedTokenAccount,
5851
+ amount,
5852
+ slippageBps,
5853
+ swapMode,
5854
+ onlyDirectRoutes,
5855
+ reduceOnly,
5856
+ userAccountPublicKey,
5857
+ }: {
5858
+ titanClient: TitanClient;
5859
+ outMarketIndex: number;
5860
+ inMarketIndex: number;
5861
+ outAssociatedTokenAccount?: PublicKey;
5862
+ inAssociatedTokenAccount?: PublicKey;
5863
+ amount: BN;
5864
+ slippageBps?: number;
5865
+ swapMode?: string;
5866
+ onlyDirectRoutes?: boolean;
5867
+ reduceOnly?: SwapReduceOnly;
5868
+ userAccountPublicKey?: PublicKey;
5869
+ }): Promise<{
5870
+ ixs: TransactionInstruction[];
5871
+ lookupTables: AddressLookupTableAccount[];
5872
+ }> {
5873
+ const outMarket = this.getSpotMarketAccount(outMarketIndex);
5874
+ const inMarket = this.getSpotMarketAccount(inMarketIndex);
5875
+
5876
+ const isExactOut = swapMode === 'ExactOut';
5877
+ const exactOutBufferedAmountIn = amount.muln(1001).divn(1000); // Add 10bp buffer
5878
+
5879
+ const preInstructions = [];
5880
+ if (!outAssociatedTokenAccount) {
5881
+ const tokenProgram = this.getTokenProgramForSpotMarket(outMarket);
5882
+ outAssociatedTokenAccount = await this.getAssociatedTokenAccount(
5883
+ outMarket.marketIndex,
5884
+ false,
5885
+ tokenProgram
5886
+ );
5887
+
5888
+ const accountInfo = await this.connection.getAccountInfo(
5889
+ outAssociatedTokenAccount
5890
+ );
5891
+ if (!accountInfo) {
5892
+ preInstructions.push(
5893
+ this.createAssociatedTokenAccountIdempotentInstruction(
5894
+ outAssociatedTokenAccount,
5895
+ this.provider.wallet.publicKey,
5896
+ this.provider.wallet.publicKey,
5897
+ outMarket.mint,
5898
+ tokenProgram
5899
+ )
5900
+ );
5901
+ }
5902
+ }
5903
+
5904
+ if (!inAssociatedTokenAccount) {
5905
+ const tokenProgram = this.getTokenProgramForSpotMarket(inMarket);
5906
+ inAssociatedTokenAccount = await this.getAssociatedTokenAccount(
5907
+ inMarket.marketIndex,
5908
+ false,
5909
+ tokenProgram
5910
+ );
5911
+
5912
+ const accountInfo = await this.connection.getAccountInfo(
5913
+ inAssociatedTokenAccount
5914
+ );
5915
+ if (!accountInfo) {
5916
+ preInstructions.push(
5917
+ this.createAssociatedTokenAccountIdempotentInstruction(
5918
+ inAssociatedTokenAccount,
5919
+ this.provider.wallet.publicKey,
5920
+ this.provider.wallet.publicKey,
5921
+ inMarket.mint,
5922
+ tokenProgram
5923
+ )
5924
+ );
5925
+ }
5926
+ }
5927
+
5928
+ const { beginSwapIx, endSwapIx } = await this.getSwapIx({
5929
+ outMarketIndex,
5930
+ inMarketIndex,
5931
+ amountIn: isExactOut ? exactOutBufferedAmountIn : amount,
5932
+ inTokenAccount: inAssociatedTokenAccount,
5933
+ outTokenAccount: outAssociatedTokenAccount,
5934
+ reduceOnly,
5935
+ userAccountPublicKey,
5936
+ });
5937
+
5938
+ const { transactionMessage, lookupTables } = await titanClient.getSwap({
5939
+ inputMint: inMarket.mint,
5940
+ outputMint: outMarket.mint,
5941
+ amount,
5942
+ userPublicKey: this.provider.wallet.publicKey,
5943
+ slippageBps,
5944
+ swapMode: isExactOut ? TitanSwapMode.ExactOut : TitanSwapMode.ExactIn,
5945
+ onlyDirectRoutes,
5946
+ sizeConstraint: MAX_TX_BYTE_SIZE - 375, // buffer for drift instructions
5947
+ });
5948
+
5949
+ const titanInstructions = titanClient.getTitanInstructions({
5950
+ transactionMessage,
5951
+ inputMint: inMarket.mint,
5952
+ outputMint: outMarket.mint,
5953
+ });
5954
+
5955
+ const ixs = [
5956
+ ...preInstructions,
5957
+ beginSwapIx,
5958
+ ...titanInstructions,
5959
+ endSwapIx,
5960
+ ];
5961
+
5962
+ return { ixs, lookupTables };
5963
+ }
5964
+
5814
5965
  public async getJupiterSwapIxV6({
5815
5966
  jupiterClient,
5816
5967
  outMarketIndex,