# Basic Market Making API

## 1. Getting started

### Connecting to maker API

* **Production**: `wss://api.liquorice.tech/v1/maker/ws`

Include the following headers with the WebSocket request

* `maker` - name of the Market Maker
* `authorization` - authorization token
* `index` (optional) - index of the connected client (between 0 and 9). \
  Allows multiple connections with the same credentials.

The WebSocket server sends `Ping` message every 30 seconds.

## 2. Managing token approvals

### Standard approvals

Token approval must be granted solely to the Liquorice Balance Manager [smart contract address](https://liquorice.gitbook.io/liquorice-docs/links/smart-contracts). Giving standard approval is the most gas efficient way to trade.\
\ <mark style="color:red;">Important note:</mark>  providing approval to any other Liquorice address outside of Balance Manager may compromise the security of your funds, as it does not guarantee the same level of protection. Approvals should be given strictly to the Balance Manager address.&#x20;

### Permit2

Liquorice integrates with the [Uniswap Permit2](https://docs.uniswap.org/contracts/permit2/overview) contract.

To use Permit2, first grant standard token approval to address: `0x000000000022d473030f116ddee9f6b43ac78ba3`

## 3. Publishing Price Levels

Maker needs to publish indicative quotes (price levels) to the API. These should be published **for every supported pair**. Note that `WETH/USDC` and `USDC/WETH` are considered different pairs and should be published separately.

Published price levels will expire in 30 seconds. Desired frequency of update is no more than once every second.

Schema

```typescript
{
  messageType: "priceLevels",
  message: {
    chainId: 1,
    baseToken: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
    quoteToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
    levels: [
      ["2999.5", "0.5"],
      ["3000.5", "1"],
      ["3002.0", "2"]
    ]
  }
}
```

Assuming that trader wants to swap `2 WETH` for `USDC`, the maker would be buying `0.5 WETH` for `2999.5 USDC`, `1 WETH` for `3000.5 USDC`, and another `0.5 WETH` for `3002.0 USDC`&#x20;

## 4. Receiving RFQ

When a trader or intent system requests a quote, Liquorice forwards the request to market makers using the following format:

```typescript
{
  messageType: "rfq";
  message: {
    /// Chain ID (e.g for ArbitrumOne chainId = 42161)
    chainId: number;
    /// Name of the solver requesting the quote
    solver: string;type
    /// UUID of the originating solver RFQ
    solverRfqId: string;
    /// UUID of the RFQ
    rfqId: string;
    /// Hex-encoded 32-byte nonce
    nonce: string;
    /// Address of token to be sent by trader
    baseToken: string;
    /// Address of the token to be received by trader
    quoteToken: string;
    /// Address of the account receiving quoteToken   
    trader: string;
    /// Address of the account sending baseToken
    effectiveTrader: string;
    /// Note: Exactly one of the following two fields must be present
    /// Amount of baseToken with up to 18 decimal places, e.g. 100000000 (1 WBTC)
    baseTokenAmount?: string;
    /// Amount of quoteToken with up to 18 decimal places
    quoteTokenAmount?: string;
    /// Optional metadata that provides context about the intent origin
    intentMetadata?: {
        source: 'cow_protocol';
        content: { 
            /// CoW auction ID
            auctionId: number;
        }
    }
  };
}
```

* The RFQ will include either `baseTokenAmount` or `quoteTokenAmount`, but not both.
  * When `baseTokenAmount` is specified, the `trader` (order initiator) is requesting the amount of `quoteToken` they would receive in exchange for a specific amount of `baseToken`.
  * When `quoteTokenAmount` is specified, the `trader` (order initiator) is requesting the amount of `baseToken` they need to provide to receive a specific amount of `quoteToken`.
* `intentMetadata` - For the moment, only CoW Protocol is supported. If this parameter is absent, it means that the quote is requested for another intent system.

## 5. Providing Quote

Send the quote response through the same WebSocket connection using the following format:

```typescript
{
  messageType: "rfqQuote",
  message: {
    /// UUID of the RFQ (must match rfqId of the received RFQ message)
    rfqId: string,
    /// Quote levels
    levels: [{...}]
  }
}
```

Market makers can provide multiple quote levels with different amounts, but the quoted amounts must not exceed those specified in the RFQ.

**Example**\
For an RFQ where:

* baseToken = ETH
* quoteToken = USDT
* baseTokenAmount = 1 ETH

The resulting quote may contain these levels:&#x20;

Level 0: Exchange 1 ETH for 3,000 USDT&#x20;

Level 1: Exchange 0.5 ETH for 1,502 USDT&#x20;

Level 2: Exchange 0.1 ETH for 310 USDT

### Quote level Lite

Use this type of quote level for simple and gas-efficient swaps.

**Note**: supports only Standard approvals

```typescript
{
    /// Type identifier for the quote level
    type: "lite",
    /// Quote level expiration UNIX timestamp (seconds)
    expiry: number,
    /// Address of the LiquoriceSettlement contract
    settlementContract: string,
    /// Address of the signign account.
    /// The quoteToken will be transferred from this address during the settlement
    signer: string,
    /// Optional address of the baseToken recipieint
    /// If omitted, the signer address will be used as the recipient
    recipient?: string,
    /// Address of token to be sent by trader
    baseToken: string,
    /// Address of the token to be received by trader
    quoteToken: string,
    /// Amount of baseToken with up to 18 decimal places, e.g. 100000000 (1 WBTC)
    baseTokenAmount: string,
    /// Amount of quoteToken with up to 18 decimal places
    quoteTokenAmount: string,
    /// Minimum amount of quoteToken to receive for partial fills,
    /// with up to 18 decimal places
    minQuoteTokenAmount?: string,
    /// 0x-prefixed signature for this quote level
    signature: string,
}
```

<details>

<summary>Signature schema</summary>

The following Solidity code demonstrates how to create an EIP-712 signature by hashing RFQ and quote level data.

To implement this in your preferred programming language, port the code below and populate values from the corresponding RFQ and quote level objects.

```solidity
bytes32 constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
bytes32 constant SINGLE_ORDER_TYPEHASH = keccak256('Single(string,uint256,address,address,address,address,uint256,uint256,uint256,uint256,address)');

bytes32 constant DOMAIN_NAME = keccak256('LiquoriceSettlement');
bytes32 constant DOMAIN_VERSION = keccak256('1');
bytes32 constant DOMAIN_SEPARATOR = keccak256(
  abi.encode(
    EIP712_DOMAIN_TYPEHASH,
    DOMAIN_NAME,
    DOMAIN_VERSION,
    block.chainid, // rfq.chainId
    address(this)  // quoteLevel.settlementContract
  )
);

bytes32 hash = keccak256(
 abi.encodePacked(
   '\x19\x01',
   DOMAIN_SEPARATOR,
   keccak256(
     // avoiding stack too deep
     bytes.concat(
       abi.encode(
         SINGLE_ORDER_TYPEHASH,
         keccak256(abi.encode(_order.rfqId)),   // rfq.rfqId
         _order.nonce,                          // rfq.nonce 
         _order.trader                          // rfq.trader
       ),
       abi.encode(
         _order.effectiveTrader,                // rfq.effectiveTrader
         _order.baseToken,                      // quoteLevel.baseToken
         _order.quoteToken,                     // quoteLevel.quoteToken
         _order.baseTokenAmount,                // quoteLevel.baseTokenAmount
         _order.quoteTokenAmount,               // quoteLevel.quoteTokenAmount
         _order.minFillAmount,                  // quoteLevel.minQuoteTokenAmount
         _order.quoteExpiry,                    // quoteLevel.expiry
         _order.recipient                       // quoteLevel.recipient
       )
     )
   )
 )
);
```

Sign the resulting hash with the signer's private key.

**Check yourself**

Use the following example to verify your hashing implementation. The RFQ and quote level payloads below should produce this hash:

`0x2342c2e81befd9dda11c9e769d6d867e347d5b84a0137bf9fa31acbe7ee4f5ac`

```json
// RFQ part of the hash
{
  "baseToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
  "baseTokenAmount": "1",
  "chainId": 42161,
  "effectiveTrader": "0x033F42e758cEbEbC70Ee147F56ff92C9f7CA45F4",
  "nonce": "0000000000000000000000000000000000000000000000000000000000000000",
  "quoteToken": "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
  "rfqId": "c99d2e3f-702b-49c9-8bb8-43775770f2f3",
  "trader": "0x48426Ef27C3555D44DACDD647D8f9bd0A7C06155"
}

// Quote level part of the hash
{
  "baseToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
  "baseTokenAmount": "1",
  "expiry": 1715787259,
  "minQuoteTokenAmount": "1",
  "quoteToken": "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
  "quoteTokenAmount": "2",
  "recipient": "0x033F42e758cEbEbC70Ee147F56ff92C9f7CA45F4",
  "settlementContract": "0x48426Ef27C3555D44DACDD647D8f9bd0A7C06155",
}
```

</details>

### Quote level Extended

Use this quote level type for customizations that require hooks and interactions.

It supports both Standard and Permit2 approvals.

```typescript
{
    /// Type identifier for the quote level
    type: "ext",
    /// Quote level expiration UNIX timestamp (seconds)
    expiry: number,
    /// Address of the LiquoriceSettlement contract
    settlementContract: string,
    /// Address of the LendingPool contract
    /// This field is optional and must be provided only when quote 
    /// presumes interaction with the lending pool
    lendingPoolContract?: string,
    /// Address of the signign account.
    /// The quoteToken will be transferred from this address during the settlement
    signer: string,
    /// Optional address of the baseToken recipieint
    /// If omitted, system will assume that `signer` is the baseToken recipient
    recipient?: string,
    /// Data of token to be sent by trader
    baseTokenData: {
        /// Token address
        address: string,
        /// Total amount of baseToken to take from effectiveTrader,
        /// with up to 18 decimal places
        amount: string,     
        /// Part of the amount to transfer to the recipieint
        toRecipient?: string,
        /// Part of the amount to repay token debt with
        toRepay?: string,
        /// Part of the amount to supply as collateral
        toSupply?: string, 
    },
    /// Data of token to be received by trader
    quoteTokenData: {
        /// Token address
        address: string,
        /// Total amount of quoteToken to take from signer,
        /// with up to 18 decimal places
        amount: string,
        /// Minimum amount of quoteToken to take for partial fills,
        minAmount?: string,     
        /// Part of the amount to be transferred from `signer`
        toTrader?: string,
        /// Part of the amount to be filled by withdrawing collateral
        toWithdraw?: string,
        /// Part of the amount to be filled by borrowing
        toBorrow?: string,
    },
    // Optional before hooks to execute on settlement
    beforeHooks?: [{
        to: string,
        calldata: string,
        value": "string,
    }],
    // Optional interactions to execute on settlement
    interactions?: [{
        to: string,
        calldata: string,
        value": "string,
    }],
    // Optional after hooks to execute on settlement
    afterHooks?: [{
        to: string,
        calldata: string,
        value": "string,
    }],
    /// Token approval type: "Standard" or "Permit2".
    /// If omitted, standard approval will be used
    approvalType?: string,
    /// 0x-prefixed signature for this quote level
    signature: string,
}



```

<details>

<summary>Signature schema</summary>

The following Solidity code demonstrates how to create an EIP-712 signature by hashing RFQ and quote level data.

To implement this in your preferred programming language, port the code below and populate values from the corresponding RFQ and quote level objects.

```solidity
bytes32 constant EIP712_DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
bytes32 constant SETTLE_ORDER_TYPEHASH = keccak256('Order(string,uint256,address,address,uint256,address,uint256,BaseTokenData,QuoteTokenData)BaseTokenData(address,uint256,uint256,uint256,uint256)QuoteTokenData(address,uint256,uint256,uint256,uint256)');
bytes32 constant BASE_TOKEN_DATA_TYPEHASH = keccak256('BaseTokenData(address,uint256,uint256,uint256,uint256)');
bytes32 constant QUOTE_TOKEN_DATA_TYPEHASH = keccak256('QuoteTokenData(address,uint256,uint256,uint256,uint256)');

bytes32 constant DOMAIN_NAME = keccak256('LiquoriceSettlement');
bytes32 constant DOMAIN_VERSION = keccak256('1');
bytes32 constant DOMAIN_SEPARATOR = keccak256(
  abi.encode(
    EIP712_DOMAIN_TYPEHASH,
    DOMAIN_NAME,
    DOMAIN_VERSION,
    block.chainid, // rfq.chainId
    address(this)  // quoteLevel.settlementContract
  )
);

// Hash base token data
bytes32 baseTokenDataHash = keccak256(
  abi.encode(
    BASE_TOKEN_DATA_TYPEHASH,
    _baseTokenData.addr,        // quoteLevel.baseTokenData.address
    _baseTokenData.amount,      // quoteLevel.baseTokenData.amount
    _baseTokenData.toRecipient, // quoteLevel.baseTokenData.toRecipient
    _baseTokenData.toRepay,     // quoteLevel.baseTokenData.toRepay                   
    _baseTokenData.toSupply     // quoteLevel.baseTokenData.toSupply
  )
);

// Hash quote token data    
bytes32 quoteTokenDataHash = keccak256(
  abi.encode(
    QUOTE_TOKEN_DATA_TYPEHASH,
    _quoteTokenData.addr,       // quoteLevel.quoteTokenData.address
    _quoteTokenData.amount,     // quoteLevel.quoteTokenData.amount
    _quoteTokenData.toTrader,   // quoteLevel.quoteTokenData.toTrader
    _quoteTokenData.toWithdraw, // quoteLevel.quoteTokenData.toWithdraw                        
    _quoteTokenData.toBorrow    // quoteLevel.quoteTokenData.toBorrow
  )
);

// Assemble resulting hash
bytes32 hash = keccak256(
  abi.encodePacked(
    '\x19\x01',
    DOMAIN_SEPARATOR,
    keccak256(
      // avoiding stack too deep
      bytes.concat(
        abi.encode(
          SETTLE_ORDER_TYPEHASH,
          keccak256(abi.encode(_order.rfqId)),  // rfq.rfqId
          _order.nonce,                         // rfq.nonce
          _order.trader,                        // rfq.trader
          _order.effectiveTrader,               // rfq.effectiveTrader
          _order.quoteExpiry,                   // quoteLevel.expiry
          _order.recipient,                     // quoteLevel.recipieint
          _order.minFillAmount                  // quoteLevel.quoteTokenData.minAmount
        ),
        baseTokenDataHash,
        quoteTokenDataHash
      )
    )
  )
);
```

Sign the resulting hash with the signer's private key.

**Check yourself**

Use the following example to verify your hashing implementation. The RFQ and quote level payloads below should produce this hash:

`0x7fedd067fe195b613ac92786b82aa93b076513c59da93becb052b54a36ccf41b`

```json
// RFQ part of the hash
{
  "baseToken": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
  "baseTokenAmount": "1",
  "chainId": 42161,
  "effectiveTrader": "0x033F42e758cEbEbC70Ee147F56ff92C9f7CA45F4",
  "nonce": "0000000000000000000000000000000000000000000000000000000000000000",
  "quoteToken": "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
  "quoteTokenAmount": null,
  "rfqId": "c99d2e3f-702b-49c9-8bb8-43775770f2f3",
  "trader": "0x48426Ef27C3555D44DACDD647D8f9bd0A7C06155"
}

// Quote level part of the hash
{
 "baseTokenData": {
   "address": "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1",
   "amount": "1",
   "toRecipient": "1",
   "toSupply": "0"
 },
 "quoteTokenData": {
   "address": "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f",
   "amount": "2",
   "minAmount": "1",
   "toTrader": "1",
   "toBorrow": "0"
 },
 "expiry": 1715787259,
 "recipient": "0x033F42e758cEbEbC70Ee147F56ff92C9f7CA45F4",
 "settlementContract": "0x48426Ef27C3555D44DACDD647D8f9bd0A7C06155",
}
```

</details>

### Using lending pools

Liquorice allows market makers to settle trades using borrowed funds, provided they have posted collateral equal to at least 25% of the quoted amount.

Consider the following example: a market maker receives an RFQ where a trader intends to exchange WETH for USDC. If the market maker does not have USDC in their inventory, they can borrow it from the lending pool—assuming they have supplied collateral (in any token) worth at least 25% of the quoted USDC amount.

For instance, if the quote is to exchange 0.3 WETH for 1,000 USDC, the market maker can post 250 USDT as collateral and borrow 1,000 USDC from the pool.

When the trade is settled, the 0.3 WETH will be added to the locked collateral alongside the market maker’s initially supplied 250 USDT. As a result, the market maker’s position in the pool will consist of 0.3 WETH and 250 USDT as locked collateral, with 1,000 USDC as outstanding debt.

To settle a trade using borrowed funds, the market maker must use the **Extended Quote** level and specify token amounts in the `toSupply` field of `baseTokenData` and the `toBorrow` field of `quoteTokenData`.

* `baseTokenData.toSupply`: the amount (e.g., 0.3 WETH) that the trader will supply and which will be added to the locked collateral on behalf of the market maker.
* `quoteTokenData.toBorrow`: the amount (e.g., 1,000 USDC) to be borrowed on behalf of the market maker and sent to the trader.

The system will automatically generate the transaction payload and forward it to the origin of the intent.

To withdraw the 0.3 WETH collateral, the market maker must first repay a sufficient portion of the 1,000 USDC debt.

### Error handling

If the quote payload is not valid, WS API will send a message in the following format

```typescript
/// Validation error
{
  /// Type of the error
  type: string,
  /// Error message
  message: string,
  /// Optional metadata
  metadata?: {
    rfq_id: string
  }
}
```

## 6. Getting quote summary

Use the following endpoint to retrieve an extended information for the provided quote. \
\
Quote summary includes information such as originating solver RFQ and resulting solver quote with the competing quote levels from other market makers, and settlement result if the resulting solver quote was used to settle the trade on-chain.

**URL:** `https://api.liquorice.tech/v1/maker/quote-summaries/{rfq-id}`

**Method:** `GET`&#x20;

<details>

<summary>Response</summary>

```json
{
    "originatingSolverRfq": {
        "receivedAt": 1749207556472,
        "rfqId": "e6125d14-989e-4d67-acb5-64c883656203",
        "intentMetadata": {
            "source": "cow_protocol",
            "content": {
                "auctionId": 10696411
            }
        }
    },
    "makerRfq": {
        "sentAt": 1749207556483,
        "solver": "solver_name",
        "nonce": "5ee0634e178b21c9524e383a155ff26b0fd4b852c7de99c8c16886a229827dba",
        "chainId": 1,
        "expiry": 1749207586000,
        "baseToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "baseTokenAmount": "100000000000",
        "quoteToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
        "quoteTokenAmount": null,
        "trader": "0xA6775ae22067D71FaE76E22Fa377Cfb38f223F0D",
        "effectiveTrader": null
    },
    "makerQuote": {
        "receivedAt": 1749207556534,
        "levels": [
            {
                "baseTokenAmount": "100000000000",
                "quoteTokenAmount": "100000000",
                "signer": "0xf7e93CC543d97af6632c9b8864417379dBa4bf15",
                "recipient": null,
                "expiry": 1749207586000,
                "minQuoteTokenAmount": null
            }
        ],
        "status": "settle",
        "transactionHash": "0x0c516419523f44ddc6b1877973d0c665734f4f7794c2749b4ec5e80821dc0358"
    },
    "resultingSolverQuote": {
        "sentAt": 1749207556542,
        "userLevels": [
            {
                "baseTokenAmount": "100000000000",
                "quoteTokenAmount": "100000000",
                "signer": "0xf7e93CC543d97af6632c9b8864417379dBa4bf15",
                "recipient": null,
                "expiry": 1749207586000,
                "minQuoteTokenAmount": null,
                "minBaseTokenAmount": null
            }
        ],
        "competingLevels": [
            {
                "baseTokenAmount": "100000000000",
                "quoteTokenAmount": "99999999",
                "expiry": 1749207586000,
                "minQuoteTokenAmount": null,
                "minBaseTokenAmount": null
            }
        ]
    },
    "settlementResult": {
        "winningSolutions": [
            {
                "transactionHash": "0xa5ec72c1fe8cfcfea410ce4a854beddb7d23492861d41435c6e892cba39db6bc",
                "orders": [
                    {
                        "baseToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                        "quoteToken": "0x539bdE0d7Dbd336b79148AA742883198BBF60342",
                        "baseTokenAmount": "56866",
                        "quoteTokenAmount": "465593933993483323"
                    },
                    {
                        "baseToken": "0x539bdE0d7Dbd336b79148AA742883198BBF60342",
                        "quoteToken": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
                        "baseTokenAmount": "465593933993483323",
                        "quoteTokenAmount": "56866"
                    }
                ]
            },
            {
                "transactionHash": "0x0c516419523f44ddc6b1877973d0c665734f4f7794c2749b4ec5e80821dc0358",
                "orders": [
                    {
                        "baseToken": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
                        "quoteToken": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
                        "baseTokenAmount": "100000000000",
                        "quoteTokenAmount": "100000000"
                    }
                ]
            }
        ]
    }
}
```

</details>

* `makerQuote.status` can be one of `live`, `settled`, `expired` or `missedDeadline`&#x20;
* field `settlementResult` shows data on on final settlement which can be used, for example, to compare price provided by you with the winning price. If this field is "null", it normally means that settlement did not occur
* field `competing levels` shows prices provided by other liquidity sources connected to Liquorice

## 7. Getting info on settled trades

When a trade executes on-chain, the [Liquorice settlement contract](https://liquorice.gitbook.io/liquorice-docs/links/smart-contracts) emits a TraderOrder event containing information about transferred amounts. The event reflects actual token transfers, including cases of partial fills, and always shows the final amounts transferred on-chain.

```typescript
event TradeOrder(
  string indexed rfqId,
  address trader,
  address effectiveTrader,
  address baseToken,
  address quoteToken,
  uint256 baseTokenAmount,
  uint256 quoteTokenAmount,
  address recipient // the same recipient address specified by market maker in the original quote 
);
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://liquorice.gitbook.io/liquorice-docs/for-market-makers/basic-market-making-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
