@drift-labs/sdk 2.87.0-beta.5 → 2.87.0-beta.6

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,167 @@
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 async getBestBid(): Promise<BN | undefined> {
99
+ const bids = await this.market.loadBids();
100
+ const bestBid = bids.best();
101
+
102
+ if (bestBid === undefined) {
103
+ return undefined;
104
+ }
105
+
106
+ return new BN(Math.floor(bestBid.price * PRICE_PRECISION.toNumber()));
107
+ }
108
+
109
+ public async getBestAsk(): Promise<BN | undefined> {
110
+ const asks = await this.market.loadAsks();
111
+ const bestAsk = asks.best();
112
+
113
+ if (bestAsk === undefined) {
114
+ return undefined;
115
+ }
116
+
117
+ return new BN(Math.floor(bestAsk.price * PRICE_PRECISION.toNumber()));
118
+ }
119
+
120
+ public getL2Bids(): Generator<L2Level> {
121
+ return this.getL2Levels('bids');
122
+ }
123
+
124
+ public getL2Asks(): Generator<L2Level> {
125
+ return this.getL2Levels('asks');
126
+ }
127
+
128
+ *getL2Levels(side: 'bids' | 'asks'): Generator<L2Level> {
129
+ const basePrecision = Math.ceil(
130
+ 1 / this.market.baseNativeFactor.toNumber()
131
+ );
132
+ const pricePrecision = PRICE_PRECISION.toNumber();
133
+
134
+ const levels = side === 'bids' ? this.market.bids : this.market.asks;
135
+
136
+ for (const order of levels.items()) {
137
+ const size = new BN(order.size * basePrecision);
138
+ const price = new BN(order.price * pricePrecision);
139
+ yield {
140
+ price,
141
+ size,
142
+ sources: {
143
+ openbook: size,
144
+ },
145
+ };
146
+ }
147
+ }
148
+
149
+ public async unsubscribe(): Promise<void> {
150
+ if (!this.subscribed) {
151
+ return;
152
+ }
153
+
154
+ if (this.subscriptionType === 'websocket') {
155
+ await this.connection.removeAccountChangeListener(
156
+ this.marketCallbackId as number
157
+ );
158
+ } else {
159
+ this.accountLoader.removeAccount(
160
+ this.marketAddress,
161
+ this.marketCallbackId as string
162
+ );
163
+ }
164
+
165
+ this.subscribed = false;
166
+ }
167
+ }
package/src/types.ts CHANGED
@@ -746,6 +746,8 @@ export type SpotMarketAccount = {
746
746
  fuelBoostTaker: number;
747
747
  fuelBoostMaker: number;
748
748
  fuelBoostInsurance: number;
749
+
750
+ tokenProgram: number;
749
751
  };
750
752
 
751
753
  export type PoolBalance = {
@@ -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
+ });