From dc6fda19c180e7184a66392939ebdd0da31951d2 Mon Sep 17 00:00:00 2001 From: 0xbigz <0xbigz> Date: Mon, 11 Apr 2022 13:35:46 -0400 Subject: [PATCH 1/3] add maker example (devnet default) --- src/maker-orders.ts | 272 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 src/maker-orders.ts diff --git a/src/maker-orders.ts b/src/maker-orders.ts new file mode 100644 index 0000000..249405f --- /dev/null +++ b/src/maker-orders.ts @@ -0,0 +1,272 @@ +import * as anchor from "@project-serum/anchor"; +import { Provider } from "@project-serum/anchor"; +import { + Admin, + BN, + AMM_RESERVE_PRECISION, + calculateMarkPrice, + getLimitOrderParams, + PositionDirection, + ClearingHouse, + ClearingHouseUser, + ZERO, + OrderParams, + isVariant, + Markets, + initialize, + DriftEnv, + // Wallet, +} from "@drift-labs/sdk"; + +import dotenv = require("dotenv"); +import { PublicKey, TransactionSignature, Transaction } from "@solana/web3.js"; + +dotenv.config(); + +async function cancelAllThenPlaceNewOrders( + clearingHouse: ClearingHouse, + clearingHouseUser: ClearingHouseUser, + orderParams: OrderParams[], + discountToken?: PublicKey, + referrer?: PublicKey +): Promise { + const instructions: anchor.web3.TransactionInstruction[] = []; + const userOrdersAccountExists = + await clearingHouse.userOrdersAccountExists(); + + const orderAccount = clearingHouseUser.getUserOrdersAccount(); + let numOpenOrders = 0; + for (const order of orderAccount.orders) { + if (!isVariant(order.status, "init")) { + numOpenOrders += 1; + } + } + const markets = clearingHouse.getMarketsAccount(); + + const markets_with_position = clearingHouseUser + .getUserPositionsAccount() + .positions.map((position) => { + return position.marketIndex.toString(); + }); + + // cancel all open orders before placing new ones + // if (userOrdersAccountExists && numOpenOrders > 0) { + // const oracles = clearingHouseUser + // .getUserPositionsAccount() + // .positions.map((position) => { + // return markets.markets[position.marketIndex.toString()].amm + // .oracle; + // }); + // console.log("canceling all open orders (", numOpenOrders, ")"); + + // // todo: check if all orders cannot be filled at market first + // instructions.push(await clearingHouse.getCancelAllOrdersIx(oracles)); + // } + + console.log("placing", orderParams.length, "new orders"); + + // post list of orders + for (const orderParm of orderParams) { + const orderMarket = orderParm.marketIndex.toString(); + + // convient check/message for too many positions/orders across markets + if ( + markets_with_position.length >= 5 && + !markets_with_position.includes(orderMarket) + ) { + throw new Error( + "Cannot place an order for additional MarketIndex=" + + orderMarket + + ". The max number of cross margined positions per account is 5." + ); + } + + instructions.push( + await clearingHouse.getPlaceOrderIx( + orderParm, + discountToken, + referrer + ) + ); + } + const tx = new Transaction(); + for (const instruction of instructions) { + tx.add(instruction); + } + + // send all of above in single atomic transaction + let txResult = await clearingHouse.txSender.send( + tx, + [], // additionalSigners + clearingHouse.opts // transaction ConfirmOptions + ); + + return txResult; +} + +function constructFloatingMMSpread( + clearingHouse: ClearingHouse, + marketIndex: BN, + baseAssetAmount: BN, + postOnly: boolean +) { + const market = clearingHouse.getMarket(marketIndex); + const limitPrice = ZERO; + // const limitPrice = market.amm.lastMarkPriceTwap; + + // 10 bps + const offset = market.amm.lastMarkPriceTwap.div(new BN(-1000)); + + //bid + const orderParams = getLimitOrderParams( + marketIndex, + PositionDirection.LONG, + baseAssetAmount, + limitPrice, + false, + undefined, + undefined, + 0, + postOnly, + offset, + false //ioc + ); + + // ask + const orderParams2 = getLimitOrderParams( + marketIndex, + PositionDirection.SHORT, + baseAssetAmount, + limitPrice, + false, + undefined, + undefined, + 0, + postOnly, + offset.mul(new BN(-1)), + false //ioc + ); + + return [orderParams, orderParams2]; +} + +function constructFixedMMSpread( + clearingHouse: ClearingHouse, + marketIndex: BN, + baseAssetAmount: BN, + postOnly: boolean +) { + const market = clearingHouse.getMarket(marketIndex); + const limitPrice = calculateMarkPrice(market); + const offset = limitPrice.div(new BN(10000)); // 1 bps + + //bid + const orderParams = getLimitOrderParams( + marketIndex, + PositionDirection.LONG, + baseAssetAmount, + limitPrice.sub(offset), + false, + undefined, + undefined, + 0, + postOnly, + ZERO, + false //ioc + ); + + // ask + const orderParams2 = getLimitOrderParams( + marketIndex, + PositionDirection.SHORT, + baseAssetAmount, + limitPrice.add(offset), + false, + undefined, + undefined, + 0, + postOnly, + ZERO, + false //ioc + ); + + return [orderParams, orderParams2]; +} + +async function makeMarket(provider: Provider, marketIndex: BN) { + const connection = provider.connection; + + const sdkConfig = initialize({ env: "devnet" as DriftEnv }); //change to "mainnet-beta" for production + const clearingHousePublicKey = new PublicKey( + sdkConfig.CLEARING_HOUSE_PROGRAM_ID + // 'AsW7LnXB9UA1uec9wi9MctYTgTz7YH9snhxd16GsFaGX' // devnet + // "dammHkt7jmytvbS3nHTxQNEcP59aE57nxwV21YdqEDN" // mainnet-beta + ); + const clearingHouse = Admin.from( + connection, + provider.wallet, + clearingHousePublicKey + ); + + console.log("Deploying wallet:", provider.wallet.publicKey.toString()); + console.log("ClearingHouse ProgramID:", clearingHousePublicKey.toString()); + const usdcMint = new PublicKey( + "8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2" + ); + + console.log("USDC Mint:", usdcMint.toString()); // TODO: put into Next config + console.log("Initializing ClearingHouse"); + console.log("Initialized ClearingHouse"); + await clearingHouse.subscribe(); + + const user = ClearingHouseUser.from( + clearingHouse, + provider.wallet.publicKey + ); + + await user.subscribe(); + + const baseAssetAmount = AMM_RESERVE_PRECISION; // AMM_RESERVE_PRECISION is 1 SOL + const postOnly = true; + + const bidAskOrdersParams: OrderParams[] = constructFixedMMSpread( + clearingHouse, + marketIndex, + baseAssetAmount, + postOnly + ); + + let tx = await cancelAllThenPlaceNewOrders( + clearingHouse, + user, + bidAskOrdersParams + ); + + console.log(tx); + + // await clearingHouse.cancelOrder(new BN(46)); + // await clearingHouse.cancelOrder(new BN(19)); + // await clearingHouse.cancelOrder(new BN(20)); + + await clearingHouse.unsubscribe(); +} + +try { + if (!process.env.ANCHOR_WALLET) { + throw new Error( + "ANCHOR_WALLET env variable must be set. (file path, e.g. ~/.config/solana/.json)" + ); + } + + // Get current price + const solMarketInfo = Markets.find( + (market) => market.baseAssetSymbol === "SOL" + ); + + const marketIndex = solMarketInfo.marketIndex; + const rpcAddress = "https://api.devnet.solana.com"; // for devnet; https://api.mainnet-beta.solana.com for mainnet; + + makeMarket(anchor.Provider.local(rpcAddress), marketIndex); +} catch (e) { + console.error(e); +} From da8a2d7c3b41f85f7fa0bf434c19621b2f9eb3d7 Mon Sep 17 00:00:00 2001 From: 0xbigz <0xbigz> Date: Mon, 11 Apr 2022 13:46:37 -0400 Subject: [PATCH 2/3] cleanup / description --- src/maker-orders.ts | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/maker-orders.ts b/src/maker-orders.ts index 249405f..d1e03a1 100644 --- a/src/maker-orders.ts +++ b/src/maker-orders.ts @@ -195,37 +195,42 @@ function constructFixedMMSpread( async function makeMarket(provider: Provider, marketIndex: BN) { const connection = provider.connection; - const sdkConfig = initialize({ env: "devnet" as DriftEnv }); //change to "mainnet-beta" for production const clearingHousePublicKey = new PublicKey( sdkConfig.CLEARING_HOUSE_PROGRAM_ID - // 'AsW7LnXB9UA1uec9wi9MctYTgTz7YH9snhxd16GsFaGX' // devnet - // "dammHkt7jmytvbS3nHTxQNEcP59aE57nxwV21YdqEDN" // mainnet-beta ); const clearingHouse = Admin.from( connection, provider.wallet, clearingHousePublicKey ); - - console.log("Deploying wallet:", provider.wallet.publicKey.toString()); - console.log("ClearingHouse ProgramID:", clearingHousePublicKey.toString()); - const usdcMint = new PublicKey( - "8zGuJQqwhZafTah7Uc7Z4tXRnguqkn5KLFAP8oV6PHe2" - ); - - console.log("USDC Mint:", usdcMint.toString()); // TODO: put into Next config - console.log("Initializing ClearingHouse"); - console.log("Initialized ClearingHouse"); await clearingHouse.subscribe(); const user = ClearingHouseUser.from( clearingHouse, provider.wallet.publicKey ); - await user.subscribe(); + console.log( + "User Authority:", + provider.wallet.publicKey.toString(), + "| ClearingHouse ProgramID:", + clearingHousePublicKey.toString() + ); + + // Drift Protocol Maker Order Example: + // + // Places a Bid/Ask 1 bp from the current Mark Price for the selected Market + // + // e.g. Long 1 SOL @ + // e.g. Short 1 SOL @ + // ( for a $100 contract this is a 2 cent spread ) + + // The orders are placed atomically (all or nothing) and are by default Post Only (0 protocol fee if filled) + // the transaction will fail if the market moves s.t any order can be filled when recieved. this guarantees + // that the spread will be posted. + const baseAssetAmount = AMM_RESERVE_PRECISION; // AMM_RESERVE_PRECISION is 1 SOL const postOnly = true; @@ -235,7 +240,6 @@ async function makeMarket(provider: Provider, marketIndex: BN) { baseAssetAmount, postOnly ); - let tx = await cancelAllThenPlaceNewOrders( clearingHouse, user, From cb8cd6384bb4e114cd40bfa27d7ae3998c55c4d2 Mon Sep 17 00:00:00 2001 From: 0xbigz <0xbigz> Date: Mon, 11 Apr 2022 13:49:42 -0400 Subject: [PATCH 3/3] more comments --- src/maker-orders.ts | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/maker-orders.ts b/src/maker-orders.ts index d1e03a1..7624aae 100644 --- a/src/maker-orders.ts +++ b/src/maker-orders.ts @@ -165,11 +165,11 @@ function constructFixedMMSpread( marketIndex, PositionDirection.LONG, baseAssetAmount, - limitPrice.sub(offset), - false, - undefined, - undefined, - 0, + limitPrice.sub(offset), // price + false, // reduce only + undefined, //discount (not currently supported) + undefined, // referfee (not currently supported) + 0, // user Order Id postOnly, ZERO, false //ioc @@ -220,20 +220,19 @@ async function makeMarket(provider: Provider, marketIndex: BN) { ); // Drift Protocol Maker Order Example: - // - // Places a Bid/Ask 1 bp from the current Mark Price for the selected Market - // - // e.g. Long 1 SOL @ - // e.g. Short 1 SOL @ - // ( for a $100 contract this is a 2 cent spread ) // The orders are placed atomically (all or nothing) and are by default Post Only (0 protocol fee if filled) // the transaction will fail if the market moves s.t any order can be filled when recieved. this guarantees // that the spread will be posted. - const baseAssetAmount = AMM_RESERVE_PRECISION; // AMM_RESERVE_PRECISION is 1 SOL - const postOnly = true; + const baseAssetAmount = AMM_RESERVE_PRECISION; // AMM_RESERVE_PRECISION (1e13) represents 1 base asset (SOL/BTC/etc) + const postOnly = true; // 0 protocol fee (but also no price improvement) + // Places a Bid/Ask 1 bp from the current Mark Price for the selected Market + // + // e.g. Long 1 SOL @ + // e.g. Short 1 SOL @ + // ( for a $100 contract this is a 2 cent spread ) const bidAskOrdersParams: OrderParams[] = constructFixedMMSpread( clearingHouse, marketIndex,