@drift-labs/sdk 2.96.0-beta.2 → 2.96.0-beta.20
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 +1 -0
- package/VERSION +1 -1
- package/bun.lockb +0 -0
- package/lib/accounts/pollingDriftClientAccountSubscriber.d.ts +5 -3
- package/lib/accounts/pollingDriftClientAccountSubscriber.js +24 -1
- package/lib/accounts/types.d.ts +5 -8
- package/lib/accounts/types.js +7 -1
- package/lib/accounts/utils.d.ts +7 -0
- package/lib/accounts/utils.js +33 -1
- package/lib/accounts/webSocketAccountSubscriber.d.ts +1 -1
- package/lib/accounts/webSocketDriftClientAccountSubscriber.d.ts +8 -7
- package/lib/accounts/webSocketDriftClientAccountSubscriber.js +24 -1
- package/lib/accounts/webSocketProgramAccountSubscriber.d.ts +1 -1
- package/lib/config.d.ts +5 -1
- package/lib/config.js +9 -1
- package/lib/constants/perpMarkets.js +21 -0
- package/lib/constants/spotMarkets.js +12 -1
- package/lib/driftClient.d.ts +44 -9
- package/lib/driftClient.js +181 -61
- package/lib/driftClientConfig.d.ts +2 -6
- package/lib/events/eventSubscriber.js +9 -8
- package/lib/events/types.js +1 -5
- package/lib/idl/drift.json +169 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/math/margin.d.ts +16 -1
- package/lib/math/margin.js +67 -1
- package/lib/orderParams.js +8 -8
- package/lib/orderSubscriber/OrderSubscriber.d.ts +1 -2
- package/lib/orderSubscriber/OrderSubscriber.js +4 -19
- package/lib/orderSubscriber/types.d.ts +0 -9
- package/lib/tokenFaucet.js +2 -1
- package/lib/tx/baseTxSender.js +2 -2
- package/lib/tx/fastSingleTxSender.js +2 -2
- package/lib/tx/forwardOnlyTxSender.js +2 -2
- package/lib/tx/retryTxSender.js +2 -2
- package/lib/tx/txHandler.js +10 -7
- package/lib/tx/whileValidTxSender.d.ts +2 -4
- package/lib/tx/whileValidTxSender.js +16 -17
- package/lib/types.d.ts +21 -1
- package/lib/types.js +6 -1
- package/lib/user.d.ts +4 -1
- package/lib/user.js +13 -13
- package/lib/userConfig.d.ts +1 -6
- package/lib/userMap/userMap.js +0 -14
- package/lib/userMap/userMapConfig.d.ts +0 -7
- package/lib/userStatsConfig.d.ts +0 -6
- package/lib/util/TransactionConfirmationManager.d.ts +14 -0
- package/lib/util/TransactionConfirmationManager.js +96 -0
- package/package.json +4 -5
- package/src/accounts/pollingDriftClientAccountSubscriber.ts +41 -5
- package/src/accounts/types.ts +6 -9
- package/src/accounts/utils.ts +42 -0
- package/src/accounts/webSocketAccountSubscriber.ts +1 -1
- package/src/accounts/webSocketDriftClientAccountSubscriber.ts +43 -8
- package/src/accounts/webSocketProgramAccountSubscriber.ts +1 -1
- package/src/config.ts +15 -1
- package/src/constants/perpMarkets.ts +22 -0
- package/src/constants/spotMarkets.ts +14 -1
- package/src/driftClient.ts +423 -91
- package/src/driftClientConfig.ts +2 -7
- package/src/events/eventSubscriber.ts +18 -11
- package/src/events/types.ts +1 -5
- package/src/idl/drift.json +169 -1
- package/src/index.ts +1 -0
- package/src/math/margin.ts +137 -1
- package/src/orderParams.ts +20 -12
- package/src/orderSubscriber/OrderSubscriber.ts +1 -15
- package/src/orderSubscriber/types.ts +0 -10
- package/src/tokenFaucet.ts +2 -2
- package/src/tx/baseTxSender.ts +2 -2
- package/src/tx/fastSingleTxSender.ts +2 -2
- package/src/tx/forwardOnlyTxSender.ts +2 -2
- package/src/tx/retryTxSender.ts +2 -2
- package/src/tx/txHandler.ts +8 -2
- package/src/tx/whileValidTxSender.ts +23 -26
- package/src/types.ts +30 -1
- package/src/user.ts +35 -13
- package/src/userConfig.ts +1 -7
- package/src/userMap/userMap.ts +1 -17
- package/src/userMap/userMapConfig.ts +0 -8
- package/src/userStatsConfig.ts +0 -7
- package/src/util/TransactionConfirmationManager.ts +155 -0
- package/tests/ci/idl.ts +12 -3
- package/tests/ci/verifyConstants.ts +13 -0
- package/tests/tx/TransactionConfirmationManager.test.ts +286 -0
- package/lib/accounts/grpcAccountSubscriber.d.ts +0 -16
- package/lib/accounts/grpcAccountSubscriber.js +0 -155
- package/lib/accounts/grpcDriftClientAccountSubscriber.d.ts +0 -13
- package/lib/accounts/grpcDriftClientAccountSubscriber.js +0 -96
- package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.d.ts +0 -10
- package/lib/accounts/grpcInsuranceFundStakeAccountSubscriber.js +0 -30
- package/lib/accounts/grpcProgramAccountSubscriber.d.ts +0 -19
- package/lib/accounts/grpcProgramAccountSubscriber.js +0 -161
- package/lib/accounts/grpcUserAccountSubscriber.d.ts +0 -10
- package/lib/accounts/grpcUserAccountSubscriber.js +0 -28
- package/lib/accounts/grpcUserStatsAccountSubscriber.d.ts +0 -10
- package/lib/accounts/grpcUserStatsAccountSubscriber.js +0 -28
- package/lib/orderSubscriber/grpcSubscription.d.ts +0 -25
- package/lib/orderSubscriber/grpcSubscription.js +0 -68
- package/lib/userMap/grpcSubscription.d.ts +0 -26
- package/lib/userMap/grpcSubscription.js +0 -42
- package/src/accounts/grpcAccountSubscriber.ts +0 -158
- package/src/accounts/grpcDriftClientAccountSubscriber.ts +0 -196
- package/src/accounts/grpcInsuranceFundStakeAccountSubscriber.ts +0 -62
- package/src/accounts/grpcProgramAccountSubscriber.ts +0 -181
- package/src/accounts/grpcUserAccountSubscriber.ts +0 -48
- package/src/accounts/grpcUserStatsAccountSubscriber.ts +0 -51
- package/src/orderSubscriber/grpcSubscription.ts +0 -126
- package/src/userMap/grpcSubscription.ts +0 -83
package/src/tx/txHandler.ts
CHANGED
|
@@ -29,6 +29,7 @@ import { CachedBlockhashFetcher } from './blockhashFetcher/cachedBlockhashFetche
|
|
|
29
29
|
import { BaseBlockhashFetcher } from './blockhashFetcher/baseBlockhashFetcher';
|
|
30
30
|
import { BlockhashFetcher } from './blockhashFetcher/types';
|
|
31
31
|
import { isVersionedTransaction } from './utils';
|
|
32
|
+
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Explanation for SIGNATURE_BLOCK_AND_EXPIRY:
|
|
@@ -81,7 +82,8 @@ export class TxHandler {
|
|
|
81
82
|
private preSignedCb?: () => void;
|
|
82
83
|
private onSignedCb?: (txSigs: DriftClientMetricsEvents['txSigned']) => void;
|
|
83
84
|
|
|
84
|
-
private blockhashCommitment: Commitment =
|
|
85
|
+
private blockhashCommitment: Commitment =
|
|
86
|
+
DEFAULT_CONFIRMATION_OPTS.commitment;
|
|
85
87
|
private blockHashFetcher: BlockhashFetcher;
|
|
86
88
|
|
|
87
89
|
constructor(props: {
|
|
@@ -98,6 +100,11 @@ export class TxHandler {
|
|
|
98
100
|
this.connection = props.connection;
|
|
99
101
|
this.wallet = props.wallet;
|
|
100
102
|
this.confirmationOptions = props.confirmationOptions;
|
|
103
|
+
this.blockhashCommitment =
|
|
104
|
+
props.confirmationOptions?.preflightCommitment ??
|
|
105
|
+
props?.connection?.commitment ??
|
|
106
|
+
this.blockhashCommitment ??
|
|
107
|
+
'confirmed';
|
|
101
108
|
|
|
102
109
|
this.blockHashFetcher = props?.config?.blockhashCachingEnabled
|
|
103
110
|
? new CachedBlockhashFetcher(
|
|
@@ -536,7 +543,6 @@ export class TxHandler {
|
|
|
536
543
|
}
|
|
537
544
|
} else {
|
|
538
545
|
const marketLookupTable = await fetchMarketLookupTableAccount();
|
|
539
|
-
|
|
540
546
|
lookupTables = lookupTables
|
|
541
547
|
? [...lookupTables, marketLookupTable]
|
|
542
548
|
: [marketLookupTable];
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
import { TxSigAndSlot } from './types';
|
|
2
2
|
import {
|
|
3
|
-
Commitment,
|
|
4
3
|
ConfirmOptions,
|
|
5
4
|
Connection,
|
|
5
|
+
SendTransactionError,
|
|
6
6
|
Signer,
|
|
7
7
|
Transaction,
|
|
8
8
|
VersionedTransaction,
|
|
9
9
|
} from '@solana/web3.js';
|
|
10
|
-
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
11
10
|
import { BaseTxSender } from './baseTxSender';
|
|
12
11
|
import bs58 from 'bs58';
|
|
13
12
|
import { TxHandler } from './txHandler';
|
|
14
13
|
import { IWallet } from '../types';
|
|
14
|
+
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
15
15
|
|
|
16
16
|
const DEFAULT_RETRY = 2000;
|
|
17
17
|
|
|
18
|
-
const VALID_BLOCK_HEIGHT_OFFSET = -150; // This is a bit of weirdness but the lastValidBlockHeight value returned from connection.getLatestBlockhash is always 300 blocks ahead of the current block, even though the transaction actually expires after 150 blocks. This accounts for that so that we can at least accuractely estimate the transaction expiry.
|
|
19
|
-
|
|
20
18
|
type ResolveReference = {
|
|
21
19
|
resolve?: () => void;
|
|
22
20
|
};
|
|
@@ -33,7 +31,6 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
33
31
|
string,
|
|
34
32
|
{ blockhash: string; lastValidBlockHeight: number }
|
|
35
33
|
>();
|
|
36
|
-
blockhashCommitment: Commitment;
|
|
37
34
|
|
|
38
35
|
useBlockHeightOffset = true;
|
|
39
36
|
|
|
@@ -62,11 +59,10 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
62
59
|
public constructor({
|
|
63
60
|
connection,
|
|
64
61
|
wallet,
|
|
65
|
-
opts = { ...
|
|
62
|
+
opts = { ...DEFAULT_CONFIRMATION_OPTS, maxRetries: 0 },
|
|
66
63
|
retrySleep = DEFAULT_RETRY,
|
|
67
64
|
additionalConnections = new Array<Connection>(),
|
|
68
65
|
additionalTxSenderCallbacks = [],
|
|
69
|
-
blockhashCommitment = 'finalized',
|
|
70
66
|
txHandler,
|
|
71
67
|
trackTxLandRate,
|
|
72
68
|
txLandRateLookbackWindowMinutes,
|
|
@@ -78,7 +74,6 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
78
74
|
retrySleep?: number;
|
|
79
75
|
additionalConnections?;
|
|
80
76
|
additionalTxSenderCallbacks?: ((base58EncodedTx: string) => void)[];
|
|
81
|
-
blockhashCommitment?: Commitment;
|
|
82
77
|
txHandler?: TxHandler;
|
|
83
78
|
trackTxLandRate?: boolean;
|
|
84
79
|
txLandRateLookbackWindowMinutes?: number;
|
|
@@ -96,7 +91,6 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
96
91
|
landRateToFeeFunc,
|
|
97
92
|
});
|
|
98
93
|
this.retrySleep = retrySleep;
|
|
99
|
-
this.blockhashCommitment = blockhashCommitment;
|
|
100
94
|
|
|
101
95
|
this.checkAndSetUseBlockHeightOffset();
|
|
102
96
|
}
|
|
@@ -139,7 +133,7 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
139
133
|
|
|
140
134
|
// handle subclass-specific side effects
|
|
141
135
|
const txSig = bs58.encode(
|
|
142
|
-
signedTx
|
|
136
|
+
signedTx?.signature || signedTx.signatures[0]?.signature
|
|
143
137
|
);
|
|
144
138
|
this.untilValid.set(txSig, latestBlockhash);
|
|
145
139
|
|
|
@@ -193,6 +187,11 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
193
187
|
const txSig = bs58.encode(signedTx.signatures[0]);
|
|
194
188
|
this.untilValid.set(txSig, latestBlockhash);
|
|
195
189
|
|
|
190
|
+
console.debug(
|
|
191
|
+
`preflight_commitment`,
|
|
192
|
+
`sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
|
|
193
|
+
);
|
|
194
|
+
|
|
196
195
|
return this.sendRawTransaction(signedTx.serialize(), opts);
|
|
197
196
|
}
|
|
198
197
|
|
|
@@ -202,6 +201,10 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
202
201
|
): Promise<TxSigAndSlot> {
|
|
203
202
|
const startTime = this.getTimestamp();
|
|
204
203
|
|
|
204
|
+
console.debug(
|
|
205
|
+
`preflight_commitment`,
|
|
206
|
+
`sending_tx_with_preflight_commitment::${opts?.preflightCommitment}`
|
|
207
|
+
);
|
|
205
208
|
const txid = await this.connection.sendRawTransaction(rawTransaction, opts);
|
|
206
209
|
this.txSigCache?.set(txid, false);
|
|
207
210
|
this.sendToAdditionalConnections(rawTransaction, opts);
|
|
@@ -234,27 +237,21 @@ export class WhileValidTxSender extends BaseTxSender {
|
|
|
234
237
|
|
|
235
238
|
let slot: number;
|
|
236
239
|
try {
|
|
237
|
-
const
|
|
238
|
-
|
|
239
|
-
const result = await this.connection.confirmTransaction(
|
|
240
|
-
{
|
|
241
|
-
signature: txid,
|
|
242
|
-
blockhash,
|
|
243
|
-
lastValidBlockHeight: this.useBlockHeightOffset
|
|
244
|
-
? lastValidBlockHeight + VALID_BLOCK_HEIGHT_OFFSET
|
|
245
|
-
: lastValidBlockHeight,
|
|
246
|
-
},
|
|
247
|
-
opts?.commitment
|
|
248
|
-
);
|
|
249
|
-
|
|
250
|
-
if (!result) {
|
|
251
|
-
throw new Error(`Couldn't get signature status for txid: ${txid}`);
|
|
252
|
-
}
|
|
240
|
+
const result = await this.confirmTransaction(txid, opts.commitment);
|
|
253
241
|
|
|
254
242
|
this.txSigCache?.set(txid, true);
|
|
255
243
|
|
|
256
244
|
await this.checkConfirmationResultForError(txid, result.value);
|
|
257
245
|
|
|
246
|
+
if (result?.value?.err) {
|
|
247
|
+
// Fallback error handling if there's a problem reporting the error in checkConfirmationResultForError
|
|
248
|
+
throw new SendTransactionError({
|
|
249
|
+
action: 'send',
|
|
250
|
+
signature: txid,
|
|
251
|
+
transactionMessage: `Transaction Failed`,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
258
255
|
slot = result.context.slot;
|
|
259
256
|
// eslint-disable-next-line no-useless-catch
|
|
260
257
|
} catch (e) {
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Keypair,
|
|
3
|
+
PublicKey,
|
|
4
|
+
Transaction,
|
|
5
|
+
VersionedTransaction,
|
|
6
|
+
} from '@solana/web3.js';
|
|
2
7
|
import { BN, ZERO } from '.';
|
|
3
8
|
|
|
4
9
|
// Utility type which lets you denote record with values of type A mapped to a record with the same keys but values of type B
|
|
@@ -1051,6 +1056,23 @@ export const DefaultOrderParams: OrderParams = {
|
|
|
1051
1056
|
auctionEndPrice: null,
|
|
1052
1057
|
};
|
|
1053
1058
|
|
|
1059
|
+
export type SwiftServerMessage = {
|
|
1060
|
+
slot: BN;
|
|
1061
|
+
swiftOrderSignature: Uint8Array;
|
|
1062
|
+
};
|
|
1063
|
+
|
|
1064
|
+
export type SwiftOrderParamsMessage = {
|
|
1065
|
+
swiftOrderParams: OptionalOrderParams;
|
|
1066
|
+
expectedOrderId: number;
|
|
1067
|
+
takeProfitOrderParams: SwiftTriggerOrderParams | null;
|
|
1068
|
+
stopLossOrderParams: SwiftTriggerOrderParams | null;
|
|
1069
|
+
};
|
|
1070
|
+
|
|
1071
|
+
export type SwiftTriggerOrderParams = {
|
|
1072
|
+
triggerPrice: BN;
|
|
1073
|
+
baseAssetAmount: BN;
|
|
1074
|
+
};
|
|
1075
|
+
|
|
1054
1076
|
export type MakerInfo = {
|
|
1055
1077
|
maker: PublicKey;
|
|
1056
1078
|
makerStats: PublicKey;
|
|
@@ -1070,6 +1092,11 @@ export type ReferrerInfo = {
|
|
|
1070
1092
|
referrerStats: PublicKey;
|
|
1071
1093
|
};
|
|
1072
1094
|
|
|
1095
|
+
export enum PlaceAndTakeOrderSuccessCondition {
|
|
1096
|
+
PartialFill = 1,
|
|
1097
|
+
FullFill = 2,
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1073
1100
|
type ExactType<T> = Pick<T, keyof T>;
|
|
1074
1101
|
|
|
1075
1102
|
export type BaseTxParams = ExactType<{
|
|
@@ -1097,6 +1124,7 @@ export interface IWallet {
|
|
|
1097
1124
|
signTransaction(tx: Transaction): Promise<Transaction>;
|
|
1098
1125
|
signAllTransactions(txs: Transaction[]): Promise<Transaction[]>;
|
|
1099
1126
|
publicKey: PublicKey;
|
|
1127
|
+
payer?: Keypair;
|
|
1100
1128
|
}
|
|
1101
1129
|
export interface IVersionedWallet {
|
|
1102
1130
|
signVersionedTransaction(
|
|
@@ -1106,6 +1134,7 @@ export interface IVersionedWallet {
|
|
|
1106
1134
|
txs: VersionedTransaction[]
|
|
1107
1135
|
): Promise<VersionedTransaction[]>;
|
|
1108
1136
|
publicKey: PublicKey;
|
|
1137
|
+
payer?: Keypair;
|
|
1109
1138
|
}
|
|
1110
1139
|
|
|
1111
1140
|
export type FeeStructure = {
|
package/src/user.ts
CHANGED
|
@@ -78,6 +78,8 @@ import {
|
|
|
78
78
|
import { calculateMarketOpenBidAsk } from './math/amm';
|
|
79
79
|
import {
|
|
80
80
|
calculateBaseAssetValueWithOracle,
|
|
81
|
+
calculateCollateralDepositRequiredForTrade,
|
|
82
|
+
calculateMarginUSDCRequiredForTrade,
|
|
81
83
|
calculateWorstCaseBaseAssetAmount,
|
|
82
84
|
} from './math/margin';
|
|
83
85
|
import { OraclePriceData } from './oracles/types';
|
|
@@ -98,7 +100,6 @@ import {
|
|
|
98
100
|
calculatePerpFuelBonus,
|
|
99
101
|
calculateInsuranceFuelBonus,
|
|
100
102
|
} from './math/fuel';
|
|
101
|
-
import { grpcUserAccountSubscriber } from './accounts/grpcUserAccountSubscriber';
|
|
102
103
|
|
|
103
104
|
export class User {
|
|
104
105
|
driftClient: DriftClient;
|
|
@@ -129,16 +130,6 @@ export class User {
|
|
|
129
130
|
);
|
|
130
131
|
} else if (config.accountSubscription?.type === 'custom') {
|
|
131
132
|
this.accountSubscriber = config.accountSubscription.userAccountSubscriber;
|
|
132
|
-
} else if (config.accountSubscription?.type === 'grpc') {
|
|
133
|
-
this.accountSubscriber = new grpcUserAccountSubscriber(
|
|
134
|
-
config.accountSubscription.configs,
|
|
135
|
-
config.driftClient.program,
|
|
136
|
-
config.userAccountPublicKey,
|
|
137
|
-
{
|
|
138
|
-
resubTimeoutMs: config.accountSubscription?.resubTimeoutMs,
|
|
139
|
-
logResubMessages: config.accountSubscription?.logResubMessages,
|
|
140
|
-
}
|
|
141
|
-
);
|
|
142
133
|
} else {
|
|
143
134
|
this.accountSubscriber = new WebSocketUserAccountSubscriber(
|
|
144
135
|
config.driftClient.program,
|
|
@@ -2285,6 +2276,7 @@ export class User {
|
|
|
2285
2276
|
* @param estimatedEntryPrice
|
|
2286
2277
|
* @param marginCategory // allow Initial to be passed in if we are trying to calculate price for DLP de-risking
|
|
2287
2278
|
* @param includeOpenOrders
|
|
2279
|
+
* @param offsetCollateral // allows calculating the liquidation price after this offset collateral is added to the user's account (e.g. : what will the liquidation price be for this position AFTER I deposit $x worth of collateral)
|
|
2288
2280
|
* @returns Precision : PRICE_PRECISION
|
|
2289
2281
|
*/
|
|
2290
2282
|
public liquidationPrice(
|
|
@@ -2292,7 +2284,8 @@ export class User {
|
|
|
2292
2284
|
positionBaseSizeChange: BN = ZERO,
|
|
2293
2285
|
estimatedEntryPrice: BN = ZERO,
|
|
2294
2286
|
marginCategory: MarginCategory = 'Maintenance',
|
|
2295
|
-
includeOpenOrders = false
|
|
2287
|
+
includeOpenOrders = false,
|
|
2288
|
+
offsetCollateral = ZERO
|
|
2296
2289
|
): BN {
|
|
2297
2290
|
const totalCollateral = this.getTotalCollateral(marginCategory);
|
|
2298
2291
|
const marginRequirement = this.getMarginRequirement(
|
|
@@ -2301,7 +2294,10 @@ export class User {
|
|
|
2301
2294
|
false,
|
|
2302
2295
|
includeOpenOrders
|
|
2303
2296
|
);
|
|
2304
|
-
let freeCollateral = BN.max(
|
|
2297
|
+
let freeCollateral = BN.max(
|
|
2298
|
+
ZERO,
|
|
2299
|
+
totalCollateral.sub(marginRequirement)
|
|
2300
|
+
).add(offsetCollateral);
|
|
2305
2301
|
|
|
2306
2302
|
const oracle =
|
|
2307
2303
|
this.driftClient.getPerpMarketAccount(marketIndex).amm.oracle;
|
|
@@ -2604,6 +2600,32 @@ export class User {
|
|
|
2604
2600
|
);
|
|
2605
2601
|
}
|
|
2606
2602
|
|
|
2603
|
+
public getMarginUSDCRequiredForTrade(
|
|
2604
|
+
targetMarketIndex: number,
|
|
2605
|
+
baseSize: BN
|
|
2606
|
+
): BN {
|
|
2607
|
+
return calculateMarginUSDCRequiredForTrade(
|
|
2608
|
+
this.driftClient,
|
|
2609
|
+
targetMarketIndex,
|
|
2610
|
+
baseSize,
|
|
2611
|
+
this.getUserAccount().maxMarginRatio
|
|
2612
|
+
);
|
|
2613
|
+
}
|
|
2614
|
+
|
|
2615
|
+
public getCollateralDepositRequiredForTrade(
|
|
2616
|
+
targetMarketIndex: number,
|
|
2617
|
+
baseSize: BN,
|
|
2618
|
+
collateralIndex: number
|
|
2619
|
+
): BN {
|
|
2620
|
+
return calculateCollateralDepositRequiredForTrade(
|
|
2621
|
+
this.driftClient,
|
|
2622
|
+
targetMarketIndex,
|
|
2623
|
+
baseSize,
|
|
2624
|
+
collateralIndex,
|
|
2625
|
+
this.getUserAccount().maxMarginRatio
|
|
2626
|
+
);
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2607
2629
|
/**
|
|
2608
2630
|
* Get the maximum trade size for a given market, taking into account the user's current leverage, positions, collateral, etc.
|
|
2609
2631
|
*
|
package/src/userConfig.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DriftClient } from './driftClient';
|
|
2
2
|
import { Commitment, PublicKey } from '@solana/web3.js';
|
|
3
3
|
import { BulkAccountLoader } from './accounts/bulkAccountLoader';
|
|
4
|
-
import {
|
|
4
|
+
import { UserAccountSubscriber } from './accounts/types';
|
|
5
5
|
|
|
6
6
|
export type UserConfig = {
|
|
7
7
|
accountSubscription?: UserSubscriptionConfig;
|
|
@@ -23,10 +23,4 @@ export type UserSubscriptionConfig =
|
|
|
23
23
|
| {
|
|
24
24
|
type: 'custom';
|
|
25
25
|
userAccountSubscriber: UserAccountSubscriber;
|
|
26
|
-
}
|
|
27
|
-
| {
|
|
28
|
-
type: 'grpc';
|
|
29
|
-
resubTimeoutMs?: number;
|
|
30
|
-
logResubMessages?: boolean;
|
|
31
|
-
configs: GrpcConfigs;
|
|
32
26
|
};
|
package/src/userMap/userMap.ts
CHANGED
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
import { WebsocketSubscription } from './WebsocketSubscription';
|
|
37
37
|
import { PollingSubscription } from './PollingSubscription';
|
|
38
38
|
import { decodeUser } from '../decode/user';
|
|
39
|
-
import { grpcSubscription } from './grpcSubscription';
|
|
40
39
|
|
|
41
40
|
const MAX_USER_ACCOUNT_SIZE_BYTES = 4376;
|
|
42
41
|
|
|
@@ -76,10 +75,7 @@ export class UserMap implements UserMapInterface {
|
|
|
76
75
|
private includeIdle: boolean;
|
|
77
76
|
private disableSyncOnTotalAccountsChange: boolean;
|
|
78
77
|
private lastNumberOfSubAccounts: BN;
|
|
79
|
-
private subscription:
|
|
80
|
-
| PollingSubscription
|
|
81
|
-
| WebsocketSubscription
|
|
82
|
-
| grpcSubscription;
|
|
78
|
+
private subscription: PollingSubscription | WebsocketSubscription;
|
|
83
79
|
private stateAccountUpdateCallback = async (state: StateAccount) => {
|
|
84
80
|
if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
|
|
85
81
|
await this.sync();
|
|
@@ -126,18 +122,6 @@ export class UserMap implements UserMapInterface {
|
|
|
126
122
|
frequency: config.subscriptionConfig.frequency,
|
|
127
123
|
skipInitialLoad: config.skipInitialLoad,
|
|
128
124
|
});
|
|
129
|
-
} else if (config.subscriptionConfig.type === 'grpc') {
|
|
130
|
-
this.subscription = new grpcSubscription({
|
|
131
|
-
configs: config.subscriptionConfig.configs,
|
|
132
|
-
userMap: this,
|
|
133
|
-
commitment: this.commitment,
|
|
134
|
-
resubOpts: {
|
|
135
|
-
resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
|
|
136
|
-
logResubMessages: config.subscriptionConfig.logResubMessages,
|
|
137
|
-
},
|
|
138
|
-
skipInitialLoad: config.skipInitialLoad,
|
|
139
|
-
decodeFn,
|
|
140
|
-
});
|
|
141
125
|
} else {
|
|
142
126
|
this.subscription = new WebsocketSubscription({
|
|
143
127
|
userMap: this,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Commitment, Connection } from '@solana/web3.js';
|
|
2
2
|
import { DriftClient } from '../driftClient';
|
|
3
|
-
import { GrpcConfigs } from '../accounts/types';
|
|
4
3
|
|
|
5
4
|
// passed into UserMap.getUniqueAuthorities to filter users
|
|
6
5
|
export type UserAccountFilterCriteria = {
|
|
@@ -28,13 +27,6 @@ export type UserMapConfig = {
|
|
|
28
27
|
frequency: number;
|
|
29
28
|
commitment?: Commitment;
|
|
30
29
|
}
|
|
31
|
-
| {
|
|
32
|
-
type: 'grpc';
|
|
33
|
-
configs: GrpcConfigs;
|
|
34
|
-
resubTimeoutMs?: number;
|
|
35
|
-
logResubMessages?: boolean;
|
|
36
|
-
commitment?: Commitment;
|
|
37
|
-
}
|
|
38
30
|
| {
|
|
39
31
|
type: 'websocket';
|
|
40
32
|
resubTimeoutMs?: number;
|
package/src/userStatsConfig.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { DriftClient } from './driftClient';
|
|
2
2
|
import { Commitment, PublicKey } from '@solana/web3.js';
|
|
3
3
|
import { BulkAccountLoader } from './accounts/bulkAccountLoader';
|
|
4
|
-
import { GrpcConfigs } from './accounts/types';
|
|
5
4
|
|
|
6
5
|
export type UserStatsConfig = {
|
|
7
6
|
accountSubscription?: UserStatsSubscriptionConfig;
|
|
@@ -22,10 +21,4 @@ export type UserStatsSubscriptionConfig =
|
|
|
22
21
|
}
|
|
23
22
|
| {
|
|
24
23
|
type: 'custom';
|
|
25
|
-
}
|
|
26
|
-
| {
|
|
27
|
-
type: 'grpc';
|
|
28
|
-
resubTimeoutMs?: number;
|
|
29
|
-
logResubMessages?: boolean;
|
|
30
|
-
configs: GrpcConfigs;
|
|
31
24
|
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection,
|
|
3
|
+
SignatureStatus,
|
|
4
|
+
TransactionConfirmationStatus,
|
|
5
|
+
} from '@solana/web3.js';
|
|
6
|
+
import { DEFAULT_CONFIRMATION_OPTS } from '../config';
|
|
7
|
+
|
|
8
|
+
const confirmationStatusValues: Record<TransactionConfirmationStatus, number> =
|
|
9
|
+
{
|
|
10
|
+
processed: 0,
|
|
11
|
+
confirmed: 1,
|
|
12
|
+
finalized: 2,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
interface TransactionConfirmationRequest {
|
|
16
|
+
txSig: string;
|
|
17
|
+
desiredConfirmationStatus: TransactionConfirmationStatus;
|
|
18
|
+
timeout: number;
|
|
19
|
+
pollInterval: number;
|
|
20
|
+
searchTransactionHistory: boolean;
|
|
21
|
+
startTime: number;
|
|
22
|
+
resolve: (status: SignatureStatus) => void;
|
|
23
|
+
reject: (error: Error) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Class to await for transaction confirmations in an optimised manner. It tracks a shared list of all pending transactions and fetches them in bulk in a shared RPC request whenever they have an "overlapping" polling interval. E.g. tx1 with an interval of 200ms and tx2 with an interval of 300ms (if sent at the same time) will be fetched together at at 600ms, 1200ms, 1800ms, etc.
|
|
28
|
+
*/
|
|
29
|
+
export class TransactionConfirmationManager {
|
|
30
|
+
private connection: Connection;
|
|
31
|
+
private pendingConfirmations: Map<string, TransactionConfirmationRequest> =
|
|
32
|
+
new Map();
|
|
33
|
+
private intervalId: NodeJS.Timeout | null = null;
|
|
34
|
+
|
|
35
|
+
constructor(connection: Connection) {
|
|
36
|
+
this.connection = connection;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async confirmTransaction(
|
|
40
|
+
txSig: string,
|
|
41
|
+
desiredConfirmationStatus = DEFAULT_CONFIRMATION_OPTS.commitment as TransactionConfirmationStatus,
|
|
42
|
+
timeout = 30000,
|
|
43
|
+
pollInterval = 1000,
|
|
44
|
+
searchTransactionHistory = false
|
|
45
|
+
): Promise<SignatureStatus> {
|
|
46
|
+
// Interval must be > 400ms and a multiple of 100ms
|
|
47
|
+
if (pollInterval < 400 || pollInterval % 100 !== 0) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
'Transaction confirmation polling interval must be at least 400ms and a multiple of 100ms'
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
this.pendingConfirmations.set(txSig, {
|
|
55
|
+
txSig,
|
|
56
|
+
desiredConfirmationStatus,
|
|
57
|
+
timeout,
|
|
58
|
+
pollInterval,
|
|
59
|
+
searchTransactionHistory,
|
|
60
|
+
startTime: Date.now(),
|
|
61
|
+
resolve,
|
|
62
|
+
reject,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!this.intervalId) {
|
|
66
|
+
this.startConfirmationLoop();
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private startConfirmationLoop() {
|
|
72
|
+
this.intervalId = setInterval(() => this.checkPendingConfirmations(), 100);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async checkPendingConfirmations() {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const transactionsToCheck: TransactionConfirmationRequest[] = [];
|
|
78
|
+
|
|
79
|
+
for (const [txSig, request] of this.pendingConfirmations.entries()) {
|
|
80
|
+
if (now - request.startTime >= request.timeout) {
|
|
81
|
+
request.reject(
|
|
82
|
+
new Error(
|
|
83
|
+
`Transaction confirmation timeout after ${request.timeout}ms`
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
this.pendingConfirmations.delete(txSig);
|
|
87
|
+
} else if ((now - request.startTime) % request.pollInterval < 100) {
|
|
88
|
+
transactionsToCheck.push(request);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (transactionsToCheck.length > 0) {
|
|
93
|
+
await this.checkTransactionStatuses(transactionsToCheck);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (this.pendingConfirmations.size === 0 && this.intervalId) {
|
|
97
|
+
clearInterval(this.intervalId);
|
|
98
|
+
this.intervalId = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private async checkTransactionStatuses(
|
|
103
|
+
requests: TransactionConfirmationRequest[]
|
|
104
|
+
) {
|
|
105
|
+
const txSigs = requests.map((request) => request.txSig);
|
|
106
|
+
const { value: statuses } = await this.connection.getSignatureStatuses(
|
|
107
|
+
txSigs,
|
|
108
|
+
{
|
|
109
|
+
searchTransactionHistory: requests.some(
|
|
110
|
+
(req) => req.searchTransactionHistory
|
|
111
|
+
),
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!statuses || statuses.length !== txSigs.length) {
|
|
116
|
+
throw new Error('Failed to get signature statuses');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (let i = 0; i < statuses.length; i++) {
|
|
120
|
+
const status = statuses[i];
|
|
121
|
+
const request = requests[i];
|
|
122
|
+
|
|
123
|
+
if (status === null) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (status.err) {
|
|
128
|
+
request.reject(
|
|
129
|
+
new Error(`Transaction failed: ${JSON.stringify(status.err)}`)
|
|
130
|
+
);
|
|
131
|
+
this.pendingConfirmations.delete(request.txSig);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
confirmationStatusValues[status.confirmationStatus] === undefined ||
|
|
137
|
+
confirmationStatusValues[request.desiredConfirmationStatus] ===
|
|
138
|
+
undefined
|
|
139
|
+
) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Invalid confirmation status when awaiting confirmation: ${status.confirmationStatus}`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (
|
|
146
|
+
status.confirmationStatus &&
|
|
147
|
+
confirmationStatusValues[status.confirmationStatus] >=
|
|
148
|
+
confirmationStatusValues[request.desiredConfirmationStatus]
|
|
149
|
+
) {
|
|
150
|
+
request.resolve(status);
|
|
151
|
+
this.pendingConfirmations.delete(request.txSig);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
package/tests/ci/idl.ts
CHANGED
|
@@ -51,7 +51,9 @@ describe('Verify IDL', function () {
|
|
|
51
51
|
);
|
|
52
52
|
|
|
53
53
|
if (onChainIdl === null) {
|
|
54
|
-
throw new Error(
|
|
54
|
+
throw new Error(
|
|
55
|
+
`onChainIdl for ${mainnetDriftClient.program.programId.toBase58()} null`
|
|
56
|
+
);
|
|
55
57
|
}
|
|
56
58
|
|
|
57
59
|
// anchor idl init seems to strip the metadata
|
|
@@ -65,13 +67,20 @@ describe('Verify IDL', function () {
|
|
|
65
67
|
const encodedSdkIdl = JSON.stringify(sdkIdl);
|
|
66
68
|
|
|
67
69
|
try {
|
|
68
|
-
assert(
|
|
70
|
+
assert(
|
|
71
|
+
encodedSdkIdl === encodedMainnetIdl,
|
|
72
|
+
'on-chain IDL does not match SDK IDL'
|
|
73
|
+
);
|
|
69
74
|
} catch (error) {
|
|
70
75
|
const diff = {};
|
|
71
76
|
for (const key of IDL_KEYS_TO_CHECK) {
|
|
72
77
|
const onChainItems = onChainIdl[key];
|
|
73
78
|
const sdkItems = sdkIdl[key];
|
|
74
|
-
for (
|
|
79
|
+
for (
|
|
80
|
+
let i = 0;
|
|
81
|
+
i < Math.max(onChainItems.length, sdkItems.length);
|
|
82
|
+
i++
|
|
83
|
+
) {
|
|
75
84
|
let onChainItem = null;
|
|
76
85
|
let sdkItem = null;
|
|
77
86
|
if (i < onChainItems.length) {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
MainnetPerpMarkets,
|
|
7
7
|
BulkAccountLoader,
|
|
8
8
|
getVariant,
|
|
9
|
+
isOneOfVariant,
|
|
9
10
|
} from '../../src';
|
|
10
11
|
import { Connection, Keypair } from '@solana/web3.js';
|
|
11
12
|
import { Wallet } from '@coral-xyz/anchor';
|
|
@@ -128,6 +129,12 @@ describe('Verify Constants', function () {
|
|
|
128
129
|
market.marketIndex
|
|
129
130
|
} oracle ${market.oracle.toBase58()}`
|
|
130
131
|
);
|
|
132
|
+
|
|
133
|
+
if (isOneOfVariant(market.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
|
|
134
|
+
if (!correspondingConfigMarket.pythFeedId) {
|
|
135
|
+
assert(false, `spot market ${market.marketIndex} missing feed id`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
const perpMarkets = mainnetDriftClient.getPerpMarketAccounts();
|
|
@@ -177,6 +184,12 @@ describe('Verify Constants', function () {
|
|
|
177
184
|
market.marketIndex
|
|
178
185
|
} oracle ${market.amm.oracle.toBase58()}`
|
|
179
186
|
);
|
|
187
|
+
|
|
188
|
+
if (isOneOfVariant(market.amm.oracleSource, ['pythPull', 'pyth1KPull', 'pyth1MPull', 'pythStableCoinPull'])) {
|
|
189
|
+
if (!correspondingConfigMarket.pythFeedId) {
|
|
190
|
+
assert(false, `perp market ${market.marketIndex} missing feed id`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
180
193
|
}
|
|
181
194
|
});
|
|
182
195
|
|