Overview
The LFG SDK supports various order types for trading perpetual contracts. This guide covers everything from placing basic market orders to managing complex trading strategies.
Short-Term Orders Fast orders that expire in blocks (~seconds to minutes)
Long-Term Orders Traditional limit orders with time-based expiration (hours to days)
Order Types
Short-Term Orders
Short-term orders are ideal for:
Market-making strategies
High-frequency trading
Quick in-and-out trades
Time-sensitive opportunities
Characteristics:
Expire based on block height
Lower gas costs
Faster execution
Typical lifespan: seconds to minutes
Long-Term Orders
Long-term orders are ideal for:
Position building
Swing trading
Limit orders that can wait
Strategy orders
Characteristics:
Expire based on timestamp
Standard gas costs
Can stay on orderbook for days
Typical lifespan: hours to 28 days
Placing Short-Term Orders
Basic Short-Term Order
import {
CompositeClient ,
LocalWallet ,
SubaccountInfo ,
OrderSide ,
Order_TimeInForce ,
} from "@oraichain/lfg-client-js" ;
async function placeShortTermOrder (
client : CompositeClient ,
wallet : LocalWallet
) {
// Get current block height
const currentBlock = await client . validatorClient . get . latestBlockHeight ();
// Order expires in 20 blocks (~2 minutes)
const goodTilBlock = currentBlock + 20 ;
// Generate unique client ID
const clientId = Math . floor ( Math . random () * 100000000 );
// Create subaccount
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
// Place BUY order
const tx = await client . placeShortTermOrder (
subaccount , // Trading account
"ETH-USD" , // Market
OrderSide . BUY , // Side
3800 , // Price ($3800/ETH)
0.1 , // Size (0.1 ETH)
clientId , // Unique order ID
goodTilBlock , // Expiration block
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED , // Time in force
false // Not reduce-only
);
console . log ( "✅ Order placed!" );
console . log ( "TX Hash:" , Buffer . from ( tx . hash ). toString ( "hex" ));
console . log ( "Client ID:" , clientId );
return { txHash: Buffer . from ( tx . hash ). toString ( "hex" ), clientId };
}
The subaccount placing the order
Market identifier (e.g., "ETH-USD", "BTC-USD")
OrderSide.BUY or OrderSide.SELL
Limit price in quote currency (USD). Set to market price for market orders.
Order size in base currency (e.g., ETH, BTC)
Unique identifier for this order (0 to 2^32-1)
Block height when order expires. Must be greater than current block.
timeInForce
Order_TimeInForce
required
Order time in force policy. Use TIME_IN_FORCE_UNSPECIFIED for most cases.
If true, order can only reduce position size (not increase it)
Sell Order Example
// Place SELL order to close or short
const tx = await client . placeShortTermOrder (
subaccount ,
"ETH-USD" ,
OrderSide . SELL , // Selling ETH
3900 , // Sell at $3900
0.1 , // Sell 0.1 ETH
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
false
);
Market Order (Short-Term)
// Get current market price first
const markets = await client . indexerClient . markets . getPerpetualMarkets ();
const ethMarket = markets . markets [ "ETH-USD" ];
const marketPrice = parseFloat ( ethMarket . oraclePrice );
// Place order at market price
const tx = await client . placeShortTermOrder (
subaccount ,
"ETH-USD" ,
OrderSide . BUY ,
marketPrice , // Use current market price
0.1 ,
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
false
);
For true market orders, query the current market price and place a short-term
order at that price with a short expiration.
Placing Long-Term Orders
Basic Long-Term Order
import {
OrderType ,
OrderTimeInForce ,
OrderExecution ,
} from "@oraichain/lfg-client-js" ;
async function placeLongTermOrder (
client : CompositeClient ,
wallet : LocalWallet
) {
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
// Generate unique client ID
const clientId = Math . floor ( Math . random () * 100000000 );
// Order valid for 24 hours
const goodTilTimeInSeconds = 24 * 60 * 60 ;
// Place limit order
const tx = await client . placeOrder (
subaccount ,
"ETH-USD" , // Market
OrderType . LIMIT , // Order type
OrderSide . BUY , // Side
3800 , // Price
0.1 , // Size
clientId , // Client ID
OrderTimeInForce . GTT , // Good-til-time
goodTilTimeInSeconds , // Expires in 24 hours
OrderExecution . DEFAULT , // Execution type
false , // Post-only (false)
false // Reduce-only (false)
);
console . log ( "✅ Long-term order placed!" );
console . log ( "TX Hash:" , Buffer . from ( tx . hash ). toString ( "hex" ));
console . log ( "Client ID:" , clientId );
console . log ( "Expires in:" , goodTilTimeInSeconds , "seconds" );
return { txHash: Buffer . from ( tx . hash ). toString ( "hex" ), clientId };
}
OrderType.LIMIT, OrderType.MARKET, OrderType.STOP_LIMIT, or
OrderType.STOP_MARKET
OrderTimeInForce.GTT - Good-til-time (most common) -
OrderTimeInForce.IOC - Immediate or cancel - OrderTimeInForce.FOK - Fill
or kill
Seconds from now until order expires (max 28 days)
OrderExecution.DEFAULT, OrderExecution.POST_ONLY, or OrderExecution.IOC
If true, order will only execute as maker (adds liquidity)
If true, order can only reduce existing position
Post-Only Orders
Post-only orders ensure you’re always a maker:
const tx = await client . placeOrder (
subaccount ,
"BTC-USD" ,
OrderType . LIMIT ,
OrderSide . BUY ,
65000 , // Price
0.01 , // Size
clientId ,
OrderTimeInForce . GTT ,
7 * 24 * 60 * 60 , // 7 days
OrderExecution . POST_ONLY , // Post-only execution
true , // Post-only flag
false
);
Post-only orders are rejected if they would immediately match and execute as a
taker. This guarantees maker rebates.
Reduce-Only Orders
Reduce-only orders can only close existing positions:
// Close half of your long position
const tx = await client . placeShortTermOrder (
subaccount ,
"ETH-USD" ,
OrderSide . SELL , // Sell to reduce long position
3900 ,
0.05 , // Reduce by 0.05 ETH
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
true // Reduce-only = true
);
Reduce-only orders will be rejected if you don’t have an open position in that
market.
Canceling Orders
Cancel Short-Term Order
import { OrderFlags } from "@oraichain/lfg-client-js" ;
async function cancelShortTermOrder (
client : CompositeClient ,
wallet : LocalWallet ,
clientId : number ,
originalGoodTilBlock : number
) {
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
// Cancel with new goodTilBlock
const tx = await client . cancelOrder (
subaccount ,
clientId , // Original client ID
OrderFlags . SHORT_TERM , // Order flag
"ETH-USD" , // Market
originalGoodTilBlock + 10 , // New expiration
0 // goodTilTimeInSeconds (0 for short-term)
);
console . log ( "✅ Order cancelled!" );
console . log ( "TX Hash:" , Buffer . from ( tx . hash ). toString ( "hex" ));
}
Cancel Long-Term Order
async function cancelLongTermOrder (
client : CompositeClient ,
wallet : LocalWallet ,
clientId : number ,
originalGoodTilBlockTime : number
) {
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
const currentTime = Math . floor ( Date . now () / 1000 );
const timeUntilExpiration = originalGoodTilBlockTime - currentTime ;
const tx = await client . cancelOrder (
subaccount ,
clientId ,
OrderFlags . LONG_TERM , // Order flag
"ETH-USD" ,
0 , // goodTilBlock (0 for long-term)
timeUntilExpiration + 60 // Add 60 seconds buffer
);
console . log ( "✅ Order cancelled!" );
console . log ( "TX Hash:" , Buffer . from ( tx . hash ). toString ( "hex" ));
}
To cancel an order, you need the original clientId and order flags. Store
these when placing orders.
Querying Orders
Get All Orders for Subaccount
async function getUserOrders (
client : CompositeClient ,
address : string ,
parentSubaccountNumber : number = 0
) {
const orders = await client . indexerClient . account . get (
"/v4/orders/parentSubaccountNumber" ,
{
address ,
parentSubaccountNumber ,
returnLatestOrders: true ,
}
);
for ( const order of orders ) {
console . log ( `Order ${ order . id } :` );
console . log ( ` Market: ${ order . ticker } ` );
console . log ( ` Side: ${ order . side } ` );
console . log ( ` Price: $ ${ order . price } ` );
console . log ( ` Size: ${ order . size } ` );
console . log ( ` Filled: ${ order . totalFilled } ` );
console . log ( ` Status: ${ order . status } ` );
}
return orders ;
}
Filter Orders by Market
const ethOrders = await client . indexerClient . account . get (
"/v4/orders/parentSubaccountNumber" ,
{
address: wallet . address ,
parentSubaccountNumber: 0 ,
ticker: "ETH-USD" , // Filter by market
returnLatestOrders: true ,
}
);
Filter Orders by Status
import { OrderStatus } from "@oraichain/lfg-client-js" ;
// Get only open orders
const openOrders = await client . indexerClient . account . get (
"/v4/orders/parentSubaccountNumber" ,
{
address: wallet . address ,
parentSubaccountNumber: 0 ,
status: OrderStatus . OPEN , // Filter by status
returnLatestOrders: true ,
}
);
Get Order by ID
async function getOrderById ( client : CompositeClient , orderId : string ) {
const order = await client . indexerClient . account . getOrder ( orderId );
console . log ( "Order Details:" );
console . log ( ` ID: ${ order . id } ` );
console . log ( ` Client ID: ${ order . clientId } ` );
console . log ( ` Market: ${ order . ticker } ` );
console . log ( ` Side: ${ order . side } ` );
console . log ( ` Type: ${ order . type } ` );
console . log ( ` Price: $ ${ order . price } ` );
console . log ( ` Size: ${ order . size } ` );
console . log ( ` Filled: ${ order . totalFilled } ` );
console . log ( ` Status: ${ order . status } ` );
console . log ( ` Created: ${ order . createdAt } ` );
return order ;
}
Advanced Trading Strategies
Grid Trading Bot
async function setupGridOrders (
client : CompositeClient ,
wallet : LocalWallet ,
market : string ,
basePrice : number ,
gridLevels : number ,
gridSpacing : number ,
orderSize : number
) {
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
const currentBlock = await client . validatorClient . get . latestBlockHeight ();
const goodTilBlock = currentBlock + 100 ;
const orders = [];
// Place buy orders below base price
for ( let i = 1 ; i <= gridLevels ; i ++ ) {
const price = basePrice * ( 1 - gridSpacing * i );
const clientId = Math . floor ( Math . random () * 100000000 );
const tx = await client . placeShortTermOrder (
subaccount ,
market ,
OrderSide . BUY ,
price ,
orderSize ,
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
false
);
orders . push ({ side: "BUY" , price , clientId , txHash: tx . hash });
}
// Place sell orders above base price
for ( let i = 1 ; i <= gridLevels ; i ++ ) {
const price = basePrice * ( 1 + gridSpacing * i );
const clientId = Math . floor ( Math . random () * 100000000 );
const tx = await client . placeShortTermOrder (
subaccount ,
market ,
OrderSide . SELL ,
price ,
orderSize ,
clientId ,
goodTilBlock ,
Order_TimeInForce . TIME_IN_FORCE_UNSPECIFIED ,
false
);
orders . push ({ side: "SELL" , price , clientId , txHash: tx . hash });
}
console . log ( `✅ Placed ${ orders . length } grid orders` );
return orders ;
}
// Usage
await setupGridOrders (
client ,
wallet ,
"ETH-USD" ,
3800 , // Base price
5 , // 5 levels on each side
0.01 , // 1% spacing
0.01 // 0.01 ETH per order
);
Stop-Loss Order
async function placeStopLossOrder (
client : CompositeClient ,
wallet : LocalWallet ,
market : string ,
stopPrice : number ,
size : number
) {
const subaccount = SubaccountInfo . forLocalWallet ( wallet , 0 );
const clientId = Math . floor ( Math . random () * 100000000 );
const tx = await client . placeOrder (
subaccount ,
market ,
OrderType . STOP_MARKET , // Stop order
OrderSide . SELL , // Sell to close long
stopPrice , // Trigger price
size ,
clientId ,
OrderTimeInForce . GTT ,
7 * 24 * 60 * 60 , // Valid for 7 days
OrderExecution . DEFAULT ,
false ,
true , // Reduce-only
stopPrice // triggerPrice parameter
);
console . log ( "✅ Stop-loss order placed at $" + stopPrice );
return tx ;
}
Best Practices
Generate Unique Client IDs
Always use unique client IDs for each order: const clientId = Date . now () * 1000 + Math . floor ( Math . random () * 1000 );
Or use a counter with your bot instance: let orderCounter = 0 ;
const clientId = orderCounter ++ ;
Short-term orders expire quickly. Always check if enough blocks remain: const currentBlock = await client . validatorClient . get . latestBlockHeight ();
const minBlocksNeeded = 10 ;
if ( goodTilBlock < currentBlock + minBlocksNeeded ) {
// Order would expire too soon, refresh goodTilBlock
goodTilBlock = currentBlock + 20 ;
}
Implement Order Retry Logic
Network issues can cause order placement to fail: async function placeOrderWithRetry (
maxRetries : number = 3
) : Promise < any > {
for ( let i = 0 ; i < maxRetries ; i ++ ) {
try {
return await client . placeShortTermOrder ( /* ... */ );
} catch ( error ) {
if ( i === maxRetries - 1 ) throw error ;
await new Promise ( resolve => setTimeout ( resolve , 1000 * ( i + 1 )));
}
}
}
Next Steps