@drift-labs/sdk 2.12.0-beta.0 → 2.12.0-beta.2
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/README.md +63 -48
- package/lib/examples/makeTradeExample.js +18 -19
- package/lib/idl/drift.json +12 -11
- package/lib/math/exchangeStatus.js +5 -3
- package/lib/math/trade.d.ts +15 -0
- package/lib/math/trade.js +135 -1
- package/lib/types.d.ts +11 -23
- package/lib/types.js +12 -10
- package/lib/user.js +1 -1
- package/package.json +1 -1
- package/src/examples/makeTradeExample.ts +30 -35
- package/src/idl/drift.json +12 -11
- package/src/math/exchangeStatus.ts +6 -4
- package/src/math/trade.ts +216 -1
- package/src/types.ts +11 -9
- package/src/user.ts +1 -1
- package/tests/dlob/helpers.ts +1 -2
package/README.md
CHANGED
|
@@ -11,14 +11,6 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
</div>
|
|
13
13
|
|
|
14
|
-
# Drift Protocol v2
|
|
15
|
-
|
|
16
|
-
This repository provides open source access to Drift's Typescript SDK, Solana Programs, and more.
|
|
17
|
-
|
|
18
|
-
# SDK Guide
|
|
19
|
-
|
|
20
|
-
The technical documentation for the SDK can be found [here](https://drift-labs.github.io/protocol-v2/sdk/), and you can visit Drift's general purpose documentation [here](https://docs.drift.trade/sdk-documentation).
|
|
21
|
-
|
|
22
14
|
## Installation
|
|
23
15
|
|
|
24
16
|
```
|
|
@@ -84,7 +76,7 @@ convertToNumber(new BN(10500), new BN(1000)); // = 10.5
|
|
|
84
76
|
### Setting up an account and making a trade
|
|
85
77
|
|
|
86
78
|
```typescript
|
|
87
|
-
import {
|
|
79
|
+
import { AnchorProvider, BN } from '@project-serum/anchor';
|
|
88
80
|
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
|
89
81
|
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
90
82
|
import {
|
|
@@ -92,14 +84,18 @@ import {
|
|
|
92
84
|
DriftClient,
|
|
93
85
|
User,
|
|
94
86
|
initialize,
|
|
95
|
-
Markets,
|
|
96
87
|
PositionDirection,
|
|
97
88
|
convertToNumber,
|
|
98
89
|
calculateTradeSlippage,
|
|
99
90
|
PRICE_PRECISION,
|
|
100
91
|
QUOTE_PRECISION,
|
|
101
92
|
Wallet,
|
|
102
|
-
|
|
93
|
+
PerpMarkets,
|
|
94
|
+
BASE_PRECISION,
|
|
95
|
+
getMarketOrderParams,
|
|
96
|
+
BulkAccountLoader,
|
|
97
|
+
getMarketsAndOraclesForSubscription
|
|
98
|
+
} from '../sdk';
|
|
103
99
|
|
|
104
100
|
export const getTokenAddress = (
|
|
105
101
|
mintAddress: string,
|
|
@@ -114,8 +110,9 @@ export const getTokenAddress = (
|
|
|
114
110
|
};
|
|
115
111
|
|
|
116
112
|
const main = async () => {
|
|
113
|
+
const env = 'devnet';
|
|
117
114
|
// Initialize Drift SDK
|
|
118
|
-
const sdkConfig = initialize({ env
|
|
115
|
+
const sdkConfig = initialize({ env });
|
|
119
116
|
|
|
120
117
|
// Set up the Wallet and Provider
|
|
121
118
|
const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
|
|
@@ -129,7 +126,11 @@ const main = async () => {
|
|
|
129
126
|
const connection = new Connection(rpcAddress);
|
|
130
127
|
|
|
131
128
|
// Set up the Provider
|
|
132
|
-
const provider = new
|
|
129
|
+
const provider = new AnchorProvider(
|
|
130
|
+
connection,
|
|
131
|
+
wallet,
|
|
132
|
+
AnchorProvider.defaultOptions()
|
|
133
|
+
);
|
|
133
134
|
|
|
134
135
|
// Check SOL Balance
|
|
135
136
|
const lamportsBalance = await connection.getBalance(wallet.publicKey);
|
|
@@ -142,18 +143,35 @@ const main = async () => {
|
|
|
142
143
|
);
|
|
143
144
|
|
|
144
145
|
// Set up the Drift Client
|
|
145
|
-
const
|
|
146
|
-
const
|
|
146
|
+
const driftPublicKey = new PublicKey(sdkConfig.DRIFT_PROGRAM_ID);
|
|
147
|
+
const bulkAccountLoader = new BulkAccountLoader(
|
|
147
148
|
connection,
|
|
148
|
-
|
|
149
|
-
|
|
149
|
+
'confirmed',
|
|
150
|
+
1000
|
|
150
151
|
);
|
|
152
|
+
const driftClient = new DriftClient({
|
|
153
|
+
connection,
|
|
154
|
+
wallet: provider.wallet,
|
|
155
|
+
programID: driftPublicKey,
|
|
156
|
+
...getMarketsAndOraclesForSubscription(env),
|
|
157
|
+
accountSubscription: {
|
|
158
|
+
type: 'polling',
|
|
159
|
+
accountLoader: bulkAccountLoader,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
151
162
|
await driftClient.subscribe();
|
|
152
163
|
|
|
153
|
-
// Set up
|
|
154
|
-
const user = User
|
|
155
|
-
|
|
156
|
-
|
|
164
|
+
// Set up user client
|
|
165
|
+
const user = new User({
|
|
166
|
+
driftClient: driftClient,
|
|
167
|
+
userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
|
|
168
|
+
accountSubscription: {
|
|
169
|
+
type: 'polling',
|
|
170
|
+
accountLoader: bulkAccountLoader,
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
//// Check if user account exists for the current wallet
|
|
157
175
|
const userAccountExists = await user.exists();
|
|
158
176
|
|
|
159
177
|
if (!userAccountExists) {
|
|
@@ -171,51 +189,48 @@ const main = async () => {
|
|
|
171
189
|
await user.subscribe();
|
|
172
190
|
|
|
173
191
|
// Get current price
|
|
174
|
-
const solMarketInfo =
|
|
192
|
+
const solMarketInfo = PerpMarkets[env].find(
|
|
175
193
|
(market) => market.baseAssetSymbol === 'SOL'
|
|
176
194
|
);
|
|
177
195
|
|
|
178
|
-
const
|
|
179
|
-
driftClient.
|
|
196
|
+
const [bid, ask] = calculateBidAskPrice(
|
|
197
|
+
driftClient.getPerpMarketAccount(marketIndex).amm,
|
|
198
|
+
driftClient.getOracleDataForPerpMarket(marketIndex)
|
|
180
199
|
);
|
|
181
200
|
|
|
182
|
-
const
|
|
201
|
+
const formattedBidPrice = convertToNumber(bid, PRICE_PRECISION);
|
|
202
|
+
const formattedAskPrice = convertToNumber(ask, PRICE_PRECISION);
|
|
183
203
|
|
|
184
|
-
console.log(
|
|
204
|
+
console.log(
|
|
205
|
+
`Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`
|
|
206
|
+
);
|
|
185
207
|
|
|
186
208
|
// Estimate the slippage for a $5000 LONG trade
|
|
187
|
-
const solMarketAccount = driftClient.
|
|
209
|
+
const solMarketAccount = driftClient.getPerpMarketAccount(
|
|
210
|
+
solMarketInfo.marketIndex
|
|
211
|
+
);
|
|
188
212
|
|
|
189
213
|
const slippage = convertToNumber(
|
|
190
214
|
calculateTradeSlippage(
|
|
191
215
|
PositionDirection.LONG,
|
|
192
|
-
new BN(
|
|
193
|
-
solMarketAccount
|
|
216
|
+
new BN(1).mul(BASE_PRECISION),
|
|
217
|
+
solMarketAccount,
|
|
218
|
+
'base',
|
|
219
|
+
driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex)
|
|
194
220
|
)[0],
|
|
195
221
|
PRICE_PRECISION
|
|
196
222
|
);
|
|
197
223
|
|
|
198
|
-
console.log(
|
|
199
|
-
`Slippage for a $5000 LONG on the SOL market would be $${slippage}`
|
|
200
|
-
);
|
|
224
|
+
console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
|
|
201
225
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
226
|
+
await driftClient.placePerpOrder(
|
|
227
|
+
getMarketOrderParams({
|
|
228
|
+
baseAssetAmount: new BN(1).mul(BASE_PRECISION),
|
|
229
|
+
direction: PositionDirection.LONG,
|
|
230
|
+
marketIndex: solMarketAccount.marketIndex,
|
|
231
|
+
})
|
|
207
232
|
);
|
|
208
|
-
console.log(`
|
|
209
|
-
|
|
210
|
-
// Reduce the position by $2000
|
|
211
|
-
await driftClient.openPosition(
|
|
212
|
-
PositionDirection.SHORT,
|
|
213
|
-
new BN(2000).mul(QUOTE_PRECISION),
|
|
214
|
-
solMarketInfo.marketIndex
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
// Close the rest of the position
|
|
218
|
-
await driftClient.closePosition(solMarketInfo.marketIndex);
|
|
233
|
+
console.log(`Placed a 1 SOL-PERP LONG order`);
|
|
219
234
|
};
|
|
220
235
|
|
|
221
236
|
main();
|
|
@@ -11,10 +11,10 @@ const getTokenAddress = (mintAddress, userPubKey) => {
|
|
|
11
11
|
return spl_token_1.Token.getAssociatedTokenAddress(new web3_js_1.PublicKey(`ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL`), spl_token_1.TOKEN_PROGRAM_ID, new web3_js_1.PublicKey(mintAddress), new web3_js_1.PublicKey(userPubKey));
|
|
12
12
|
};
|
|
13
13
|
exports.getTokenAddress = getTokenAddress;
|
|
14
|
-
const
|
|
14
|
+
const env = 'devnet';
|
|
15
15
|
const main = async () => {
|
|
16
16
|
// Initialize Drift SDK
|
|
17
|
-
const sdkConfig = (0, __2.initialize)({ env
|
|
17
|
+
const sdkConfig = (0, __2.initialize)({ env });
|
|
18
18
|
// Set up the Wallet and Provider
|
|
19
19
|
const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
|
|
20
20
|
const keypair = web3_js_1.Keypair.fromSecretKey(Uint8Array.from(JSON.parse(privateKey)));
|
|
@@ -36,14 +36,14 @@ const main = async () => {
|
|
|
36
36
|
connection,
|
|
37
37
|
wallet: provider.wallet,
|
|
38
38
|
programID: driftPublicKey,
|
|
39
|
-
...(0, __2.getMarketsAndOraclesForSubscription)(
|
|
39
|
+
...(0, __2.getMarketsAndOraclesForSubscription)(env),
|
|
40
40
|
accountSubscription: {
|
|
41
41
|
type: 'polling',
|
|
42
42
|
accountLoader: bulkAccountLoader,
|
|
43
43
|
},
|
|
44
44
|
});
|
|
45
45
|
await driftClient.subscribe();
|
|
46
|
-
// Set up
|
|
46
|
+
// Set up user client
|
|
47
47
|
const user = new __2.User({
|
|
48
48
|
driftClient: driftClient,
|
|
49
49
|
userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
|
|
@@ -52,7 +52,7 @@ const main = async () => {
|
|
|
52
52
|
accountLoader: bulkAccountLoader,
|
|
53
53
|
},
|
|
54
54
|
});
|
|
55
|
-
//// Check if
|
|
55
|
+
//// Check if user account exists for the current wallet
|
|
56
56
|
const userAccountExists = await user.exists();
|
|
57
57
|
if (!userAccountExists) {
|
|
58
58
|
//// Create a Clearing House account by Depositing some USDC ($10,000 in this case)
|
|
@@ -62,21 +62,20 @@ const main = async () => {
|
|
|
62
62
|
await user.subscribe();
|
|
63
63
|
// Get current price
|
|
64
64
|
const solMarketInfo = sdkConfig.PERP_MARKETS.find((market) => market.baseAssetSymbol === 'SOL');
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
|
|
65
|
+
const marketIndex = solMarketInfo.marketIndex;
|
|
66
|
+
const [bid, ask] = (0, __1.calculateBidAskPrice)(driftClient.getPerpMarketAccount(marketIndex).amm, driftClient.getOracleDataForPerpMarket(marketIndex));
|
|
67
|
+
const formattedBidPrice = (0, __2.convertToNumber)(bid, __2.PRICE_PRECISION);
|
|
68
|
+
const formattedAskPrice = (0, __2.convertToNumber)(ask, __2.PRICE_PRECISION);
|
|
69
|
+
console.log(`Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`);
|
|
68
70
|
// Estimate the slippage for a $5000 LONG trade
|
|
69
71
|
const solMarketAccount = driftClient.getPerpMarketAccount(solMarketInfo.marketIndex);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await driftClient.openPosition(__2.PositionDirection.SHORT, reduceAmount, solMarketInfo.marketIndex);
|
|
79
|
-
// Close the rest of the position
|
|
80
|
-
await driftClient.closePosition(solMarketInfo.marketIndex);
|
|
72
|
+
const slippage = (0, __2.convertToNumber)((0, __2.calculateTradeSlippage)(__2.PositionDirection.LONG, new anchor_1.BN(1).mul(__1.BASE_PRECISION), solMarketAccount, 'base', driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex))[0], __2.PRICE_PRECISION);
|
|
73
|
+
console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
|
|
74
|
+
await driftClient.placePerpOrder((0, __1.getMarketOrderParams)({
|
|
75
|
+
baseAssetAmount: new anchor_1.BN(1).mul(__1.BASE_PRECISION),
|
|
76
|
+
direction: __2.PositionDirection.LONG,
|
|
77
|
+
marketIndex: solMarketAccount.marketIndex,
|
|
78
|
+
}));
|
|
79
|
+
console.log(`Placed a 1 SOL-PERP LONG order`);
|
|
81
80
|
};
|
|
82
81
|
main();
|
package/lib/idl/drift.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.12.0-beta.
|
|
2
|
+
"version": "2.12.0-beta.2",
|
|
3
3
|
"name": "drift",
|
|
4
4
|
"instructions": [
|
|
5
5
|
{
|
|
@@ -3705,9 +3705,7 @@
|
|
|
3705
3705
|
"args": [
|
|
3706
3706
|
{
|
|
3707
3707
|
"name": "exchangeStatus",
|
|
3708
|
-
"type":
|
|
3709
|
-
"defined": "ExchangeStatus"
|
|
3710
|
-
}
|
|
3708
|
+
"type": "u8"
|
|
3711
3709
|
}
|
|
3712
3710
|
]
|
|
3713
3711
|
},
|
|
@@ -4384,9 +4382,7 @@
|
|
|
4384
4382
|
},
|
|
4385
4383
|
{
|
|
4386
4384
|
"name": "exchangeStatus",
|
|
4387
|
-
"type":
|
|
4388
|
-
"defined": "ExchangeStatus"
|
|
4389
|
-
}
|
|
4385
|
+
"type": "u8"
|
|
4390
4386
|
},
|
|
4391
4387
|
{
|
|
4392
4388
|
"name": "liquidationDuration",
|
|
@@ -6360,10 +6356,10 @@
|
|
|
6360
6356
|
"kind": "enum",
|
|
6361
6357
|
"variants": [
|
|
6362
6358
|
{
|
|
6363
|
-
"name": "
|
|
6359
|
+
"name": "DepositPaused"
|
|
6364
6360
|
},
|
|
6365
6361
|
{
|
|
6366
|
-
"name": "
|
|
6362
|
+
"name": "WithdrawPaused"
|
|
6367
6363
|
},
|
|
6368
6364
|
{
|
|
6369
6365
|
"name": "AmmPaused"
|
|
@@ -6375,10 +6371,10 @@
|
|
|
6375
6371
|
"name": "LiqPaused"
|
|
6376
6372
|
},
|
|
6377
6373
|
{
|
|
6378
|
-
"name": "
|
|
6374
|
+
"name": "FundingPaused"
|
|
6379
6375
|
},
|
|
6380
6376
|
{
|
|
6381
|
-
"name": "
|
|
6377
|
+
"name": "SettlePnlPaused"
|
|
6382
6378
|
}
|
|
6383
6379
|
]
|
|
6384
6380
|
}
|
|
@@ -8568,6 +8564,11 @@
|
|
|
8568
8564
|
"code": 6224,
|
|
8569
8565
|
"name": "MaxNumberOfUsers",
|
|
8570
8566
|
"msg": "Max Number Of Users"
|
|
8567
|
+
},
|
|
8568
|
+
{
|
|
8569
|
+
"code": 6225,
|
|
8570
|
+
"name": "InvalidOracleForSettlePnl",
|
|
8571
|
+
"msg": "InvalidOracleForSettlePnl"
|
|
8571
8572
|
}
|
|
8572
8573
|
]
|
|
8573
8574
|
}
|
|
@@ -3,16 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ammPaused = exports.fillPaused = exports.exchangePaused = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
function exchangePaused(state) {
|
|
6
|
-
return
|
|
6
|
+
return state.exchangeStatus !== types_1.ExchangeStatus.ACTIVE;
|
|
7
7
|
}
|
|
8
8
|
exports.exchangePaused = exchangePaused;
|
|
9
9
|
function fillPaused(state, market) {
|
|
10
|
-
return ((
|
|
10
|
+
return ((state.exchangeStatus & types_1.ExchangeStatus.FILL_PAUSED) ===
|
|
11
|
+
types_1.ExchangeStatus.FILL_PAUSED ||
|
|
11
12
|
(0, types_1.isOneOfVariant)(market.status, ['paused', 'fillPaused']));
|
|
12
13
|
}
|
|
13
14
|
exports.fillPaused = fillPaused;
|
|
14
15
|
function ammPaused(state, market) {
|
|
15
|
-
return ((
|
|
16
|
+
return ((state.exchangeStatus & types_1.ExchangeStatus.AMM_PAUSED) ===
|
|
17
|
+
types_1.ExchangeStatus.AMM_PAUSED ||
|
|
16
18
|
(0, types_1.isOneOfVariant)(market.status, ['paused', 'ammPaused']));
|
|
17
19
|
}
|
|
18
20
|
exports.ammPaused = ammPaused;
|
package/lib/math/trade.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { PerpMarketAccount, PositionDirection } from '../types';
|
|
|
3
3
|
import { BN } from '@project-serum/anchor';
|
|
4
4
|
import { AssetType } from './amm';
|
|
5
5
|
import { OraclePriceData } from '../oracles/types';
|
|
6
|
+
import { DLOB } from '../dlob/DLOB';
|
|
6
7
|
export declare type PriceImpactUnit = 'entryPrice' | 'maxPrice' | 'priceDelta' | 'priceDeltaAsNumber' | 'pctAvg' | 'pctMax' | 'quoteAssetAmount' | 'quoteAssetAmountPeg' | 'acquiredBaseAssetAmount' | 'acquiredQuoteAssetAmount' | 'all';
|
|
7
8
|
/**
|
|
8
9
|
* Calculates avg/max slippage (price impact) for candidate trade
|
|
@@ -52,3 +53,17 @@ export declare function calculateTradeAcquiredAmounts(direction: PositionDirecti
|
|
|
52
53
|
* ]
|
|
53
54
|
*/
|
|
54
55
|
export declare function calculateTargetPriceTrade(market: PerpMarketAccount, targetPrice: BN, pct?: BN, outputAssetType?: AssetType, oraclePriceData?: OraclePriceData, useSpread?: boolean): [PositionDirection, BN, BN, BN];
|
|
56
|
+
/**
|
|
57
|
+
* Calculates the estimated entry price and price impact of order, in base or quote
|
|
58
|
+
* Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
|
|
59
|
+
*
|
|
60
|
+
* @param assetType
|
|
61
|
+
* @param amount
|
|
62
|
+
* @param direction
|
|
63
|
+
* @param market
|
|
64
|
+
* @param oraclePriceData
|
|
65
|
+
* @param dlob
|
|
66
|
+
* @param slot
|
|
67
|
+
* @param minPerpAuctionDuration
|
|
68
|
+
*/
|
|
69
|
+
export declare function calculateEstimatedPerpEntryPrice(assetType: AssetType, amount: BN, direction: PositionDirection, market: PerpMarketAccount, oraclePriceData: OraclePriceData, dlob: DLOB, slot: number, minPerpAuctionDuration: number): [BN, BN];
|
package/lib/math/trade.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
|
|
3
|
+
exports.calculateEstimatedPerpEntryPrice = exports.calculateTargetPriceTrade = exports.calculateTradeAcquiredAmounts = exports.calculateTradeSlippage = void 0;
|
|
4
4
|
const types_1 = require("../types");
|
|
5
5
|
const anchor_1 = require("@project-serum/anchor");
|
|
6
6
|
const assert_1 = require("../assert/assert");
|
|
@@ -247,3 +247,137 @@ function calculateTargetPriceTrade(market, targetPrice, pct = MAXPCT, outputAsse
|
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
exports.calculateTargetPriceTrade = calculateTargetPriceTrade;
|
|
250
|
+
/**
|
|
251
|
+
* Calculates the estimated entry price and price impact of order, in base or quote
|
|
252
|
+
* Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
|
|
253
|
+
*
|
|
254
|
+
* @param assetType
|
|
255
|
+
* @param amount
|
|
256
|
+
* @param direction
|
|
257
|
+
* @param market
|
|
258
|
+
* @param oraclePriceData
|
|
259
|
+
* @param dlob
|
|
260
|
+
* @param slot
|
|
261
|
+
* @param minPerpAuctionDuration
|
|
262
|
+
*/
|
|
263
|
+
function calculateEstimatedPerpEntryPrice(assetType, amount, direction, market, oraclePriceData, dlob, slot, minPerpAuctionDuration) {
|
|
264
|
+
if (amount.eq(numericConstants_1.ZERO)) {
|
|
265
|
+
return [numericConstants_1.ZERO, numericConstants_1.ZERO];
|
|
266
|
+
}
|
|
267
|
+
const takerIsLong = (0, types_2.isVariant)(direction, 'long');
|
|
268
|
+
const limitOrders = dlob[takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'](market.marketIndex, slot, types_1.MarketType.PERP, oraclePriceData, minPerpAuctionDuration);
|
|
269
|
+
const swapDirection = (0, amm_1.getSwapDirection)(assetType, direction);
|
|
270
|
+
const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } = (0, amm_1.calculateUpdatedAMMSpreadReserves)(market.amm, direction, oraclePriceData);
|
|
271
|
+
const amm = {
|
|
272
|
+
baseAssetReserve,
|
|
273
|
+
quoteAssetReserve,
|
|
274
|
+
sqrtK: sqrtK,
|
|
275
|
+
pegMultiplier: newPeg,
|
|
276
|
+
};
|
|
277
|
+
const invariant = amm.sqrtK.mul(amm.sqrtK);
|
|
278
|
+
let initialPrice = (0, amm_1.calculatePrice)(amm.baseAssetReserve, amm.quoteAssetReserve, amm.pegMultiplier);
|
|
279
|
+
let cumulativeBaseFilled = numericConstants_1.ZERO;
|
|
280
|
+
let cumulativeQuoteFilled = numericConstants_1.ZERO;
|
|
281
|
+
let limitOrder = limitOrders.next().value;
|
|
282
|
+
if (limitOrder) {
|
|
283
|
+
const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
|
|
284
|
+
initialPrice = takerIsLong
|
|
285
|
+
? anchor_1.BN.min(limitOrderPrice, initialPrice)
|
|
286
|
+
: anchor_1.BN.max(limitOrderPrice, initialPrice);
|
|
287
|
+
}
|
|
288
|
+
if (assetType === 'base') {
|
|
289
|
+
while (!cumulativeBaseFilled.eq(amount)) {
|
|
290
|
+
const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
|
|
291
|
+
let maxAmmFill;
|
|
292
|
+
if (limitOrderPrice) {
|
|
293
|
+
const newBaseReserves = (0, utils_1.squareRootBN)(invariant
|
|
294
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
295
|
+
.mul(amm.pegMultiplier)
|
|
296
|
+
.div(limitOrderPrice)
|
|
297
|
+
.div(numericConstants_1.PEG_PRECISION));
|
|
298
|
+
// will be zero if the limit order price is better than the amm price
|
|
299
|
+
maxAmmFill = takerIsLong
|
|
300
|
+
? amm.baseAssetReserve.sub(newBaseReserves)
|
|
301
|
+
: newBaseReserves.sub(amm.baseAssetReserve);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
maxAmmFill = amount.sub(cumulativeBaseFilled);
|
|
305
|
+
}
|
|
306
|
+
if (maxAmmFill.gt(numericConstants_1.ZERO)) {
|
|
307
|
+
const baseFilled = anchor_1.BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
|
|
308
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'base', baseFilled, swapDirection);
|
|
309
|
+
const quoteFilled = (0, amm_1.calculateQuoteAssetAmountSwapped)(amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(), amm.pegMultiplier, swapDirection);
|
|
310
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
311
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
312
|
+
amm.baseAssetReserve = afterSwapBaseReserves;
|
|
313
|
+
amm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
314
|
+
if (cumulativeBaseFilled.eq(amount)) {
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
const baseFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount.sub(limitOrder.order.baseAssetAmountFilled), amount.sub(cumulativeBaseFilled));
|
|
319
|
+
const quoteFilled = baseFilled.mul(limitOrderPrice).div(numericConstants_1.BASE_PRECISION);
|
|
320
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
321
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
322
|
+
if (cumulativeBaseFilled.eq(amount)) {
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
limitOrder = limitOrders.next().value;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
while (!cumulativeQuoteFilled.eq(amount)) {
|
|
330
|
+
const limitOrderPrice = limitOrder === null || limitOrder === void 0 ? void 0 : limitOrder.getPrice(oraclePriceData, slot);
|
|
331
|
+
let maxAmmFill;
|
|
332
|
+
if (limitOrderPrice) {
|
|
333
|
+
const newQuoteReserves = (0, utils_1.squareRootBN)(invariant
|
|
334
|
+
.mul(numericConstants_1.PEG_PRECISION)
|
|
335
|
+
.mul(limitOrderPrice)
|
|
336
|
+
.div(amm.pegMultiplier)
|
|
337
|
+
.div(numericConstants_1.PRICE_PRECISION));
|
|
338
|
+
// will be zero if the limit order price is better than the amm price
|
|
339
|
+
maxAmmFill = takerIsLong
|
|
340
|
+
? newQuoteReserves.sub(amm.quoteAssetReserve)
|
|
341
|
+
: amm.quoteAssetReserve.sub(newQuoteReserves);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
maxAmmFill = amount.sub(cumulativeQuoteFilled);
|
|
345
|
+
}
|
|
346
|
+
if (maxAmmFill.gt(numericConstants_1.ZERO)) {
|
|
347
|
+
const quoteFilled = anchor_1.BN.min(amount.sub(cumulativeQuoteFilled), maxAmmFill);
|
|
348
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] = (0, amm_1.calculateAmmReservesAfterSwap)(amm, 'quote', quoteFilled, swapDirection);
|
|
349
|
+
const baseFilled = afterSwapBaseReserves
|
|
350
|
+
.sub(amm.baseAssetReserve)
|
|
351
|
+
.abs();
|
|
352
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
353
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
354
|
+
amm.baseAssetReserve = afterSwapBaseReserves;
|
|
355
|
+
amm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
356
|
+
if (cumulativeQuoteFilled.eq(amount)) {
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const quoteFilled = anchor_1.BN.min(limitOrder.order.baseAssetAmount
|
|
361
|
+
.sub(limitOrder.order.baseAssetAmountFilled)
|
|
362
|
+
.mul(limitOrderPrice)
|
|
363
|
+
.div(numericConstants_1.BASE_PRECISION), amount.sub(cumulativeQuoteFilled));
|
|
364
|
+
const baseFilled = quoteFilled.mul(numericConstants_1.BASE_PRECISION).div(limitOrderPrice);
|
|
365
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
366
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
367
|
+
if (cumulativeQuoteFilled.eq(amount)) {
|
|
368
|
+
break;
|
|
369
|
+
}
|
|
370
|
+
limitOrder = limitOrders.next().value;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const entryPrice = cumulativeQuoteFilled
|
|
374
|
+
.mul(numericConstants_1.BASE_PRECISION)
|
|
375
|
+
.div(cumulativeBaseFilled);
|
|
376
|
+
const priceImpact = entryPrice
|
|
377
|
+
.sub(initialPrice)
|
|
378
|
+
.mul(numericConstants_1.PRICE_PRECISION)
|
|
379
|
+
.div(initialPrice)
|
|
380
|
+
.abs();
|
|
381
|
+
return [entryPrice, priceImpact];
|
|
382
|
+
}
|
|
383
|
+
exports.calculateEstimatedPerpEntryPrice = calculateEstimatedPerpEntryPrice;
|
package/lib/types.d.ts
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
/// <reference types="bn.js" />
|
|
2
2
|
import { PublicKey, Transaction } from '@solana/web3.js';
|
|
3
3
|
import { BN } from '.';
|
|
4
|
-
export declare
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
static readonly FILL_PAUSED: {
|
|
15
|
-
fillPaused: {};
|
|
16
|
-
};
|
|
17
|
-
static readonly LIQ_PAUSED: {
|
|
18
|
-
liqPaused: {};
|
|
19
|
-
};
|
|
20
|
-
static readonly WITHDRAW_PAUSED: {
|
|
21
|
-
withdrawPaused: {};
|
|
22
|
-
};
|
|
23
|
-
static readonly PAUSED: {
|
|
24
|
-
paused: {};
|
|
25
|
-
};
|
|
4
|
+
export declare enum ExchangeStatus {
|
|
5
|
+
ACTIVE = 0,
|
|
6
|
+
DEPOSIT_PAUSED = 1,
|
|
7
|
+
WITHDRAW_PAUSED = 2,
|
|
8
|
+
AMM_PAUSED = 4,
|
|
9
|
+
FILL_PAUSED = 8,
|
|
10
|
+
LIQ_PAUSED = 16,
|
|
11
|
+
FUNDING_PAUSED = 32,
|
|
12
|
+
SETTLE_PNL_PAUSED = 64,
|
|
13
|
+
PAUSED = 127
|
|
26
14
|
}
|
|
27
15
|
export declare class MarketStatus {
|
|
28
16
|
static readonly INITIALIZED: {
|
|
@@ -587,7 +575,7 @@ export declare type OrderActionRecord = {
|
|
|
587
575
|
};
|
|
588
576
|
export declare type StateAccount = {
|
|
589
577
|
admin: PublicKey;
|
|
590
|
-
exchangeStatus:
|
|
578
|
+
exchangeStatus: number;
|
|
591
579
|
whitelistMint: PublicKey;
|
|
592
580
|
discountMint: PublicKey;
|
|
593
581
|
oracleGuardRails: OracleGuardRails;
|
package/lib/types.js
CHANGED
|
@@ -3,16 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DefaultOrderParams = exports.LiquidationType = exports.LPAction = exports.TradeSide = exports.getVariant = exports.isOneOfVariant = exports.isVariant = exports.StakeAction = exports.SpotFulfillmentConfigStatus = exports.SettlePnlExplanation = exports.DepositExplanation = exports.SpotFulfillmentStatus = exports.SpotFulfillmentType = exports.OrderTriggerCondition = exports.OrderActionExplanation = exports.OrderAction = exports.OrderStatus = exports.MarketType = exports.OrderType = exports.OracleSource = exports.DepositDirection = exports.PositionDirection = exports.SpotBalanceType = exports.SwapDirection = exports.AssetTier = exports.ContractTier = exports.ContractType = exports.UserStatus = exports.MarketStatus = exports.ExchangeStatus = void 0;
|
|
4
4
|
const _1 = require(".");
|
|
5
5
|
// # Utility Types / Enums / Constants
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
ExchangeStatus
|
|
10
|
-
ExchangeStatus
|
|
11
|
-
ExchangeStatus
|
|
12
|
-
ExchangeStatus
|
|
13
|
-
ExchangeStatus
|
|
14
|
-
ExchangeStatus
|
|
15
|
-
ExchangeStatus
|
|
6
|
+
var ExchangeStatus;
|
|
7
|
+
(function (ExchangeStatus) {
|
|
8
|
+
ExchangeStatus[ExchangeStatus["ACTIVE"] = 0] = "ACTIVE";
|
|
9
|
+
ExchangeStatus[ExchangeStatus["DEPOSIT_PAUSED"] = 1] = "DEPOSIT_PAUSED";
|
|
10
|
+
ExchangeStatus[ExchangeStatus["WITHDRAW_PAUSED"] = 2] = "WITHDRAW_PAUSED";
|
|
11
|
+
ExchangeStatus[ExchangeStatus["AMM_PAUSED"] = 4] = "AMM_PAUSED";
|
|
12
|
+
ExchangeStatus[ExchangeStatus["FILL_PAUSED"] = 8] = "FILL_PAUSED";
|
|
13
|
+
ExchangeStatus[ExchangeStatus["LIQ_PAUSED"] = 16] = "LIQ_PAUSED";
|
|
14
|
+
ExchangeStatus[ExchangeStatus["FUNDING_PAUSED"] = 32] = "FUNDING_PAUSED";
|
|
15
|
+
ExchangeStatus[ExchangeStatus["SETTLE_PNL_PAUSED"] = 64] = "SETTLE_PNL_PAUSED";
|
|
16
|
+
ExchangeStatus[ExchangeStatus["PAUSED"] = 127] = "PAUSED";
|
|
17
|
+
})(ExchangeStatus = exports.ExchangeStatus || (exports.ExchangeStatus = {}));
|
|
16
18
|
class MarketStatus {
|
|
17
19
|
}
|
|
18
20
|
exports.MarketStatus = MarketStatus;
|
package/lib/user.js
CHANGED
|
@@ -797,7 +797,7 @@ class User {
|
|
|
797
797
|
const freeCollateralExcludingTargetMarket = totalCollateral.sub(marginRequirementExcludingTargetMarket);
|
|
798
798
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
799
799
|
if (totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
800
|
-
proposedPerpPosition.baseAssetAmount.
|
|
800
|
+
proposedPerpPosition.baseAssetAmount.gt(numericConstants_1.ZERO)) {
|
|
801
801
|
return new _1.BN(-1);
|
|
802
802
|
}
|
|
803
803
|
const marginRequirementAfterTrade = marginRequirementExcludingTargetMarket.add(proposedPerpPositionValue
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { AnchorProvider, BN } from '@project-serum/anchor';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
BASE_PRECISION,
|
|
4
|
+
calculateBidAskPrice,
|
|
5
|
+
getMarketOrderParams,
|
|
6
|
+
Wallet,
|
|
7
|
+
} from '..';
|
|
3
8
|
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
|
4
9
|
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
5
10
|
import {
|
|
6
|
-
calculateReservePrice,
|
|
7
11
|
DriftClient,
|
|
8
12
|
User,
|
|
9
13
|
initialize,
|
|
@@ -29,11 +33,11 @@ export const getTokenAddress = (
|
|
|
29
33
|
);
|
|
30
34
|
};
|
|
31
35
|
|
|
32
|
-
const
|
|
36
|
+
const env = 'devnet';
|
|
33
37
|
|
|
34
38
|
const main = async () => {
|
|
35
39
|
// Initialize Drift SDK
|
|
36
|
-
const sdkConfig = initialize({ env
|
|
40
|
+
const sdkConfig = initialize({ env });
|
|
37
41
|
|
|
38
42
|
// Set up the Wallet and Provider
|
|
39
43
|
const privateKey = process.env.BOT_PRIVATE_KEY; // stored as an array string
|
|
@@ -74,7 +78,7 @@ const main = async () => {
|
|
|
74
78
|
connection,
|
|
75
79
|
wallet: provider.wallet,
|
|
76
80
|
programID: driftPublicKey,
|
|
77
|
-
...getMarketsAndOraclesForSubscription(
|
|
81
|
+
...getMarketsAndOraclesForSubscription(env),
|
|
78
82
|
accountSubscription: {
|
|
79
83
|
type: 'polling',
|
|
80
84
|
accountLoader: bulkAccountLoader,
|
|
@@ -82,7 +86,7 @@ const main = async () => {
|
|
|
82
86
|
});
|
|
83
87
|
await driftClient.subscribe();
|
|
84
88
|
|
|
85
|
-
// Set up
|
|
89
|
+
// Set up user client
|
|
86
90
|
const user = new User({
|
|
87
91
|
driftClient: driftClient,
|
|
88
92
|
userAccountPublicKey: await driftClient.getUserAccountPublicKey(),
|
|
@@ -92,7 +96,7 @@ const main = async () => {
|
|
|
92
96
|
},
|
|
93
97
|
});
|
|
94
98
|
|
|
95
|
-
//// Check if
|
|
99
|
+
//// Check if user account exists for the current wallet
|
|
96
100
|
const userAccountExists = await user.exists();
|
|
97
101
|
|
|
98
102
|
if (!userAccountExists) {
|
|
@@ -115,54 +119,45 @@ const main = async () => {
|
|
|
115
119
|
(market) => market.baseAssetSymbol === 'SOL'
|
|
116
120
|
);
|
|
117
121
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
const marketIndex = solMarketInfo.marketIndex;
|
|
123
|
+
const [bid, ask] = calculateBidAskPrice(
|
|
124
|
+
driftClient.getPerpMarketAccount(marketIndex).amm,
|
|
125
|
+
driftClient.getOracleDataForPerpMarket(marketIndex)
|
|
121
126
|
);
|
|
122
127
|
|
|
123
|
-
const
|
|
128
|
+
const formattedBidPrice = convertToNumber(bid, PRICE_PRECISION);
|
|
129
|
+
const formattedAskPrice = convertToNumber(ask, PRICE_PRECISION);
|
|
124
130
|
|
|
125
|
-
console.log(
|
|
131
|
+
console.log(
|
|
132
|
+
`Current amm bid and ask price are $${formattedBidPrice} and $${formattedAskPrice}`
|
|
133
|
+
);
|
|
126
134
|
|
|
127
135
|
// Estimate the slippage for a $5000 LONG trade
|
|
128
136
|
const solMarketAccount = driftClient.getPerpMarketAccount(
|
|
129
137
|
solMarketInfo.marketIndex
|
|
130
138
|
);
|
|
131
139
|
|
|
132
|
-
const longAmount = new BN(5000).mul(QUOTE_PRECISION);
|
|
133
140
|
const slippage = convertToNumber(
|
|
134
141
|
calculateTradeSlippage(
|
|
135
142
|
PositionDirection.LONG,
|
|
136
|
-
|
|
143
|
+
new BN(1).mul(BASE_PRECISION),
|
|
137
144
|
solMarketAccount,
|
|
138
|
-
'
|
|
145
|
+
'base',
|
|
139
146
|
driftClient.getOracleDataForPerpMarket(solMarketInfo.marketIndex)
|
|
140
147
|
)[0],
|
|
141
148
|
PRICE_PRECISION
|
|
142
149
|
);
|
|
143
150
|
|
|
144
|
-
console.log(
|
|
145
|
-
`Slippage for a $5000 LONG on the SOL market would be $${slippage}`
|
|
146
|
-
);
|
|
151
|
+
console.log(`Slippage for a 1 SOL-PERP would be $${slippage}`);
|
|
147
152
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
await driftClient.placePerpOrder(
|
|
154
|
+
getMarketOrderParams({
|
|
155
|
+
baseAssetAmount: new BN(1).mul(BASE_PRECISION),
|
|
156
|
+
direction: PositionDirection.LONG,
|
|
157
|
+
marketIndex: solMarketAccount.marketIndex,
|
|
158
|
+
})
|
|
153
159
|
);
|
|
154
|
-
console.log(`
|
|
155
|
-
|
|
156
|
-
// Reduce the position by $2000
|
|
157
|
-
const reduceAmount = new BN(2000).mul(QUOTE_PRECISION);
|
|
158
|
-
await driftClient.openPosition(
|
|
159
|
-
PositionDirection.SHORT,
|
|
160
|
-
reduceAmount,
|
|
161
|
-
solMarketInfo.marketIndex
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
// Close the rest of the position
|
|
165
|
-
await driftClient.closePosition(solMarketInfo.marketIndex);
|
|
160
|
+
console.log(`Placed a 1 SOL-PERP LONG order`);
|
|
166
161
|
};
|
|
167
162
|
|
|
168
163
|
main();
|
package/src/idl/drift.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "2.12.0-beta.
|
|
2
|
+
"version": "2.12.0-beta.2",
|
|
3
3
|
"name": "drift",
|
|
4
4
|
"instructions": [
|
|
5
5
|
{
|
|
@@ -3705,9 +3705,7 @@
|
|
|
3705
3705
|
"args": [
|
|
3706
3706
|
{
|
|
3707
3707
|
"name": "exchangeStatus",
|
|
3708
|
-
"type":
|
|
3709
|
-
"defined": "ExchangeStatus"
|
|
3710
|
-
}
|
|
3708
|
+
"type": "u8"
|
|
3711
3709
|
}
|
|
3712
3710
|
]
|
|
3713
3711
|
},
|
|
@@ -4384,9 +4382,7 @@
|
|
|
4384
4382
|
},
|
|
4385
4383
|
{
|
|
4386
4384
|
"name": "exchangeStatus",
|
|
4387
|
-
"type":
|
|
4388
|
-
"defined": "ExchangeStatus"
|
|
4389
|
-
}
|
|
4385
|
+
"type": "u8"
|
|
4390
4386
|
},
|
|
4391
4387
|
{
|
|
4392
4388
|
"name": "liquidationDuration",
|
|
@@ -6360,10 +6356,10 @@
|
|
|
6360
6356
|
"kind": "enum",
|
|
6361
6357
|
"variants": [
|
|
6362
6358
|
{
|
|
6363
|
-
"name": "
|
|
6359
|
+
"name": "DepositPaused"
|
|
6364
6360
|
},
|
|
6365
6361
|
{
|
|
6366
|
-
"name": "
|
|
6362
|
+
"name": "WithdrawPaused"
|
|
6367
6363
|
},
|
|
6368
6364
|
{
|
|
6369
6365
|
"name": "AmmPaused"
|
|
@@ -6375,10 +6371,10 @@
|
|
|
6375
6371
|
"name": "LiqPaused"
|
|
6376
6372
|
},
|
|
6377
6373
|
{
|
|
6378
|
-
"name": "
|
|
6374
|
+
"name": "FundingPaused"
|
|
6379
6375
|
},
|
|
6380
6376
|
{
|
|
6381
|
-
"name": "
|
|
6377
|
+
"name": "SettlePnlPaused"
|
|
6382
6378
|
}
|
|
6383
6379
|
]
|
|
6384
6380
|
}
|
|
@@ -8568,6 +8564,11 @@
|
|
|
8568
8564
|
"code": 6224,
|
|
8569
8565
|
"name": "MaxNumberOfUsers",
|
|
8570
8566
|
"msg": "Max Number Of Users"
|
|
8567
|
+
},
|
|
8568
|
+
{
|
|
8569
|
+
"code": 6225,
|
|
8570
|
+
"name": "InvalidOracleForSettlePnl",
|
|
8571
|
+
"msg": "InvalidOracleForSettlePnl"
|
|
8571
8572
|
}
|
|
8572
8573
|
]
|
|
8573
8574
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ExchangeStatus,
|
|
2
3
|
isOneOfVariant,
|
|
3
|
-
isVariant,
|
|
4
4
|
PerpMarketAccount,
|
|
5
5
|
SpotMarketAccount,
|
|
6
6
|
StateAccount,
|
|
7
7
|
} from '../types';
|
|
8
8
|
|
|
9
9
|
export function exchangePaused(state: StateAccount): boolean {
|
|
10
|
-
return
|
|
10
|
+
return state.exchangeStatus !== ExchangeStatus.ACTIVE;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
export function fillPaused(
|
|
@@ -15,7 +15,8 @@ export function fillPaused(
|
|
|
15
15
|
market: PerpMarketAccount | SpotMarketAccount
|
|
16
16
|
): boolean {
|
|
17
17
|
return (
|
|
18
|
-
|
|
18
|
+
(state.exchangeStatus & ExchangeStatus.FILL_PAUSED) ===
|
|
19
|
+
ExchangeStatus.FILL_PAUSED ||
|
|
19
20
|
isOneOfVariant(market.status, ['paused', 'fillPaused'])
|
|
20
21
|
);
|
|
21
22
|
}
|
|
@@ -25,7 +26,8 @@ export function ammPaused(
|
|
|
25
26
|
market: PerpMarketAccount | SpotMarketAccount
|
|
26
27
|
): boolean {
|
|
27
28
|
return (
|
|
28
|
-
|
|
29
|
+
(state.exchangeStatus & ExchangeStatus.AMM_PAUSED) ===
|
|
30
|
+
ExchangeStatus.AMM_PAUSED ||
|
|
29
31
|
isOneOfVariant(market.status, ['paused', 'ammPaused'])
|
|
30
32
|
);
|
|
31
33
|
}
|
package/src/math/trade.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PerpMarketAccount, PositionDirection } from '../types';
|
|
1
|
+
import { MarketType, PerpMarketAccount, PositionDirection } from '../types';
|
|
2
2
|
import { BN } from '@project-serum/anchor';
|
|
3
3
|
import { assert } from '../assert/assert';
|
|
4
4
|
import {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
PEG_PRECISION,
|
|
7
7
|
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
8
8
|
ZERO,
|
|
9
|
+
BASE_PRECISION,
|
|
9
10
|
} from '../constants/numericConstants';
|
|
10
11
|
import {
|
|
11
12
|
calculateBidPrice,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
import { squareRootBN } from './utils';
|
|
24
25
|
import { isVariant } from '../types';
|
|
25
26
|
import { OraclePriceData } from '../oracles/types';
|
|
27
|
+
import { DLOB } from '../dlob/DLOB';
|
|
26
28
|
|
|
27
29
|
const MAXPCT = new BN(1000); //percentage units are [0,1000] => [0,1]
|
|
28
30
|
|
|
@@ -349,3 +351,216 @@ export function calculateTargetPriceTrade(
|
|
|
349
351
|
return [direction, baseSize, entryPrice, targetPrice];
|
|
350
352
|
}
|
|
351
353
|
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Calculates the estimated entry price and price impact of order, in base or quote
|
|
357
|
+
* Price impact is based on the difference between the entry price and the best bid/ask price (whether it's dlob or vamm)
|
|
358
|
+
*
|
|
359
|
+
* @param assetType
|
|
360
|
+
* @param amount
|
|
361
|
+
* @param direction
|
|
362
|
+
* @param market
|
|
363
|
+
* @param oraclePriceData
|
|
364
|
+
* @param dlob
|
|
365
|
+
* @param slot
|
|
366
|
+
* @param minPerpAuctionDuration
|
|
367
|
+
*/
|
|
368
|
+
export function calculateEstimatedPerpEntryPrice(
|
|
369
|
+
assetType: AssetType,
|
|
370
|
+
amount: BN,
|
|
371
|
+
direction: PositionDirection,
|
|
372
|
+
market: PerpMarketAccount,
|
|
373
|
+
oraclePriceData: OraclePriceData,
|
|
374
|
+
dlob: DLOB,
|
|
375
|
+
slot: number,
|
|
376
|
+
minPerpAuctionDuration: number
|
|
377
|
+
): [BN, BN] {
|
|
378
|
+
if (amount.eq(ZERO)) {
|
|
379
|
+
return [ZERO, ZERO];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const takerIsLong = isVariant(direction, 'long');
|
|
383
|
+
const limitOrders = dlob[
|
|
384
|
+
takerIsLong ? 'getRestingLimitAsks' : 'getRestingLimitBids'
|
|
385
|
+
](
|
|
386
|
+
market.marketIndex,
|
|
387
|
+
slot,
|
|
388
|
+
MarketType.PERP,
|
|
389
|
+
oraclePriceData,
|
|
390
|
+
minPerpAuctionDuration
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
const swapDirection = getSwapDirection(assetType, direction);
|
|
394
|
+
|
|
395
|
+
const { baseAssetReserve, quoteAssetReserve, sqrtK, newPeg } =
|
|
396
|
+
calculateUpdatedAMMSpreadReserves(market.amm, direction, oraclePriceData);
|
|
397
|
+
const amm = {
|
|
398
|
+
baseAssetReserve,
|
|
399
|
+
quoteAssetReserve,
|
|
400
|
+
sqrtK: sqrtK,
|
|
401
|
+
pegMultiplier: newPeg,
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
const invariant = amm.sqrtK.mul(amm.sqrtK);
|
|
405
|
+
|
|
406
|
+
let initialPrice = calculatePrice(
|
|
407
|
+
amm.baseAssetReserve,
|
|
408
|
+
amm.quoteAssetReserve,
|
|
409
|
+
amm.pegMultiplier
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
let cumulativeBaseFilled = ZERO;
|
|
413
|
+
let cumulativeQuoteFilled = ZERO;
|
|
414
|
+
|
|
415
|
+
let limitOrder = limitOrders.next().value;
|
|
416
|
+
if (limitOrder) {
|
|
417
|
+
const limitOrderPrice = limitOrder.getPrice(oraclePriceData, slot);
|
|
418
|
+
initialPrice = takerIsLong
|
|
419
|
+
? BN.min(limitOrderPrice, initialPrice)
|
|
420
|
+
: BN.max(limitOrderPrice, initialPrice);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (assetType === 'base') {
|
|
424
|
+
while (!cumulativeBaseFilled.eq(amount)) {
|
|
425
|
+
const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
|
|
426
|
+
|
|
427
|
+
let maxAmmFill: BN;
|
|
428
|
+
if (limitOrderPrice) {
|
|
429
|
+
const newBaseReserves = squareRootBN(
|
|
430
|
+
invariant
|
|
431
|
+
.mul(PRICE_PRECISION)
|
|
432
|
+
.mul(amm.pegMultiplier)
|
|
433
|
+
.div(limitOrderPrice)
|
|
434
|
+
.div(PEG_PRECISION)
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// will be zero if the limit order price is better than the amm price
|
|
438
|
+
maxAmmFill = takerIsLong
|
|
439
|
+
? amm.baseAssetReserve.sub(newBaseReserves)
|
|
440
|
+
: newBaseReserves.sub(amm.baseAssetReserve);
|
|
441
|
+
} else {
|
|
442
|
+
maxAmmFill = amount.sub(cumulativeBaseFilled);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (maxAmmFill.gt(ZERO)) {
|
|
446
|
+
const baseFilled = BN.min(amount.sub(cumulativeBaseFilled), maxAmmFill);
|
|
447
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] =
|
|
448
|
+
calculateAmmReservesAfterSwap(amm, 'base', baseFilled, swapDirection);
|
|
449
|
+
|
|
450
|
+
const quoteFilled = calculateQuoteAssetAmountSwapped(
|
|
451
|
+
amm.quoteAssetReserve.sub(afterSwapQuoteReserves).abs(),
|
|
452
|
+
amm.pegMultiplier,
|
|
453
|
+
swapDirection
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
457
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
458
|
+
|
|
459
|
+
amm.baseAssetReserve = afterSwapBaseReserves;
|
|
460
|
+
amm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
461
|
+
|
|
462
|
+
if (cumulativeBaseFilled.eq(amount)) {
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const baseFilled = BN.min(
|
|
468
|
+
limitOrder.order.baseAssetAmount.sub(
|
|
469
|
+
limitOrder.order.baseAssetAmountFilled
|
|
470
|
+
),
|
|
471
|
+
amount.sub(cumulativeBaseFilled)
|
|
472
|
+
);
|
|
473
|
+
const quoteFilled = baseFilled.mul(limitOrderPrice).div(BASE_PRECISION);
|
|
474
|
+
|
|
475
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
476
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
477
|
+
|
|
478
|
+
if (cumulativeBaseFilled.eq(amount)) {
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
limitOrder = limitOrders.next().value;
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
while (!cumulativeQuoteFilled.eq(amount)) {
|
|
486
|
+
const limitOrderPrice = limitOrder?.getPrice(oraclePriceData, slot);
|
|
487
|
+
|
|
488
|
+
let maxAmmFill: BN;
|
|
489
|
+
if (limitOrderPrice) {
|
|
490
|
+
const newQuoteReserves = squareRootBN(
|
|
491
|
+
invariant
|
|
492
|
+
.mul(PEG_PRECISION)
|
|
493
|
+
.mul(limitOrderPrice)
|
|
494
|
+
.div(amm.pegMultiplier)
|
|
495
|
+
.div(PRICE_PRECISION)
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// will be zero if the limit order price is better than the amm price
|
|
499
|
+
maxAmmFill = takerIsLong
|
|
500
|
+
? newQuoteReserves.sub(amm.quoteAssetReserve)
|
|
501
|
+
: amm.quoteAssetReserve.sub(newQuoteReserves);
|
|
502
|
+
} else {
|
|
503
|
+
maxAmmFill = amount.sub(cumulativeQuoteFilled);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (maxAmmFill.gt(ZERO)) {
|
|
507
|
+
const quoteFilled = BN.min(
|
|
508
|
+
amount.sub(cumulativeQuoteFilled),
|
|
509
|
+
maxAmmFill
|
|
510
|
+
);
|
|
511
|
+
const [afterSwapQuoteReserves, afterSwapBaseReserves] =
|
|
512
|
+
calculateAmmReservesAfterSwap(
|
|
513
|
+
amm,
|
|
514
|
+
'quote',
|
|
515
|
+
quoteFilled,
|
|
516
|
+
swapDirection
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const baseFilled = afterSwapBaseReserves
|
|
520
|
+
.sub(amm.baseAssetReserve)
|
|
521
|
+
.abs();
|
|
522
|
+
|
|
523
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
524
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
525
|
+
|
|
526
|
+
amm.baseAssetReserve = afterSwapBaseReserves;
|
|
527
|
+
amm.quoteAssetReserve = afterSwapQuoteReserves;
|
|
528
|
+
|
|
529
|
+
if (cumulativeQuoteFilled.eq(amount)) {
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const quoteFilled = BN.min(
|
|
535
|
+
limitOrder.order.baseAssetAmount
|
|
536
|
+
.sub(limitOrder.order.baseAssetAmountFilled)
|
|
537
|
+
.mul(limitOrderPrice)
|
|
538
|
+
.div(BASE_PRECISION),
|
|
539
|
+
amount.sub(cumulativeQuoteFilled)
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const baseFilled = quoteFilled.mul(BASE_PRECISION).div(limitOrderPrice);
|
|
543
|
+
|
|
544
|
+
cumulativeBaseFilled = cumulativeBaseFilled.add(baseFilled);
|
|
545
|
+
cumulativeQuoteFilled = cumulativeQuoteFilled.add(quoteFilled);
|
|
546
|
+
|
|
547
|
+
if (cumulativeQuoteFilled.eq(amount)) {
|
|
548
|
+
break;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
limitOrder = limitOrders.next().value;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const entryPrice = cumulativeQuoteFilled
|
|
556
|
+
.mul(BASE_PRECISION)
|
|
557
|
+
.div(cumulativeBaseFilled);
|
|
558
|
+
|
|
559
|
+
const priceImpact = entryPrice
|
|
560
|
+
.sub(initialPrice)
|
|
561
|
+
.mul(PRICE_PRECISION)
|
|
562
|
+
.div(initialPrice)
|
|
563
|
+
.abs();
|
|
564
|
+
|
|
565
|
+
return [entryPrice, priceImpact];
|
|
566
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -3,14 +3,16 @@ import { BN, ZERO } from '.';
|
|
|
3
3
|
|
|
4
4
|
// # Utility Types / Enums / Constants
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export enum ExchangeStatus {
|
|
7
|
+
ACTIVE = 0,
|
|
8
|
+
DEPOSIT_PAUSED = 1,
|
|
9
|
+
WITHDRAW_PAUSED = 2,
|
|
10
|
+
AMM_PAUSED = 4,
|
|
11
|
+
FILL_PAUSED = 8,
|
|
12
|
+
LIQ_PAUSED = 16,
|
|
13
|
+
FUNDING_PAUSED = 32,
|
|
14
|
+
SETTLE_PNL_PAUSED = 64,
|
|
15
|
+
PAUSED = 127,
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export class MarketStatus {
|
|
@@ -501,7 +503,7 @@ export type OrderActionRecord = {
|
|
|
501
503
|
|
|
502
504
|
export type StateAccount = {
|
|
503
505
|
admin: PublicKey;
|
|
504
|
-
exchangeStatus:
|
|
506
|
+
exchangeStatus: number;
|
|
505
507
|
whitelistMint: PublicKey;
|
|
506
508
|
discountMint: PublicKey;
|
|
507
509
|
oracleGuardRails: OracleGuardRails;
|
package/src/user.ts
CHANGED
|
@@ -1335,7 +1335,7 @@ export class User {
|
|
|
1335
1335
|
// if the position value after the trade is less than free collateral, there is no liq price
|
|
1336
1336
|
if (
|
|
1337
1337
|
totalPositionValueAfterTrade.lte(freeCollateralExcludingTargetMarket) &&
|
|
1338
|
-
proposedPerpPosition.baseAssetAmount.
|
|
1338
|
+
proposedPerpPosition.baseAssetAmount.gt(ZERO)
|
|
1339
1339
|
) {
|
|
1340
1340
|
return new BN(-1);
|
|
1341
1341
|
}
|
package/tests/dlob/helpers.ts
CHANGED
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
UserMapInterface,
|
|
20
20
|
Wallet,
|
|
21
21
|
OrderRecord,
|
|
22
|
-
ExchangeStatus,
|
|
23
22
|
ZERO,
|
|
24
23
|
ContractTier,
|
|
25
24
|
} from '../../src';
|
|
@@ -478,7 +477,7 @@ export const mockStateAccount: StateAccount = {
|
|
|
478
477
|
defaultMarketOrderTimeInForce: 0,
|
|
479
478
|
defaultSpotAuctionDuration: 0,
|
|
480
479
|
discountMint: PublicKey.default,
|
|
481
|
-
exchangeStatus:
|
|
480
|
+
exchangeStatus: 0,
|
|
482
481
|
liquidationMarginBufferRatio: 0,
|
|
483
482
|
lpCooldownTime: new BN(0),
|
|
484
483
|
minPerpAuctionDuration: 0,
|