diff --git a/src/instructions.js b/src/instructions.js index dfbbe3d..fcebf61 100644 --- a/src/instructions.js +++ b/src/instructions.js @@ -34,29 +34,18 @@ INSTRUCTION_LAYOUT.inner.addVariant( 1, struct([ sideLayout('side'), - zeros(3), u64('limitPrice'), u64('maxQuantity'), orderTypeLayout('orderType'), - zeros(3), ]), 'newOrder', ); -INSTRUCTION_LAYOUT.inner.addVariant( - 2, - struct([u16('limit'), blob(2)]), - 'matchOrders', -); -INSTRUCTION_LAYOUT.inner.addVariant( - 3, - struct([u16('limit'), blob(2)]), - 'consumeEvents', -); +INSTRUCTION_LAYOUT.inner.addVariant(2, struct([u16('limit')]), 'matchOrders'); +INSTRUCTION_LAYOUT.inner.addVariant(3, struct([u16('limit')]), 'consumeEvents'); INSTRUCTION_LAYOUT.inner.addVariant( 4, struct([ sideLayout('side'), - zeros(3), u128('orderId'), publicKeyLayout('owner'), u8('ownerSlot'), @@ -128,9 +117,9 @@ export class DexInstructions { keys: [ { pubkey: market, isSigner: false, isWritable: false }, { pubkey: openOrders, isSigner: false, isWritable: true }, + { pubkey: requestQueue, isSigner: false, isWritable: true }, { pubkey: payer, isSigner: false, isWritable: true }, { pubkey: owner, isSigner: true, isWritable: false }, - { pubkey: requestQueue, isSigner: false, isWritable: true }, { pubkey: baseVault, isSigner: false, isWritable: true }, { pubkey: quoteVault, isSigner: false, isWritable: true }, { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, @@ -170,13 +159,13 @@ export class DexInstructions { static consumeEvents({ market, eventQueue, openOrdersAccounts, limit }) { return new TransactionInstruction({ keys: [ - { pubkey: market, isSigner: false, isWritable: true }, - { pubkey: eventQueue, isSigner: false, isWritable: true }, ...openOrdersAccounts.map((account) => ({ pubkey: account, isSigner: false, isWritable: true, })), + { pubkey: market, isSigner: false, isWritable: true }, + { pubkey: eventQueue, isSigner: false, isWritable: true }, ], programId: DEX_PROGRAM_ID, data: encodeInstruction({ consumeEvents: { limit } }), diff --git a/src/layout.js b/src/layout.js index fa18d59..03c15f2 100644 --- a/src/layout.js +++ b/src/layout.js @@ -131,11 +131,11 @@ class EnumLayout extends UInt { } export function sideLayout(property) { - return new EnumLayout({ buy: 0, sell: 1 }, 1, property); + return new EnumLayout({ buy: 0, sell: 1 }, 4, property); } export function orderTypeLayout(property) { - return new EnumLayout({ limit: 0, ioc: 1, postOnly: 2 }, 1, property); + return new EnumLayout({ limit: 0, ioc: 1, postOnly: 2 }, 4, property); } export function setLayoutDecoder(layout, decoder) { diff --git a/src/market.js b/src/market.js index 05c95bc..e7be914 100644 --- a/src/market.js +++ b/src/market.js @@ -61,8 +61,8 @@ export class Market { throw new Error('Invalid market state'); } this._decoded = decoded; - this._baseMintDecimals = baseMintDecimals; - this._quoteMintDecimals = quoteMintDecimals; + this._baseSplTokenDecimals = baseMintDecimals; + this._quoteSplTokenDecimals = quoteMintDecimals; } static get LAYOUT() { @@ -202,16 +202,62 @@ export class Market { return { transaction, signers }; } - async cancelOrder(connection, order) { - throw new Error('not yet implemented'); + async cancelOrder(connection, owner, order) { + const transaction = await this.makeCancelOrderTransaction( + connection, + order, + ); + return await connection.sendTransaction(transaction, [owner]); + } + + async makeCancelOrderTransaction(connection, order) { + const openOrdersAccount = await this.findOpenOrdersAccountForOrder( + connection, + order, + ); + if (openOrdersAccount === null) { + throw new Error('Order not found'); + } + const transaction = new Transaction(); + transaction.add( + DexInstructions.cancelOrder({ + market: this.address, + openOrders: openOrdersAccount.address, + owner: order.owner, + requestQueue: this._decoded.requestQueue, + side: order.side, + orderId: order.orderId, + ownerSlot: order.ownerSlot, + }), + ); + return transaction; + } + + async findOpenOrdersAccountForOrder(connection, order) { + const openOrdersAccounts = await this.findOpenOrdersAccountsForOwner( + connection, + order.owner, + ); + for (const account of openOrdersAccounts) { + if (account.orders.some((orderId) => orderId.eq(order.orderId))) { + return account; + } + } + return null; + } + + get _baseSplTokenMultiplier() { + return new BN(10).pow(new BN(this._baseSplTokenDecimals)); + } + + get _quoteSplTokenMultiplier() { + return new BN(10).pow(new BN(this._quoteSplTokenDecimals)); } priceLotsToNumber(price) { return divideBnToNumber( - price - .mul(this._decoded.quoteLotSize) - .mul(new BN(10).pow(this._baseMintDecimals)), - this._decoded.baseLotSize.mul(new BN(10).pow(this._quoteMintDecimals)), + price.mul(this._decoded.quoteLotSize).mul(this._baseSplTokenMultiplier), + this._decoded.baseLotSize.mul(this._quoteSplTokenMultiplier), ); } @@ -219,9 +265,9 @@ export class Market { return new BN( Math.round( (price * - Math.pow(10, this._quoteMintDecimals) * + Math.pow(10, this._quoteSplTokenDecimals) * this._decoded.baseLotSize.toNumber()) / - (Math.pow(10, this._baseMintDecimals) * + (Math.pow(10, this._baseSplTokenDecimals) * this._decoded.quoteLotSize.toNumber()), ), ); @@ -230,13 +276,13 @@ export class Market { baseSizeLotsToNumber(size) { return divideBnToNumber( size.mul(this._decoded.baseLotSize), - new BN(10).pow(this._baseMintDecimals), + this._baseSplTokenMultiplier, ); } baseSizeNumberToLots(size) { const native = new BN( - Math.round(size * Math.pow(10, this._baseMintDecimals)), + Math.round(size * Math.pow(10, this._baseSplTokenDecimals)), ); // rounds down to the nearest lot size return native.div(this._decoded.baseLotSize); @@ -245,17 +291,34 @@ export class Market { quoteSizeLotsToNumber(size) { return divideBnToNumber( size.mul(this._decoded.quoteLotSize), - new BN(10).pow(this._quoteMintDecimals), + this._quoteSplTokenMultiplier, ); } quoteSizeNumberToLots(size) { const native = new BN( - Math.round(size * Math.pow(10, this._quoteMintDecimals)), + Math.round(size * Math.pow(10, this._quoteSplTokenDecimals)), ); // rounds down to the nearest lot size return native.div(this._decoded.quoteLotSize); } + + async matchOrders(connection, feePayer, limit) { + const tx = new Transaction(); + tx.add( + DexInstructions.matchOrders({ + market: this.address, + requestQueue: this._decoded.requestQueue, + eventQueue: this._decoded.eventQueue, + bids: this._decoded.bids, + asks: this._decoded.asks, + baseVault: this._decoded.baseVault, + quoteVault: this._decoded.quoteVault, + limit, + }), + ); + return await connection.sendTransaction(tx, [feePayer]); + } } export const OPEN_ORDERS_LAYOUT = struct([ @@ -264,11 +327,11 @@ export const OPEN_ORDERS_LAYOUT = struct([ publicKeyLayout('market'), publicKeyLayout('owner'), - // These are in native (i.e. not lot) units - u64('baseTotal'), - u64('baseFree'), - u64('quoteTotal'), - u64('quoteFree'), + // These are in spl-token (i.e. not lot) units + u64('baseTokenFree'), + u64('baseTokenTotal'), + u64('quoteTokenFree'), + u64('quoteTokenTotal'), u128('freeSlotBits'), u128('isBidBits'),