@drift-labs/sdk 2.16.0-beta.2 → 2.16.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/dlob/DLOB.js +18 -2
- package/lib/driftClient.d.ts +43 -1
- package/lib/driftClient.js +120 -0
- package/lib/idl/drift.json +1 -1
- package/lib/math/spotBalance.js +4 -2
- package/lib/math/utils.d.ts +1 -1
- package/lib/math/utils.js +13 -21
- package/package.json +1 -1
- package/src/dlob/DLOB.ts +21 -2
- package/src/driftClient.ts +172 -0
- package/src/idl/drift.json +1 -1
- package/src/math/spotBalance.ts +12 -7
- package/src/math/utils.ts +13 -27
- package/tests/dlob/test.ts +16 -16
package/lib/dlob/DLOB.js
CHANGED
|
@@ -289,6 +289,14 @@ class DLOB {
|
|
|
289
289
|
const nodesToFill = new Array();
|
|
290
290
|
let marketOrderGenerator = this.getMarketAsks(marketIndex, marketType);
|
|
291
291
|
const marketAsksCrossingBids = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getMakerLimitBids.bind(this), (takerPrice, makerPrice) => {
|
|
292
|
+
if (__1.isVariant(marketType, 'spot')) {
|
|
293
|
+
if (takerPrice === undefined) {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
if (fallbackBid && makerPrice.lt(fallbackBid)) {
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
292
300
|
return takerPrice === undefined || takerPrice.lte(makerPrice);
|
|
293
301
|
}, fallbackAsk);
|
|
294
302
|
for (const marketAskCrossingBid of marketAsksCrossingBids) {
|
|
@@ -304,8 +312,16 @@ class DLOB {
|
|
|
304
312
|
}
|
|
305
313
|
}
|
|
306
314
|
marketOrderGenerator = this.getMarketBids(marketIndex, marketType);
|
|
307
|
-
const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getMakerLimitAsks.bind(this), (takerPrice,
|
|
308
|
-
|
|
315
|
+
const marketBidsToFill = this.findMarketNodesCrossingLimitNodes(marketIndex, slot, marketType, oraclePriceData, marketOrderGenerator, this.getMakerLimitAsks.bind(this), (takerPrice, makerPrice) => {
|
|
316
|
+
if (__1.isVariant(marketType, 'spot')) {
|
|
317
|
+
if (takerPrice === undefined) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
if (fallbackAsk && makerPrice.gt(fallbackAsk)) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return takerPrice === undefined || takerPrice.gte(makerPrice);
|
|
309
325
|
}, fallbackBid);
|
|
310
326
|
for (const marketBidToFill of marketBidsToFill) {
|
|
311
327
|
nodesToFill.push(marketBidToFill);
|
package/lib/driftClient.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
/// <reference types="bn.js" />
|
|
3
3
|
import { AnchorProvider, BN, Program } from '@project-serum/anchor';
|
|
4
|
-
import { StateAccount, IWallet, PositionDirection, UserAccount, PerpMarketAccount, OrderParams, Order, SpotMarketAccount, SpotPosition, MakerInfo, TakerInfo, OptionalOrderParams, ReferrerInfo, MarketType, TxParams, SerumV3FulfillmentConfigAccount } from './types';
|
|
4
|
+
import { StateAccount, IWallet, PositionDirection, UserAccount, PerpMarketAccount, OrderParams, Order, SpotMarketAccount, SpotPosition, MakerInfo, TakerInfo, OptionalOrderParams, OrderType, ReferrerInfo, MarketType, TxParams, SerumV3FulfillmentConfigAccount } from './types';
|
|
5
5
|
import * as anchor from '@project-serum/anchor';
|
|
6
6
|
import { Connection, PublicKey, TransactionSignature, ConfirmOptions, Transaction, TransactionInstruction, AccountMeta, Signer } from '@solana/web3.js';
|
|
7
7
|
import { TokenFaucet } from './tokenFaucet';
|
|
@@ -212,6 +212,7 @@ export declare class DriftClient {
|
|
|
212
212
|
closePosition(marketIndex: number, limitPrice?: BN): Promise<TransactionSignature>;
|
|
213
213
|
/**
|
|
214
214
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
215
|
+
* @deprecated use modifyOrder instead
|
|
215
216
|
* @param orderId: The open order to modify
|
|
216
217
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
217
218
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -219,8 +220,29 @@ export declare class DriftClient {
|
|
|
219
220
|
* @returns
|
|
220
221
|
*/
|
|
221
222
|
modifyPerpOrder(orderId: number, newBaseAmount?: BN, newLimitPrice?: BN, newOraclePriceOffset?: number): Promise<TransactionSignature>;
|
|
223
|
+
/**
|
|
224
|
+
* Modifies an open order (spot or perp) by closing it and replacing it with a new order.
|
|
225
|
+
* @param orderId: The open order to modify
|
|
226
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
227
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
228
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
229
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
230
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
231
|
+
* @param isSpot: Optional - Set to true if the order is a spot order
|
|
232
|
+
* @returns
|
|
233
|
+
*/
|
|
234
|
+
modifyOrder({ orderId, newBaseAmount, newLimitPrice, newOraclePriceOffset, newOrderType, newTriggerPrice, isSpot, }: {
|
|
235
|
+
orderId: number;
|
|
236
|
+
newBaseAmount?: BN;
|
|
237
|
+
newLimitPrice?: BN;
|
|
238
|
+
newOraclePriceOffset?: number;
|
|
239
|
+
newOrderType?: OrderType;
|
|
240
|
+
newTriggerPrice?: BN;
|
|
241
|
+
isSpot?: boolean;
|
|
242
|
+
}): Promise<TransactionSignature>;
|
|
222
243
|
/**
|
|
223
244
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
245
|
+
* @deprecated use modifyOrderByUserOrderId instead
|
|
224
246
|
* @param userOrderId: The open order to modify
|
|
225
247
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
226
248
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -228,6 +250,26 @@ export declare class DriftClient {
|
|
|
228
250
|
* @returns
|
|
229
251
|
*/
|
|
230
252
|
modifyPerpOrderByUserOrderId(userOrderId: number, newBaseAmount?: BN, newLimitPrice?: BN, newOraclePriceOffset?: number): Promise<TransactionSignature>;
|
|
253
|
+
/**
|
|
254
|
+
* Modifies an open order by closing it and replacing it with a new order.
|
|
255
|
+
* @param userOrderId: The open order to modify
|
|
256
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
257
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
258
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
259
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
260
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
261
|
+
* @param isSpot: Set to true if the order is a spot order
|
|
262
|
+
* @returns
|
|
263
|
+
*/
|
|
264
|
+
modifyOrderByUserOrderId({ userOrderId, newBaseAmount, newLimitPrice, newOraclePriceOffset, newOrderType, newTriggerPrice, isSpot, }: {
|
|
265
|
+
userOrderId: number;
|
|
266
|
+
newBaseAmount?: BN;
|
|
267
|
+
newLimitPrice?: BN;
|
|
268
|
+
newOraclePriceOffset?: number;
|
|
269
|
+
newOrderType?: OrderType;
|
|
270
|
+
newTriggerPrice?: BN;
|
|
271
|
+
isSpot?: boolean;
|
|
272
|
+
}): Promise<TransactionSignature>;
|
|
231
273
|
settlePNLs(users: {
|
|
232
274
|
settleeUserAccountPublicKey: PublicKey;
|
|
233
275
|
settleeUserAccount: UserAccount;
|
package/lib/driftClient.js
CHANGED
|
@@ -1750,6 +1750,7 @@ class DriftClient {
|
|
|
1750
1750
|
}
|
|
1751
1751
|
/**
|
|
1752
1752
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
1753
|
+
* @deprecated use modifyOrder instead
|
|
1753
1754
|
* @param orderId: The open order to modify
|
|
1754
1755
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1755
1756
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -1796,8 +1797,68 @@ class DriftClient {
|
|
|
1796
1797
|
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1797
1798
|
return txSig;
|
|
1798
1799
|
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Modifies an open order (spot or perp) by closing it and replacing it with a new order.
|
|
1802
|
+
* @param orderId: The open order to modify
|
|
1803
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1804
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1805
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1806
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
1807
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
1808
|
+
* @param isSpot: Optional - Set to true if the order is a spot order
|
|
1809
|
+
* @returns
|
|
1810
|
+
*/
|
|
1811
|
+
async modifyOrder({ orderId, newBaseAmount, newLimitPrice, newOraclePriceOffset, newOrderType, newTriggerPrice, isSpot, }) {
|
|
1812
|
+
if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
|
|
1813
|
+
throw new Error(`Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`);
|
|
1814
|
+
}
|
|
1815
|
+
const openOrder = this.getUser().getOrder(orderId);
|
|
1816
|
+
if (!openOrder) {
|
|
1817
|
+
throw new Error(`No open order with id ${orderId.toString()}`);
|
|
1818
|
+
}
|
|
1819
|
+
const cancelOrderIx = await this.getCancelOrderIx(orderId);
|
|
1820
|
+
const newOrderParams = {
|
|
1821
|
+
orderType: newOrderType || openOrder.orderType,
|
|
1822
|
+
marketType: openOrder.marketType,
|
|
1823
|
+
direction: openOrder.direction,
|
|
1824
|
+
baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
|
|
1825
|
+
price: newLimitPrice || openOrder.price,
|
|
1826
|
+
marketIndex: openOrder.marketIndex,
|
|
1827
|
+
reduceOnly: openOrder.reduceOnly,
|
|
1828
|
+
postOnly: openOrder.postOnly,
|
|
1829
|
+
immediateOrCancel: openOrder.immediateOrCancel,
|
|
1830
|
+
triggerPrice: newTriggerPrice || openOrder.triggerPrice,
|
|
1831
|
+
triggerCondition: openOrder.triggerCondition,
|
|
1832
|
+
oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
|
|
1833
|
+
auctionDuration: openOrder.auctionDuration,
|
|
1834
|
+
maxTs: openOrder.maxTs,
|
|
1835
|
+
auctionStartPrice: openOrder.auctionStartPrice,
|
|
1836
|
+
auctionEndPrice: openOrder.auctionEndPrice,
|
|
1837
|
+
userOrderId: openOrder.userOrderId,
|
|
1838
|
+
};
|
|
1839
|
+
const placeOrderIx = isSpot
|
|
1840
|
+
? await this.getPlaceSpotOrderIx(newOrderParams)
|
|
1841
|
+
: await this.getPlacePerpOrderIx(newOrderParams);
|
|
1842
|
+
const tx = new web3_js_1.Transaction();
|
|
1843
|
+
tx.add(web3_js_1.ComputeBudgetProgram.requestUnits({
|
|
1844
|
+
units: 1000000,
|
|
1845
|
+
additionalFee: 0,
|
|
1846
|
+
}));
|
|
1847
|
+
tx.add(cancelOrderIx);
|
|
1848
|
+
tx.add(placeOrderIx);
|
|
1849
|
+
const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
|
|
1850
|
+
if (isSpot) {
|
|
1851
|
+
this.spotMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1852
|
+
this.spotMarketLastSlotCache.set(numericConstants_1.QUOTE_SPOT_MARKET_INDEX, slot);
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1856
|
+
}
|
|
1857
|
+
return txSig;
|
|
1858
|
+
}
|
|
1799
1859
|
/**
|
|
1800
1860
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
1861
|
+
* @deprecated use modifyOrderByUserOrderId instead
|
|
1801
1862
|
* @param userOrderId: The open order to modify
|
|
1802
1863
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1803
1864
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -1844,6 +1905,65 @@ class DriftClient {
|
|
|
1844
1905
|
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1845
1906
|
return txSig;
|
|
1846
1907
|
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Modifies an open order by closing it and replacing it with a new order.
|
|
1910
|
+
* @param userOrderId: The open order to modify
|
|
1911
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1912
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1913
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
1914
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
1915
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
1916
|
+
* @param isSpot: Set to true if the order is a spot order
|
|
1917
|
+
* @returns
|
|
1918
|
+
*/
|
|
1919
|
+
async modifyOrderByUserOrderId({ userOrderId, newBaseAmount, newLimitPrice, newOraclePriceOffset, newOrderType, newTriggerPrice, isSpot, }) {
|
|
1920
|
+
if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
|
|
1921
|
+
throw new Error(`Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`);
|
|
1922
|
+
}
|
|
1923
|
+
const openOrder = this.getUser().getOrderByUserOrderId(userOrderId);
|
|
1924
|
+
if (!openOrder) {
|
|
1925
|
+
throw new Error(`No open order with user order id ${userOrderId.toString()}`);
|
|
1926
|
+
}
|
|
1927
|
+
const cancelOrderIx = await this.getCancelOrderByUserIdIx(userOrderId);
|
|
1928
|
+
const newOrderParams = {
|
|
1929
|
+
orderType: newOrderType || openOrder.orderType,
|
|
1930
|
+
marketType: openOrder.marketType,
|
|
1931
|
+
direction: openOrder.direction,
|
|
1932
|
+
baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
|
|
1933
|
+
price: newLimitPrice || openOrder.price,
|
|
1934
|
+
marketIndex: openOrder.marketIndex,
|
|
1935
|
+
reduceOnly: openOrder.reduceOnly,
|
|
1936
|
+
postOnly: openOrder.postOnly,
|
|
1937
|
+
immediateOrCancel: openOrder.immediateOrCancel,
|
|
1938
|
+
triggerPrice: newTriggerPrice || openOrder.triggerPrice,
|
|
1939
|
+
triggerCondition: openOrder.triggerCondition,
|
|
1940
|
+
oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
|
|
1941
|
+
auctionDuration: openOrder.auctionDuration,
|
|
1942
|
+
maxTs: openOrder.maxTs,
|
|
1943
|
+
auctionStartPrice: openOrder.auctionStartPrice,
|
|
1944
|
+
auctionEndPrice: openOrder.auctionEndPrice,
|
|
1945
|
+
userOrderId: openOrder.userOrderId,
|
|
1946
|
+
};
|
|
1947
|
+
const placeOrderIx = isSpot
|
|
1948
|
+
? await this.getPlaceSpotOrderIx(newOrderParams)
|
|
1949
|
+
: await this.getPlacePerpOrderIx(newOrderParams);
|
|
1950
|
+
const tx = new web3_js_1.Transaction();
|
|
1951
|
+
tx.add(web3_js_1.ComputeBudgetProgram.requestUnits({
|
|
1952
|
+
units: 1000000,
|
|
1953
|
+
additionalFee: 0,
|
|
1954
|
+
}));
|
|
1955
|
+
tx.add(cancelOrderIx);
|
|
1956
|
+
tx.add(placeOrderIx);
|
|
1957
|
+
const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
|
|
1958
|
+
if (isSpot) {
|
|
1959
|
+
this.spotMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1960
|
+
this.spotMarketLastSlotCache.set(numericConstants_1.QUOTE_SPOT_MARKET_INDEX, slot);
|
|
1961
|
+
}
|
|
1962
|
+
else {
|
|
1963
|
+
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
1964
|
+
}
|
|
1965
|
+
return txSig;
|
|
1966
|
+
}
|
|
1847
1967
|
async settlePNLs(users, marketIndex) {
|
|
1848
1968
|
const ixs = [];
|
|
1849
1969
|
for (const { settleeUserAccountPublicKey, settleeUserAccount } of users) {
|
package/lib/idl/drift.json
CHANGED
package/lib/math/spotBalance.js
CHANGED
|
@@ -201,9 +201,11 @@ function calculateWithdrawLimit(spotMarket, now) {
|
|
|
201
201
|
.add(marketDepositTokenAmount.mul(sinceLast))
|
|
202
202
|
.div(sinceLast.add(sinceStart));
|
|
203
203
|
const maxBorrowTokens = anchor_1.BN.max(spotMarket.withdrawGuardThreshold, anchor_1.BN.min(anchor_1.BN.max(marketDepositTokenAmount.div(new anchor_1.BN(6)), borrowTokenTwapLive.add(borrowTokenTwapLive.div(new anchor_1.BN(5)))), marketDepositTokenAmount.sub(marketDepositTokenAmount.div(new anchor_1.BN(5))))); // between ~15-80% utilization with friction on twap
|
|
204
|
-
const minDepositTokens = depositTokenTwapLive.sub(anchor_1.BN.
|
|
204
|
+
const minDepositTokens = depositTokenTwapLive.sub(anchor_1.BN.max(depositTokenTwapLive.div(new anchor_1.BN(5)), anchor_1.BN.min(spotMarket.withdrawGuardThreshold, depositTokenTwapLive)));
|
|
205
205
|
let withdrawLimit = anchor_1.BN.max(marketDepositTokenAmount.sub(minDepositTokens), numericConstants_1.ZERO);
|
|
206
|
-
let borrowLimit =
|
|
206
|
+
let borrowLimit = maxBorrowTokens.sub(marketBorrowTokenAmount);
|
|
207
|
+
borrowLimit = anchor_1.BN.min(borrowLimit, marketDepositTokenAmount.sub(minDepositTokens));
|
|
208
|
+
borrowLimit = anchor_1.BN.min(borrowLimit, marketDepositTokenAmount.sub(marketBorrowTokenAmount));
|
|
207
209
|
if (borrowLimit.eq(numericConstants_1.ZERO)) {
|
|
208
210
|
withdrawLimit = numericConstants_1.ZERO;
|
|
209
211
|
}
|
package/lib/math/utils.d.ts
CHANGED
package/lib/math/utils.js
CHANGED
|
@@ -6,28 +6,20 @@ function clampBN(x, min, max) {
|
|
|
6
6
|
return __1.BN.max(min, __1.BN.min(x, max));
|
|
7
7
|
}
|
|
8
8
|
exports.clampBN = clampBN;
|
|
9
|
-
const squareRootBN = (n
|
|
10
|
-
if (n.lt(__1.
|
|
11
|
-
throw new Error('
|
|
9
|
+
const squareRootBN = (n) => {
|
|
10
|
+
if (n.lt(new __1.BN(0))) {
|
|
11
|
+
throw new Error('Sqrt only works on non-negtiave inputs');
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
// Calculate more closed x
|
|
24
|
-
root = x.add(n.div(x)).div(TWO);
|
|
25
|
-
// Check for closeness
|
|
26
|
-
if (x.sub(root).abs().lte(closeness))
|
|
27
|
-
break;
|
|
28
|
-
// Update root
|
|
29
|
-
x = root;
|
|
13
|
+
if (n.lt(new __1.BN(2))) {
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
const smallCand = exports.squareRootBN(n.shrn(2)).shln(1);
|
|
17
|
+
const largeCand = smallCand.add(new __1.BN(1));
|
|
18
|
+
if (largeCand.mul(largeCand).gt(n)) {
|
|
19
|
+
return smallCand;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return largeCand;
|
|
30
23
|
}
|
|
31
|
-
return root;
|
|
32
24
|
};
|
|
33
25
|
exports.squareRootBN = squareRootBN;
|
package/package.json
CHANGED
package/src/dlob/DLOB.ts
CHANGED
|
@@ -521,6 +521,15 @@ export class DLOB {
|
|
|
521
521
|
marketOrderGenerator,
|
|
522
522
|
this.getMakerLimitBids.bind(this),
|
|
523
523
|
(takerPrice, makerPrice) => {
|
|
524
|
+
if (isVariant(marketType, 'spot')) {
|
|
525
|
+
if (takerPrice === undefined) {
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (fallbackBid && makerPrice.lt(fallbackBid)) {
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
524
533
|
return takerPrice === undefined || takerPrice.lte(makerPrice);
|
|
525
534
|
},
|
|
526
535
|
fallbackAsk
|
|
@@ -557,8 +566,18 @@ export class DLOB {
|
|
|
557
566
|
oraclePriceData,
|
|
558
567
|
marketOrderGenerator,
|
|
559
568
|
this.getMakerLimitAsks.bind(this),
|
|
560
|
-
(takerPrice,
|
|
561
|
-
|
|
569
|
+
(takerPrice, makerPrice) => {
|
|
570
|
+
if (isVariant(marketType, 'spot')) {
|
|
571
|
+
if (takerPrice === undefined) {
|
|
572
|
+
return false;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (fallbackAsk && makerPrice.gt(fallbackAsk)) {
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
return takerPrice === undefined || takerPrice.gte(makerPrice);
|
|
562
581
|
},
|
|
563
582
|
fallbackBid
|
|
564
583
|
);
|
package/src/driftClient.ts
CHANGED
|
@@ -3018,6 +3018,7 @@ export class DriftClient {
|
|
|
3018
3018
|
|
|
3019
3019
|
/**
|
|
3020
3020
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
3021
|
+
* @deprecated use modifyOrder instead
|
|
3021
3022
|
* @param orderId: The open order to modify
|
|
3022
3023
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3023
3024
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -3077,8 +3078,93 @@ export class DriftClient {
|
|
|
3077
3078
|
return txSig;
|
|
3078
3079
|
}
|
|
3079
3080
|
|
|
3081
|
+
/**
|
|
3082
|
+
* Modifies an open order (spot or perp) by closing it and replacing it with a new order.
|
|
3083
|
+
* @param orderId: The open order to modify
|
|
3084
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3085
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3086
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3087
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
3088
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
3089
|
+
* @param isSpot: Optional - Set to true if the order is a spot order
|
|
3090
|
+
* @returns
|
|
3091
|
+
*/
|
|
3092
|
+
public async modifyOrder({
|
|
3093
|
+
orderId,
|
|
3094
|
+
newBaseAmount,
|
|
3095
|
+
newLimitPrice,
|
|
3096
|
+
newOraclePriceOffset,
|
|
3097
|
+
newOrderType,
|
|
3098
|
+
newTriggerPrice,
|
|
3099
|
+
isSpot,
|
|
3100
|
+
}: {
|
|
3101
|
+
orderId: number;
|
|
3102
|
+
newBaseAmount?: BN;
|
|
3103
|
+
newLimitPrice?: BN;
|
|
3104
|
+
newOraclePriceOffset?: number;
|
|
3105
|
+
newOrderType?: OrderType;
|
|
3106
|
+
newTriggerPrice?: BN;
|
|
3107
|
+
isSpot?: boolean;
|
|
3108
|
+
}): Promise<TransactionSignature> {
|
|
3109
|
+
if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
|
|
3110
|
+
throw new Error(
|
|
3111
|
+
`Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`
|
|
3112
|
+
);
|
|
3113
|
+
}
|
|
3114
|
+
|
|
3115
|
+
const openOrder = this.getUser().getOrder(orderId);
|
|
3116
|
+
if (!openOrder) {
|
|
3117
|
+
throw new Error(`No open order with id ${orderId.toString()}`);
|
|
3118
|
+
}
|
|
3119
|
+
const cancelOrderIx = await this.getCancelOrderIx(orderId);
|
|
3120
|
+
|
|
3121
|
+
const newOrderParams: OptionalOrderParams = {
|
|
3122
|
+
orderType: newOrderType || openOrder.orderType,
|
|
3123
|
+
marketType: openOrder.marketType,
|
|
3124
|
+
direction: openOrder.direction,
|
|
3125
|
+
baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
|
|
3126
|
+
price: newLimitPrice || openOrder.price,
|
|
3127
|
+
marketIndex: openOrder.marketIndex,
|
|
3128
|
+
reduceOnly: openOrder.reduceOnly,
|
|
3129
|
+
postOnly: openOrder.postOnly,
|
|
3130
|
+
immediateOrCancel: openOrder.immediateOrCancel,
|
|
3131
|
+
triggerPrice: newTriggerPrice || openOrder.triggerPrice,
|
|
3132
|
+
triggerCondition: openOrder.triggerCondition,
|
|
3133
|
+
oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
|
|
3134
|
+
auctionDuration: openOrder.auctionDuration,
|
|
3135
|
+
maxTs: openOrder.maxTs,
|
|
3136
|
+
auctionStartPrice: openOrder.auctionStartPrice,
|
|
3137
|
+
auctionEndPrice: openOrder.auctionEndPrice,
|
|
3138
|
+
userOrderId: openOrder.userOrderId,
|
|
3139
|
+
};
|
|
3140
|
+
const placeOrderIx = isSpot
|
|
3141
|
+
? await this.getPlaceSpotOrderIx(newOrderParams)
|
|
3142
|
+
: await this.getPlacePerpOrderIx(newOrderParams);
|
|
3143
|
+
|
|
3144
|
+
const tx = new Transaction();
|
|
3145
|
+
tx.add(
|
|
3146
|
+
ComputeBudgetProgram.requestUnits({
|
|
3147
|
+
units: 1_000_000,
|
|
3148
|
+
additionalFee: 0,
|
|
3149
|
+
})
|
|
3150
|
+
);
|
|
3151
|
+
tx.add(cancelOrderIx);
|
|
3152
|
+
tx.add(placeOrderIx);
|
|
3153
|
+
const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
|
|
3154
|
+
|
|
3155
|
+
if (isSpot) {
|
|
3156
|
+
this.spotMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
3157
|
+
this.spotMarketLastSlotCache.set(QUOTE_SPOT_MARKET_INDEX, slot);
|
|
3158
|
+
} else {
|
|
3159
|
+
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
3160
|
+
}
|
|
3161
|
+
|
|
3162
|
+
return txSig;
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3080
3165
|
/**
|
|
3081
3166
|
* Modifies an open order by closing it and replacing it with a new order.
|
|
3167
|
+
* @deprecated use modifyOrderByUserOrderId instead
|
|
3082
3168
|
* @param userOrderId: The open order to modify
|
|
3083
3169
|
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3084
3170
|
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
@@ -3140,6 +3226,92 @@ export class DriftClient {
|
|
|
3140
3226
|
return txSig;
|
|
3141
3227
|
}
|
|
3142
3228
|
|
|
3229
|
+
/**
|
|
3230
|
+
* Modifies an open order by closing it and replacing it with a new order.
|
|
3231
|
+
* @param userOrderId: The open order to modify
|
|
3232
|
+
* @param newBaseAmount: The new base amount for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3233
|
+
* @param newLimitPice: The new limit price for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3234
|
+
* @param newOraclePriceOffset: The new oracle price offset for the order. One of [newBaseAmount|newLimitPrice|newOraclePriceOffset] must be provided.
|
|
3235
|
+
* @param newOrderType: Optional - New order type for the order.
|
|
3236
|
+
* @param newTriggerPrice: Optional - Thew new trigger price for the order.
|
|
3237
|
+
* @param isSpot: Set to true if the order is a spot order
|
|
3238
|
+
* @returns
|
|
3239
|
+
*/
|
|
3240
|
+
public async modifyOrderByUserOrderId({
|
|
3241
|
+
userOrderId,
|
|
3242
|
+
newBaseAmount,
|
|
3243
|
+
newLimitPrice,
|
|
3244
|
+
newOraclePriceOffset,
|
|
3245
|
+
newOrderType,
|
|
3246
|
+
newTriggerPrice,
|
|
3247
|
+
isSpot,
|
|
3248
|
+
}: {
|
|
3249
|
+
userOrderId: number;
|
|
3250
|
+
newBaseAmount?: BN;
|
|
3251
|
+
newLimitPrice?: BN;
|
|
3252
|
+
newOraclePriceOffset?: number;
|
|
3253
|
+
newOrderType?: OrderType;
|
|
3254
|
+
newTriggerPrice?: BN;
|
|
3255
|
+
isSpot?: boolean;
|
|
3256
|
+
}): Promise<TransactionSignature> {
|
|
3257
|
+
if (!newBaseAmount && !newLimitPrice && !newOraclePriceOffset) {
|
|
3258
|
+
throw new Error(
|
|
3259
|
+
`Must provide newBaseAmount or newLimitPrice or newOraclePriceOffset to modify order`
|
|
3260
|
+
);
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
const openOrder = this.getUser().getOrderByUserOrderId(userOrderId);
|
|
3264
|
+
if (!openOrder) {
|
|
3265
|
+
throw new Error(
|
|
3266
|
+
`No open order with user order id ${userOrderId.toString()}`
|
|
3267
|
+
);
|
|
3268
|
+
}
|
|
3269
|
+
const cancelOrderIx = await this.getCancelOrderByUserIdIx(userOrderId);
|
|
3270
|
+
|
|
3271
|
+
const newOrderParams: OptionalOrderParams = {
|
|
3272
|
+
orderType: newOrderType || openOrder.orderType,
|
|
3273
|
+
marketType: openOrder.marketType,
|
|
3274
|
+
direction: openOrder.direction,
|
|
3275
|
+
baseAssetAmount: newBaseAmount || openOrder.baseAssetAmount,
|
|
3276
|
+
price: newLimitPrice || openOrder.price,
|
|
3277
|
+
marketIndex: openOrder.marketIndex,
|
|
3278
|
+
reduceOnly: openOrder.reduceOnly,
|
|
3279
|
+
postOnly: openOrder.postOnly,
|
|
3280
|
+
immediateOrCancel: openOrder.immediateOrCancel,
|
|
3281
|
+
triggerPrice: newTriggerPrice || openOrder.triggerPrice,
|
|
3282
|
+
triggerCondition: openOrder.triggerCondition,
|
|
3283
|
+
oraclePriceOffset: newOraclePriceOffset || openOrder.oraclePriceOffset,
|
|
3284
|
+
auctionDuration: openOrder.auctionDuration,
|
|
3285
|
+
maxTs: openOrder.maxTs,
|
|
3286
|
+
auctionStartPrice: openOrder.auctionStartPrice,
|
|
3287
|
+
auctionEndPrice: openOrder.auctionEndPrice,
|
|
3288
|
+
userOrderId: openOrder.userOrderId,
|
|
3289
|
+
};
|
|
3290
|
+
const placeOrderIx = isSpot
|
|
3291
|
+
? await this.getPlaceSpotOrderIx(newOrderParams)
|
|
3292
|
+
: await this.getPlacePerpOrderIx(newOrderParams);
|
|
3293
|
+
|
|
3294
|
+
const tx = new Transaction();
|
|
3295
|
+
tx.add(
|
|
3296
|
+
ComputeBudgetProgram.requestUnits({
|
|
3297
|
+
units: 1_000_000,
|
|
3298
|
+
additionalFee: 0,
|
|
3299
|
+
})
|
|
3300
|
+
);
|
|
3301
|
+
tx.add(cancelOrderIx);
|
|
3302
|
+
tx.add(placeOrderIx);
|
|
3303
|
+
const { txSig, slot } = await this.sendTransaction(tx, [], this.opts);
|
|
3304
|
+
|
|
3305
|
+
if (isSpot) {
|
|
3306
|
+
this.spotMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
3307
|
+
this.spotMarketLastSlotCache.set(QUOTE_SPOT_MARKET_INDEX, slot);
|
|
3308
|
+
} else {
|
|
3309
|
+
this.perpMarketLastSlotCache.set(newOrderParams.marketIndex, slot);
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
return txSig;
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3143
3315
|
public async settlePNLs(
|
|
3144
3316
|
users: {
|
|
3145
3317
|
settleeUserAccountPublicKey: PublicKey;
|
package/src/idl/drift.json
CHANGED
package/src/math/spotBalance.ts
CHANGED
|
@@ -338,12 +338,9 @@ export function calculateWithdrawLimit(
|
|
|
338
338
|
); // between ~15-80% utilization with friction on twap
|
|
339
339
|
|
|
340
340
|
const minDepositTokens = depositTokenTwapLive.sub(
|
|
341
|
-
BN.
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
spotMarket.withdrawGuardThreshold
|
|
345
|
-
),
|
|
346
|
-
depositTokenTwapLive
|
|
341
|
+
BN.max(
|
|
342
|
+
depositTokenTwapLive.div(new BN(5)),
|
|
343
|
+
BN.min(spotMarket.withdrawGuardThreshold, depositTokenTwapLive)
|
|
347
344
|
)
|
|
348
345
|
);
|
|
349
346
|
|
|
@@ -352,7 +349,15 @@ export function calculateWithdrawLimit(
|
|
|
352
349
|
ZERO
|
|
353
350
|
);
|
|
354
351
|
|
|
355
|
-
let borrowLimit =
|
|
352
|
+
let borrowLimit = maxBorrowTokens.sub(marketBorrowTokenAmount);
|
|
353
|
+
borrowLimit = BN.min(
|
|
354
|
+
borrowLimit,
|
|
355
|
+
marketDepositTokenAmount.sub(minDepositTokens)
|
|
356
|
+
);
|
|
357
|
+
borrowLimit = BN.min(
|
|
358
|
+
borrowLimit,
|
|
359
|
+
marketDepositTokenAmount.sub(marketBorrowTokenAmount)
|
|
360
|
+
);
|
|
356
361
|
|
|
357
362
|
if (borrowLimit.eq(ZERO)) {
|
|
358
363
|
withdrawLimit = ZERO;
|
package/src/math/utils.ts
CHANGED
|
@@ -1,37 +1,23 @@
|
|
|
1
|
-
import { BN
|
|
1
|
+
import { BN } from '../';
|
|
2
2
|
|
|
3
3
|
export function clampBN(x: BN, min: BN, max: BN): BN {
|
|
4
4
|
return BN.max(min, BN.min(x, max));
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export const squareRootBN = (n
|
|
8
|
-
if (n.lt(
|
|
9
|
-
throw new Error('
|
|
7
|
+
export const squareRootBN = (n: BN): BN => {
|
|
8
|
+
if (n.lt(new BN(0))) {
|
|
9
|
+
throw new Error('Sqrt only works on non-negtiave inputs');
|
|
10
|
+
}
|
|
11
|
+
if (n.lt(new BN(2))) {
|
|
12
|
+
return n;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
// The closed guess will be stored in the root
|
|
16
|
-
let root;
|
|
17
|
-
|
|
18
|
-
// To count the number of iterations
|
|
19
|
-
let count = 0;
|
|
20
|
-
const TWO = new BN(2);
|
|
21
|
-
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
23
|
-
while (count < Number.MAX_SAFE_INTEGER) {
|
|
24
|
-
count++;
|
|
25
|
-
|
|
26
|
-
// Calculate more closed x
|
|
27
|
-
root = x.add(n.div(x)).div(TWO);
|
|
28
|
-
|
|
29
|
-
// Check for closeness
|
|
30
|
-
if (x.sub(root).abs().lte(closeness)) break;
|
|
15
|
+
const smallCand = squareRootBN(n.shrn(2)).shln(1);
|
|
16
|
+
const largeCand = smallCand.add(new BN(1));
|
|
31
17
|
|
|
32
|
-
|
|
33
|
-
|
|
18
|
+
if (largeCand.mul(largeCand).gt(n)) {
|
|
19
|
+
return smallCand;
|
|
20
|
+
} else {
|
|
21
|
+
return largeCand;
|
|
34
22
|
}
|
|
35
|
-
|
|
36
|
-
return root;
|
|
37
23
|
};
|
package/tests/dlob/test.ts
CHANGED
|
@@ -4266,8 +4266,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
4266
4266
|
});
|
|
4267
4267
|
|
|
4268
4268
|
it('Test two market orders to fill one limit order', () => {
|
|
4269
|
-
const
|
|
4270
|
-
const
|
|
4269
|
+
const fallbackAsk = new BN(15);
|
|
4270
|
+
const fallbackBid = new BN(8);
|
|
4271
4271
|
|
|
4272
4272
|
const user0 = Keypair.generate();
|
|
4273
4273
|
const user1 = Keypair.generate();
|
|
@@ -4280,7 +4280,7 @@ describe('DLOB Spot Tests', () => {
|
|
|
4280
4280
|
|
|
4281
4281
|
const slot = 12;
|
|
4282
4282
|
const oracle = {
|
|
4283
|
-
price:
|
|
4283
|
+
price: fallbackBid.add(fallbackAsk).div(new BN(2)),
|
|
4284
4284
|
slot: new BN(slot),
|
|
4285
4285
|
confidence: new BN(1),
|
|
4286
4286
|
hasSufficientNumberOfDataPoints: true,
|
|
@@ -4297,8 +4297,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
4297
4297
|
new BN(14), // price
|
|
4298
4298
|
BASE_PRECISION, // quantity
|
|
4299
4299
|
PositionDirection.SHORT,
|
|
4300
|
-
|
|
4301
|
-
|
|
4300
|
+
fallbackBid,
|
|
4301
|
+
fallbackAsk,
|
|
4302
4302
|
undefined,
|
|
4303
4303
|
undefined,
|
|
4304
4304
|
undefined,
|
|
@@ -4314,8 +4314,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
4314
4314
|
new BN(13), // price
|
|
4315
4315
|
BASE_PRECISION, // quantity
|
|
4316
4316
|
PositionDirection.SHORT,
|
|
4317
|
-
|
|
4318
|
-
|
|
4317
|
+
fallbackBid,
|
|
4318
|
+
fallbackAsk,
|
|
4319
4319
|
undefined,
|
|
4320
4320
|
undefined,
|
|
4321
4321
|
undefined,
|
|
@@ -4331,8 +4331,8 @@ describe('DLOB Spot Tests', () => {
|
|
|
4331
4331
|
new BN(8), // price <-- best price
|
|
4332
4332
|
new BN(3).mul(BASE_PRECISION), // quantity
|
|
4333
4333
|
PositionDirection.SHORT,
|
|
4334
|
-
|
|
4335
|
-
|
|
4334
|
+
fallbackBid,
|
|
4335
|
+
fallbackAsk,
|
|
4336
4336
|
undefined,
|
|
4337
4337
|
undefined,
|
|
4338
4338
|
undefined,
|
|
@@ -4361,11 +4361,11 @@ describe('DLOB Spot Tests', () => {
|
|
|
4361
4361
|
MarketType.SPOT,
|
|
4362
4362
|
4, // orderId
|
|
4363
4363
|
marketIndex,
|
|
4364
|
-
|
|
4364
|
+
fallbackAsk, // price
|
|
4365
4365
|
new BN(1).mul(BASE_PRECISION), // quantity
|
|
4366
4366
|
PositionDirection.LONG,
|
|
4367
|
-
|
|
4368
|
-
|
|
4367
|
+
fallbackBid,
|
|
4368
|
+
fallbackAsk
|
|
4369
4369
|
);
|
|
4370
4370
|
insertOrderToDLOB(
|
|
4371
4371
|
dlob,
|
|
@@ -4374,11 +4374,11 @@ describe('DLOB Spot Tests', () => {
|
|
|
4374
4374
|
MarketType.SPOT,
|
|
4375
4375
|
5, // orderId
|
|
4376
4376
|
marketIndex,
|
|
4377
|
-
|
|
4377
|
+
fallbackAsk, // price
|
|
4378
4378
|
new BN(2).mul(BASE_PRECISION), // quantity
|
|
4379
4379
|
PositionDirection.LONG,
|
|
4380
|
-
|
|
4381
|
-
|
|
4380
|
+
fallbackBid,
|
|
4381
|
+
fallbackAsk
|
|
4382
4382
|
);
|
|
4383
4383
|
|
|
4384
4384
|
const nodesToFillAfter = dlob.findNodesToFill(
|
|
@@ -4399,7 +4399,7 @@ describe('DLOB Spot Tests', () => {
|
|
|
4399
4399
|
);
|
|
4400
4400
|
console.log(`market nodes: ${mktNodes.length}`);
|
|
4401
4401
|
|
|
4402
|
-
printBookState(dlob, marketIndex,
|
|
4402
|
+
printBookState(dlob, marketIndex, fallbackBid, fallbackAsk, slot, oracle);
|
|
4403
4403
|
|
|
4404
4404
|
for (const n of nodesToFillAfter) {
|
|
4405
4405
|
console.log(
|