basic cranker setup

This commit is contained in:
Sayantan Karmakar 2022-06-22 00:47:43 +05:30
parent 57880d10e1
commit 1a4ff84d2a
8 changed files with 144 additions and 11 deletions

View File

@ -51,12 +51,17 @@ const main = async () => {
console.log(`Funded owner with ${baseCoin.symbol} and ${quoteCoin.symbol}`); console.log(`Funded owner with ${baseCoin.symbol} and ${quoteCoin.symbol}`);
dex.runMarketMaker(market, owner, { dex.runMarketMaker(market, owner, {
durationInSecs: 15, durationInSecs: 30,
orderCount: 3, orderCount: 3,
initialBidSize: 1000, initialBidSize: 1000,
baseGeckoSymbol: "solana", baseGeckoSymbol: "solana",
quoteGeckoSymbol: "usd", quoteGeckoSymbol: "usd",
}); });
dex.runCrank(market, owner, {
durationInSecs: 20,
verbose: true,
});
}; };
const runMain = async () => { const runMain = async () => {

View File

@ -41,6 +41,11 @@ export type MarketMakerOpts = {
quoteGeckoSymbol: string; quoteGeckoSymbol: string;
}; };
export type CrankOpts = {
durationInSecs: number;
verbose: boolean;
};
/** /**
* Dex is a wrapper class for a deployed Serum Dex program. * Dex is a wrapper class for a deployed Serum Dex program.
*/ */
@ -263,7 +268,7 @@ export class Dex {
}); });
console.log( console.log(
`Process ${child.pid}: Running Market Maker for ${market.baseCoin.symbol}/${market.quoteCoin.symbol}. Note: No crank running`, `Process ${child.pid}: Running Market Maker for ${market.baseCoin.symbol}/${market.quoteCoin.symbol}.`,
); );
// unref doesn't seem to be making a difference for a forked process. // unref doesn't seem to be making a difference for a forked process.
@ -289,4 +294,38 @@ export class Dex {
return child; return child;
} }
public runCrank(
market: DexMarket,
owner: FileKeypair,
opts: CrankOpts,
): ChildProcess {
if (opts.durationInSecs < 0)
throw new Error("Duration must be greater than 0.");
const child = fork(`${__dirname}/scripts/cranker`, null, {
// https://nodejs.org/api/child_process.html#optionsdetached
// detached also doesn't seem to be making a difference.
detached: true,
stdio: ["pipe", 0, 0, "ipc"],
});
console.log(
`Process ${child.pid}: Running Crank for ${market.baseCoin.symbol}/${market.quoteCoin.symbol}.`,
);
child.send({
action: "start",
args: {
marketAddress: market.address.toString(),
programID: this.address.toString(),
rpcEndpoint: this.connection.rpcEndpoint,
ownerFilePath: owner.filePath,
duration: opts.durationInSecs * 1000,
verbose: opts.verbose ? "true" : "false",
},
});
return child;
}
} }

View File

@ -2,4 +2,4 @@ export * from "./dex";
export * from "./coin"; export * from "./coin";
export * from "./market"; export * from "./market";
export * from "./fileKeypair"; export * from "./fileKeypair";
export * from "./types"; export { OrderType, TransactionWithSigners } from "./types";

View File

@ -19,7 +19,7 @@ import {
import { Market as SerumMarket } from "@project-serum/serum"; import { Market as SerumMarket } from "@project-serum/serum";
import { Coin } from "./coin"; import { Coin } from "./coin";
import { getDecimalCount, withAssociatedTokenAccount } from "./utils"; import { getDecimalCount, withAssociatedTokenAccount } from "./utils";
import { OrderType, TransactionWithSigners } from "./types"; import { OrderType, SelfTradeBehaviour, TransactionWithSigners } from "./types";
const REQUEST_QUEUE_SIZE = 5120 + 12; // https://github.com/mithraiclabs/psyoptions/blob/f0c9f73408a27676e0c7f156f5cae71f73f59c3f/programs/psy_american/src/lib.rs#L1003 const REQUEST_QUEUE_SIZE = 5120 + 12; // https://github.com/mithraiclabs/psyoptions/blob/f0c9f73408a27676e0c7f156f5cae71f73f59c3f/programs/psy_american/src/lib.rs#L1003
const EVENT_QUEUE_SIZE = 262144 + 12; // https://github.com/mithraiclabs/psyoptions-ts/blob/ba1888ea83e634e1c7a8dad820fe67d053cf3f5c/packages/psy-american/src/instructions/initializeSerumMarket.ts#L84 const EVENT_QUEUE_SIZE = 262144 + 12; // https://github.com/mithraiclabs/psyoptions-ts/blob/ba1888ea83e634e1c7a8dad820fe67d053cf3f5c/packages/psy-american/src/instructions/initializeSerumMarket.ts#L84
@ -289,6 +289,7 @@ export class DexMarket {
orderType: OrderType, orderType: OrderType,
size: number, size: number,
price: number, price: number,
selfTradeBehaviour?: SelfTradeBehaviour,
): Promise<TransactionWithSigners> { ): Promise<TransactionWithSigners> {
try { try {
DexMarket.sanityCheck(serumMarket, price, size); DexMarket.sanityCheck(serumMarket, price, size);
@ -335,6 +336,7 @@ export class DexMarket {
orderType, orderType,
feeDiscountPubkey: null, feeDiscountPubkey: null,
openOrdersAddressKey: openOrders.address, openOrdersAddressKey: openOrders.address,
selfTradeBehavior: selfTradeBehaviour,
}; };
const { transaction: placeOrderTx, signers: placeOrderSigners } = const { transaction: placeOrderTx, signers: placeOrderSigners } =

70
ts/src/scripts/cranker.ts Normal file
View File

@ -0,0 +1,70 @@
import { MessageType } from "../types";
import { Market as SerumMarket } from "@project-serum/serum";
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
import { delay, logIfVerbose } from "../utils";
import { FileKeypair } from "../fileKeypair";
const MAX_OPEN_ORDERS = 10;
process.on("message", async (message: MessageType) => {
if (message.action === "start") {
await basicCranker(message.args);
}
});
const crank = async (
market: SerumMarket,
owner: Keypair,
connection: Connection,
isVerbose: boolean,
) => {
const eventQueue = await market.loadEventQueue(connection);
logIfVerbose(`EventQueue length: ${eventQueue.length}`, isVerbose);
if (eventQueue.length > 0) {
const orderedAccounts: PublicKey[] = eventQueue
.slice(0, MAX_OPEN_ORDERS)
.map((e) => e.openOrders)
.sort((a, b) => a.toBuffer().swap64().compare(b.toBuffer().swap64()));
const tx = new Transaction();
tx.add(market.makeConsumeEventsInstruction(orderedAccounts, 65535));
try {
const sig = await connection.sendTransaction(tx, [owner]);
await connection.confirmTransaction(sig, "confirmed");
logIfVerbose(`ConsumeEvents: ${sig}`, isVerbose);
logIfVerbose(`------ ConsumeEvents Confirmed ------`, isVerbose);
} catch (err) {
logIfVerbose(`Error: ${err}`, isVerbose);
logIfVerbose(`------ ConsumeEvents Failed ------`, isVerbose);
}
}
};
const basicCranker = async (args) => {
setTimeout(() => {
console.log(`Exiting Cranker @ ${process.pid}`);
process.exit(0);
}, Number.parseInt(args.duration));
const isVerbose = args.verbose === "true";
const owner = FileKeypair.load(args.ownerFilePath);
const connection = new Connection(args.rpcEndpoint, "confirmed");
const serumMarket = await SerumMarket.load(
connection,
new PublicKey(args.marketAddress),
{ commitment: "confirmed" },
new PublicKey(args.programID),
);
await crank(serumMarket, owner.keypair, connection, isVerbose);
do {
await delay(2000);
await crank(serumMarket, owner.keypair, connection, isVerbose);
// eslint-disable-next-line no-constant-condition
} while (true);
};

View File

@ -10,12 +10,7 @@ import { FileKeypair } from "../fileKeypair";
import { DexMarket } from "../market"; import { DexMarket } from "../market";
import axios from "axios"; import axios from "axios";
import { getDecimalCount, roundToDecimal } from "../utils"; import { getDecimalCount, roundToDecimal } from "../utils";
import { MessageType } from "../types";
type MessageType = {
action: "start";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any;
};
process.on("message", async (message: MessageType) => { process.on("message", async (message: MessageType) => {
if (message.action === "start") { if (message.action === "start") {
@ -121,6 +116,7 @@ const placeOrders = async (
"postOnly", "postOnly",
buySize, buySize,
buyPrice, buyPrice,
"decrementTake",
); );
tx.add(buyTransaction); tx.add(buyTransaction);
signersArray.push(buySigners); signersArray.push(buySigners);
@ -136,6 +132,7 @@ const placeOrders = async (
"postOnly", "postOnly",
sellSize, sellSize,
sellPrice, sellPrice,
"decrementTake",
); );
tx.add(sellTransaction); tx.add(sellTransaction);
signersArray.push(sellSigners); signersArray.push(sellSigners);
@ -169,7 +166,7 @@ const marketMaker = async (args) => {
const owner = FileKeypair.load(args.ownerFilePath); const owner = FileKeypair.load(args.ownerFilePath);
placeOrders(owner.keypair, serumMarket, connection, { await placeOrders(owner.keypair, serumMarket, connection, {
orderCount: Number.parseInt(args.orderCount), orderCount: Number.parseInt(args.orderCount),
initialBidSize: Number.parseInt(args.initialBidSize), initialBidSize: Number.parseInt(args.initialBidSize),
baseGeckoSymbol: args.baseGeckoSymbol, baseGeckoSymbol: args.baseGeckoSymbol,

View File

@ -6,3 +6,13 @@ export type TransactionWithSigners = {
}; };
export type OrderType = "limit" | "ioc" | "postOnly"; export type OrderType = "limit" | "ioc" | "postOnly";
export type SelfTradeBehaviour =
| "decrementTake"
| "cancelProvide"
| "abortTransaction";
export type MessageType = {
action: "start";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
args: any;
};

View File

@ -77,3 +77,13 @@ export function roundToDecimal(
) { ) {
return decimals ? Math.round(value * 10 ** decimals) / 10 ** decimals : value; return decimals ? Math.round(value * 10 ** decimals) / 10 ** decimals : value;
} }
export function logIfVerbose(message: string, isVerbose: boolean) {
if (isVerbose) {
console.log(message);
}
}
export function delay(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}