@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.
- package/VERSION +1 -1
- package/lib/addresses/pda.d.ts +2 -0
- package/lib/addresses/pda.js +9 -1
- package/lib/adminClient.d.ts +2 -0
- package/lib/adminClient.js +27 -4
- package/lib/bankrun/bankrunConnection.js +1 -1
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -0
- package/lib/constants/perpMarkets.js +12 -12
- package/lib/constants/spotMarkets.d.ts +1 -0
- package/lib/constants/spotMarkets.js +13 -3
- package/lib/dlob/orderBookLevels.d.ts +1 -1
- package/lib/driftClient.d.ts +15 -5
- package/lib/driftClient.js +155 -41
- package/lib/events/types.d.ts +3 -2
- package/lib/events/types.js +1 -0
- package/lib/factory/oracleClient.js +4 -0
- package/lib/idl/drift.json +147 -5
- package/lib/idl/openbook.json +3854 -0
- package/lib/idl/switchboard_on_demand_30.json +4383 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/math/spotMarket.d.ts +6 -0
- package/lib/math/spotMarket.js +16 -1
- package/lib/math/superStake.d.ts +3 -2
- package/lib/openbook/openbookV2FulfillmentConfigMap.d.ts +10 -0
- package/lib/openbook/openbookV2FulfillmentConfigMap.js +17 -0
- package/lib/openbook/openbookV2Subscriber.d.ts +36 -0
- package/lib/openbook/openbookV2Subscriber.js +102 -0
- package/lib/oracles/switchboardOnDemandClient.d.ts +11 -0
- package/lib/oracles/switchboardOnDemandClient.js +32 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +6 -2
- package/src/addresses/pda.ts +10 -0
- package/src/adminClient.ts +47 -4
- package/src/bankrun/bankrunConnection.ts +2 -2
- package/src/config.ts +3 -0
- package/src/constants/perpMarkets.ts +12 -12
- package/src/constants/spotMarkets.ts +15 -3
- package/src/dlob/orderBookLevels.ts +1 -1
- package/src/driftClient.ts +229 -52
- package/src/events/types.ts +5 -1
- package/src/factory/oracleClient.ts +5 -0
- package/src/idl/drift.json +147 -5
- package/src/idl/switchboard_on_demand_30.json +4383 -0
- package/src/index.ts +2 -0
- package/src/math/spotMarket.ts +28 -2
- package/src/openbook/openbookV2FulfillmentConfigMap.ts +29 -0
- package/src/openbook/openbookV2Subscriber.ts +165 -0
- package/src/oracles/switchboardOnDemandClient.ts +56 -0
- package/src/types.ts +13 -0
- package/tests/ci/verifyConstants.ts +3 -6
- 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';
|
package/src/math/spotMarket.ts
CHANGED
|
@@ -5,8 +5,12 @@ import {
|
|
|
5
5
|
SpotBalanceType,
|
|
6
6
|
SpotMarketAccount,
|
|
7
7
|
} from '../types';
|
|
8
|
-
import {
|
|
9
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
+
});
|