@drift-labs/common
Version:
Common functions for Drift
1,023 lines โข 47 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseSwapMode = exports.parseDirection = exports.parseAmount = exports.parseArgs = exports.main = void 0;
const anchor = __importStar(require("@coral-xyz/anchor"));
const web3_js_1 = require("@solana/web3.js");
const sdk_1 = require("@drift-labs/sdk");
const tweetnacl_1 = require("tweetnacl");
const CentralServerDrift_1 = require("./Drift/clients/CentralServerDrift");
const utils_1 = require("../utils");
const apiUrls_1 = require("./constants/apiUrls");
const path = __importStar(require("path"));
// Load environment variables from .env file
const dotenv = require('dotenv');
dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* CLI Tool for CentralServerDrift client
*
* This CLI tool allows you to execute various Drift protocol operations via command line
* with human-readable parameters that are automatically converted to the proper precision.
*
* Prerequisites:
* - Create a .env file in the same directory as this CLI file with:
* ANCHOR_WALLET=[private key byte array]
* ENDPOINT=https://your-rpc-endpoint.com
*
* Usage Examples:
* ts-node cli.ts deposit --marketIndex=0 --amount=2 --userAccount=11111111111111111111111111111111
* ts-node cli.ts withdraw --marketIndex=0 --amount=1.5 --userAccount=11111111111111111111111111111111
* ts-node cli.ts settleFunding --userAccount=11111111111111111111111111111111
* ts-node cli.ts settlePnl --marketIndexes=0,1 --userAccount=11111111111111111111111111111111
* ts-node cli.ts openPerpMarketOrder --marketIndex=0 --direction=long --amount=0.1 --assetType=base --userAccount=11111111111111111111111111111111
* ts-node cli.ts openPerpMarketOrderSwift --marketIndex=0 --direction=short --amount=100 --assetType=quote --userAccount=11111111111111111111111111111111
* ts-node cli.ts openPerpNonMarketOrder --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=100 --userAccount=11111111111111111111111111111111
* ts-node cli.ts openPerpNonMarketOrderSwift --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=99.5 --userAccount=11111111111111111111111111111111
* ts-node cli.ts editOrder --userAccount=11111111111111111111111111111111 --orderId=123 --newLimitPrice=105.5
* ts-node cli.ts cancelOrder --userAccount=11111111111111111111111111111111 --orderIds=123,456,789
* ts-node cli.ts cancelAllOrders --userAccount=11111111111111111111111111111111 --marketType=perp
* ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --fromAmount=1.5 --slippage=100 --swapMode=ExactIn
* ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --toAmount=150 --slippage=100 --swapMode=ExactOut
* ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary"
*/
// Shared configuration
let centralServerDrift;
let wallet;
/**
* Parse command line arguments into a key-value object
*/
function parseArgs(args) {
const parsed = {};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg.startsWith('--')) {
const [key, value] = arg.substring(2).split('=');
if (value !== undefined) {
// Handle comma-separated arrays
if (value.includes(',')) {
parsed[key] = value.split(',');
}
else {
parsed[key] = value;
}
}
else if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
// Handle space-separated values
const nextValue = args[i + 1];
if (nextValue.includes(',')) {
parsed[key] = nextValue.split(',');
}
else {
parsed[key] = nextValue;
}
i++; // Skip the next argument as it's the value
}
else {
parsed[key] = 'true'; // Boolean flag
}
}
}
return parsed;
}
exports.parseArgs = parseArgs;
/**
* Convert human-readable amount to BN with proper precision
*/
function parseAmount(amount, precision = sdk_1.QUOTE_PRECISION) {
const floatAmount = parseFloat(amount);
if (isNaN(floatAmount)) {
throw new Error(`Invalid amount: ${amount}`);
}
// Convert to the proper precision
const scaledAmount = Math.floor(floatAmount * precision.toNumber());
return new sdk_1.BN(scaledAmount);
}
exports.parseAmount = parseAmount;
/**
* Parse direction string to PositionDirection
*/
function parseDirection(direction) {
const normalized = direction.toLowerCase();
if (normalized === 'long' || normalized === 'buy') {
return sdk_1.PositionDirection.LONG;
}
else if (normalized === 'short' || normalized === 'sell') {
return sdk_1.PositionDirection.SHORT;
}
else {
throw new Error(`Invalid direction: ${direction}. Use 'long', 'short', 'buy', or 'sell'`);
}
}
exports.parseDirection = parseDirection;
/**
* Parse order type string to OrderType
*/
function _parseOrderType(orderType) {
const normalized = orderType.toLowerCase();
switch (normalized) {
case 'limit':
return sdk_1.OrderType.LIMIT;
case 'market':
return sdk_1.OrderType.MARKET;
case 'oracle':
return sdk_1.OrderType.ORACLE;
case 'trigger_market':
case 'stopmarket':
return sdk_1.OrderType.TRIGGER_MARKET;
case 'trigger_limit':
case 'stoplimit':
return sdk_1.OrderType.TRIGGER_LIMIT;
default:
throw new Error(`Invalid order type: ${orderType}. Use 'limit', 'market', 'oracle', 'trigger_market', or 'trigger_limit'`);
}
}
/**
* Parse post only string to PostOnlyParams
*/
function parsePostOnly(postOnly) {
const normalized = postOnly.toLowerCase();
switch (normalized) {
case 'none':
case 'false':
return sdk_1.PostOnlyParams.NONE;
case 'must_post_only':
case 'true':
return sdk_1.PostOnlyParams.MUST_POST_ONLY;
case 'try_post_only':
return sdk_1.PostOnlyParams.TRY_POST_ONLY;
case 'slide':
return sdk_1.PostOnlyParams.SLIDE;
default:
throw new Error(`Invalid post only: ${postOnly}. Use 'none', 'must_post_only', 'try_post_only', or 'slide'`);
}
}
/**
* Parse swap mode string to SwapMode
*/
function parseSwapMode(swapMode) {
const normalized = swapMode.toLowerCase();
switch (normalized) {
case 'exactin':
case 'exact_in':
return 'ExactIn';
case 'exactout':
case 'exact_out':
return 'ExactOut';
default:
throw new Error(`Invalid swap mode: ${swapMode}. Use 'ExactIn' or 'ExactOut'`);
}
}
exports.parseSwapMode = parseSwapMode;
/**
* Get the precision for a spot market based on environment and market index
*/
function getMarketPrecision(marketIndex, isMainnet = true) {
const markets = isMainnet ? sdk_1.MainnetSpotMarkets : sdk_1.DevnetSpotMarkets;
const market = markets.find((m) => m.marketIndex === marketIndex);
if (!market) {
console.warn(`โ ๏ธ Market ${marketIndex} not found, using QUOTE_PRECISION as fallback`);
return sdk_1.QUOTE_PRECISION;
}
return market.precision;
}
/**
* Initialize CentralServerDrift instance
*/
async function initializeCentralServerDrift() {
console.log('๐ Initializing CentralServerDrift...\n');
// Validate required environment variables
if (!process.env.ANCHOR_WALLET) {
throw new Error('ANCHOR_WALLET must be set in .env file');
}
if (!process.env.ENDPOINT) {
throw new Error('ENDPOINT environment variable must be set to your Solana RPC endpoint');
}
// Set up the wallet
wallet = new anchor.Wallet((0, sdk_1.loadKeypair)(process.env.ANCHOR_WALLET));
console.log(`โ
Wallet Public Key: ${wallet.publicKey.toString()}`);
console.log(`โ
RPC Endpoint: ${process.env.ENDPOINT}\n`);
// Initialize CentralServerDrift
console.log('๐๏ธ Initializing CentralServerDrift...');
centralServerDrift = new CentralServerDrift_1.CentralServerDrift({
solanaRpcEndpoint: process.env.ENDPOINT,
driftEnv: 'mainnet-beta', // Change to 'devnet' for devnet testing
supportedPerpMarkets: [0, 1, 2], // SOL, BTC, ETH
supportedSpotMarkets: [0, 1], // USDC, SOL
additionalDriftClientConfig: {
txVersion: 0,
txParams: {
computeUnits: 200000,
computeUnitsPrice: 1000,
},
},
});
console.log('โ
CentralServerDrift instance created successfully\n');
// Subscribe to market data
console.log('๐ก Subscribing to market data...');
await centralServerDrift.subscribe();
console.log('โ
Successfully subscribed to market data\n');
}
/**
* Execute a regular transaction
*/
async function executeTransaction(txn, transactionType) {
console.log(`โ
${transactionType} transaction created successfully`);
console.log('\n๐ Signing Transaction...');
txn.sign([wallet.payer]);
console.log('โ
Transaction signed successfully');
console.log('\n๐ Sending transaction to the network...');
const { txSig } = await centralServerDrift.sendSignedTransaction(txn);
console.log('โ
Transaction sent successfully!');
console.log(`๐ Transaction Signature: ${txSig === null || txSig === void 0 ? void 0 : txSig.toString()}`);
console.log(`๐ View on Solscan: https://solscan.io/tx/${txSig === null || txSig === void 0 ? void 0 : txSig.toString()}`);
}
const createSwiftOrderCallbacks = (orderType) => {
const terminalCall = () => {
console.log('๐ Order monitoring completed');
};
return {
onSigningExpiry: () => {
console.log('Swift order signing expired');
},
onSigningSuccess: (signedMessage, orderUuid, orderParamsMessage) => {
console.log('Swift order signed successfully. Signed message:', signedMessage, orderUuid, orderParamsMessage);
},
onSent: () => {
console.log(`โ
${orderType} Swift order submitted successfully`);
},
onConfirmed: (event) => {
console.log('โ
Order confirmed!');
console.log(`๐ Order ID: ${event.orderId}`);
console.log(`๐ Hash: ${event.hash}`);
terminalCall();
},
onExpired: (event) => {
console.error('โฐ Order expired:', event.message);
terminalCall();
},
onErrored: (event) => {
console.error('โ Order failed:', event.message);
console.error(`๐ Status: ${event.status}`);
terminalCall();
},
};
};
/**
* CLI Command: deposit
*/
async function depositCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const amount = args.amount;
if (!userAccount || isNaN(marketIndex) || !amount) {
throw new Error('Required arguments: --userAccount, --marketIndex, --amount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const amountBN = parseAmount(amount, sdk_1.QUOTE_PRECISION); // Most deposits are USDC (quote precision)
console.log('--- ๐ฅ Deposit Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
const depositTxn = await centralServerDrift.getDepositTxn(userAccountPubkey, amountBN, marketIndex);
await executeTransaction(depositTxn, 'Deposit');
}
/**
* CLI Command: createUserAndDeposit
*/
async function createUserAndDepositCommand(args) {
const marketIndexArg = args.marketIndex;
const amountArg = args.amount;
const referrerName = args.referrerName;
const accountName = args.accountName;
const poolIdArg = args.poolId;
const fromSubAccountIdArg = args.fromSubAccountId;
const customMaxMarginRatioArg = args.customMaxMarginRatio;
if (!marketIndexArg || !amountArg) {
throw new Error('Required arguments: --marketIndex, --amount');
}
const marketIndex = parseInt(marketIndexArg, 10);
if (isNaN(marketIndex)) {
throw new Error(`Invalid marketIndex: ${marketIndexArg}`);
}
const precision = getMarketPrecision(marketIndex, true);
const amountBN = parseAmount(amountArg, precision);
const options = {};
if (referrerName) {
options.referrerName = referrerName;
}
if (accountName) {
options.accountName = accountName;
}
if (poolIdArg !== undefined) {
const poolId = parseInt(poolIdArg, 10);
if (isNaN(poolId)) {
throw new Error(`Invalid poolId: ${poolIdArg}`);
}
options.poolId = poolId;
}
if (fromSubAccountIdArg !== undefined) {
const fromSubAccountId = parseInt(fromSubAccountIdArg, 10);
if (isNaN(fromSubAccountId)) {
throw new Error(`Invalid fromSubAccountId: ${fromSubAccountIdArg}`);
}
options.fromSubAccountId = fromSubAccountId;
}
if (customMaxMarginRatioArg !== undefined) {
const customMaxMarginRatio = parseFloat(customMaxMarginRatioArg);
if (isNaN(customMaxMarginRatio)) {
throw new Error(`Invalid customMaxMarginRatio: ${customMaxMarginRatioArg}`);
}
options.customMaxMarginRatio = customMaxMarginRatio;
}
console.log('--- ๐ Create User & Deposit Transaction ---');
console.log(`๐ Authority (wallet): ${wallet.publicKey.toString()}`);
console.log(`๐ช Spot Market Index: ${marketIndex}`);
console.log(`๐ฐ Initial Deposit: ${amountArg} (${amountBN.toString()} raw units)`);
if (options.accountName) {
console.log(`๐ Account Name: ${options.accountName}`);
}
if (options.referrerName) {
console.log(`๐ Referrer Name: ${options.referrerName}`);
}
if (options.poolId !== undefined) {
console.log(`๐ Pool ID: ${options.poolId}`);
}
if (options.fromSubAccountId !== undefined) {
console.log(`๐ Funding From Subaccount ID: ${options.fromSubAccountId}`);
}
if (options.customMaxMarginRatio !== undefined) {
console.log(`๐ Custom Max Margin Ratio: ${options.customMaxMarginRatio}`);
}
const hasOptions = Object.keys(options).length > 0;
const { transaction, userAccountPublicKey, subAccountId } = await centralServerDrift.getCreateAndDepositTxn(wallet.publicKey, amountBN, marketIndex, hasOptions ? options : undefined);
console.log(`๐ New User Account: ${userAccountPublicKey.toString()}`);
console.log(`๐งพ Subaccount ID: ${subAccountId}`);
await executeTransaction(transaction, 'Create User & Deposit');
}
/**
* CLI Command: withdraw
*/
async function withdrawCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const amount = args.amount;
const isBorrow = args.isBorrow === 'true';
const isMax = args.isMax === 'true';
if (!userAccount || isNaN(marketIndex) || !amount) {
throw new Error('Required arguments: --userAccount, --marketIndex, --amount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const amountBN = parseAmount(amount, sdk_1.QUOTE_PRECISION);
console.log('--- ๐ค Withdraw Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
console.log(`๐ณ Is Borrow: ${isBorrow}`);
console.log(`๐ Is Max: ${isMax}`);
const withdrawTxn = await centralServerDrift.getWithdrawTxn(userAccountPubkey, amountBN, marketIndex, { isBorrow, isMax });
await executeTransaction(withdrawTxn, 'Withdraw');
}
/**
* CLI Command: settleFunding
*/
async function settleFundingCommand(args) {
const userAccount = args.userAccount;
if (!userAccount) {
throw new Error('Required arguments: --userAccount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
console.log('--- ๐ฐ Settle Funding Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
const settleFundingTxn = await centralServerDrift.getSettleFundingTxn(userAccountPubkey);
await executeTransaction(settleFundingTxn, 'Settle Funding');
}
/**
* CLI Command: settlePnl
*/
async function settlePnlCommand(args) {
const userAccount = args.userAccount;
const marketIndexesArg = args.marketIndexes;
if (!userAccount || !marketIndexesArg) {
throw new Error('Required arguments: --userAccount, --marketIndexes (comma-separated)');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const marketIndexes = Array.isArray(marketIndexesArg)
? marketIndexesArg.map((idx) => parseInt(idx))
: [parseInt(marketIndexesArg)];
console.log('--- ๐ Settle PnL Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ Market Indexes: ${marketIndexes.join(', ')}`);
const settlePnlTxn = await centralServerDrift.getSettlePnlTxn(userAccountPubkey, marketIndexes);
await executeTransaction(settlePnlTxn, 'Settle PnL');
}
/**
* CLI Command: openPerpMarketOrder (regular transaction)
*/
async function openPerpMarketOrderCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const direction = args.direction;
const amount = args.amount;
const assetType = args.assetType || 'base';
const dlobServerHttpUrl = apiUrls_1.API_URLS.DLOB;
if (!userAccount || isNaN(marketIndex) || !direction || !amount) {
throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --amount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const directionEnum = parseDirection(direction);
const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION;
const amountBN = parseAmount(amount, precision);
console.log('--- ๐ฏ Open Perp Order Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
console.log(`๐ฑ Asset Type: ${assetType}`);
console.log(`๐ DLOB Server: ${dlobServerHttpUrl}`);
const orderTxn = await centralServerDrift.getOpenPerpMarketOrderTxn({
userAccountPublicKey: userAccountPubkey,
assetType: assetType,
marketIndex,
direction: directionEnum,
amount: amountBN,
useSwift: false,
});
await executeTransaction(orderTxn, 'Open Perp Order');
}
/**
* CLI Command: openPerpMarketOrderSwift (Swift signed message order)
*/
async function openPerpMarketOrderSwiftCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const direction = args.direction;
const amount = args.amount;
const assetType = args.assetType || 'base';
const dlobServerHttpUrl = apiUrls_1.API_URLS.DLOB;
const swiftServerUrl = args.swiftServerUrl || apiUrls_1.API_URLS.SWIFT;
if (!userAccount || isNaN(marketIndex) || !direction || !amount) {
throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --amount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const directionEnum = parseDirection(direction);
const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION;
const amountBN = parseAmount(amount, precision);
console.log('--- โก Open Perp Order Swift ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
console.log(`๐ฑ Asset Type: ${assetType}`);
console.log(`๐ DLOB Server: ${dlobServerHttpUrl}`);
console.log(`โก Swift Server: ${swiftServerUrl}`);
console.log(`๐ Wallet Public Key: ${wallet.publicKey.toString()}`);
console.log('\n๐๏ธ Monitoring order status...');
try {
await centralServerDrift.getOpenPerpMarketOrderTxn({
userAccountPublicKey: userAccountPubkey,
assetType: assetType,
marketIndex,
direction: directionEnum,
amount: amountBN,
useSwift: true,
swiftOptions: {
wallet: {
signMessage: async (message) => {
const signature = tweetnacl_1.sign.detached(message, wallet.payer.secretKey);
return new Uint8Array(signature);
},
takerAuthority: wallet.publicKey,
signingAuthority: wallet.publicKey,
},
callbacks: createSwiftOrderCallbacks('Open Perp Order'),
},
});
console.log('โ
[CLI] Swift order finished');
}
catch (error) {
console.error('โ [CLI] Error creating Swift order:', error);
throw error;
}
}
/**
* CLI Command: openPerpNonMarketOrder (regular limit/oracle order)
*/
async function openPerpNonMarketOrderCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const direction = args.direction;
const amount = args.amount;
const assetType = args.assetType || 'base';
const baseAssetAmount = args.baseAssetAmount;
const orderType = args.orderType;
const limitPrice = args.limitPrice;
const triggerPrice = args.triggerPrice;
const reduceOnly = args.reduceOnly === 'true';
const postOnly = args.postOnly || 'none';
const oraclePriceOffset = args.oraclePriceOffset;
if (!userAccount || isNaN(marketIndex) || !direction || !orderType) {
throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --orderType');
}
if (!amount && !baseAssetAmount) {
throw new Error('Either --amount or --baseAssetAmount must be provided');
}
// Validate price requirements based on order type
if (orderType === 'limit') {
if (!limitPrice) {
throw new Error(`Order type '${orderType}' requires --limitPrice`);
}
}
else if (orderType === 'takeProfit' || orderType === 'stopLoss') {
if (!triggerPrice) {
throw new Error(`Order type '${orderType}' requires --triggerPrice`);
}
}
else if (orderType === 'oracleLimit') {
if (!oraclePriceOffset) {
throw new Error(`Order type '${orderType}' requires --oraclePriceOffset`);
}
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const directionEnum = parseDirection(direction);
const postOnlyEnum = parsePostOnly(postOnly);
const limitPriceBN = limitPrice
? parseAmount(limitPrice, sdk_1.PRICE_PRECISION)
: undefined;
const triggerPriceBN = triggerPrice
? parseAmount(triggerPrice, sdk_1.PRICE_PRECISION)
: undefined;
const oraclePriceOffsetBN = oraclePriceOffset
? parseAmount(oraclePriceOffset, sdk_1.PRICE_PRECISION)
: undefined;
const orderConfig = orderType === 'limit'
? {
orderType,
limitPrice: limitPriceBN,
}
: orderType === 'takeProfit' || orderType === 'stopLoss'
? {
orderType,
triggerPrice: triggerPriceBN,
}
: {
orderType,
oraclePriceOffset: oraclePriceOffsetBN,
};
console.log('--- ๐ Open Perp Non-Market Order ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`);
let amountBN;
if (amount) {
const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION;
amountBN = parseAmount(amount, precision);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
console.log(`๐ฑ Asset Type: ${assetType}`);
}
else {
amountBN = parseAmount(baseAssetAmount, sdk_1.BASE_PRECISION);
console.log(`๐ฐ Base Asset Amount: ${baseAssetAmount} (${amountBN.toString()} raw units)`);
}
if (limitPriceBN) {
console.log(`๐ต Limit Price: ${limitPrice} (${limitPriceBN.toString()} raw units)`);
}
if (triggerPriceBN) {
console.log(`๐ฏ Trigger Price: ${triggerPrice} (${triggerPriceBN.toString()} raw units)`);
}
console.log(`๐ Order Type: ${orderType}`);
console.log(`๐ Reduce Only: ${reduceOnly}`);
console.log(`๐ Post Only: ${postOnly} (${utils_1.ENUM_UTILS.toStr(postOnlyEnum)})`);
// Just call the main method - it will handle both approaches internally
const orderTxn = await centralServerDrift.getOpenPerpNonMarketOrderTxn({
userAccountPublicKey: userAccountPubkey,
marketIndex,
direction: directionEnum,
orderConfig,
useSwift: false,
reduceOnly,
postOnly: postOnlyEnum,
assetType: assetType,
amount: amountBN,
});
await executeTransaction(orderTxn, 'Open Perp Non-Market Order');
}
/**
* CLI Command: openPerpNonMarketOrderSwift (Swift signed message limit order)
*/
async function openPerpNonMarketOrderSwiftCommand(args) {
const userAccount = args.userAccount;
const marketIndex = parseInt(args.marketIndex);
const direction = args.direction;
const amount = args.amount;
const assetType = args.assetType || 'base';
const baseAssetAmount = args.baseAssetAmount;
const orderType = args.orderType;
const limitPrice = args.limitPrice;
const reduceOnly = args.reduceOnly === 'true';
const postOnly = args.postOnly || 'none';
const swiftServerUrl = apiUrls_1.API_URLS.SWIFT;
if (!userAccount || isNaN(marketIndex) || !direction || !orderType) {
throw new Error('Required arguments: --userAccount, --marketIndex, --direction, --orderType');
}
if (!amount && !baseAssetAmount) {
throw new Error('Either --amount or --baseAssetAmount must be provided');
}
// Swift orders only support LIMIT order type
if (orderType.toLowerCase() !== 'limit') {
throw new Error('Swift orders only support LIMIT order type');
}
// LIMIT orders require limitPrice
if (!limitPrice) {
throw new Error('LIMIT orders require --limitPrice');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const directionEnum = parseDirection(direction);
const postOnlyEnum = parsePostOnly(postOnly);
const limitPriceBN = parseAmount(limitPrice, sdk_1.PRICE_PRECISION);
const orderConfig = {
orderType: 'limit',
limitPrice: limitPriceBN,
};
console.log('--- โก Open Perp Non-Market Order Swift ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Index: ${marketIndex}`);
console.log(`๐ Direction: ${direction} (${utils_1.ENUM_UTILS.toStr(directionEnum)})`);
let amountBN;
if (amount) {
const precision = assetType === 'base' ? sdk_1.BASE_PRECISION : sdk_1.QUOTE_PRECISION;
amountBN = parseAmount(amount, precision);
console.log(`๐ฐ Amount: ${amount} (${amountBN.toString()} raw units)`);
console.log(`๐ฑ Asset Type: ${assetType}`);
}
else {
amountBN = parseAmount(baseAssetAmount, sdk_1.BASE_PRECISION);
console.log(`๐ฐ Base Asset Amount: ${baseAssetAmount} (${amountBN.toString()} raw units)`);
}
console.log(`๐ต Limit Price: ${limitPrice} (${limitPriceBN.toString()} raw units)`);
console.log(`๐ Order Type: ${orderType})`);
console.log(`๐ Reduce Only: ${reduceOnly}`);
console.log(`๐ Post Only: ${postOnly} (${utils_1.ENUM_UTILS.toStr(postOnlyEnum)})`);
console.log(`โก Swift Server: ${swiftServerUrl}`);
console.log(`๐ Wallet Public Key: ${wallet.publicKey.toString()}`);
try {
const swiftOptions = {
wallet: {
signMessage: async (message) => {
const signature = tweetnacl_1.sign.detached(message, wallet.payer.secretKey);
return new Uint8Array(signature);
},
takerAuthority: wallet.publicKey,
signingAuthority: wallet.publicKey,
},
callbacks: createSwiftOrderCallbacks('Open Perp Non-Market Order'),
};
console.log('\n๐๏ธ Monitoring order status...');
// Use the main method - it handles both approaches internally
await centralServerDrift.getOpenPerpNonMarketOrderTxn({
userAccountPublicKey: userAccountPubkey,
marketIndex,
direction: directionEnum,
orderConfig,
reduceOnly,
postOnly: postOnlyEnum,
assetType: assetType,
amount: amountBN,
useSwift: true,
swiftOptions,
});
console.log('โ
[CLI] Swift order finished');
}
catch (error) {
console.error('โ [CLI] Error creating Swift non-market order:', error);
throw error;
}
}
/**
* Show CLI usage information
*/
function showUsage() {
console.log('๐ Drift CLI Usage');
console.log('');
console.log('Available Commands:');
console.log('');
console.log('๐ฐ deposit');
console.log(' ts-node cli.ts deposit --userAccount=<pubkey> --marketIndex=<num> --amount=<num>');
console.log(' Example: ts-node cli.ts deposit --userAccount=11111111111111111111111111111111 --marketIndex=0 --amount=100');
console.log('');
console.log('๐ createUserAndDeposit');
console.log(' ts-node cli.ts createUserAndDeposit --marketIndex=<num> --amount=<num> [--accountName=<string>] [--referrerName=<string>] [--poolId=<num>] [--fromSubAccountId=<num>] [--customMaxMarginRatio=<num>]');
console.log(' Example: ts-node cli.ts createUserAndDeposit --marketIndex=0 --amount=100 --accountName="Primary"');
console.log('');
console.log('๐ธ withdraw');
console.log(' ts-node cli.ts withdraw --userAccount=<pubkey> --marketIndex=<num> --amount=<num> [--isBorrow=<bool>] [--isMax=<bool>]');
console.log(' Example: ts-node cli.ts withdraw --userAccount=11111111111111111111111111111111 --marketIndex=0 --amount=50');
console.log('');
console.log('๐ฆ settleFunding');
console.log(' ts-node cli.ts settleFunding --userAccount=<pubkey>');
console.log(' Example: ts-node cli.ts settleFunding --userAccount=11111111111111111111111111111111');
console.log('');
console.log('๐ settlePnl');
console.log(' ts-node cli.ts settlePnl --userAccount=<pubkey> --marketIndexes=<comma-separated>');
console.log(' Example: ts-node cli.ts settlePnl --userAccount=11111111111111111111111111111111 --marketIndexes=0,1');
console.log('');
console.log('๐ฏ openPerpMarketOrder');
console.log(' ts-node cli.ts openPerpMarketOrder --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>]');
console.log(' Example: ts-node cli.ts openPerpMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base');
console.log('');
console.log('โก openPerpMarketOrderSwift');
console.log(' ts-node cli.ts openPerpMarketOrderSwift --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>]');
console.log(' Example: ts-node cli.ts openPerpMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=short --amount=100 --assetType=quote');
console.log('');
console.log('๐ openPerpNonMarketOrder');
console.log(' ts-node cli.ts openPerpNonMarketOrder --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>] --orderType=<limit|trigger_limit|trigger_market|oracle> [--limitPrice=<num>] [--triggerPrice=<num>] [--reduceOnly=<true|false>] [--postOnly=<none|must_post_only|try_post_only|slide>]');
console.log(' New LIMIT: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base --orderType=limit --limitPrice=100');
console.log(' New QUOTE: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=100 --assetType=quote --orderType=limit --limitPrice=100');
console.log(' Legacy: ts-node cli.ts openPerpNonMarketOrder --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=100');
console.log('');
console.log('โก openPerpNonMarketOrderSwift');
console.log(' ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=<pubkey> --marketIndex=<num> --direction=<long|short> --amount=<num> [--assetType=<base|quote>] --orderType=limit --limitPrice=<num> [--reduceOnly=<true|false>] [--postOnly=<none|must_post_only|try_post_only|slide>]');
console.log(' New: ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --amount=0.1 --assetType=base --orderType=limit --limitPrice=99.5');
console.log(' Legacy: ts-node cli.ts openPerpNonMarketOrderSwift --userAccount=11111111111111111111111111111111 --marketIndex=0 --direction=long --baseAssetAmount=0.1 --orderType=limit --limitPrice=99.5');
console.log('');
console.log('โ๏ธ editOrder');
console.log(' ts-node cli.ts editOrder --userAccount=<pubkey> --orderId=<num> [--newDirection=<long|short>] [--newBaseAmount=<num>] [--newLimitPrice=<num>] [--newTriggerPrice=<num>] [--reduceOnly=<true|false>] [--postOnly=<true|false>]');
console.log(' Example: ts-node cli.ts editOrder --userAccount=11111111111111111111111111111111 --orderId=123 --newLimitPrice=105.5');
console.log('');
console.log('โ cancelOrder');
console.log(' ts-node cli.ts cancelOrder --userAccount=<pubkey> --orderIds=<comma-separated-list>');
console.log(' Example: ts-node cli.ts cancelOrder --userAccount=11111111111111111111111111111111 --orderIds=123,456,789');
console.log('');
console.log('๐งน cancelAllOrders');
console.log(' ts-node cli.ts cancelAllOrders --userAccount=<pubkey> [--marketType=<perp|spot>] [--marketIndex=<num>] [--direction=<long|short>]');
console.log(' Example: ts-node cli.ts cancelAllOrders --userAccount=11111111111111111111111111111111 --marketType=perp');
console.log('');
console.log('๐ swap');
console.log(' ts-node cli.ts swap --userAccount=<pubkey> --fromMarketIndex=<num> --toMarketIndex=<num> [--fromAmount=<num>] [--toAmount=<num>] [--slippage=<bps>] [--swapMode=<ExactIn|ExactOut>]');
console.log(' ExactIn: ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --fromAmount=1.5 --swapMode=ExactIn');
console.log(' ExactOut: ts-node cli.ts swap --userAccount=11111111111111111111111111111111 --fromMarketIndex=1 --toMarketIndex=0 --toAmount=150 --swapMode=ExactOut');
console.log('');
console.log('Options:');
console.log(' --help, -h Show this help message');
console.log('');
console.log('Notes:');
console.log(' - Amounts are in human-readable format (e.g., 1.5 USDC, 0.1 SOL)');
console.log(' - Direction can be: long, short, buy, sell');
console.log(' - Asset type: base (for native tokens like SOL) or quote (for USDC amounts)');
console.log(' - Swap modes:');
console.log(' * ExactIn: Specify --fromAmount (how much input token to swap)');
console.log(' * ExactOut: Specify --toAmount (how much output token to receive)');
console.log(' - Ensure your .env file contains ANCHOR_WALLET and ENDPOINT');
}
/**
* CLI Command: editOrder
*/
async function editOrderCommand(args) {
const userAccount = args.userAccount;
const orderId = parseInt(args.orderId);
const newDirection = args.newDirection;
const newBaseAmount = args.newBaseAmount;
const newLimitPrice = args.newLimitPrice;
const newTriggerPrice = args.newTriggerPrice;
const reduceOnly = args.reduceOnly === 'true';
const postOnly = args.postOnly === 'true';
if (!userAccount || isNaN(orderId)) {
throw new Error('Required arguments: --userAccount, --orderId');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const editParams = {};
if (newDirection) {
editParams.newDirection = parseDirection(newDirection);
}
if (newBaseAmount) {
editParams.newBaseAmount = parseAmount(newBaseAmount, sdk_1.BASE_PRECISION);
}
if (newLimitPrice) {
editParams.newLimitPrice = parseAmount(newLimitPrice, sdk_1.PRICE_PRECISION);
}
if (newTriggerPrice) {
editParams.newTriggerPrice = parseAmount(newTriggerPrice, sdk_1.PRICE_PRECISION);
}
if (reduceOnly) {
editParams.reduceOnly = true;
}
if (postOnly) {
editParams.postOnly = true;
}
console.log('--- โ๏ธ Edit Order ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ Order ID: ${orderId}`);
console.log(`๐ Edit Parameters:`, editParams);
const editOrderTxn = await centralServerDrift.getEditOrderTxn(userAccountPubkey, orderId, editParams);
await executeTransaction(editOrderTxn, 'Edit Order');
}
/**
* CLI Command: cancelOrder
*/
async function cancelOrderCommand(args) {
const userAccount = args.userAccount;
const orderIds = args.orderIds;
if (!userAccount || !orderIds) {
throw new Error('Required arguments: --userAccount, --orderIds');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
const orderIdArray = orderIds.split(',').map((id) => parseInt(id.trim()));
console.log('--- โ Cancel Orders ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ Order IDs: ${orderIdArray.join(', ')}`);
const cancelOrderTxn = await centralServerDrift.getCancelOrdersTxn(userAccountPubkey, orderIdArray);
await executeTransaction(cancelOrderTxn, 'Cancel Orders');
}
/**
* CLI Command: cancelAllOrders
*/
async function cancelAllOrdersCommand(args) {
const userAccount = args.userAccount;
const marketType = args.marketType;
const marketIndex = args.marketIndex
? parseInt(args.marketIndex)
: undefined;
const direction = args.direction;
if (!userAccount) {
throw new Error('Required arguments: --userAccount');
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
let marketTypeEnum = undefined;
let directionEnum = undefined;
if (marketType) {
marketTypeEnum =
marketType.toLowerCase() === 'perp' ? { perp: {} } : { spot: {} };
}
if (direction) {
directionEnum = parseDirection(direction);
}
console.log('--- ๐งน Cancel All Orders ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ช Market Type Filter: ${marketType || 'none'}`);
console.log(`๐ Market Index Filter: ${marketIndex !== undefined ? marketIndex : 'none'}`);
console.log(`๐ Direction Filter: ${direction || 'none'}`);
const cancelAllOrdersTxn = await centralServerDrift.getCancelAllOrdersTxn(userAccountPubkey, marketTypeEnum, marketIndex, directionEnum);
await executeTransaction(cancelAllOrdersTxn, 'Cancel All Orders');
}
/**
* CLI Command: swap
*/
async function swapCommand(args) {
const userAccount = args.userAccount;
const fromMarketIndex = parseInt(args.fromMarketIndex);
const toMarketIndex = parseInt(args.toMarketIndex);
const fromAmount = args.fromAmount;
const toAmount = args.toAmount;
const slippage = args.slippage ? parseInt(args.slippage) : 10; // Default 0.1%
const swapMode = args.swapMode || 'ExactIn';
if (!userAccount || isNaN(fromMarketIndex) || isNaN(toMarketIndex)) {
throw new Error('Required arguments: --userAccount, --fromMarketIndex, --toMarketIndex');
}
const swapModeEnum = parseSwapMode(swapMode);
// Validate amount parameters based on swap mode
if (swapModeEnum === 'ExactIn') {
if (!fromAmount) {
throw new Error('ExactIn swap mode requires --fromAmount');
}
if (toAmount) {
console.warn('โ ๏ธ Warning: --toAmount ignored in ExactIn mode, using --fromAmount');
}
}
else if (swapModeEnum === 'ExactOut') {
if (!toAmount) {
throw new Error('ExactOut swap mode requires --toAmount');
}
if (fromAmount) {
console.warn('โ ๏ธ Warning: --fromAmount ignored in ExactOut mode, using --toAmount');
}
}
const userAccountPubkey = new web3_js_1.PublicKey(userAccount);
// Use the appropriate amount based on swap mode
const amount = swapModeEnum === 'ExactIn' ? fromAmount : toAmount;
// Determine which market to use for precision (input market for ExactIn, output market for ExactOut)
const precisionMarketIndex = swapModeEnum === 'ExactIn' ? fromMarketIndex : toMarketIndex;
// Get the appropriate precision for the amount based on the market
const isMainnet = true; // Default to mainnet, could be made configurable
const precision = getMarketPrecision(precisionMarketIndex, isMainnet);
const amountBN = parseAmount(amount, precision);
console.log('--- ๐ Swap Transaction ---');
console.log(`๐ค User Account: ${userAccount}`);
console.log(`๐ค From Market Index: ${fromMarketIndex}`);
console.log(`๐ฅ To Market Index: ${toMarketIndex}`);
console.log(`๐ Swap Mode: ${swapMode}`);
console.log(`๐ฏ Using precision from market ${precisionMarketIndex}: ${precision.toString()}`);
if (swapModeEnum === 'ExactIn') {
console.log(`๐ฐ From Amount: ${fromAmount} (${amountBN.toString()} raw units)`);
console.log(`๐ฐ To Amount: Will be calculated based on quote`);
}
else {
console.log(`๐ฐ From Amount: Will be calculated based on quote`);
console.log(`๐ฐ To Amount: ${toAmount} (${amountBN.toString()} raw units)`);
}
console.log(`๐ Slippage: ${slippage} BPS (${slippage / 100}%)`);
const swapTxn = await centralServerDrift.getSwapTxn(userAccountPubkey, fromMarketIndex, toMarketIndex, amountBN, {
slippageBps: slippage,
swapMode: swapModeEnum,
});
await executeTransaction(swapTxn, 'Swap');
}
/**
* Main CLI entry point
*/
async function main() {
const args = process.argv.slice(2);
if (args.length === 0 ||
args[0] === '--help' ||
args[0] === '-h' ||
args[0] === 'help') {
showUsage();
return;
}
const command = args[0];
const parsedArgs = parseArgs(args.slice(1));
// Initialize the client first
await initializeCentralServerDrift();
// Route to appropriate command
switch (command) {
case 'createUserAndDeposit':
await createUserAndDepositCommand(parsedArgs);
break;
case 'deposit':
await depositCommand(parsedArgs);
break;
case 'withdraw':
await withdrawCommand(parsedArgs);
break;
case 'settleFunding':
await settleFundingCommand(parsedArgs);
break;
case 'settlePnl':
await settlePnlCommand(parsedArgs);
break;
case 'openPerpMarketOrder':
await openPerpMarketOrderCommand(parsedArgs);
break;
case 'openPerpMarketOrderSwift':
await openPerpMarketOrderSwiftCommand(parsedArgs);
break;
case 'openPerpNonMarketOrder':
await openPerpNonMarketOrderCommand(parsedArgs);
break;
case 'openPerpNonMarketOrderSwift':
await openPerpNonMarketOrderSwiftCommand(parsedArgs);
break;
case 'editOrder':
await editOrderCommand(parsedArgs);
break;
case 'cancelOrder':
await cancelOrderCommand(parsedArgs);
break;
case 'cancelAllOrders':
await cancelAllOrdersCommand(parsedArgs);
break;
case 'swap':
await swapCommand(parsedArgs);
break;
default:
console.error(`โ Unknown command: ${command}`);
console.error('');
showUsage();
process.exit(1);
}
}
exports.main = main;
// Run CLI if this file is executed directly
if (require.main === module) {
main()
.then(() => {
console.log('\nโจ Command executed successfully!');
// Don't exit immediately for Swift orders (they need time to process)
setTimeout(() => process.exit(0), 2000);
})
.catch((error) => {
console.error('\n๐ฅ Command failed:', error.message || error);
process.exit(1);
});
}
//# sourceMappingURL=cli.js.map
;