@drift-labs/sdk 2.28.0-beta.4 → 2.28.0-beta.5

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.
@@ -9,10 +9,14 @@ import { calculateAssetWeight, calculateLiabilityWeight } from './spotBalance';
9
9
  import { MARGIN_PRECISION } from '../constants/numericConstants';
10
10
 
11
11
  export function castNumberToSpotPrecision(
12
- value: number,
12
+ value: number | BN,
13
13
  spotMarket: SpotMarketAccount
14
14
  ): BN {
15
- return new BN(value * Math.pow(10, spotMarket.decimals));
15
+ if (typeof value === 'number') {
16
+ return new BN(value * Math.pow(10, spotMarket.decimals));
17
+ } else {
18
+ return value.mul(new BN(Math.pow(10, spotMarket.decimals)));
19
+ }
16
20
  }
17
21
 
18
22
  export function calculateSpotMarketMarginRatio(
package/src/math/utils.ts CHANGED
@@ -33,3 +33,49 @@ export const divCeil = (a: BN, b: BN): BN => {
33
33
  return quotient;
34
34
  }
35
35
  };
36
+
37
+ /**
38
+ * calculates the time remaining until the next update based on a rounded, "on-the-hour" update schedule
39
+ * this schedule is used for Perpetual Funding Rate and Revenue -> Insurance Updates
40
+ * @param now: current blockchain unix timestamp
41
+ * @param lastUpdateTs: the unix timestamp of the last update
42
+ * @param updatePeriod: desired interval between updates (in seconds)
43
+ * @returns: timeRemainingUntilUpdate (in seconds)
44
+ */
45
+ export function timeRemainingUntilUpdate(
46
+ now: BN,
47
+ lastUpdateTs: BN,
48
+ updatePeriod: BN
49
+ ): BN {
50
+ const timeSinceLastUpdate = now.sub(lastUpdateTs);
51
+
52
+ // round next update time to be available on the hour
53
+ let nextUpdateWait = updatePeriod;
54
+ if (updatePeriod.gt(new BN(1))) {
55
+ const lastUpdateDelay = lastUpdateTs.umod(updatePeriod);
56
+ if (!lastUpdateDelay.isZero()) {
57
+ const maxDelayForNextPeriod = updatePeriod.div(new BN(3));
58
+
59
+ const twoFundingPeriods = updatePeriod.mul(new BN(2));
60
+
61
+ if (lastUpdateDelay.gt(maxDelayForNextPeriod)) {
62
+ // too late for on the hour next period, delay to following period
63
+ nextUpdateWait = twoFundingPeriods.sub(lastUpdateDelay);
64
+ } else {
65
+ // allow update on the hour
66
+ nextUpdateWait = updatePeriod.sub(lastUpdateDelay);
67
+ }
68
+
69
+ if (nextUpdateWait.gt(twoFundingPeriods)) {
70
+ nextUpdateWait = nextUpdateWait.sub(updatePeriod);
71
+ }
72
+ }
73
+ }
74
+ const timeRemainingUntilUpdate = nextUpdateWait
75
+ .sub(timeSinceLastUpdate)
76
+ .isNeg()
77
+ ? ZERO
78
+ : nextUpdateWait.sub(timeSinceLastUpdate);
79
+
80
+ return timeRemainingUntilUpdate;
81
+ }
@@ -163,8 +163,10 @@ export class PhoenixSubscriber implements L2OrderBookGenerator {
163
163
  }
164
164
 
165
165
  *getL2Levels(side: 'bids' | 'asks'): Generator<L2Level> {
166
- // @ts-ignore
167
- const basePrecision = Math.pow(10, this.market.header.baseParams.decimals);
166
+ const basePrecision = Math.pow(
167
+ 10,
168
+ this.market.data.header.baseParams.decimals
169
+ );
168
170
  const pricePrecision = PRICE_PRECISION.toNumber();
169
171
 
170
172
  const ladder = getMarketUiLadder(
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTokenAccount = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ function parseTokenAccount(data) {
7
+ const accountInfo = spl_token_1.AccountLayout.decode(data);
8
+ accountInfo.mint = new web3_js_1.PublicKey(accountInfo.mint);
9
+ accountInfo.owner = new web3_js_1.PublicKey(accountInfo.owner);
10
+ accountInfo.amount = spl_token_1.u64.fromBuffer(accountInfo.amount);
11
+ if (accountInfo.delegateOption === 0) {
12
+ accountInfo.delegate = null;
13
+ // eslint-disable-next-line new-cap
14
+ accountInfo.delegatedAmount = new spl_token_1.u64(0);
15
+ }
16
+ else {
17
+ accountInfo.delegate = new web3_js_1.PublicKey(accountInfo.delegate);
18
+ accountInfo.delegatedAmount = spl_token_1.u64.fromBuffer(accountInfo.delegatedAmount);
19
+ }
20
+ accountInfo.isInitialized = accountInfo.state !== 0;
21
+ accountInfo.isFrozen = accountInfo.state === 2;
22
+ if (accountInfo.isNativeOption === 1) {
23
+ accountInfo.rentExemptReserve = spl_token_1.u64.fromBuffer(accountInfo.isNative);
24
+ accountInfo.isNative = true;
25
+ }
26
+ else {
27
+ accountInfo.rentExemptReserve = null;
28
+ accountInfo.isNative = false;
29
+ }
30
+ if (accountInfo.closeAuthorityOption === 0) {
31
+ accountInfo.closeAuthority = null;
32
+ }
33
+ else {
34
+ accountInfo.closeAuthority = new web3_js_1.PublicKey(accountInfo.closeAuthority);
35
+ }
36
+ return accountInfo;
37
+ }
38
+ exports.parseTokenAccount = parseTokenAccount;
@@ -115,18 +115,10 @@ export class RetryTxSender implements TxSender {
115
115
  }
116
116
 
117
117
  async sendVersionedTransaction(
118
- ixs: TransactionInstruction[],
119
- lookupTableAccounts: AddressLookupTableAccount[],
118
+ tx: VersionedTransaction,
120
119
  additionalSigners?: Array<Signer>,
121
120
  opts?: ConfirmOptions
122
121
  ): Promise<TxSigAndSlot> {
123
- const tx = await this.getVersionedTransaction(
124
- ixs,
125
- lookupTableAccounts,
126
- additionalSigners,
127
- opts
128
- );
129
-
130
122
  // @ts-ignore
131
123
  tx.sign(additionalSigners.concat(this.provider.wallet.payer));
132
124
 
package/src/tx/types.ts CHANGED
@@ -25,8 +25,7 @@ export interface TxSender {
25
25
  ): Promise<TxSigAndSlot>;
26
26
 
27
27
  sendVersionedTransaction(
28
- ixs: TransactionInstruction[],
29
- lookupTableAccounts: AddressLookupTableAccount[],
28
+ tx: VersionedTransaction,
30
29
  additionalSigners?: Array<Signer>,
31
30
  opts?: ConfirmOptions
32
31
  ): Promise<TxSigAndSlot>;
package/src/types.ts CHANGED
@@ -857,7 +857,6 @@ export type OrderParams = {
857
857
  immediateOrCancel: boolean;
858
858
  triggerPrice: BN | null;
859
859
  triggerCondition: OrderTriggerCondition;
860
- positionLimit: BN;
861
860
  oraclePriceOffset: number | null;
862
861
  auctionDuration: number | null;
863
862
  maxTs: BN | null;
@@ -899,7 +898,6 @@ export const DefaultOrderParams: OrderParams = {
899
898
  immediateOrCancel: false,
900
899
  triggerPrice: null,
901
900
  triggerCondition: OrderTriggerCondition.ABOVE,
902
- positionLimit: ZERO,
903
901
  oraclePriceOffset: null,
904
902
  auctionDuration: null,
905
903
  maxTs: null,
package/src/user.ts CHANGED
@@ -162,6 +162,28 @@ export class User {
162
162
  );
163
163
  }
164
164
 
165
+ /**
166
+ * Returns the token amount for a given market. The spot market precision is based on the token mint decimals.
167
+ * Positive if it is a deposit, negative if it is a borrow.
168
+ *
169
+ * @param marketIndex
170
+ */
171
+ public getTokenAmount(marketIndex: number): BN {
172
+ const spotPosition = this.getSpotPosition(marketIndex);
173
+ if (spotPosition === undefined) {
174
+ return ZERO;
175
+ }
176
+ const spotMarket = this.driftClient.getSpotMarketAccount(marketIndex);
177
+ return getSignedTokenAmount(
178
+ getTokenAmount(
179
+ spotPosition.scaledBalance,
180
+ spotMarket,
181
+ spotPosition.balanceType
182
+ ),
183
+ spotPosition.balanceType
184
+ );
185
+ }
186
+
165
187
  public getEmptyPosition(marketIndex: number): PerpPosition {
166
188
  return {
167
189
  baseAssetAmount: ZERO,
@@ -206,6 +228,12 @@ export class User {
206
228
  );
207
229
  }
208
230
 
231
+ public getOpenOrders(): Order[] {
232
+ return this.getUserAccount()?.orders.filter((order) =>
233
+ isVariant(order.status, 'open')
234
+ );
235
+ }
236
+
209
237
  public getUserAccountPublicKey(): PublicKey {
210
238
  return this.userAccountPublicKey;
211
239
  }
@@ -0,0 +1,27 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.findComputeUnitConsumption = void 0;
13
+ function findComputeUnitConsumption(programId, connection, txSignature, commitment = 'confirmed') {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ const tx = yield connection.getTransaction(txSignature, { commitment });
16
+ const computeUnits = [];
17
+ const regex = new RegExp(`Program ${programId.toString()} consumed ([0-9]{0,6}) of ([0-9]{0,7}) compute units`);
18
+ tx.meta.logMessages.forEach((logMessage) => {
19
+ const match = logMessage.match(regex);
20
+ if (match && match[1]) {
21
+ computeUnits.push(match[1]);
22
+ }
23
+ });
24
+ return computeUnits;
25
+ });
26
+ }
27
+ exports.findComputeUnitConsumption = findComputeUnitConsumption;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getTokenAddress = void 0;
4
+ const spl_token_1 = require("@solana/spl-token");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ const getTokenAddress = (mintAddress, userPubKey) => {
7
+ return spl_token_1.Token.getAssociatedTokenAddress(spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID, spl_token_1.TOKEN_PROGRAM_ID, new web3_js_1.PublicKey(mintAddress), new web3_js_1.PublicKey(userPubKey));
8
+ };
9
+ exports.getTokenAddress = getTokenAddress;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseTimeout = void 0;
4
+ function promiseTimeout(promise, timeoutMs) {
5
+ let timeoutId;
6
+ const timeoutPromise = new Promise((resolve) => {
7
+ timeoutId = setTimeout(() => resolve(null), timeoutMs);
8
+ });
9
+ return Promise.race([promise, timeoutPromise]).then((result) => {
10
+ clearTimeout(timeoutId);
11
+ return result;
12
+ });
13
+ }
14
+ exports.promiseTimeout = promiseTimeout;
@@ -0,0 +1,27 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.estimateTps = void 0;
13
+ function estimateTps(programId, connection, failed) {
14
+ return __awaiter(this, void 0, void 0, function* () {
15
+ let signatures = yield connection.getSignaturesForAddress(programId, undefined, 'finalized');
16
+ if (failed) {
17
+ signatures = signatures.filter((signature) => signature.err);
18
+ }
19
+ const numberOfSignatures = signatures.length;
20
+ if (numberOfSignatures === 0) {
21
+ return 0;
22
+ }
23
+ return (numberOfSignatures /
24
+ (signatures[0].blockTime - signatures[numberOfSignatures - 1].blockTime));
25
+ });
26
+ }
27
+ exports.estimateTps = estimateTps;
@@ -0,0 +1,40 @@
1
+ import { BN, ZERO, timeRemainingUntilUpdate, ONE } from '../../src';
2
+ // import { mockPerpMarkets } from '../dlob/helpers';
3
+
4
+ import { assert } from '../../src/assert/assert';
5
+
6
+ describe('Insurance Tests', () => {
7
+ it('time remaining updates', () => {
8
+ const now = new BN(1683576852);
9
+ const lastUpdate = new BN(1683576000);
10
+ const period = new BN(3600); //hourly
11
+
12
+ let tr;
13
+ // console.log(now.sub(lastUpdate).toString());
14
+
15
+ tr = timeRemainingUntilUpdate(now, lastUpdate, period);
16
+ // console.log(tr.toString());
17
+ assert(tr.eq(new BN('2748')));
18
+
19
+ tr = timeRemainingUntilUpdate(now, lastUpdate.sub(period), period);
20
+ // console.log(tr.toString());
21
+ assert(tr.eq(ZERO));
22
+
23
+ const tooLateUpdate = lastUpdate.sub(period.div(new BN(3)).add(ONE));
24
+ tr = timeRemainingUntilUpdate(
25
+ tooLateUpdate.add(ONE),
26
+ tooLateUpdate,
27
+ period
28
+ );
29
+ // console.log(tr.toString());
30
+ assert(tr.eq(new BN('4800')));
31
+
32
+ tr = timeRemainingUntilUpdate(now, lastUpdate.add(ONE), period);
33
+ // console.log(tr.toString());
34
+ assert(tr.eq(new BN('2748')));
35
+
36
+ tr = timeRemainingUntilUpdate(now, lastUpdate.sub(ONE), period);
37
+ // console.log(tr.toString());
38
+ assert(tr.eq(new BN('2748')));
39
+ });
40
+ });
package/dlob_read.ts DELETED
@@ -1,155 +0,0 @@
1
- import { Connection, Keypair, PublicKey } from '@solana/web3.js';
2
- import {
3
- BASE_PRECISION,
4
- BulkAccountLoader,
5
- configs,
6
- convertToNumber,
7
- DLOBSubscriber,
8
- DriftClient,
9
- getMarketsAndOraclesForSubscription,
10
- MarketType,
11
- PRICE_PRECISION,
12
- SlotSubscriber,
13
- UserMap,
14
- Wallet,
15
- } from './src/index';
16
- import {
17
- DLOBApiClient,
18
- } from './src/dlob/DLOBApiClient';
19
-
20
- async function main() {
21
-
22
- const driftConfig = configs['mainnet-beta'];
23
- const connection = new Connection('https://api.mainnet-beta.solana.com');
24
-
25
- const accountLoader = new BulkAccountLoader(
26
- connection,
27
- 'confirmed',
28
- 10000
29
- );
30
-
31
- const { perpMarketIndexes, spotMarketIndexes, oracleInfos } = getMarketsAndOraclesForSubscription('mainnet-beta');
32
-
33
-
34
- const driftClient = new DriftClient({
35
- connection: connection,
36
- wallet: new Wallet(new Keypair()),
37
- programID: new PublicKey(driftConfig.DRIFT_PROGRAM_ID),
38
- accountSubscription: {
39
- type: 'polling',
40
- accountLoader: accountLoader,
41
- },
42
- perpMarketIndexes,
43
- spotMarketIndexes,
44
- oracleInfos,
45
- userStats: true,
46
- env: 'mainnet-beta',
47
- });
48
- console.log(`driftClientSubscribed: ${await driftClient.subscribe()}`);
49
-
50
- const slotSubscriber = new SlotSubscriber(connection);
51
- await slotSubscriber.subscribe();
52
-
53
-
54
- // Alternatively, you can also use the UserMap class, which loads the DLOB via RPC calls.
55
- // const userMap = new UserMap(driftClient, driftClient.userAccountSubscriptionConfig, false);
56
-
57
- // This loads the DLOB from a server hosted by Drift.
58
- const dlobAPI = new DLOBApiClient({
59
- url: 'https://dlob.drift.trade/orders/idlWithSlot',
60
- });
61
-
62
- const dlobSubscriber = new DLOBSubscriber({
63
- dlobSource: dlobAPI,
64
- slotSource: slotSubscriber,
65
- driftClient: driftClient,
66
- updateFrequency: 10000,
67
- });
68
- await dlobSubscriber.subscribe();
69
-
70
- const l2 = dlobSubscriber.getL2({
71
- marketIndex: 0,
72
- marketType: MarketType.PERP,
73
- });
74
- console.log("Level 2 order book:");
75
-
76
- console.log("Asks:");
77
- const asks = l2.asks.slice().reverse();
78
- for (let i = 0; i < asks.length; i++) {
79
- const ask = asks[i];
80
- console.log(` [${asks.length - i - 1}] ${convertToNumber(ask.size, BASE_PRECISION)} @ $${convertToNumber(ask.price, PRICE_PRECISION)}`);
81
- }
82
-
83
- console.log("Bids:");
84
- const bids = l2.bids;
85
- for (let i = 0; i < bids.length; i++) {
86
- const bid = bids[i];
87
- console.log(` [${i}] ${convertToNumber(bid.size, BASE_PRECISION)} @ $${convertToNumber(bid.price, PRICE_PRECISION)}`);
88
- }
89
- console.log("");
90
- console.log("");
91
-
92
- /**
93
- Level 2 order book:
94
- Asks:
95
- [9] 664.8 @ $21.3248
96
- [8] 3.5 @ $21.288301
97
- [7] 3.5 @ $21.287237
98
- [6] 3.5 @ $21.283129
99
- [5] 0.1 @ $21.2825
100
- [4] 1937.6 @ $21.2748
101
- [3] 1 @ $21.26
102
- [2] 2015 @ $21.2589
103
- [1] 503.7 @ $21.2483
104
- [0] 377.8 @ $21.243
105
- Bids:
106
- [0] 1168.2 @ $21.2464
107
- [1] 136.7 @ $21.2461
108
- [2] 2570.2 @ $21.2426
109
- [3] 0.1 @ $21.2419
110
- [4] 47 @ $21.216188
111
- [5] 327.8 @ $21.211
112
- [6] 437 @ $21.2057
113
- [7] 47 @ $21.205592
114
- [8] 1748.3 @ $21.1951
115
- [9] 187 @ $21.1792
116
- */
117
-
118
- const l3 = dlobSubscriber.getL3({
119
- marketIndex: 0,
120
- marketType: MarketType.PERP,
121
- });
122
-
123
- console.log("Level 3 order book:");
124
- console.log("Asks:");
125
- const l3asks = l3.asks.slice().reverse();
126
- for (let i = 0; i < l3asks.length; i++) {
127
- const ask = l3asks[i];
128
- console.log(` [${l3asks.length - i - 1}] ${ask.maker.toBase58()} ${convertToNumber(ask.size, BASE_PRECISION)} @ $${convertToNumber(ask.price, PRICE_PRECISION)}`);
129
- }
130
-
131
- console.log("Bids:");
132
- const l3bids = l3.bids;
133
- for (let i = 0; i < l3bids.length; i++) {
134
- const bid = l3bids[i];
135
- console.log(` [${i}] ${bid.maker.toBase58()} ${convertToNumber(bid.size, BASE_PRECISION)} @ $${convertToNumber(bid.price, PRICE_PRECISION)}`);
136
- }
137
-
138
- /**
139
- Level 3 order book:
140
- Asks:
141
- ...
142
- [3] FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS 1 @ $21.26
143
- [2] C13FZykQfLXKuMAMh2iuG7JxhQqd8otujNRAgVETU6id 2015 @ $21.2589
144
- [1] C13FZykQfLXKuMAMh2iuG7JxhQqd8otujNRAgVETU6id 503.7 @ $21.2483
145
- [0] C13FZykQfLXKuMAMh2iuG7JxhQqd8otujNRAgVETU6id 377.8 @ $21.243
146
- Bids:
147
- [0] FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS 1168.2 @ $21.2464
148
- [1] FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS 2570.2 @ $21.2464
149
- [2] FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS 136.7 @ $21.2461
150
- [3] FrEFAwxdrzHxgc7S4cuFfsfLmcg8pfbxnkCQW83euyCS 2570.2 @ $21.2426
151
- ...
152
- */
153
- }
154
-
155
- main().catch(console.error);