@drift-labs/sdk-browser
Version:
SDK for Drift Protocol
239 lines (214 loc) • 6.44 kB
text/typescript
import { getSignedMsgUserOrdersFilter } from '../memcmp';
import { WebSocketProgramAccountSubscriber } from '../accounts/webSocketProgramAccountSubscriber';
import { SignedMsgOrderId, SignedMsgUserOrdersAccount } from '../types';
import { Commitment, Context, PublicKey } from '@solana/web3.js';
import { ResubOpts } from '../accounts/types';
import { DriftClient } from '../driftClient';
import StrictEventEmitter from 'strict-event-emitter-types';
import { EventEmitter } from 'events';
export interface SignedMsgUserOrdersAccountSubscriberEvents {
onAccountUpdate: (
activeSignedMsgOrderIds: SignedMsgOrderId[],
authorityPubkey: PublicKey,
slot: number
) => void;
newSignedMsgOrderIds: (
newSignedMsgOrderIds: SignedMsgOrderId[],
authorityPubkey: PublicKey,
slot: number
) => void;
}
export class SignedMsgUserOrdersAccountSubscriber {
protected driftClient: DriftClient;
protected commitment: Commitment;
protected resubOpts?: ResubOpts;
protected resyncTimeoutId?: ReturnType<typeof setTimeout>;
protected resyncIntervalMs?: number;
protected decodeFn: (
name: string,
data: Buffer
) => SignedMsgUserOrdersAccount;
public signedMsgUserOrderAccounts = new Map<
string,
{ slot: number; signedMsgUserOrdersAccount: SignedMsgUserOrdersAccount }
>();
mostRecentSlot: number;
fetchPromise?: Promise<void>;
fetchPromiseResolver: () => void;
protected subscriber: WebSocketProgramAccountSubscriber<SignedMsgUserOrdersAccount>;
public eventEmitter: StrictEventEmitter<
EventEmitter,
SignedMsgUserOrdersAccountSubscriberEvents
>;
constructor({
driftClient,
commitment,
resubOpts,
decodeFn,
resyncIntervalMs,
}: {
driftClient: DriftClient;
commitment?: Commitment;
resubOpts?: ResubOpts;
decodeFn?: (name: string, data: Buffer) => SignedMsgUserOrdersAccount;
resyncIntervalMs?: number;
}) {
this.commitment = commitment ?? 'confirmed';
this.resubOpts = resubOpts;
this.driftClient = driftClient;
this.decodeFn =
decodeFn ??
this.driftClient.program.account.signedMsgUserOrders.coder.accounts.decodeUnchecked.bind(
this.driftClient.program.account.signedMsgUserOrders.coder.accounts
);
this.resyncIntervalMs = resyncIntervalMs;
this.eventEmitter = new EventEmitter();
this.resubOpts = resubOpts;
}
public async subscribe(): Promise<void> {
if (!this.subscriber) {
const filters = [getSignedMsgUserOrdersFilter()];
this.subscriber =
new WebSocketProgramAccountSubscriber<SignedMsgUserOrdersAccount>(
'SingedMsgUserOrdersAccountMap',
'SignedMsgUserOrders',
this.driftClient.program,
this.decodeFn,
{
filters,
commitment: this.commitment,
},
this.resubOpts
);
}
await this.subscriber.subscribe(
(
_accountId: PublicKey,
account: SignedMsgUserOrdersAccount,
context: Context
) => {
this.tryUpdateSignedMsgUserOrdersAccount(
account,
'decoded',
context.slot
);
}
);
await this.fetch();
if (this.resyncIntervalMs) {
const recursiveResync = () => {
this.resyncTimeoutId = setTimeout(() => {
this.fetch()
.catch((e) => {
console.error('Failed to resync in OrderSubscriber');
console.log(e);
})
.finally(() => {
if (!this.resyncTimeoutId) return;
recursiveResync();
});
}, this.resyncIntervalMs);
};
recursiveResync();
}
}
async fetch(): Promise<void> {
if (this.fetchPromise) {
return this.fetchPromise;
}
this.fetchPromise = new Promise((resolver) => {
this.fetchPromiseResolver = resolver;
});
const skipEventEmitting = this.signedMsgUserOrderAccounts.size === 0;
try {
const rpcResponseAndContext =
await this.driftClient.connection.getProgramAccounts(
this.driftClient.program.programId,
{
commitment: this.commitment,
filters: [getSignedMsgUserOrdersFilter()],
encoding: 'base64',
withContext: true,
}
);
const slot: number = rpcResponseAndContext.context.slot;
for (const programAccount of rpcResponseAndContext.value) {
this.tryUpdateSignedMsgUserOrdersAccount(
programAccount.account.data,
'buffer',
slot,
skipEventEmitting
);
await new Promise((resolve) => setTimeout(resolve, 0));
}
} catch (e) {
console.error(e);
} finally {
this.fetchPromiseResolver();
this.fetchPromise = undefined;
}
}
tryUpdateSignedMsgUserOrdersAccount(
data: Buffer | SignedMsgUserOrdersAccount,
dataType: 'buffer' | 'decoded',
slot: number,
skipEventEmitting = false
): void {
if (!this.mostRecentSlot || slot > this.mostRecentSlot) {
this.mostRecentSlot = slot;
}
const signedMsgUserOrdersAccount =
dataType === 'buffer'
? this.decodeFn('SignedMsgUserOrders', data as Buffer)
: (data as SignedMsgUserOrdersAccount);
const key = signedMsgUserOrdersAccount.authorityPubkey.toBase58();
const slotAndSignedMsgUserOrdersAccount =
this.signedMsgUserOrderAccounts.get(key);
if (
!slotAndSignedMsgUserOrdersAccount ||
slotAndSignedMsgUserOrdersAccount.slot <= slot
) {
if (!skipEventEmitting) {
this.eventEmitter.emit(
'onAccountUpdate',
signedMsgUserOrdersAccount.signedMsgOrderData.filter(
(signedMsgOrderId) => signedMsgOrderId.orderId !== 0
),
signedMsgUserOrdersAccount.authorityPubkey,
slot
);
}
const existingSignedMsgOrderIds =
slotAndSignedMsgUserOrdersAccount?.signedMsgUserOrdersAccount.signedMsgOrderData.map(
(signedMsgOrderId) => signedMsgOrderId.orderId
) ?? [];
const newSignedMsgOrderIds =
signedMsgUserOrdersAccount.signedMsgOrderData.filter(
(signedMsgOrderId: SignedMsgOrderId) =>
!existingSignedMsgOrderIds.includes(signedMsgOrderId.orderId) &&
signedMsgOrderId.orderId !== 0
);
if (newSignedMsgOrderIds.length > 0 && !skipEventEmitting) {
this.eventEmitter.emit(
'newSignedMsgOrderIds',
newSignedMsgOrderIds,
signedMsgUserOrdersAccount.authorityPubkey,
slot
);
}
this.signedMsgUserOrderAccounts.set(key, {
slot,
signedMsgUserOrdersAccount,
});
}
}
public async unsubscribe(): Promise<void> {
if (!this.subscriber) return;
await this.subscriber.unsubscribe();
this.subscriber = undefined;
if (this.resyncTimeoutId !== undefined) {
clearTimeout(this.resyncTimeoutId);
this.resyncTimeoutId = undefined;
}
}
}