@drift-labs/sdk 2.87.0-beta.0 → 2.87.0-beta.10

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.
Files changed (54) hide show
  1. package/VERSION +1 -1
  2. package/lib/addresses/pda.d.ts +2 -0
  3. package/lib/addresses/pda.js +9 -1
  4. package/lib/adminClient.d.ts +2 -0
  5. package/lib/adminClient.js +27 -4
  6. package/lib/bankrun/bankrunConnection.js +1 -1
  7. package/lib/config.d.ts +1 -0
  8. package/lib/config.js +2 -0
  9. package/lib/constants/perpMarkets.js +12 -12
  10. package/lib/constants/spotMarkets.d.ts +1 -0
  11. package/lib/constants/spotMarkets.js +13 -3
  12. package/lib/dlob/orderBookLevels.d.ts +1 -1
  13. package/lib/driftClient.d.ts +15 -5
  14. package/lib/driftClient.js +155 -41
  15. package/lib/events/types.d.ts +3 -2
  16. package/lib/events/types.js +1 -0
  17. package/lib/factory/oracleClient.js +4 -0
  18. package/lib/idl/drift.json +147 -5
  19. package/lib/idl/openbook.json +3854 -0
  20. package/lib/idl/switchboard_on_demand_30.json +4383 -0
  21. package/lib/index.d.ts +2 -0
  22. package/lib/index.js +2 -0
  23. package/lib/math/spotMarket.d.ts +6 -0
  24. package/lib/math/spotMarket.js +16 -1
  25. package/lib/math/superStake.d.ts +3 -2
  26. package/lib/openbook/openbookV2FulfillmentConfigMap.d.ts +10 -0
  27. package/lib/openbook/openbookV2FulfillmentConfigMap.js +17 -0
  28. package/lib/openbook/openbookV2Subscriber.d.ts +36 -0
  29. package/lib/openbook/openbookV2Subscriber.js +102 -0
  30. package/lib/oracles/switchboardOnDemandClient.d.ts +11 -0
  31. package/lib/oracles/switchboardOnDemandClient.js +32 -0
  32. package/lib/types.d.ts +13 -0
  33. package/lib/types.js +1 -0
  34. package/package.json +6 -2
  35. package/src/addresses/pda.ts +10 -0
  36. package/src/adminClient.ts +47 -4
  37. package/src/bankrun/bankrunConnection.ts +2 -2
  38. package/src/config.ts +3 -0
  39. package/src/constants/perpMarkets.ts +12 -12
  40. package/src/constants/spotMarkets.ts +15 -3
  41. package/src/dlob/orderBookLevels.ts +1 -1
  42. package/src/driftClient.ts +229 -52
  43. package/src/events/types.ts +5 -1
  44. package/src/factory/oracleClient.ts +5 -0
  45. package/src/idl/drift.json +147 -5
  46. package/src/idl/switchboard_on_demand_30.json +4383 -0
  47. package/src/index.ts +2 -0
  48. package/src/math/spotMarket.ts +28 -2
  49. package/src/openbook/openbookV2FulfillmentConfigMap.ts +29 -0
  50. package/src/openbook/openbookV2Subscriber.ts +165 -0
  51. package/src/oracles/switchboardOnDemandClient.ts +56 -0
  52. package/src/types.ts +13 -0
  53. package/tests/ci/verifyConstants.ts +3 -6
  54. package/tests/subscriber/openbook.ts +58 -0
package/src/index.ts CHANGED
@@ -73,6 +73,8 @@ export * from './serum/serumFulfillmentConfigMap';
73
73
  export * from './phoenix/phoenixSubscriber';
74
74
  export * from './priorityFee';
75
75
  export * from './phoenix/phoenixFulfillmentConfigMap';
76
+ export * from './openbook/openbookV2Subscriber';
77
+ export * from './openbook/openbookV2FulfillmentConfigMap';
76
78
  export * from './tx/fastSingleTxSender';
77
79
  export * from './tx/retryTxSender';
78
80
  export * from './tx/whileValidTxSender';
@@ -5,8 +5,12 @@ import {
5
5
  SpotBalanceType,
6
6
  SpotMarketAccount,
7
7
  } from '../types';
8
- import { calculateAssetWeight, calculateLiabilityWeight } from './spotBalance';
9
- import { MARGIN_PRECISION } from '../constants/numericConstants';
8
+ import {
9
+ calculateAssetWeight,
10
+ calculateLiabilityWeight,
11
+ getTokenAmount,
12
+ } from './spotBalance';
13
+ import { MARGIN_PRECISION, ZERO } from '../constants/numericConstants';
10
14
  import { numberToSafeBN } from './utils';
11
15
 
12
16
  export function castNumberToSpotPrecision(
@@ -54,3 +58,25 @@ export function calculateSpotMarketMarginRatio(
54
58
 
55
59
  return marginRatio;
56
60
  }
61
+
62
+ /**
63
+ * Returns the maximum remaining deposit that can be made to the spot market. If the maxTokenDeposits on the market is zero then there is no limit and this function will also return zero. (so that needs to be checked)
64
+ * @param market
65
+ * @returns
66
+ */
67
+ export function calculateMaxRemainingDeposit(market: SpotMarketAccount) {
68
+ const marketMaxTokenDeposits = market.maxTokenDeposits;
69
+
70
+ if (marketMaxTokenDeposits.eq(ZERO)) {
71
+ // If the maxTokenDeposits is set to zero then that means there is no limit. Return the largest number we can to represent infinite available deposit.
72
+ return ZERO;
73
+ }
74
+
75
+ const totalDepositsTokenAmount = getTokenAmount(
76
+ market.depositBalance,
77
+ market,
78
+ SpotBalanceType.DEPOSIT
79
+ );
80
+
81
+ return marketMaxTokenDeposits.sub(totalDepositsTokenAmount);
82
+ }
@@ -0,0 +1,29 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { OpenbookV2FulfillmentConfigAccount } from '../types';
3
+ import { DriftClient } from '../driftClient';
4
+
5
+ export class OpenbookV2FulfillmentConfigMap {
6
+ driftClient: DriftClient;
7
+ map = new Map<number, OpenbookV2FulfillmentConfigAccount>();
8
+
9
+ public constructor(driftClient: DriftClient) {
10
+ this.driftClient = driftClient;
11
+ }
12
+
13
+ public async add(
14
+ marketIndex: number,
15
+ openbookV2MarketAddress: PublicKey
16
+ ): Promise<void> {
17
+ const account = await this.driftClient.getOpenbookV2FulfillmentConfig(
18
+ openbookV2MarketAddress
19
+ );
20
+
21
+ this.map.set(marketIndex, account);
22
+ }
23
+
24
+ public get(
25
+ marketIndex: number
26
+ ): OpenbookV2FulfillmentConfigAccount | undefined {
27
+ return this.map.get(marketIndex);
28
+ }
29
+ }
@@ -0,0 +1,165 @@
1
+ import { Connection, Keypair, PublicKey } from '@solana/web3.js';
2
+ import { BulkAccountLoader } from '../accounts/bulkAccountLoader';
3
+ import { PRICE_PRECISION } from '../constants/numericConstants';
4
+ import { AnchorProvider, BN, Idl, Program, Wallet } from '@coral-xyz/anchor';
5
+ import { L2Level, L2OrderBookGenerator } from '../dlob/orderBookLevels';
6
+ import { Market, OpenBookV2Client } from '@openbook-dex/openbook-v2';
7
+ import openbookV2Idl from '../idl/openbook.json';
8
+
9
+ export type OpenbookV2SubscriberConfig = {
10
+ connection: Connection;
11
+ programId: PublicKey;
12
+ marketAddress: PublicKey;
13
+ accountSubscription:
14
+ | {
15
+ // enables use to add web sockets in the future
16
+ type: 'polling';
17
+ accountLoader: BulkAccountLoader;
18
+ }
19
+ | {
20
+ type: 'websocket';
21
+ };
22
+ };
23
+
24
+ export class OpenbookV2Subscriber implements L2OrderBookGenerator {
25
+ connection: Connection;
26
+ programId: PublicKey;
27
+ marketAddress: PublicKey;
28
+ subscriptionType: 'polling' | 'websocket';
29
+ accountLoader: BulkAccountLoader | undefined;
30
+ subscribed: boolean;
31
+ market: Market;
32
+ marketCallbackId: string | number;
33
+ client: OpenBookV2Client;
34
+
35
+ public constructor(config: OpenbookV2SubscriberConfig) {
36
+ this.connection = config.connection;
37
+ this.programId = config.programId;
38
+ this.marketAddress = config.marketAddress;
39
+ this.subscribed = false;
40
+ if (config.accountSubscription.type === 'polling') {
41
+ this.subscriptionType = 'polling';
42
+ this.accountLoader = config.accountSubscription.accountLoader;
43
+ } else {
44
+ this.subscriptionType = 'websocket';
45
+ }
46
+ }
47
+
48
+ public async subscribe(): Promise<void> {
49
+ if (this.subscribed === true) {
50
+ return;
51
+ }
52
+
53
+ const anchorProvider = new AnchorProvider(
54
+ this.connection,
55
+ new Wallet(Keypair.generate()),
56
+ {}
57
+ );
58
+ const openbookV2Program = new Program(
59
+ openbookV2Idl as Idl,
60
+ this.programId,
61
+ anchorProvider
62
+ );
63
+ this.client = new OpenBookV2Client(anchorProvider);
64
+ const market = await Market.load(this.client, this.marketAddress);
65
+ this.market = await market.loadOrderBook();
66
+
67
+ if (this.subscriptionType === 'websocket') {
68
+ this.marketCallbackId = this.connection.onAccountChange(
69
+ this.marketAddress,
70
+ async (accountInfo, _) => {
71
+ const marketRaw = openbookV2Program.coder.accounts.decode(
72
+ 'Market',
73
+ accountInfo.data
74
+ );
75
+ this.market = new Market(this.client, this.marketAddress, marketRaw);
76
+ await this.market.loadOrderBook();
77
+ }
78
+ );
79
+ } else {
80
+ this.marketCallbackId = await this.accountLoader.addAccount(
81
+ this.marketAddress,
82
+ (buffer, _) => {
83
+ const marketRaw = openbookV2Program.coder.accounts.decode(
84
+ 'Market',
85
+ buffer
86
+ );
87
+ this.market = new Market(this.client, this.marketAddress, marketRaw);
88
+ (async () => {
89
+ await this.market.loadOrderBook();
90
+ })();
91
+ }
92
+ );
93
+ }
94
+
95
+ this.subscribed = true;
96
+ }
97
+
98
+ public getBestBid(): BN | undefined {
99
+ const bestBid = this.market.bids.best();
100
+
101
+ if (bestBid === undefined) {
102
+ return undefined;
103
+ }
104
+
105
+ return new BN(Math.floor(bestBid.price * PRICE_PRECISION.toNumber()));
106
+ }
107
+
108
+ public getBestAsk(): BN | undefined {
109
+ const bestAsk = this.market.asks.best();
110
+
111
+ if (bestAsk === undefined) {
112
+ return undefined;
113
+ }
114
+
115
+ return new BN(Math.floor(bestAsk.price * PRICE_PRECISION.toNumber()));
116
+ }
117
+
118
+ public getL2Bids(): Generator<L2Level> {
119
+ return this.getL2Levels('bids');
120
+ }
121
+
122
+ public getL2Asks(): Generator<L2Level> {
123
+ return this.getL2Levels('asks');
124
+ }
125
+
126
+ *getL2Levels(side: 'bids' | 'asks'): Generator<L2Level> {
127
+ const basePrecision = Math.ceil(
128
+ 1 / this.market.baseNativeFactor.toNumber()
129
+ );
130
+ const pricePrecision = PRICE_PRECISION.toNumber();
131
+
132
+ const levels = side === 'bids' ? this.market.bids : this.market.asks;
133
+
134
+ for (const order of levels.items()) {
135
+ const size = new BN(order.size * basePrecision);
136
+ const price = new BN(order.price * pricePrecision);
137
+ yield {
138
+ price,
139
+ size,
140
+ sources: {
141
+ openbook: size,
142
+ },
143
+ };
144
+ }
145
+ }
146
+
147
+ public async unsubscribe(): Promise<void> {
148
+ if (!this.subscribed) {
149
+ return;
150
+ }
151
+
152
+ if (this.subscriptionType === 'websocket') {
153
+ await this.connection.removeAccountChangeListener(
154
+ this.marketCallbackId as number
155
+ );
156
+ } else {
157
+ this.accountLoader.removeAccount(
158
+ this.marketAddress,
159
+ this.marketCallbackId as string
160
+ );
161
+ }
162
+
163
+ this.subscribed = false;
164
+ }
165
+ }
@@ -0,0 +1,56 @@
1
+ import { Connection, PublicKey } from '@solana/web3.js';
2
+ import { OracleClient, OraclePriceData } from './types';
3
+ import { BN } from '@coral-xyz/anchor';
4
+ import switchboardOnDemandIdl from '../idl/switchboard_on_demand_30.json';
5
+ import { PRICE_PRECISION_EXP } from '../constants/numericConstants';
6
+ import {
7
+ BorshAccountsCoder as BorshAccountsCoder30,
8
+ Idl as Idl30,
9
+ } from '@coral-xyz/anchor-30';
10
+
11
+ const SB_PRECISION_EXP = new BN(18);
12
+ const SB_PRECISION = new BN(10).pow(SB_PRECISION_EXP.sub(PRICE_PRECISION_EXP));
13
+
14
+ type PullFeedAccountData = {
15
+ result: {
16
+ value: BN;
17
+ std_dev: BN;
18
+ mean: BN;
19
+ slot: BN;
20
+ range: BN;
21
+ };
22
+ last_update_timestamp: BN;
23
+ max_variance: BN;
24
+ min_responses: BN;
25
+ };
26
+
27
+ export class SwitchboardOnDemandClient implements OracleClient {
28
+ connection: Connection;
29
+ coder: BorshAccountsCoder30;
30
+
31
+ public constructor(connection: Connection) {
32
+ this.connection = connection;
33
+ this.coder = new BorshAccountsCoder30(switchboardOnDemandIdl as Idl30);
34
+ }
35
+
36
+ public async getOraclePriceData(
37
+ pricePublicKey: PublicKey
38
+ ): Promise<OraclePriceData> {
39
+ const accountInfo = await this.connection.getAccountInfo(pricePublicKey);
40
+ return this.getOraclePriceDataFromBuffer(accountInfo.data);
41
+ }
42
+
43
+ public getOraclePriceDataFromBuffer(buffer: Buffer): OraclePriceData {
44
+ const pullFeedAccountData = this.coder.decodeUnchecked(
45
+ 'PullFeedAccountData',
46
+ buffer
47
+ ) as PullFeedAccountData;
48
+
49
+ return {
50
+ price: pullFeedAccountData.result.value.div(SB_PRECISION),
51
+ slot: pullFeedAccountData.result.slot,
52
+ confidence: pullFeedAccountData.result.range.div(SB_PRECISION),
53
+ hasSufficientNumberOfDataPoints: true,
54
+ };
55
+ }
56
+ }
package/src/types.ts CHANGED
@@ -117,6 +117,7 @@ export class OracleSource {
117
117
  static readonly PYTH_STABLE_COIN = { pythStableCoin: {} };
118
118
  static readonly PYTH_STABLE_COIN_PULL = { pythStableCoinPull: {} };
119
119
  static readonly Prelaunch = { prelaunch: {} };
120
+ static readonly SWITCHBOARD_ON_DEMAND = { switchboardOnDemand: {} };
120
121
  }
121
122
 
122
123
  export class OrderType {
@@ -575,6 +576,16 @@ export type SwapRecord = {
575
576
  fee: BN;
576
577
  };
577
578
 
579
+ export type SpotMarketVaultDepositRecord = {
580
+ ts: BN;
581
+ marketIndex: number;
582
+ depositBalance: BN;
583
+ cumulativeDepositInterestBefore: BN;
584
+ cumulativeDepositInterestAfter: BN;
585
+ depositTokenAmountBefore: BN;
586
+ amount: BN;
587
+ };
588
+
578
589
  export type StateAccount = {
579
590
  admin: PublicKey;
580
591
  exchangeStatus: number;
@@ -745,6 +756,8 @@ export type SpotMarketAccount = {
745
756
  fuelBoostTaker: number;
746
757
  fuelBoostMaker: number;
747
758
  fuelBoostInsurance: number;
759
+
760
+ tokenProgram: number;
748
761
  };
749
762
 
750
763
  export type PoolBalance = {
@@ -19,12 +19,9 @@ describe('Verify Constants', function () {
19
19
  const MAINNET_RPC_ENDPOINT = process.env.MAINNET_RPC_ENDPOINT;
20
20
  const DEVNET_RPC_ENDPOINT = process.env.DEVNET_RPC_ENDPOINT;
21
21
 
22
- if (MAINNET_RPC_ENDPOINT === undefined) {
23
- throw new Error('MAINNET_RPC_ENDPOINT not found in .env');
24
- }
25
-
26
- if (DEVNET_RPC_ENDPOINT === undefined) {
27
- throw new Error('DEVNET_RPC_ENDPOINT not found in .env');
22
+ // avoid breaking pre-commit
23
+ if (MAINNET_RPC_ENDPOINT === undefined && DEVNET_RPC_ENDPOINT === undefined) {
24
+ return;
28
25
  }
29
26
 
30
27
  const wallet = new Wallet(Keypair.generate());
@@ -0,0 +1,58 @@
1
+ import { OpenbookV2Subscriber, PRICE_PRECISION } from '../../lib';
2
+ import { Connection, PublicKey } from '@solana/web3.js';
3
+
4
+ describe('openbook v2 subscriber', function () {
5
+ this.timeout(100_000);
6
+
7
+ it('works', async function () {
8
+ const connection = new Connection(
9
+ process.env.MAINNET_RPC_ENDPOINT as string
10
+ );
11
+ const solUsdc = new PublicKey(
12
+ 'AFgkED1FUVfBe2trPUDqSqK9QKd4stJrfzq5q1RwAFTa'
13
+ );
14
+ const openbook = new PublicKey(
15
+ 'opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb'
16
+ );
17
+
18
+ const openbookV2Subscriber = new OpenbookV2Subscriber({
19
+ connection,
20
+ programId: openbook,
21
+ marketAddress: solUsdc,
22
+ accountSubscription: {
23
+ type: 'websocket',
24
+ },
25
+ });
26
+
27
+ await openbookV2Subscriber.subscribe();
28
+
29
+ // wait for updates
30
+ await new Promise((resolve) => setTimeout(resolve, 5_000));
31
+
32
+ const basePrecision = Math.ceil(
33
+ 1 / openbookV2Subscriber.market.baseNativeFactor.toNumber()
34
+ );
35
+
36
+ console.log('Bids');
37
+ for (const bid of openbookV2Subscriber.getL2Bids()) {
38
+ console.log('Price: ', bid.price.toNumber() / PRICE_PRECISION.toNumber());
39
+ console.log('Size: ', bid.size.toNumber() / basePrecision);
40
+ console.log('Source: ', bid.sources);
41
+ }
42
+
43
+ console.log('Asks');
44
+ for (const ask of openbookV2Subscriber.getL2Asks()) {
45
+ console.log('Price: ', ask.price.toNumber() / PRICE_PRECISION.toNumber());
46
+ console.log('Size: ', ask.size.toNumber() / basePrecision);
47
+ console.log('Source: ', ask.sources);
48
+ }
49
+
50
+ const bestBid = await openbookV2Subscriber.getBestBid();
51
+ console.log('Best bid:', bestBid.toNumber());
52
+
53
+ const bestAsk = await openbookV2Subscriber.getBestAsk();
54
+ console.log('Best ask:', bestAsk.toNumber());
55
+
56
+ await openbookV2Subscriber.unsubscribe();
57
+ });
58
+ });