πŸ“ƒLimitOrderManager contracts

The `LimitOrderManager` is a critical component of the f(x) Protocol that enables users to create and execute limit orders for position management. This system allows traders to:

  • Open new positions when market conditions meet specific price criteria

  • Close existing positions at predetermined price levels

  • Set up stop-loss and take-profit orders for risk management

  • Execute complex trading strategies programmatically

Interacting with LimitOrderManager

To interact with LimitOrderManager, send a contract call to its address.

Key Functions

fillOrder(order, signature, makingAmount, takingAmount)

Executes a limit order by filling it with the specified amounts. (taker's operation)

cancelOrder(order)

Cancels an existing order (only callable by the order maker).

getOrderDetails(order)

Returns the order details (including making/taking tokens and amounts) for an order.

getExecution(orderHash)

Returns the current execution status of an order.

getOrderHash(order)

Calculates and returns the unique hash for a given order. This hash is used as the identifier for tracking order execution status.

increaseNonce()

Increments the caller's nonce, effectively invalidating all previously signed orders with the old nonce. Useful for batch cancelling multiple orders. (Basically, it means cancelAll)

Key Concepts

Order Structure

The Order struct contains the following key fields:

struct Order {
    address maker;           // Order creator
    address pool;           // Target pool address
    uint256 positionId;     // Position ID (0 for new positions)
    bool positionSide;      // true = long, false = short
    bool orderType;         // false = limit order, true = stop order
    // orderType
    // for limit order (orderType = true)
    //   orderSide = true: open order, order can be filled only oracle price is <= triggerPrice
    //   orderSide = false: close order, order can be filled only oracle price is >= triggerPrice
    // stop order (orderType = false)
    //   always close order
    //   orderSide = true: take profit order, order can be filled only oracle price is >= triggerPrice
    //   orderSide = false: stop loss order, order can be filled only oracle price is <= triggerPrice
    bool orderType;
    bool allowPartialFill;  // Allow partial execution
    uint256 triggerPrice;   // Price trigger condition
    int256 fxUSDDelta;      // fxUSD amount change
    int256 collDelta;       // Collateral amount change
    int256 debtDelta;       // Debt amount change
    uint256 nonce;          // Maker's nonce for batch cancellation
    bytes32 salt;           // Unique order identifier
    uint256 deadline;       // Order expiration timestamp
}

Pool Address: f(x) pool addresses, for example

  • WBTCLong : 0xAB709e26Fa6B0A30c119D8c55B887DeD24952473

  • wstETHLong: 0x6Ecfa38FeE8a5277B91eFdA204c235814F0122E8

  • WBTCShort: 0xA0cC8162c523998856D59065fAa254F87D20A5b0

  • wstETHShort: 0x25707b9e6690B52C60aE6744d711cf9C1dFC1876

Making Amount vs Taking Amount

  • Making Amount: The amount of tokens the order maker is offering

  • Taking Amount: The amount of tokens the order maker wants to receive

These amounts are calculated based on the order type and side.

Order Hash

The Order Hash is a unique identifier for each order, calculated using the EIP712 standard. This hash serves as:

  • Unique Identifier: Each order has a distinct hash based on its parameters

  • Execution Tracker: Used to store and retrieve order execution status

  • Signature Target: The hash is what gets signed by the order maker

  • Database Key: Off-chain systems use this hash to index and track orders

The order hash is deterministic - the same order parameters will always produce the same hash. This allows for consistent tracking across different systems and ensures order integrity.

Signature

The Signature is a cryptographic proof that the order hash was authorized by the order maker. The signature content is the order hash (not the raw order data), ensuring any parameter changes invalidate the signature.

Signature Format:

  • EOA Signatures: 65 bytes (r, s, v) or 64 bytes (r, s) for EIP-2098 compact format

  • Smart Contract Signatures: Variable length, validated through EIP-1271 isValidSignature method

Integrate Examples:

Signature Creation Example

// EIP712 Domain for f(x) Limit Order Manager
const domain = {
    name: "f(x) Limit Order Manager",
    version: "1",
    chainId: 1, // Mainnet
    verifyingContract: limitOrderManager.address
};

// EIP712 Types for Order struct
const types = {
    Order: [
        { name: "maker", type: "address" },
        { name: "pool", type: "address" },
        { name: "positionId", type: "uint256" },
        { name: "positionSide", type: "bool" },
        { name: "orderType", type: "bool" },
        { name: "orderSide", type: "bool" },
        { name: "allowPartialFill", type: "bool" },
        { name: "triggerPrice", type: "uint256" },
        { name: "fxUSDDelta", type: "int256" },
        { name: "collDelta", type: "int256" },
        { name: "debtDelta", type: "int256" },
        { name: "nonce", type: "uint256" },
        { name: "salt", type: "bytes32" },
        { name: "deadline", type: "uint256" }
    ]
};

// Sign the order (actually signs the order hash)
async function signOrder(order, signer) {
    // _signTypedData internally calculates the order hash and signs it
    const signature = await signer._signTypedData(domain, types, order);
    return signature; // This signature proves the signer authorized this specific order hash
}

// Verify signature before using
async function verifySignature(order, signature, expectedSigner) {
    const recoveredAddress = ethers.utils.verifyTypedData(domain, types, order, signature);
    return recoveredAddress.toLowerCase() === expectedSigner.toLowerCase();
}

// Usage example
const signature = await signOrder(order, signer);
const isValid = await verifySignature(order, signature, await signer.getAddress());
console.log(`Signature valid: ${isValid}`);

How to Call fillOrder

Function Signature

function fillOrder(
    OrderLibrary.Order memory order,
    bytes memory signature,
    uint256 makingAmount,
    uint256 takingAmount
) external nonReentrant

Parameters

  • order: The order struct containing all order details

  • signature: EIP712 signature from the order maker

  • makingAmount: Amount of making tokens the filler provides

  • takingAmount: Amount of taking tokens the filler wants from the order

Prerequisites

  1. Valid Signature: The order must be properly signed by the maker

  2. Order Validation: Order parameters must pass validation checks

  3. Price Conditions: Trigger price conditions must be met (if applicable)

  4. Token Approvals: Filler must approve tokens to the LimitOrderManager

  5. Sufficient Balance: Filler must have sufficient token balance

Example Integration

// Example using ethers.js
const order = {
    maker: "0x...",
    pool: "0x...",
    positionId: 0,
    positionSide: true,
    orderType: false,
    orderSide: true,
    allowPartialFill: true,
    triggerPrice: ethers.utils.parseEther("2000"),
    fxUSDDelta: ethers.utils.parseEther("1000"),
    collDelta: ethers.utils.parseEther("0.5"),
    debtDelta: ethers.utils.parseEther("500"),
    nonce: 1,
    salt: "0x1234...",
    deadline: Math.floor(Date.now() / 1000) + 3600
};

// Get the order hash for tracking
const orderHash = await limitOrderManager.getOrderHash(order);
console.log(`Order hash: ${orderHash}`);

// Check if order has been executed before
const execution = await limitOrderManager.getExecution(orderHash);
console.log(`Order status: ${execution.status}`);

// Get order details
const [makingToken, takingToken, makingAmount, takingAmount] = 
    await limitOrderManager.getOrderDetails(order);

// Approve tokens
await IERC20(takingToken).approve(limitOrderManager.address, makingAmount);

// Fill the order
await limitOrderManager.fillOrder(
    order,
    signature,
    makingAmount,
    takingAmount
);

Last updated