UNPKG

@drift-labs/sdk

Version:
181 lines (180 loc) • 8.51 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SwiftOrderSubscriber = void 0; const perpMarkets_1 = require("../constants/perpMarkets"); const pda_1 = require("../addresses/pda"); const types_1 = require("../types"); const web3_js_1 = require("@solana/web3.js"); const tweetnacl_1 = __importDefault(require("tweetnacl")); const tweetnacl_util_1 = require("tweetnacl-util"); const ws_1 = __importDefault(require("ws")); const sha256_1 = require("@noble/hashes/sha256"); class SwiftOrderSubscriber { constructor(config) { this.config = config; this.heartbeatTimeout = null; this.heartbeatIntervalMs = 60000; this.ws = null; this.subscribed = false; this.driftClient = config.driftClient; this.userAccountGetter = config.userAccountGetter; } unsubscribe() { if (this.subscribed) { this.ws.removeAllListeners(); this.ws.terminate(); this.ws = null; this.subscribed = false; } } getSymbolForMarketIndex(marketIndex) { const markets = this.config.driftEnv === 'devnet' ? perpMarkets_1.DevnetPerpMarkets : perpMarkets_1.MainnetPerpMarkets; return markets[marketIndex].symbol; } generateChallengeResponse(nonce) { const messageBytes = (0, tweetnacl_util_1.decodeUTF8)(nonce); const signature = tweetnacl_1.default.sign.detached(messageBytes, this.config.keypair.secretKey); const signatureBase64 = Buffer.from(signature).toString('base64'); return signatureBase64; } handleAuthMessage(message) { var _a, _b; if (message['channel'] === 'auth' && message['nonce'] != null) { const signatureBase64 = this.generateChallengeResponse(message['nonce']); (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ pubkey: this.config.keypair.publicKey.toBase58(), signature: signatureBase64, })); } if (message['channel'] === 'auth' && ((_b = message['message']) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === 'authenticated') { this.subscribed = true; this.config.marketIndexes.forEach(async (marketIndex) => { var _a; (_a = this.ws) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ action: 'subscribe', market_type: 'perp', market_name: this.getSymbolForMarketIndex(marketIndex), })); await new Promise((resolve) => setTimeout(resolve, 100)); }); } } async subscribe(onOrder, acceptSanitized = false) { this.onOrder = onOrder; const endpoint = this.config.endpoint || this.config.driftEnv === 'devnet' ? 'wss://master.swift.drift.trade/ws' : 'wss://swift.drift.trade/ws'; const ws = new ws_1.default(endpoint + '?pubkey=' + this.config.keypair.publicKey.toBase58()); this.ws = ws; ws.on('open', async () => { console.log('Connected to the server'); ws.on('message', async (data) => { const message = JSON.parse(data.toString()); this.startHeartbeatTimer(); if (message['channel'] === 'auth') { this.handleAuthMessage(message); } if (message['order']) { const order = message['order']; // ignore likely sanitized orders by default if (order['will_sanitize'] === true && !acceptSanitized) { return; } const signedMsgOrderParamsBuf = Buffer.from(order['order_message'], 'hex'); const isDelegateSigner = signedMsgOrderParamsBuf .slice(0, 8) .equals(Uint8Array.from(Buffer.from((0, sha256_1.sha256)('global' + ':' + 'SignedMsgOrderParamsDelegateMessage')).slice(0, 8))); const signedMessage = this.driftClient.decodeSignedMsgOrderParamsMessage(signedMsgOrderParamsBuf, isDelegateSigner); if (!signedMessage.signedMsgOrderParams.price) { console.error(`order has no price: ${JSON.stringify(signedMessage.signedMsgOrderParams)}`); return; } onOrder(order, signedMessage, isDelegateSigner); } }); ws.on('close', () => { console.log('Disconnected from the server'); this.reconnect(); }); ws.on('error', (error) => { console.error('WebSocket error:', error); this.reconnect(); }); }); ws.on('unexpected-response', async (request, response) => { console.error('Unexpected response, reconnecting in 5s:', response.statusCode); setTimeout(() => { if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); this.reconnect(); }, 5000); }); ws.on('error', async (request, response) => { console.error('WS closed from error, reconnecting in 1s:', response.statusCode); setTimeout(() => { if (this.heartbeatTimeout) clearTimeout(this.heartbeatTimeout); this.reconnect(); }, 1000); }); } async getPlaceAndMakeSignedMsgOrderIxs(orderMessageRaw, signedMsgOrderParamsMessage, makerOrderParams) { if (!this.userAccountGetter) { throw new Error('userAccountGetter must be set to use this function'); } const signedMsgOrderParamsBuf = Buffer.from(orderMessageRaw['order_message'], 'hex'); const isDelegateSigner = signedMsgOrderParamsBuf .slice(0, 8) .equals(Uint8Array.from(Buffer.from((0, sha256_1.sha256)('global' + ':' + 'SignedMsgOrderParamsDelegateMessage')).slice(0, 8))); const signedMessage = this.driftClient.decodeSignedMsgOrderParamsMessage(signedMsgOrderParamsBuf, isDelegateSigner); const takerAuthority = new web3_js_1.PublicKey(orderMessageRaw['taker_authority']); const signingAuthority = new web3_js_1.PublicKey(orderMessageRaw['signing_authority']); const takerUserPubkey = isDelegateSigner ? signedMessage.takerPubkey : await (0, pda_1.getUserAccountPublicKey)(this.driftClient.program.programId, takerAuthority, signedMessage.subAccountId); const takerUserAccount = await this.userAccountGetter.mustGetUserAccount(takerUserPubkey.toString()); const ixs = await this.driftClient.getPlaceAndMakeSignedMsgPerpOrderIxs({ orderParams: signedMsgOrderParamsBuf, signature: Buffer.from(orderMessageRaw['order_signature'], 'base64'), }, (0, tweetnacl_util_1.decodeUTF8)(orderMessageRaw['uuid']), { taker: takerUserPubkey, takerUserAccount, takerStats: (0, pda_1.getUserStatsAccountPublicKey)(this.driftClient.program.programId, takerUserAccount.authority), signingAuthority: signingAuthority, }, Object.assign({}, makerOrderParams, { postOnly: types_1.PostOnlyParams.MUST_POST_ONLY, immediateOrCancel: true, marketType: types_1.MarketType.PERP, })); return ixs; } startHeartbeatTimer() { if (this.heartbeatTimeout) { clearTimeout(this.heartbeatTimeout); } if (!this.onOrder) { throw new Error('onOrder callback function must be set'); } this.heartbeatTimeout = setTimeout(() => { console.warn('No heartbeat received within 30 seconds, reconnecting...'); this.reconnect(); }, this.heartbeatIntervalMs); } reconnect() { if (this.ws) { this.ws.removeAllListeners(); this.ws.terminate(); } console.log('Reconnecting to WebSocket...'); setTimeout(() => { this.subscribe(this.onOrder); }, 1000); } } exports.SwiftOrderSubscriber = SwiftOrderSubscriber;