UNPKG

@drift-labs/sdk-browser

Version:
379 lines (378 loc) 15.1 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.grpcMultiAccountSubscriber = void 0; const web3_js_1 = require("@solana/web3.js"); const Buffer = __importStar(require("buffer")); const bs58_1 = __importDefault(require("bs58")); const grpc_1 = require("../isomorphic/grpc"); function commitmentLevelToCommitment(commitmentLevel) { switch (commitmentLevel) { case grpc_1.CommitmentLevel.PROCESSED: return 'processed'; case grpc_1.CommitmentLevel.CONFIRMED: return 'confirmed'; case grpc_1.CommitmentLevel.FINALIZED: return 'finalized'; default: return 'confirmed'; } } class grpcMultiAccountSubscriber { constructor(client, commitmentLevel, accountName, program, decodeBuffer, resubOpts, onUnsubscribe, accountPropsMap) { this.isUnsubscribing = false; this.receivingData = false; this.subscribedAccounts = new Set(); this.onChangeMap = new Map(); this.dataMap = new Map(); this.accountPropsMap = new Map(); this.bufferMap = new Map(); this.client = client; this.commitmentLevel = commitmentLevel; this.accountName = accountName; this.program = program; this.decodeBufferFn = decodeBuffer; this.resubOpts = resubOpts; this.onUnsubscribe = onUnsubscribe; this.accountPropsMap = accountPropsMap; } static async create(grpcConfigs, accountName, program, decodeBuffer, resubOpts, clientProp, onUnsubscribe, accountPropsMap) { var _a, _b; const client = clientProp ? clientProp : await (0, grpc_1.createClient)(grpcConfigs.endpoint, grpcConfigs.token, (_a = grpcConfigs.channelOptions) !== null && _a !== void 0 ? _a : {}); const commitmentLevel = // @ts-ignore :: isomorphic exported enum fails typescript but will work at runtime (_b = grpcConfigs.commitmentLevel) !== null && _b !== void 0 ? _b : grpc_1.CommitmentLevel.CONFIRMED; return new grpcMultiAccountSubscriber(client, commitmentLevel, accountName, program, decodeBuffer, resubOpts, onUnsubscribe, accountPropsMap); } setAccountData(accountPubkey, data, slot) { this.dataMap.set(accountPubkey, { data, slot }); } getAccountData(accountPubkey) { return this.dataMap.get(accountPubkey); } getAccountDataMap() { return this.dataMap; } async fetch() { var _a; try { // Chunk account IDs into groups of 100 (getMultipleAccounts limit) const chunkSize = 100; const chunks = []; const accountIds = Array.from(this.subscribedAccounts.values()); for (let i = 0; i < accountIds.length; i += chunkSize) { chunks.push(accountIds.slice(i, i + chunkSize)); } // Process all chunks concurrently await Promise.all(chunks.map(async (chunk) => { const accountAddresses = chunk.map((accountId) => new web3_js_1.PublicKey(accountId)); const rpcResponseAndContext = await this.program.provider.connection.getMultipleAccountsInfoAndContext(accountAddresses, { commitment: commitmentLevelToCommitment(this.commitmentLevel), }); const rpcResponse = rpcResponseAndContext.value; const currentSlot = rpcResponseAndContext.context.slot; for (let i = 0; i < chunk.length; i++) { const accountId = chunk[i]; const accountInfo = rpcResponse[i]; if (accountInfo) { const prev = this.bufferMap.get(accountId); const newBuffer = accountInfo.data; if (prev && currentSlot < prev.slot) { continue; } if (prev && prev.buffer && newBuffer && newBuffer.equals(prev.buffer)) { continue; } this.bufferMap.set(accountId, { buffer: newBuffer, slot: currentSlot, }); const accountDecoded = this.program.coder.accounts.decode(this.capitalize(this.accountName), newBuffer); this.setAccountData(accountId, accountDecoded, currentSlot); } } })); } catch (error) { if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) { console.log(`[${this.accountName}] grpcMultiAccountSubscriber error fetching accounts:`, error); } } } async subscribe(accounts, onChange) { var _a; if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.logResubMessages) { console.log(`[${this.accountName}] grpcMultiAccountSubscriber subscribe`); } if (this.listenerId != null || this.isUnsubscribing) { return; } // Track accounts and single onChange for all for (const pk of accounts) { const key = pk.toBase58(); this.subscribedAccounts.add(key); this.onChangeMap.set(key, (data, ctx, buffer, accountProps) => { this.setAccountData(key, data, ctx.slot); onChange(new web3_js_1.PublicKey(key), data, ctx, buffer, accountProps); }); } this.stream = (await this.client.subscribe()); const request = { slots: {}, accounts: { account: { account: accounts.map((a) => a.toBase58()), owner: [], filters: [], }, }, transactions: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], commitment: this.commitmentLevel, entry: {}, transactionsStatus: {}, }; this.stream.on('data', (chunk) => { var _a, _b; if (!chunk.account) { return; } const slot = Number(chunk.account.slot); const accountPubkeyBytes = chunk.account.account.pubkey; const accountPubkey = bs58_1.default.encode(accountPubkeyBytes); if (!accountPubkey || !this.subscribedAccounts.has(accountPubkey)) { return; } // Touch resub timer on any incoming account update for subscribed keys if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) { this.receivingData = true; clearTimeout(this.timeoutId); this.setTimeout(); } // Skip processing if we already have data for this account at a newer slot const existing = this.dataMap.get(accountPubkey); if ((existing === null || existing === void 0 ? void 0 : existing.slot) !== undefined && existing.slot > slot) { return; } const accountInfo = { owner: new web3_js_1.PublicKey(chunk.account.account.owner), lamports: Number(chunk.account.account.lamports), data: Buffer.Buffer.from(chunk.account.account.data), executable: chunk.account.account.executable, rentEpoch: Number(chunk.account.account.rentEpoch), }; const context = { slot }; const buffer = accountInfo.data; // Check existing buffer for this account and skip if unchanged or slot regressed const prevBuffer = this.bufferMap.get(accountPubkey); if (prevBuffer && slot < prevBuffer.slot) { return; } if (prevBuffer && prevBuffer.buffer && buffer && buffer.equals(prevBuffer.buffer)) { return; } this.bufferMap.set(accountPubkey, { buffer, slot }); const accountProps = (_b = this.accountPropsMap) === null || _b === void 0 ? void 0 : _b.get(accountPubkey); const handleDataBuffer = (context, buffer, accountProps) => { const data = this.decodeBufferFn ? this.decodeBufferFn(buffer, accountPubkey, accountProps) : this.program.account[this.accountName].coder.accounts.decode(this.capitalize(this.accountName), buffer); const handler = this.onChangeMap.get(accountPubkey); if (handler) { handler(data, context, buffer, accountProps); } }; if (Array.isArray(accountProps)) { for (const props of accountProps) { handleDataBuffer(context, buffer, props); } } else { handleDataBuffer(context, buffer, accountProps); } }); return new Promise((resolve, reject) => { this.stream.write(request, (err) => { var _a; if (err === null || err === undefined) { this.listenerId = 1; if ((_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs) { this.receivingData = true; } resolve(); } else { reject(err); } }); }).catch((reason) => { console.error(reason); throw reason; }); } async addAccounts(accounts) { for (const pk of accounts) { this.subscribedAccounts.add(pk.toBase58()); } const request = { slots: {}, accounts: { account: { account: Array.from(this.subscribedAccounts.values()), owner: [], filters: [], }, }, transactions: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], commitment: this.commitmentLevel, entry: {}, transactionsStatus: {}, }; await new Promise((resolve, reject) => { this.stream.write(request, (err) => { if (err === null || err === undefined) { resolve(); } else { reject(err); } }); }); await this.fetch(); } async removeAccounts(accounts) { for (const pk of accounts) { const k = pk.toBase58(); this.subscribedAccounts.delete(k); this.onChangeMap.delete(k); } const request = { slots: {}, accounts: { account: { account: Array.from(this.subscribedAccounts.values()), owner: [], filters: [], }, }, transactions: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], commitment: this.commitmentLevel, entry: {}, transactionsStatus: {}, }; await new Promise((resolve, reject) => { this.stream.write(request, (err) => { if (err === null || err === undefined) { resolve(); } else { reject(err); } }); }); } async unsubscribe() { this.isUnsubscribing = true; clearTimeout(this.timeoutId); this.timeoutId = undefined; if (this.listenerId != null) { const promise = new Promise((resolve, reject) => { const request = { slots: {}, accounts: {}, transactions: {}, blocks: {}, blocksMeta: {}, accountsDataSlice: [], entry: {}, transactionsStatus: {}, }; this.stream.write(request, (err) => { if (err === null || err === undefined) { this.listenerId = undefined; this.isUnsubscribing = false; resolve(); } else { reject(err); } }); }).catch((reason) => { console.error(reason); throw reason; }); return promise; } else { this.isUnsubscribing = false; } if (this.onUnsubscribe) { try { await this.onUnsubscribe(); } catch (e) { console.error(e); } } } setTimeout() { var _a; this.timeoutId = setTimeout(async () => { if (this.isUnsubscribing) { return; } if (this.receivingData) { await this.unsubscribe(); this.receivingData = false; } }, (_a = this.resubOpts) === null || _a === void 0 ? void 0 : _a.resubTimeoutMs); } capitalize(value) { if (!value) return value; return value.charAt(0).toUpperCase() + value.slice(1); } } exports.grpcMultiAccountSubscriber = grpcMultiAccountSubscriber;