Getting Started with Serum Dex
Start building on Serum DEX using Project Serum's all-new Developer Tooling Suite.
In this article, we will be looking at Project Serum's new Developer Tooling suite, serum-dev-tools
, that comes with a new SDK, a CLI and as well as a Serum Explorer that provides you with more insight into various Serum DEX markets.
The purpose of this project is to create a developer tooling suite that significantly improves the developer experience of building projects composing with Serum DEX.
Now, let's look into the different tools one by one, shall we?
The CLI
The serum-dev-tools
CLI can be installed into your machine with the following command,
cargo install serum-dev-tools
The CLI tool allows you to easily deploy a Serum DEX program into any Solana cluster you wish to do so. At the time of writing this article, it deploys the v3 version of the Serum DEX, which can be found here.
The first step to deploying is, initialising a workspace folder in whichever directory you're currently at. This is done by the following command,
serum-dev-tools init
// Output: Initialized dev-tools!
You'll now be able to see a folder by the name, dev-tools in your current directory. This folder will have,
1) The built serum-dex.so
file, fetched from the Anchor registry.
2) A serum-dex-dev.json
key-pair, which is used as the address at which the program will be deployed to.
You can print the public key for the above mentioned key-pair using the following command,
serum-dev-tools instance
// Output: 5bJT7CEMipCkEjmx6urgaJKP78x5Ag5ZEqbCb87UwdC4
Now that you have the binary file, and a key-pair for the program, it's time to deploy!
You can easily deploy the Serum Dex program to a cluster of your choice using the following command,
serum-dev-tools deploy localnet
// Output: Program Id: 5bJT7CEMipCkEjmx6urgaJKP78x5Ag5ZEqbCb87UwdC4
You have now, successfully deployed a Serum DEX v3 program!
Note: If you're looking to deploy to any other cluster apart from localnet, you will need a significant amount of SOL to do so everytime.
The SDK
Now that we have our program deployed, the next thing we wanna do is interact with it. This is where the new, @project-serum/serum-dev-tools
SDK comes into play. It is available as a npm package here.
Using this package, you can easily create markets on a deployed Serum DEX program, place orders, cancel orders, and even run a market maker!
Persisting Keypairs
Since this package has a market making feature, which basically runs a market making script on a separate process, you will need to persist key-pairs between two or more different process. Due to this, serum-dev-tools
provides FileKeypair
, which is a wrapper class around @solana/web3.js
's Keypair
class, but provides additional functionality of saving those key-pairs in your local filesystem, allowing different processes to use them.
Here's how you would use the FileKeypair
class,
import { FileKeypair } from "@project-serum/serum-dev-tools";
const owner = FileKeypair.generate("./scripts/keys/owner.json");
console.log("Owner: ", owner.keypair.publicKey.toString());
The owner
key-pair shown above will now be used to make all the transactions interacting with the deployed Serum DEX program. But before we can do that, we need to fund this key-pair with some SOL.
import { Connection, LAMPORTS_PER_SOL } from "@solana/web3.js";
const connection = new Connection("http://localhost:8899", "confirmed");
const airdropSig = await connection.requestAirdrop(
owner.keypair.publicKey,
10 * LAMPORTS_PER_SOL,
);
await connection.confirmTransaction(airdropSig);
Initialize a Dex
To interact with the DEX program deployed, you will first need to initialize a Dex
class. With an instance of this class, you can initialize a market, register coins, place orders, cancel orders, etc.
const dexAddress = new PublicKey(
"5bJT7CEMipCkEjmx6urgaJKP78x5Ag5ZEqbCb87UwdC4",
);
const dex = new Dex(dexAddress, connection);
Create Coins
The Dex
class also stores a list of tokens, or Coins
for which you can then create markets on the DEX. The markets created using an instance of Dex
are also stored in a list of DexMarkets
.
To create a Coin
,
const baseCoin = await dex.createCoin(
"SAYA",
9,
owner.keypair,
owner.keypair,
owner.keypair,
);
const quoteCoin = await dex.createCoin(
"SRM",
9,
owner.keypair,
owner.keypair,
owner.keypair,
);
The createCoin
method creates a Mint
with the required parameters such as, decimals
, mintAuthority
, freezeAuthority
, and also stores a symbol
string as metadata to be able to fetch a Coin
using it. It also registers this instance of Coin
with the Dex
by adding it to the list of Coins
.
You can get a Coin
that is registered with the Dex
using the getCoin
method as shown below,
const coin = dex.getCoin("SAYA");
You will also have to fund the owner
key-pair with these coins before you can place orders and this is how that's done,
await baseCoin.fundAccount(2e6, owner.keypair, connection);
await quoteCoin.fundAccount(2e6, owner.keypair, connection);
Start a Market
We now have a Dex
and a couple of Coins
. It's time to create a market for these Coins
on the Dex
.
Here's how you do that,
const market = await dex.initDexMarket(owner.keypair, baseCoin, quoteCoin, {
lotSize: 1e-3,
tickSize: 1e-2,
});
The initDexMarket
method takes in as parameters, a Keypair
to be the payer, the Base Coin
, the Quote Coin
and two more parameters which are,
lotSize
: This is the lowest representable quantity of the base coin. That would mean, if a market has alotSize
of0.01
, then the size of any order placed on this market must be an increment of0.01
.tickSize
: This is the lowest representable quantity of the quote coin. That would mean, if a market has atickSize
of0.001
then the price of any order placed on this market must be an increment of0.001
.
In return, you get a DexMarket
object which wraps @project-serum/serum
's Market
class along with metadata such as marketSymbol
and the Coins
for the market.
Place/Cancel Orders
Once you have a DexMarket
, you're ready to place and cancel orders on the market. Here's how you place an order,
Note: All of the methods for
DexMarket
are static to allow using them in a separate node script, such as a market making script where you don't need properties such asbaseCoin
andquoteCoin
, and only require using@project-serum/serum
'sMarket
.
const txSig = await DexMarket.placeOrder(
connection,
owner.keypair,
market.serumMarket,
"buy",
"postOnly",
10,
10,
);
To be able to cancel orders, you first need to get which order you want to cancel. You can get all the orders for a key-pair using the following method,
const orders = await DexMarket.getOrdersForOwner(
owner.keypair,
market.serumMarket,
connection
);
And now, to cancel one of these orders, you would do something like this,
const txSig = await DexMarket.cancelOrder(
connection,
owner.keypair,
market.serumMarket,
orders[0]
);
You can also get the Transaction
object for placing and cancelling orders using the following methods,
const { placeTransaction, placeSigners } = await DexMarket.getPlaceOrderTransaction(
connection,
owner.keypair,
market.serumMarket,
"buy",
"postOnly",
10,
10,
);
const { cancelTransaction, cancelSigners } = await DexMarket.getCancelOrderTransaction(
connection,
owner.keypair,
serumMarket,
orders[0],
);
We have now covered the basic features of serum-dev-tools
, but we still have one last feature left to show!
Market Making
With serum-dev-tools
, you can now run a market maker and test out your markets easily. What a market maker does is, it basically places multiple orders on the market with the sizes and prices spread across a range, in intervals.
Currently, the market making process we support is pretty basic, where we first fetch the latest price of an actual market from CoinGecko, and then place both bids and asks orders with prices spread on both sides. This allows your Serum market to behave like the actual market we referred to from CoinGecko.
For example, let's say the price returned from CoinGecko is p
. Then, we would place 3 bids and 3 asks, and have the spread widen out by 1% for each successive order. The number of orders on each side here can be configured to any number as we'll see.
So, the bids prices would be 0.99 * p
, 0.98 * p
, and so on. Similarly, for asks, the prices would be 1.01 * p
, 1.02 * p
and so on.
In terms of sizing, the innermost orders should have the least size, increasing outward. You would have to specify an initialBidSize
, which would be the size of the first bid placed.
The orders would look something like this,
- Bid #1: Price =
0.99p
, Size =initialBidSize * 1
- Bid #2: Price =
0.98p
, Size =initialBidSize * 2
- Bid #3: Price =
0.97p
, Size =initialBidSize * 3
- Ask #1: Price =
1.01p
, Size =(initialBidSize * 1) / p
- Ask #2: Price =
1.02p
, Size =(initialBidSize * 2) / p
- Ask #3: Price =
1.03p
, Size =(initialBidSize * 3) / p
We divide by p for the ask sizes so that the notional value of each successive ask is the same size as that of the corresponding bid.
After a specified interval, these orders are then cancelled and new orders with the latest price p
are placed again. This process of placing and cancelling orders can go on for however long you wish for.
The following code shows how you can start a market maker using serum-dev-tools
,
dex.runMarketMaker(market, owner, {
durationInSecs: 15, // How long the market maker should run
orderCount: 3, // How many orders on each side to be placed
initialBidSize: 1000, // As explained above
baseGeckoSymbol: "solana", // The base symbol used by CoinGecko for their API
quoteGeckoSymbol: "usd", // The quote symbol used by CoinGecko for their API
});
This method, runs a market maker script on a separate node process. You can have a look at the script here.
That's it for today on Project Serum's new Developer Tooling Suite, serum-dev-tools
. I hope you guys do check it out and let us know what you guys think! You can reach out to me on Twitter, @sayantanxyz
This project is in active development, and we have new features such as cranking, v4 support, and more configurations for market making lined up already. Feel free to report any bugs or issues here.