@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/lib/orders.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.calculateAmountToTradeForTriggerLimit = exports.calculateAmountToTradeForLimit = exports.calculateNewStateAfterOrder = void 0;
|
|
3
|
+
exports.calculateBaseAssetAmountUserCanExecute = exports.calculateAmountToTradeForTriggerLimit = exports.calculateAmountToTradeForLimit = exports.calculateBaseAssetAmountMarketCanExecute = exports.calculateNewStateAfterOrder = void 0;
|
|
4
4
|
const types_1 = require("./types");
|
|
5
|
+
const _1 = require(".");
|
|
5
6
|
const market_1 = require("./math/market");
|
|
6
7
|
const numericConstants_1 = require("./constants/numericConstants");
|
|
7
8
|
const amm_1 = require("./math/amm");
|
|
8
9
|
const position_1 = require("./math/position");
|
|
9
10
|
function calculateNewStateAfterOrder(userAccount, userPosition, market, order) {
|
|
10
|
-
if (types_1.isVariant(order.status, 'init')) {
|
|
11
|
+
if ((0, types_1.isVariant)(order.status, 'init')) {
|
|
11
12
|
return null;
|
|
12
13
|
}
|
|
13
|
-
const baseAssetAmountToTrade =
|
|
14
|
+
const baseAssetAmountToTrade = calculateBaseAssetAmountMarketCanExecute(market, order);
|
|
14
15
|
if (baseAssetAmountToTrade.lt(market.amm.minimumBaseAssetTradeSize)) {
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
const userAccountAfter = Object.assign({}, userAccount);
|
|
18
19
|
const userPositionAfter = Object.assign({}, userPosition);
|
|
19
|
-
const currentPositionDirection = position_1.positionCurrentDirection(userPosition);
|
|
20
|
+
const currentPositionDirection = (0, position_1.positionCurrentDirection)(userPosition);
|
|
20
21
|
const increasePosition = userPosition.baseAssetAmount.eq(numericConstants_1.ZERO) ||
|
|
21
22
|
isSameDirection(order.direction, currentPositionDirection);
|
|
22
23
|
if (increasePosition) {
|
|
23
|
-
const marketAfter = market_1.calculateNewMarketAfterTrade(baseAssetAmountToTrade, order.direction, market);
|
|
24
|
+
const marketAfter = (0, market_1.calculateNewMarketAfterTrade)(baseAssetAmountToTrade, order.direction, market);
|
|
24
25
|
const { quoteAssetAmountSwapped, baseAssetAmountSwapped } = calculateAmountSwapped(market, marketAfter);
|
|
25
26
|
userPositionAfter.baseAssetAmount = userPositionAfter.baseAssetAmount.add(baseAssetAmountSwapped);
|
|
26
27
|
userPositionAfter.quoteAssetAmount = userPositionAfter.quoteAssetAmount.add(quoteAssetAmountSwapped);
|
|
@@ -29,10 +30,10 @@ function calculateNewStateAfterOrder(userAccount, userPosition, market, order) {
|
|
|
29
30
|
else {
|
|
30
31
|
const reversePosition = baseAssetAmountToTrade.gt(userPosition.baseAssetAmount.abs());
|
|
31
32
|
if (reversePosition) {
|
|
32
|
-
const intermediateMarket = market_1.calculateNewMarketAfterTrade(userPosition.baseAssetAmount, position_1.findDirectionToClose(userPosition), market);
|
|
33
|
+
const intermediateMarket = (0, market_1.calculateNewMarketAfterTrade)(userPosition.baseAssetAmount, (0, position_1.findDirectionToClose)(userPosition), market);
|
|
33
34
|
const { quoteAssetAmountSwapped: baseAssetValue } = calculateAmountSwapped(market, intermediateMarket);
|
|
34
35
|
let pnl;
|
|
35
|
-
if (types_1.isVariant(currentPositionDirection, 'long')) {
|
|
36
|
+
if ((0, types_1.isVariant)(currentPositionDirection, 'long')) {
|
|
36
37
|
pnl = baseAssetValue.sub(userPosition.quoteAssetAmount);
|
|
37
38
|
}
|
|
38
39
|
else {
|
|
@@ -40,20 +41,20 @@ function calculateNewStateAfterOrder(userAccount, userPosition, market, order) {
|
|
|
40
41
|
}
|
|
41
42
|
userAccountAfter.collateral = userAccountAfter.collateral.add(pnl);
|
|
42
43
|
const baseAssetAmountLeft = baseAssetAmountToTrade.sub(userPosition.baseAssetAmount.abs());
|
|
43
|
-
const marketAfter = market_1.calculateNewMarketAfterTrade(baseAssetAmountLeft, order.direction, intermediateMarket);
|
|
44
|
+
const marketAfter = (0, market_1.calculateNewMarketAfterTrade)(baseAssetAmountLeft, order.direction, intermediateMarket);
|
|
44
45
|
const { quoteAssetAmountSwapped, baseAssetAmountSwapped } = calculateAmountSwapped(intermediateMarket, marketAfter);
|
|
45
46
|
userPositionAfter.quoteAssetAmount = quoteAssetAmountSwapped;
|
|
46
47
|
userPositionAfter.baseAssetAmount = baseAssetAmountSwapped;
|
|
47
48
|
return [userAccountAfter, userPositionAfter, marketAfter];
|
|
48
49
|
}
|
|
49
50
|
else {
|
|
50
|
-
const marketAfter = market_1.calculateNewMarketAfterTrade(baseAssetAmountToTrade, order.direction, market);
|
|
51
|
+
const marketAfter = (0, market_1.calculateNewMarketAfterTrade)(baseAssetAmountToTrade, order.direction, market);
|
|
51
52
|
const { quoteAssetAmountSwapped: baseAssetValue, baseAssetAmountSwapped, } = calculateAmountSwapped(market, marketAfter);
|
|
52
53
|
const costBasisRealized = userPosition.quoteAssetAmount
|
|
53
54
|
.mul(baseAssetAmountSwapped.abs())
|
|
54
55
|
.div(userPosition.baseAssetAmount.abs());
|
|
55
56
|
let pnl;
|
|
56
|
-
if (types_1.isVariant(currentPositionDirection, 'long')) {
|
|
57
|
+
if ((0, types_1.isVariant)(currentPositionDirection, 'long')) {
|
|
57
58
|
pnl = baseAssetValue.sub(costBasisRealized);
|
|
58
59
|
}
|
|
59
60
|
else {
|
|
@@ -79,14 +80,14 @@ function calculateAmountSwapped(marketBefore, marketAfter) {
|
|
|
79
80
|
baseAssetAmountSwapped: marketBefore.amm.baseAssetReserve.sub(marketAfter.amm.baseAssetReserve),
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
function
|
|
83
|
-
if (types_1.isVariant(order.orderType, 'limit')) {
|
|
83
|
+
function calculateBaseAssetAmountMarketCanExecute(market, order) {
|
|
84
|
+
if ((0, types_1.isVariant)(order.orderType, 'limit')) {
|
|
84
85
|
return calculateAmountToTradeForLimit(market, order);
|
|
85
86
|
}
|
|
86
|
-
else if (types_1.isVariant(order.orderType, 'triggerLimit')) {
|
|
87
|
+
else if ((0, types_1.isVariant)(order.orderType, 'triggerLimit')) {
|
|
87
88
|
return calculateAmountToTradeForTriggerLimit(market, order);
|
|
88
89
|
}
|
|
89
|
-
else if (types_1.isVariant(order.orderType, 'market')) {
|
|
90
|
+
else if ((0, types_1.isVariant)(order.orderType, 'market')) {
|
|
90
91
|
// should never be a market order queued
|
|
91
92
|
return numericConstants_1.ZERO;
|
|
92
93
|
}
|
|
@@ -94,8 +95,9 @@ function calculateAmountToTrade(market, order) {
|
|
|
94
95
|
return calculateAmountToTradeForTriggerMarket(market, order);
|
|
95
96
|
}
|
|
96
97
|
}
|
|
98
|
+
exports.calculateBaseAssetAmountMarketCanExecute = calculateBaseAssetAmountMarketCanExecute;
|
|
97
99
|
function calculateAmountToTradeForLimit(market, order) {
|
|
98
|
-
const [maxAmountToTrade, direction] = amm_1.calculateMaxBaseAssetAmountToTrade(market.amm, order.price);
|
|
100
|
+
const [maxAmountToTrade, direction] = (0, amm_1.calculateMaxBaseAssetAmountToTrade)(market.amm, order.price);
|
|
99
101
|
// Check that directions are the same
|
|
100
102
|
const sameDirection = isSameDirection(direction, order.direction);
|
|
101
103
|
if (!sameDirection) {
|
|
@@ -117,8 +119,8 @@ function calculateAmountToTradeForTriggerLimit(market, order) {
|
|
|
117
119
|
}
|
|
118
120
|
exports.calculateAmountToTradeForTriggerLimit = calculateAmountToTradeForTriggerLimit;
|
|
119
121
|
function isSameDirection(firstDirection, secondDirection) {
|
|
120
|
-
return ((types_1.isVariant(firstDirection, 'long') && types_1.isVariant(secondDirection, 'long')) ||
|
|
121
|
-
(types_1.isVariant(firstDirection, 'short') && types_1.isVariant(secondDirection, 'short')));
|
|
122
|
+
return (((0, types_1.isVariant)(firstDirection, 'long') && (0, types_1.isVariant)(secondDirection, 'long')) ||
|
|
123
|
+
((0, types_1.isVariant)(firstDirection, 'short') && (0, types_1.isVariant)(secondDirection, 'short')));
|
|
122
124
|
}
|
|
123
125
|
function calculateAmountToTradeForTriggerMarket(market, order) {
|
|
124
126
|
return isTriggerConditionSatisfied(market, order)
|
|
@@ -126,11 +128,37 @@ function calculateAmountToTradeForTriggerMarket(market, order) {
|
|
|
126
128
|
: numericConstants_1.ZERO;
|
|
127
129
|
}
|
|
128
130
|
function isTriggerConditionSatisfied(market, order) {
|
|
129
|
-
const markPrice = market_1.calculateMarkPrice(market);
|
|
130
|
-
if (types_1.isVariant(order.triggerCondition, 'above')) {
|
|
131
|
+
const markPrice = (0, market_1.calculateMarkPrice)(market);
|
|
132
|
+
if ((0, types_1.isVariant)(order.triggerCondition, 'above')) {
|
|
131
133
|
return markPrice.gt(order.triggerPrice);
|
|
132
134
|
}
|
|
133
135
|
else {
|
|
134
136
|
return markPrice.lt(order.triggerPrice);
|
|
135
137
|
}
|
|
136
138
|
}
|
|
139
|
+
function calculateBaseAssetAmountUserCanExecute(market, order, user) {
|
|
140
|
+
const maxLeverage = user.getMaxLeverage('Initial');
|
|
141
|
+
const freeCollateral = user.getFreeCollateral();
|
|
142
|
+
let quoteAssetAmount;
|
|
143
|
+
if ((0, _1.isOrderRiskIncreasingInSameDirection)(user, order)) {
|
|
144
|
+
quoteAssetAmount = freeCollateral.mul(maxLeverage).div(_1.TEN_THOUSAND);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
const position = user.getUserPosition(order.marketIndex) ||
|
|
148
|
+
user.getEmptyPosition(order.marketIndex);
|
|
149
|
+
const positionValue = (0, _1.calculateBaseAssetValue)(market, position);
|
|
150
|
+
quoteAssetAmount = freeCollateral
|
|
151
|
+
.mul(maxLeverage)
|
|
152
|
+
.div(_1.TEN_THOUSAND)
|
|
153
|
+
.add(positionValue.mul(numericConstants_1.TWO));
|
|
154
|
+
}
|
|
155
|
+
if (quoteAssetAmount.lte(numericConstants_1.ZERO)) {
|
|
156
|
+
return numericConstants_1.ZERO;
|
|
157
|
+
}
|
|
158
|
+
const baseAssetReservesBefore = market.amm.baseAssetReserve;
|
|
159
|
+
const [_, baseAssetReservesAfter] = (0, _1.calculateAmmReservesAfterSwap)(market.amm, 'quote', quoteAssetAmount, (0, types_1.isVariant)(order.direction, 'long')
|
|
160
|
+
? types_1.SwapDirection.ADD
|
|
161
|
+
: types_1.SwapDirection.REMOVE);
|
|
162
|
+
return baseAssetReservesBefore.sub(baseAssetReservesAfter).abs();
|
|
163
|
+
}
|
|
164
|
+
exports.calculateBaseAssetAmountUserCanExecute = calculateBaseAssetAmountUserCanExecute;
|
package/lib/pythClient.js
CHANGED
|
@@ -18,7 +18,7 @@ class PythClient {
|
|
|
18
18
|
getPriceData(pricePublicKey) {
|
|
19
19
|
return __awaiter(this, void 0, void 0, function* () {
|
|
20
20
|
const account = yield this.connection.getAccountInfo(pricePublicKey);
|
|
21
|
-
return client_1.parsePriceData(account.data);
|
|
21
|
+
return (0, client_1.parsePriceData)(account.data);
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TxSender } from './types';
|
|
2
|
+
import { Commitment, ConfirmOptions, RpcResponseAndContext, Signer, SignatureResult, Transaction, TransactionSignature } from '@solana/web3.js';
|
|
3
|
+
import { Provider } from '@project-serum/anchor';
|
|
4
|
+
declare type ResolveReference = {
|
|
5
|
+
resolve?: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare class RetryTxSender implements TxSender {
|
|
8
|
+
provider: Provider;
|
|
9
|
+
timeout: number;
|
|
10
|
+
retrySleep: number;
|
|
11
|
+
constructor(provider: Provider, timeout?: number, retrySleep?: number);
|
|
12
|
+
send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<TransactionSignature>;
|
|
13
|
+
prepareTx(tx: Transaction, additionalSigners: Array<Signer>, opts: ConfirmOptions): Promise<Transaction>;
|
|
14
|
+
confirmTransaction(signature: TransactionSignature, commitment?: Commitment): Promise<RpcResponseAndContext<SignatureResult>>;
|
|
15
|
+
getTimestamp(): number;
|
|
16
|
+
sleep(reference: ResolveReference): Promise<void>;
|
|
17
|
+
promiseTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T | null>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.RetryTxSender = void 0;
|
|
16
|
+
const assert_1 = __importDefault(require("assert"));
|
|
17
|
+
const bs58_1 = __importDefault(require("bs58"));
|
|
18
|
+
const DEFAULT_TIMEOUT = 35000;
|
|
19
|
+
const DEFAULT_RETRY = 8000;
|
|
20
|
+
class RetryTxSender {
|
|
21
|
+
constructor(provider, timeout, retrySleep) {
|
|
22
|
+
this.provider = provider;
|
|
23
|
+
this.timeout = timeout !== null && timeout !== void 0 ? timeout : DEFAULT_TIMEOUT;
|
|
24
|
+
this.retrySleep = retrySleep !== null && retrySleep !== void 0 ? retrySleep : DEFAULT_RETRY;
|
|
25
|
+
}
|
|
26
|
+
send(tx, additionalSigners, opts) {
|
|
27
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
28
|
+
if (additionalSigners === undefined) {
|
|
29
|
+
additionalSigners = [];
|
|
30
|
+
}
|
|
31
|
+
if (opts === undefined) {
|
|
32
|
+
opts = this.provider.opts;
|
|
33
|
+
}
|
|
34
|
+
yield this.prepareTx(tx, additionalSigners, opts);
|
|
35
|
+
const rawTransaction = tx.serialize();
|
|
36
|
+
const startTime = this.getTimestamp();
|
|
37
|
+
const txid = yield this.provider.connection.sendRawTransaction(rawTransaction, opts);
|
|
38
|
+
let done = false;
|
|
39
|
+
const resolveReference = {
|
|
40
|
+
resolve: undefined,
|
|
41
|
+
};
|
|
42
|
+
const stopWaiting = () => {
|
|
43
|
+
done = true;
|
|
44
|
+
if (resolveReference.resolve) {
|
|
45
|
+
resolveReference.resolve();
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
(() => __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
while (!done && this.getTimestamp() - startTime < this.timeout) {
|
|
50
|
+
yield this.sleep(resolveReference);
|
|
51
|
+
if (!done) {
|
|
52
|
+
this.provider.connection
|
|
53
|
+
.sendRawTransaction(rawTransaction, opts)
|
|
54
|
+
.catch((e) => {
|
|
55
|
+
console.error(e);
|
|
56
|
+
stopWaiting();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}))();
|
|
61
|
+
try {
|
|
62
|
+
yield this.confirmTransaction(txid, opts.commitment);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
console.error(e);
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
stopWaiting();
|
|
70
|
+
}
|
|
71
|
+
return txid;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
prepareTx(tx, additionalSigners, opts) {
|
|
75
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
76
|
+
tx.feePayer = this.provider.wallet.publicKey;
|
|
77
|
+
tx.recentBlockhash = (yield this.provider.connection.getRecentBlockhash(opts.preflightCommitment)).blockhash;
|
|
78
|
+
yield this.provider.wallet.signTransaction(tx);
|
|
79
|
+
additionalSigners
|
|
80
|
+
.filter((s) => s !== undefined)
|
|
81
|
+
.forEach((kp) => {
|
|
82
|
+
tx.partialSign(kp);
|
|
83
|
+
});
|
|
84
|
+
return tx;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
confirmTransaction(signature, commitment) {
|
|
88
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
89
|
+
let decodedSignature;
|
|
90
|
+
try {
|
|
91
|
+
decodedSignature = bs58_1.default.decode(signature);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw new Error('signature must be base58 encoded: ' + signature);
|
|
95
|
+
}
|
|
96
|
+
(0, assert_1.default)(decodedSignature.length === 64, 'signature has invalid length');
|
|
97
|
+
const start = Date.now();
|
|
98
|
+
const subscriptionCommitment = commitment || this.provider.opts.commitment;
|
|
99
|
+
let subscriptionId;
|
|
100
|
+
let response = null;
|
|
101
|
+
const confirmPromise = new Promise((resolve, reject) => {
|
|
102
|
+
try {
|
|
103
|
+
subscriptionId = this.provider.connection.onSignature(signature, (result, context) => {
|
|
104
|
+
subscriptionId = undefined;
|
|
105
|
+
response = {
|
|
106
|
+
context,
|
|
107
|
+
value: result,
|
|
108
|
+
};
|
|
109
|
+
resolve(null);
|
|
110
|
+
}, subscriptionCommitment);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
reject(err);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
yield this.promiseTimeout(confirmPromise, this.timeout);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
if (subscriptionId) {
|
|
121
|
+
this.provider.connection.removeSignatureListener(subscriptionId);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (response === null) {
|
|
125
|
+
const duration = (Date.now() - start) / 1000;
|
|
126
|
+
throw new Error(`Transaction was not confirmed in ${duration.toFixed(2)} seconds. It is unknown if it succeeded or failed. Check signature ${signature} using the Solana Explorer or CLI tools.`);
|
|
127
|
+
}
|
|
128
|
+
return response;
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
getTimestamp() {
|
|
132
|
+
return new Date().getTime();
|
|
133
|
+
}
|
|
134
|
+
sleep(reference) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
reference.resolve = resolve;
|
|
138
|
+
setTimeout(resolve, this.retrySleep);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
promiseTimeout(promise, timeoutMs) {
|
|
143
|
+
let timeoutId;
|
|
144
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
145
|
+
timeoutId = setTimeout(() => resolve(null), timeoutMs);
|
|
146
|
+
});
|
|
147
|
+
return Promise.race([promise, timeoutPromise]).then((result) => {
|
|
148
|
+
clearTimeout(timeoutId);
|
|
149
|
+
return result;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.RetryTxSender = RetryTxSender;
|
package/lib/tx/types.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { Provider } from '@project-serum/anchor';
|
|
1
2
|
import { ConfirmOptions, Signer, Transaction, TransactionSignature } from '@solana/web3.js';
|
|
2
3
|
export interface TxSender {
|
|
4
|
+
provider: Provider;
|
|
3
5
|
send(tx: Transaction, additionalSigners?: Array<Signer>, opts?: ConfirmOptions): Promise<TransactionSignature>;
|
|
4
6
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ClearingHouseUser } from '../clearingHouseUser';
|
|
2
2
|
import { BulkAccountLoader } from './bulkAccountLoader';
|
|
3
3
|
import { PollingUserAccountSubscriber } from './pollingUserAccountSubscriber';
|
|
4
|
+
import { UserAccount, UserOrdersAccount } from '../types';
|
|
5
|
+
import { UserPublicKeys } from './types';
|
|
6
|
+
import { ProgramAccount } from '@project-serum/anchor';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @param users
|
|
@@ -10,11 +13,58 @@ export async function bulkPollingUserSubscribe(
|
|
|
10
13
|
users: ClearingHouseUser[],
|
|
11
14
|
accountLoader: BulkAccountLoader
|
|
12
15
|
): Promise<void> {
|
|
16
|
+
if (users.length === 0) {
|
|
17
|
+
await accountLoader.load();
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Fetch all the accounts first
|
|
22
|
+
const program = users[0].clearingHouse.program;
|
|
23
|
+
let userProgramAccounts: ProgramAccount[];
|
|
24
|
+
let orderProgramAccounts: ProgramAccount[];
|
|
25
|
+
await Promise.all([
|
|
26
|
+
(async () => {
|
|
27
|
+
userProgramAccounts = await program.account.user.all();
|
|
28
|
+
})(),
|
|
29
|
+
(async () => {
|
|
30
|
+
orderProgramAccounts = await program.account.userOrders.all();
|
|
31
|
+
})(),
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
// Create a map of the authority to keys
|
|
35
|
+
const authorityToKeys = new Map<string, UserPublicKeys>();
|
|
36
|
+
const userToAuthority = new Map<string, string>();
|
|
37
|
+
for (const userProgramAccount of userProgramAccounts) {
|
|
38
|
+
const userAccountPublicKey = userProgramAccount.publicKey;
|
|
39
|
+
const userAccount = userProgramAccount.account as UserAccount;
|
|
40
|
+
|
|
41
|
+
authorityToKeys.set(userAccount.authority.toString(), {
|
|
42
|
+
user: userAccountPublicKey,
|
|
43
|
+
userPositions: userAccount.positions,
|
|
44
|
+
userOrders: undefined,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
userToAuthority.set(
|
|
48
|
+
userAccountPublicKey.toString(),
|
|
49
|
+
userAccount.authority.toString()
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
for (const orderProgramAccount of orderProgramAccounts) {
|
|
53
|
+
const userOrderAccountPublicKey = orderProgramAccount.publicKey;
|
|
54
|
+
const userOrderAccount = orderProgramAccount.account as UserOrdersAccount;
|
|
55
|
+
|
|
56
|
+
const authority = userToAuthority.get(userOrderAccount.user.toString());
|
|
57
|
+
const userPublicKeys = authorityToKeys.get(authority);
|
|
58
|
+
userPublicKeys.userOrders = userOrderAccountPublicKey;
|
|
59
|
+
}
|
|
60
|
+
|
|
13
61
|
await Promise.all(
|
|
14
62
|
users.map((user) => {
|
|
63
|
+
// Pull the keys from the authority map so we can skip fetching them in addToAccountLoader
|
|
64
|
+
const userPublicKeys = authorityToKeys.get(user.authority.toString());
|
|
15
65
|
return (
|
|
16
66
|
user.accountSubscriber as PollingUserAccountSubscriber
|
|
17
|
-
).addToAccountLoader();
|
|
67
|
+
).addToAccountLoader(userPublicKeys);
|
|
18
68
|
})
|
|
19
69
|
);
|
|
20
70
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
NotSubscribedError,
|
|
4
4
|
UserAccountEvents,
|
|
5
5
|
UserAccountSubscriber,
|
|
6
|
+
UserPublicKeys,
|
|
6
7
|
} from './types';
|
|
7
8
|
import { Program } from '@project-serum/anchor';
|
|
8
9
|
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
@@ -58,55 +59,73 @@ export class PollingUserAccountSubscriber implements UserAccountSubscriber {
|
|
|
58
59
|
return true;
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
async addToAccountLoader(): Promise<void> {
|
|
62
|
+
async addToAccountLoader(userPublicKeys?: UserPublicKeys): Promise<void> {
|
|
62
63
|
if (this.accountsToPoll.size > 0) {
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
if (!userPublicKeys) {
|
|
68
|
+
const userPublicKey = await getUserAccountPublicKey(
|
|
69
|
+
this.program.programId,
|
|
70
|
+
this.authority
|
|
71
|
+
);
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
const userAccount = (await this.program.account.user.fetch(
|
|
74
|
+
userPublicKey
|
|
75
|
+
)) as UserAccount;
|
|
74
76
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
this.accountsToPoll.set(userPublicKey.toString(), {
|
|
78
|
+
key: 'user',
|
|
79
|
+
publicKey: userPublicKey,
|
|
80
|
+
eventType: 'userAccountData',
|
|
81
|
+
});
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
this.accountsToPoll.set(userAccount.positions.toString(), {
|
|
84
|
+
key: 'userPositions',
|
|
85
|
+
publicKey: userAccount.positions,
|
|
86
|
+
eventType: 'userPositionsData',
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const userOrdersPublicKey = await getUserOrdersAccountPublicKey(
|
|
90
|
+
this.program.programId,
|
|
91
|
+
userPublicKey
|
|
92
|
+
);
|
|
86
93
|
|
|
87
|
-
const userOrdersPublicKey = await getUserOrdersAccountPublicKey(
|
|
88
|
-
this.program.programId,
|
|
89
|
-
userPublicKey
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const userOrdersExist =
|
|
93
|
-
(
|
|
94
|
-
await this.program.provider.connection.getParsedAccountInfo(
|
|
95
|
-
userOrdersPublicKey
|
|
96
|
-
)
|
|
97
|
-
).value !== null;
|
|
98
|
-
if (userOrdersExist) {
|
|
99
94
|
this.accountsToPoll.set(userOrdersPublicKey.toString(), {
|
|
100
95
|
key: 'userOrders',
|
|
101
96
|
publicKey: userOrdersPublicKey,
|
|
102
97
|
eventType: 'userOrdersData',
|
|
103
98
|
});
|
|
99
|
+
} else {
|
|
100
|
+
this.accountsToPoll.set(userPublicKeys.user.toString(), {
|
|
101
|
+
key: 'user',
|
|
102
|
+
publicKey: userPublicKeys.user,
|
|
103
|
+
eventType: 'userAccountData',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.accountsToPoll.set(userPublicKeys.userPositions.toString(), {
|
|
107
|
+
key: 'userPositions',
|
|
108
|
+
publicKey: userPublicKeys.userPositions,
|
|
109
|
+
eventType: 'userPositionsData',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (userPublicKeys.userOrders) {
|
|
113
|
+
this.accountsToPoll.set(userPublicKeys.userOrders.toString(), {
|
|
114
|
+
key: 'userOrders',
|
|
115
|
+
publicKey: userPublicKeys.userOrders,
|
|
116
|
+
eventType: 'userOrdersData',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
for (const [_, accountToPoll] of this.accountsToPoll) {
|
|
107
122
|
accountToPoll.callbackId = this.accountLoader.addAccount(
|
|
108
123
|
accountToPoll.publicKey,
|
|
109
124
|
(buffer) => {
|
|
125
|
+
if (!buffer) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
110
129
|
const account = this.program.account[
|
|
111
130
|
accountToPoll.key
|
|
112
131
|
].coder.accounts.decode(capitalize(accountToPoll.key), buffer);
|
package/src/accounts/types.ts
CHANGED
|
@@ -82,6 +82,12 @@ export interface ClearingHouseAccountSubscriber {
|
|
|
82
82
|
type: ClearingHouseConfigType;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export type UserPublicKeys = {
|
|
86
|
+
user: PublicKey;
|
|
87
|
+
userPositions: PublicKey;
|
|
88
|
+
userOrders: PublicKey | undefined;
|
|
89
|
+
};
|
|
90
|
+
|
|
85
91
|
export interface UserAccountEvents {
|
|
86
92
|
userAccountData: (payload: UserAccount) => void;
|
|
87
93
|
userPositionsData: (payload: UserPositionsAccount) => void;
|
package/src/clearingHouse.ts
CHANGED
|
@@ -52,7 +52,6 @@ import {
|
|
|
52
52
|
ClearingHouseAccountTypes,
|
|
53
53
|
} from './accounts/types';
|
|
54
54
|
import { TxSender } from './tx/types';
|
|
55
|
-
import { DefaultTxSender } from './tx/defaultTxSender';
|
|
56
55
|
import { wrapInTx } from './tx/utils';
|
|
57
56
|
import {
|
|
58
57
|
getClearingHouse,
|
|
@@ -250,12 +249,13 @@ export class ClearingHouse {
|
|
|
250
249
|
this.program.programId,
|
|
251
250
|
newProvider
|
|
252
251
|
);
|
|
253
|
-
|
|
252
|
+
|
|
253
|
+
// Update provider for txSender with new wallet details
|
|
254
|
+
this.txSender.provider = newProvider;
|
|
254
255
|
|
|
255
256
|
this.wallet = newWallet;
|
|
256
257
|
this.provider = newProvider;
|
|
257
258
|
this.program = newProgram;
|
|
258
|
-
this.txSender = newTxSender;
|
|
259
259
|
this.userAccountPublicKey = undefined;
|
|
260
260
|
this.userAccount = undefined;
|
|
261
261
|
}
|
|
@@ -1321,6 +1321,52 @@
|
|
|
1321
1321
|
}
|
|
1322
1322
|
]
|
|
1323
1323
|
},
|
|
1324
|
+
{
|
|
1325
|
+
"name": "initializeUserOrdersWithExplicitPayer",
|
|
1326
|
+
"accounts": [
|
|
1327
|
+
{
|
|
1328
|
+
"name": "user",
|
|
1329
|
+
"isMut": false,
|
|
1330
|
+
"isSigner": false
|
|
1331
|
+
},
|
|
1332
|
+
{
|
|
1333
|
+
"name": "userOrders",
|
|
1334
|
+
"isMut": true,
|
|
1335
|
+
"isSigner": false
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
"name": "state",
|
|
1339
|
+
"isMut": false,
|
|
1340
|
+
"isSigner": false
|
|
1341
|
+
},
|
|
1342
|
+
{
|
|
1343
|
+
"name": "authority",
|
|
1344
|
+
"isMut": false,
|
|
1345
|
+
"isSigner": true
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
"name": "payer",
|
|
1349
|
+
"isMut": true,
|
|
1350
|
+
"isSigner": true
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"name": "rent",
|
|
1354
|
+
"isMut": false,
|
|
1355
|
+
"isSigner": false
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
"name": "systemProgram",
|
|
1359
|
+
"isMut": false,
|
|
1360
|
+
"isSigner": false
|
|
1361
|
+
}
|
|
1362
|
+
],
|
|
1363
|
+
"args": [
|
|
1364
|
+
{
|
|
1365
|
+
"name": "userOrdersNonce",
|
|
1366
|
+
"type": "u8"
|
|
1367
|
+
}
|
|
1368
|
+
]
|
|
1369
|
+
},
|
|
1324
1370
|
{
|
|
1325
1371
|
"name": "deleteUser",
|
|
1326
1372
|
"accounts": [
|
|
@@ -3667,6 +3713,23 @@
|
|
|
3667
3713
|
]
|
|
3668
3714
|
}
|
|
3669
3715
|
},
|
|
3716
|
+
{
|
|
3717
|
+
"name": "LiquidationType",
|
|
3718
|
+
"type": {
|
|
3719
|
+
"kind": "enum",
|
|
3720
|
+
"variants": [
|
|
3721
|
+
{
|
|
3722
|
+
"name": "NONE"
|
|
3723
|
+
},
|
|
3724
|
+
{
|
|
3725
|
+
"name": "PARTIAL"
|
|
3726
|
+
},
|
|
3727
|
+
{
|
|
3728
|
+
"name": "FULL"
|
|
3729
|
+
}
|
|
3730
|
+
]
|
|
3731
|
+
}
|
|
3732
|
+
},
|
|
3670
3733
|
{
|
|
3671
3734
|
"name": "OracleSource",
|
|
3672
3735
|
"type": {
|
|
@@ -4064,6 +4127,11 @@
|
|
|
4064
4127
|
"code": 6055,
|
|
4065
4128
|
"name": "UserOrderIdAlreadyInUse",
|
|
4066
4129
|
"msg": "User Order Id Already In Use"
|
|
4130
|
+
},
|
|
4131
|
+
{
|
|
4132
|
+
"code": 6056,
|
|
4133
|
+
"name": "NoPositionsLiquidatable",
|
|
4134
|
+
"msg": "No positions liquidatable"
|
|
4067
4135
|
}
|
|
4068
4136
|
]
|
|
4069
4137
|
}
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './types';
|
|
|
7
7
|
export * from './constants/markets';
|
|
8
8
|
export * from './accounts/webSocketClearingHouseAccountSubscriber';
|
|
9
9
|
export * from './accounts/bulkAccountLoader';
|
|
10
|
+
export * from './accounts/bulkUserSubscription';
|
|
10
11
|
export * from './accounts/pollingClearingHouseAccountSubscriber';
|
|
11
12
|
export * from './accounts/pollingTokenAccountSubscriber';
|
|
12
13
|
export * from './accounts/types';
|
|
@@ -31,6 +32,7 @@ export * from './types';
|
|
|
31
32
|
export * from './math/utils';
|
|
32
33
|
export * from './config';
|
|
33
34
|
export * from './constants/numericConstants';
|
|
35
|
+
export * from './tx/retryTxSender';
|
|
34
36
|
export * from './util/computeUnits';
|
|
35
37
|
export * from './util/tps';
|
|
36
38
|
|