@drift-labs/sdk 2.49.0-beta.0 → 2.49.0-beta.10
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/VERSION +1 -1
- package/lib/accounts/{mockUserAccountSubscriber.d.ts → basicUserAccountSubscriber.d.ts} +2 -2
- package/lib/accounts/{mockUserAccountSubscriber.js → basicUserAccountSubscriber.js} +9 -6
- package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.d.ts +29 -0
- package/lib/accounts/pollingInsuranceFundStakeAccountSubscriber.js +110 -0
- package/lib/accounts/types.d.ts +14 -1
- package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.d.ts +23 -0
- package/lib/accounts/webSocketInsuranceFundStakeAccountSubscriber.js +65 -0
- package/lib/dlob/DLOB.d.ts +6 -2
- package/lib/dlob/DLOB.js +37 -12
- package/lib/driftClient.d.ts +66 -66
- package/lib/driftClient.js +208 -194
- package/lib/events/eventSubscriber.js +2 -1
- package/lib/events/sort.d.ts +2 -2
- package/lib/events/sort.js +6 -23
- package/lib/examples/loadDlob.js +10 -5
- package/lib/index.d.ts +3 -1
- package/lib/index.js +3 -1
- package/lib/math/auction.js +6 -1
- package/lib/orderSubscriber/OrderSubscriber.js +4 -0
- package/lib/orderSubscriber/WebsocketSubscription.d.ts +1 -1
- package/lib/orderSubscriber/WebsocketSubscription.js +8 -6
- package/lib/types.d.ts +0 -2
- package/lib/userMap/PollingSubscription.d.ts +15 -0
- package/lib/userMap/PollingSubscription.js +26 -0
- package/lib/userMap/WebsocketSubscription.d.ts +19 -0
- package/lib/userMap/WebsocketSubscription.js +40 -0
- package/lib/userMap/userMap.d.ts +15 -18
- package/lib/userMap/userMap.js +62 -31
- package/lib/userMap/userMapConfig.d.ts +20 -0
- package/lib/userMap/userMapConfig.js +2 -0
- package/package.json +1 -1
- package/src/accounts/{mockUserAccountSubscriber.ts → basicUserAccountSubscriber.ts} +8 -6
- package/src/accounts/pollingInsuranceFundStakeAccountSubscriber.ts +185 -0
- package/src/accounts/types.ts +21 -0
- package/src/accounts/webSocketInsuranceFundStakeAccountSubscriber.ts +127 -0
- package/src/dlob/DLOB.ts +55 -15
- package/src/driftClient.ts +429 -272
- package/src/events/eventSubscriber.ts +2 -1
- package/src/events/sort.ts +7 -29
- package/src/examples/loadDlob.ts +11 -6
- package/src/index.ts +3 -1
- package/src/math/auction.ts +8 -1
- package/src/orderSubscriber/OrderSubscriber.ts +4 -0
- package/src/orderSubscriber/WebsocketSubscription.ts +19 -16
- package/src/types.ts +0 -2
- package/src/userMap/PollingSubscription.ts +46 -0
- package/src/userMap/WebsocketSubscription.ts +74 -0
- package/src/userMap/userMap.ts +88 -60
- package/src/userMap/userMapConfig.ts +31 -0
- package/tests/amm/test.ts +6 -3
- package/tests/dlob/helpers.ts +2 -6
- package/tests/dlob/test.ts +194 -0
|
@@ -47,7 +47,7 @@ export class EventSubscriber {
|
|
|
47
47
|
new EventList(
|
|
48
48
|
eventType,
|
|
49
49
|
this.options.maxEventsPerType,
|
|
50
|
-
getSortFn(this.options.orderBy, this.options.orderDir
|
|
50
|
+
getSortFn(this.options.orderBy, this.options.orderDir),
|
|
51
51
|
this.options.orderDir
|
|
52
52
|
)
|
|
53
53
|
);
|
|
@@ -159,6 +159,7 @@ export class EventSubscriber {
|
|
|
159
159
|
|
|
160
160
|
if (!this.lastSeenSlot || slot > this.lastSeenSlot) {
|
|
161
161
|
this.lastSeenTxSig = txSig;
|
|
162
|
+
this.lastSeenSlot = slot;
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
if (
|
package/src/events/sort.ts
CHANGED
|
@@ -4,10 +4,7 @@ import {
|
|
|
4
4
|
EventSubscriptionOrderDirection,
|
|
5
5
|
EventType,
|
|
6
6
|
SortFn,
|
|
7
|
-
Event,
|
|
8
7
|
} from './types';
|
|
9
|
-
import { OrderActionRecord } from '../types';
|
|
10
|
-
import { ZERO } from '../index';
|
|
11
8
|
|
|
12
9
|
function clientSortAscFn(): 'less than' {
|
|
13
10
|
return 'less than';
|
|
@@ -17,45 +14,26 @@ function clientSortDescFn(): 'greater than' {
|
|
|
17
14
|
return 'greater than';
|
|
18
15
|
}
|
|
19
16
|
|
|
20
|
-
function
|
|
17
|
+
function blockchainSortFn(
|
|
21
18
|
currentEvent: EventMap[EventType],
|
|
22
19
|
newEvent: EventMap[EventType]
|
|
23
20
|
): 'less than' | 'greater than' {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
function orderActionRecordSortFn(
|
|
28
|
-
currentEvent: Event<OrderActionRecord>,
|
|
29
|
-
newEvent: Event<OrderActionRecord>
|
|
30
|
-
): 'less than' | 'greater than' {
|
|
31
|
-
const currentEventMarketIndex = currentEvent.marketIndex;
|
|
32
|
-
const newEventMarketIndex = newEvent.marketIndex;
|
|
33
|
-
if (currentEventMarketIndex !== newEventMarketIndex) {
|
|
34
|
-
return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (currentEvent.fillRecordId?.gt(ZERO) && newEvent.fillRecordId?.gt(ZERO)) {
|
|
38
|
-
return currentEvent.fillRecordId.lte(newEvent.fillRecordId)
|
|
21
|
+
if (currentEvent.slot == newEvent.slot) {
|
|
22
|
+
return currentEvent.txSigIndex < newEvent.txSigIndex
|
|
39
23
|
? 'less than'
|
|
40
24
|
: 'greater than';
|
|
41
|
-
} else {
|
|
42
|
-
return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
|
|
43
25
|
}
|
|
26
|
+
|
|
27
|
+
return currentEvent.slot < newEvent.slot ? 'less than' : 'greater than';
|
|
44
28
|
}
|
|
45
29
|
|
|
46
30
|
export function getSortFn(
|
|
47
31
|
orderBy: EventSubscriptionOrderBy,
|
|
48
|
-
orderDir: EventSubscriptionOrderDirection
|
|
49
|
-
eventType: EventType
|
|
32
|
+
orderDir: EventSubscriptionOrderDirection
|
|
50
33
|
): SortFn {
|
|
51
34
|
if (orderBy === 'client') {
|
|
52
35
|
return orderDir === 'asc' ? clientSortAscFn : clientSortDescFn;
|
|
53
36
|
}
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
case 'OrderActionRecord':
|
|
57
|
-
return orderActionRecordSortFn;
|
|
58
|
-
default:
|
|
59
|
-
return defaultBlockchainSortFn;
|
|
60
|
-
}
|
|
38
|
+
return blockchainSortFn;
|
|
61
39
|
}
|
package/src/examples/loadDlob.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnchorProvider } from '@coral-xyz/anchor';
|
|
2
|
-
import {
|
|
2
|
+
import { UserMap, Wallet } from '..';
|
|
3
3
|
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
|
|
4
4
|
import {
|
|
5
5
|
DriftClient,
|
|
@@ -55,17 +55,22 @@ const main = async () => {
|
|
|
55
55
|
await driftClient.subscribe();
|
|
56
56
|
|
|
57
57
|
console.log('Loading user map...');
|
|
58
|
-
const userMap = new UserMap(
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
const userMap = new UserMap({
|
|
59
|
+
driftClient,
|
|
60
|
+
subscriptionConfig: {
|
|
61
|
+
type: 'websocket',
|
|
62
|
+
commitment: 'processed',
|
|
63
|
+
},
|
|
64
|
+
skipInitialLoad: false,
|
|
65
|
+
includeIdle: false,
|
|
61
66
|
});
|
|
62
67
|
|
|
63
68
|
// fetches all users and subscribes for updates
|
|
64
69
|
await userMap.subscribe();
|
|
65
70
|
|
|
66
71
|
console.log('Loading dlob from user map...');
|
|
67
|
-
const
|
|
68
|
-
await
|
|
72
|
+
const slot = await driftClient.connection.getSlot();
|
|
73
|
+
const dlob = await userMap.getDLOB(slot);
|
|
69
74
|
|
|
70
75
|
console.log('number of orders', dlob.getDLOBOrders().length);
|
|
71
76
|
|
package/src/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './types';
|
|
|
10
10
|
export * from './constants/perpMarkets';
|
|
11
11
|
export * from './accounts/fetch';
|
|
12
12
|
export * from './accounts/webSocketDriftClientAccountSubscriber';
|
|
13
|
+
export * from './accounts/webSocketInsuranceFundStakeAccountSubscriber';
|
|
13
14
|
export * from './accounts/bulkAccountLoader';
|
|
14
15
|
export * from './accounts/bulkUserSubscription';
|
|
15
16
|
export * from './accounts/bulkUserStatsSubscription';
|
|
@@ -18,7 +19,8 @@ export * from './accounts/pollingOracleAccountSubscriber';
|
|
|
18
19
|
export * from './accounts/pollingTokenAccountSubscriber';
|
|
19
20
|
export * from './accounts/pollingUserAccountSubscriber';
|
|
20
21
|
export * from './accounts/pollingUserStatsAccountSubscriber';
|
|
21
|
-
export * from './accounts/
|
|
22
|
+
export * from './accounts/pollingInsuranceFundStakeAccountSubscriber';
|
|
23
|
+
export * from './accounts/basicUserAccountSubscriber';
|
|
22
24
|
export * from './accounts/types';
|
|
23
25
|
export * from './addresses/pda';
|
|
24
26
|
export * from './adminClient';
|
package/src/math/auction.ts
CHANGED
|
@@ -26,7 +26,14 @@ export function getAuctionPrice(
|
|
|
26
26
|
slot: number,
|
|
27
27
|
oraclePrice: BN
|
|
28
28
|
): BN {
|
|
29
|
-
if (
|
|
29
|
+
if (
|
|
30
|
+
isOneOfVariant(order.orderType, [
|
|
31
|
+
'market',
|
|
32
|
+
'triggerMarket',
|
|
33
|
+
'limit',
|
|
34
|
+
'triggerLimit',
|
|
35
|
+
])
|
|
36
|
+
) {
|
|
30
37
|
return getAuctionPriceForFixedAuction(order, slot);
|
|
31
38
|
} else if (isVariant(order.orderType, 'oracle')) {
|
|
32
39
|
return getAuctionPriceForOracleOffsetAuction(order, slot, oraclePrice);
|
|
@@ -94,12 +94,16 @@ export class OrderSubscriber {
|
|
|
94
94
|
programAccount.account.data,
|
|
95
95
|
slot
|
|
96
96
|
);
|
|
97
|
+
// give event loop a chance to breathe
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
for (const key of this.usersAccounts.keys()) {
|
|
100
102
|
if (!programAccountSet.has(key)) {
|
|
101
103
|
this.usersAccounts.delete(key);
|
|
102
104
|
}
|
|
105
|
+
// give event loop a chance to breathe
|
|
106
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
103
107
|
}
|
|
104
108
|
} catch (e) {
|
|
105
109
|
console.error(e);
|
|
@@ -10,7 +10,7 @@ export class WebsocketSubscription {
|
|
|
10
10
|
private skipInitialLoad: boolean;
|
|
11
11
|
private resubTimeoutMs?: number;
|
|
12
12
|
|
|
13
|
-
private subscriber
|
|
13
|
+
private subscriber?: WebSocketProgramAccountSubscriber<UserAccount>;
|
|
14
14
|
|
|
15
15
|
constructor({
|
|
16
16
|
orderSubscriber,
|
|
@@ -30,22 +30,24 @@ export class WebsocketSubscription {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
public async subscribe(): Promise<void> {
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
'OrderSubscriber',
|
|
36
|
-
'User',
|
|
37
|
-
this.orderSubscriber.driftClient.program,
|
|
38
|
-
this.orderSubscriber.driftClient.program.account.user.coder.accounts.decode.bind(
|
|
39
|
-
this.orderSubscriber.driftClient.program.account.user.coder.accounts
|
|
40
|
-
),
|
|
41
|
-
{
|
|
42
|
-
filters: [getUserFilter(), getNonIdleUserFilter()],
|
|
43
|
-
commitment: this.commitment,
|
|
44
|
-
},
|
|
45
|
-
this.resubTimeoutMs
|
|
46
|
-
);
|
|
33
|
+
if (this.subscriber) {
|
|
34
|
+
return;
|
|
47
35
|
}
|
|
48
36
|
|
|
37
|
+
this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
|
|
38
|
+
'OrderSubscriber',
|
|
39
|
+
'User',
|
|
40
|
+
this.orderSubscriber.driftClient.program,
|
|
41
|
+
this.orderSubscriber.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
|
|
42
|
+
this.orderSubscriber.driftClient.program.account.user.coder.accounts
|
|
43
|
+
),
|
|
44
|
+
{
|
|
45
|
+
filters: [getUserFilter(), getNonIdleUserFilter()],
|
|
46
|
+
commitment: this.commitment,
|
|
47
|
+
},
|
|
48
|
+
this.resubTimeoutMs
|
|
49
|
+
);
|
|
50
|
+
|
|
49
51
|
await this.subscriber.subscribe(
|
|
50
52
|
(accountId: PublicKey, account: UserAccount, context: Context) => {
|
|
51
53
|
const userKey = accountId.toBase58();
|
|
@@ -65,6 +67,7 @@ export class WebsocketSubscription {
|
|
|
65
67
|
|
|
66
68
|
public async unsubscribe(): Promise<void> {
|
|
67
69
|
if (!this.subscriber) return;
|
|
68
|
-
this.subscriber.unsubscribe();
|
|
70
|
+
await this.subscriber.unsubscribe();
|
|
71
|
+
this.subscriber = undefined;
|
|
69
72
|
}
|
|
70
73
|
}
|
package/src/types.ts
CHANGED
|
@@ -1000,8 +1000,6 @@ export interface IVersionedWallet {
|
|
|
1000
1000
|
|
|
1001
1001
|
export type FeeStructure = {
|
|
1002
1002
|
feeTiers: FeeTier[];
|
|
1003
|
-
makerRebateNumerator: BN;
|
|
1004
|
-
makerRebateDenominator: BN;
|
|
1005
1003
|
fillerRewardStructure: OrderFillerRewardStructure;
|
|
1006
1004
|
flatFillerFee: BN;
|
|
1007
1005
|
referrerRewardEpochUpperBound: BN;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { UserMap } from './userMap';
|
|
2
|
+
|
|
3
|
+
export class PollingSubscription {
|
|
4
|
+
private userMap: UserMap;
|
|
5
|
+
private frequency: number;
|
|
6
|
+
private skipInitialLoad: boolean;
|
|
7
|
+
|
|
8
|
+
intervalId?: ReturnType<typeof setTimeout>;
|
|
9
|
+
|
|
10
|
+
constructor({
|
|
11
|
+
userMap,
|
|
12
|
+
frequency,
|
|
13
|
+
skipInitialLoad = false,
|
|
14
|
+
}: {
|
|
15
|
+
userMap: UserMap;
|
|
16
|
+
frequency: number;
|
|
17
|
+
skipInitialLoad?: boolean;
|
|
18
|
+
includeIdle?: boolean;
|
|
19
|
+
}) {
|
|
20
|
+
this.userMap = userMap;
|
|
21
|
+
this.frequency = frequency;
|
|
22
|
+
this.skipInitialLoad = skipInitialLoad;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public async subscribe(): Promise<void> {
|
|
26
|
+
if (this.intervalId) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.intervalId = setInterval(
|
|
31
|
+
this.userMap.sync.bind(this.userMap),
|
|
32
|
+
this.frequency
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (!this.skipInitialLoad) {
|
|
36
|
+
await this.userMap.sync();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public async unsubscribe(): Promise<void> {
|
|
41
|
+
if (this.intervalId) {
|
|
42
|
+
clearInterval(this.intervalId);
|
|
43
|
+
this.intervalId = undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { UserMap } from './userMap';
|
|
2
|
+
import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
|
|
3
|
+
import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
|
|
4
|
+
import { UserAccount } from '../types';
|
|
5
|
+
import { Commitment, Context, PublicKey } from '@solana/web3.js';
|
|
6
|
+
|
|
7
|
+
export class WebsocketSubscription {
|
|
8
|
+
private userMap: UserMap;
|
|
9
|
+
private commitment: Commitment;
|
|
10
|
+
private skipInitialLoad: boolean;
|
|
11
|
+
private resubTimeoutMs?: number;
|
|
12
|
+
private includeIdle?: boolean;
|
|
13
|
+
|
|
14
|
+
private subscriber: WebSocketProgramAccountSubscriber<UserAccount>;
|
|
15
|
+
|
|
16
|
+
constructor({
|
|
17
|
+
userMap,
|
|
18
|
+
commitment,
|
|
19
|
+
skipInitialLoad = false,
|
|
20
|
+
resubTimeoutMs,
|
|
21
|
+
includeIdle = false,
|
|
22
|
+
}: {
|
|
23
|
+
userMap: UserMap;
|
|
24
|
+
commitment: Commitment;
|
|
25
|
+
skipInitialLoad?: boolean;
|
|
26
|
+
resubTimeoutMs?: number;
|
|
27
|
+
includeIdle?: boolean;
|
|
28
|
+
}) {
|
|
29
|
+
this.userMap = userMap;
|
|
30
|
+
this.commitment = commitment;
|
|
31
|
+
this.skipInitialLoad = skipInitialLoad;
|
|
32
|
+
this.resubTimeoutMs = resubTimeoutMs;
|
|
33
|
+
this.includeIdle = includeIdle || false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async subscribe(): Promise<void> {
|
|
37
|
+
if (!this.subscriber) {
|
|
38
|
+
const filters = [getUserFilter()];
|
|
39
|
+
if (!this.includeIdle) {
|
|
40
|
+
filters.push(getNonIdleUserFilter());
|
|
41
|
+
}
|
|
42
|
+
this.subscriber = new WebSocketProgramAccountSubscriber<UserAccount>(
|
|
43
|
+
'UserMap',
|
|
44
|
+
'User',
|
|
45
|
+
this.userMap.driftClient.program,
|
|
46
|
+
this.userMap.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(
|
|
47
|
+
this.userMap.driftClient.program.account.user.coder.accounts
|
|
48
|
+
),
|
|
49
|
+
{
|
|
50
|
+
filters,
|
|
51
|
+
commitment: this.commitment,
|
|
52
|
+
},
|
|
53
|
+
this.resubTimeoutMs
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await this.subscriber.subscribe(
|
|
58
|
+
(accountId: PublicKey, account: UserAccount, context: Context) => {
|
|
59
|
+
const userKey = accountId.toBase58();
|
|
60
|
+
this.userMap.updateUserAccount(userKey, account, context.slot);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (!this.skipInitialLoad) {
|
|
65
|
+
await this.userMap.sync();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public async unsubscribe(): Promise<void> {
|
|
70
|
+
if (!this.subscriber) return;
|
|
71
|
+
await this.subscriber.unsubscribe();
|
|
72
|
+
this.subscriber = undefined;
|
|
73
|
+
}
|
|
74
|
+
}
|
package/src/userMap/userMap.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DriftClient,
|
|
4
4
|
UserAccount,
|
|
5
5
|
OrderRecord,
|
|
6
|
-
UserSubscriptionConfig,
|
|
7
6
|
WrappedEvent,
|
|
8
7
|
DepositRecord,
|
|
9
8
|
FundingPaymentRecord,
|
|
@@ -14,11 +13,24 @@ import {
|
|
|
14
13
|
LPRecord,
|
|
15
14
|
StateAccount,
|
|
16
15
|
DLOB,
|
|
16
|
+
BasicUserAccountSubscriber,
|
|
17
|
+
BN,
|
|
17
18
|
} from '..';
|
|
18
19
|
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
Commitment,
|
|
22
|
+
Connection,
|
|
23
|
+
PublicKey,
|
|
24
|
+
RpcResponseAndContext,
|
|
25
|
+
} from '@solana/web3.js';
|
|
20
26
|
import { Buffer } from 'buffer';
|
|
21
27
|
import { getNonIdleUserFilter, getUserFilter } from '../memcmp';
|
|
28
|
+
import {
|
|
29
|
+
UserAccountFilterCriteria as UserFilterCriteria,
|
|
30
|
+
UserMapConfig,
|
|
31
|
+
} from './userMapConfig';
|
|
32
|
+
import { WebsocketSubscription } from './WebsocketSubscription';
|
|
33
|
+
import { PollingSubscription } from './PollingSubscription';
|
|
22
34
|
|
|
23
35
|
export interface UserMapInterface {
|
|
24
36
|
subscribe(): Promise<void>;
|
|
@@ -32,59 +44,51 @@ export interface UserMapInterface {
|
|
|
32
44
|
values(): IterableIterator<User>;
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
// filter users that meet these criteria when passing into syncCallback
|
|
36
|
-
export type SyncCallbackCriteria = {
|
|
37
|
-
// only sync users that have open orders
|
|
38
|
-
hasOpenOrders: boolean;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
47
|
export class UserMap implements UserMapInterface {
|
|
42
48
|
private userMap = new Map<string, User>();
|
|
43
|
-
|
|
44
|
-
private
|
|
49
|
+
driftClient: DriftClient;
|
|
50
|
+
private connection: Connection;
|
|
51
|
+
private commitment: Commitment;
|
|
45
52
|
private includeIdle: boolean;
|
|
46
|
-
private lastNumberOfSubAccounts;
|
|
53
|
+
private lastNumberOfSubAccounts: BN;
|
|
54
|
+
private subscription: PollingSubscription | WebsocketSubscription;
|
|
47
55
|
private stateAccountUpdateCallback = async (state: StateAccount) => {
|
|
48
|
-
if (state.numberOfSubAccounts
|
|
56
|
+
if (!state.numberOfSubAccounts.eq(this.lastNumberOfSubAccounts)) {
|
|
49
57
|
await this.sync();
|
|
50
58
|
this.lastNumberOfSubAccounts = state.numberOfSubAccounts;
|
|
51
59
|
}
|
|
52
60
|
};
|
|
53
|
-
private syncCallback: (authorities: PublicKey[]) => Promise<void>;
|
|
54
|
-
private syncCallbackCriteria: SyncCallbackCriteria;
|
|
55
61
|
|
|
56
62
|
private syncPromise?: Promise<void>;
|
|
57
63
|
private syncPromiseResolver: () => void;
|
|
58
64
|
|
|
59
65
|
/**
|
|
60
66
|
* Constructs a new UserMap instance.
|
|
61
|
-
*
|
|
62
|
-
* @param {DriftClient} driftClient - The DriftClient instance.
|
|
63
|
-
* @param {UserSubscriptionConfig} accountSubscription - The UserSubscriptionConfig instance.
|
|
64
|
-
* @param {boolean} includeIdle - Whether idle users are subscribed to. Defaults to false to decrease # of user subscriptions.
|
|
65
|
-
* @param {(authorities: PublicKey[]) => Promise<void>} syncCallback - Called after `sync` completes, will pas in unique list of authorities. Useful for using it to sync UserStatsMap.
|
|
66
|
-
* @param {SyncCallbackCriteria} syncCallbackCriteria - The criteria for the sync callback. Defaults to having no filters
|
|
67
67
|
*/
|
|
68
|
-
constructor(
|
|
69
|
-
driftClient
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.
|
|
76
|
-
|
|
77
|
-
this.includeIdle = includeIdle;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
constructor(config: UserMapConfig) {
|
|
69
|
+
this.driftClient = config.driftClient;
|
|
70
|
+
if (config.connection) {
|
|
71
|
+
this.connection = config.connection;
|
|
72
|
+
} else {
|
|
73
|
+
this.connection = this.driftClient.connection;
|
|
74
|
+
}
|
|
75
|
+
this.commitment =
|
|
76
|
+
config.subscriptionConfig.commitment ?? this.driftClient.opts.commitment;
|
|
77
|
+
this.includeIdle = config.includeIdle ?? false;
|
|
78
|
+
if (config.subscriptionConfig.type === 'polling') {
|
|
79
|
+
this.subscription = new PollingSubscription({
|
|
80
|
+
userMap: this,
|
|
81
|
+
frequency: config.subscriptionConfig.frequency,
|
|
82
|
+
skipInitialLoad: config.skipInitialLoad,
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
this.subscription = new WebsocketSubscription({
|
|
86
|
+
userMap: this,
|
|
87
|
+
commitment: this.commitment,
|
|
88
|
+
resubTimeoutMs: config.subscriptionConfig.resubTimeoutMs,
|
|
89
|
+
skipInitialLoad: config.skipInitialLoad,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
public async subscribe() {
|
|
@@ -100,17 +104,25 @@ export class UserMap implements UserMapInterface {
|
|
|
100
104
|
this.stateAccountUpdateCallback
|
|
101
105
|
);
|
|
102
106
|
|
|
103
|
-
await this.
|
|
107
|
+
await this.subscription.subscribe();
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
public async addPubkey(
|
|
107
111
|
userAccountPublicKey: PublicKey,
|
|
108
|
-
userAccount?: UserAccount
|
|
112
|
+
userAccount?: UserAccount,
|
|
113
|
+
slot?: number
|
|
109
114
|
) {
|
|
110
115
|
const user = new User({
|
|
111
116
|
driftClient: this.driftClient,
|
|
112
117
|
userAccountPublicKey,
|
|
113
|
-
accountSubscription:
|
|
118
|
+
accountSubscription: {
|
|
119
|
+
type: 'custom',
|
|
120
|
+
userAccountSubscriber: new BasicUserAccountSubscriber(
|
|
121
|
+
userAccountPublicKey,
|
|
122
|
+
userAccount,
|
|
123
|
+
slot
|
|
124
|
+
),
|
|
125
|
+
},
|
|
114
126
|
});
|
|
115
127
|
await user.subscribe(userAccount);
|
|
116
128
|
this.userMap.set(userAccountPublicKey.toString(), user);
|
|
@@ -216,14 +228,18 @@ export class UserMap implements UserMapInterface {
|
|
|
216
228
|
return this.userMap.size;
|
|
217
229
|
}
|
|
218
230
|
|
|
219
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Returns a unique list of authorities for all users in the UserMap that meet the filter criteria
|
|
233
|
+
* @param filterCriteria: Users must meet these criteria to be included
|
|
234
|
+
* @returns
|
|
235
|
+
*/
|
|
236
|
+
public getUniqueAuthorities(
|
|
237
|
+
filterCriteria?: UserFilterCriteria
|
|
238
|
+
): PublicKey[] {
|
|
220
239
|
const usersMeetingCriteria = Array.from(this.userMap.values()).filter(
|
|
221
240
|
(user) => {
|
|
222
241
|
let pass = true;
|
|
223
|
-
if (
|
|
224
|
-
useSyncCallbackCriteria &&
|
|
225
|
-
this.syncCallbackCriteria.hasOpenOrders
|
|
226
|
-
) {
|
|
242
|
+
if (filterCriteria && filterCriteria.hasOpenOrders) {
|
|
227
243
|
pass = pass && user.getUserAccount().hasOpenOrder;
|
|
228
244
|
}
|
|
229
245
|
return pass;
|
|
@@ -257,7 +273,7 @@ export class UserMap implements UserMapInterface {
|
|
|
257
273
|
const rpcRequestArgs = [
|
|
258
274
|
this.driftClient.program.programId.toBase58(),
|
|
259
275
|
{
|
|
260
|
-
commitment: this.
|
|
276
|
+
commitment: this.commitment,
|
|
261
277
|
filters,
|
|
262
278
|
encoding: 'base64',
|
|
263
279
|
withContext: true,
|
|
@@ -266,10 +282,7 @@ export class UserMap implements UserMapInterface {
|
|
|
266
282
|
|
|
267
283
|
const rpcJSONResponse: any =
|
|
268
284
|
// @ts-ignore
|
|
269
|
-
await this.
|
|
270
|
-
'getProgramAccounts',
|
|
271
|
-
rpcRequestArgs
|
|
272
|
-
);
|
|
285
|
+
await this.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
|
|
273
286
|
|
|
274
287
|
const rpcResponseAndContext: RpcResponseAndContext<
|
|
275
288
|
Array<{
|
|
@@ -297,12 +310,14 @@ export class UserMap implements UserMapInterface {
|
|
|
297
310
|
for (const [key, buffer] of programAccountBufferMap.entries()) {
|
|
298
311
|
if (!this.has(key)) {
|
|
299
312
|
const userAccount =
|
|
300
|
-
this.driftClient.program.account.user.coder.accounts.
|
|
313
|
+
this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
|
|
301
314
|
'User',
|
|
302
315
|
buffer
|
|
303
316
|
);
|
|
304
317
|
await this.addPubkey(new PublicKey(key), userAccount);
|
|
305
318
|
}
|
|
319
|
+
// give event loop a chance to breathe
|
|
320
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
306
321
|
}
|
|
307
322
|
|
|
308
323
|
for (const [key, user] of this.userMap.entries()) {
|
|
@@ -311,19 +326,17 @@ export class UserMap implements UserMapInterface {
|
|
|
311
326
|
this.userMap.delete(key);
|
|
312
327
|
} else {
|
|
313
328
|
const userAccount =
|
|
314
|
-
this.driftClient.program.account.user.coder.accounts.
|
|
329
|
+
this.driftClient.program.account.user.coder.accounts.decodeUnchecked(
|
|
315
330
|
'User',
|
|
316
331
|
programAccountBufferMap.get(key)
|
|
317
332
|
);
|
|
318
333
|
user.accountSubscriber.updateData(userAccount, slot);
|
|
319
334
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (this.syncCallback) {
|
|
323
|
-
await this.syncCallback(this.getUniqueAuthorities());
|
|
335
|
+
// give event loop a chance to breathe
|
|
336
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
324
337
|
}
|
|
325
338
|
} catch (e) {
|
|
326
|
-
console.error(`Error in UserMap.sync()
|
|
339
|
+
console.error(`Error in UserMap.sync():`);
|
|
327
340
|
console.error(e);
|
|
328
341
|
} finally {
|
|
329
342
|
this.syncPromiseResolver();
|
|
@@ -332,6 +345,8 @@ export class UserMap implements UserMapInterface {
|
|
|
332
345
|
}
|
|
333
346
|
|
|
334
347
|
public async unsubscribe() {
|
|
348
|
+
await this.subscription.unsubscribe();
|
|
349
|
+
|
|
335
350
|
for (const [key, user] of this.userMap.entries()) {
|
|
336
351
|
await user.unsubscribe();
|
|
337
352
|
this.userMap.delete(key);
|
|
@@ -345,4 +360,17 @@ export class UserMap implements UserMapInterface {
|
|
|
345
360
|
this.lastNumberOfSubAccounts = undefined;
|
|
346
361
|
}
|
|
347
362
|
}
|
|
363
|
+
|
|
364
|
+
public async updateUserAccount(
|
|
365
|
+
key: string,
|
|
366
|
+
userAccount: UserAccount,
|
|
367
|
+
slot: number
|
|
368
|
+
) {
|
|
369
|
+
if (!this.userMap.has(key)) {
|
|
370
|
+
this.addPubkey(new PublicKey(key), userAccount, slot);
|
|
371
|
+
} else {
|
|
372
|
+
const user = this.userMap.get(key);
|
|
373
|
+
user.accountSubscriber.updateData(userAccount, slot);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
348
376
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Commitment, Connection } from '@solana/web3.js';
|
|
2
|
+
import { DriftClient } from '../driftClient';
|
|
3
|
+
|
|
4
|
+
// passed into UserMap.getUniqueAuthorities to filter users
|
|
5
|
+
export type UserAccountFilterCriteria = {
|
|
6
|
+
// only return users that have open orders
|
|
7
|
+
hasOpenOrders: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type UserMapConfig = {
|
|
11
|
+
driftClient: DriftClient;
|
|
12
|
+
// connection object to use specifically for the UserMap. If undefined, will use the driftClient's connection
|
|
13
|
+
connection?: Connection;
|
|
14
|
+
subscriptionConfig:
|
|
15
|
+
| {
|
|
16
|
+
type: 'polling';
|
|
17
|
+
frequency: number;
|
|
18
|
+
commitment?: Commitment;
|
|
19
|
+
}
|
|
20
|
+
| {
|
|
21
|
+
type: 'websocket';
|
|
22
|
+
resubTimeoutMs?: number;
|
|
23
|
+
commitment?: Commitment;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// True to skip the initial load of userAccounts via getProgramAccounts
|
|
27
|
+
skipInitialLoad?: boolean;
|
|
28
|
+
|
|
29
|
+
// True to include idle users when loading. Defaults to false to decrease # of accounts subscribed to.
|
|
30
|
+
includeIdle?: boolean;
|
|
31
|
+
};
|
package/tests/amm/test.ts
CHANGED
|
@@ -361,7 +361,10 @@ describe('AMM Tests', () => {
|
|
|
361
361
|
|
|
362
362
|
console.log(terms2);
|
|
363
363
|
assert(terms2.effectiveLeverageCapped <= 1.000001);
|
|
364
|
-
assert(
|
|
364
|
+
assert(
|
|
365
|
+
terms2.inventorySpreadScale == 1.013527,
|
|
366
|
+
`got: ${terms2.inventorySpreadScale}`
|
|
367
|
+
);
|
|
365
368
|
assert(terms2.longSpread == 1146);
|
|
366
369
|
assert(terms2.shortSpread == 6686);
|
|
367
370
|
});
|
|
@@ -466,7 +469,7 @@ describe('AMM Tests', () => {
|
|
|
466
469
|
|
|
467
470
|
assert(markTwapLive.eq(new BN('1949826')));
|
|
468
471
|
assert(oracleTwapLive.eq(new BN('1942510')));
|
|
469
|
-
assert(est1.eq(new BN('15692')));
|
|
472
|
+
assert(est1.eq(new BN('15692')), `got: ${est1}`);
|
|
470
473
|
assert(est2.eq(new BN('15692')));
|
|
471
474
|
});
|
|
472
475
|
|
|
@@ -556,7 +559,7 @@ describe('AMM Tests', () => {
|
|
|
556
559
|
assert(markTwapLive.eq(new BN('1222131')));
|
|
557
560
|
assert(oracleTwapLive.eq(new BN('1222586')));
|
|
558
561
|
assert(est1.eq(est2));
|
|
559
|
-
assert(est2.eq(new BN('-1550')));
|
|
562
|
+
assert(est2.eq(new BN('-1550')), `got: ${est2}`);
|
|
560
563
|
});
|
|
561
564
|
|
|
562
565
|
it('orderbook L2 gen (no topOfBookQuoteAmounts, 10 numOrders, low liquidity)', async () => {
|