serum: Middleware for market proxy (#138)

This commit is contained in:
Armani Ferrante 2021-07-18 00:46:21 -07:00 committed by GitHub
parent 145b5f1290
commit 4e132e831d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 539 additions and 446 deletions

View File

@ -65,8 +65,5 @@
"ts-jest": "^26.4.3",
"ts-node": "^9.0.0",
"typescript": "^4.0.5"
},
"resolutions": {
"@solana/web3.js": "0.90.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@project-serum/serum",
"version": "0.13.47",
"version": "0.13.48",
"description": "Library for interacting with the serum dex",
"license": "MIT",
"repository": "project-serum/serum-ts",
@ -47,7 +47,8 @@
},
"dependencies": {
"@solana/spl-token": "^0.1.6",
"@solana/web3.js": "^0.90.0",
"@project-serum/anchor": "^0.11.1",
"@solana/web3.js": "^1.21.0",
"bn.js": "^5.1.2",
"buffer-layout": "^1.2.0"
},

View File

@ -1,4 +1,10 @@
export { Market, Orderbook, OpenOrders } from './market';
export {
Market,
Orderbook,
OpenOrders,
MARKET_STATE_LAYOUT_V3,
MARKET_STATE_LAYOUT_V2,
} from './market';
export {
DexInstructions,
decodeInstruction,
@ -19,4 +25,10 @@ export {
} from './queue';
export * as TokenInstructions from './token-instructions';
export * from './error';
export { PermissionedMarket } from './permissioned-market';
export { MarketProxy, MarketProxyBuilder } from './market-proxy';
export {
OpenOrdersPda,
ReferralFees,
Logger,
Middleware,
} from './market-proxy/middleware';

View File

@ -340,18 +340,18 @@ export class DexInstructions {
});
}
static cancelOrderV2({
market,
bids,
asks,
eventQueue,
openOrders,
owner,
side,
orderId,
openOrdersSlot,
programId,
}) {
static cancelOrderV2(order) {
const {
market,
bids,
asks,
eventQueue,
openOrders,
owner,
side,
orderId,
programId,
} = order;
return new TransactionInstruction({
keys: [
{ pubkey: market, isSigner: false, isWritable: false },

View File

@ -0,0 +1,224 @@
import BN from 'bn.js';
import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@project-serum/anchor';
import {
Market,
MarketOptions,
OrderParams,
MARKET_STATE_LAYOUT_V3,
} from '../market';
import { DexInstructions } from '../instructions';
import { Middleware } from './middleware';
// MarketProxy provides an API for constructing transactions to an on-chain
// DEX proxy, which relays all instructions to the orderbook. Minimally, this
// requires two modifications for DEX instructions.
//
// 1. Transasctions are sent to the proxy program--not the DEX.
// 2. The DEX program ID must be inserted as the first account in instructions
// using the proxy relay, so that the proxy can use the account for CPI.
// The program is responsible for removing this account before relaying to
// the dex.
//
// Additionally, a middleware abstraction is provided so that one can configure
// both the client and the smart contract with the ability to send and processs
// arbitrary accounts and instruction data *in addition* to what the Serum DEX
// expects.
//
// Similar to the layers of an onion, each middleware wraps a transaction
// request with additional accounts and instruction data before sending it to
// the program. Upon receiving the request, the program--with its own set of
// middleware-- unwraps and processes each layer. The process ends with all
// layers being unwrapped and the proxy relaying the transaction to the DEX.
//
// As a result, the order of the middleware matters and the client should
// process middleware in the *reverse* order of the proxy smart contract.
export class MarketProxy {
// DEX market being proxied.
get market(): Market {
return this._market;
}
private _market: Market;
// Instruction namespace.
get instruction(): MarketProxyInstruction {
return this._instruction;
}
private _instruction: MarketProxyInstruction;
// Ctor.
constructor(market: Market, instruction: MarketProxyInstruction) {
this._market = market;
this._instruction = instruction;
}
}
// Instruction builder for the market proxy.
export class MarketProxyInstruction {
// Program ID of the permissioning proxy program.
private _proxyProgramId: PublicKey;
// Dex program ID.
private _dexProgramId: PublicKey;
// Underlying DEX market.
private _market: Market;
// Middlewares for processing the creation of transactions.
private _middlewares: Middleware[];
constructor(
proxyProgramId: PublicKey,
dexProgramId: PublicKey,
market: Market,
middlewares: Middleware[],
) {
this._proxyProgramId = proxyProgramId;
this._dexProgramId = dexProgramId;
this._market = market;
this._middlewares = middlewares;
}
public newOrderV3(params: OrderParams<PublicKey>): TransactionInstruction {
const tradeIx = this._market.makeNewOrderV3Instruction({
...params,
programId: this._proxyProgramId,
});
this._middlewares.forEach((mw) => mw.newOrderV3(tradeIx));
return this.proxy(tradeIx);
}
public initOpenOrders(
owner: PublicKey,
market: PublicKey,
openOrders: PublicKey,
marketAuthority: PublicKey,
): TransactionInstruction {
const ix = DexInstructions.initOpenOrders({
market,
openOrders,
owner,
programId: this._proxyProgramId,
marketAuthority,
});
this._middlewares.forEach((mw) => mw.initOpenOrders(ix));
return this.proxy(ix);
}
public cancelOrderByClientId(
owner: PublicKey,
openOrders: PublicKey,
clientId: BN,
): TransactionInstruction {
const ix = DexInstructions.cancelOrderByClientIdV2({
market: this._market.address,
openOrders,
owner,
bids: this._market.decoded.bids,
asks: this._market.decoded.asks,
eventQueue: this._market.decoded.eventQueue,
clientId,
programId: this._proxyProgramId,
});
this._middlewares.forEach((mw) => mw.cancelOrderByClientIdV2(ix));
return this.proxy(ix);
}
public settleFunds(
openOrders: PublicKey,
owner: PublicKey,
baseWallet: PublicKey,
quoteWallet: PublicKey,
referrerQuoteWallet: PublicKey,
): TransactionInstruction {
const ix = DexInstructions.settleFunds({
market: this._market.address,
openOrders,
owner,
baseVault: this._market.decoded.baseVault,
quoteVault: this._market.decoded.quoteVault,
baseWallet,
quoteWallet,
vaultSigner: utils.publicKey.createProgramAddressSync(
[
this._market.address.toBuffer(),
this._market.decoded.vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
],
this._dexProgramId,
),
programId: this._proxyProgramId,
referrerQuoteWallet,
});
this._middlewares.forEach((mw) => mw.settleFunds(ix));
return this.proxy(ix);
}
public closeOpenOrders(
openOrders: PublicKey,
owner: PublicKey,
solWallet: PublicKey,
): TransactionInstruction {
const ix = DexInstructions.closeOpenOrders({
market: this._market.address,
openOrders,
owner,
solWallet,
programId: this._proxyProgramId,
});
this._middlewares.forEach((mw) => mw.closeOpenOrders(ix));
return this.proxy(ix);
}
// Adds the serum dex account to the instruction so that proxies can
// relay (CPI requires the executable account).
private proxy(ix: TransactionInstruction) {
ix.keys = [
{ pubkey: this._dexProgramId, isWritable: false, isSigner: false },
...ix.keys,
];
return ix;
}
}
export class MarketProxyBuilder {
private _middlewares: Middleware[];
constructor() {
this._middlewares = [];
}
public middleware(mw: Middleware): MarketProxyBuilder {
this._middlewares.push(mw);
return this;
}
public async load({
connection,
market,
options = {},
dexProgramId,
proxyProgramId,
}: {
connection: Connection;
market: PublicKey;
options: MarketOptions;
dexProgramId: PublicKey;
proxyProgramId: PublicKey;
}): Promise<MarketProxy> {
const marketClient = await Market.load(
connection,
market,
options,
dexProgramId,
MARKET_STATE_LAYOUT_V3,
);
const instruction = new MarketProxyInstruction(
proxyProgramId,
dexProgramId,
marketClient,
this._middlewares,
);
return new MarketProxy(marketClient, instruction);
}
}

View File

@ -0,0 +1,181 @@
import { utils } from '@project-serum/anchor';
import {
SystemProgram,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
export interface Middleware {
initOpenOrders(ix: TransactionInstruction): void;
newOrderV3(ix: TransactionInstruction): void;
cancelOrderV2(ix: TransactionInstruction): void;
cancelOrderByClientIdV2(ix: TransactionInstruction): void;
settleFunds(ix: TransactionInstruction): void;
closeOpenOrders(ix: TransactionInstruction): void;
}
export class OpenOrdersPda implements Middleware {
private _proxyProgramId: PublicKey;
private _dexProgramId: PublicKey;
constructor({
proxyProgramId,
dexProgramId,
}: {
proxyProgramId: PublicKey;
dexProgramId: PublicKey;
}) {
this._proxyProgramId = proxyProgramId;
this._dexProgramId = dexProgramId;
}
public static async openOrdersAddress(
market: PublicKey,
owner: PublicKey,
dexProgramId: PublicKey,
proxyProgramId: PublicKey,
): Promise<PublicKey> {
// b"open-orders".
const openOrdersStr = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
]);
const [addr] = await PublicKey.findProgramAddress(
[
openOrdersStr,
dexProgramId.toBuffer(),
market.toBuffer(),
owner.toBuffer(),
],
proxyProgramId,
);
return addr;
}
initOpenOrders(ix: TransactionInstruction) {
const market = ix.keys[2].pubkey;
const owner = ix.keys[1].pubkey;
// b"open-orders"
const openOrdersSeed = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
]);
// b"open-orders-init"
const openOrdersInitSeed = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
45,
105,
110,
105,
116,
]);
const [openOrders, bump] = utils.publicKey.findProgramAddressSync(
[
openOrdersSeed,
this._dexProgramId.toBuffer(),
market.toBuffer(),
owner.toBuffer(),
],
this._proxyProgramId,
);
const [marketAuthority, bumpInit] = utils.publicKey.findProgramAddressSync(
[openOrdersInitSeed, this._dexProgramId.toBuffer(), market.toBuffer()],
this._proxyProgramId,
);
// Override the open orders account and market authority.
ix.keys[0].pubkey = openOrders;
ix.keys[4].pubkey = marketAuthority;
// Prepend to the account list extra accounts needed for PDA initialization.
ix.keys = [
{ pubkey: this._dexProgramId, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
...ix.keys,
];
// Prepend the ix discriminator, bump, and bumpInit to the instruction data,
// which saves the program compute by avoiding recalculating them in the
// program.
ix.data = Buffer.concat([Buffer.from([0, bump, bumpInit]), ix.data]);
}
newOrderV3(ix: TransactionInstruction) {
ix.data = Buffer.concat([Buffer.from([1]), ix.data]);
}
cancelOrderV2(ix: TransactionInstruction) {
ix.data = Buffer.concat([Buffer.from([2]), ix.data]);
}
cancelOrderByClientIdV2(ix: TransactionInstruction) {
ix.data = Buffer.concat([Buffer.from([3]), ix.data]);
}
settleFunds(ix: TransactionInstruction) {
ix.data = Buffer.concat([Buffer.from([4]), ix.data]);
}
closeOpenOrders(ix: TransactionInstruction) {
ix.data = Buffer.concat([Buffer.from([5]), ix.data]);
}
}
export class ReferralFees implements Middleware {
// eslint-disable-next-line
initOpenOrders(_ix: TransactionInstruction) {}
// eslint-disable-next-line
newOrderV3(_ix: TransactionInstruction) {}
// eslint-disable-next-line
cancelOrderV2(_ix: TransactionInstruction) {}
// eslint-disable-next-line
cancelOrderByClientIdV2(_ix: TransactionInstruction) {}
// eslint-disable-next-line
settleFunds(_ix: TransactionInstruction) {}
// eslint-disable-next-line
closeOpenOrders(_ix: TransactionInstruction) {}
}
export class Logger implements Middleware {
initOpenOrders(ix: TransactionInstruction) {
console.log('Proxying initOpeNorders', ix);
}
newOrderV3(ix: TransactionInstruction) {
console.log('Proxying newOrderV3', ix);
}
cancelOrderV2(ix: TransactionInstruction) {
console.log('Proxying cancelOrderV2', ix);
}
cancelOrderByClientIdV2(ix: TransactionInstruction) {
console.log('Proxying cancelOrderByClientIdV2', ix);
}
settleFunds(ix: TransactionInstruction) {
console.log('Proxying settleFunds', ix);
}
closeOpenOrders(ix: TransactionInstruction) {
console.log('Proxying closeOpenOrders', ix);
}
}

View File

@ -1,12 +1,5 @@
import * as assert from 'assert';
import { blob, seq, struct, u8 } from 'buffer-layout';
import {
accountFlagsLayout,
publicKeyLayout,
selfTradeBehaviorLayout,
u128,
u64,
} from './layout';
import { accountFlagsLayout, publicKeyLayout, u128, u64 } from './layout';
import { Slab, SLAB_LAYOUT } from './slab';
import { DexInstructions } from './instructions';
import BN from 'bn.js';
@ -73,7 +66,7 @@ export const _MARKET_STAT_LAYOUT_V1 = struct([
blob(7),
]);
export const _MARKET_STATE_LAYOUT_V2 = struct([
export const MARKET_STATE_LAYOUT_V2 = struct([
blob(5),
accountFlagsLayout('accountFlags'),
@ -111,6 +104,46 @@ export const _MARKET_STATE_LAYOUT_V2 = struct([
blob(7),
]);
export const MARKET_STATE_LAYOUT_V3 = struct([
blob(5),
accountFlagsLayout('accountFlags'),
publicKeyLayout('ownAddress'),
u64('vaultSignerNonce'),
publicKeyLayout('baseMint'),
publicKeyLayout('quoteMint'),
publicKeyLayout('baseVault'),
u64('baseDepositsTotal'),
u64('baseFeesAccrued'),
publicKeyLayout('quoteVault'),
u64('quoteDepositsTotal'),
u64('quoteFeesAccrued'),
u64('quoteDustThreshold'),
publicKeyLayout('requestQueue'),
publicKeyLayout('eventQueue'),
publicKeyLayout('bids'),
publicKeyLayout('asks'),
u64('baseLotSize'),
u64('quoteLotSize'),
u64('feeRateBps'),
u64('referrerRebatesAccrued'),
publicKeyLayout('authority'),
blob(7),
]);
export class Market {
private _decoded: any;
private _baseSplTokenDecimals: number;
@ -121,6 +154,7 @@ export class Market {
private _openOrdersAccountsCache: {
[publickKey: string]: { accounts: OpenOrders[]; ts: number };
};
private _layoutOverride?: any;
private _feeDiscountKeysCache: {
[publicKey: string]: {
@ -140,6 +174,7 @@ export class Market {
quoteMintDecimals: number,
options: MarketOptions = {},
programId: PublicKey,
layoutOverride?: any,
) {
const { skipPreflight = false, commitment = 'recent' } = options;
if (!decoded.accountFlags.initialized || !decoded.accountFlags.market) {
@ -153,13 +188,14 @@ export class Market {
this._programId = programId;
this._openOrdersAccountsCache = {};
this._feeDiscountKeysCache = {};
this._layoutOverride = layoutOverride;
}
static getLayout(programId: PublicKey) {
if (getLayoutVersion(programId) === 1) {
return _MARKET_STAT_LAYOUT_V1;
}
return _MARKET_STATE_LAYOUT_V2;
return MARKET_STATE_LAYOUT_V2;
}
static async findAccountsByMints(
@ -190,6 +226,7 @@ export class Market {
address: PublicKey,
options: MarketOptions = {},
programId: PublicKey,
layoutOverride?: any,
) {
const { owner, data } = throwIfNull(
await connection.getAccountInfo(address),
@ -216,6 +253,7 @@ export class Market {
quoteMintDecimals,
options,
programId,
layoutOverride,
);
}
@ -690,7 +728,9 @@ export class Market {
makePlaceOrderInstruction<T extends PublicKey | Account>(
connection: Connection,
{
params: OrderParams<T>,
): TransactionInstruction {
const {
owner,
payer,
side,
@ -701,9 +741,7 @@ export class Market {
openOrdersAddressKey,
openOrdersAccount,
feeDiscountPubkey = null,
selfTradeBehavior = 'decrementTake',
}: OrderParams<T>,
): TransactionInstruction {
} = params;
// @ts-ignore
const ownerAddress: PublicKey = owner.publicKey ?? owner;
if (this.baseSizeNumberToLots(size).lte(new BN(0))) {
@ -712,9 +750,6 @@ export class Market {
if (this.priceNumberToLots(price).lte(new BN(0))) {
throw new Error('invalid price');
}
if (!this.supportsSrmFeeDiscounts) {
feeDiscountPubkey = null;
}
if (this.usesRequestQueue) {
return DexInstructions.newOrder({
market: this.address,
@ -732,37 +767,63 @@ export class Market {
orderType,
clientId,
programId: this._programId,
feeDiscountPubkey,
feeDiscountPubkey: this.supportsSrmFeeDiscounts
? feeDiscountPubkey
: null,
});
} else {
return DexInstructions.newOrderV3({
market: this.address,
bids: this._decoded.bids,
asks: this._decoded.asks,
requestQueue: this._decoded.requestQueue,
eventQueue: this._decoded.eventQueue,
baseVault: this._decoded.baseVault,
quoteVault: this._decoded.quoteVault,
openOrders: openOrdersAccount
? openOrdersAccount.publicKey
: openOrdersAddressKey,
owner: ownerAddress,
payer,
side,
limitPrice: this.priceNumberToLots(price),
maxBaseQuantity: this.baseSizeNumberToLots(size),
maxQuoteQuantity: new BN(this._decoded.quoteLotSize.toNumber()).mul(
this.baseSizeNumberToLots(size).mul(this.priceNumberToLots(price)),
),
orderType,
clientId,
programId: this._programId,
selfTradeBehavior,
feeDiscountPubkey,
});
return this.makeNewOrderV3Instruction(params);
}
}
makeNewOrderV3Instruction<T extends PublicKey | Account>(
params: OrderParams<T>,
): TransactionInstruction {
const {
owner,
payer,
side,
price,
size,
orderType = 'limit',
clientId,
openOrdersAddressKey,
openOrdersAccount,
feeDiscountPubkey = null,
selfTradeBehavior = 'decrementTake',
programId,
} = params;
// @ts-ignore
const ownerAddress: PublicKey = owner.publicKey ?? owner;
return DexInstructions.newOrderV3({
market: this.address,
bids: this._decoded.bids,
asks: this._decoded.asks,
requestQueue: this._decoded.requestQueue,
eventQueue: this._decoded.eventQueue,
baseVault: this._decoded.baseVault,
quoteVault: this._decoded.quoteVault,
openOrders: openOrdersAccount
? openOrdersAccount.publicKey
: openOrdersAddressKey,
owner: ownerAddress,
payer,
side,
limitPrice: this.priceNumberToLots(price),
maxBaseQuantity: this.baseSizeNumberToLots(size),
maxQuoteQuantity: new BN(this._decoded.quoteLotSize.toNumber()).mul(
this.baseSizeNumberToLots(size).mul(this.priceNumberToLots(price)),
),
orderType,
clientId,
programId: programId ?? this._programId,
selfTradeBehavior,
feeDiscountPubkey: this.supportsSrmFeeDiscounts
? feeDiscountPubkey
: null,
});
}
private async _sendTransaction(
connection: Connection,
transaction: Transaction,
@ -1189,6 +1250,7 @@ export interface OrderParams<T = Account> {
| 'cancelProvide'
| 'abortTransaction'
| undefined;
programId?: PublicKey;
}
export const _OPEN_ORDERS_LAYOUT_V1 = struct([

View File

@ -1,384 +0,0 @@
import { blob, struct } from 'buffer-layout';
import BN from 'bn.js';
import {
Connection,
PublicKey,
Account,
TransactionInstruction,
SystemProgram,
AccountMeta,
} from '@solana/web3.js';
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token';
import { accountFlagsLayout, publicKeyLayout, u64 } from './layout';
import { Market, MarketOptions, OrderParams } from './market';
import { DexInstructions } from './instructions';
////////////////////////////////////////////////////////////////////////////////
//
// This API is experimental. It may be subject to imminent breaking changes.
//
////////////////////////////////////////////////////////////////////////////////
// Permissioned market overrides Market since it requires a frontend, on-chain
// proxy program, which relays all instructions to the orderbook. This requires
// two modifications for most instructions.
//
// 1. The dex program ID must be changed to the proxy program.
// 2. The canonical dex program ID must be inserted as the first account
// in instructions using the proxy relay. The program is responsible for
// removing this account before relaying to the dex.
//
// Otherwise, the client should function the same as a regular Market client.
export class PermissionedMarket extends Market {
// Program ID of the permissioning proxy program.
private _proxyProgramId: PublicKey;
// Dex program ID.
private _dexProgramId: PublicKey;
// Account metas that are loaded as the first account to *all* transactions
// to the proxy.
private _preloadAccountMetas?: AccountMeta[];
constructor(
decoded: any,
baseMintDecimals: number,
quoteMintDecimals: number,
options: MarketOptions = {},
dexProgramId: PublicKey,
proxyProgramId: PublicKey,
preloadAccountMetas?: AccountMeta[],
) {
super(
decoded,
baseMintDecimals,
quoteMintDecimals,
options,
proxyProgramId,
);
this._proxyProgramId = proxyProgramId;
this._dexProgramId = dexProgramId;
this._preloadAccountMetas = preloadAccountMetas;
}
public static async openOrdersAddress(
market: PublicKey,
owner: PublicKey,
dexProgramId: PublicKey,
proxyProgramId: PublicKey,
): Promise<PublicKey> {
// b"open-orders".
const openOrdersStr = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
]);
const [addr] = await PublicKey.findProgramAddress(
[
openOrdersStr,
dexProgramId.toBuffer(),
market.toBuffer(),
owner.toBuffer(),
],
proxyProgramId,
);
return addr;
}
public makePlaceOrderInstructionPermissioned(
connection: Connection,
params: OrderParams<PublicKey>,
): Array<TransactionInstruction> {
// The amount of USDC transferred into the dex for the trade.
let amount;
if (params.side === 'buy') {
// @ts-ignore
amount = new BN(this._decoded.quoteLotSize.toNumber()).mul(
this.baseSizeNumberToLots(params.size).mul(
this.priceNumberToLots(params.price),
),
);
} else {
amount = this.baseSizeNumberToLots(params.size);
}
const approveIx = Token.createApproveInstruction(
TOKEN_PROGRAM_ID,
params.payer,
params.openOrdersAddressKey!,
params.owner,
[],
amount.toNumber(),
);
const tradeIx = this.proxy(
super.makePlaceOrderInstruction(connection, params),
);
return [approveIx, tradeIx];
}
/**
* @override
*/
static async load(
connection: Connection,
address: PublicKey,
options: MarketOptions = {},
dexProgramId: PublicKey,
proxyProgramId?: PublicKey,
preloadAccountMetas?: AccountMeta[],
): Promise<PermissionedMarket> {
const market = await Market.load(
connection,
address,
options,
dexProgramId,
);
return new PermissionedMarket(
market.decoded,
// @ts-ignore
market._baseSplTokenDecimals,
// @ts-ignore
market._quoteSplTokenDecimals,
options,
dexProgramId,
proxyProgramId!,
preloadAccountMetas!,
);
}
/**
* @override
*/
public static getLayout(_programId: PublicKey) {
return _MARKET_STATE_LAYOUT_V3;
}
/**
* @override
*/
public async makeInitOpenOrdersInstruction(
owner: PublicKey,
market: PublicKey,
): Promise<TransactionInstruction> {
// b"open-orders"
const openOrdersSeed = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
]);
// b"open-orders-init"
const openOrdersInitSeed = Buffer.from([
111,
112,
101,
110,
45,
111,
114,
100,
101,
114,
115,
45,
105,
110,
105,
116,
]);
const [openOrders] = await PublicKey.findProgramAddress(
[
openOrdersSeed,
this._dexProgramId.toBuffer(),
market.toBuffer(),
owner.toBuffer(),
],
this._proxyProgramId,
);
const [marketAuthority] = await PublicKey.findProgramAddress(
[openOrdersInitSeed, this._dexProgramId.toBuffer(), market.toBuffer()],
this._proxyProgramId,
);
const ix = DexInstructions.initOpenOrders({
market,
openOrders,
owner,
programId: this._proxyProgramId,
marketAuthority,
});
// Prepend to the account list extra accounts needed for PDA initialization.
ix.keys = [
{ pubkey: this._dexProgramId, isSigner: false, isWritable: false },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
...ix.keys,
];
return this.proxy(ix);
}
/**
* @override
*/
public makePlaceOrderInstruction<T extends PublicKey | Account>(
connection: Connection,
params: OrderParams<T>,
): TransactionInstruction {
const ix = super.makePlaceOrderInstruction(connection, params);
return this.proxy(ix);
}
/**
* @override
*/
public async makeCancelOrderByClientIdInstruction(
connection: Connection,
owner: PublicKey,
openOrders: PublicKey,
clientId: BN,
): Promise<TransactionInstruction> {
const ix = (
await this.makeCancelOrderByClientIdTransaction(
connection,
owner,
openOrders,
clientId,
)
).instructions[0];
return this.proxy(ix);
}
/**
* @override
*/
public async makeSettleFundsInstruction(
openOrders: PublicKey,
owner: PublicKey,
baseWallet: PublicKey,
quoteWallet: PublicKey,
referrerQuoteWallet: PublicKey,
): Promise<TransactionInstruction> {
const ix = DexInstructions.settleFunds({
market: this.address,
openOrders,
owner,
baseVault: this.decoded.baseVault,
quoteVault: this.decoded.quoteVault,
baseWallet,
quoteWallet,
vaultSigner: await PublicKey.createProgramAddress(
[
this.address.toBuffer(),
this.decoded.vaultSignerNonce.toArrayLike(Buffer, 'le', 8),
],
this._dexProgramId,
),
programId: this._proxyProgramId,
referrerQuoteWallet,
});
return this.proxy(ix);
}
/**
* @override
*/
public makeCloseOpenOrdersInstruction(
openOrders: PublicKey,
owner: PublicKey,
solWallet: PublicKey,
): TransactionInstruction {
const ix = DexInstructions.closeOpenOrders({
market: this.address,
openOrders,
owner,
solWallet,
programId: this._proxyProgramId,
});
return this.proxy(ix);
}
/**
* @override
*/
// Skips the proxy frontend and goes directly to the orderbook.
public makeConsumeEventsInstruction(
openOrdersAccounts: Array<PublicKey>,
limit: number,
): TransactionInstruction {
return DexInstructions.consumeEvents({
market: this.address,
eventQueue: this.decoded.eventQueue,
coinFee: this.decoded.eventQueue,
pcFee: this.decoded.eventQueue,
openOrdersAccounts,
limit,
programId: this._dexProgramId,
});
}
// Adds the serum dex account to the instruction so that proxies can
// relay (CPI requires the executable account).
private proxy(ix: TransactionInstruction) {
ix.keys = [
{ pubkey: this._dexProgramId, isWritable: false, isSigner: false },
...(this._preloadAccountMetas ?? []),
...ix.keys,
];
return ix;
}
}
const _MARKET_STATE_LAYOUT_V3 = struct([
blob(5),
accountFlagsLayout('accountFlags'),
publicKeyLayout('ownAddress'),
u64('vaultSignerNonce'),
publicKeyLayout('baseMint'),
publicKeyLayout('quoteMint'),
publicKeyLayout('baseVault'),
u64('baseDepositsTotal'),
u64('baseFeesAccrued'),
publicKeyLayout('quoteVault'),
u64('quoteDepositsTotal'),
u64('quoteFeesAccrued'),
u64('quoteDustThreshold'),
publicKeyLayout('requestQueue'),
publicKeyLayout('eventQueue'),
publicKeyLayout('bids'),
publicKeyLayout('asks'),
u64('baseLotSize'),
u64('quoteLotSize'),
u64('feeRateBps'),
u64('referrerRebatesAccrued'),
publicKeyLayout('authority'),
blob(7),
]);