💸Contract Interaction

Use the Lighter contracts to perform swaps, and create, update, or cancel orders.

Start with the Lighter Python client for quick setup. If you want to make your own smart contract and connect with the Lighter protocol, check out the Lighter Examples repository. It has a Hardhat project with contracts that work with the DEX. This project includes unit tests for the forked Arbitrum Mainnet. If that doesn't fit your needs, we have Python and TypeScript examples with web3 libraries below.

Interacting with Order Book Contracts

From Lighter V2 onwards, you can talk directly to the order books. To do this, make sure your contract uses the ILighterV2TransferCallback interface. You won't need approvals to interact with order books, but you will need to set up your own smart wallet that has the right interface, such as the Market Making Wallet from the examples repository.

A big plus of this direct approach is the chance to make Performance Limit Orders. These use less gas than the usual limit orders. Why? Because the token transfer happens right in the order book, so tokens stay put. To kick off a Performance order, top up the order book contract first. When you make an order, it uses the tokens in the order book. Once your order finds a match, the tokens land back in the order book for you and are ready for future trades.

Interacting with Router Contract

The easiest way to work with the Lighter DEX is using the Router contract. With the Router, you can Create, Update, and Cancel orders. Plus, you can do swaps and even multi-swaps that use several order books, like changing ETH to WBTC in two steps: ETH -> USDC -> WBTC.

Remember, before using the Router contract, you need to give it approval to use your tokens. For a multi-swap like ETH -> USDC -> WBTC, you only need to okay the use of WETH token. No need to approve USDC.

Single Swap

With Lighter V2, we introduced Single and Multi Swaps. These give users an easy way to trade on the exchange, similar to how AMMs work. These swaps have no extra fees and use the same available liquidity as regular orders. If you want to exchange ETH for USDC at the current rate, doing a swap is straightforward. If you want more options, you could use an Immediate or Cancel order or a Fill or Kill order.

To clarify: In a swap, you decide how much ETH you want to sell and the least USDC you'll take for it. This gives you a general idea of the rate but doesn't fix a specific rate for each ETH unit. In contrast, Immediate or Cancel orders are like saying "I want to sell up to 2 ETH, and receive at least $2000 for each unit."

Single Swaps come in 2 different flavours: exact-input and exact-output. In the case of swap exact input, you specify the fixed amount that you'll depart from, for example, 1 ETH, and you need to provide the min amount of tokens that you'll receive, for example, 1990 USDC. For the swap exact output, you specify the fixed amount of tokens that you want to receive, for example, 2000 USDC and specify the maximum amount of ETH that you'll pay for that, like 1.05. Of course, swaps work the other way around as well, if you want to buy ETH with USDC.

/// @notice Performs swap in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param isAsk Whether the order is an ask order
/// @param exactInput exactInput to pay for the swap (can be token0 or token1 based on isAsk)
/// @param minOutput Minimum output amount expected to recieve from swap (can be token0 or token1 based on isAsk)
/// @param recipient The address of the recipient of the output
/// @param unwrap Boolean indicator wheter to unwrap the wrapped native token output or not
/// @dev Unwrap is only applicable if native wrapped token is the output token
/// @return swappedInput The amount of input taker paid for the swap
/// @return swappedOutput The amount of output taker received from the swap
function swapExactInputSingle(
    uint8 orderBookId,
    bool isAsk,
    uint256 exactInput,
    uint256 minOutput,
    address recipient,
    bool unwrap
) external payable returns (uint256 swappedInput, uint256 swappedOutput);

Multi Swap

Multi Swaps are like an upgraded version of single swaps. While a single swap works with one order book, Multi Swaps can work with several at once. Let's say you want to change ETH to WBTC, but there's no direct way to do it. Normally, you'd first swap ETH for USDC, then USDC for WBTC.

But with Multi Swaps, it's simpler. Using swapExactInputMulti, you tell how much ETH you want to give and the least WBTC you want. Or with swapExactOutputMulti, you decide on the WBTC you want and the most ETH you'd give for it. This way, you don't have to think about the USDC part.

Like the single version, Multi Swaps also come in two kinds. But here, we're just talking about swapExactInputMulti as an example.

/// @notice Structure to represent a swap request.
struct SwapRequest {
    bool isAsk; // Whether the order is an ask order
    uint8 orderBookId; // The unique identifier of the order book associated with the swap request
}

/// @notice Structure to represent a multi-path swapExactInput request.
struct MultiPathExactInputRequest {
    SwapRequest[] swapRequests; // Array of swap requests defining the sequence of swaps to be executed
    uint256 exactInput; // exactInput to pay for the first swap in the sequence
    uint256 minOutput; // Minimum output amount expected to recieve from last swap in the sequence
    address recipient; // The address of the recipient of the output
    bool unwrap; // Boolean indicator wheter to unwrap the wrapped native token output or not
}

/// @notice Performs a multi path exact input swap
/// @param multiPathExactInputRequest The input request containing swap details
/// @return swappedInput The amount of input taker paid for the swap
/// @return swappedOutput The amount of output taker received from the swap
function swapExactInputMulti(
    MultiPathExactInputRequest memory multiPathExactInputRequest
) external payable returns (uint256 swappedInput, uint256 swappedOutput);

Create Limit Order

Single or batch limit orders can be created in a transaction.

/// @notice Creates a limit order in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param amount0Base The amount of token0 in terms of number of sizeTicks.
/// The exact amount of token0 for each order will be amount0Base * sizeTick
/// @param priceBase The price of the token0. Exact price for unit token0 is calculated as priceBase[i] * priceTick
/// @param isAsk Whether the order is an ask order
/// @param hintId Where to insert the order in the given order book, meant to be calculated
/// off-chain using the suggestHintId function
/// @return orderId The id of the created order
function createLimitOrder(
    uint8 orderBookId,
    uint64 amount0Base,
    uint64 priceBase,
    bool isAsk,
    uint32 hintId
) external returns (uint32 orderId);


/// @notice Creates multiple limit orders in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param size The number of limit orders to create. Size of each array given should be equal to `size`
/// @param amount0Base The amount of token0 for each limit order in terms of number of sizeTicks.
/// The exact amount of token0 for each order will be amount0Base[i] * sizeTick
/// @param priceBase The price of the token0 for each limit order.
/// Exact price for unit token0 is calculated as priceBase[i] * priceTick
/// @param isAsk Whether each order is an ask order
/// @param hintId Where to insert each order in the given order book. Meant to be calculated
/// off-chain using the suggestHintId function
/// @return orderId The id of the each created order
function createLimitOrderBatch(
    uint8 orderBookId,
    uint8 size,
    uint64[] memory amount0Base,
    uint64[] memory priceBase,
    bool[] memory isAsk,
    uint32[] memory hintId
) external returns (uint32[] memory orderId);

Update Limit Order

Single or multiple limit orders can be updated in a transaction.

/// @notice Cancels a limit order in the given order book and creates a new one with given parameters
/// @param orderBookId The unique identifier of the order book
/// @param orderId The id of the order to update
/// @param newAmount0Base The amount of token0 for updated limit order in terms of number of sizeTicks.
/// The exact amount of token0 will be newAmount0Base * sizeTick
/// @param newPriceBase The new price of the token0 for updated limit order.
/// Exact price for unit token0 is calculated as newPriceBase * priceTick
/// @param hintId Where to insert the updated order in the given order book. Meant to
/// be calculated off-chain using the suggestHintId function
/// @return newOrderId The new id of the updated order
function updateLimitOrder(
    uint8 orderBookId,
    uint32 orderId,
    uint64 newAmount0Base,
    uint64 newPriceBase,
    uint32 hintId
) external returns (uint32 newOrderId);


/// @notice Update multiple limit orders in the order book
/// @notice Cancels and creates multiple limit orders in the given order book with given parameters
/// @param orderBookId The unique identifier of the order book
/// @param size The number of limit orders to update. Size of each array given should be equal to `size`
/// @param orderId Id of the each order to update
/// @param newAmount0Base The amount of token0 for each updated limit order in terms of number of sizeTicks.
/// The exact amount of token0 for each order will be newAmount0Base[i] * sizeTick
/// @param newPriceBase The new price of the token0 for each limit order.
/// Exact price for unit token0 is calculated as newPriceBase[i] * priceTick
/// @param hintId Where to insert each updated order in the given order book. Meant to be calculated
/// off-chain using the suggestHintId function
/// @return newOrderId The new id of the each updated order
function updateLimitOrderBatch(
    uint8 orderBookId,
    uint8 size,
    uint32[] memory orderId,
    uint64[] memory newAmount0Base,
    uint64[] memory newPriceBase,
    uint32[] memory hintId
) external returns (uint32[] memory newOrderId);

Cancel Limit Order

Single or multiple limit orders can be canceled in a transaction.

/// @notice Cancels a limit order in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param orderId The id of the order to cancel
/// @return isCanceled A boolean indicating whether the order was successfully canceled
function cancelLimitOrder(uint8 orderBookId, uint32 orderId) external returns (bool);

/// @notice Cancels multiple limit orders in the given order book
/// @dev Including an inactive order in the batch cancelation does not
/// revert the entire transaction, function returns false for that order
/// @param orderBookId The unique identifier of the order book
/// @param size The number of limit orders to cancel. Size of each array given should be equal to `size`
/// @param orderId The id for each limit order to cancel
/// @return isCanceled List of booleans indicating whether each order was successfully canceled
function cancelLimitOrderBatch(
    uint8 orderBookId,
    uint8 size,
    uint32[] memory orderId
) external returns (bool[] memory isCanceled);

Create Immediate or Cancel Order (IoC)

If an IoC order runs and isn't filled on the spot, the rest of the order is canceled and the trades are finalized. This contrasts with Limit orders, which stay in the order book, and FoK orders which revert the trades.

/// @notice Creates an immediate or cancel order in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param amount0Base The amount of token0 in terms of number of sizeTicks.
/// The exact amount of token0 for each order will be amount0Base * sizeTick
/// @param priceBase The price of the token0. Exact price for unit token0 is calculated as priceBase[i] * priceTick
/// @param isAsk Whether the order is an ask order
/// @return orderId The id of the created order
function createIoCOrder(
    uint8 orderBookId,
    uint64 amount0Base,
    uint64 priceBase,
    bool isAsk
) external returns (uint32 orderId);

Create Fill or Kill Order (FoK)

If a FoK order runs and isn't filled on the spot, the transaction gets canceled and no trades are made. This contrasts with Limit orders, which stay in the order book, and IoC orders which complete trades but don't stay in the order book.

/// @notice Creates a fill or kill order in the given order book
/// @param orderBookId The unique identifier of the order book
/// @param amount0Base The amount of token0 in terms of number of sizeTicks.
/// The exact amount of token0 for each order will be amount0Base * sizeTick
/// @param priceBase The price of the token0. Exact price for unit token0 is calculated as priceBase[i] * priceTick
/// @param isAsk Whether the order is an ask order
/// @return orderId The id of the created order
function createFoKOrder(
    uint8 orderBookId,
    uint64 amount0Base,
    uint64 priceBase,
    bool isAsk
) external returns (uint32 orderId);

L2 Calldata Optimization

Even though L2 has low gas costs, it sends transaction details to L1, including all the calldata, to ensure data is accessible. Since L1 gas costs can be high, it's beneficial to keep the calldata brief to cut down on fees. For more on calldata compression, check out this article.

To reduce calldata size, the Lighter SDK doesn't call a contract function based on its ABI directly. Instead, it uses the fallback function. This is because the ABI rule pads arguments to 32-byte chunks. So, a small boolean variable, needing just 1 byte, still uses up 32 bytes of calldata.

Lighter's calldata compression employs various techniques, including representing numbers in unique formats for added compression. Because of its complexity, we couldn't squeeze the entire implementation into these docs. But, we've shared the reference calldata compression code in TypeScript for easy standalone use in any project. The Python version will come very soon.

The TypeScript SDK extends the calldata compression and introduces features including event parsing, types for on-chain view functions, like fetching Orders, and order book configurations. Additionally, it comes pre-configured with all necessary addresses for integration across all supported blockchains.

If you plan to incorporate the SDK into your project, the hardhat tasks available in the examples repository serve as excellent guides on querying the Order Books, creating limit orders, and executing swaps.

As a reminder, the Lighter Python client takes advantage of these optimizations out of the box and comes with pre-configured addresses for each chain as well.

Create Limit Order

Below you can find some examples of how to create a limit order in TypeScript, assuming you first copied the contents of the reference implementation.

const contract = new web3.eth.Contract(routerAbi, routerAddress);
const hintRes = await contract.methods
  .suggestHintId(orderbookId, priceBase, isAsk)
  .call();

const data = getCreateLimitOrderFallbackData(
  orderbookId,
  OrderType.LimitOrder,
  amount0Base,
  priceBase,
  isAsk
);

const txn = await web3.eth
  .sendTransaction({
    to: routerAddress,
    from,
    data,
    gas: 400000,
  })
  .on('transactionHash', (txnHash: string) => {
    if (txnHashCallback) txnHashCallback(txnHash);
  })
  .catch((e: any) => {
    console.log(e);
  });

Last updated