Merge pull request #2 from project-serum/feat/crank
FEAT: Cranks and minor improvements
This commit is contained in:
commit
167107f31b
|
@ -88,3 +88,12 @@ dex.runMarketMaker(market, owner, {
|
||||||
quoteGeckoSymbol: "usd",
|
quoteGeckoSymbol: "usd",
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Run a crank
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
dex.runCrank(market, owner, {
|
||||||
|
durationInSecs: 20,
|
||||||
|
verbose: true,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
134
ts/src/coin.ts
134
ts/src/coin.ts
|
@ -1,4 +1,8 @@
|
||||||
import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
|
import {
|
||||||
|
getMint,
|
||||||
|
getOrCreateAssociatedTokenAccount,
|
||||||
|
mintTo,
|
||||||
|
} from "@solana/spl-token";
|
||||||
import {
|
import {
|
||||||
Connection,
|
Connection,
|
||||||
Keypair,
|
Keypair,
|
||||||
|
@ -8,28 +12,130 @@ import {
|
||||||
} from "@solana/web3.js";
|
} from "@solana/web3.js";
|
||||||
|
|
||||||
export class Coin {
|
export class Coin {
|
||||||
symbol: string;
|
private _symbol: string;
|
||||||
|
|
||||||
decimals: number;
|
private _decimals: number;
|
||||||
|
|
||||||
mint: PublicKey;
|
private _mint: PublicKey;
|
||||||
|
|
||||||
mintAuthority: Keypair;
|
private _mintAuthority: Keypair;
|
||||||
|
|
||||||
freezeAuthority: Keypair;
|
private _freezeAuthority: Keypair | null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
symbol: string,
|
symbol: string,
|
||||||
decimals: number,
|
decimals: number,
|
||||||
mint: PublicKey,
|
mint: PublicKey,
|
||||||
mintAuthority: Keypair,
|
mintAuthority: Keypair,
|
||||||
freezeAuthority: Keypair,
|
freezeAuthority: Keypair | null,
|
||||||
) {
|
) {
|
||||||
this.symbol = symbol;
|
this._symbol = symbol;
|
||||||
this.decimals = decimals;
|
this._decimals = decimals;
|
||||||
this.mint = mint;
|
this._mint = mint;
|
||||||
this.mintAuthority = mintAuthority;
|
this._mintAuthority = mintAuthority;
|
||||||
this.freezeAuthority = freezeAuthority;
|
this._freezeAuthority = freezeAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get symbol() {
|
||||||
|
return this._symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get decimals() {
|
||||||
|
return this._decimals;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get mint() {
|
||||||
|
return this._mint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get mintAuthority() {
|
||||||
|
return this._mintAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get freezeAuthority() {
|
||||||
|
return this._freezeAuthority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load an exisiting mint as a Coin.
|
||||||
|
*
|
||||||
|
* @param connection The `Connection` object to connect to Solana.
|
||||||
|
* @param symbol The symbol to assign to the coin.
|
||||||
|
* @param mint The `PublicKey` of the Mint for the coin.
|
||||||
|
* @param mintAuthority The minting authority `Keypair` for the coin.
|
||||||
|
* @param freezeAuthority The optional freezing authority `Keypair` for the coin.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
static async load(
|
||||||
|
connection: Connection,
|
||||||
|
symbol: string,
|
||||||
|
mint: PublicKey,
|
||||||
|
mintAuthority: Keypair,
|
||||||
|
freezeAuthority: Keypair | null,
|
||||||
|
): Promise<Coin> {
|
||||||
|
const {
|
||||||
|
decimals,
|
||||||
|
mintAuthority: tokenMintAuthority,
|
||||||
|
freezeAuthority: tokenFreezeAuthority,
|
||||||
|
} = await getMint(connection, mint, "confirmed");
|
||||||
|
|
||||||
|
// tokenMintAuthority has to be truthy since createMint requires a mint authority as well.
|
||||||
|
if (
|
||||||
|
!tokenMintAuthority ||
|
||||||
|
tokenMintAuthority.toBase58() !== mintAuthority.publicKey.toBase58()
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid Mint authority provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!tokenFreezeAuthority !== !!freezeAuthority) {
|
||||||
|
throw new Error("Invalid Freeze authority provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
tokenFreezeAuthority &&
|
||||||
|
freezeAuthority &&
|
||||||
|
tokenFreezeAuthority.toBase58() !== freezeAuthority.publicKey.toBase58()
|
||||||
|
) {
|
||||||
|
throw new Error("Invalid Freeze authority provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Coin(symbol, decimals, mint, mintAuthority, freezeAuthority);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Equality check between two `Coin`s.
|
||||||
|
*
|
||||||
|
* @param to The `Coin` object to compare to.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public isEqual(to: Coin) {
|
||||||
|
const { mintAuthority, freezeAuthority } = to;
|
||||||
|
|
||||||
|
if (
|
||||||
|
mintAuthority.publicKey.toBase58() !==
|
||||||
|
this.mintAuthority.publicKey.toBase58()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!!freezeAuthority !== !!this.freezeAuthority) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
freezeAuthority &&
|
||||||
|
this.freezeAuthority &&
|
||||||
|
freezeAuthority.publicKey.toBase58() !==
|
||||||
|
this.freezeAuthority.publicKey.toBase58()
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
to.symbol === this.symbol &&
|
||||||
|
to.decimals === this.decimals &&
|
||||||
|
to.mint.toBase58() === this.mint.toBase58()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,6 +178,10 @@ export class Coin {
|
||||||
owner: Keypair,
|
owner: Keypair,
|
||||||
connection: Connection,
|
connection: Connection,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (!this.mintAuthority) {
|
||||||
|
throw new Error("Coin has no mint authority");
|
||||||
|
}
|
||||||
|
|
||||||
const destination = await getOrCreateAssociatedTokenAccount(
|
const destination = await getOrCreateAssociatedTokenAccount(
|
||||||
connection,
|
connection,
|
||||||
owner,
|
owner,
|
||||||
|
|
|
@ -41,23 +41,44 @@ 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.
|
||||||
*/
|
*/
|
||||||
export class Dex {
|
export class Dex {
|
||||||
public address: PublicKey;
|
private _address: PublicKey;
|
||||||
|
|
||||||
coins: Coin[];
|
private _coins: Coin[];
|
||||||
|
|
||||||
markets: DexMarket[];
|
private _markets: DexMarket[];
|
||||||
|
|
||||||
connection: Connection;
|
private _connection: Connection;
|
||||||
|
|
||||||
constructor(address: PublicKey, connection: Connection) {
|
constructor(address: PublicKey, connection: Connection) {
|
||||||
this.address = address;
|
this._address = address;
|
||||||
this.connection = connection;
|
this._connection = connection;
|
||||||
this.coins = [];
|
this._coins = [];
|
||||||
this.markets = [];
|
this._markets = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get coins() {
|
||||||
|
return this._coins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get markets() {
|
||||||
|
return this._markets;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connection() {
|
||||||
|
return this._connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get address() {
|
||||||
|
return this._address;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -66,23 +87,29 @@ export class Dex {
|
||||||
* @param symbol The symbol of the coin to create
|
* @param symbol The symbol of the coin to create
|
||||||
* @param decimals The decimals of the coin to create
|
* @param decimals The decimals of the coin to create
|
||||||
* @param payer The payer `Keypair` to use for the transactions
|
* @param payer The payer `Keypair` to use for the transactions
|
||||||
* @param mintAuthority The mint authority `Keypair` to use for the mint
|
* @param mintAuthority The optional mint authority `Keypair` to use for the mint
|
||||||
* @param freezeAuthority The freeze authority `Keypair` to use for the mint
|
* @param freezeAuthority The optionals freeze authority `Keypair` to use for the mint
|
||||||
|
* @param keypair The optional keypair for the Mint to be created, defaults to a random one
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public createCoin = async (
|
public createCoin = async (
|
||||||
symbol: string,
|
symbol: string,
|
||||||
decimals: number,
|
decimals: number,
|
||||||
payer: Keypair,
|
payer: Keypair,
|
||||||
mintAuthority: Keypair | null,
|
mintAuthority: Keypair,
|
||||||
freezeAuthority: Keypair | null,
|
freezeAuthority: Keypair | null,
|
||||||
|
keypair?: Keypair,
|
||||||
): Promise<Coin> => {
|
): Promise<Coin> => {
|
||||||
const mint = await createMint(
|
const mint = await createMint(
|
||||||
this.connection,
|
this.connection,
|
||||||
payer,
|
payer,
|
||||||
mintAuthority ? mintAuthority.publicKey : null,
|
mintAuthority.publicKey,
|
||||||
freezeAuthority ? freezeAuthority.publicKey : null,
|
freezeAuthority ? freezeAuthority.publicKey : null,
|
||||||
decimals,
|
decimals,
|
||||||
|
keypair,
|
||||||
|
{
|
||||||
|
commitment: "confirmed",
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const coin = new Coin(
|
const coin = new Coin(
|
||||||
|
@ -255,7 +282,7 @@ export class Dex {
|
||||||
if (opts.durationInSecs < 0)
|
if (opts.durationInSecs < 0)
|
||||||
throw new Error("Duration must be greater than 0.");
|
throw new Error("Duration must be greater than 0.");
|
||||||
|
|
||||||
const child = fork(`${__dirname}/scripts/marketMaker`, null, {
|
const child = fork(`${__dirname}/scripts/marketMaker`, {
|
||||||
// https://nodejs.org/api/child_process.html#optionsdetached
|
// https://nodejs.org/api/child_process.html#optionsdetached
|
||||||
// detached also doesn't seem to be making a difference.
|
// detached also doesn't seem to be making a difference.
|
||||||
detached: true,
|
detached: true,
|
||||||
|
@ -263,7 +290,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 +316,46 @@ export class Dex {
|
||||||
|
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a crank on a separate node process for the given `DexMarket` for specified duration.
|
||||||
|
*
|
||||||
|
* @param market The `DexMarket` to run a crank for
|
||||||
|
* @param owner The owner `FileKeypair` consuming events.
|
||||||
|
* @param opts The crank options used
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
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`, {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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 } =
|
||||||
|
|
|
@ -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);
|
||||||
|
};
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
|
|
@ -48,15 +48,10 @@ describe("Serum Dev Tools", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("can init dex market", async () => {
|
it("can init dex market", async () => {
|
||||||
dexMarket = await dex.initDexMarket(
|
dexMarket = await dex.initDexMarket(owner.keypair, baseCoin, quoteCoin, {
|
||||||
owner.keypair,
|
lotSize: 1e-3,
|
||||||
dex.getCoin("SAYA"),
|
tickSize: 1e-2,
|
||||||
dex.getCoin("SRM"),
|
});
|
||||||
{
|
|
||||||
lotSize: 1e-3,
|
|
||||||
tickSize: 1e-2,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
dexMarket.address.toBase58(),
|
dexMarket.address.toBase58(),
|
||||||
|
@ -96,4 +91,41 @@ describe("Serum Dev Tools", () => {
|
||||||
assert.equal(orders[0].size, 10);
|
assert.equal(orders[0].size, 10);
|
||||||
assert.equal(orders[0].side, "buy");
|
assert.equal(orders[0].side, "buy");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("can load coins", async () => {
|
||||||
|
const tempCoin = await dex.createCoin(
|
||||||
|
"test",
|
||||||
|
9,
|
||||||
|
owner.keypair,
|
||||||
|
owner.keypair,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
const loadedCoin = await Coin.load(
|
||||||
|
connection,
|
||||||
|
"test",
|
||||||
|
tempCoin.mint,
|
||||||
|
owner.keypair,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(tempCoin.isEqual(loadedCoin));
|
||||||
|
assert.deepEqual(tempCoin, loadedCoin);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("invalid freeze authority while load coins", async () => {
|
||||||
|
const tempCoin = await dex.createCoin(
|
||||||
|
"test",
|
||||||
|
9,
|
||||||
|
owner.keypair,
|
||||||
|
owner.keypair,
|
||||||
|
owner.keypair,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Coin.load(connection, "test", tempCoin.mint, owner.keypair, null);
|
||||||
|
} catch (err) {
|
||||||
|
assert.ok(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"types": ["mocha", "chai", "node"],
|
"types": ["mocha", "chai", "node"],
|
||||||
|
"strictNullChecks": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "tests/**/*.ts", "**/*.test.ts"],
|
"include": ["src/**/*.ts", "tests/**/*.ts", "**/*.test.ts"],
|
||||||
"exclude": ["node_modules", "dist"],
|
"exclude": ["node_modules", "dist"],
|
||||||
|
|
Loading…
Reference in New Issue