import { MonacoSDK } from "@0xmonaco/core";
import { createWalletClient, custom, parseUnits } from "viem";
import { seiTestnet } from "viem/chains";
async function perpFlowExample() {
// ---------------------------------------------------------------------------
// 1. Connect wallet and initialize SDK
// ---------------------------------------------------------------------------
const [walletAccount] = await window.ethereum.request({
method: "eth_requestAccounts",
});
const walletClient = createWalletClient({
account: walletAccount,
chain: seiTestnet,
transport: custom(window.ethereum),
});
const sdk = new MonacoSDK({
walletClient,
network: "staging",
seiRpcUrl: "https://evm-rpc-testnet.sei-apis.com", // public testnet RPC — use a private provider in production
});
// ---------------------------------------------------------------------------
// 2. Authenticate
// ---------------------------------------------------------------------------
const auth = await sdk.login("your-client-id");
console.log("Authenticated as", auth.user.address);
// ---------------------------------------------------------------------------
// 3. Deposit USDC into Monaco's vault (skip if collateral is already on-chain)
// ---------------------------------------------------------------------------
// Get USDC asset metadata from any pair that uses it as quote
const ethUsdcPair = await sdk.market.getTradingPairBySymbol("ETH/USDC");
if (!ethUsdcPair) throw new Error("ETH/USDC pair not found");
const usdcAssetId = ethUsdcPair.quoteAssetId;
const usdcDecimals = ethUsdcPair.quoteDecimals;
// Approve and deposit 5,000 USDC into the vault
const depositAmount = parseUnits("5000", usdcDecimals);
if (await sdk.vault.needsApproval(usdcAssetId, depositAmount)) {
await sdk.vault.approve(usdcAssetId, depositAmount);
}
await sdk.vault.deposit(usdcAssetId, depositAmount);
// ---------------------------------------------------------------------------
// 4. Find a perp trading pair
// ---------------------------------------------------------------------------
// Perp pairs are distinguished by marketType === "MARGIN"
const { tradingPairs } = await sdk.market.getPaginatedTradingPairs({
pageSize: 100,
});
const perpPair = tradingPairs.find(
(p) => p.marketType === "MARGIN" && p.baseToken === "ETH",
);
if (!perpPair) throw new Error("No ETH perp pair available");
const tradingPairId = perpPair.id;
console.log(`Trading on ${perpPair.symbol} (${tradingPairId})`);
// ---------------------------------------------------------------------------
// 5. Fund the parent margin account with 1,000 USDC.
// ---------------------------------------------------------------------------
await sdk.marginAccounts.transferCollateralToParentMarginAccount({
asset: "USDC",
amount: "1000",
});
// Verify funding succeeded without storing a margin account id.
const summary = await sdk.marginAccounts.getParentMarginAccountSummary();
console.log(`Equity: ${summary.equity}, free: ${summary.freeCollateral}`);
// ---------------------------------------------------------------------------
// 6. Pre-flight the order with simulateRiskBucketOrderRisk
// ---------------------------------------------------------------------------
// Open a 5x long ETH at $2,000 with 1.5 ETH size
const orderQuantity = "1.5";
const limitPrice = "2000";
const leverage = "5";
const risk = await sdk.marginAccounts.simulateRiskBucketOrderRisk({
tradingPairId,
side: "BUY",
orderType: "LIMIT",
price: limitPrice,
quantity: orderQuantity,
leverage,
});
if (!risk.accepted) {
console.error("Order would be rejected:", risk.rejectReason);
return;
}
console.log(
`Estimated liquidation: ${risk.estimatedLiquidationPrice}, ` +
`free after: ${risk.freeCollateralAfter}`,
);
// ---------------------------------------------------------------------------
// 7. Place the perp order
// ---------------------------------------------------------------------------
const order = await sdk.trading.placeLimitOrder(
tradingPairId,
"BUY",
orderQuantity,
limitPrice,
{
tradingMode: "MARGIN",
leverage,
},
);
console.log(`Order ${order.orderId} submitted`);
// ---------------------------------------------------------------------------
// 8. Wait for the position to open via websocket
// ---------------------------------------------------------------------------
await sdk.ws.connect();
const positionId: string = await new Promise((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error("Order didn't fill in 30s")),
30_000,
);
const unsubscribe = sdk.ws.userOrders((evt) => {
if (evt.orderId !== order.orderId) return;
if (evt.eventType === "OrderFilled" || evt.eventType === "OrderPartiallyFilled") {
clearTimeout(timeout);
unsubscribe();
// Position lookup happens after a small settle delay
setTimeout(async () => {
const { positions } = await sdk.positions.listPositions({
tradingPairId: tradingPairId,
status: "OPEN",
});
const pos = positions[0];
if (!pos) reject(new Error("Position not found after fill"));
else resolve(pos.positionId);
}, 500);
} else if (evt.eventType === "OrderRejected" || evt.eventType === "OrderCancelled") {
clearTimeout(timeout);
unsubscribe();
reject(new Error(`Order ${evt.eventType}: ${(evt.data as any).reason}`));
}
});
});
console.log(`Position opened: ${positionId}`);
// ---------------------------------------------------------------------------
// 9. Inspect live risk
// ---------------------------------------------------------------------------
const positionRisk = await sdk.positions.getPositionRisk(positionId);
console.log({
mark: positionRisk.mark_price,
liquidation: positionRisk.liquidation_price,
pnl: positionRisk.unrealized_pnl,
margin_ratio: positionRisk.margin_ratio,
});
// ---------------------------------------------------------------------------
// 10. Attach TP and SL (OCO group)
// ---------------------------------------------------------------------------
const tpSl = await sdk.positions.attachPositionTpSl(positionId, {
takeProfit: { triggerPrice: "2200", orderType: "MARKET" },
stopLoss: { triggerPrice: "1900", orderType: "MARKET" },
oco: true,
});
console.log(
`TP: ${tpSl.takeProfitOrderId}, SL: ${tpSl.stopLossOrderId}`,
);
// Subscribe to conditional-order lifecycle (optional — useful for UI updates)
const unsubConditionals = sdk.ws.conditionalOrders((evt) => {
console.log(
`Conditional ${evt.data.conditionalOrderId} → ${evt.data.state} ` +
`(${evt.data.reason})`,
);
});
// ---------------------------------------------------------------------------
// 11. Manual close (if you don't want to wait for TP/SL)
// ---------------------------------------------------------------------------
// Half close at limit
await sdk.positions.closePosition(positionId, {
closeType: "LIMIT",
limitPrice: "2050",
quantity: "0.75",
});
// Or full market close
// await sdk.positions.closePosition(positionId, { closeType: "MARKET" });
// ---------------------------------------------------------------------------
// 12. Cleanup
// ---------------------------------------------------------------------------
unsubConditionals();
sdk.ws.disconnect();
}
perpFlowExample().catch(console.error);