Dexterity Typscript Scripts
A recolection of scripts that expose different ways to interact & fetch information from the Dexterity Protocol through the Typescript SDK
Index:
Create TRG, Get TRGs, Deposit, Withdraw, Place Order
Get all MPGs and their Products for each
Orderbook
Mark & Index Price
Account info
Instructions Bundle
Requirements:
Create a typescript project:
mkdir dexterity-project
cd dexterity-project
npm i typescript --save-dev
npx tsc --init
Install Dependencies:
npm install @solana/web3.js @hxronetwork/dexterity-ts @project-serum/anchor
Create a Solana Wallet and export your private/secret-key:
Send yourself some devnet SOL:
Send yourself some devnet UXDC to trade on Devnet:
Create TRG, Get TRGs, Deposit, Withdraw, Place Order:
import { Keypair, PublicKey } from '@solana/web3.js';
import dexterityTs from '@hxronetwork/dexterity-ts';
import { Wallet } from '@project-serum/anchor';
const dexterity = dexterityTs;
// Configure Solana Devnet connection and relevant constants
const rpc = "your-devnet-rpc-url";
const keypair = Keypair.fromSecretKey(new Uint8Array([])); // Replace with your private key
const wallet = new Wallet(keypair);
const pubkey = 'wallet-pubkey'; // Replace with your wallet public key
const MPG = "BRWNCEzQTm8kvEXHsVVY9jpb1VLbpv9B8mkF43nMLCtu";
const TRG = new PublicKey("your-created-trg"); // Replace with the TRG public key
const PRODUCT_NAME = 'SOLUSD-PERP'; // Replace with the desired product name
/**
* Creates a Trader Account (TRG) for a specified MPG.
* Consumes 0.54 SOL in rent, which is refundable upon account closure.
*/
const createTrg = async () => {
const manifest = await dexterity.getManifest(rpc, true, wallet);
const trg = await manifest.createTrg(new PublicKey(MPG));
console.log('\nTRG: ', trg.toBase58());
};
/**
* Retrieves all Trader Accounts (TRGs) associated with a specified MPG for a given wallet.
*/
const getTrgs = async () => {
const manifest = await dexterity.getManifest(rpc, true, wallet);
const trgArr = await manifest.getTRGsOfWallet(new PublicKey(MPG));
const TRGs = trgArr.map(trg => trg.pubkey);
console.log(`\nTRGs:\n`, TRGs);
};
getTrgs();
/**
* Deposits or withdraws a specified amount to/from a Trader Account (TRG).
*/
const depositOrWithdraw = async (amount: number, type: 'deposit' | 'withdraw') => {
const manifest = await dexterity.getManifest(rpc, true, wallet);
const trader = new dexterity.Trader(manifest, TRG);
const n = dexterity.Fractional.New(amount, 0);
await trader.connect(NaN, async () => {
console.log(`\nBALANCE: ${Number(trader.getCashBalance()).toLocaleString()} UXDC`);
});
const callbacks = {
onGettingBlockHashFn: () => {},
onGotBlockHashFn: () => {},
onTxSentFn: (sig: string) => console.log(`\nSUCCESSFUL ${type.toUpperCase()} OF ${amount.toLocaleString()} UXDC\n${sig ? `SIGNATURE: https://solscan.io/tx/${sig}?cluster=devnet` : ''}`),
};
if (type === 'deposit') await trader.deposit(n, callbacks);
if (type === 'withdraw') await trader.withdraw(n);
await trader.connect(NaN, async () => {
console.log(`\nBALANCE: ${Number(trader.getCashBalance()).toLocaleString()} USDC`);
});
};
depositOrWithdraw(1000, 'deposit');
depositOrWithdraw(500, 'withdraw');
/**
* Places a buy or sell order for a specified product in the MPG.
*/
const placeOrder = async (type: 'buy' | 'sell', price: number, size: number) => {
const manifest = await dexterity.getManifest(rpc, true, wallet);
const trader = new dexterity.Trader(manifest, TRG);
await trader.connect(NaN, async () => {
console.log(`\nBALANCE: ${trader.getCashBalance()} | OPEN ORDERS: ${(await Promise.all(trader.getOpenOrders([PRODUCT_NAME]))).length} | POSITIONS VALUE: ${trader.getPositionValue()} | PNL: ${trader.getPnL()}`);
});
// Find the index of the desired product within the MPG
let ProductIndex;
for (const [name, { index }] of trader.getProducts()) {
if (name.trim() === PRODUCT_NAME.trim()) {
ProductIndex = index;
break;
}
}
// Prepare order details
const QUOTE_SIZE = dexterity.Fractional.New(size, 0);
const priceFractional = dexterity.Fractional.New(price, 0);
// Define callback functions for order handling
const callbacks = {
onGettingBlockHashFn: () => {},
onGotBlockHashFn: () => {},
onTxSentFn: (sig: string) => console.log(`\nSUCCESSFULLY PLACED LIMIT ${type.toUpperCase()} ORDER\nSIGNATURE: https://solscan.io/tx/${sig}?cluster=devnet`)
};
// Make sure Mark Prices are always up-to-date for orders to go through
await trader.updateMarkPrices()
// Place a limit order based on the specified type (buy or sell)
if (type === 'buy') {
await trader.newOrder(ProductIndex, true, priceFractional, QUOTE_SIZE, false {/*true = fillOrKill*/}, null, null, null, null, callbacks); // For a buy order
} else if (type === 'sell') {
await trader.newOrder(ProductIndex, false, priceFractional, QUOTE_SIZE, false {/*true = fillOrKill*/}, null, null, null, null, callbacks); // For a sell order
}
};
placeOrder('buy', 30000, 100);
placeOrder('sell', 30000, 50);
Get all MPGs and Products for each:
Get all the MPGs for dexterity on the selected Solana network (mainnet, devenet or testnet) and each of their products
import { Keypair } from '@solana/web3.js';
import dexterityTs from '@hxronetwork/dexterity-ts';
import { Wallet } from '@project-serum/anchor';
const dexterity = dexterityTs;
// Replace with your private key
const keypair = Keypair.fromSecretKey(new Uint8Array([]));
// Initialize a wallet instance for Dexterity using the keypair
const wallet = new Wallet(keypair);
// Specify your RPC URL here
const rpc = 'rpc-url';
/**
* Retrieves and logs information about all Market Product Groups (MPGs) and their associated products.
* Filters out a specific MPG if necessary.
*/
const getMpgs = async () => {
// Fetch the manifest from Dexterity which contains market information
const manifest = await dexterity.getManifest(rpc, true, wallet);
// Convert the MPG Map to an array for easier processing
const mpgs = Array.from(manifest.fields.mpgs.values());
// Iterate through each MPG to access its products
for (const { pubkey, mpg, orderbooks } of mpgs) {
// Skip a specific MPG if needed (replace "MPG-PUBKEY-HERE" with the actual public key to skip)
if (pubkey.toBase58() === "MPG-PUBKEY-HERE") continue;
// Iterate through each product in the MPG
for (const [_, { index, product }] of dexterity.Manifest.GetProductsOfMPG(mpg)) {
// Convert the product data to a more readable format
const meta = dexterity.productToMeta(product);
// Log the index and name of each product for debugging and information purposes
console.log('productIndex: ', index);
console.log('Name: ', dexterity.bytesToString(meta.name).trim());
}
}
};
getMpgs();
Orderbook:
Get Live Orderbook ASK & BID data for a given product in a given MPG
import { Keypair } from '@solana/web3.js';
import dexterityTs from '@hxronetwork/dexterity-ts';
import { Wallet } from '@project-serum/anchor';
const dexterity = dexterityTs;
// Initialize your keypair from your private/secret key
const keypair = Keypair.fromSecretKey(new Uint8Array([]));
// When working with a UI you need to use the DexterityWallet type
const wallet = new Wallet(keypair);
const rpc =
'https://devnet.helius-rpc.com/?api-key=';
// Testnet MPG
const mpgPubkey = 'BRWNCEzQTm8kvEXHsVVY9jpb1VLbpv9B8mkF43nMLCtu';
// Desired Product
const productName = 'SOLUSD-PERP';
/**
* Asynchronously retrieves the order book for a specific product.
*
* - Fetches the manifest containing market information.
* - Filters to find the desired Market Product Group (MPG).
* - Iterates through products to find the specific product and its associated market state.
* - Initializes streaming of order book data for the selected product.
*/
const getOB = async () => {
// Fetch the manifest containing market information
const manifest = await dexterity.getManifest(rpc, true, wallet);
// Convert MPG map to an array for easier processing
const mpgs = Array.from(manifest.fields.mpgs.values());
// Find the MPG with the matching public key
const selectedMPG = mpgs.filter(
(value) => value.pubkey.toBase58() === mpgPubkey,
);
// Initialize variables to store market state and product details
let MarketState;
let PRODUCT;
let productIndex;
// Loop through each MPG to find the desired product
for (const { pubkey, mpg, orderbooks } of selectedMPG) {
for (const [_, { index, product }] of dexterity.Manifest.GetProductsOfMPG(mpg)) {
// Extract metadata of the product
const meta = dexterity.productToMeta(product);
// Check for the specified product name
if (dexterity.bytesToString(meta.name).trim() === productName.trim()) {
// Fetch the current state of the order book for the product
MarketState = await manifest.fetchOrderbook(meta.orderbook);
productIndex = index;
// Determine the type of product and assign it to PRODUCT
if (product.hasOwnProperty('outright')) {
PRODUCT = product.outright.outright;
} else {
PRODUCT = product.combo.combo;
}
// Logging for debugging purposes
console.log('productIndex: ', productIndex);
console.log('Name: ', dexterity.bytesToString(meta.name).trim());
// Break out of the loop once the desired product is found
break;
}
}
}
// Stream order book data for the selected product
const { asksSocket, bidsSocket, markPricesSocket } = manifest.streamBooks(
PRODUCT,
MarketState,
aggregateBookL2 // Callback function for handling streamed data
);
};
getOB();
/**
* Aggregates and processes Level 2 order book data.
* @param data The incoming order book data, containing asks and bids.
*/
function aggregateBookL2(data: any) {
// Define the structure for cumulative order data
interface CumulativeOrder {
price: number;
ordersSize: number;
}
// Check for the presence of both asks and bids
if (data.asks.length !== 0 && data.bids.length !== 0) {
const cumulativeBids = new Map<number, CumulativeOrder>();
const cumulativeAsks = new Map<number, CumulativeOrder>();
// Process bids
data.bids.forEach((order: any) => {
const price = order.price.toNumber();
const quantity = order.quantity.toDecimal();
if (cumulativeBids.has(price)) {
cumulativeBids.get(price)!.ordersSize += quantity;
} else {
cumulativeBids.set(price, { price, ordersSize: quantity });
}
});
// Process asks
data.asks.forEach((offer: any) => {
const price = offer.price.toNumber();
const quantity = offer.quantity.toDecimal();
if (cumulativeAsks.has(price)) {
cumulativeAsks.get(price)!.ordersSize += quantity;
} else {
cumulativeAsks.set(price, { price, ordersSize: quantity });
}
});
// Convert Maps to sorted arrays and limit to top 10
const bids = Array.from(cumulativeBids.values()).sort((a, b) => b.price - a.price).slice(0, 10);
const asks = Array.from(cumulativeAsks.values()).sort((a, b) => a.price - b.price).slice(0, 10);
// Prepare and output the result
let output = `Order book update received:\n\nASKS:\n`;
asks.forEach((ask, index) => {
output += `#${index + 1} QTY: ${ask.ordersSize} | PRICE: $${ask.price}\n`;
});
output += '\nBIDS:\n';
bids.forEach((bid, index) => {
output += `#${index + 1} QTY: ${bid.ordersSize} | PRICE: $${bid.price}\n`;
});
process.stdout.write(output);
process.stdout.write('\u001b[?25h'); // Show cursor
} else {
// Handle missing data cases
console.log(`Missing ${data.bids.length === 0 && data.asks.length > 0 ? 'BIDS' : data.asks.length === 0 && data.bids.length > 0 ? 'ASKS' : 'BIDS & ASKS'}`);
}
}
Mark & Index Price:
Fetch for Index & Mark prices for a desired product in a desired MPG
import { Keypair, PublicKey } from '@solana/web3.js';
import dexterityTs from '@hxronetwork/dexterity-ts';
import { Wallet } from '@project-serum/anchor';
const dexterity = dexterityTs;
// Initialize your keypair from your private/secret key
const keypair = Keypair.fromSecretKey(new Uint8Array([]));
// Initialize a wallet instance for Dexterity using the keypair
const wallet = new Wallet(keypair);
const rpc = 'https://devnet.helius-rpc.com/?api-key=';
// Testnet Market Product Group (MPG) public key
const mpgPubkey = 'BRWNCEzQTm8kvEXHsVVY9jpb1VLbpv9B8mkF43nMLCtu';
// Trader Risk Group (TRG) for the selected MPG
const TRG = new PublicKey("your-created-trg")
// Name of the desired product
const productName = 'SOLUSD-PERP';
// Uninitialized account public key
const UNINITIALIZED = new PublicKey('11111111111111111111111111111111');
/**
* Periodically updates the market prices for products in a specified MPG.
* - Initializes the Dexterity manifest.
* - Creates a trader instance and repeatedly updates market prices.
*/
const updatePrices = async () => {
// Initialize the Dexterity manifest with the RPC endpoint and wallet
const manifest = await dexterity.getManifest(rpc, true, wallet);
// Create a trader instance for the specified MPG
const trader = new dexterity.Trader(manifest, TRG);
// Set an interval to periodically update market prices
setInterval(async () => {
// Update the mark prices for all products in the MPG
await trader.updateMarkPrices();
// Iterate through all products in the MPG
for (const [productName, obj] of dexterity.Manifest.GetProductsOfMPG(trader.mpg)) {
const { product } = obj;
// Skip if the product name is empty
if (!productName.trim()) {
continue;
}
// Get metadata of the product
const meta = dexterity.productToMeta(product);
// Skip if the product key is uninitialized
if (meta.productKey.equals(UNINITIALIZED)) {
continue;
}
// Skip if the product is of type 'combo'
if (product.combo?.combo) {
continue;
}
// Update mark prices again (consider if this is necessary)
await trader.updateMarkPrices();
// Fetch and log the index and mark prices for the product
const index = Number(dexterity.Manifest.GetIndexPrice(trader.markPrices, meta.productKey));
const mark = Number(dexterity.Manifest.GetMarkPrice(trader.markPrices, meta.productKey));
console.log({ index, mark });
}
}, 500); // The interval is set to 500 milliseconds
};
Account info:
import { clusterApiUrl, Keypair, PublicKey } from '@solana/web3.js';
import { Wallet } from '@project-serum/anchor';
import dexterityTs from '@hxronetwork/dexterity-ts';
const dexterity = dexterityTs;
// Solana RPC URL for connecting to the blockchain
const rpc = 'https://example-rpc.com';
// Setting up our wallet using a private key to sign transactions and interact with the blockchain
const keypair = Keypair.fromSecretKey(new Uint8Array([]));
const wallet = new Wallet(keypair);
/**
* View account information and open orders for a specified trading group (TRG) in Dexterity.
*/
const viewAccount = async () => {
// Fetch the latest manifest from Dexterity
const manifest = await dexterity.getManifest(rpc, false, wallet);
// Specify the Market-Product-Group (MPG) public key
const MPG = new PublicKey('BRWNCEzQTm8kvEXHsVVY9jpb1VLbpv9B8mkF43nMLCtu');
const trgPubkey = new PublicKey('your-trg'); // Replace with actual TRG public key
console.log(`Wallet: ${wallet.publicKey.toBase58()} TRG: ${trgPubkey.toBase58()}`);
// Define the product name to filter orders
const PRODUCT_NAME = ['SOLUSD-PERP']; // Array of product names
// Create a trader instance to interact with Dexterity
const trader = new dexterity.Trader(manifest, trgPubkey);
// Function to stream and display account and order information
const streamAccount = async () => {
// Retrieve all open orders for specified products
const orders = await Promise.all(trader.getOpenOrders(PRODUCT_NAME));
// Format and display open orders
if (orders.length === 0) {
console.log('No Open Orders');
} else {
orders.forEach((order, index) => {
console.log(`Index: ${index} | Product: ${order.productName} | Price: $${order.price.m} | Qty: ${order.qty.m.toNumber() / 10 ** 6} | Type: ${order.isBid ? 'Bid' : 'Ask'} | Id: ${order.id.toString()}`);
});
}
// Display account information
console.log('\nOpen Orders:', orders.length, '\nPortfolio Value:', trader.getPortfolioValue().toString(), 'Position Value:', trader.getPositionValue().toString(), 'Net Cash:', trader.getNetCash().toString(), 'PnL:', trader.getPnL().toString());
};
// Connect to the trader account and stream account information
await trader.connect(streamAccount, NaN);
};
viewAccount();
Instructions Bundles
import { clusterApiUrl, Keypair, PublicKey } from '@solana/web3.js';
import { Wallet } from '@project-serum/anchor';
import dexterityTs from '@hxronetwork/dexterity-ts';
const dexterity = dexterityTs;
// Solana RPC URL for connecting to the blockchain
const rpc = 'https://example-rpc.com';
// Set up the wallet using a private key to sign transactions
const keypair = Keypair.fromSecretKey(new Uint8Array([])); // Replace with actual private key
const wallet = new Wallet(keypair);
// Initialize constants like the Trader Account (TRG) and Product Index
const TRG = new PublicKey(''); // Replace with actual TRG public key
const PRODUCT_INDEX = 0; // Index for 'SOLUSD-PERP' or similar product
/**
* Demonstrates how to efficiently submit multiple instructions as a bundle.
*/
const submitIxBundle = async () => {
// Fetch the latest manifest from Dexterity
const manifest = await dexterity.getManifest(rpc, true, wallet);
// Create a trader instance to interact with Dexterity
const trader = new dexterity.Trader(manifest, TRG);
// Connect to the trader account and log account details
await trader.connect(NaN, async () => {
console.log(`\nBALANCE: ${trader.getCashBalance()} | OPEN ORDERS: ${(await Promise.all(trader.getOpenOrders([PRODUCT_NAME]))).length} | POSITIONS VALUE: ${trader.getPositionValue()} | PNL: ${trader.getPnL()}`);
});
// Prepare order details using fractional values
const sizeFractional = dexterity.Fractional.New(1, 0); // Replace with desired size
const priceFractional = dexterity.Fractional.New(65, 0); // Replace with desired price
// Define callback functions for handling order transactions
const callbacks = {
onGettingBlockHashFn: () => {},
onGotBlockHashFn: () => {},
onTxSentFn: (sig: string) => console.log(`SIGNATURE: https://solscan.io/tx/${sig}?cluster=devnet`)
};
// Generate the instruction for updating market prices
const products = Array.from(dexterity.Manifest.GetProductsOfMPG(trader.mpg));
const updateMarkIx = trader.getUpdateMarkPricesIx(products);
// Generate the instruction for a new limit order
const orderIx = trader.getNewOrderIx(PRODUCT_INDEX, false, priceFractional, sizeFractional, false, null, null, null, null);
// Submit the bundled instructions
await trader.sendTx([updateMarkIx, orderIx], callbacks);
};
submitIxBundle();
Last updated