Add more instructions
This commit is contained in:
parent
0888234da3
commit
02f41fd3b1
|
@ -15,11 +15,9 @@
|
|||
"prepare": "run-s build",
|
||||
"test": "run-s test:unit test:lint test:build",
|
||||
"test:build": "run-s build",
|
||||
"test:lint": "eslint .",
|
||||
"test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
|
||||
"test:watch": "react-scripts test --env=jsdom",
|
||||
"predeploy": "cd example && yarn install && yarn run build",
|
||||
"deploy": "gh-pages -d example/build"
|
||||
"test:lint": "eslint src",
|
||||
"test:unit": "cross-env CI=1 react-scripts test",
|
||||
"test:watch": "react-scripts test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.10.5",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { MarketState, Orderbook } from './market-state';
|
||||
export { Market, Orderbook } from './market';
|
||||
export { DexInstructions } from './instructions';
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import { blob, Layout, struct, u16, u32, u8, union } from 'buffer-layout';
|
||||
import { u64, VersionedLayout } from './layout';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import { blob, struct, u16, u32, u8, union } from 'buffer-layout';
|
||||
import {
|
||||
orderTypeLayout,
|
||||
publicKeyLayout,
|
||||
sideLayout,
|
||||
u128,
|
||||
u64,
|
||||
VersionedLayout,
|
||||
zeros,
|
||||
} from './layout';
|
||||
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
|
||||
export const DEX_PROGRAM_ID = new PublicKey(
|
||||
'6iM2JjaPVViB2u82aVjf3ZfHDNHQ4G635XgnvgN6CNhY',
|
||||
'9o1FisE366msTQcEvXapyMorTLmvezrxSD8DnM5e5XKw',
|
||||
);
|
||||
|
||||
export const INSTRUCTION_LAYOUT = new VersionedLayout(
|
||||
|
@ -24,12 +32,12 @@ INSTRUCTION_LAYOUT.inner.addVariant(
|
|||
INSTRUCTION_LAYOUT.inner.addVariant(
|
||||
1,
|
||||
struct([
|
||||
u8('side'), // buy = 0, sell = 1
|
||||
blob(3),
|
||||
sideLayout('side'),
|
||||
zeros(3),
|
||||
u64('limitPrice'),
|
||||
u64('maxQuantity'),
|
||||
u8('orderType'), // limit = 0, ioc = 1, postOnly = 2
|
||||
blob(3),
|
||||
orderTypeLayout('orderType'),
|
||||
zeros(3),
|
||||
]),
|
||||
'newOrder',
|
||||
);
|
||||
|
@ -43,9 +51,156 @@ INSTRUCTION_LAYOUT.inner.addVariant(
|
|||
struct([u16('limit'), blob(2)]),
|
||||
'consumeEvents',
|
||||
);
|
||||
INSTRUCTION_LAYOUT.inner.addVariant(4, struct([]), 'cancelOrder');
|
||||
INSTRUCTION_LAYOUT.inner.addVariant(
|
||||
4,
|
||||
struct([
|
||||
sideLayout('side'),
|
||||
zeros(3),
|
||||
u128('orderId'),
|
||||
publicKeyLayout('owner'),
|
||||
u8('ownerSlot'),
|
||||
]),
|
||||
'cancelOrder',
|
||||
);
|
||||
|
||||
export function encodeInstruction(instruction) {
|
||||
const b = Buffer.alloc(100);
|
||||
return b.slice(0, INSTRUCTION_LAYOUT.encode(instruction, b));
|
||||
}
|
||||
|
||||
export class DexInstructions {
|
||||
static initializeMarket({
|
||||
market,
|
||||
requestQueue,
|
||||
eventQueue,
|
||||
bids,
|
||||
asks,
|
||||
baseVault,
|
||||
quoteVault,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
baseLotSize,
|
||||
quoteLotSize,
|
||||
feeRateBps,
|
||||
vaultSignerNonce,
|
||||
quoteDustThreshold,
|
||||
}) {
|
||||
return new TransactionInstruction({
|
||||
keys: [
|
||||
{ pubkey: market, isSigner: false, isWritable: true },
|
||||
{ pubkey: requestQueue, isSigner: false, isWritable: true },
|
||||
{ pubkey: eventQueue, isSigner: false, isWritable: true },
|
||||
{ pubkey: bids, isSigner: false, isWritable: true },
|
||||
{ pubkey: asks, isSigner: false, isWritable: true },
|
||||
{ pubkey: baseVault, isSigner: false, isWritable: true },
|
||||
{ pubkey: quoteVault, isSigner: false, isWritable: true },
|
||||
{ pubkey: baseMint, isSigner: false, isWritable: false },
|
||||
{ pubkey: quoteMint, isSigner: false, isWritable: false },
|
||||
],
|
||||
programId: DEX_PROGRAM_ID,
|
||||
data: encodeInstruction({
|
||||
initializeMarket: {
|
||||
baseLotSize,
|
||||
quoteLotSize,
|
||||
feeRateBps,
|
||||
vaultSignerNonce,
|
||||
quoteDustThreshold,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
static newOrder({
|
||||
market,
|
||||
openOrders,
|
||||
payer,
|
||||
owner,
|
||||
requestQueue,
|
||||
baseVault,
|
||||
quoteVault,
|
||||
side,
|
||||
limitPrice,
|
||||
maxQuantity,
|
||||
orderType,
|
||||
}) {
|
||||
return new TransactionInstruction({
|
||||
keys: [
|
||||
{ pubkey: market, isSigner: false, isWritable: false },
|
||||
{ pubkey: openOrders, 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: false },
|
||||
{ pubkey: quoteVault, isSigner: false, isWritable: false },
|
||||
],
|
||||
programId: DEX_PROGRAM_ID,
|
||||
data: encodeInstruction({
|
||||
newOrder: { side, limitPrice, maxQuantity, orderType },
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
static matchOrders({
|
||||
market,
|
||||
requestQueue,
|
||||
eventQueue,
|
||||
bids,
|
||||
asks,
|
||||
baseVault,
|
||||
quoteVault,
|
||||
limit,
|
||||
}) {
|
||||
return new TransactionInstruction({
|
||||
keys: [
|
||||
{ pubkey: market, isSigner: false, isWritable: true },
|
||||
{ pubkey: requestQueue, isSigner: false, isWritable: true },
|
||||
{ pubkey: eventQueue, isSigner: false, isWritable: true },
|
||||
{ pubkey: bids, isSigner: false, isWritable: true },
|
||||
{ pubkey: asks, isSigner: false, isWritable: true },
|
||||
{ pubkey: baseVault, isSigner: false, isWritable: true },
|
||||
{ pubkey: quoteVault, isSigner: false, isWritable: true },
|
||||
],
|
||||
programId: DEX_PROGRAM_ID,
|
||||
data: encodeInstruction({ matchOrders: { limit } }),
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
})),
|
||||
],
|
||||
programId: DEX_PROGRAM_ID,
|
||||
data: encodeInstruction({ consumeEvents: { limit } }),
|
||||
});
|
||||
}
|
||||
|
||||
static cancelOrder({
|
||||
market,
|
||||
openOrders,
|
||||
owner,
|
||||
requestQueue,
|
||||
side,
|
||||
orderId,
|
||||
ownerSlot,
|
||||
}) {
|
||||
return new TransactionInstruction({
|
||||
keys: [
|
||||
{ pubkey: market, isSigner: false, isWritable: false },
|
||||
{ pubkey: openOrders, isSigner: false, isWritable: true },
|
||||
{ pubkey: owner, isSigner: true, isWritable: false },
|
||||
{ pubkey: requestQueue, isSigner: false, isWritable: true },
|
||||
],
|
||||
programId: DEX_PROGRAM_ID,
|
||||
data: encodeInstruction({
|
||||
cancelOrder: { side, orderId, owner, ownerSlot },
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { encodeInstruction, INSTRUCTION_LAYOUT } from './instructions';
|
||||
import { encodeInstruction } from './instructions';
|
||||
import BN from 'bn.js';
|
||||
|
||||
describe('instruction', () => {
|
||||
it('encodes initialize market', () => {
|
||||
let b = encodeInstruction({
|
||||
const b = encodeInstruction({
|
||||
initializeMarket: {
|
||||
baseLotSize: new BN(10),
|
||||
quoteLotSize: new BN(100000),
|
||||
|
@ -18,12 +18,12 @@ describe('instruction', () => {
|
|||
});
|
||||
|
||||
it('encodes new order', () => {
|
||||
let b = encodeInstruction({
|
||||
const b = encodeInstruction({
|
||||
newOrder: {
|
||||
side: 1, // buy
|
||||
side: 'sell',
|
||||
limitPrice: new BN(10),
|
||||
maxQuantity: new BN(5),
|
||||
orderType: 2, // postOnly
|
||||
orderType: 'postOnly',
|
||||
},
|
||||
});
|
||||
expect(b.toString('hex')).toEqual(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { bits, Blob, Layout, u32 } from 'buffer-layout';
|
||||
import { bits, Blob, Layout, u32, UInt } from 'buffer-layout';
|
||||
import { PublicKey } from '@solana/web3.js';
|
||||
import BN from 'bn.js';
|
||||
|
||||
|
@ -105,6 +105,39 @@ export class VersionedLayout extends Layout {
|
|||
}
|
||||
}
|
||||
|
||||
class EnumLayout extends UInt {
|
||||
constructor(values, span, property) {
|
||||
super(span, property);
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
encode(src, b, offset) {
|
||||
if (this.values[src] !== undefined) {
|
||||
return super.encode(this.values[src], b, offset);
|
||||
}
|
||||
throw new Error('Invalid ' + this.property);
|
||||
}
|
||||
|
||||
decode(b, offset) {
|
||||
const decodedValue = super.decode(b, offset);
|
||||
const entry = Object.entries(this.values).find(
|
||||
([key, value]) => value === decodedValue,
|
||||
);
|
||||
if (entry) {
|
||||
return entry[0];
|
||||
}
|
||||
throw new Error('Invalid ' + this.property);
|
||||
}
|
||||
}
|
||||
|
||||
export function sideLayout(property) {
|
||||
return new EnumLayout({ buy: 0, sell: 1 }, 1, property);
|
||||
}
|
||||
|
||||
export function orderTypeLayout(property) {
|
||||
return new EnumLayout({ limit: 0, ioc: 1, postOnly: 2 }, 1, property);
|
||||
}
|
||||
|
||||
export function setLayoutDecoder(layout, decoder) {
|
||||
const originalDecode = layout.decode;
|
||||
layout.decode = function decode(b, offset = 0) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from './layout';
|
||||
import { SLAB_LAYOUT } from './slab';
|
||||
import { DEX_PROGRAM_ID } from './instructions';
|
||||
import BN from 'bn.js';
|
||||
|
||||
const ACCOUNT_FLAGS_LAYOUT = new WideBits();
|
||||
ACCOUNT_FLAGS_LAYOUT.addBoolean('initialized');
|
||||
|
@ -52,7 +53,7 @@ export const MARKET_STATE_LAYOUT = struct([
|
|||
u64('quoteLotSize'),
|
||||
]);
|
||||
|
||||
export class MarketState {
|
||||
export class Market {
|
||||
constructor(decoded) {
|
||||
if (!decoded.accountFlags.initialized || !decoded.accountFlags.market) {
|
||||
throw new Error('Invalid market state');
|
||||
|
@ -74,6 +75,7 @@ export class MarketState {
|
|||
throw new Error('Address not owned by program');
|
||||
}
|
||||
return MARKET_STATE_LAYOUT.decode(data);
|
||||
// TODO: also load mint account info
|
||||
}
|
||||
|
||||
async loadBids(connection) {
|
||||
|
@ -95,14 +97,14 @@ export class MarketState {
|
|||
);
|
||||
}
|
||||
|
||||
baseBnToNumber(size) {
|
||||
baseSizeBnToNumber(size) {
|
||||
return divideBnToNumber(
|
||||
size.mul(this._decoded.baseLotSize),
|
||||
new BN(10).pow(this._baseMintDecimals),
|
||||
);
|
||||
}
|
||||
|
||||
quoteBnToNumber(size) {
|
||||
quoteSizeBnToNumber(size) {
|
||||
return divideBnToNumber(
|
||||
size.mul(this._decoded.quoteLotSize),
|
||||
new BN(10).pow(this._quoteMintDecimals),
|
||||
|
@ -110,7 +112,7 @@ export class MarketState {
|
|||
}
|
||||
}
|
||||
|
||||
setLayoutDecoder(MARKET_STATE_LAYOUT, (decoded) => new MarketState(decoded));
|
||||
setLayoutDecoder(MARKET_STATE_LAYOUT, (decoded) => new Market(decoded));
|
||||
|
||||
export const OPEN_ORDERS_LAYOUT = struct([
|
||||
accountFlags('accountFlags'),
|
||||
|
@ -130,6 +132,8 @@ export const OPEN_ORDERS_LAYOUT = struct([
|
|||
seq(u128(), 128, 'orders'),
|
||||
]);
|
||||
|
||||
export class OpenOrders {}
|
||||
|
||||
export const ORDERBOOK_LAYOUT = struct([
|
||||
accountFlags('accountFlags'),
|
||||
SLAB_LAYOUT.replicate('slab'),
|
||||
|
@ -153,7 +157,7 @@ export class Orderbook {
|
|||
getLevels(depth) {
|
||||
const descending = this.isBids;
|
||||
const levels = []; // (price, size)
|
||||
for (let { key, quantity } of this.slab.items(descending)) {
|
||||
for (const { key, quantity } of this.slab.items(descending)) {
|
||||
const price = getPriceFromKey(key);
|
||||
if (levels.length > 0 && levels[levels.length - 1][0].equals(price)) {
|
||||
levels[levels.length - 1].iadd(quantity);
|
||||
|
@ -165,7 +169,7 @@ export class Orderbook {
|
|||
}
|
||||
return levels.map(([price, size]) => [
|
||||
this.market.priceBnToNumber(price),
|
||||
this.market.baseBnToNumber(size.mul(this.market.baseLotSize)),
|
||||
this.market.baseSizeBnToNumber(size.mul(this.market.baseLotSize)),
|
||||
]);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { accountFlags } from './market-state';
|
||||
import { accountFlags } from './market';
|
||||
|
||||
describe('accountFlags', () => {
|
||||
const layout = accountFlags();
|
|
@ -69,7 +69,7 @@ export class Slab {
|
|||
return SLAB_LAYOUT.decode(buffer);
|
||||
}
|
||||
|
||||
get = (searchKey) => {
|
||||
get(searchKey) {
|
||||
if (this.header.leafCount === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -101,7 +101,7 @@ export class Slab {
|
|||
throw new Error('Invalid slab');
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[Symbol.iterator]() {
|
||||
return this.items(false);
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('slab', () => {
|
|||
|
||||
it('iterates in order', () => {
|
||||
let previous = null;
|
||||
for (let item of slab) {
|
||||
for (const item of slab) {
|
||||
if (previous) {
|
||||
expect(item.key.gt(previous.key)).toBeTruthy();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue