@drift-labs/sdk 0.1.22 → 0.1.23-master.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/accounts/bulkAccountLoader.js +2 -2
- package/lib/accounts/bulkUserSubscription.js +39 -1
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +3 -3
- package/lib/accounts/pollingTokenAccountSubscriber.js +2 -2
- package/lib/accounts/pollingUserAccountSubscriber.d.ts +2 -2
- package/lib/accounts/pollingUserAccountSubscriber.js +39 -18
- package/lib/accounts/types.d.ts +5 -0
- package/lib/accounts/webSocketAccountSubscriber.js +2 -2
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +1 -1
- package/lib/accounts/webSocketUserAccountSubscriber.js +2 -2
- package/lib/admin.js +7 -7
- package/lib/clearingHouse.js +21 -22
- package/lib/clearingHouseUser.js +21 -21
- package/lib/examples/makeTradeExample.js +6 -6
- package/lib/idl/clearing_house.json +68 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/lib/math/amm.js +12 -12
- package/lib/math/conversion.js +1 -1
- package/lib/math/funding.js +1 -1
- package/lib/math/market.js +2 -2
- package/lib/math/orders.d.ts +1 -0
- package/lib/math/orders.js +26 -4
- package/lib/math/position.js +4 -3
- package/lib/math/trade.js +21 -17
- package/lib/orders.d.ts +3 -1
- package/lib/orders.js +47 -19
- package/lib/pythClient.js +1 -1
- package/lib/tx/retryTxSender.d.ts +19 -0
- package/lib/tx/retryTxSender.js +153 -0
- package/lib/tx/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/accounts/bulkUserSubscription.ts +51 -1
- package/src/accounts/pollingUserAccountSubscriber.ts +49 -30
- package/src/accounts/types.ts +6 -0
- package/src/clearingHouse.ts +3 -3
- package/src/idl/clearing_house.json +68 -0
- package/src/index.ts +2 -0
- package/src/math/orders.ts +33 -0
- package/src/math/position.ts +3 -2
- package/src/math/trade.ts +8 -3
- package/src/orders.ts +56 -3
- package/src/tx/retryTxSender.ts +196 -0
- package/src/tx/types.ts +3 -0
package/src/math/orders.ts
CHANGED
|
@@ -42,3 +42,36 @@ export function isOrderRiskIncreasing(
|
|
|
42
42
|
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
export function isOrderRiskIncreasingInSameDirection(
|
|
47
|
+
user: ClearingHouseUser,
|
|
48
|
+
order: Order
|
|
49
|
+
): boolean {
|
|
50
|
+
if (isVariant(order.status, 'init')) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const position =
|
|
55
|
+
user.getUserPosition(order.marketIndex) ||
|
|
56
|
+
user.getEmptyPosition(order.marketIndex);
|
|
57
|
+
|
|
58
|
+
// if no position exists, it's risk increasing
|
|
59
|
+
if (position.baseAssetAmount.eq(ZERO)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// if position is long and order is long
|
|
64
|
+
if (position.baseAssetAmount.gt(ZERO) && isVariant(order.direction, 'long')) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// if position is short and order is short
|
|
69
|
+
if (
|
|
70
|
+
position.baseAssetAmount.lt(ZERO) &&
|
|
71
|
+
isVariant(order.direction, 'short')
|
|
72
|
+
) {
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
}
|
package/src/math/position.ts
CHANGED
|
@@ -47,7 +47,8 @@ export function calculateBaseAssetValue(
|
|
|
47
47
|
return newQuoteAssetReserve
|
|
48
48
|
.sub(market.amm.quoteAssetReserve)
|
|
49
49
|
.mul(market.amm.pegMultiplier)
|
|
50
|
-
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
|
|
50
|
+
.div(AMM_TIMES_PEG_TO_QUOTE_PRECISION_RATIO)
|
|
51
|
+
.add(ONE);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -74,7 +75,7 @@ export function calculatePositionPNL(
|
|
|
74
75
|
if (marketPosition.baseAssetAmount.gt(ZERO)) {
|
|
75
76
|
pnl = baseAssetValue.sub(marketPosition.quoteAssetAmount);
|
|
76
77
|
} else {
|
|
77
|
-
pnl = marketPosition.quoteAssetAmount.sub(baseAssetValue)
|
|
78
|
+
pnl = marketPosition.quoteAssetAmount.sub(baseAssetValue);
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (withFunding) {
|
package/src/math/trade.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Market, PositionDirection } from '../types';
|
|
1
|
+
import { isVariant, Market, 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
|
+
ONE,
|
|
9
10
|
} from '../constants/numericConstants';
|
|
10
11
|
import { calculateMarkPrice } from './market';
|
|
11
12
|
import {
|
|
@@ -114,16 +115,20 @@ export function calculateTradeAcquiredAmounts(
|
|
|
114
115
|
return [ZERO, ZERO];
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
const swapDirection = getSwapDirection(inputAssetType, direction);
|
|
117
119
|
const [newQuoteAssetReserve, newBaseAssetReserve] =
|
|
118
120
|
calculateAmmReservesAfterSwap(
|
|
119
121
|
market.amm,
|
|
120
122
|
inputAssetType,
|
|
121
123
|
amount,
|
|
122
|
-
|
|
124
|
+
swapDirection
|
|
123
125
|
);
|
|
124
126
|
|
|
125
127
|
const acquiredBase = market.amm.baseAssetReserve.sub(newBaseAssetReserve);
|
|
126
|
-
|
|
128
|
+
let acquiredQuote = market.amm.quoteAssetReserve.sub(newQuoteAssetReserve);
|
|
129
|
+
if (inputAssetType === 'base' && isVariant(swapDirection, 'remove')) {
|
|
130
|
+
acquiredQuote = acquiredQuote.sub(ONE);
|
|
131
|
+
}
|
|
127
132
|
|
|
128
133
|
return [acquiredBase, acquiredQuote];
|
|
129
134
|
}
|
package/src/orders.ts
CHANGED
|
@@ -3,16 +3,25 @@ import {
|
|
|
3
3
|
Market,
|
|
4
4
|
Order,
|
|
5
5
|
PositionDirection,
|
|
6
|
+
SwapDirection,
|
|
6
7
|
UserAccount,
|
|
7
8
|
UserPosition,
|
|
8
9
|
} from './types';
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
BN,
|
|
12
|
+
calculateAmmReservesAfterSwap,
|
|
13
|
+
calculateBaseAssetValue,
|
|
14
|
+
ClearingHouseUser,
|
|
15
|
+
isOrderRiskIncreasingInSameDirection,
|
|
16
|
+
TEN_THOUSAND,
|
|
17
|
+
} from '.';
|
|
10
18
|
import {
|
|
11
19
|
calculateMarkPrice,
|
|
12
20
|
calculateNewMarketAfterTrade,
|
|
13
21
|
} from './math/market';
|
|
14
22
|
import {
|
|
15
23
|
AMM_TO_QUOTE_PRECISION_RATIO,
|
|
24
|
+
TWO,
|
|
16
25
|
PEG_PRECISION,
|
|
17
26
|
ZERO,
|
|
18
27
|
} from './constants/numericConstants';
|
|
@@ -32,7 +41,10 @@ export function calculateNewStateAfterOrder(
|
|
|
32
41
|
return null;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
|
-
const baseAssetAmountToTrade =
|
|
44
|
+
const baseAssetAmountToTrade = calculateBaseAssetAmountMarketCanExecute(
|
|
45
|
+
market,
|
|
46
|
+
order
|
|
47
|
+
);
|
|
36
48
|
if (baseAssetAmountToTrade.lt(market.amm.minimumBaseAssetTradeSize)) {
|
|
37
49
|
return null;
|
|
38
50
|
}
|
|
@@ -157,7 +169,10 @@ function calculateAmountSwapped(
|
|
|
157
169
|
};
|
|
158
170
|
}
|
|
159
171
|
|
|
160
|
-
function
|
|
172
|
+
export function calculateBaseAssetAmountMarketCanExecute(
|
|
173
|
+
market: Market,
|
|
174
|
+
order: Order
|
|
175
|
+
): BN {
|
|
161
176
|
if (isVariant(order.orderType, 'limit')) {
|
|
162
177
|
return calculateAmountToTradeForLimit(market, order);
|
|
163
178
|
} else if (isVariant(order.orderType, 'triggerLimit')) {
|
|
@@ -234,3 +249,41 @@ function isTriggerConditionSatisfied(market: Market, order: Order): boolean {
|
|
|
234
249
|
return markPrice.lt(order.triggerPrice);
|
|
235
250
|
}
|
|
236
251
|
}
|
|
252
|
+
|
|
253
|
+
export function calculateBaseAssetAmountUserCanExecute(
|
|
254
|
+
market: Market,
|
|
255
|
+
order: Order,
|
|
256
|
+
user: ClearingHouseUser
|
|
257
|
+
): BN {
|
|
258
|
+
const maxLeverage = user.getMaxLeverage('Initial');
|
|
259
|
+
const freeCollateral = user.getFreeCollateral();
|
|
260
|
+
let quoteAssetAmount: BN;
|
|
261
|
+
if (isOrderRiskIncreasingInSameDirection(user, order)) {
|
|
262
|
+
quoteAssetAmount = freeCollateral.mul(maxLeverage).div(TEN_THOUSAND);
|
|
263
|
+
} else {
|
|
264
|
+
const position =
|
|
265
|
+
user.getUserPosition(order.marketIndex) ||
|
|
266
|
+
user.getEmptyPosition(order.marketIndex);
|
|
267
|
+
const positionValue = calculateBaseAssetValue(market, position);
|
|
268
|
+
quoteAssetAmount = freeCollateral
|
|
269
|
+
.mul(maxLeverage)
|
|
270
|
+
.div(TEN_THOUSAND)
|
|
271
|
+
.add(positionValue.mul(TWO));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (quoteAssetAmount.lte(ZERO)) {
|
|
275
|
+
return ZERO;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const baseAssetReservesBefore = market.amm.baseAssetReserve;
|
|
279
|
+
const [_, baseAssetReservesAfter] = calculateAmmReservesAfterSwap(
|
|
280
|
+
market.amm,
|
|
281
|
+
'quote',
|
|
282
|
+
quoteAssetAmount,
|
|
283
|
+
isVariant(order.direction, 'long')
|
|
284
|
+
? SwapDirection.ADD
|
|
285
|
+
: SwapDirection.REMOVE
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
return baseAssetReservesBefore.sub(baseAssetReservesAfter).abs();
|
|
289
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { TxSender } from './types';
|
|
2
|
+
import {
|
|
3
|
+
Commitment,
|
|
4
|
+
ConfirmOptions,
|
|
5
|
+
Context,
|
|
6
|
+
RpcResponseAndContext,
|
|
7
|
+
Signer,
|
|
8
|
+
SignatureResult,
|
|
9
|
+
Transaction,
|
|
10
|
+
TransactionSignature,
|
|
11
|
+
} from '@solana/web3.js';
|
|
12
|
+
import { Provider } from '@project-serum/anchor';
|
|
13
|
+
import assert from 'assert';
|
|
14
|
+
import bs58 from 'bs58';
|
|
15
|
+
|
|
16
|
+
const DEFAULT_TIMEOUT = 35000;
|
|
17
|
+
const DEFAULT_RETRY = 8000;
|
|
18
|
+
|
|
19
|
+
type ResolveReference = {
|
|
20
|
+
resolve?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export class RetryTxSender implements TxSender {
|
|
24
|
+
provider: Provider;
|
|
25
|
+
timeout: number;
|
|
26
|
+
retrySleep: number;
|
|
27
|
+
|
|
28
|
+
public constructor(
|
|
29
|
+
provider: Provider,
|
|
30
|
+
timeout?: number,
|
|
31
|
+
retrySleep?: number
|
|
32
|
+
) {
|
|
33
|
+
this.provider = provider;
|
|
34
|
+
this.timeout = timeout ?? DEFAULT_TIMEOUT;
|
|
35
|
+
this.retrySleep = retrySleep ?? DEFAULT_RETRY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async send(
|
|
39
|
+
tx: Transaction,
|
|
40
|
+
additionalSigners?: Array<Signer>,
|
|
41
|
+
opts?: ConfirmOptions
|
|
42
|
+
): Promise<TransactionSignature> {
|
|
43
|
+
if (additionalSigners === undefined) {
|
|
44
|
+
additionalSigners = [];
|
|
45
|
+
}
|
|
46
|
+
if (opts === undefined) {
|
|
47
|
+
opts = this.provider.opts;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await this.prepareTx(tx, additionalSigners, opts);
|
|
51
|
+
|
|
52
|
+
const rawTransaction = tx.serialize();
|
|
53
|
+
const startTime = this.getTimestamp();
|
|
54
|
+
|
|
55
|
+
const txid: TransactionSignature =
|
|
56
|
+
await this.provider.connection.sendRawTransaction(rawTransaction, opts);
|
|
57
|
+
|
|
58
|
+
let done = false;
|
|
59
|
+
const resolveReference: ResolveReference = {
|
|
60
|
+
resolve: undefined,
|
|
61
|
+
};
|
|
62
|
+
const stopWaiting = () => {
|
|
63
|
+
done = true;
|
|
64
|
+
if (resolveReference.resolve) {
|
|
65
|
+
resolveReference.resolve();
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
(async () => {
|
|
70
|
+
while (!done && this.getTimestamp() - startTime < this.timeout) {
|
|
71
|
+
await this.sleep(resolveReference);
|
|
72
|
+
if (!done) {
|
|
73
|
+
this.provider.connection
|
|
74
|
+
.sendRawTransaction(rawTransaction, opts)
|
|
75
|
+
.catch((e) => {
|
|
76
|
+
console.error(e);
|
|
77
|
+
stopWaiting();
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await this.confirmTransaction(txid, opts.commitment);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
console.error(e);
|
|
87
|
+
throw e;
|
|
88
|
+
} finally {
|
|
89
|
+
stopWaiting();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return txid;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async prepareTx(
|
|
96
|
+
tx: Transaction,
|
|
97
|
+
additionalSigners: Array<Signer>,
|
|
98
|
+
opts: ConfirmOptions
|
|
99
|
+
): Promise<Transaction> {
|
|
100
|
+
tx.feePayer = this.provider.wallet.publicKey;
|
|
101
|
+
tx.recentBlockhash = (
|
|
102
|
+
await this.provider.connection.getRecentBlockhash(
|
|
103
|
+
opts.preflightCommitment
|
|
104
|
+
)
|
|
105
|
+
).blockhash;
|
|
106
|
+
|
|
107
|
+
await this.provider.wallet.signTransaction(tx);
|
|
108
|
+
additionalSigners
|
|
109
|
+
.filter((s): s is Signer => s !== undefined)
|
|
110
|
+
.forEach((kp) => {
|
|
111
|
+
tx.partialSign(kp);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return tx;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async confirmTransaction(
|
|
118
|
+
signature: TransactionSignature,
|
|
119
|
+
commitment?: Commitment
|
|
120
|
+
): Promise<RpcResponseAndContext<SignatureResult>> {
|
|
121
|
+
let decodedSignature;
|
|
122
|
+
try {
|
|
123
|
+
decodedSignature = bs58.decode(signature);
|
|
124
|
+
} catch (err) {
|
|
125
|
+
throw new Error('signature must be base58 encoded: ' + signature);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
assert(decodedSignature.length === 64, 'signature has invalid length');
|
|
129
|
+
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
const subscriptionCommitment = commitment || this.provider.opts.commitment;
|
|
132
|
+
|
|
133
|
+
let subscriptionId;
|
|
134
|
+
let response: RpcResponseAndContext<SignatureResult> | null = null;
|
|
135
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
136
|
+
try {
|
|
137
|
+
subscriptionId = this.provider.connection.onSignature(
|
|
138
|
+
signature,
|
|
139
|
+
(result: SignatureResult, context: Context) => {
|
|
140
|
+
subscriptionId = undefined;
|
|
141
|
+
response = {
|
|
142
|
+
context,
|
|
143
|
+
value: result,
|
|
144
|
+
};
|
|
145
|
+
resolve(null);
|
|
146
|
+
},
|
|
147
|
+
subscriptionCommitment
|
|
148
|
+
);
|
|
149
|
+
} catch (err) {
|
|
150
|
+
reject(err);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await this.promiseTimeout(confirmPromise, this.timeout);
|
|
156
|
+
} finally {
|
|
157
|
+
if (subscriptionId) {
|
|
158
|
+
this.provider.connection.removeSignatureListener(subscriptionId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (response === null) {
|
|
163
|
+
const duration = (Date.now() - start) / 1000;
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Transaction was not confirmed in ${duration.toFixed(
|
|
166
|
+
2
|
|
167
|
+
)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
getTimestamp(): number {
|
|
175
|
+
return new Date().getTime();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async sleep(reference: ResolveReference): Promise<void> {
|
|
179
|
+
return new Promise((resolve) => {
|
|
180
|
+
reference.resolve = resolve;
|
|
181
|
+
setTimeout(resolve, this.retrySleep);
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
promiseTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null> {
|
|
186
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
187
|
+
const timeoutPromise: Promise<null> = new Promise((resolve) => {
|
|
188
|
+
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
return Promise.race([promise, timeoutPromise]).then((result: T | null) => {
|
|
192
|
+
clearTimeout(timeoutId);
|
|
193
|
+
return result;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
package/src/tx/types.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Provider } from '@project-serum/anchor';
|
|
1
2
|
import {
|
|
2
3
|
ConfirmOptions,
|
|
3
4
|
Signer,
|
|
@@ -6,6 +7,8 @@ import {
|
|
|
6
7
|
} from '@solana/web3.js';
|
|
7
8
|
|
|
8
9
|
export interface TxSender {
|
|
10
|
+
provider: Provider;
|
|
11
|
+
|
|
9
12
|
send(
|
|
10
13
|
tx: Transaction,
|
|
11
14
|
additionalSigners?: Array<Signer>,
|