UNPKG

@drift-labs/sdk

Version:
197 lines (196 loc) • 9.29 kB
"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;