@thespidercode/openbook-swap
Version:
Ready-to-use swap tool using Openbook DEX
185 lines (163 loc) • 4.9 kB
text/typescript
import { bits, blob, struct, u32, u8 } from 'buffer-layout';
import {
accountFlagsLayout,
publicKeyLayout,
u128,
u64,
zeros,
} from './layout';
import BN from 'bn.js';
import { PublicKey } from '@solana/web3.js';
const REQUEST_QUEUE_HEADER = struct([
blob(5),
accountFlagsLayout('accountFlags'),
u32('head'),
zeros(4),
u32('count'),
zeros(4),
u32('nextSeqNum'),
zeros(4),
]);
const REQUEST_FLAGS = bits(u8(), false, 'requestFlags');
REQUEST_FLAGS.addBoolean('newOrder');
REQUEST_FLAGS.addBoolean('cancelOrder');
REQUEST_FLAGS.addBoolean('bid');
REQUEST_FLAGS.addBoolean('postOnly');
REQUEST_FLAGS.addBoolean('ioc');
const REQUEST = struct([
REQUEST_FLAGS,
u8('openOrdersSlot'),
u8('feeTier'),
blob(5),
u64('maxBaseSizeOrCancelId'),
u64('nativeQuoteQuantityLocked'),
u128('orderId'),
publicKeyLayout('openOrders'),
u64('clientOrderId'),
]);
const EVENT_QUEUE_HEADER = struct([
blob(5),
accountFlagsLayout('accountFlags'),
u32('head'),
zeros(4),
u32('count'),
zeros(4),
u32('seqNum'),
zeros(4),
]);
const EVENT_FLAGS = bits(u8(), false, 'eventFlags');
EVENT_FLAGS.addBoolean('fill');
EVENT_FLAGS.addBoolean('out');
EVENT_FLAGS.addBoolean('bid');
EVENT_FLAGS.addBoolean('maker');
const EVENT = struct([
EVENT_FLAGS,
u8('openOrdersSlot'),
u8('feeTier'),
blob(5),
u64('nativeQuantityReleased'), // Amount the user received
u64('nativeQuantityPaid'), // Amount the user paid
u64('nativeFeeOrRebate'),
u128('orderId'),
publicKeyLayout('openOrders'),
u64('clientOrderId'),
]);
export interface Event {
eventFlags: { fill: boolean; out: boolean; bid: boolean; maker: boolean };
seqNum?: number;
orderId: BN;
openOrders: PublicKey;
openOrdersSlot: number;
feeTier: number;
nativeQuantityReleased: BN;
nativeQuantityPaid: BN;
nativeFeeOrRebate: BN;
}
function decodeQueueItem(headerLayout: any, nodeLayout: any, buffer: Buffer, nodeIndex: any) {
return nodeLayout.decode(
buffer,
headerLayout.span + nodeIndex * nodeLayout.span,
);
}
function decodeQueue(
headerLayout: any,
nodeLayout: any,
buffer: Buffer,
history?: number,
) {
const header = headerLayout.decode(buffer);
const allocLen = Math.floor(
(buffer.length - headerLayout.span) / nodeLayout.span,
);
const nodes: any[] = [];
if (history) {
for (let i = 0; i < Math.min(history, allocLen); ++i) {
const nodeIndex =
(header.head + header.count + allocLen - 1 - i) % allocLen;
nodes.push(decodeQueueItem(headerLayout, nodeLayout, buffer, nodeIndex));
}
} else {
for (let i = 0; i < header.count; ++i) {
const nodeIndex = (header.head + i) % allocLen;
nodes.push(decodeQueueItem(headerLayout, nodeLayout, buffer, nodeIndex));
}
}
return { header, nodes };
}
export function decodeEventsSince(buffer: Buffer, lastSeqNum: number): Event[] {
const header = EVENT_QUEUE_HEADER.decode(buffer);
const allocLen = Math.floor(
(buffer.length - EVENT_QUEUE_HEADER.span) / EVENT.span,
);
// calculate number of missed events
// account for u32 & ringbuffer overflows
const modulo32Uint = 0x100000000;
let missedEvents = (header.seqNum - lastSeqNum + modulo32Uint) % modulo32Uint;
if (missedEvents > allocLen) {
missedEvents = allocLen - 1;
}
const startSeq = (header.seqNum - missedEvents + modulo32Uint) % modulo32Uint;
// define boundary indexes in ring buffer [start;end)
const endIndex = (header.head + header.count) % allocLen;
const startIndex = (endIndex - missedEvents + allocLen) % allocLen;
const results: Event[] = [];
for (let i = 0; i < missedEvents; ++i) {
const nodeIndex = (startIndex + i) % allocLen;
const event = decodeQueueItem(EVENT_QUEUE_HEADER, EVENT, buffer, nodeIndex);
event.seqNum = (startSeq + i) % modulo32Uint;
results.push(event);
}
return results;
}
export function decodeRequestQueue(buffer: Buffer, history?: number) {
const { header, nodes } = decodeQueue(
REQUEST_QUEUE_HEADER,
REQUEST,
buffer,
history,
);
if (!header.accountFlags.initialized || !header.accountFlags.requestQueue) {
throw new Error('Invalid requests queue');
}
return nodes;
}
export function decodeEventQueue(buffer: Buffer, history?: number): Event[] {
const { header, nodes } = decodeQueue(
EVENT_QUEUE_HEADER,
EVENT,
buffer,
history,
);
if (!header.accountFlags.initialized || !header.accountFlags.eventQueue) {
throw new Error('Invalid events queue');
}
return nodes;
}
export const REQUEST_QUEUE_LAYOUT = {
HEADER: REQUEST_QUEUE_HEADER,
NODE: REQUEST,
};
export const EVENT_QUEUE_LAYOUT = {
HEADER: EVENT_QUEUE_HEADER,
NODE: EVENT,
};