@drift-labs/sdk 2.30.0-beta.0 → 2.30.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.
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.JupiterClient = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const node_fetch_1 = __importDefault(require("node-fetch"));
9
+ class JupiterClient {
10
+ constructor({ connection }) {
11
+ this.url = 'https://quote-api.jup.ag/v4';
12
+ this.lookupTableCahce = new Map();
13
+ this.connection = connection;
14
+ }
15
+ /**
16
+ * Get routes for a swap
17
+ * @param inputMint the mint of the input token
18
+ * @param outputMint the mint of the output token
19
+ * @param amount the amount of the input token
20
+ * @param slippageBps the slippage tolerance in basis points
21
+ * @param swapMode the swap mode (ExactIn or ExactOut)
22
+ */
23
+ async getRoutes({ inputMint, outputMint, amount, slippageBps = 50, swapMode = 'ExactIn', }) {
24
+ const params = new URLSearchParams({
25
+ inputMint: inputMint.toString(),
26
+ outputMint: outputMint.toString(),
27
+ amount: amount.toString(),
28
+ slippageBps: slippageBps.toString(),
29
+ swapMode,
30
+ }).toString();
31
+ const { data: routes } = await (await (0, node_fetch_1.default)(`https://quote-api.jup.ag/v4/quote?${params}`)).json();
32
+ return routes;
33
+ }
34
+ /**
35
+ * Get a swap transaction for a route
36
+ * @param route the route to perform swap
37
+ * @param userPublicKey the user's wallet public key
38
+ * @param slippageBps the slippage tolerance in basis points
39
+ */
40
+ async getSwapTransaction({ route, userPublicKey, slippageBps = 50, }) {
41
+ const resp = await (await (0, node_fetch_1.default)(`${this.url}/swap`, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ body: JSON.stringify({
47
+ route,
48
+ userPublicKey,
49
+ slippageBps,
50
+ }),
51
+ })).json();
52
+ const { swapTransaction } = resp;
53
+ const swapTransactionBuf = Buffer.from(swapTransaction, 'base64');
54
+ return web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
55
+ }
56
+ /**
57
+ * Get the transaction message and lookup tables for a transaction
58
+ * @param transaction
59
+ */
60
+ async getTransactionMessageAndLookupTables({ transaction, }) {
61
+ const message = transaction.message;
62
+ const lookupTables = (await Promise.all(message.addressTableLookups.map(async (lookup) => {
63
+ return await this.getLookupTable(lookup.accountKey);
64
+ }))).filter((lookup) => lookup);
65
+ const transactionMessage = web3_js_1.TransactionMessage.decompile(message);
66
+ return {
67
+ transactionMessage,
68
+ lookupTables,
69
+ };
70
+ }
71
+ async getLookupTable(accountKey) {
72
+ if (this.lookupTableCahce.has(accountKey.toString())) {
73
+ return this.lookupTableCahce.get(accountKey.toString());
74
+ }
75
+ return (await this.connection.getAddressLookupTable(accountKey)).value;
76
+ }
77
+ /**
78
+ * Get the jupiter instructions from transaction by filtering out instructions to compute budget and associated token programs
79
+ * @param transactionMessage the transaction message
80
+ * @param inputMint the input mint
81
+ * @param outputMint the output mint
82
+ */
83
+ getJupiterInstructions({ transactionMessage, inputMint, outputMint, }) {
84
+ return transactionMessage.instructions.filter((instruction) => {
85
+ if (instruction.programId.toString() ===
86
+ 'ComputeBudget111111111111111111111111111111') {
87
+ return false;
88
+ }
89
+ if (instruction.programId.toString() ===
90
+ 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA') {
91
+ return false;
92
+ }
93
+ if (instruction.programId.toString() ===
94
+ 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL') {
95
+ const mint = instruction.keys[3].pubkey;
96
+ if (mint.equals(inputMint) || mint.equals(outputMint)) {
97
+ return false;
98
+ }
99
+ }
100
+ return true;
101
+ });
102
+ }
103
+ }
104
+ exports.JupiterClient = JupiterClient;
package/lib/types.d.ts CHANGED
@@ -584,6 +584,17 @@ export type OrderActionRecord = {
584
584
  makerOrderCumulativeQuoteAssetAmountFilled: BN | null;
585
585
  oraclePrice: BN;
586
586
  };
587
+ export type SwapRecord = {
588
+ ts: number;
589
+ user: PublicKey;
590
+ amountOut: BN;
591
+ amountIn: BN;
592
+ outMarketIndex: number;
593
+ inMarketIndex: number;
594
+ outOraclePrice: BN;
595
+ inOraclePrice: BN;
596
+ fee: BN;
597
+ };
587
598
  export type StateAccount = {
588
599
  admin: PublicKey;
589
600
  exchangeStatus: number;
@@ -711,6 +722,9 @@ export type SpotMarketAccount = {
711
722
  nextFillRecordId: BN;
712
723
  spotFeePool: PoolBalance;
713
724
  totalSpotFee: BN;
725
+ totalSwapFee: BN;
726
+ flashLoanAmount: BN;
727
+ flashLoanInitialTokenAmount: BN;
714
728
  ordersEnabled: boolean;
715
729
  };
716
730
  export type PoolBalance = {
@@ -851,6 +865,7 @@ export type UserAccount = {
851
865
  totalWithdraws: BN;
852
866
  totalSocialLoss: BN;
853
867
  cumulativePerpFunding: BN;
868
+ cumulativeSpotFees: BN;
854
869
  liquidationMarginFreed: BN;
855
870
  lastActiveSlot: BN;
856
871
  isMarginTradingEnabled: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drift-labs/sdk",
3
- "version": "2.30.0-beta.0",
3
+ "version": "2.30.0-beta.1",
4
4
  "main": "lib/index.js",
5
5
  "types": "lib/index.d.ts",
6
6
  "author": "crispheaney",
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assert = void 0;
4
+ function assert(condition, error) {
5
+ if (!condition) {
6
+ throw new Error(error || 'Unspecified AssertionError');
7
+ }
8
+ }
9
+ exports.assert = assert;
@@ -113,6 +113,7 @@ import { isSpotPositionAvailable } from './math/spotPosition';
113
113
  import { calculateMarketMaxAvailableInsurance } from './math/market';
114
114
  import { fetchUserStatsAccount } from './accounts/fetch';
115
115
  import { castNumberToSpotPrecision } from './math/spotMarket';
116
+ import { JupiterClient } from './jupiter/jupiterClient';
116
117
 
117
118
  type RemainingAccountParams = {
118
119
  userAccounts: UserAccount[];
@@ -1496,6 +1497,30 @@ export class DriftClient {
1496
1497
  );
1497
1498
  }
1498
1499
 
1500
+ public async createAssociatedTokenAccountIdempotentInstruction(
1501
+ account: PublicKey,
1502
+ payer: PublicKey,
1503
+ owner: PublicKey,
1504
+ mint: PublicKey
1505
+ ): Promise<TransactionInstruction> {
1506
+ return new TransactionInstruction({
1507
+ keys: [
1508
+ { pubkey: payer, isSigner: true, isWritable: true },
1509
+ { pubkey: account, isSigner: false, isWritable: true },
1510
+ { pubkey: owner, isSigner: false, isWritable: false },
1511
+ { pubkey: mint, isSigner: false, isWritable: false },
1512
+ {
1513
+ pubkey: SystemProgram.programId,
1514
+ isSigner: false,
1515
+ isWritable: false,
1516
+ },
1517
+ { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
1518
+ ],
1519
+ programId: ASSOCIATED_TOKEN_PROGRAM_ID,
1520
+ data: Buffer.from([0x1]),
1521
+ });
1522
+ }
1523
+
1499
1524
  /**
1500
1525
  * Deposit funds into the given spot market
1501
1526
  *
@@ -3266,6 +3291,223 @@ export class DriftClient {
3266
3291
  });
3267
3292
  }
3268
3293
 
3294
+ /**
3295
+ * Swap tokens in drift account using jupiter
3296
+ * @param jupiterClient jupiter client to find routes and jupiter instructions
3297
+ * @param outMarketIndex the market index of the token you're buying
3298
+ * @param inMarketIndex the market index of the token you're selling
3299
+ * @param outAssociatedTokenAccount the token account to receive the token being sold on jupiter
3300
+ * @param inAssociatedTokenAccount the token account to
3301
+ * @param amount the amount of the token to sell
3302
+ * @param slippageBps the max slippage passed to jupiter api
3303
+ * @param txParams
3304
+ */
3305
+ public async swap({
3306
+ jupiterClient,
3307
+ outMarketIndex,
3308
+ inMarketIndex,
3309
+ outAssociatedTokenAccount,
3310
+ inAssociatedTokenAccount,
3311
+ amount,
3312
+ slippageBps,
3313
+ txParams,
3314
+ }: {
3315
+ jupiterClient: JupiterClient;
3316
+ outMarketIndex: number;
3317
+ inMarketIndex: number;
3318
+ outAssociatedTokenAccount: PublicKey;
3319
+ inAssociatedTokenAccount: PublicKey;
3320
+ amount: BN;
3321
+ slippageBps: number;
3322
+ txParams?: TxParams;
3323
+ }): Promise<TransactionSignature> {
3324
+ const outMarket = this.getSpotMarketAccount(outMarketIndex);
3325
+ const inMarket = this.getSpotMarketAccount(inMarketIndex);
3326
+
3327
+ const routes = await jupiterClient.getRoutes({
3328
+ inputMint: inMarket.mint,
3329
+ outputMint: outMarket.mint,
3330
+ amount,
3331
+ slippageBps,
3332
+ });
3333
+
3334
+ if (!routes || routes.length === 0) {
3335
+ throw new Error('No jupiter routes found');
3336
+ }
3337
+
3338
+ const route = routes[0];
3339
+ const transaction = await jupiterClient.getSwapTransaction({
3340
+ route,
3341
+ userPublicKey: this.provider.wallet.publicKey,
3342
+ slippageBps,
3343
+ });
3344
+
3345
+ const { transactionMessage, lookupTables } =
3346
+ await jupiterClient.getTransactionMessageAndLookupTables({
3347
+ transaction,
3348
+ });
3349
+
3350
+ const jupiterInstructions = jupiterClient.getJupiterInstructions({
3351
+ transactionMessage,
3352
+ inputMint: inMarket.mint,
3353
+ outputMint: outMarket.mint,
3354
+ });
3355
+
3356
+ const preInstructions = [];
3357
+ if (!outAssociatedTokenAccount) {
3358
+ outAssociatedTokenAccount = await this.getAssociatedTokenAccount(
3359
+ outMarket.marketIndex
3360
+ );
3361
+
3362
+ const accountInfo = await this.connection.getAccountInfo(
3363
+ outAssociatedTokenAccount
3364
+ );
3365
+ if (!accountInfo) {
3366
+ preInstructions.push(
3367
+ this.createAssociatedTokenAccountIdempotentInstruction(
3368
+ outAssociatedTokenAccount,
3369
+ this.provider.wallet.publicKey,
3370
+ this.provider.wallet.publicKey,
3371
+ outMarket.mint
3372
+ )
3373
+ );
3374
+ }
3375
+ }
3376
+
3377
+ if (!inAssociatedTokenAccount) {
3378
+ inAssociatedTokenAccount = await this.getAssociatedTokenAccount(
3379
+ inMarket.marketIndex
3380
+ );
3381
+
3382
+ const accountInfo = await this.connection.getAccountInfo(
3383
+ inAssociatedTokenAccount
3384
+ );
3385
+ if (!accountInfo) {
3386
+ preInstructions.push(
3387
+ this.createAssociatedTokenAccountIdempotentInstruction(
3388
+ inAssociatedTokenAccount,
3389
+ this.provider.wallet.publicKey,
3390
+ this.provider.wallet.publicKey,
3391
+ inMarket.mint
3392
+ )
3393
+ );
3394
+ }
3395
+ }
3396
+
3397
+ const { beginSwapIx, endSwapIx } = await this.getSwapIx({
3398
+ outMarketIndex,
3399
+ inMarketIndex,
3400
+ amountIn: amount,
3401
+ inTokenAccount: inAssociatedTokenAccount,
3402
+ outTokenAccount: outAssociatedTokenAccount,
3403
+ });
3404
+
3405
+ const instructions = [
3406
+ ...preInstructions,
3407
+ beginSwapIx,
3408
+ ...jupiterInstructions,
3409
+ endSwapIx,
3410
+ ];
3411
+
3412
+ const tx = await this.buildTransaction(
3413
+ instructions,
3414
+ txParams,
3415
+ 0,
3416
+ lookupTables
3417
+ );
3418
+
3419
+ const { txSig, slot } = await this.sendTransaction(tx);
3420
+ this.spotMarketLastSlotCache.set(outMarketIndex, slot);
3421
+ this.spotMarketLastSlotCache.set(inMarketIndex, slot);
3422
+
3423
+ return txSig;
3424
+ }
3425
+
3426
+ /**
3427
+ * Get the drift begin_swap and end_swap instructions
3428
+ *
3429
+ * @param outMarketIndex the market index of the token you're buying
3430
+ * @param inMarketIndex the market index of the token you're selling
3431
+ * @param amountIn the amount of the token to sell
3432
+ * @param inTokenAccount the token account to move the tokens being sold
3433
+ * @param outTokenAccount the token account to receive the tokens being bought
3434
+ * @param limitPrice the limit price of the swap
3435
+ */
3436
+ public async getSwapIx({
3437
+ outMarketIndex,
3438
+ inMarketIndex,
3439
+ amountIn,
3440
+ inTokenAccount,
3441
+ outTokenAccount,
3442
+ limitPrice,
3443
+ }: {
3444
+ outMarketIndex: number;
3445
+ inMarketIndex: number;
3446
+ amountIn: BN;
3447
+ inTokenAccount: PublicKey;
3448
+ outTokenAccount: PublicKey;
3449
+ limitPrice?: BN;
3450
+ }): Promise<{
3451
+ beginSwapIx: TransactionInstruction;
3452
+ endSwapIx: TransactionInstruction;
3453
+ }> {
3454
+ const userAccountPublicKey = await this.getUserAccountPublicKey();
3455
+
3456
+ const remainingAccounts = this.getRemainingAccounts({
3457
+ userAccounts: [this.getUserAccount()],
3458
+ writableSpotMarketIndexes: [outMarketIndex, inMarketIndex],
3459
+ });
3460
+
3461
+ const outSpotMarket = this.getSpotMarketAccount(outMarketIndex);
3462
+ const inSpotMarket = this.getSpotMarketAccount(inMarketIndex);
3463
+
3464
+ const beginSwapIx = await this.program.instruction.beginSwap(
3465
+ inMarketIndex,
3466
+ outMarketIndex,
3467
+ amountIn,
3468
+ {
3469
+ accounts: {
3470
+ state: await this.getStatePublicKey(),
3471
+ user: userAccountPublicKey,
3472
+ userStats: this.getUserStatsAccountPublicKey(),
3473
+ authority: this.authority,
3474
+ outSpotMarketVault: outSpotMarket.vault,
3475
+ inSpotMarketVault: inSpotMarket.vault,
3476
+ inTokenAccount,
3477
+ outTokenAccount,
3478
+ tokenProgram: TOKEN_PROGRAM_ID,
3479
+ driftSigner: this.getStateAccount().signer,
3480
+ instructions: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
3481
+ },
3482
+ remainingAccounts,
3483
+ }
3484
+ );
3485
+
3486
+ const endSwapIx = await this.program.instruction.endSwap(
3487
+ inMarketIndex,
3488
+ outMarketIndex,
3489
+ limitPrice ?? null,
3490
+ {
3491
+ accounts: {
3492
+ state: await this.getStatePublicKey(),
3493
+ user: userAccountPublicKey,
3494
+ userStats: this.getUserStatsAccountPublicKey(),
3495
+ authority: this.authority,
3496
+ outSpotMarketVault: outSpotMarket.vault,
3497
+ inSpotMarketVault: inSpotMarket.vault,
3498
+ inTokenAccount,
3499
+ outTokenAccount,
3500
+ tokenProgram: TOKEN_PROGRAM_ID,
3501
+ driftSigner: this.getStateAccount().signer,
3502
+ instructions: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
3503
+ },
3504
+ remainingAccounts,
3505
+ }
3506
+ );
3507
+
3508
+ return { beginSwapIx, endSwapIx };
3509
+ }
3510
+
3269
3511
  public async triggerOrder(
3270
3512
  userAccountPublicKey: PublicKey,
3271
3513
  user: UserAccount,
@@ -5094,7 +5336,9 @@ export class DriftClient {
5094
5336
 
5095
5337
  async buildTransaction(
5096
5338
  instructions: TransactionInstruction | TransactionInstruction[],
5097
- txParams?: TxParams
5339
+ txParams?: TxParams,
5340
+ txVersion?: TransactionVersion,
5341
+ lookupTables?: AddressLookupTableAccount[]
5098
5342
  ): Promise<Transaction | VersionedTransaction> {
5099
5343
  const allIx = [];
5100
5344
  const computeUnits = txParams?.computeUnits ?? 600_000;
@@ -5124,6 +5368,9 @@ export class DriftClient {
5124
5368
  return new Transaction().add(...allIx);
5125
5369
  } else {
5126
5370
  const marketLookupTable = await this.fetchMarketLookupTableAccount();
5371
+ lookupTables = lookupTables
5372
+ ? [...lookupTables, marketLookupTable]
5373
+ : [marketLookupTable];
5127
5374
  const message = new TransactionMessage({
5128
5375
  payerKey: this.provider.wallet.publicKey,
5129
5376
  recentBlockhash: (
@@ -5132,7 +5379,7 @@ export class DriftClient {
5132
5379
  )
5133
5380
  ).blockhash,
5134
5381
  instructions: allIx,
5135
- }).compileToV0Message([marketLookupTable]);
5382
+ }).compileToV0Message(lookupTables);
5136
5383
 
5137
5384
  return new VersionedTransaction(message);
5138
5385
  }
@@ -13,6 +13,7 @@ import {
13
13
  SpotInterestRecord,
14
14
  InsuranceFundStakeRecord,
15
15
  CurveRecord,
16
+ SwapRecord,
16
17
  } from '../index';
17
18
 
18
19
  export type EventSubscriptionOptions = {
@@ -43,6 +44,7 @@ export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
43
44
  'SpotInterestRecord',
44
45
  'InsuranceFundStakeRecord',
45
46
  'CurveRecord',
47
+ 'SwapRecord',
46
48
  ],
47
49
  maxEventsPerType: 4096,
48
50
  orderBy: 'blockchain',
@@ -83,6 +85,7 @@ export type EventMap = {
83
85
  SpotInterestRecord: Event<SpotInterestRecord>;
84
86
  InsuranceFundStakeRecord: Event<InsuranceFundStakeRecord>;
85
87
  CurveRecord: Event<CurveRecord>;
88
+ SwapRecord: Event<SwapRecord>;
86
89
  };
87
90
 
88
91
  export type EventType = keyof EventMap;
@@ -100,7 +103,8 @@ export type DriftEvent =
100
103
  | Event<InsuranceFundRecord>
101
104
  | Event<SpotInterestRecord>
102
105
  | Event<InsuranceFundStakeRecord>
103
- | Event<CurveRecord>;
106
+ | Event<CurveRecord>
107
+ | Event<SwapRecord>;
104
108
 
105
109
  export interface EventSubscriberEvents {
106
110
  newEvent: (event: WrappedEvent<EventType>) => void;