serum: Middleware for market proxy (#138)
This commit is contained in:
parent
145b5f1290
commit
4e132e831d
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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([
|
||||
|
|
|
@ -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),
|
||||
]);
|
Loading…
Reference in New Issue