Market Making API

API documentation for private market makers

Getting Started

1. Connecting to the Maker API

  • Production: wss://api.liquorice.tech/v1/maker/ws

Provide the following headers in the WebSocket request

  • maker - name of the Market Maker

  • authorization - authorization token

The WebSocket server sends Ping message every 30 seconds.

2. Receiving RFQ

Whenever a trader requests a quote, the Liquorice system forwards it to the chosen Market Makers using the following message

{
  "messageType": "rfq";
  "message": {
    /// Chain ID, e.g for ArbitrumOne chainId = 42161
    chainId: number;
    /// UUID of the RFQ
    rfqId: string;
    /// Hex-encoded 32-byte nonce
    nonce: string;
    /// 0x-prefixed base token address
    baseToken: string;
    /// 0x-prefixed quote token address
    quoteToken: string;
    /// 0x-prefixed address of the account receiving the quoteToken   
    trader: string;
    /// 0x-prefixed address of the account performing the 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 will be present. Having baseTokenAmount present is equivalent to asking "How much ETH would I get if I pay 1000 USDC?" Having quoteTokenAmount is equivalent to asking "How much USDC do I need to pay to get 1 ETH?"

3. Providing signed Quote

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

{
  "messageType": "rfqQuote",
  "message": {
    /// UUID of the RFQ (must match rfqId of the received rfq message)
    rfqId: string;
    /// Quote expiry UNIX timestamp (seconds)
    quoteExpiry: number;
    /// 0x-prefixed address of the Liquorice market chosen to settle the trade
    market: string,
    /// 0x-prefixed address of the baseToken recipient
    recipient: string,
    /// 0x-prefixed address of the quote signer
    signer: 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;
    /// 0x-prefixed quote signature
    signature: string;
  }
}

3.1 Quote signature schema

Solidity code that generates signing payload looks as follows:

keccak256(
  abi.encodePacked(
    address(this),
    block.chainid,
    _order.rfqId,
    _order.nonce,
    _order.trader,
    _order.effectiveTrader,
    _order.baseToken,
    _order.quoteToken,
    _order.baseTokenAmount,
    _order.quoteTokenAmount,
    _order.quoteExpiry,
    _order.recipient
  )
)

address(this) refers to the address of the selected Liquorice market (corresponding to the market field in the rfqQuote)

Contracts use the EIP-191 standard, which prepends hash with the string \x19Ethereum Signed Message:\n32 before verifying the signature. Most signing libraries handle this automatically (e.g. hashMessage function in ethers.js does this)

Full form of the payload to be verified:

keccak256(
  abi.encodePacked(
    '\x19Ethereum Signed Message:\n32',
    keccak256(
      abi.encodePacked(
        address(this),
        block.chainid,
        _order.rfqId,
        _order.nonce,
        _order.trader,
        _order.effectiveTrader,
        _order.baseToken,
        _order.quoteToken,
        _order.baseTokenAmount,
        _order.quoteTokenAmount,
        _order.quoteExpiry,
        _order.recipient
      )
    )
  )
)

The above code results in a 32-byte keccak hash. This hash must be signed with the Private Key that corresponds to the signer address in rfqQuote.

Below is an example of how to generate the hash in TypeScript using ethers.js v6

import { solidityPackedKeccak256 } from 'ethers';

static hashRFQQuote(
  quote: UnsignedRFQQuote,
  chainId: number
): string {
  return solidityPackedKeccak256(
    [
      'address',
      'uint256',
      'string',
      'uint256',
      'address',
      'address',
      'address',
      'address',
      'uint256',
      'uint256',
      'uint256',
      'address',
    ],
    [
      quote.market,
      chainId
      quote.rfqId,
      quote.nonce,
      quote.trader,
      quote.effectiveTrader,
      quote.baseToken,
      quote.quoteToken,
      quote.baseTokenAmount,
      quote.quoteTokenAmount,
      quote.quoteExpiry,
      quote.recipient,
    ]
  );
}

Last updated