WebSocket API

Page explains how to use Liquorice RFQ WebSocket API

1. Connecting to the Liquorice WebSocket API

Production: wss://api.liquorice.tech/v1/solver/ws

Provide the following headers in the WebSocket request

  • solver - name of the Solver

  • authorization - authorization token

WebSocket server sends Ping message every 30 seconds.

2. Sending RFQ

Whenever the solver needs a quote from Liquorice, it sends an RFQ to the system using the following message

{
  messageType: "rfq";
  message: {
    /// Chain ID, e.g for ArbitrumOne chainId = 42161
    chainId: number;
    /// UUID of the RFQ (Must be generated by caller to be able to match incoming RFQQuote message)
    rfqId: string;
    /// RFQ expiry UNIX timestamp (seconds)
    expiry: number,
    /// 0x-prefixed base token address
    baseToken: string;
    /// 0x-prefixed quote token address
    quoteToken: string;
    /// 0x-prefixed address of the account receiving quoteToken   
    trader: string;
    /// 0x-prefixed address of the account performing trade
    effectiveTrader: string;
    // (Exactly one of the following two fields will be present.)
    /// Number with up to 18 decimal places, e.g. 100000000 (1 WBTC)
    baseTokenAmount?: string;
    /// Number with up to 18 decimal places
    quoteTokenAmount?: string;
  };
}

Exactly one of the baseTokenAmount or quoteTokenAmount fields must be present. Having baseTokenAmount present equals to asking "How much ETH would I get if I pay 1000 USDC?" and having quoteTokenAmount equals to asking "How much USDC do I need to pay to get 1 ETH?"

3. Receiving Quote

The quote message will be sent within the same WebSocket connection that received the RFQ, using following format:

{
  messageType: "rfqQuote",
  message: {
    /// UUID of the RFQ
    rfqId: string,
    /// Quote price levels
    levels: [{
      /// Hex-encoded 32-byte nonce
      nonce: string,
      /// Quote expiry UNIX timestamp (seconds)
      expiry: number,
      /// Transaction data
      tx: {
        /// 0x-prefixed address of the Liquorice settlement contract
        to: string,
        /// 0x-prefixed calldata string for the function within 
        /// Liquorice settlement contract
        data: string
      },
      /// 0x-prefixed base token address
      baseToken: string,
      /// 0x-prefixed quote token address
      quoteToken: string,
      /// Number with up to 18 decimal places, e.g. 100000000 (1 WBTC)
      baseTokenAmount: string,
      /// Number with up to 18 decimal places
      quoteTokenAmount: string,
      /// Partial fill settings.
      /// If this field is absent, it means that the order is not partially fillable
      partialFill?: {
        /// Offset of the curFillAmount argument within the tx.data (in bytes)
        offset: number,
        /// Number with up to 18 decimal places
        /// Minimal base token amount that can be filled
        minBaseTokenAmount: string
      }
    }]
  }
}

Levels array provides multiple price levels with different sizes. E.g.

  • Level 0 - 1 ETH / 3000 USDT

  • Level 1 - 0.5 ETH / 1502 USDT

  • Level 2 - 0.1 ETH / 310 USDT

IMPORTANT NOTE: normally it is assumed that solver will always pick one price level for execution. However it is techincally possible to use multiple price levels provided that they belong to different market makers. In order to use multiple price levels, make sure you pick quotes with different nonce as otherwise only one quote will pass execution.

4. Partial fills

Partial fills can be achieved by adjusting the curFillAmount argument in the tx.data. The quote level will contain partialFill.offset field which indicates how many bytes are in the tx.data before reaching curFillAmount. Also note that partial fill amount can't be less than partialFill.minBaseTokenAmount

If partialFillis absent, then this level cannot be filled partially.

let calldata = tx.data.slice(0, 10 + partialFill.offset * 2) // Calldata up to offset. 2 is for `0x` prefix and 8 for the function selector.
    + fillAmount.toString(16).padStart(64, "0") // Replace with partial fill amount in hex, padded to 64 chars
    + tx.data.slice(10 + partialFillOffset * 2 + 64); // Remaining calldata after curFillAmount

5. Error handling

Errors will be sent within the same WebSocket connection using following format:

{
  "messageType": "error",
  "message": {
    /// Type of the error
    errorType: string;
    /// Error message
    message: string;
  }
}

Last updated