From 3fd28e535b909d02fc04ce9af98ff568bb9e5682 Mon Sep 17 00:00:00 2001 From: Nishad Date: Tue, 25 Aug 2020 06:30:44 +0800 Subject: [PATCH] Update program ID, update layouts --- src/instructions.js | 34 +++++++++++- src/instructions.test.js | 2 +- src/market.ts | 108 ++++++++++++++++++++++++++++++++++----- src/queue.ts | 21 +++++--- src/slab.ts | 11 +++- 5 files changed, 151 insertions(+), 25 deletions(-) diff --git a/src/instructions.js b/src/instructions.js index 0ef2e1b..c7fee75 100644 --- a/src/instructions.js +++ b/src/instructions.js @@ -11,7 +11,7 @@ import { PublicKey, TransactionInstruction } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID } from './token-instructions'; export const DEX_PROGRAM_ID = new PublicKey( - '8FxUAcZc27zBH39TfH1G7qVqDTZn3Bm4ym1qxWnAsdKa', + '3v9kjrBLN7Awr9BGC2qmFnWLM1EgMAdNm2rXLQFUcQ2d', ); export const INSTRUCTION_LAYOUT = new VersionedLayout( @@ -36,6 +36,7 @@ INSTRUCTION_LAYOUT.inner.addVariant( u64('limitPrice'), u64('maxQuantity'), orderTypeLayout('orderType'), + u64('clientId'), ]), 'newOrder', ); @@ -52,6 +53,11 @@ INSTRUCTION_LAYOUT.inner.addVariant( 'cancelOrder', ); INSTRUCTION_LAYOUT.inner.addVariant(5, struct([]), 'settleFunds'); +INSTRUCTION_LAYOUT.inner.addVariant( + 6, + struct([u64('clientId')]), + 'cancelOrderByClientId', +); export function encodeInstruction(instruction) { const b = Buffer.alloc(100); @@ -112,6 +118,7 @@ export class DexInstructions { limitPrice, maxQuantity, orderType, + clientId, }) { return new TransactionInstruction({ keys: [ @@ -126,7 +133,9 @@ export class DexInstructions { ], programId: DEX_PROGRAM_ID, data: encodeInstruction({ - newOrder: { side, limitPrice, maxQuantity, orderType }, + newOrder: clientId + ? { side, limitPrice, maxQuantity, orderType, clientId } + : { side, limitPrice, maxQuantity, orderType }, }), }); } @@ -195,6 +204,27 @@ export class DexInstructions { }); } + static cancelOrderByClientId({ + market, + openOrders, + owner, + requestQueue, + clientId, + }) { + return new TransactionInstruction({ + keys: [ + { pubkey: market, isSigner: false, isWritable: false }, + { pubkey: openOrders, isSigner: false, isWritable: true }, + { pubkey: requestQueue, isSigner: false, isWritable: true }, + { pubkey: owner, isSigner: true, isWritable: false }, + ], + programId: DEX_PROGRAM_ID, + data: encodeInstruction({ + cancelOrderByClientId: { clientId }, + }), + }); + } + static settleFunds({ market, openOrders, diff --git a/src/instructions.test.js b/src/instructions.test.js index c62e2fe..db1d940 100644 --- a/src/instructions.test.js +++ b/src/instructions.test.js @@ -27,7 +27,7 @@ describe('instruction', () => { }, }); expect(b.toString('hex')).toEqual( - '0001000000010000000a00000000000000050000000000000002000000', + '0001000000010000000a000000000000000500000000000000020000000000000000000000', ); }); }); diff --git a/src/market.ts b/src/market.ts index 81d6306..333972a 100644 --- a/src/market.ts +++ b/src/market.ts @@ -143,11 +143,18 @@ export class Market { this.loadAsks(connection), this.findOpenOrdersAccountsForOwner(connection, ownerAddress), ]); - return [...bids, ...asks].filter((order) => + const orders = [...bids, ...asks].filter((order) => openOrdersAccounts.some((openOrders) => order.openOrdersAddress.equals(openOrders.address), ), ); + // return orders; + return orders.map((order) => ({ + ...order, + clientId: openOrdersAccounts.find((openOrders) => + order.openOrdersAddress.equals(openOrders.address), + )?.clientIds[order.openOrdersSlot], + })); } async findBaseTokenAccountsForOwner( @@ -185,7 +192,15 @@ export class Market { async placeOrder( connection: Connection, - { owner, payer, side, price, size, orderType = 'limit' }: OrderParams, + { + owner, + payer, + side, + price, + size, + orderType = 'limit', + clientId, + }: OrderParams, ) { const { transaction, signers } = await this.makePlaceOrderTransaction( connection, @@ -196,6 +211,7 @@ export class Market { price, size, orderType, + clientId, }, ); return await this._sendTransaction(connection, transaction, signers); @@ -203,7 +219,15 @@ export class Market { async makePlaceOrderTransaction( connection: Connection, - { owner, payer, side, price, size, orderType = 'limit' }: OrderParams, + { + owner, + payer, + side, + price, + size, + orderType = 'limit', + clientId, + }: OrderParams, ) { // @ts-ignore const ownerAddress: PublicKey = owner.publicKey ?? owner; @@ -248,6 +272,7 @@ export class Market { limitPrice: this.priceNumberToLots(price), maxQuantity: this.baseSizeNumberToLots(size), orderType, + clientId, }), ); return { transaction, signers }; @@ -273,6 +298,40 @@ export class Market { return signature; } + async cancelOrderByClientId( + connection: Connection, + owner: Account, + openOrders: PublicKey, + clientId: BN, + ) { + const transaction = await this.makeCancelOrderByClientIdTransaction( + connection, + owner.publicKey, + openOrders, + clientId, + ); + return await this._sendTransaction(connection, transaction, [owner]); + } + + async makeCancelOrderByClientIdTransaction( + connection: Connection, + owner: PublicKey, + openOrders: PublicKey, + clientId: BN, + ) { + const transaction = new Transaction(); + transaction.add( + DexInstructions.cancelOrderByClientId({ + market: this.address, + owner, + openOrders, + requestQueue: this._decoded.requestQueue, + clientId, + }), + ); + return transaction; + } + async cancelOrder(connection: Connection, owner: Account, order: Order) { const transaction = await this.makeCancelOrderTransaction( connection, @@ -372,23 +431,33 @@ export class Market { ); const events = decodeEventQueue(data, limit); return events - .filter((event) => event.eventFlags.fill && event.quantityPaid.gtn(0)) + .filter( + (event) => event.eventFlags.fill && event.nativeQuantityPaid.gtn(0), + ) .map((event) => event.eventFlags.bid ? { ...event, - size: this.baseSizeLotsToNumber(event.quantityReleased), - price: this.priceLotsToNumber( - event.quantityPaid.divRound(event.quantityReleased), - ), + size: this.baseSplSizeToNumber(event.nativeQuantityReleased), + price: event.nativeQuantityPaid + .mul(this._quoteSplTokenMultiplier) + .divRound( + event.nativeQuantityReleased.mul( + this._baseSplTokenMultiplier, + ), + ) + .toNumber(), side: 'buy', } : { ...event, - size: this.baseSizeLotsToNumber(event.quantityPaid), - price: this.priceLotsToNumber( - event.quantityReleased.divRound(event.quantityPaid), - ), + size: this.baseSplSizeToNumber(event.nativeQuantityPaid), + price: event.nativeQuantityReleased + .mul(this._quoteSplTokenMultiplier) + .divRound( + event.nativeQuantityPaid.mul(this._baseSplTokenMultiplier), + ) + .toNumber(), side: 'sell', }, ); @@ -421,6 +490,14 @@ export class Market { ); } + baseSplSizeToNumber(size: BN) { + return divideBnToNumber(size, this._baseSplTokenMultiplier); + } + + quoteSplSizeToNumber(size: BN) { + return divideBnToNumber(size, this._quoteSplTokenMultiplier); + } + baseSizeLotsToNumber(size: BN) { return divideBnToNumber( size.mul(this._decoded.baseLotSize), @@ -491,6 +568,7 @@ export interface OrderParams { price: number; size: number; orderType?: 'limit' | 'ioc' | 'postOnly'; + clientId?: BN; } export const OPEN_ORDERS_LAYOUT = struct([ @@ -511,6 +589,7 @@ export const OPEN_ORDERS_LAYOUT = struct([ u128('isBidBits'), seq(u128(), 128, 'orders'), + seq(u64(), 128, 'clientIds'), blob(7), ]); @@ -526,6 +605,7 @@ export class OpenOrders { quoteTokenTotal!: BN; orders!: BN[]; + clientIds!: BN[]; constructor(address: PublicKey, decoded) { this.address = address; @@ -662,12 +742,13 @@ export class Orderbook { } *[Symbol.iterator](): Generator { - for (const { key, ownerSlot, owner, quantity } of this.slab) { + for (const { key, ownerSlot, owner, quantity, feeTier } of this.slab) { const price = getPriceFromKey(key); yield { orderId: key, openOrdersAddress: owner, openOrdersSlot: ownerSlot, + feeTier, price: this.market.priceLotsToNumber(price), priceLots: price, size: this.market.baseSizeLotsToNumber(quantity), @@ -685,6 +766,7 @@ export interface Order { price: number; priceLots: BN; size: number; + feeTier: number; sizeLots: BN; side: 'buy' | 'sell'; } diff --git a/src/queue.ts b/src/queue.ts index b195228..9f73814 100644 --- a/src/queue.ts +++ b/src/queue.ts @@ -31,9 +31,10 @@ REQUEST_FLAGS.addBoolean('ioc'); const REQUEST = struct([ REQUEST_FLAGS, u8('openOrdersSlot'), - blob(6), + u8('feeTier'), + blob(5), u64('maxBaseSizeOrCancelId'), - u64('maxQuoteSize'), + u64('nativeQuoteQuantityLocked'), u128('orderId'), publicKeyLayout('openOrders'), ]); @@ -46,6 +47,8 @@ const EVENT_QUEUE_HEADER = struct([ zeros(4), u32('count'), zeros(4), + u32('seqNum'), + zeros(4), ]); const EVENT_FLAGS = bits(u8(), false, 'eventFlags'); @@ -57,9 +60,11 @@ EVENT_FLAGS.addBoolean('maker'); const EVENT = struct([ EVENT_FLAGS, u8('openOrdersSlot'), - blob(6), - u64('quantityReleased'), - u64('quantityPaid'), + u8('feeTier'), + blob(5), + u64('nativeQuantityReleased'), + u64('nativeQuantityPaid'), + u64('nativeFeeOrRebate'), u128('orderId'), publicKeyLayout('openOrders'), ]); @@ -70,9 +75,11 @@ export interface Event { orderId: BN; openOrders: PublicKey; openOrdersSlot: number; + feeTier: number; - quantityReleased: BN; - quantityPaid: BN; + nativeQuantityReleased: BN; + nativeQuantityPaid: BN; + nativeFeeOrRebate: BN; } function decodeQueue( diff --git a/src/slab.ts b/src/slab.ts index c57884b..984eff7 100644 --- a/src/slab.ts +++ b/src/slab.ts @@ -38,7 +38,8 @@ SLAB_NODE_LAYOUT.addVariant( 2, struct([ u8('ownerSlot'), // Index into OPEN_ORDERS_LAYOUT.orders - blob(3), + u8('feeTier'), + blob(2), u128('key'), // (price, seqNum) publicKeyLayout('owner'), // Open orders account u64('quantity'), // In units of lot size @@ -113,7 +114,13 @@ export class Slab { *items( descending = false, - ): Generator<{ ownerSlot: number; key: BN; owner: PublicKey; quantity: BN }> { + ): Generator<{ + ownerSlot: number; + key: BN; + owner: PublicKey; + quantity: BN; + feeTier: number; + }> { if (this.header.leafCount === 0) { return; }