@drift-labs/sdk
Version:
SDK for Drift Protocol
197 lines (196 loc) • 9.29 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrderSubscriber = void 0;
const memcmp_1 = require("../memcmp");
const web3_js_1 = require("@solana/web3.js");
const buffer_1 = require("buffer");
const DLOB_1 = require("../dlob/DLOB");
const PollingSubscription_1 = require("./PollingSubscription");
const WebsocketSubscription_1 = require("./WebsocketSubscription");
const events_1 = require("events");
const anchor_1 = require("@coral-xyz/anchor");
const user_1 = require("../decode/user");
const grpcSubscription_1 = require("./grpcSubscription");
const userStatus_1 = require("../math/userStatus");
const orders_1 = require("../math/orders");
const numericConstants_1 = require("../constants/numericConstants");
class OrderSubscriber {
constructor(config) {
var _a, _b, _c, _d, _e;
this.usersAccounts = new Map();
this.driftClient = config.driftClient;
this.commitment = config.subscriptionConfig.commitment || 'processed';
if (config.subscriptionConfig.type === 'polling') {
this.subscription = new PollingSubscription_1.PollingSubscription({
orderSubscriber: this,
frequency: config.subscriptionConfig.frequency,
});
}
else if (config.subscriptionConfig.type === 'grpc') {
this.subscription = new grpcSubscription_1.grpcSubscription({
orderSubscriber: this,
grpcConfigs: config.subscriptionConfig.grpcConfigs,
skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
resubOpts: {
resubTimeoutMs: (_a = config.subscriptionConfig) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs,
logResubMessages: (_b = config.subscriptionConfig) === null || _b === void 0 ? void 0 : _b.logResubMessages,
},
resyncIntervalMs: config.subscriptionConfig.resyncIntervalMs,
decoded: config.decodeData,
});
}
else {
this.subscription = new WebsocketSubscription_1.WebsocketSubscription({
orderSubscriber: this,
commitment: this.commitment,
skipInitialLoad: config.subscriptionConfig.skipInitialLoad,
resubOpts: {
resubTimeoutMs: (_c = config.subscriptionConfig) === null || _c === void 0 ? void 0 : _c.resubTimeoutMs,
logResubMessages: (_d = config.subscriptionConfig) === null || _d === void 0 ? void 0 : _d.logResubMessages,
},
resyncIntervalMs: config.subscriptionConfig.resyncIntervalMs,
decoded: config.decodeData,
});
}
if ((_e = config.fastDecode) !== null && _e !== void 0 ? _e : true) {
this.decodeFn = (name, data) => (0, user_1.decodeUser)(data);
}
else {
this.decodeFn =
this.driftClient.program.account.user.coder.accounts.decodeUnchecked.bind(this.driftClient.program.account.user.coder.accounts);
}
this.eventEmitter = new events_1.EventEmitter();
this.fetchAllNonIdleUsers = config.fetchAllNonIdleUsers;
}
async subscribe() {
await this.subscription.subscribe();
}
async fetch() {
if (this.fetchPromise) {
return this.fetchPromise;
}
this.fetchPromise = new Promise((resolver) => {
this.fetchPromiseResolver = resolver;
});
const filters = this.fetchAllNonIdleUsers
? [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getNonIdleUserFilter)()]
: [(0, memcmp_1.getUserFilter)(), (0, memcmp_1.getUserWithOrderFilter)()];
try {
const rpcRequestArgs = [
this.driftClient.program.programId.toBase58(),
{
commitment: this.commitment,
filters,
encoding: 'base64',
withContext: true,
},
];
const rpcJSONResponse =
// @ts-ignore
await this.driftClient.connection._rpcRequest('getProgramAccounts', rpcRequestArgs);
const rpcResponseAndContext = rpcJSONResponse.result;
const slot = rpcResponseAndContext.context.slot;
for (const programAccount of rpcResponseAndContext.value) {
const key = programAccount.pubkey.toString();
this.tryUpdateUserAccount(key, 'raw', programAccount.account.data, slot);
// give event loop a chance to breathe
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
catch (e) {
console.error(e);
}
finally {
this.fetchPromiseResolver();
this.fetchPromise = undefined;
}
}
tryUpdateUserAccount(key, dataType, data, slot) {
if (!this.mostRecentSlot || slot > this.mostRecentSlot) {
this.mostRecentSlot = slot;
}
this.eventEmitter.emit('updateReceived', new web3_js_1.PublicKey(key), slot, dataType);
const slotAndUserAccount = this.usersAccounts.get(key);
if (!slotAndUserAccount || slotAndUserAccount.slot <= slot) {
let userAccount;
// Polling leads to a lot of redundant decoding, so we only decode if data is from a fresh slot
if (dataType === 'raw') {
// @ts-ignore
const buffer = buffer_1.Buffer.from(data[0], data[1]);
const newLastActiveSlot = new anchor_1.BN(buffer.subarray(4328, 4328 + 8), undefined, 'le');
if (slotAndUserAccount &&
slotAndUserAccount.userAccount.lastActiveSlot.gt(newLastActiveSlot)) {
return;
}
userAccount = this.decodeFn('User', buffer);
}
else if (dataType === 'buffer') {
const buffer = data;
const newLastActiveSlot = new anchor_1.BN(buffer.subarray(4328, 4328 + 8), undefined, 'le');
if (slotAndUserAccount &&
slotAndUserAccount.userAccount.lastActiveSlot.gt(newLastActiveSlot)) {
return;
}
userAccount = this.decodeFn('User', data);
}
else {
userAccount = data;
}
this.eventEmitter.emit('userUpdated', userAccount, new web3_js_1.PublicKey(key), slot, dataType);
const newOrders = userAccount.orders.filter((order) => {
var _a;
return order.slot.toNumber() > ((_a = slotAndUserAccount === null || slotAndUserAccount === void 0 ? void 0 : slotAndUserAccount.slot) !== null && _a !== void 0 ? _a : 0) &&
order.slot.toNumber() <= slot;
});
if (newOrders.length > 0) {
this.eventEmitter.emit('orderCreated', userAccount, newOrders, new web3_js_1.PublicKey(key), slot, dataType);
}
this.usersAccounts.set(key, { slot, userAccount });
}
}
/**
* Creates a new DLOB for the order subscriber to fill. This will allow a
* caller to extend the DLOB Subscriber with a custom DLOB type.
* @returns New, empty DLOB object.
*/
createDLOB(protectedMakerParamsMap) {
return new DLOB_1.DLOB(protectedMakerParamsMap);
}
async getDLOB(slot, protectedMakerParamsMap) {
var _a;
const dlob = this.createDLOB(protectedMakerParamsMap);
for (const [key, { userAccount }] of this.usersAccounts.entries()) {
const protectedMaker = (0, userStatus_1.isUserProtectedMaker)(userAccount);
for (const order of userAccount.orders) {
let baseAssetAmount = order.baseAssetAmount;
if (order.reduceOnly) {
const existingBaseAmount = ((_a = userAccount.perpPositions.find((pos) => pos.marketIndex === order.marketIndex && pos.openOrders > 0)) === null || _a === void 0 ? void 0 : _a.baseAssetAmount) || numericConstants_1.ZERO;
baseAssetAmount = (0, orders_1.calculateOrderBaseAssetAmount)(order, existingBaseAmount);
}
dlob.insertOrder(order, key, slot, protectedMaker, baseAssetAmount);
}
}
return dlob;
}
getSlot() {
var _a;
return (_a = this.mostRecentSlot) !== null && _a !== void 0 ? _a : 0;
}
async addPubkey(userAccountPublicKey) {
const accountInfo = await this.driftClient.connection.getAccountInfoAndContext(userAccountPublicKey, this.commitment);
if (accountInfo) {
this.tryUpdateUserAccount(userAccountPublicKey.toString(), 'buffer', accountInfo.value.data, accountInfo.context.slot);
}
}
async mustGetUserAccount(key) {
if (!this.usersAccounts.has(key)) {
await this.addPubkey(new web3_js_1.PublicKey(key));
}
return this.usersAccounts.get(key).userAccount;
}
async unsubscribe() {
this.usersAccounts.clear();
await this.subscription.unsubscribe();
}
}
exports.OrderSubscriber = OrderSubscriber;