UNPKG

@cks-systems/manifest-sdk

Version:
186 lines (185 loc) 7.86 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FillFeed = void 0; const ws_1 = __importDefault(require("ws")); const FillLog_1 = require("./manifest/accounts/FillLog"); const manifest_1 = require("./manifest"); const numbers_1 = require("./utils/numbers"); const discriminator_1 = require("./utils/discriminator"); const promClient = __importStar(require("prom-client")); // For live monitoring of the fill feed. For a more complete look at fill // history stats, need to index all trades. const fills = new promClient.Counter({ name: 'fills', help: 'Number of fills', labelNames: ['market', 'isGlobal', 'takerIsBuy'], }); /** * FillFeed example implementation. */ class FillFeed { connection; wss; shouldEnd = false; ended = false; lastUpdateUnix = Date.now(); constructor(connection) { this.connection = connection; this.wss = new ws_1.default.Server({ port: 1234 }); this.wss.on('connection', (ws) => { console.log('New client connected'); ws.on('message', (message) => { console.log(`Received message: ${message}`); }); ws.on('close', () => { console.log('Client disconnected'); }); }); } msSinceLastUpdate() { return Date.now() - this.lastUpdateUnix; } async stopParseLogs() { this.shouldEnd = true; const start = Date.now(); while (!this.ended) { const timeout = 30_000; const pollInterval = 500; if (Date.now() - start > timeout) { return Promise.reject(new Error(`failed to stop parseLogs after ${timeout / 1_000} seconds`)); } await new Promise((resolve) => setTimeout(resolve, pollInterval)); } return Promise.resolve(); } /** * Parse logs in an endless loop. */ async parseLogs(endEarly) { // Start with a hopefully recent signature. const lastSignatureStatus = (await this.connection.getSignaturesForAddress(manifest_1.PROGRAM_ID, { limit: 1 }, 'finalized'))[0]; let lastSignature = lastSignatureStatus.signature; let lastSlot = lastSignatureStatus.slot; // End early is 30 seconds, used for testing. const endTime = endEarly ? new Date(Date.now() + 30_000) : new Date(Date.now() + 1_000_000_000_000); // TODO: remove endTime in favor of stopParseLogs for testing while (!this.shouldEnd && new Date(Date.now()) < endTime) { await new Promise((f) => setTimeout(f, 10_000)); const signatures = await this.connection.getSignaturesForAddress(manifest_1.PROGRAM_ID, { until: lastSignature, }, 'finalized'); // Flip it so we do oldest first. signatures.reverse(); // If there is only 1, do not use it because it could get stuck on the same sig. if (signatures.length <= 1) { continue; } for (const signature of signatures) { // Separately track the last slot. This is necessary because sometimes // gsfa ignores the until param and just gives 1_000 signatures. if (signature.slot < lastSlot) { continue; } await this.handleSignature(signature); } console.log('New last signature:', signatures[signatures.length - 1].signature, 'New last signature slot:', signatures[signatures.length - 1].slot, 'num sigs', signatures.length); lastSignature = signatures[signatures.length - 1].signature; lastSlot = signatures[signatures.length - 1].slot; this.lastUpdateUnix = Date.now(); } console.log('ended loop'); this.wss.close(); this.ended = true; } /** * Handle a signature by fetching the tx onchain and possibly sending a fill * notification. */ async handleSignature(signature) { console.log('Handling', signature.signature, 'slot', signature.slot); const tx = await this.connection.getTransaction(signature.signature, { maxSupportedTransactionVersion: 0, }); if (!tx?.meta?.logMessages) { console.log('No log messages'); return; } if (tx.meta.err != null) { console.log('Skipping failed tx', signature.signature); return; } const messages = tx?.meta?.logMessages; const programDatas = messages.filter((message) => { return message.includes('Program data:'); }); if (programDatas.length == 0) { console.log('No program datas'); return; } for (const programDataEntry of programDatas) { const programData = programDataEntry.split(' ')[2]; const byteArray = Uint8Array.from(atob(programData), (c) => c.charCodeAt(0)); const buffer = Buffer.from(byteArray); if (!buffer.subarray(0, 8).equals(fillDiscriminant)) { continue; } const deserializedFillLog = FillLog_1.FillLog.deserialize(buffer.subarray(8))[0]; const resultString = JSON.stringify(toFillLogResult(deserializedFillLog, signature.slot, signature.signature)); console.log('Got a fill', resultString); fills.inc({ market: deserializedFillLog.market.toString(), isGlobal: deserializedFillLog.isMakerGlobal.toString(), takerIsBuy: deserializedFillLog.takerIsBuy.toString(), }); this.wss.clients.forEach((client) => { client.send(JSON.stringify(toFillLogResult(deserializedFillLog, signature.slot, signature.signature))); }); } } } exports.FillFeed = FillFeed; const fillDiscriminant = (0, discriminator_1.genAccDiscriminator)('manifest::logs::FillLog'); function toFillLogResult(fillLog, slot, signature) { return { market: fillLog.market.toBase58(), maker: fillLog.maker.toBase58(), taker: fillLog.taker.toBase58(), baseAtoms: fillLog.baseAtoms.inner.toString(), quoteAtoms: fillLog.quoteAtoms.inner.toString(), priceAtoms: (0, numbers_1.convertU128)(fillLog.price.inner), takerIsBuy: fillLog.takerIsBuy, isMakerGlobal: fillLog.isMakerGlobal, makerSequenceNumber: fillLog.makerSequenceNumber.toString(), takerSequenceNumber: fillLog.takerSequenceNumber.toString(), signature, slot, }; }