DEX API
Use PMM RFQ

Use PMM RFQ#

A Request for Quote (RFQ) is a business process in which a customer requests a quote from a market maker to purchase tokens.

Integration process#

If you provide market-making services and are interested in integrating with us, follow the steps below to expedite your integration process.

  1. Please send an email to DEX-PMM@okg.com to apply for access and connect with our team.
  2. Please provide the corresponding API in accordance with our specifications. You need to ensure that the interface request address and return results comply with our relevant specifications.
  3. If you need to customize a settlement contract, click Smart contract integration to view our smart contract architecture and settlement contract interface configuration.

Please ensure that your service adheres to our specifications and requirements. If you encounter any issues during the integration process, feel free to contact us at any time.

PMM RFQ API specification#

Market makers must provide the following two interfaces:

Request price#

1. Request address#

「GET」 /levels

2. Description#

Market maker needs to provide this interface so that OKX can obtain the full amount of quotation data through this interface.

The interface needs to return currency pair information, as well as corresponding quotes and depths for each level.

3. Response example#

{
    "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2_0xdac17f958d2ee523a2206206994597c13d831ec7":
    [
     ["4083.38", "0.005"],
     ["4083.38", "1.2172"],
     ["4083.2", "0.005"],
     ["4083.2", "0.040"],
     ["4083.1", "0.1122534"],
     ["4082.78", "0.70018422"]
    ],
    "0xdac17f958d2ee523a2206206994597c13d831ec7_0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2":
    [
     ["0.000244553189095862","20.44545"],
     ["0.000244553189095862", "5471.949504"],
     ["0.0002356123123", "1000.2342"],
    ]
}

Request order and signature#

1. Request address#

「POST」 /order

2. Description#

Get a signed order for a certain amount of token pairs

3. Request parameter#

In the Request Body, OKX will pass in the following parameters:

ParameterDescription
baseTokenOrder taker token address
quoteTokenOrder maker token address
amounttakingAsset amount
takerAddress of order taker

4. Response parameter#

ParameterDescription
infoOrder information, actually using 128 bits, with the lowest 64 bits representing the order ID, and bits 64 to 128 representing the order timeout timestamp
makerAssetAddress of makerToken
takerAssetAddress of takerToken
makerAddress of order signer
allowedSenderSpecify taker address, zero address represents a public order
makingAmountTrade quantity of makerToken
takingAmountTrade quantity of takerToken
settlerAddress of settlement contract,market makers can customize the settlement processing logic. If not required, they can set a zero address
signatureOrder signing,you can get example from example of order signing

5. Request example#

{
        "baseToken": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",  //Address of takerToken
        "quoteToken": "0xdac17f958d2ee523a2206206994597c13d831ec7",  //Address of makerToken
        "amount": "6000000000000000", //Quantity of takerToken
        "taker": "taker address" //Address of order taker
}

6. Response example#

{
    "order": {
        "info": "30267040123324574557115271801", //Order information, actually using 128 bits, with the lowest 64 bits representing the order ID, and bits 64 to 128 representing the order timeout timestamp.
        "makerAsset": "0xdac17f958d2ee523a2206206994597c13d831ec7", //Address of makerToken
        "takerAsset": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", //Address of takerToken
        "maker": "marker address", //Address of order signer
        "allowedSender": "0x0000000000000000000000000000000000000000", //Specify Taker address, zero address represents a public order.
        "makingAmount": "22723800",    //Trade quantity of makerToken
        "takingAmount": "6000000000000000",    //Trade quantity of takerToken
        "settler": "0x0000000000000000000000000000000000000000"  //Address of Settlement contract,market makers can customize the settlement processing logic. If not required, they can set a zero address.
    },
    "signature": "0xc64bf62b7619edda019fe491da256b9fbe892fbfeac91f9d1fce168478ad53053dde038584f063fe21e267fcb4e758bcf420036cd2838fe5cbd993ec6d3dde561b"
}


7. Example of order signing#

Orders are signed by EIP-712. Users can view the signature content when signing.

const name = 'OKX PMM Protocol';
const version = '1.0';

const OrderRFQ = [
    { name: 'info', type: 'uint256' },
    { name: 'makerAsset', type: 'address' },
    { name: 'takerAsset', type: 'address' },
    { name: 'maker', type: 'address' },
    { name: 'allowedSender', type: 'address' },
    { name: 'makingAmount', type: 'uint256' },
    { name: 'takingAmount', type: 'uint256' },
    { name: 'settler', type: 'address' },
];

//Build the RFQ order object
function buildOrderRFQ(
    info,
    makerAsset,
    takerAsset,
    maker,
    makingAmount,
    takingAmount,
    settler,
    allowedSender = constants.ZERO_ADDRESS,
) {
    return {
        info,
        makerAsset,
        takerAsset,
        maker,
        allowedSender,
        makingAmount,
        takingAmount,
        settler,
    };
}

//Build the signature data
function buildOrderRFQData(chainId, verifyingContract, order) {
    return {
        domain: { name, version, chainId, verifyingContract },
        types: { OrderRFQ },
        value: order,
    };
}

//Sign the order
async function signOrderRFQ(order, chainId, target, wallet) {
    const orderData = buildOrderRFQData(chainId, target, order);
    return await wallet._signTypedData(orderData.domain, orderData.types, orderData.value);
}

//1.Build the RFQ order object
const order = buildOrderRFQ(salt, dai.address, weth.address, maker.address, 1, 1, constants.ZERO_ADDRESS);
//2.Generate order signature
const signature = await signOrderRFQ(order, chainId, swap.address, maker);

Smart contract integration#

Contract event#

// Order filled event.
event OrderFilledRFQ(
    bytes32 orderHash,   // order hash
    uint256 makingAmount // Quantity of makerToken traded.
);

Contract function#

/**
 * @notice Returns bitmask for double-spend invalidators based on lowest byte of order.info and filled quotes
 * @param Maker Maker address
 * @param slot Slot number to return bitmask to
 * @return Result, each bit represents whether corresponding bitmask was already invalidated
 */
function invalidatorForOrderRFQ(address maker, uint256 slot) external view returns(uint256);

/**
 * cancel orders.
 * @notice Cancels order quote
 * @param orderInfo Order info (only order id in lowest 64 bits is used)
 */
function cancelOrderRFQ(uint256 orderInfo) external;

/**
 * Batch cancel orders.
 * @notice Cancels multiple order quotes
 */
function cancelOrderRFQ(uint256 orderInfo, uint256 additionalMask) external;

/**
 * Order matching.
 * @notice Fills order quote, fully or partially (whichever is possible)
 * @param order Order quote to fill
 * @param order signature
 * @param flagsAndAmount Fill configuration flags with amount packed in one slot
 * @return filledMakingAmount Actual amount transferred from maker to taker
 * @return filledTakingAmount Actual amount transferred from taker to maker
 * @return orderHash Hash of the filled order
 */
function fillOrderRFQ(
    OrderRFQLib.OrderRFQ memory order,
    bytes calldata signature,
    uint256 flagsAndAmount
) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash);


/**
 * Order matching (signature provides r, v, s).
 * @notice Fills order quote, fully or partially (whichever is possible)
 * @param order Order quote to fill
 * @param r R component of signature
 * @param vs VS component of signature
 * @param flagsAndAmount Fill configuration flags with amount packed in one slot
 * @return filledMakingAmount Actual amount transferred from maker to taker
 * @return filledTakingAmount Actual amount transferred from taker to maker
 * @return orderHash Hash of the filled order
 */
function fillOrderRFQCompact(
    OrderRFQLib.OrderRFQ memory order,
    bytes32 r,
    bytes32 vs,
    uint256 flagsAndAmount
) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash);


/**
 * Order matching (supports ERC20 tokens with Permit function).
 * @notice Same as `fillOrderRFQTo` but calls permit first.
 * Allows token spending approval and making a swap in one transaction.
 * Also allows funds destination specification instead of `msg.sender`
 * @param order Order quote to fill
 * @param order signature
 * @param flagsAndAmount Fill configuration flags with amount packed in one slot
 * @param target Address that will receive swap funds
 * @param permit Should contain abi-encoded calldata for `IERC20Permit.permit` call
 * @return filledMakingAmount Actual amount transferred from maker to taker
 * @return filledTakingAmount Actual amount transferred from taker to maker
 * @return orderHash Hash of the filled order
 * @dev See tests for examples
 */
function fillOrderRFQToWithPermit(
    OrderRFQLib.OrderRFQ memory order,
    bytes calldata signature,
    uint256 flagsAndAmount,
    address target,
    bytes calldata permit
) external returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash);


/**
 * Order matching (taker can specify target address).
 * @notice Same as `fillOrderRFQ` but allows funds destination to be specified instead of `msg.sender`
 * @param order Order quote to fill
 * @param order signature
 * @param flagsAndAmount Fill configuration flags with amount packed in one slot
 * @param target Address that will receive swap funds
 * @return filledMakingAmount Actual amount transferred from maker to taker
 * @return filledTakingAmount Actual amount transferred from taker to maker
 * @return orderHash Hash of the filled order
 */
function fillOrderRFQTo(
    OrderRFQLib.OrderRFQ memory order,
    bytes calldata signature,
    uint256 flagsAndAmount,
    address target
) external payable returns(uint256 filledMakingAmount, uint256 filledTakingAmount, bytes32 orderHash);

flagsAndAmount Flag Description#

The flagsAndAmount type is uint256. The highest 252-256 bits are used to store the status identifier and the remaining bits are used to store the transaction amount.

  • 256 bits: Stores transaction amount direction identification (_MAKER_AMOUNT_FLAG): 0-taker direction; 1-maker direction
  • 255 bits: Stores contract signature ID (_SIGNER_SMART_CONTRACT_HINT): 0-no; 1-yes
  • 254 bits: Stores 65 bytes Signature ID (_IS_VALID_SIGNATURE_65_BYTES): 0-no; 1-yes
  • 253 bits: Stores whether to perform Unwrap WETH flag (_UNWRAP_WETH_FLAG): 0-no; 1-yes
  • 252 bits: Stores whether to call market maker custom settlement contract ID (_SETTLE_FLAG): 0-no; 1-yes

invalidatorForOrderRFQ method description#

The invalidatorForOrderRFQ method can query the status of all orders stored in the specified slot by the market maker address

ParameterDescription
Address makerMarket maker address
UINT256 slotThe slot to which the order belongs
TIP

The result of dividing the order ID from orderInfo by 256 (shifted 8 bits to the right) is equal to the slot to which the order belongs. Each slot stores up to 256 order statuses.

For example:

  • 0000... 0001 indicates that the first order for this slot has expired (filled or cancelled)
  • 0000... 0101 indicates that the 1st and 3rd orders for this slot have expired (filled or cancelled)
  • 1000... 0101 indicates that the 1st, 3rd and 256th orders for this slot have expired (filled or cancelled)
  • 1111... 1111 indicates that all orders for this slot have expired (filled or cancelled)

Batch cancellation parameter description#

ParameterTypeDescription
orderInfouint256The same as the info field in the order structure: 128 bits are actually used, the lowest 64 bits represent the order ID, and 64 to 128 bits represent the order timestamp
additionalMaskuint256A batch withdrawal unit mask, which supports batch cancellation of up to 256 orders. For bit mask rules, please refer to the invalidatorForOrderRFQ method description above
  • additionalMask = 0000... 0001 indicates cancelling the first order for this slot
  • additionalMask = 0000..... 0101 indicates cancelling the 1st and 3rd order for this slot
  • additionalMask = 1111 ..... 1111 marks the cancellation of all 256 orders for this slot

Contract ABI#

"abi": [
    {
      "anonymous": false,
      "inputs": [
        {
          "indexed": false,
          "internalType": "bytes32",
          "name": "orderHash",
          "type": "bytes32"
        },
        {
          "indexed": false,
          "internalType": "uint256",
          "name": "makingAmount",
          "type": "uint256"
        }
      ],
      "name": "OrderFilledRFQ",
      "type": "event"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "orderInfo",
          "type": "uint256"
        }
      ],
      "name": "cancelOrderRFQ",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "uint256",
          "name": "orderInfo",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "additionalMask",
          "type": "uint256"
        }
      ],
      "name": "cancelOrderRFQ",
      "outputs": [],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "info",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "makerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "takerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "maker",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "allowedSender",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "makingAmount",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "takingAmount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "settler",
              "type": "address"
            }
          ],
          "internalType": "struct OrderRFQLib.OrderRFQ",
          "name": "order",
          "type": "tuple"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "flagsAndAmount",
          "type": "uint256"
        }
      ],
      "name": "fillOrderRFQ",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        },
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "info",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "makerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "takerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "maker",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "allowedSender",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "makingAmount",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "takingAmount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "settler",
              "type": "address"
            }
          ],
          "internalType": "struct OrderRFQLib.OrderRFQ",
          "name": "order",
          "type": "tuple"
        },
        {
          "internalType": "bytes32",
          "name": "r",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "vs",
          "type": "bytes32"
        },
        {
          "internalType": "uint256",
          "name": "flagsAndAmount",
          "type": "uint256"
        }
      ],
      "name": "fillOrderRFQCompact",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "filledMakingAmount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "filledTakingAmount",
          "type": "uint256"
        },
        {
          "internalType": "bytes32",
          "name": "orderHash",
          "type": "bytes32"
        }
      ],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "info",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "makerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "takerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "maker",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "allowedSender",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "makingAmount",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "takingAmount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "settler",
              "type": "address"
            }
          ],
          "internalType": "struct OrderRFQLib.OrderRFQ",
          "name": "order",
          "type": "tuple"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "flagsAndAmount",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        }
      ],
      "name": "fillOrderRFQTo",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "filledMakingAmount",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "filledTakingAmount",
          "type": "uint256"
        },
        {
          "internalType": "bytes32",
          "name": "orderHash",
          "type": "bytes32"
        }
      ],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint256",
              "name": "info",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "makerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "takerAsset",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "maker",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "allowedSender",
              "type": "address"
            },
            {
              "internalType": "uint256",
              "name": "makingAmount",
              "type": "uint256"
            },
            {
              "internalType": "uint256",
              "name": "takingAmount",
              "type": "uint256"
            },
            {
              "internalType": "address",
              "name": "settler",
              "type": "address"
            }
          ],
          "internalType": "struct OrderRFQLib.OrderRFQ",
          "name": "order",
          "type": "tuple"
        },
        {
          "internalType": "bytes",
          "name": "signature",
          "type": "bytes"
        },
        {
          "internalType": "uint256",
          "name": "flagsAndAmount",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "target",
          "type": "address"
        },
        {
          "internalType": "bytes",
          "name": "permit",
          "type": "bytes"
        }
      ],
      "name": "fillOrderRFQToWithPermit",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        },
        {
          "internalType": "bytes32",
          "name": "",
          "type": "bytes32"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address",
          "name": "maker",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "slot",
          "type": "uint256"
        }
      ],
      "name": "invalidatorForOrderRFQ",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "view",
      "type": "function"
    }
  ]

Settlement contract (optional)#

Market makers can customize the settlement processing logic in settlement contracts. The settlement contract must implement the following interfaces:

// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

interface IPMMSettler {
    /**
     * @notice Interface for interactor which acts for `maker -> taker` transfers.
     * @param taker Taker address
     * @param token Settle token address
     * @param amount Settle token amount
     * @param isUnwrap Whether unwrap WETH
     */
    function settleToTaker(
        address taker,
        address token,
        uint256 amount,
        bool isUnwrap
    ) external;

    /**
     * @notice Returns the settlement treasury address.
     */
    function getTreasury() external view returns (address);
}

See the example below for implementing a settlement contract:

// SPDX-License-Identifier: MIT

pragma solidity 0.8.17;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./interfaces/IPMMSettler.sol";
import "./interfaces/IWETH.sol";
import "./libraries/SafeERC20.sol";

contract PMMSettlerDemo is IPMMSettler, Ownable {
    using SafeERC20 for IERC20;

    uint256 private constant _RAW_CALL_GAS_LIMIT = 5000;
    IWETH private immutable _WETH;
    address _TREASURY;

    constructor(IWETH weth) {
        _WETH = weth;
    }

    function setTreasury(address treasury) public onlyOwner {
        _TREASURY = treasury;
    }

    function getTreasury() external view returns (address) {
        return _TREASURY;
    }

    /**
     * @notice Interface for interactor which acts for `maker -> taker` transfers.
     * @param taker Taker address
     * @param token Settle token address
     * @param amount Settle token amount
     * @param isUnwrap Whether to unwrap WETH
     */
    function settleToTaker(
        address taker,
        address token,
        uint256 amount,
        bool isUnwrap
    ) external {
        require(taker != address(0), "zero address");
        require(amount > 0, "amount must be greater than zero");
        if (isUnwrap) {
            _WETH.transferFrom(_TREASURY, address(this), amount);
            _WETH.withdraw(amount);
            (bool success, ) = taker.call{
                value: amount,
                gas: _RAW_CALL_GAS_LIMIT
            }("");

            require(success, "settleToTaker failed");
        } else {
            IERC20(token).safeTransferFrom(_TREASURY, taker, amount);
        }
    }

    receive() external payable {
        require(msg.sender == address(_WETH), "Eth deposit rejected");
    }
}