@drift-labs/sdk 0.1.36-master.7 → 0.2.0-master.0
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.d.ts +6 -6
- package/lib/accounts/bulkAccountLoader.js +81 -95
- package/lib/accounts/bulkUserSubscription.js +13 -57
- package/lib/accounts/fetch.d.ts +4 -0
- package/lib/accounts/fetch.js +18 -0
- package/lib/accounts/pollingClearingHouseAccountSubscriber.d.ts +34 -38
- package/lib/accounts/pollingClearingHouseAccountSubscriber.js +224 -224
- package/lib/accounts/pollingOracleSubscriber.d.ts +3 -3
- package/lib/accounts/pollingOracleSubscriber.js +37 -49
- package/lib/accounts/pollingTokenAccountSubscriber.d.ts +3 -3
- package/lib/accounts/pollingTokenAccountSubscriber.js +35 -50
- package/lib/accounts/pollingUserAccountSubscriber.d.ts +7 -13
- package/lib/accounts/pollingUserAccountSubscriber.js +71 -134
- package/lib/accounts/types.d.ts +34 -41
- package/lib/accounts/webSocketAccountSubscriber.d.ts +6 -4
- package/lib/accounts/webSocketAccountSubscriber.js +39 -35
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.d.ts +33 -28
- package/lib/accounts/webSocketClearingHouseAccountSubscriber.js +153 -185
- package/lib/accounts/webSocketUserAccountSubscriber.d.ts +5 -11
- package/lib/accounts/webSocketUserAccountSubscriber.js +22 -67
- package/lib/addresses/marketAddresses.d.ts +4 -0
- package/lib/addresses/marketAddresses.js +15 -0
- package/lib/addresses/pda.d.ts +12 -0
- package/lib/addresses/pda.js +83 -0
- package/lib/admin.d.ts +8 -17
- package/lib/admin.js +366 -558
- package/lib/clearingHouse.d.ts +84 -109
- package/lib/clearingHouse.js +778 -899
- package/lib/clearingHouseConfig.d.ts +34 -0
- package/lib/clearingHouseConfig.js +2 -0
- package/lib/clearingHouseUser.d.ts +19 -22
- package/lib/clearingHouseUser.js +155 -141
- package/lib/clearingHouseUserConfig.d.ts +14 -0
- package/lib/clearingHouseUserConfig.js +2 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.js +35 -4
- package/lib/constants/banks.d.ts +16 -0
- package/lib/constants/banks.js +34 -0
- package/lib/constants/markets.d.ts +8 -3
- package/lib/constants/markets.js +13 -206
- package/lib/constants/numericConstants.d.ts +16 -0
- package/lib/constants/numericConstants.js +22 -6
- package/lib/events/eventList.d.ts +22 -0
- package/lib/events/eventList.js +77 -0
- package/lib/events/eventSubscriber.d.ts +34 -0
- package/lib/events/eventSubscriber.js +126 -0
- package/lib/events/fetchLogs.d.ts +13 -0
- package/lib/events/fetchLogs.js +39 -0
- package/lib/events/pollingLogProvider.d.ts +15 -0
- package/lib/events/pollingLogProvider.js +53 -0
- package/lib/events/sort.d.ts +2 -0
- package/lib/events/sort.js +44 -0
- package/lib/events/txEventCache.d.ts +24 -0
- package/lib/events/txEventCache.js +71 -0
- package/lib/events/types.d.ts +49 -0
- package/lib/events/types.js +20 -0
- package/lib/events/webSocketLogProvider.d.ts +12 -0
- package/lib/events/webSocketLogProvider.js +30 -0
- package/lib/examples/makeTradeExample.js +26 -27
- package/lib/factory/bigNum.d.ts +112 -0
- package/lib/factory/bigNum.js +356 -0
- package/lib/factory/oracleClient.d.ts +1 -2
- package/lib/factory/oracleClient.js +6 -2
- package/lib/idl/clearing_house.json +2213 -2951
- package/lib/index.d.ts +12 -4
- package/lib/index.js +12 -4
- package/lib/math/amm.d.ts +19 -29
- package/lib/math/amm.js +129 -179
- package/lib/math/auction.d.ts +5 -0
- package/lib/math/auction.js +39 -0
- package/lib/math/bankBalance.d.ts +9 -0
- package/lib/math/bankBalance.js +75 -0
- package/lib/math/conversion.d.ts +0 -1
- package/lib/math/conversion.js +1 -5
- package/lib/math/funding.d.ts +6 -6
- package/lib/math/funding.js +158 -175
- package/lib/math/market.d.ts +6 -6
- package/lib/math/market.js +10 -9
- package/lib/math/orders.d.ts +5 -0
- package/lib/math/orders.js +31 -1
- package/lib/math/position.d.ts +7 -5
- package/lib/math/position.js +27 -27
- package/lib/math/repeg.d.ts +22 -0
- package/lib/math/repeg.js +128 -0
- package/lib/math/trade.d.ts +5 -4
- package/lib/math/trade.js +29 -21
- package/lib/mockUSDCFaucet.js +87 -116
- package/lib/oracles/oracleClientCache.d.ts +8 -0
- package/lib/oracles/oracleClientCache.js +19 -0
- package/lib/oracles/pythClient.d.ts +3 -5
- package/lib/oracles/pythClient.js +12 -31
- package/lib/oracles/quoteAssetOracleClient.d.ts +9 -0
- package/lib/oracles/quoteAssetOracleClient.js +21 -0
- package/lib/oracles/switchboardClient.d.ts +3 -5
- package/lib/oracles/switchboardClient.js +29 -50
- package/lib/oracles/types.d.ts +6 -1
- package/lib/orders.d.ts +6 -6
- package/lib/orders.js +10 -9
- package/lib/slot/SlotSubscriber.d.ts +12 -0
- package/lib/slot/SlotSubscriber.js +23 -0
- package/lib/tx/retryTxSender.d.ts +2 -2
- package/lib/tx/retryTxSender.js +108 -123
- package/lib/tx/types.d.ts +5 -1
- package/lib/tx/utils.d.ts +1 -1
- package/lib/tx/utils.js +11 -2
- package/lib/types.d.ts +105 -109
- package/lib/types.js +13 -1
- package/lib/userName.d.ts +4 -0
- package/lib/userName.js +20 -0
- package/lib/util/computeUnits.js +10 -21
- package/lib/util/tps.js +11 -22
- package/lib/wallet.js +7 -20
- package/package.json +10 -3
- package/src/accounts/bulkAccountLoader.ts +21 -15
- package/src/accounts/bulkUserSubscription.ts +1 -45
- package/src/accounts/fetch.ts +33 -0
- package/src/accounts/pollingClearingHouseAccountSubscriber.ts +249 -214
- package/src/accounts/pollingOracleSubscriber.ts +16 -8
- package/src/accounts/pollingTokenAccountSubscriber.ts +11 -8
- package/src/accounts/pollingUserAccountSubscriber.ts +21 -86
- package/src/accounts/types.ts +41 -70
- package/src/accounts/webSocketAccountSubscriber.ts +33 -16
- package/src/accounts/webSocketClearingHouseAccountSubscriber.ts +190 -240
- package/src/accounts/webSocketUserAccountSubscriber.ts +11 -71
- package/src/addresses/marketAddresses.ts +18 -0
- package/src/addresses/pda.ts +118 -0
- package/src/admin.ts +205 -346
- package/src/clearingHouse.ts +918 -961
- package/src/clearingHouseConfig.ts +37 -0
- package/src/clearingHouseUser.ts +275 -185
- package/src/clearingHouseUserConfig.ts +18 -0
- package/src/config.ts +54 -1
- package/src/constants/banks.ts +43 -0
- package/src/constants/markets.ts +16 -207
- package/src/constants/numericConstants.ts +33 -5
- package/src/events/eventList.ts +94 -0
- package/src/events/eventSubscriber.ts +194 -0
- package/src/events/fetchLogs.ts +80 -0
- package/src/events/pollingLogProvider.ts +79 -0
- package/src/events/sort.ts +65 -0
- package/src/events/txEventCache.ts +74 -0
- package/src/events/types.ts +98 -0
- package/src/events/webSocketLogProvider.ts +38 -0
- package/src/examples/makeTradeExample.ts +20 -11
- package/src/factory/bigNum.ts +507 -0
- package/src/factory/oracleClient.ts +7 -4
- package/src/idl/clearing_house.json +2213 -2951
- package/src/index.ts +12 -4
- package/src/math/amm.ts +229 -245
- package/src/math/auction.ts +39 -0
- package/src/math/bankBalance.ts +112 -0
- package/src/math/conversion.ts +1 -11
- package/src/math/funding.ts +12 -9
- package/src/math/market.ts +37 -30
- package/src/math/orders.ts +38 -0
- package/src/math/position.ts +48 -35
- package/src/math/repeg.ts +175 -0
- package/src/math/trade.ts +45 -35
- package/src/math/utils.js +27 -0
- package/src/math/utils.js.map +1 -0
- package/src/oracles/oracleClientCache.ts +20 -0
- package/src/oracles/pythClient.ts +4 -11
- package/src/oracles/quoteAssetOracleClient.ts +25 -0
- package/src/oracles/switchboardClient.ts +11 -24
- package/src/oracles/types.ts +7 -1
- package/src/orders.ts +35 -20
- package/src/slot/SlotSubscriber.ts +32 -0
- package/src/tx/retryTxSender.ts +6 -4
- package/src/tx/types.ts +6 -1
- package/src/tx/utils.ts +22 -3
- package/src/types.ts +108 -122
- package/src/userName.ts +20 -0
- package/src/util/computeUnits.js +17 -0
- package/src/util/computeUnits.js.map +1 -0
- package/tests/bn/test.ts +255 -0
- package/tsconfig.json +12 -12
- package/lib/addresses.d.ts +0 -10
- package/lib/addresses.js +0 -93
- package/lib/constants/accounts.d.ts +0 -15
- package/lib/constants/accounts.js +0 -18
- package/lib/factory/clearingHouse.d.ts +0 -35
- package/lib/factory/clearingHouse.js +0 -81
- package/lib/factory/clearingHouseUser.d.ts +0 -19
- package/lib/factory/clearingHouseUser.js +0 -34
- package/lib/math/insuranceFund.d.ts +0 -15
- package/lib/math/insuranceFund.js +0 -33
- package/lib/settlement.d.ts +0 -4
- package/lib/settlement.js +0 -10
- package/lib/tx/defaultTxSender.d.ts +0 -8
- package/lib/tx/defaultTxSender.js +0 -12
- package/src/addresses.ts +0 -82
- package/src/constants/accounts.ts +0 -26
- package/src/factory/clearingHouse.ts +0 -173
- package/src/factory/clearingHouseUser.ts +0 -73
- package/src/math/insuranceFund.ts +0 -29
- package/src/settlement.ts +0 -9
- package/src/tx/defaultTxSender.ts +0 -24
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { Connection, TransactionSignature } from '@solana/web3.js';
|
|
2
|
+
import { Program } from '@project-serum/anchor';
|
|
3
|
+
import {
|
|
4
|
+
DefaultEventSubscriptionOptions,
|
|
5
|
+
EventSubscriptionOptions,
|
|
6
|
+
EventType,
|
|
7
|
+
WrappedEvents,
|
|
8
|
+
EventMap,
|
|
9
|
+
LogProvider,
|
|
10
|
+
EventSubscriberEvents,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { TxEventCache } from './txEventCache';
|
|
13
|
+
import { EventList } from './eventList';
|
|
14
|
+
import { PollingLogProvider } from './pollingLogProvider';
|
|
15
|
+
import { fetchLogs } from './fetchLogs';
|
|
16
|
+
import { WebSocketLogProvider } from './webSocketLogProvider';
|
|
17
|
+
import { EventEmitter } from 'events';
|
|
18
|
+
import StrictEventEmitter from 'strict-event-emitter-types';
|
|
19
|
+
import { getSortFn } from './sort';
|
|
20
|
+
|
|
21
|
+
export class EventSubscriber {
|
|
22
|
+
private eventListMap: Map<EventType, EventList<EventType>>;
|
|
23
|
+
private txEventCache: TxEventCache;
|
|
24
|
+
private awaitTxPromises = new Map<string, Promise<void>>();
|
|
25
|
+
private awaitTxResolver = new Map<string, () => void>();
|
|
26
|
+
private logProvider: LogProvider;
|
|
27
|
+
public eventEmitter: StrictEventEmitter<EventEmitter, EventSubscriberEvents>;
|
|
28
|
+
|
|
29
|
+
public constructor(
|
|
30
|
+
private connection: Connection,
|
|
31
|
+
private program: Program,
|
|
32
|
+
private options: EventSubscriptionOptions = DefaultEventSubscriptionOptions
|
|
33
|
+
) {
|
|
34
|
+
this.options = Object.assign({}, DefaultEventSubscriptionOptions, options);
|
|
35
|
+
this.txEventCache = new TxEventCache(this.options.maxTx);
|
|
36
|
+
this.eventListMap = new Map<EventType, EventList<EventType>>();
|
|
37
|
+
for (const eventType of this.options.eventTypes) {
|
|
38
|
+
this.eventListMap.set(
|
|
39
|
+
eventType,
|
|
40
|
+
new EventList(
|
|
41
|
+
eventType,
|
|
42
|
+
this.options.maxEventsPerType,
|
|
43
|
+
getSortFn(this.options.orderBy, this.options.orderDir, eventType),
|
|
44
|
+
this.options.orderDir
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
this.eventEmitter = new EventEmitter();
|
|
49
|
+
if (this.options.logProviderConfig.type === 'websocket') {
|
|
50
|
+
this.logProvider = new WebSocketLogProvider(
|
|
51
|
+
this.connection,
|
|
52
|
+
this.program.programId,
|
|
53
|
+
this.options.commitment
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
this.logProvider = new PollingLogProvider(
|
|
57
|
+
this.connection,
|
|
58
|
+
this.program.programId,
|
|
59
|
+
options.commitment,
|
|
60
|
+
this.options.logProviderConfig.frequency
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public subscribe(): boolean {
|
|
66
|
+
if (this.logProvider.isSubscribed()) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.fetchPreviousTx().catch((e) => {
|
|
71
|
+
console.error('Error fetching previous txs in event subscriber');
|
|
72
|
+
console.error(e);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return this.logProvider.subscribe((txSig, slot, logs) => {
|
|
76
|
+
this.handleTxLogs(txSig, slot, logs);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private handleTxLogs(
|
|
81
|
+
txSig: TransactionSignature,
|
|
82
|
+
slot: number,
|
|
83
|
+
logs: string[]
|
|
84
|
+
): void {
|
|
85
|
+
if (this.txEventCache.has(txSig)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const wrappedEvents = this.parseEventsFromLogs(txSig, slot, logs);
|
|
90
|
+
for (const wrappedEvent of wrappedEvents) {
|
|
91
|
+
this.eventListMap.get(wrappedEvent.eventType).insert(wrappedEvent);
|
|
92
|
+
this.eventEmitter.emit('newEvent', wrappedEvent);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (this.awaitTxPromises.has(txSig)) {
|
|
96
|
+
this.awaitTxPromises.delete(txSig);
|
|
97
|
+
this.awaitTxResolver.get(txSig)();
|
|
98
|
+
this.awaitTxResolver.delete(txSig);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this.txEventCache.add(txSig, wrappedEvents);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async fetchPreviousTx(): Promise<void> {
|
|
105
|
+
if (!this.options.untilTx) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let txFetched = 0;
|
|
110
|
+
let beforeTx: TransactionSignature = undefined;
|
|
111
|
+
const untilTx: TransactionSignature = this.options.untilTx;
|
|
112
|
+
while (txFetched < this.options.maxTx) {
|
|
113
|
+
const response = await fetchLogs(
|
|
114
|
+
this.connection,
|
|
115
|
+
this.program.programId,
|
|
116
|
+
this.options.commitment === 'finalized' ? 'finalized' : 'confirmed',
|
|
117
|
+
beforeTx,
|
|
118
|
+
untilTx
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (response === undefined) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
txFetched += response.transactionLogs.length;
|
|
126
|
+
beforeTx = response.earliestTx;
|
|
127
|
+
|
|
128
|
+
for (const { txSig, slot, logs } of response.transactionLogs) {
|
|
129
|
+
this.handleTxLogs(txSig, slot, logs);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public async unsubscribe(): Promise<boolean> {
|
|
135
|
+
return await this.logProvider.unsubscribe();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private parseEventsFromLogs(
|
|
139
|
+
txSig: TransactionSignature,
|
|
140
|
+
slot: number,
|
|
141
|
+
logs: string[]
|
|
142
|
+
): WrappedEvents {
|
|
143
|
+
const records = [];
|
|
144
|
+
// @ts-ignore
|
|
145
|
+
this.program._events._eventParser.parseLogs(logs, (event) => {
|
|
146
|
+
const expectRecordType = this.eventListMap.has(event.name);
|
|
147
|
+
if (expectRecordType) {
|
|
148
|
+
event.data.txSig = txSig;
|
|
149
|
+
event.data.slot = slot;
|
|
150
|
+
event.data.eventType = event.name;
|
|
151
|
+
records.push(event.data);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return records;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public awaitTx(txSig: TransactionSignature): Promise<void> {
|
|
158
|
+
if (this.awaitTxPromises.has(txSig)) {
|
|
159
|
+
return this.awaitTxPromises.get(txSig);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (this.txEventCache.has(txSig)) {
|
|
163
|
+
return Promise.resolve();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const promise = new Promise<void>((resolve) => {
|
|
167
|
+
this.awaitTxResolver.set(txSig, resolve);
|
|
168
|
+
});
|
|
169
|
+
this.awaitTxPromises.set(txSig, promise);
|
|
170
|
+
return promise;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public getEventList<Type extends keyof EventMap>(
|
|
174
|
+
eventType: Type
|
|
175
|
+
): EventList<Type> {
|
|
176
|
+
return this.eventListMap.get(eventType) as EventList<Type>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* This requires the EventList be cast to an array, which requires reallocation of memory.
|
|
181
|
+
* Would bias to using getEventList over getEvents
|
|
182
|
+
*
|
|
183
|
+
* @param eventType
|
|
184
|
+
*/
|
|
185
|
+
public getEventsArray<Type extends EventType>(
|
|
186
|
+
eventType: Type
|
|
187
|
+
): EventMap[Type][] {
|
|
188
|
+
return this.eventListMap.get(eventType).toArray() as EventMap[Type][];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
public getEventsByTx(txSig: TransactionSignature): WrappedEvents | undefined {
|
|
192
|
+
return this.txEventCache.get(txSig);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Connection,
|
|
3
|
+
Finality,
|
|
4
|
+
PublicKey,
|
|
5
|
+
TransactionSignature,
|
|
6
|
+
} from '@solana/web3.js';
|
|
7
|
+
|
|
8
|
+
type Log = { txSig: TransactionSignature; slot: number; logs: string[] };
|
|
9
|
+
type FetchLogsResponse = {
|
|
10
|
+
earliestTx: string;
|
|
11
|
+
mostRecentTx: string;
|
|
12
|
+
transactionLogs: Log[];
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function fetchLogs(
|
|
16
|
+
connection: Connection,
|
|
17
|
+
programId: PublicKey,
|
|
18
|
+
finality: Finality,
|
|
19
|
+
beforeTx?: TransactionSignature,
|
|
20
|
+
untilTx?: TransactionSignature
|
|
21
|
+
): Promise<FetchLogsResponse | undefined> {
|
|
22
|
+
const signatures = await connection.getSignaturesForAddress(
|
|
23
|
+
programId,
|
|
24
|
+
{
|
|
25
|
+
before: beforeTx,
|
|
26
|
+
until: untilTx,
|
|
27
|
+
},
|
|
28
|
+
finality
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const sortedSignatures = signatures.sort((a, b) =>
|
|
32
|
+
a.slot < b.slot ? -1 : 1
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const filteredSignatures = sortedSignatures.filter(
|
|
36
|
+
(signature) => !signature.err
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (filteredSignatures.length === 0) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const chunkedSignatures = chunk(filteredSignatures, 100);
|
|
44
|
+
|
|
45
|
+
const transactionLogs = (
|
|
46
|
+
await Promise.all(
|
|
47
|
+
chunkedSignatures.map(async (chunk) => {
|
|
48
|
+
const transactions = await connection.getTransactions(
|
|
49
|
+
chunk.map((confirmedSignature) => confirmedSignature.signature),
|
|
50
|
+
finality
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return transactions.map((transaction) => {
|
|
54
|
+
return {
|
|
55
|
+
txSig: transaction.transaction.signatures[0],
|
|
56
|
+
slot: transaction.slot,
|
|
57
|
+
logs: transaction.meta.logMessages,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
).flat();
|
|
63
|
+
|
|
64
|
+
const earliestTx = filteredSignatures[0].signature;
|
|
65
|
+
const mostRecentTx =
|
|
66
|
+
filteredSignatures[filteredSignatures.length - 1].signature;
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
transactionLogs: transactionLogs,
|
|
70
|
+
earliestTx: earliestTx,
|
|
71
|
+
mostRecentTx: mostRecentTx,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function chunk<T>(array: readonly T[], size: number): T[][] {
|
|
76
|
+
return new Array(Math.ceil(array.length / size))
|
|
77
|
+
.fill(null)
|
|
78
|
+
.map((_, index) => index * size)
|
|
79
|
+
.map((begin) => array.slice(begin, begin + size));
|
|
80
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { LogProvider, logProviderCallback } from './types';
|
|
2
|
+
import {
|
|
3
|
+
Commitment,
|
|
4
|
+
Connection,
|
|
5
|
+
Finality,
|
|
6
|
+
PublicKey,
|
|
7
|
+
TransactionSignature,
|
|
8
|
+
} from '@solana/web3.js';
|
|
9
|
+
import { fetchLogs } from './fetchLogs';
|
|
10
|
+
|
|
11
|
+
export class PollingLogProvider implements LogProvider {
|
|
12
|
+
private finality: Finality;
|
|
13
|
+
private intervalId: NodeJS.Timer;
|
|
14
|
+
private mostRecentSeenTx?: TransactionSignature;
|
|
15
|
+
private mutex: number;
|
|
16
|
+
|
|
17
|
+
public constructor(
|
|
18
|
+
private connection: Connection,
|
|
19
|
+
private programId: PublicKey,
|
|
20
|
+
commitment: Commitment,
|
|
21
|
+
private frequency = 15 * 1000
|
|
22
|
+
) {
|
|
23
|
+
this.finality = commitment === 'finalized' ? 'finalized' : 'confirmed';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public subscribe(callback: logProviderCallback): boolean {
|
|
27
|
+
if (this.intervalId) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.intervalId = setInterval(async () => {
|
|
32
|
+
if (this.mutex === 1) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.mutex = 1;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const response = await fetchLogs(
|
|
39
|
+
this.connection,
|
|
40
|
+
this.programId,
|
|
41
|
+
this.finality,
|
|
42
|
+
undefined,
|
|
43
|
+
this.mostRecentSeenTx
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (response === undefined) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const { mostRecentTx, transactionLogs } = response;
|
|
51
|
+
|
|
52
|
+
for (const { txSig, slot, logs } of transactionLogs) {
|
|
53
|
+
callback(txSig, slot, logs);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.mostRecentSeenTx = mostRecentTx;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error('PollingLogProvider threw an Error');
|
|
59
|
+
console.error(e);
|
|
60
|
+
} finally {
|
|
61
|
+
this.mutex = 0;
|
|
62
|
+
}
|
|
63
|
+
}, this.frequency);
|
|
64
|
+
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public isSubscribed(): boolean {
|
|
69
|
+
return this.intervalId !== undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async unsubscribe(): Promise<boolean> {
|
|
73
|
+
if (this.intervalId !== undefined) {
|
|
74
|
+
clearInterval(this.intervalId);
|
|
75
|
+
this.intervalId = undefined;
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
EventMap,
|
|
3
|
+
EventSubscriptionOrderBy,
|
|
4
|
+
EventSubscriptionOrderDirection,
|
|
5
|
+
EventType,
|
|
6
|
+
SortFn,
|
|
7
|
+
Event,
|
|
8
|
+
} from './types';
|
|
9
|
+
import { OrderRecord } from '../types';
|
|
10
|
+
import { PublicKey, ZERO } from '../index';
|
|
11
|
+
|
|
12
|
+
function clientSortAscFn(): 'less than' {
|
|
13
|
+
return 'less than';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function clientSortDescFn(): 'greater than' {
|
|
17
|
+
return 'greater than';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function defaultBlockchainSortFn(
|
|
21
|
+
currentEvent: EventMap[EventType],
|
|
22
|
+
newEvent: EventMap[EventType]
|
|
23
|
+
): 'less than' | 'greater than' {
|
|
24
|
+
return currentEvent.slot <= newEvent.slot ? 'less than' : 'greater than';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function orderRecordSortFn(
|
|
28
|
+
currentEvent: Event<OrderRecord>,
|
|
29
|
+
newEvent: Event<OrderRecord>
|
|
30
|
+
): 'less than' | 'greater than' {
|
|
31
|
+
const currentEventMarketIndex = !currentEvent.maker.equals(PublicKey.default)
|
|
32
|
+
? currentEvent.makerOrder.marketIndex
|
|
33
|
+
: currentEvent.takerOrder.marketIndex;
|
|
34
|
+
const newEventMarketIndex = !newEvent.maker.equals(PublicKey.default)
|
|
35
|
+
? newEvent.makerOrder.marketIndex
|
|
36
|
+
: newEvent.takerOrder.marketIndex;
|
|
37
|
+
if (!currentEventMarketIndex.eq(newEventMarketIndex)) {
|
|
38
|
+
return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (currentEvent.fillRecordId.gt(ZERO) && newEvent.fillRecordId.gt(ZERO)) {
|
|
42
|
+
return currentEvent.fillRecordId.lte(newEvent.fillRecordId)
|
|
43
|
+
? 'less than'
|
|
44
|
+
: 'greater than';
|
|
45
|
+
} else {
|
|
46
|
+
return currentEvent.ts.lte(newEvent.ts) ? 'less than' : 'greater than';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getSortFn(
|
|
51
|
+
orderBy: EventSubscriptionOrderBy,
|
|
52
|
+
orderDir: EventSubscriptionOrderDirection,
|
|
53
|
+
eventType: EventType
|
|
54
|
+
): SortFn {
|
|
55
|
+
if (orderBy === 'client') {
|
|
56
|
+
return orderDir === 'asc' ? clientSortAscFn : clientSortDescFn;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
switch (eventType) {
|
|
60
|
+
case 'OrderRecord':
|
|
61
|
+
return orderRecordSortFn;
|
|
62
|
+
default:
|
|
63
|
+
return defaultBlockchainSortFn;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { WrappedEvent, EventType } from './types';
|
|
2
|
+
|
|
3
|
+
class Node {
|
|
4
|
+
constructor(
|
|
5
|
+
public key: string,
|
|
6
|
+
public value: WrappedEvent<EventType>[],
|
|
7
|
+
public next?: Node,
|
|
8
|
+
public prev?: Node
|
|
9
|
+
) {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// lru cache
|
|
13
|
+
export class TxEventCache {
|
|
14
|
+
size = 0;
|
|
15
|
+
head?: Node;
|
|
16
|
+
tail?: Node;
|
|
17
|
+
cacheMap: { [key: string]: Node } = {};
|
|
18
|
+
|
|
19
|
+
constructor(public maxTx = 1024) {}
|
|
20
|
+
|
|
21
|
+
public add(key: string, events: WrappedEvent<EventType>[]): void {
|
|
22
|
+
const existingNode = this.cacheMap[key];
|
|
23
|
+
if (existingNode) {
|
|
24
|
+
this.detach(existingNode);
|
|
25
|
+
this.size--;
|
|
26
|
+
} else if (this.size === this.maxTx) {
|
|
27
|
+
delete this.cacheMap[this.tail.key];
|
|
28
|
+
this.detach(this.tail);
|
|
29
|
+
this.size--;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Write to head of LinkedList
|
|
33
|
+
if (!this.head) {
|
|
34
|
+
this.head = this.tail = new Node(key, events);
|
|
35
|
+
} else {
|
|
36
|
+
const node = new Node(key, events, this.head);
|
|
37
|
+
this.head.prev = node;
|
|
38
|
+
this.head = node;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// update cacheMap with LinkedList key and Node reference
|
|
42
|
+
this.cacheMap[key] = this.head;
|
|
43
|
+
this.size++;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public has(key: string): boolean {
|
|
47
|
+
return this.cacheMap.hasOwnProperty(key);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public get(key: string): WrappedEvent<EventType>[] | undefined {
|
|
51
|
+
return this.cacheMap[key]?.value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
detach(node: Node): void {
|
|
55
|
+
if (node.prev !== undefined) {
|
|
56
|
+
node.prev.next = node.next;
|
|
57
|
+
} else {
|
|
58
|
+
this.head = node.next;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (node.next !== undefined) {
|
|
62
|
+
node.next.prev = node.prev;
|
|
63
|
+
} else {
|
|
64
|
+
this.tail = node.prev;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public clear(): void {
|
|
69
|
+
this.head = undefined;
|
|
70
|
+
this.tail = undefined;
|
|
71
|
+
this.size = 0;
|
|
72
|
+
this.cacheMap = {};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Commitment, TransactionSignature } from '@solana/web3.js';
|
|
2
|
+
import {
|
|
3
|
+
DepositRecord,
|
|
4
|
+
FundingPaymentRecord,
|
|
5
|
+
FundingRateRecord,
|
|
6
|
+
LiquidationRecord,
|
|
7
|
+
OrderRecord,
|
|
8
|
+
} from '../index';
|
|
9
|
+
|
|
10
|
+
export type EventSubscriptionOptions = {
|
|
11
|
+
eventTypes?: EventType[];
|
|
12
|
+
maxEventsPerType?: number;
|
|
13
|
+
orderBy?: EventSubscriptionOrderBy;
|
|
14
|
+
orderDir?: EventSubscriptionOrderDirection;
|
|
15
|
+
commitment?: Commitment;
|
|
16
|
+
maxTx?: number;
|
|
17
|
+
logProviderConfig?: LogProviderConfig;
|
|
18
|
+
// when the subscription starts, client might want to backtrack and fetch old tx's
|
|
19
|
+
// this specifies how far to backtrack
|
|
20
|
+
untilTx?: TransactionSignature;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const DefaultEventSubscriptionOptions: EventSubscriptionOptions = {
|
|
24
|
+
eventTypes: [
|
|
25
|
+
'DepositRecord',
|
|
26
|
+
'FundingPaymentRecord',
|
|
27
|
+
'LiquidationRecord',
|
|
28
|
+
'OrderRecord',
|
|
29
|
+
'FundingRateRecord',
|
|
30
|
+
],
|
|
31
|
+
maxEventsPerType: 4096,
|
|
32
|
+
orderBy: 'blockchain',
|
|
33
|
+
orderDir: 'asc',
|
|
34
|
+
commitment: 'confirmed',
|
|
35
|
+
maxTx: 4096,
|
|
36
|
+
logProviderConfig: {
|
|
37
|
+
type: 'websocket',
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Whether we sort events based on order blockchain produced events or client receives events
|
|
42
|
+
export type EventSubscriptionOrderBy = 'blockchain' | 'client';
|
|
43
|
+
export type EventSubscriptionOrderDirection = 'asc' | 'desc';
|
|
44
|
+
|
|
45
|
+
export type Event<T> = T & {
|
|
46
|
+
txSig: TransactionSignature;
|
|
47
|
+
slot: number;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type WrappedEvent<Type extends EventType> = EventMap[Type] & {
|
|
51
|
+
eventType: Type;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type WrappedEvents = WrappedEvent<EventType>[];
|
|
55
|
+
|
|
56
|
+
export type EventMap = {
|
|
57
|
+
DepositRecord: Event<DepositRecord>;
|
|
58
|
+
FundingPaymentRecord: Event<FundingPaymentRecord>;
|
|
59
|
+
LiquidationRecord: Event<LiquidationRecord>;
|
|
60
|
+
FundingRateRecord: Event<FundingRateRecord>;
|
|
61
|
+
OrderRecord: Event<OrderRecord>;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type EventType = keyof EventMap;
|
|
65
|
+
|
|
66
|
+
export interface EventSubscriberEvents {
|
|
67
|
+
newEvent: (event: WrappedEvent<EventType>) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type SortFn = (
|
|
71
|
+
currentRecord: EventMap[EventType],
|
|
72
|
+
newRecord: EventMap[EventType]
|
|
73
|
+
) => 'less than' | 'greater than';
|
|
74
|
+
|
|
75
|
+
export type logProviderCallback = (
|
|
76
|
+
txSig: TransactionSignature,
|
|
77
|
+
slot: number,
|
|
78
|
+
logs: string[]
|
|
79
|
+
) => void;
|
|
80
|
+
|
|
81
|
+
export interface LogProvider {
|
|
82
|
+
isSubscribed(): boolean;
|
|
83
|
+
subscribe(callback: logProviderCallback): boolean;
|
|
84
|
+
unsubscribe(): Promise<boolean>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type WebSocketLogProviderConfig = {
|
|
88
|
+
type: 'websocket';
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type PollingLogProviderConfig = {
|
|
92
|
+
type: 'polling';
|
|
93
|
+
frequency: number;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type LogProviderConfig =
|
|
97
|
+
| WebSocketLogProviderConfig
|
|
98
|
+
| PollingLogProviderConfig;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { LogProvider, logProviderCallback } from './types';
|
|
2
|
+
import { Commitment, Connection, PublicKey } from '@solana/web3.js';
|
|
3
|
+
|
|
4
|
+
export class WebSocketLogProvider implements LogProvider {
|
|
5
|
+
private subscriptionId: number;
|
|
6
|
+
public constructor(
|
|
7
|
+
private connection: Connection,
|
|
8
|
+
private programId: PublicKey,
|
|
9
|
+
private commitment: Commitment
|
|
10
|
+
) {}
|
|
11
|
+
|
|
12
|
+
public subscribe(callback: logProviderCallback): boolean {
|
|
13
|
+
if (this.subscriptionId) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
this.subscriptionId = this.connection.onLogs(
|
|
18
|
+
this.programId,
|
|
19
|
+
(logs, ctx) => {
|
|
20
|
+
callback(logs.signature, ctx.slot, logs.logs);
|
|
21
|
+
},
|
|
22
|
+
this.commitment
|
|
23
|
+
);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public isSubscribed(): boolean {
|
|
28
|
+
return this.subscriptionId !== undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public async unsubscribe(): Promise<boolean> {
|
|
32
|
+
if (this.subscriptionId !== undefined) {
|
|
33
|
+
await this.connection.removeOnLogsListener(this.subscriptionId);
|
|
34
|
+
this.subscriptionId = undefined;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
ClearingHouse,
|
|
8
8
|
ClearingHouseUser,
|
|
9
9
|
initialize,
|
|
10
|
-
Markets,
|
|
11
10
|
PositionDirection,
|
|
12
11
|
convertToNumber,
|
|
13
12
|
calculateTradeSlippage,
|
|
14
13
|
MARK_PRICE_PRECISION,
|
|
15
14
|
QUOTE_PRECISION,
|
|
16
15
|
} from '..';
|
|
16
|
+
import { Banks } from '../constants/banks';
|
|
17
17
|
|
|
18
18
|
export const getTokenAddress = (
|
|
19
19
|
mintAddress: string,
|
|
@@ -63,15 +63,18 @@ const main = async () => {
|
|
|
63
63
|
const clearingHousePublicKey = new PublicKey(
|
|
64
64
|
sdkConfig.CLEARING_HOUSE_PROGRAM_ID
|
|
65
65
|
);
|
|
66
|
-
const clearingHouse = ClearingHouse
|
|
66
|
+
const clearingHouse = new ClearingHouse({
|
|
67
67
|
connection,
|
|
68
|
-
provider.wallet,
|
|
69
|
-
clearingHousePublicKey
|
|
70
|
-
);
|
|
68
|
+
wallet: provider.wallet,
|
|
69
|
+
programID: clearingHousePublicKey,
|
|
70
|
+
});
|
|
71
71
|
await clearingHouse.subscribe();
|
|
72
72
|
|
|
73
73
|
// Set up Clearing House user client
|
|
74
|
-
const user = ClearingHouseUser
|
|
74
|
+
const user = new ClearingHouseUser({
|
|
75
|
+
clearingHouse,
|
|
76
|
+
userAccountPublicKey: await clearingHouse.getUserAccountPublicKey(),
|
|
77
|
+
});
|
|
75
78
|
|
|
76
79
|
//// Check if clearing house account exists for the current wallet
|
|
77
80
|
const userAccountExists = await user.exists();
|
|
@@ -84,19 +87,21 @@ const main = async () => {
|
|
|
84
87
|
await getTokenAddress(
|
|
85
88
|
usdcTokenAddress.toString(),
|
|
86
89
|
wallet.publicKey.toString()
|
|
87
|
-
)
|
|
90
|
+
),
|
|
91
|
+
Banks['devnet'][0].bankIndex
|
|
88
92
|
);
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
await user.subscribe();
|
|
92
96
|
|
|
93
97
|
// Get current price
|
|
94
|
-
const solMarketInfo =
|
|
98
|
+
const solMarketInfo = sdkConfig.MARKETS.find(
|
|
95
99
|
(market) => market.baseAssetSymbol === 'SOL'
|
|
96
100
|
);
|
|
97
101
|
|
|
98
102
|
const currentMarketPrice = calculateMarkPrice(
|
|
99
|
-
clearingHouse.
|
|
103
|
+
clearingHouse.getMarketAccount(solMarketInfo.marketIndex),
|
|
104
|
+
undefined
|
|
100
105
|
);
|
|
101
106
|
|
|
102
107
|
const formattedPrice = convertToNumber(
|
|
@@ -107,14 +112,18 @@ const main = async () => {
|
|
|
107
112
|
console.log(`Current Market Price is $${formattedPrice}`);
|
|
108
113
|
|
|
109
114
|
// Estimate the slippage for a $5000 LONG trade
|
|
110
|
-
const solMarketAccount = clearingHouse.
|
|
115
|
+
const solMarketAccount = clearingHouse.getMarketAccount(
|
|
116
|
+
solMarketInfo.marketIndex
|
|
117
|
+
);
|
|
111
118
|
|
|
112
119
|
const longAmount = new BN(5000).mul(QUOTE_PRECISION);
|
|
113
120
|
const slippage = convertToNumber(
|
|
114
121
|
calculateTradeSlippage(
|
|
115
122
|
PositionDirection.LONG,
|
|
116
123
|
longAmount,
|
|
117
|
-
solMarketAccount
|
|
124
|
+
solMarketAccount,
|
|
125
|
+
'quote',
|
|
126
|
+
undefined
|
|
118
127
|
)[0],
|
|
119
128
|
MARK_PRICE_PRECISION
|
|
120
129
|
);
|