Init
This commit is contained in:
commit
94908126b9
|
@ -0,0 +1,5 @@
|
|||
*~
|
||||
target/
|
||||
localnet-logs/
|
||||
test-ledger/
|
||||
.anchor/
|
|
@ -0,0 +1,6 @@
|
|||
[submodule "permissioned-markets/deps/serum-dex"]
|
||||
path = permissioned-markets/deps/serum-dex
|
||||
url = https://github.com/project-serum/serum-dex
|
||||
[submodule "deps/serum-dex"]
|
||||
path = deps/serum-dex
|
||||
url = git@github.com:project-serum/serum-dex.git
|
|
@ -0,0 +1,7 @@
|
|||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[[test.genesis]]
|
||||
address = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
|
||||
program = "./deps/serum-dex/dex/target/deploy/serum_dex.so"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
||||
|
||||
exclude = [
|
||||
"deps/serum-dex",
|
||||
]
|
|
@ -0,0 +1,13 @@
|
|||
.PHONY: test build build-deps build-dex localnet
|
||||
|
||||
test: build
|
||||
anchor test
|
||||
|
||||
build: build-dex
|
||||
anchor build
|
||||
|
||||
build-dex:
|
||||
cd deps/serum-dex/dex/ && cargo build-bpf && cd ../../../
|
||||
|
||||
localnet: build
|
||||
./scripts/localnet.sh
|
|
@ -0,0 +1,103 @@
|
|||
# Permissioned Markets
|
||||
|
||||
This repo demonstrates how to create "permissioned markets" on Serum via a proxy smart contract.
|
||||
A permissioned market is a regular Serum market with an additional
|
||||
open orders authority, which must sign every transaction to create an
|
||||
open orders account.
|
||||
|
||||
In practice, what this means is that one can create a program that acts
|
||||
as this authority *and* that marks its own PDAs as the *owner* of all
|
||||
created open orders accounts, making the program the sole arbiter over
|
||||
who can trade on a given market.
|
||||
|
||||
For example, this program forces all trades that execute on this market
|
||||
to set the referral to a hardcoded address--`referral::ID`--and requires
|
||||
the client to pass in an identity token, authorizing the user.
|
||||
|
||||
See the [code](https://github.com/project-serum/permissioned-markets-quickstart/blob/master/programs/permissioned-markets/src/lib.rs).
|
||||
|
||||
## Developing
|
||||
|
||||
This program requires building the Serum DEX from source, which is done using
|
||||
git submodules.
|
||||
|
||||
### Install Submodules
|
||||
|
||||
Pull the source
|
||||
|
||||
```
|
||||
git submodule init
|
||||
git submodule update
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
[Anchor](https://github.com/project-serum/anchor) is used for developoment, and it's
|
||||
recommended workflow is used here. To get started, see the [guide](https://project-serum.github.io/anchor/getting-started/introduction.html).
|
||||
|
||||
Verify the installation with `anchor -h` and build the dex.
|
||||
|
||||
```
|
||||
make build-dex
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
A set of integration tests are provided. See these for an example of how to use a
|
||||
permissioned market from JavaScript.
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
### Localnet
|
||||
|
||||
To start a localnetwork with both the dex and proxy deployed and an orderbook
|
||||
listed with posted orders, first install the "crank" cli.
|
||||
|
||||
```
|
||||
cargo install --git https://github.com/project-serum/serum-dex --branch armani/auth crank --locked
|
||||
```
|
||||
|
||||
Then run,
|
||||
|
||||
```bash
|
||||
make localnet
|
||||
```
|
||||
|
||||
### Connect a GUI
|
||||
|
||||
To connect a GUI to the localnet, either run one locally or go to
|
||||
dex.projectserum.com and select the *localnet* network and enter the
|
||||
market address: `FcZntrVjDRPv8JnU2mHt8ejvvA1eiHqxM8d8JNEC8q9q`.
|
||||
|
||||
In short, go to this [link](https://dex.projectserum.com/#/market/FcZntrVjDRPv8JnU2mHt8ejvvA1eiHqxM8d8JNEC8q9q).
|
||||
|
||||
Don't forget to click the `+` button to "Add a custom market" so that the GUI
|
||||
can recognize the market running locally.
|
||||
|
||||
## Extending the Proxy
|
||||
|
||||
To implement a custom proxy, one can implement the [MarketMiddleware](https://github.com/project-serum/permissioned-markets-quickstart/blob/master/programs/permissioned-markets/src/lib.rs#L71) trait
|
||||
to intercept, modify, and perform any access control on DEX requests before
|
||||
they get forwarded to the orderbook. These middleware can be mixed and
|
||||
matched. Note, however, that the order of middleware matters since they can
|
||||
mutate the request.
|
||||
|
||||
One useful pattern is to treat the request like layers of an onion, where
|
||||
each middleware unwraps the request by stripping accounts and instruction
|
||||
data before relaying it to the next middleware and ultimately to the
|
||||
orderbook. This allows one to easily extend the behavior of a proxy by
|
||||
adding a custom middleware that may process information that is unknown to
|
||||
any other middleware or to the DEX.
|
||||
|
||||
After adding a middleware, the only additional requirement, of course, is
|
||||
to make sure the client sending transactions does the same, but in reverse.
|
||||
It should wrap the transaction in the opposite order. For convenience, an
|
||||
identical abstraction is provided in the JavaScript [client](https://github.com/project-serum/permissioned-markets-quickstart/blob/master/tests/utils/market-proxy.js#L15).
|
||||
|
||||
## Alternatives to Middleware
|
||||
|
||||
Note that this middleware abstraction is not required to host a
|
||||
permissioned market. One could write a regular program that manages the PDAs
|
||||
and CPI invocations onesself.
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 86a413a0f99eb7b2195f797e60183878d82e7fed
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
// Migrations are an early feature. Currently, they're nothing more than this
|
||||
// single deploy script that's invoked from the CLI, injecting a provider
|
||||
// configured from the workspace's Anchor.toml.
|
||||
|
||||
const anchor = require("@project-serum/anchor");
|
||||
|
||||
module.exports = async function (provider) {
|
||||
// Configure client to use the provider.
|
||||
anchor.setProvider(provider);
|
||||
|
||||
// Add your deploy script here.
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "permissioned-markets"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "permissioned_markets"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { git = "https://github.com/project-serum/anchor" }
|
||||
anchor-spl = { git = "https://github.com/project-serum/anchor" }
|
||||
solana-program = "1.7.4"
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,155 @@
|
|||
// Note. This example depends on unreleased Serum DEX changes.
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::dex::serum_dex::instruction::{CancelOrderInstructionV2, NewOrderInstructionV3};
|
||||
use anchor_spl::dex::{
|
||||
Context, Logger, MarketMiddleware, MarketProxy, OpenOrdersPda, ReferralFees,
|
||||
};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use solana_program::sysvar::rent;
|
||||
|
||||
/// # Permissioned Markets
|
||||
///
|
||||
/// This demonstrates how to create "permissioned markets" on Serum via a proxy.
|
||||
/// A permissioned market is a regular Serum market with an additional
|
||||
/// open orders authority, which must sign every transaction to create an open
|
||||
/// orders account.
|
||||
///
|
||||
/// In practice, what this means is that one can create a program that acts
|
||||
/// as this authority *and* that marks its own PDAs as the *owner* of all
|
||||
/// created open orders accounts, making the program the sole arbiter over
|
||||
/// who can trade on a given market.
|
||||
///
|
||||
/// For example, this example forces all trades that execute on this market
|
||||
/// to set the referral to a hardcoded address--`referral::ID`--and requires
|
||||
/// the client to pass in an identity token, authorizing the user.
|
||||
///
|
||||
/// # Extending the proxy via middleware
|
||||
///
|
||||
/// To implement a custom proxy, one can implement the `MarketMiddleware` trait
|
||||
/// to intercept, modify, and perform any access control on DEX requests before
|
||||
/// they get forwarded to the orderbook. These middleware can be mixed and
|
||||
/// matched. Note, however, that the order of middleware matters since they can
|
||||
/// mutate the request.
|
||||
///
|
||||
/// One useful pattern is to treat the request like layers of an onion, where
|
||||
/// each middleware unwraps the request by stripping accounts and instruction
|
||||
/// data before relaying it to the next middleware and ultimately to the
|
||||
/// orderbook. This allows one to easily extend the behavior of a proxy by
|
||||
/// adding a custom middleware that may process information that is unknown to
|
||||
/// any other middleware or to the DEX.
|
||||
///
|
||||
/// After adding a middleware, the only additional requirement, of course, is
|
||||
/// to make sure the client sending transactions does the same, but in reverse.
|
||||
/// It should wrap the transaction in the opposite order. For convenience, an
|
||||
/// identical abstraction is provided in the JavaScript client.
|
||||
///
|
||||
/// # Alternatives to middleware
|
||||
///
|
||||
/// Note that this middleware abstraction is not required to host a
|
||||
/// permissioned market. One could write a regular program that manages the PDAs
|
||||
/// and CPI invocations onesself, if desired.
|
||||
#[program]
|
||||
pub mod permissioned_markets {
|
||||
use super::*;
|
||||
pub fn entry(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
|
||||
MarketProxy::new()
|
||||
.middleware(&mut Logger)
|
||||
.middleware(&mut Identity)
|
||||
.middleware(&mut ReferralFees::new(referral::ID))
|
||||
.middleware(&mut OpenOrdersPda::new())
|
||||
.run(program_id, accounts, data)
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs token based authorization, confirming the identity of the user.
|
||||
/// The identity token must be given as the fist account.
|
||||
struct Identity;
|
||||
|
||||
impl MarketMiddleware for Identity {
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn init_open_orders(&self, ctx: &mut Context) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn new_order_v3(&self, ctx: &mut Context, _ix: &NewOrderInstructionV3) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn cancel_order_v2(&self, ctx: &mut Context, _ix: &CancelOrderInstructionV2) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn cancel_order_by_client_id_v2(&self, ctx: &mut Context, _client_id: u64) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn settle_funds(&self, ctx: &mut Context) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn close_open_orders(&self, ctx: &mut Context) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
/// ..
|
||||
fn fallback(&self, ctx: &mut Context) -> ProgramResult {
|
||||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// Utils.
|
||||
|
||||
fn verify_and_strip_auth(ctx: &mut Context) -> ProgramResult {
|
||||
// The rent sysvar is used as a dummy example of an identity token.
|
||||
let auth = &ctx.accounts[0];
|
||||
require!(auth.key == &rent::ID, InvalidAuth);
|
||||
|
||||
// Strip off the account before possing on the message.
|
||||
ctx.accounts = (&ctx.accounts[1..]).to_vec();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Error.
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("Invalid auth token provided")]
|
||||
InvalidAuth,
|
||||
}
|
||||
|
||||
// Constants.
|
||||
|
||||
pub mod referral {
|
||||
// This is a dummy address for testing. Do not use in production.
|
||||
solana_program::declare_id!("3oSfkjQZKCneYvsCTZc9HViGAPqR8pYr4h9YeGB5ZxHf");
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to list a permissioned market, logging the address to stdout.
|
||||
|
||||
const utils = require("../tests/utils");
|
||||
const utilsCmn = require("../tests/utils/common");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const provider = anchor.Provider.local();
|
||||
const program = anchor.workspace.PermissionedMarkets;
|
||||
const Account = anchor.web3.Account;
|
||||
|
||||
async function main() {
|
||||
const { marketProxyClient } = await utils.genesis({
|
||||
provider,
|
||||
proxyProgramId: utilsCmn.PROGRAM_KP.publicKey,
|
||||
});
|
||||
const out = {
|
||||
market: marketProxyClient.market.address.toString(),
|
||||
};
|
||||
console.log(JSON.stringify(out));
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,110 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
################################################################################
|
||||
#
|
||||
# localnet.sh runs a localnet with a permissioned market up and running.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# ./localnet.sh
|
||||
#
|
||||
# Installation:
|
||||
#
|
||||
# Before using, one must make sure to have the serum crank software built or
|
||||
# installed locally on one's machine. To build, clone the serum dex and run
|
||||
# `cargo build` inside `serum-dex/dex/crank`. Then change the $CRANK variable
|
||||
# below.
|
||||
#
|
||||
################################################################################
|
||||
|
||||
DEX_PID="9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin"
|
||||
PAYER_FILEPATH="$HOME/.config/solana/id.json"
|
||||
|
||||
#
|
||||
# If the crank cli isn't installed, use a local path.
|
||||
# Replace the path below if you'd like to use your own build.
|
||||
#
|
||||
CRANK=$(which crank)
|
||||
if [ $? -ne 0 ]; then
|
||||
CRANK="/home/armaniferrante/Documents/code/src/github.com/project-serum/permissioned-markets-quickstart/deps/serum-dex/target/debug/crank"
|
||||
fi
|
||||
|
||||
LOG_DIR="./localnet-logs"
|
||||
VALIDATOR_OUT="${LOG_DIR}/validator-stdout.txt"
|
||||
CRANK_LOGS="${LOG_DIR}/crank-logs.txt"
|
||||
CRANK_STDOUT="${LOG_DIR}/crank-stdout.txt"
|
||||
TRADE_BOT_STDOUT="${LOG_DIR}/trade-bot-stdout.txt"
|
||||
|
||||
echo "CRANK: $CRANK"
|
||||
set -euo pipefail
|
||||
|
||||
main () {
|
||||
#
|
||||
# Cleanup.
|
||||
#
|
||||
echo "Cleaning old output files..."
|
||||
mkdir -p $LOG_DIR
|
||||
rm -rf test-ledger
|
||||
rm -f $TRADE_BOT_STDOUT
|
||||
rm -f $VALIDATOR_OUT
|
||||
rm -f $CRANK_LOGS && touch $CRANK_LOGS
|
||||
|
||||
#
|
||||
# Bootup cluster.
|
||||
#
|
||||
echo "Starting local network..."
|
||||
solana-test-validator \
|
||||
--bpf-program 9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin ./deps/serum-dex/dex/target/deploy/serum_dex.so \
|
||||
--bpf-program CGnjwsdrQfJpiXqeAe1p13pUsfZnS9rgEy1DWKimGHqo ./target/deploy/permissioned_markets.so > $VALIDATOR_OUT &
|
||||
sleep 2
|
||||
|
||||
#
|
||||
# List market at a predetermined address.
|
||||
#
|
||||
local market="FcZntrVjDRPv8JnU2mHt8ejvvA1eiHqxM8d8JNEC8q9q"
|
||||
echo "Listing market $market..."
|
||||
./scripts/list-market.js
|
||||
sleep 10
|
||||
echo "Market listed $market"
|
||||
|
||||
echo "Run the trade bot"
|
||||
./scripts/trade-bot.js $market > $TRADE_BOT_STDOUT &
|
||||
|
||||
echo "Running crank..."
|
||||
$CRANK localnet consume-events \
|
||||
-c $market \
|
||||
-d $DEX_PID -e 5 \
|
||||
--log-directory $CRANK_LOGS \
|
||||
--market $market \
|
||||
--num-workers 1 \
|
||||
--payer $PAYER_FILEPATH \
|
||||
--pc-wallet $market > $CRANK_STDOUT &
|
||||
|
||||
#
|
||||
# Park.
|
||||
#
|
||||
echo "Localnet running..."
|
||||
echo "Ctl-c to exit."
|
||||
wait
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
pkill -P $$ || true
|
||||
wait || true
|
||||
}
|
||||
|
||||
trap_add() {
|
||||
trap_add_cmd=$1; shift || fatal "${FUNCNAME} usage error"
|
||||
for trap_add_name in "$@"; do
|
||||
trap -- "$(
|
||||
extract_trap_cmd() { printf '%s\n' "${3:-}"; }
|
||||
eval "extract_trap_cmd $(trap -p "${trap_add_name}")"
|
||||
printf '%s\n' "${trap_add_cmd}"
|
||||
)" "${trap_add_name}" \
|
||||
|| fatal "unable to add to trap ${trap_add_name}"
|
||||
done
|
||||
}
|
||||
|
||||
declare -f -t trap_add
|
||||
trap_add 'cleanup' EXIT
|
||||
main
|
|
@ -0,0 +1,180 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Script to infinitely post orders that are immediately filled.
|
||||
|
||||
const process = require("process");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const PublicKey = anchor.web3.PublicKey;
|
||||
const marketMaker = require("../tests/utils/market-maker");
|
||||
|
||||
const MARKET_MAKER = marketMaker.KEYPAIR;
|
||||
|
||||
async function main() {
|
||||
const market = new PublicKey(process.argv[2]);
|
||||
const provider = anchor.Provider.local();
|
||||
// TODO: enable the trade bot.
|
||||
// runTradeBot(market, provider);
|
||||
}
|
||||
|
||||
async function runTradeBot(market, provider, iterations = undefined) {
|
||||
const marketProxyClient = marketProxy.load(
|
||||
provider.connection,
|
||||
proxyProgramId,
|
||||
DEX_PID,
|
||||
market
|
||||
);
|
||||
const baseTokenUser1 = (
|
||||
await marketProxyClient.market.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
MARKET_MAKER.publicKey,
|
||||
marketProxyClient.market.baseMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
const quoteTokenUser1 = (
|
||||
await marketProxyClient.market.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
MARKET_MAKER.publicKey,
|
||||
marketProxyClient.market.quoteMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
|
||||
const baseTokenUser2 = (
|
||||
await marketProxyClient.market.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
marketProxyClient.market.baseMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
const quoteTokenUser2 = (
|
||||
await marketProxyClient.market.getTokenAccountsByOwnerForMint(
|
||||
provider.connection,
|
||||
provider.wallet.publicKey,
|
||||
marketProxyClient.market.quoteMintAddress
|
||||
)
|
||||
)[0].pubkey;
|
||||
|
||||
const makerOpenOrdersUser1 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
MARKET_MAKER.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
makerOpenOrdersUser2 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
|
||||
const price = 6.041;
|
||||
const size = 700000.8;
|
||||
|
||||
let maker = MARKET_MAKER;
|
||||
let taker = provider.wallet.payer;
|
||||
let baseToken = baseTokenUser1;
|
||||
let quoteToken = quoteTokenUser2;
|
||||
let makerOpenOrders = makerOpenOrdersUser1;
|
||||
|
||||
let k = 1;
|
||||
|
||||
while (true) {
|
||||
if (iterations && k > iterations) {
|
||||
break;
|
||||
}
|
||||
const clientId = new BN(k);
|
||||
if (k % 5 === 0) {
|
||||
if (maker.publicKey.equals(MARKET_MAKER.publicKey)) {
|
||||
maker = provider.wallet.payer;
|
||||
makerOpenOrders = makerOpenOrdersUser2;
|
||||
taker = MARKET_MAKER;
|
||||
baseToken = baseTokenUser2;
|
||||
quoteToken = quoteTokenUser1;
|
||||
} else {
|
||||
maker = MARKET_MAKER;
|
||||
makerOpenOrders = makerOpenOrdersUser1;
|
||||
taker = provider.wallet.payer;
|
||||
baseToken = baseTokenUser1;
|
||||
quoteToken = quoteTokenUser2;
|
||||
}
|
||||
}
|
||||
|
||||
// Post ask.
|
||||
const txAsk = new Transaction();
|
||||
txAsk.add(
|
||||
await marketProxyClient.instruction.newOrderV3({
|
||||
owner: maker,
|
||||
payer: baseToken,
|
||||
side: "sell",
|
||||
price,
|
||||
size,
|
||||
orderType: "postOnly",
|
||||
clientId,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
let txSig = await provider.send(txAsk, [maker]);
|
||||
console.log("Ask", txSig);
|
||||
|
||||
// Take.
|
||||
const txBid = new Transaction();
|
||||
tx.add(
|
||||
await marketProxyClient.instruction.newOrderV3({
|
||||
owner: taker,
|
||||
payer: quoteToken,
|
||||
side: "buy",
|
||||
price,
|
||||
size,
|
||||
orderType: "ioc",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey: undefined,
|
||||
openOrdersAccount: undefined,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
txSig = await provider.send(txBid, [taker]);
|
||||
console.log("Bid", txSig);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// Cancel anything remaining.
|
||||
try {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxyClient.instruction.cancelOrderByClientId(
|
||||
provider.connection,
|
||||
maker.publicKey,
|
||||
makerOpenOrders.address,
|
||||
clientId
|
||||
)
|
||||
);
|
||||
txSig = await provider.send(tx, [maker]);
|
||||
console.log("Cancelled the rest", txSig);
|
||||
await sleep(1000);
|
||||
} catch (e) {
|
||||
console.log("Unable to cancel order", e);
|
||||
}
|
||||
k += 1;
|
||||
|
||||
// If the open orders account wasn't previously initialized, it is now.
|
||||
if (makerOpenOrdersUser2 === undefined) {
|
||||
makerOpenOrdersUser2 = (
|
||||
await OpenOrders.findForMarketAndOwner(
|
||||
provider.connection,
|
||||
market,
|
||||
provider.wallet.publicKey,
|
||||
DEX_PID
|
||||
)
|
||||
)[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,234 @@
|
|||
const assert = require("assert");
|
||||
const { Token, TOKEN_PROGRAM_ID } = require("@solana/spl-token");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const serum = require("@project-serum/serum");
|
||||
const { BN } = anchor;
|
||||
const {
|
||||
Keypair,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
} = anchor.web3;
|
||||
const {
|
||||
DexInstructions,
|
||||
OpenOrders,
|
||||
OpenOrdersPda,
|
||||
Logger,
|
||||
ReferralFees,
|
||||
MarketProxyBuilder,
|
||||
} = serum;
|
||||
const { genesis, sleep } = require("./utils");
|
||||
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
const REFERRAL_AUTHORITY = new PublicKey(
|
||||
"3oSfkjQZKCneYvsCTZc9HViGAPqR8pYr4h9YeGB5ZxHf"
|
||||
);
|
||||
|
||||
describe("permissioned-markets", () => {
|
||||
// Anchor client setup.
|
||||
const provider = anchor.Provider.env();
|
||||
anchor.setProvider(provider);
|
||||
const program = anchor.workspace.PermissionedMarkets;
|
||||
|
||||
// Token client.
|
||||
let usdcClient;
|
||||
|
||||
// Global DEX accounts and clients shared accross all tests.
|
||||
let marketProxy, tokenAccount, usdcAccount;
|
||||
let openOrders, openOrdersBump, openOrdersInitAuthority, openOrdersBumpinit;
|
||||
let usdcPosted;
|
||||
let referralTokenAddress;
|
||||
|
||||
it("BOILERPLATE: Initializes an orderbook", async () => {
|
||||
const { marketProxyClient, godA, godUsdc, usdc } = await genesis({
|
||||
provider,
|
||||
proxyProgramId: program.programId,
|
||||
});
|
||||
marketProxy = marketProxyClient;
|
||||
usdcAccount = godUsdc;
|
||||
tokenAccount = godA;
|
||||
|
||||
usdcClient = new Token(
|
||||
provider.connection,
|
||||
usdc,
|
||||
TOKEN_PROGRAM_ID,
|
||||
provider.wallet.payer
|
||||
);
|
||||
|
||||
referral = await usdcClient.createAccount(REFERRAL_AUTHORITY);
|
||||
});
|
||||
|
||||
it("BOILERPLATE: Calculates open orders addresses", async () => {
|
||||
const [_openOrders, bump] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
anchor.utils.bytes.utf8.encode("open-orders"),
|
||||
DEX_PID.toBuffer(),
|
||||
marketProxy.market.address.toBuffer(),
|
||||
program.provider.wallet.publicKey.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
const [
|
||||
_openOrdersInitAuthority,
|
||||
bumpInit,
|
||||
] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
anchor.utils.bytes.utf8.encode("open-orders-init"),
|
||||
DEX_PID.toBuffer(),
|
||||
marketProxy.market.address.toBuffer(),
|
||||
],
|
||||
program.programId
|
||||
);
|
||||
|
||||
// Save global variables re-used across tests.
|
||||
openOrders = _openOrders;
|
||||
openOrdersBump = bump;
|
||||
openOrdersInitAuthority = _openOrdersInitAuthority;
|
||||
openOrdersBumpInit = bumpInit;
|
||||
});
|
||||
|
||||
it("Creates an open orders account", async () => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.initOpenOrders(
|
||||
program.provider.wallet.publicKey,
|
||||
marketProxy.market.address,
|
||||
marketProxy.market.address, // Dummy. Replaced by middleware.
|
||||
marketProxy.market.address // Dummy. Replaced by middleware.
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
const account = await provider.connection.getAccountInfo(openOrders);
|
||||
assert.ok(account.owner.toString() === DEX_PID.toString());
|
||||
});
|
||||
|
||||
it("Posts a bid on the orderbook", async () => {
|
||||
const size = 1;
|
||||
const price = 1;
|
||||
usdcPosted = new BN(
|
||||
marketProxy.market._decoded.quoteLotSize.toNumber()
|
||||
).mul(
|
||||
marketProxy.market
|
||||
.baseSizeNumberToLots(size)
|
||||
.mul(marketProxy.market.priceNumberToLots(price))
|
||||
);
|
||||
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.newOrderV3({
|
||||
owner: program.provider.wallet.publicKey,
|
||||
payer: usdcAccount,
|
||||
side: "buy",
|
||||
price,
|
||||
size,
|
||||
orderType: "postOnly",
|
||||
clientId: new BN(999),
|
||||
openOrdersAddressKey: openOrders,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
await provider.send(tx);
|
||||
});
|
||||
|
||||
it("Cancels a bid on the orderbook", async () => {
|
||||
// Given.
|
||||
const beforeOoAccount = await OpenOrders.load(
|
||||
provider.connection,
|
||||
openOrders,
|
||||
DEX_PID
|
||||
);
|
||||
|
||||
// When.
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.cancelOrderByClientId(
|
||||
program.provider.wallet.publicKey,
|
||||
openOrders,
|
||||
new BN(999)
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
// Then.
|
||||
const afterOoAccount = await OpenOrders.load(
|
||||
provider.connection,
|
||||
openOrders,
|
||||
DEX_PID
|
||||
);
|
||||
assert.ok(beforeOoAccount.quoteTokenFree.eq(new BN(0)));
|
||||
assert.ok(beforeOoAccount.quoteTokenTotal.eq(usdcPosted));
|
||||
assert.ok(afterOoAccount.quoteTokenFree.eq(usdcPosted));
|
||||
assert.ok(afterOoAccount.quoteTokenTotal.eq(usdcPosted));
|
||||
});
|
||||
|
||||
// Need to crank the cancel so that we can close later.
|
||||
it("Cranks the cancel transaction", async () => {
|
||||
// TODO: can do this in a single transaction if we covert the pubkey bytes
|
||||
// into a [u64; 4] array and sort. I'm lazy though.
|
||||
let eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
while (eq.length > 0) {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.market.makeConsumeEventsInstruction([eq[0].openOrders], 1)
|
||||
);
|
||||
await provider.send(tx);
|
||||
eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
}
|
||||
});
|
||||
|
||||
it("Settles funds on the orderbook", async () => {
|
||||
// Given.
|
||||
const beforeTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
|
||||
|
||||
// When.
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.settleFunds(
|
||||
openOrders,
|
||||
provider.wallet.publicKey,
|
||||
tokenAccount,
|
||||
usdcAccount,
|
||||
referral
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
// Then.
|
||||
const afterTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
|
||||
assert.ok(
|
||||
afterTokenAccount.amount.sub(beforeTokenAccount.amount).toNumber() ===
|
||||
usdcPosted.toNumber()
|
||||
);
|
||||
});
|
||||
|
||||
it("Closes an open orders account", async () => {
|
||||
// Given.
|
||||
const beforeAccount = await program.provider.connection.getAccountInfo(
|
||||
program.provider.wallet.publicKey
|
||||
);
|
||||
|
||||
// When.
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.closeOpenOrders(
|
||||
openOrders,
|
||||
provider.wallet.publicKey,
|
||||
provider.wallet.publicKey
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
// Then.
|
||||
const afterAccount = await program.provider.connection.getAccountInfo(
|
||||
program.provider.wallet.publicKey
|
||||
);
|
||||
const closedAccount = await program.provider.connection.getAccountInfo(
|
||||
openOrders
|
||||
);
|
||||
assert.ok(23352768 === afterAccount.lamports - beforeAccount.lamports);
|
||||
assert.ok(closedAccount === null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,81 @@
|
|||
const { PublicKey, Account } = require("@project-serum/anchor").web3;
|
||||
|
||||
const DEX_PID = new PublicKey("9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin");
|
||||
|
||||
// This msut be kept in sync with `scripts/localnet.sh`.
|
||||
const PROGRAM_KP = new Account([
|
||||
168,
|
||||
86,
|
||||
206,
|
||||
125,
|
||||
127,
|
||||
105,
|
||||
201,
|
||||
250,
|
||||
37,
|
||||
102,
|
||||
161,
|
||||
124,
|
||||
80,
|
||||
181,
|
||||
60,
|
||||
2,
|
||||
166,
|
||||
123,
|
||||
176,
|
||||
161,
|
||||
228,
|
||||
188,
|
||||
134,
|
||||
186,
|
||||
158,
|
||||
68,
|
||||
197,
|
||||
240,
|
||||
202,
|
||||
193,
|
||||
174,
|
||||
234,
|
||||
167,
|
||||
123,
|
||||
252,
|
||||
186,
|
||||
72,
|
||||
51,
|
||||
203,
|
||||
70,
|
||||
153,
|
||||
234,
|
||||
190,
|
||||
2,
|
||||
134,
|
||||
184,
|
||||
197,
|
||||
156,
|
||||
113,
|
||||
8,
|
||||
65,
|
||||
1,
|
||||
83,
|
||||
220,
|
||||
152,
|
||||
62,
|
||||
200,
|
||||
174,
|
||||
40,
|
||||
180,
|
||||
218,
|
||||
61,
|
||||
224,
|
||||
6,
|
||||
]);
|
||||
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep,
|
||||
DEX_PID,
|
||||
PROGRAM_KP,
|
||||
};
|
|
@ -0,0 +1,97 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const BN = anchor.BN;
|
||||
const { Account, Transaction, SystemProgram } = anchor.web3;
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const { TOKEN_PROGRAM_ID, Token } = require("@solana/spl-token");
|
||||
|
||||
const DECIMALS = 6;
|
||||
|
||||
// Creates mints and a token account funded with each mint.
|
||||
async function createMintGods(provider, mintCount) {
|
||||
// Setup mints with initial tokens owned by the provider.
|
||||
|
||||
let mintGods = [];
|
||||
for (let k = 0; k < mintCount; k += 1) {
|
||||
const [mint, god] = await serumCmn.createMintAndVault(
|
||||
provider,
|
||||
new BN("1000000000000000000"),
|
||||
undefined,
|
||||
DECIMALS
|
||||
);
|
||||
mintGods.push({ mint, god });
|
||||
}
|
||||
|
||||
return mintGods;
|
||||
}
|
||||
|
||||
async function createFundedAccount(provider, mints, newAccount) {
|
||||
if (!newAccount) {
|
||||
newAccount = new Account();
|
||||
}
|
||||
|
||||
const marketMaker = {
|
||||
tokens: {},
|
||||
account: newAccount,
|
||||
};
|
||||
|
||||
// Transfer lamports to market maker.
|
||||
await provider.send(
|
||||
(() => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
SystemProgram.transfer({
|
||||
fromPubkey: provider.wallet.publicKey,
|
||||
toPubkey: newAccount.publicKey,
|
||||
lamports: 100000000000,
|
||||
})
|
||||
);
|
||||
return tx;
|
||||
})()
|
||||
);
|
||||
|
||||
// Transfer SPL tokens to the market maker.
|
||||
for (let k = 0; k < mints.length; k += 1) {
|
||||
const { mint, god, amount } = mints[k];
|
||||
let MINT_A = mint;
|
||||
let GOD_A = god;
|
||||
// Setup token accounts owned by the market maker.
|
||||
const mintAClient = new Token(
|
||||
provider.connection,
|
||||
MINT_A,
|
||||
TOKEN_PROGRAM_ID,
|
||||
provider.wallet.payer // node only
|
||||
);
|
||||
const marketMakerTokenA = await mintAClient.createAccount(
|
||||
newAccount.publicKey
|
||||
);
|
||||
|
||||
await provider.send(
|
||||
(() => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
Token.createTransferCheckedInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
GOD_A,
|
||||
MINT_A,
|
||||
marketMakerTokenA,
|
||||
provider.wallet.publicKey,
|
||||
[],
|
||||
amount,
|
||||
DECIMALS
|
||||
)
|
||||
);
|
||||
return tx;
|
||||
})()
|
||||
);
|
||||
|
||||
marketMaker.tokens[mint.toString()] = marketMakerTokenA;
|
||||
}
|
||||
|
||||
return marketMaker;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createMintGods,
|
||||
createFundedAccount,
|
||||
DECIMALS,
|
||||
};
|
|
@ -0,0 +1,98 @@
|
|||
const { BN } = require("@project-serum/anchor");
|
||||
const { PublicKey } = require("@project-serum/anchor").web3;
|
||||
const marketProxy = require("./market-proxy");
|
||||
const marketLister = require("./market-lister");
|
||||
const faucet = require("./faucet");
|
||||
const { DEX_PID } = require("./common");
|
||||
const marketMaker = require("./market-maker");
|
||||
|
||||
// Initializes the genesis state for the tests and localnetwork.
|
||||
async function genesis({ provider, proxyProgramId }) {
|
||||
//
|
||||
// Create all mints and funded god accounts.
|
||||
//
|
||||
const mintGods = await faucet.createMintGods(provider, 2);
|
||||
const [mintGodA, mintGodB] = mintGods;
|
||||
|
||||
//
|
||||
// Fund an additional account.
|
||||
//
|
||||
const fundedAccount = await faucet.createFundedAccount(
|
||||
provider,
|
||||
mintGods.map((mintGod) => {
|
||||
return {
|
||||
...mintGod,
|
||||
amount: new BN("10000000000000").muln(10 ** faucet.DECIMALS),
|
||||
};
|
||||
}),
|
||||
marketMaker.KEYPAIR
|
||||
);
|
||||
|
||||
//
|
||||
// Structure the market maker object.
|
||||
//
|
||||
const marketMakerAccounts = {
|
||||
...fundedAccount,
|
||||
baseToken: fundedAccount.tokens[mintGodA.mint.toString()],
|
||||
quoteToken: fundedAccount.tokens[mintGodB.mint.toString()],
|
||||
};
|
||||
|
||||
//
|
||||
// List the market.
|
||||
//
|
||||
const [marketAPublicKey] = await marketLister.list({
|
||||
connection: provider.connection,
|
||||
wallet: provider.wallet,
|
||||
baseMint: mintGodA.mint,
|
||||
quoteMint: mintGodB.mint,
|
||||
baseLotSize: 100000,
|
||||
quoteLotSize: 100,
|
||||
dexProgramId: DEX_PID,
|
||||
proxyProgramId,
|
||||
feeRateBps: 0,
|
||||
});
|
||||
|
||||
//
|
||||
// Load a proxy client for the market.
|
||||
//
|
||||
const marketProxyClient = await marketProxy.load(
|
||||
provider.connection,
|
||||
proxyProgramId,
|
||||
DEX_PID,
|
||||
marketAPublicKey
|
||||
);
|
||||
|
||||
//
|
||||
// Market maker initializes an open orders account.
|
||||
//
|
||||
await marketMaker.initOpenOrders(
|
||||
provider,
|
||||
marketProxyClient,
|
||||
marketMakerAccounts
|
||||
);
|
||||
|
||||
//
|
||||
// Market maker posts trades on the orderbook.
|
||||
//
|
||||
await marketMaker.postOrders(
|
||||
provider,
|
||||
marketProxyClient,
|
||||
marketMakerAccounts
|
||||
);
|
||||
|
||||
//
|
||||
// Done.
|
||||
//
|
||||
return {
|
||||
marketProxyClient,
|
||||
mintA: mintGodA.mint,
|
||||
usdc: mintGodB.mint,
|
||||
godA: mintGodA.god,
|
||||
godUsdc: mintGodB.god,
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
genesis,
|
||||
DEX_PID,
|
||||
};
|
|
@ -0,0 +1,237 @@
|
|||
const anchor = require("@project-serum/anchor");
|
||||
const { BN } = anchor;
|
||||
const {
|
||||
Account,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
SystemProgram,
|
||||
} = require("@project-serum/anchor").web3;
|
||||
const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
|
||||
const {
|
||||
DexInstructions,
|
||||
TokenInstructions,
|
||||
OpenOrdersPda,
|
||||
MARKET_STATE_LAYOUT_V3,
|
||||
} = require("@project-serum/serum");
|
||||
const { DEX_PID } = require("./common");
|
||||
|
||||
// Creates a market on the dex.
|
||||
async function list({
|
||||
connection,
|
||||
wallet,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
baseLotSize,
|
||||
quoteLotSize,
|
||||
dexProgramId,
|
||||
proxyProgramId,
|
||||
feeRateBps,
|
||||
}) {
|
||||
const market = MARKET_KP;
|
||||
const requestQueue = new Account();
|
||||
const eventQueue = new Account();
|
||||
const bids = new Account();
|
||||
const asks = new Account();
|
||||
const baseVault = new Account();
|
||||
const quoteVault = new Account();
|
||||
const quoteDustThreshold = new BN(100);
|
||||
|
||||
const [vaultOwner, vaultSignerNonce] = await getVaultOwnerAndNonce(
|
||||
market.publicKey,
|
||||
dexProgramId
|
||||
);
|
||||
|
||||
const tx1 = new Transaction();
|
||||
tx1.add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: baseVault.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(165),
|
||||
space: 165,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: quoteVault.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(165),
|
||||
space: 165,
|
||||
programId: TOKEN_PROGRAM_ID,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: baseVault.publicKey,
|
||||
mint: baseMint,
|
||||
owner: vaultOwner,
|
||||
}),
|
||||
TokenInstructions.initializeAccount({
|
||||
account: quoteVault.publicKey,
|
||||
mint: quoteMint,
|
||||
owner: vaultOwner,
|
||||
})
|
||||
);
|
||||
|
||||
const tx2 = new Transaction();
|
||||
tx2.add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: market.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(
|
||||
MARKET_STATE_LAYOUT_V3.span
|
||||
),
|
||||
space: MARKET_STATE_LAYOUT_V3.span,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: requestQueue.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(5120 + 12),
|
||||
space: 5120 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: eventQueue.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(262144 + 12),
|
||||
space: 262144 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: bids.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
|
||||
space: 65536 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: wallet.publicKey,
|
||||
newAccountPubkey: asks.publicKey,
|
||||
lamports: await connection.getMinimumBalanceForRentExemption(65536 + 12),
|
||||
space: 65536 + 12,
|
||||
programId: dexProgramId,
|
||||
}),
|
||||
DexInstructions.initializeMarket({
|
||||
market: market.publicKey,
|
||||
requestQueue: requestQueue.publicKey,
|
||||
eventQueue: eventQueue.publicKey,
|
||||
bids: bids.publicKey,
|
||||
asks: asks.publicKey,
|
||||
baseVault: baseVault.publicKey,
|
||||
quoteVault: quoteVault.publicKey,
|
||||
baseMint,
|
||||
quoteMint,
|
||||
baseLotSize: new BN(baseLotSize),
|
||||
quoteLotSize: new BN(quoteLotSize),
|
||||
feeRateBps,
|
||||
vaultSignerNonce,
|
||||
quoteDustThreshold,
|
||||
programId: dexProgramId,
|
||||
authority: await OpenOrdersPda.marketAuthority(
|
||||
market.publicKey,
|
||||
DEX_PID,
|
||||
proxyProgramId
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
const transactions = [
|
||||
{ transaction: tx1, signers: [baseVault, quoteVault] },
|
||||
{
|
||||
transaction: tx2,
|
||||
signers: [market, requestQueue, eventQueue, bids, asks],
|
||||
},
|
||||
];
|
||||
for (let tx of transactions) {
|
||||
await anchor.getProvider().send(tx.transaction, tx.signers);
|
||||
}
|
||||
const acc = await connection.getAccountInfo(market.publicKey);
|
||||
|
||||
return [market.publicKey, vaultOwner];
|
||||
}
|
||||
|
||||
async function getVaultOwnerAndNonce(marketPublicKey, dexProgramId = DEX_PID) {
|
||||
const nonce = new BN(0);
|
||||
while (nonce.toNumber() < 255) {
|
||||
try {
|
||||
const vaultOwner = await PublicKey.createProgramAddress(
|
||||
[marketPublicKey.toBuffer(), nonce.toArrayLike(Buffer, "le", 8)],
|
||||
dexProgramId
|
||||
);
|
||||
return [vaultOwner, nonce];
|
||||
} catch (e) {
|
||||
nonce.iaddn(1);
|
||||
}
|
||||
}
|
||||
throw new Error("Unable to find nonce");
|
||||
}
|
||||
|
||||
// Dummy keypair for a consistent market address. Helpful when doing UI work.
|
||||
// Don't use in production.
|
||||
const MARKET_KP = new Account([
|
||||
13,
|
||||
174,
|
||||
53,
|
||||
150,
|
||||
78,
|
||||
228,
|
||||
12,
|
||||
98,
|
||||
170,
|
||||
254,
|
||||
212,
|
||||
211,
|
||||
125,
|
||||
193,
|
||||
2,
|
||||
241,
|
||||
97,
|
||||
137,
|
||||
49,
|
||||
209,
|
||||
189,
|
||||
199,
|
||||
27,
|
||||
215,
|
||||
220,
|
||||
65,
|
||||
57,
|
||||
203,
|
||||
215,
|
||||
93,
|
||||
105,
|
||||
203,
|
||||
217,
|
||||
32,
|
||||
5,
|
||||
194,
|
||||
157,
|
||||
118,
|
||||
162,
|
||||
47,
|
||||
102,
|
||||
126,
|
||||
235,
|
||||
65,
|
||||
99,
|
||||
80,
|
||||
56,
|
||||
231,
|
||||
217,
|
||||
114,
|
||||
25,
|
||||
225,
|
||||
239,
|
||||
140,
|
||||
169,
|
||||
92,
|
||||
150,
|
||||
146,
|
||||
211,
|
||||
218,
|
||||
183,
|
||||
139,
|
||||
9,
|
||||
104,
|
||||
]);
|
||||
|
||||
module.exports = {
|
||||
list,
|
||||
};
|
|
@ -0,0 +1,160 @@
|
|||
const { Account, Transaction } = require("@project-serum/anchor").web3;
|
||||
const { OpenOrdersPda } = require("@project-serum/serum");
|
||||
|
||||
// Dummy keypair.
|
||||
const KEYPAIR = new Account([
|
||||
54,
|
||||
213,
|
||||
91,
|
||||
255,
|
||||
163,
|
||||
120,
|
||||
88,
|
||||
183,
|
||||
223,
|
||||
23,
|
||||
220,
|
||||
204,
|
||||
82,
|
||||
117,
|
||||
212,
|
||||
214,
|
||||
118,
|
||||
184,
|
||||
2,
|
||||
29,
|
||||
89,
|
||||
149,
|
||||
22,
|
||||
233,
|
||||
108,
|
||||
177,
|
||||
60,
|
||||
249,
|
||||
218,
|
||||
166,
|
||||
30,
|
||||
221,
|
||||
59,
|
||||
168,
|
||||
233,
|
||||
123,
|
||||
204,
|
||||
37,
|
||||
123,
|
||||
124,
|
||||
86,
|
||||
176,
|
||||
214,
|
||||
12,
|
||||
63,
|
||||
195,
|
||||
231,
|
||||
15,
|
||||
1,
|
||||
143,
|
||||
7,
|
||||
7,
|
||||
232,
|
||||
38,
|
||||
69,
|
||||
214,
|
||||
45,
|
||||
58,
|
||||
115,
|
||||
55,
|
||||
129,
|
||||
25,
|
||||
228,
|
||||
30,
|
||||
]);
|
||||
|
||||
async function initOpenOrders(provider, marketProxy, marketMakerAccounts) {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.initOpenOrders(
|
||||
marketMakerAccounts.account.publicKey,
|
||||
marketProxy.market.address,
|
||||
marketProxy.market.address, // Dummy. Replaced by middleware.
|
||||
marketProxy.market.address // Dummy. Replaced by middleware.
|
||||
)
|
||||
);
|
||||
let signers = [marketMakerAccounts.account];
|
||||
await provider.send(tx, signers);
|
||||
}
|
||||
|
||||
async function postOrders(provider, marketProxy, marketMakerAccounts) {
|
||||
const asks = [
|
||||
[6.041, 7.8],
|
||||
[6.051, 72.3],
|
||||
[6.055, 5.4],
|
||||
[6.067, 15.7],
|
||||
[6.077, 390.0],
|
||||
[6.09, 24.0],
|
||||
[6.11, 36.3],
|
||||
[6.133, 300.0],
|
||||
[6.167, 687.8],
|
||||
];
|
||||
const bids = [
|
||||
[6.004, 8.5],
|
||||
[5.995, 12.9],
|
||||
[5.987, 6.2],
|
||||
[5.978, 15.3],
|
||||
[5.965, 82.8],
|
||||
[5.961, 25.4],
|
||||
];
|
||||
const openOrdersAddressKey = await OpenOrdersPda.openOrdersAddress(
|
||||
marketProxy.market.address,
|
||||
marketMakerAccounts.account.publicKey,
|
||||
marketProxy.dexProgramId,
|
||||
marketProxy.proxyProgramId
|
||||
);
|
||||
// Use an explicit signer because the provider wallet, which pays for
|
||||
// the tx, is different from the market maker wallet.
|
||||
let signers = [marketMakerAccounts.account];
|
||||
for (let k = 0; k < asks.length; k += 1) {
|
||||
let ask = asks[k];
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.newOrderV3({
|
||||
owner: marketMakerAccounts.account.publicKey,
|
||||
payer: marketMakerAccounts.baseToken,
|
||||
side: "sell",
|
||||
price: ask[0],
|
||||
size: ask[1],
|
||||
orderType: "postOnly",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
await provider.send(tx, signers);
|
||||
}
|
||||
|
||||
for (let k = 0; k < bids.length; k += 1) {
|
||||
let bid = bids[k];
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.newOrderV3({
|
||||
owner: marketMakerAccounts.account.publicKey,
|
||||
payer: marketMakerAccounts.quoteToken,
|
||||
side: "buy",
|
||||
price: bid[0],
|
||||
size: bid[1],
|
||||
orderType: "postOnly",
|
||||
clientId: undefined,
|
||||
openOrdersAddressKey,
|
||||
feeDiscountPubkey: null,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
await provider.send(tx, signers);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
postOrders,
|
||||
initOpenOrders,
|
||||
KEYPAIR,
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
const { SYSVAR_RENT_PUBKEY } = require("@solana/web3.js");
|
||||
const {
|
||||
OpenOrders,
|
||||
OpenOrdersPda,
|
||||
Logger,
|
||||
ReferralFees,
|
||||
MarketProxyBuilder,
|
||||
} = require("/home/armaniferrante/Documents/code/src/github.com/project-serum/serum-ts/packages/serum");
|
||||
|
||||
// Returns a client for the market proxy.
|
||||
//
|
||||
// If changing the program, one will likely need to change the builder/middleware
|
||||
// here as well.
|
||||
async function load(connection, proxyProgramId, dexProgramId, market) {
|
||||
return new MarketProxyBuilder()
|
||||
.middleware(
|
||||
new OpenOrdersPda({
|
||||
proxyProgramId,
|
||||
dexProgramId,
|
||||
})
|
||||
)
|
||||
.middleware(new ReferralFees())
|
||||
.middleware(new Identity())
|
||||
.middleware(new Logger())
|
||||
.load({
|
||||
connection,
|
||||
market,
|
||||
dexProgramId,
|
||||
proxyProgramId,
|
||||
options: { commitment: "recent" },
|
||||
});
|
||||
}
|
||||
|
||||
// Dummy identity middleware used for testing.
|
||||
class Identity {
|
||||
initOpenOrders(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
newOrderV3(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
cancelOrderV2(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
cancelOrderByClientIdV2(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
settleFunds(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
closeOpenOrders(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
proxy(ix) {
|
||||
ix.keys = [
|
||||
{ pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
|
||||
...ix.keys,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load,
|
||||
};
|
Loading…
Reference in New Issue