Place a perpetual order
This guide walks through opening a leveraged perpetual position on one of Pod's perp markets. For background, see Perpetuals and Market Configurations for the live perp market list.
Perpetual markets are quoted in USD and use cross-margin: a single USD balance serves as collateral for all open perp positions on the account. size is the order quantity in base-asset units and is signed — positive opens a long, negative opens a short. Margin is computed by the market from |size| × price / maxLeverage.
See the Orderbook precompile reference for the timestamp unit, deadline-alignment, and TTL rules that apply to every call below.
Submit a limit order for the perp market (e.g. NVDA-USD). The account needs a USD balance to cover the margin the market computes.
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://rpc.podtestnet.dev");
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const ORDERBOOK = "0x50d0000000000000000000000000000000000002";
const abi = [
"function submitOrder(bytes32 orderbookId, int256 size, uint256 price, uint8 orderType, uint128 deadline, uint128 ttl, uint8 flags)",
];
// `flags` bits — OR together the ones you want, 0 for a plain resting limit order
const REDUCE_ONLY = 0x01;
const IOC = 0x02;
const POST_ONLY = 0x04;
const orderbook = new ethers.Contract(ORDERBOOK, abi, wallet);
// USD is Pod's native token — use the canonical native-token sentinel address
const USD = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const nvdaPerpId = "0x0000000000000000000000000000000000000000000000000000000000000007"; // NVDA-USD perp (max 20x)
// Deadlines must be an exact multiple of the market's auction interval
// (500 ms on every testnet market) or validators reject the intent.
const AUCTION_INTERVAL = 500_000n; // microseconds
const deadlineAfter = (lagUs: bigint): bigint =>
((BigInt(Date.now()) * 1000n + lagUs + AUCTION_INTERVAL - 1n) / AUCTION_INTERVAL) * AUCTION_INTERVAL;
// Open a long on NVDA-USD: 5 NVDA at $140 limit
const size = ethers.parseEther("5"); // +5 NVDA long (negative = short)
const price = ethers.parseEther("140"); // limit price in USD
const orderType = 0; // 0 = Limit
const deadline = deadlineAfter(10_000_000n); // include in batches within the next ~10 seconds
const ttl = 60n * 1_000_000n;
const tx = await orderbook.submitOrder(
nvdaPerpId, size, price, orderType, deadline, ttl,
0, // flags — REDUCE_ONLY to only close, POST_ONLY to guarantee you add liquidity
);
console.log("Perp order tx:", tx.hash);Closing a position
Submit an opposite-sided order with REDUCE_ONLY (0x01) set in flags. Reduce-only orders can only decrease your existing exposure — they will be rejected if matching them would flip your position direction or open a new one.
Market-making: guarantee that you add liquidity
Set POST_ONLY (0x04) in flags to quote as a guaranteed maker. The order rests on the book as usual, but it may not trade in the batch that admitted it: if it would have taken liquidity in that batch, it is removed instead and reported with the terminal status post_only_refused. From the next batch onwards it matches like any other resting order.
POST_ONLY cannot be combined with IOC, and cannot be set on a market order — both are rejected. It also cannot be requested on a TP/SL trigger, since submitTrigger has no flags argument. See Post-only orders for the exact rules, including what happens when two post-only orders cross each other and how amendments re-arm the guarantee.
Last updated

