@drift-labs/sdk 2.26.0-beta.1 → 2.26.0-beta.3
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/lib/config.d.ts +1 -0
- package/lib/config.js +2 -0
- package/lib/constants/spotMarkets.d.ts +1 -0
- package/lib/constants/spotMarkets.js +1 -0
- package/lib/dlob/DLOB.d.ts +38 -2
- package/lib/dlob/DLOB.js +69 -0
- package/lib/dlob/DLOBApiClient.js +1 -1
- package/lib/dlob/DLOBSubscriber.d.ts +34 -0
- package/lib/dlob/DLOBSubscriber.js +100 -0
- package/lib/dlob/orderBookLevels.d.ts +44 -0
- package/lib/dlob/orderBookLevels.js +137 -0
- package/lib/dlob/types.d.ts +2 -0
- package/lib/driftClient.d.ts +28 -10
- package/lib/driftClient.js +158 -55
- package/lib/driftClientConfig.d.ts +3 -0
- package/lib/idl/drift.json +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/serum/serumSubscriber.d.ts +5 -1
- package/lib/serum/serumSubscriber.js +22 -0
- package/lib/types.d.ts +1 -0
- package/package.json +1 -1
- package/src/assert/assert.js +9 -0
- package/src/config.ts +3 -0
- package/src/constants/spotMarkets.ts +4 -0
- package/src/dlob/DLOB.ts +177 -17
- package/src/dlob/DLOBApiClient.ts +3 -1
- package/src/dlob/DLOBSubscriber.ts +141 -0
- package/src/dlob/orderBookLevels.ts +243 -0
- package/src/dlob/types.ts +2 -0
- package/src/driftClient.ts +231 -66
- package/src/driftClientConfig.ts +3 -0
- package/src/idl/drift.json +1 -1
- package/src/index.ts +1 -0
- package/src/serum/serumSubscriber.ts +29 -1
- package/src/token/index.js +38 -0
- package/src/types.ts +1 -0
- package/src/util/computeUnits.js +27 -0
- package/src/util/getTokenAddress.js +9 -0
- package/src/util/promiseTimeout.js +14 -0
- package/src/util/tps.js +27 -0
- package/tests/dlob/helpers.ts +3 -0
package/src/dlob/DLOB.ts
CHANGED
|
@@ -1,37 +1,48 @@
|
|
|
1
1
|
import { getOrderSignature, getVammNodeGenerator, NodeList } from './NodeList';
|
|
2
2
|
import {
|
|
3
|
-
MarketType,
|
|
4
3
|
BN,
|
|
5
4
|
calculateAskPrice,
|
|
6
5
|
calculateBidPrice,
|
|
7
|
-
DriftClient,
|
|
8
6
|
convertToNumber,
|
|
9
|
-
|
|
7
|
+
DLOBNode,
|
|
8
|
+
DLOBNodeType,
|
|
9
|
+
DriftClient,
|
|
10
|
+
getLimitPrice,
|
|
11
|
+
getVariant,
|
|
12
|
+
isFallbackAvailableLiquiditySource,
|
|
10
13
|
isOneOfVariant,
|
|
14
|
+
isOrderExpired,
|
|
15
|
+
isRestingLimitOrder,
|
|
16
|
+
isTakingOrder,
|
|
17
|
+
isTriggered,
|
|
11
18
|
isVariant,
|
|
12
|
-
|
|
19
|
+
MarketType,
|
|
20
|
+
MarketTypeStr,
|
|
21
|
+
mustBeTriggered,
|
|
22
|
+
OraclePriceData,
|
|
13
23
|
Order,
|
|
14
|
-
|
|
15
|
-
|
|
24
|
+
OrderActionRecord,
|
|
25
|
+
OrderRecord,
|
|
16
26
|
PerpMarketAccount,
|
|
17
|
-
|
|
27
|
+
PRICE_PRECISION,
|
|
18
28
|
SlotSubscriber,
|
|
19
|
-
|
|
29
|
+
SpotMarketAccount,
|
|
20
30
|
StateAccount,
|
|
21
|
-
|
|
22
|
-
isTriggered,
|
|
23
|
-
getLimitPrice,
|
|
31
|
+
TriggerOrderNode,
|
|
24
32
|
UserMap,
|
|
25
|
-
OrderRecord,
|
|
26
|
-
OrderActionRecord,
|
|
27
|
-
isRestingLimitOrder,
|
|
28
|
-
isTakingOrder,
|
|
29
|
-
isFallbackAvailableLiquiditySource,
|
|
30
33
|
} from '..';
|
|
31
34
|
import { PublicKey } from '@solana/web3.js';
|
|
32
|
-
import { DLOBNode, DLOBNodeType, TriggerOrderNode } from '..';
|
|
33
35
|
import { ammPaused, exchangePaused, fillPaused } from '../math/exchangeStatus';
|
|
34
36
|
import { DLOBOrders } from './DLOBOrders';
|
|
37
|
+
import {
|
|
38
|
+
createL2Levels,
|
|
39
|
+
getL2GeneratorFromDLOBNodes,
|
|
40
|
+
L2OrderBook,
|
|
41
|
+
L2OrderBookGenerator,
|
|
42
|
+
L3Level,
|
|
43
|
+
L3OrderBook,
|
|
44
|
+
mergeL2LevelGenerators,
|
|
45
|
+
} from './orderBookLevels';
|
|
35
46
|
|
|
36
47
|
export type MarketNodeLists = {
|
|
37
48
|
restingLimit: {
|
|
@@ -1689,4 +1700,153 @@ export class DLOB {
|
|
|
1689
1700
|
yield nodeLists.trigger.below;
|
|
1690
1701
|
}
|
|
1691
1702
|
}
|
|
1703
|
+
|
|
1704
|
+
/**
|
|
1705
|
+
* Get an L2 view of the order book for a given market.
|
|
1706
|
+
*
|
|
1707
|
+
* @param marketIndex
|
|
1708
|
+
* @param marketType
|
|
1709
|
+
* @param slot
|
|
1710
|
+
* @param oraclePriceData
|
|
1711
|
+
* @param depth how many levels of the order book to return
|
|
1712
|
+
* @param fallbackAsk best ask for fallback liquidity, only relevant for perps
|
|
1713
|
+
* @param fallbackBid best bid for fallback liquidity, only relevant for perps
|
|
1714
|
+
* @param fallbackL2Generators L2 generators for fallback liquidity e.g. vAMM {@link getVammL2Generator}, openbook {@link SerumSubscriber}
|
|
1715
|
+
*/
|
|
1716
|
+
public getL2({
|
|
1717
|
+
marketIndex,
|
|
1718
|
+
marketType,
|
|
1719
|
+
slot,
|
|
1720
|
+
oraclePriceData,
|
|
1721
|
+
depth,
|
|
1722
|
+
fallbackAsk,
|
|
1723
|
+
fallbackBid,
|
|
1724
|
+
fallbackL2Generators = [],
|
|
1725
|
+
}: {
|
|
1726
|
+
marketIndex: number;
|
|
1727
|
+
marketType: MarketType;
|
|
1728
|
+
slot: number;
|
|
1729
|
+
oraclePriceData: OraclePriceData;
|
|
1730
|
+
depth: number;
|
|
1731
|
+
fallbackAsk?: BN;
|
|
1732
|
+
fallbackBid?: BN;
|
|
1733
|
+
fallbackL2Generators?: L2OrderBookGenerator[];
|
|
1734
|
+
}): L2OrderBook {
|
|
1735
|
+
const makerAskL2LevelGenerator = getL2GeneratorFromDLOBNodes(
|
|
1736
|
+
this.getMakerLimitAsks(
|
|
1737
|
+
marketIndex,
|
|
1738
|
+
slot,
|
|
1739
|
+
marketType,
|
|
1740
|
+
oraclePriceData,
|
|
1741
|
+
fallbackBid
|
|
1742
|
+
),
|
|
1743
|
+
oraclePriceData,
|
|
1744
|
+
slot
|
|
1745
|
+
);
|
|
1746
|
+
|
|
1747
|
+
const fallbackAskGenerators = fallbackL2Generators.map(
|
|
1748
|
+
(fallbackL2Generator) => {
|
|
1749
|
+
return fallbackL2Generator.getL2Asks();
|
|
1750
|
+
}
|
|
1751
|
+
);
|
|
1752
|
+
|
|
1753
|
+
const askL2LevelGenerator = mergeL2LevelGenerators(
|
|
1754
|
+
[makerAskL2LevelGenerator, ...fallbackAskGenerators],
|
|
1755
|
+
(a, b) => {
|
|
1756
|
+
return a.price.lt(b.price);
|
|
1757
|
+
}
|
|
1758
|
+
);
|
|
1759
|
+
|
|
1760
|
+
const asks = createL2Levels(askL2LevelGenerator, depth);
|
|
1761
|
+
|
|
1762
|
+
const makerBidGenerator = getL2GeneratorFromDLOBNodes(
|
|
1763
|
+
this.getMakerLimitBids(
|
|
1764
|
+
marketIndex,
|
|
1765
|
+
slot,
|
|
1766
|
+
marketType,
|
|
1767
|
+
oraclePriceData,
|
|
1768
|
+
fallbackAsk
|
|
1769
|
+
),
|
|
1770
|
+
oraclePriceData,
|
|
1771
|
+
slot
|
|
1772
|
+
);
|
|
1773
|
+
|
|
1774
|
+
const fallbackBidGenerators = fallbackL2Generators.map((fallbackOrders) => {
|
|
1775
|
+
return fallbackOrders.getL2Bids();
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
const bidL2LevelGenerator = mergeL2LevelGenerators(
|
|
1779
|
+
[makerBidGenerator, ...fallbackBidGenerators],
|
|
1780
|
+
(a, b) => {
|
|
1781
|
+
return a.price.gt(b.price);
|
|
1782
|
+
}
|
|
1783
|
+
);
|
|
1784
|
+
|
|
1785
|
+
const bids = createL2Levels(bidL2LevelGenerator, depth);
|
|
1786
|
+
|
|
1787
|
+
return {
|
|
1788
|
+
bids,
|
|
1789
|
+
asks,
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
/**
|
|
1794
|
+
* Get an L3 view of the order book for a given market. Does not include fallback liquidity sources
|
|
1795
|
+
*
|
|
1796
|
+
* @param marketIndex
|
|
1797
|
+
* @param marketType
|
|
1798
|
+
* @param slot
|
|
1799
|
+
* @param oraclePriceData
|
|
1800
|
+
*/
|
|
1801
|
+
public getL3({
|
|
1802
|
+
marketIndex,
|
|
1803
|
+
marketType,
|
|
1804
|
+
slot,
|
|
1805
|
+
oraclePriceData,
|
|
1806
|
+
}: {
|
|
1807
|
+
marketIndex: number;
|
|
1808
|
+
marketType: MarketType;
|
|
1809
|
+
slot: number;
|
|
1810
|
+
oraclePriceData: OraclePriceData;
|
|
1811
|
+
}): L3OrderBook {
|
|
1812
|
+
const bids: L3Level[] = [];
|
|
1813
|
+
const asks: L3Level[] = [];
|
|
1814
|
+
|
|
1815
|
+
const restingAsks = this.getRestingLimitAsks(
|
|
1816
|
+
marketIndex,
|
|
1817
|
+
slot,
|
|
1818
|
+
marketType,
|
|
1819
|
+
oraclePriceData
|
|
1820
|
+
);
|
|
1821
|
+
|
|
1822
|
+
for (const ask of restingAsks) {
|
|
1823
|
+
asks.push({
|
|
1824
|
+
price: ask.getPrice(oraclePriceData, slot),
|
|
1825
|
+
size: ask.order.baseAssetAmount.sub(ask.order.baseAssetAmountFilled),
|
|
1826
|
+
maker: ask.userAccount,
|
|
1827
|
+
orderId: ask.order.orderId,
|
|
1828
|
+
});
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
const restingBids = this.getRestingLimitBids(
|
|
1832
|
+
marketIndex,
|
|
1833
|
+
slot,
|
|
1834
|
+
marketType,
|
|
1835
|
+
oraclePriceData
|
|
1836
|
+
);
|
|
1837
|
+
|
|
1838
|
+
for (const bid of restingBids) {
|
|
1839
|
+
bids.push({
|
|
1840
|
+
price: bid.getPrice(oraclePriceData, slot),
|
|
1841
|
+
size: bid.order.baseAssetAmount.sub(bid.order.baseAssetAmountFilled),
|
|
1842
|
+
maker: bid.userAccount,
|
|
1843
|
+
orderId: bid.order.orderId,
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
return {
|
|
1848
|
+
bids,
|
|
1849
|
+
asks,
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1692
1852
|
}
|
|
@@ -19,7 +19,9 @@ export class DLOBApiClient {
|
|
|
19
19
|
public async getDLOB(slot: number): Promise<DLOB> {
|
|
20
20
|
const r = await fetch(this.url);
|
|
21
21
|
if (!r.ok) {
|
|
22
|
-
throw new Error(
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Failed to fetch DLOB from ${this.url}. Status: ${r.status}, ${r.statusText}`
|
|
24
|
+
);
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
const resp = await r.json();
|
|
@@ -7,8 +7,18 @@ import {
|
|
|
7
7
|
DLOBSubscriptionConfig,
|
|
8
8
|
SlotSource,
|
|
9
9
|
} from './types';
|
|
10
|
+
import { DriftClient } from '../driftClient';
|
|
11
|
+
import { isVariant, MarketType } from '../types';
|
|
12
|
+
import {
|
|
13
|
+
getVammL2Generator,
|
|
14
|
+
L2OrderBook,
|
|
15
|
+
L2OrderBookGenerator,
|
|
16
|
+
L3OrderBook,
|
|
17
|
+
} from './orderBookLevels';
|
|
18
|
+
import { calculateAskPrice, calculateBidPrice } from '../math/market';
|
|
10
19
|
|
|
11
20
|
export class DLOBSubscriber {
|
|
21
|
+
driftClient: DriftClient;
|
|
12
22
|
dlobSource: DLOBSource;
|
|
13
23
|
slotSource: SlotSource;
|
|
14
24
|
updateFrequency: number;
|
|
@@ -17,6 +27,7 @@ export class DLOBSubscriber {
|
|
|
17
27
|
public eventEmitter: StrictEventEmitter<EventEmitter, DLOBSubscriberEvents>;
|
|
18
28
|
|
|
19
29
|
constructor(config: DLOBSubscriptionConfig) {
|
|
30
|
+
this.driftClient = config.driftClient;
|
|
20
31
|
this.dlobSource = config.dlobSource;
|
|
21
32
|
this.slotSource = config.slotSource;
|
|
22
33
|
this.updateFrequency = config.updateFrequency;
|
|
@@ -48,6 +59,136 @@ export class DLOBSubscriber {
|
|
|
48
59
|
return this.dlob;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Get the L2 order book for a given market.
|
|
64
|
+
*
|
|
65
|
+
* @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided.
|
|
66
|
+
* @param marketIndex
|
|
67
|
+
* @param marketType
|
|
68
|
+
* @param depth Number of orders to include in the order book. Defaults to 10.
|
|
69
|
+
* @param includeVamm Whether to include the VAMM orders in the order book. Defaults to false. If true, creates vAMM generator {@link getVammL2Generator} and adds it to fallbackL2Generators.
|
|
70
|
+
* @param fallbackL2Generators L2 generators for fallback liquidity e.g. vAMM {@link getVammL2Generator}, openbook {@link SerumSubscriber}
|
|
71
|
+
*/
|
|
72
|
+
public getL2({
|
|
73
|
+
marketName,
|
|
74
|
+
marketIndex,
|
|
75
|
+
marketType,
|
|
76
|
+
depth = 10,
|
|
77
|
+
includeVamm = false,
|
|
78
|
+
fallbackL2Generators = [],
|
|
79
|
+
}: {
|
|
80
|
+
marketName?: string;
|
|
81
|
+
marketIndex?: number;
|
|
82
|
+
marketType?: MarketType;
|
|
83
|
+
depth?: number;
|
|
84
|
+
includeVamm?: boolean;
|
|
85
|
+
fallbackL2Generators?: L2OrderBookGenerator[];
|
|
86
|
+
}): L2OrderBook {
|
|
87
|
+
if (marketName) {
|
|
88
|
+
const derivedMarketInfo =
|
|
89
|
+
this.driftClient.getMarketIndexAndType(marketName);
|
|
90
|
+
if (!derivedMarketInfo) {
|
|
91
|
+
throw new Error(`Market ${marketName} not found`);
|
|
92
|
+
}
|
|
93
|
+
marketIndex = derivedMarketInfo.marketIndex;
|
|
94
|
+
marketType = derivedMarketInfo.marketType;
|
|
95
|
+
} else {
|
|
96
|
+
if (marketIndex === undefined || marketType === undefined) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
'Either marketName or marketIndex and marketType must be provided'
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let oraclePriceData;
|
|
104
|
+
let fallbackBid;
|
|
105
|
+
let fallbackAsk;
|
|
106
|
+
const isPerp = isVariant(marketType, 'perp');
|
|
107
|
+
if (isPerp) {
|
|
108
|
+
const perpMarketAccount =
|
|
109
|
+
this.driftClient.getPerpMarketAccount(marketIndex);
|
|
110
|
+
oraclePriceData = this.driftClient.getOraclePriceDataAndSlot(
|
|
111
|
+
perpMarketAccount.amm.oracle
|
|
112
|
+
);
|
|
113
|
+
fallbackBid = calculateBidPrice(perpMarketAccount, oraclePriceData);
|
|
114
|
+
fallbackAsk = calculateAskPrice(perpMarketAccount, oraclePriceData);
|
|
115
|
+
} else {
|
|
116
|
+
oraclePriceData =
|
|
117
|
+
this.driftClient.getOracleDataForSpotMarket(marketIndex);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (isPerp && includeVamm) {
|
|
121
|
+
fallbackL2Generators = [
|
|
122
|
+
getVammL2Generator({
|
|
123
|
+
marketAccount: this.driftClient.getPerpMarketAccount(marketIndex),
|
|
124
|
+
oraclePriceData,
|
|
125
|
+
numOrders: depth,
|
|
126
|
+
}),
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return this.dlob.getL2({
|
|
131
|
+
marketIndex,
|
|
132
|
+
marketType,
|
|
133
|
+
depth,
|
|
134
|
+
oraclePriceData,
|
|
135
|
+
slot: this.slotSource.getSlot(),
|
|
136
|
+
fallbackBid,
|
|
137
|
+
fallbackAsk,
|
|
138
|
+
fallbackL2Generators: fallbackL2Generators,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get the L3 order book for a given market.
|
|
144
|
+
*
|
|
145
|
+
* @param marketName e.g. "SOL-PERP" or "SOL". If not provided, marketIndex and marketType must be provided.
|
|
146
|
+
* @param marketIndex
|
|
147
|
+
* @param marketType
|
|
148
|
+
*/
|
|
149
|
+
public getL3({
|
|
150
|
+
marketName,
|
|
151
|
+
marketIndex,
|
|
152
|
+
marketType,
|
|
153
|
+
}: {
|
|
154
|
+
marketName?: string;
|
|
155
|
+
marketIndex?: number;
|
|
156
|
+
marketType?: MarketType;
|
|
157
|
+
}): L3OrderBook {
|
|
158
|
+
if (marketName) {
|
|
159
|
+
const derivedMarketInfo =
|
|
160
|
+
this.driftClient.getMarketIndexAndType(marketName);
|
|
161
|
+
if (!derivedMarketInfo) {
|
|
162
|
+
throw new Error(`Market ${marketName} not found`);
|
|
163
|
+
}
|
|
164
|
+
marketIndex = derivedMarketInfo.marketIndex;
|
|
165
|
+
marketType = derivedMarketInfo.marketType;
|
|
166
|
+
} else {
|
|
167
|
+
if (marketIndex === undefined || marketType === undefined) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
'Either marketName or marketIndex and marketType must be provided'
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let oraclePriceData;
|
|
175
|
+
const isPerp = isVariant(marketType, 'perp');
|
|
176
|
+
if (isPerp) {
|
|
177
|
+
oraclePriceData =
|
|
178
|
+
this.driftClient.getOracleDataForPerpMarket(marketIndex);
|
|
179
|
+
} else {
|
|
180
|
+
oraclePriceData =
|
|
181
|
+
this.driftClient.getOracleDataForSpotMarket(marketIndex);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return this.dlob.getL3({
|
|
185
|
+
marketIndex,
|
|
186
|
+
marketType,
|
|
187
|
+
oraclePriceData,
|
|
188
|
+
slot: this.slotSource.getSlot(),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
51
192
|
public async unsubscribe(): Promise<void> {
|
|
52
193
|
if (this.intervalId) {
|
|
53
194
|
clearInterval(this.intervalId);
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BASE_PRECISION,
|
|
3
|
+
BN,
|
|
4
|
+
calculateAmmReservesAfterSwap,
|
|
5
|
+
calculateMarketOpenBidAsk,
|
|
6
|
+
calculateQuoteAssetAmountSwapped,
|
|
7
|
+
calculateSpreadReserves,
|
|
8
|
+
calculateUpdatedAMM,
|
|
9
|
+
DLOBNode,
|
|
10
|
+
OraclePriceData,
|
|
11
|
+
PerpMarketAccount,
|
|
12
|
+
SwapDirection,
|
|
13
|
+
} from '..';
|
|
14
|
+
import { PublicKey } from '@solana/web3.js';
|
|
15
|
+
|
|
16
|
+
type liquiditySource = 'serum' | 'vamm' | 'dlob';
|
|
17
|
+
|
|
18
|
+
export type L2Level = {
|
|
19
|
+
price: BN;
|
|
20
|
+
size: BN;
|
|
21
|
+
sources: { [key in liquiditySource]?: BN };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type L2OrderBook = {
|
|
25
|
+
asks: L2Level[];
|
|
26
|
+
bids: L2Level[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export interface L2OrderBookGenerator {
|
|
30
|
+
getL2Asks(): Generator<L2Level>;
|
|
31
|
+
getL2Bids(): Generator<L2Level>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type L3Level = {
|
|
35
|
+
price: BN;
|
|
36
|
+
size: BN;
|
|
37
|
+
maker: PublicKey;
|
|
38
|
+
orderId: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type L3OrderBook = {
|
|
42
|
+
asks: L3Level[];
|
|
43
|
+
bids: L3Level[];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get an {@link Generator<L2Level>} generator from a {@link Generator<DLOBNode>}
|
|
48
|
+
* @param dlobNodes e.g. {@link DLOB#getMakerLimitAsks} or {@link DLOB#getMakerLimitBids}
|
|
49
|
+
* @param oraclePriceData
|
|
50
|
+
* @param slot
|
|
51
|
+
*/
|
|
52
|
+
export function* getL2GeneratorFromDLOBNodes(
|
|
53
|
+
dlobNodes: Generator<DLOBNode>,
|
|
54
|
+
oraclePriceData: OraclePriceData,
|
|
55
|
+
slot: number
|
|
56
|
+
): Generator<L2Level> {
|
|
57
|
+
for (const dlobNode of dlobNodes) {
|
|
58
|
+
const size = dlobNode.order.baseAssetAmount.sub(
|
|
59
|
+
dlobNode.order.baseAssetAmountFilled
|
|
60
|
+
) as BN;
|
|
61
|
+
yield {
|
|
62
|
+
size,
|
|
63
|
+
price: dlobNode.getPrice(oraclePriceData, slot),
|
|
64
|
+
sources: {
|
|
65
|
+
dlob: size,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function* mergeL2LevelGenerators(
|
|
72
|
+
l2LevelGenerators: Generator<L2Level>[],
|
|
73
|
+
compare: (a: L2Level, b: L2Level) => boolean
|
|
74
|
+
): Generator<L2Level> {
|
|
75
|
+
const generators = l2LevelGenerators.map((generator) => {
|
|
76
|
+
return {
|
|
77
|
+
generator,
|
|
78
|
+
next: generator.next(),
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let next;
|
|
83
|
+
do {
|
|
84
|
+
next = generators.reduce((best, next) => {
|
|
85
|
+
if (next.next.done) {
|
|
86
|
+
return best;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (!best) {
|
|
90
|
+
return next;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (compare(next.next.value, best.next.value)) {
|
|
94
|
+
return next;
|
|
95
|
+
} else {
|
|
96
|
+
return best;
|
|
97
|
+
}
|
|
98
|
+
}, undefined);
|
|
99
|
+
|
|
100
|
+
if (next) {
|
|
101
|
+
yield next.next.value;
|
|
102
|
+
next.next = next.generator.next();
|
|
103
|
+
}
|
|
104
|
+
} while (next !== undefined);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createL2Levels(
|
|
108
|
+
generator: Generator<L2Level>,
|
|
109
|
+
depth: number
|
|
110
|
+
): L2Level[] {
|
|
111
|
+
const levels = [];
|
|
112
|
+
for (const level of generator) {
|
|
113
|
+
const price = level.price;
|
|
114
|
+
const size = level.size;
|
|
115
|
+
if (levels.length > 0 && levels[levels.length - 1].price.eq(price)) {
|
|
116
|
+
const currentLevel = levels[levels.length - 1];
|
|
117
|
+
currentLevel.size.add(size);
|
|
118
|
+
for (const [source, size] of Object.entries(level.sources)) {
|
|
119
|
+
if (currentLevel.sources[source]) {
|
|
120
|
+
currentLevel.sources[source] = currentLevel.sources[source].add(size);
|
|
121
|
+
} else {
|
|
122
|
+
currentLevel.sources[source] = size;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (levels.length === depth) {
|
|
126
|
+
break;
|
|
127
|
+
} else {
|
|
128
|
+
levels.push(level);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return levels;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function getVammL2Generator({
|
|
135
|
+
marketAccount,
|
|
136
|
+
oraclePriceData,
|
|
137
|
+
numOrders,
|
|
138
|
+
now,
|
|
139
|
+
}: {
|
|
140
|
+
marketAccount: PerpMarketAccount;
|
|
141
|
+
oraclePriceData: OraclePriceData;
|
|
142
|
+
numOrders: number;
|
|
143
|
+
now?: BN;
|
|
144
|
+
}): L2OrderBookGenerator {
|
|
145
|
+
const updatedAmm = calculateUpdatedAMM(marketAccount.amm, oraclePriceData);
|
|
146
|
+
|
|
147
|
+
const [openBids, openAsks] = calculateMarketOpenBidAsk(
|
|
148
|
+
updatedAmm.baseAssetReserve,
|
|
149
|
+
updatedAmm.minBaseAssetReserve,
|
|
150
|
+
updatedAmm.maxBaseAssetReserve,
|
|
151
|
+
updatedAmm.orderStepSize
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
now = now ?? new BN(Date.now() / 1000);
|
|
155
|
+
const [bidReserves, askReserves] = calculateSpreadReserves(
|
|
156
|
+
updatedAmm,
|
|
157
|
+
oraclePriceData,
|
|
158
|
+
now
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
let numBids = 0;
|
|
162
|
+
const baseSize = openBids.div(new BN(numOrders));
|
|
163
|
+
const bidAmm = {
|
|
164
|
+
baseAssetReserve: bidReserves.baseAssetReserve,
|
|
165
|
+
quoteAssetReserve: bidReserves.quoteAssetReserve,
|
|
166
|
+
sqrtK: updatedAmm.sqrtK,
|
|
167
|
+
pegMultiplier: updatedAmm.pegMultiplier,
|
|
168
|
+
};
|
|
169
|
+
const getL2Bids = function* () {
|
|
170
|
+
while (numBids < numOrders) {
|
|
171
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] =
|
|
172
|
+
calculateAmmReservesAfterSwap(
|
|
173
|
+
bidAmm,
|
|
174
|
+
'base',
|
|
175
|
+
baseSize,
|
|
176
|
+
SwapDirection.ADD
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const quoteSwapped = calculateQuoteAssetAmountSwapped(
|
|
180
|
+
bidAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
|
|
181
|
+
bidAmm.pegMultiplier,
|
|
182
|
+
SwapDirection.ADD
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
const price = quoteSwapped.mul(BASE_PRECISION).div(baseSize);
|
|
186
|
+
|
|
187
|
+
bidAmm.baseAssetReserve = afterSwapBaseReserves;
|
|
188
|
+
bidAmm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
189
|
+
|
|
190
|
+
yield {
|
|
191
|
+
price,
|
|
192
|
+
size: baseSize,
|
|
193
|
+
sources: { vamm: baseSize },
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
numBids++;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
let numAsks = 0;
|
|
201
|
+
const askSize = openAsks.abs().div(new BN(numOrders));
|
|
202
|
+
const askAmm = {
|
|
203
|
+
baseAssetReserve: askReserves.baseAssetReserve,
|
|
204
|
+
quoteAssetReserve: askReserves.quoteAssetReserve,
|
|
205
|
+
sqrtK: updatedAmm.sqrtK,
|
|
206
|
+
pegMultiplier: updatedAmm.pegMultiplier,
|
|
207
|
+
};
|
|
208
|
+
const getL2Asks = function* () {
|
|
209
|
+
while (numAsks < numOrders) {
|
|
210
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] =
|
|
211
|
+
calculateAmmReservesAfterSwap(
|
|
212
|
+
askAmm,
|
|
213
|
+
'base',
|
|
214
|
+
askSize,
|
|
215
|
+
SwapDirection.REMOVE
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const quoteSwapped = calculateQuoteAssetAmountSwapped(
|
|
219
|
+
askAmm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
|
|
220
|
+
askAmm.pegMultiplier,
|
|
221
|
+
SwapDirection.REMOVE
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const price = quoteSwapped.mul(BASE_PRECISION).div(askSize);
|
|
225
|
+
|
|
226
|
+
askAmm.baseAssetReserve = afterSwapBaseReserves;
|
|
227
|
+
askAmm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
228
|
+
|
|
229
|
+
yield {
|
|
230
|
+
price,
|
|
231
|
+
size: askSize,
|
|
232
|
+
sources: { vamm: baseSize },
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
numAsks++;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
getL2Bids,
|
|
241
|
+
getL2Asks,
|
|
242
|
+
};
|
|
243
|
+
}
|