Skip to content

Commit b1442f5

Browse files
authored
Merge pull request #1993 from drift-labs/unified-swap-client
Implement Unified Swap Client
2 parents 8ccafa5 + a1087aa commit b1442f5

File tree

4 files changed

+448
-12
lines changed

4 files changed

+448
-12
lines changed

sdk/src/driftClient.ts

Lines changed: 152 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,8 @@ import { isSpotPositionAvailable } from './math/spotPosition';
157157
import { calculateMarketMaxAvailableInsurance } from './math/market';
158158
import { fetchUserStatsAccount } from './accounts/fetch';
159159
import { castNumberToSpotPrecision } from './math/spotMarket';
160-
import {
161-
JupiterClient,
162-
QuoteResponse,
163-
SwapMode,
164-
} from './jupiter/jupiterClient';
160+
import { JupiterClient, QuoteResponse } from './jupiter/jupiterClient';
161+
import { SwapMode } from './swap/UnifiedSwapClient';
165162
import { getNonIdleUserFilter } from './memcmp';
166163
import { UserStatsSubscriptionConfig } from './userStatsConfig';
167164
import { getMarinadeDepositIx, getMarinadeFinanceProgram } from './marinade';
@@ -210,9 +207,11 @@ import {
210207
isBuilderOrderCompleted,
211208
} from './math/builder';
212209
import { TitanClient, SwapMode as TitanSwapMode } from './titan/titanClient';
210+
import { UnifiedSwapClient } from './swap/UnifiedSwapClient';
213211

214212
/**
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
216215
*/
217216
export type SwapClient = TitanClient | JupiterClient;
218217

@@ -5772,7 +5771,7 @@ export class DriftClient {
57725771
quote,
57735772
onlyDirectRoutes = false,
57745773
}: {
5775-
swapClient: SwapClient;
5774+
swapClient: UnifiedSwapClient | SwapClient;
57765775
outMarketIndex: number;
57775776
inMarketIndex: number;
57785777
outAssociatedTokenAccount?: PublicKey;
@@ -5793,7 +5792,23 @@ export class DriftClient {
57935792
lookupTables: AddressLookupTableAccount[];
57945793
};
57955794

5796-
if (swapClient instanceof TitanClient) {
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) {
57975812
res = await this.getTitanSwapIx({
57985813
titanClient: swapClient,
57995814
outMarketIndex,
@@ -5823,7 +5838,7 @@ export class DriftClient {
58235838
});
58245839
} else {
58255840
throw new Error(
5826-
'Invalid swap client type. Must be TitanClient or JupiterClient.'
5841+
'Invalid swap client type. Must be SwapClient, TitanClient, or JupiterClient.'
58275842
);
58285843
}
58295844

@@ -6243,6 +6258,134 @@ export class DriftClient {
62436258
return { beginSwapIx, endSwapIx };
62446259
}
62456260

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+
62466389
public async stakeForMSOL({ amount }: { amount: BN }): Promise<TxSigAndSlot> {
62476390
const ixs = await this.getStakeForMSOLIx({ amount });
62486391
const tx = await this.buildTransaction(ixs);

sdk/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export * from './events/webSocketLogProvider';
5353
export * from './events/parse';
5454
export * from './events/pollingLogProvider';
5555
export * from './jupiter/jupiterClient';
56-
export { TitanClient } from './titan/titanClient';
56+
// Primary swap client interface - use this for all swap operations
57+
export * from './swap/UnifiedSwapClient';
5758
export * from './math/auction';
5859
export * from './math/builder';
5960
export * from './math/spotMarket';

sdk/src/jupiter/jupiterClient.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ import {
88
} from '@solana/web3.js';
99
import fetch from 'node-fetch';
1010
import { BN } from '@coral-xyz/anchor';
11-
12-
export type SwapMode = 'ExactIn' | 'ExactOut';
11+
import { SwapMode } from '../swap/UnifiedSwapClient';
1312

1413
export interface MarketInfo {
1514
id: string;

0 commit comments

Comments
 (0)