executeWithCallback with the order info.reactorCallback and performs the Executor swap logic.| Order Time | Person eligible to fill |
|---|---|
| User sign the order → decayStartTime | exclusive filler or filler who is willing to pay the exclusivityOverrideBps can fill |
| decayStartTime → decayEndTime (decay starts) | anyone can filler |
| decayEndTime → deadline (decay stops) | anyone can filler |
PCSX only supports Wrapped native token as input. When user swap from native tokens to any tokens, our frontend will wrap the native token, get quote on the Wrap native token and send the token to the pcsx contracts. Therefore, in order to maximise the gain, we suggest all Market Makers to support both native token and Wrap native token pairs.
POST <https://sgp1.test.x.pancakeswap.com/order-price/get-price?filler=0x>..
Body:
{
"tokenInChainId": 56,
"tokenIn": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82",
"tokenOutChainId": 56,
"tokenOut": "0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c",
"amount": "1000000000000000000" //In wei,
"type": "EXACT_INPUT",
"slippageTolerance": "0.01", // Optional
"configs": [
{
"routingType": "DUTCH_LIMIT",
"swapper": "0x1111111111111111111111111111111111111111", // User
"exclusivityOverrideBps": 100, // Optional
"startTimeBufferSecs": 60, // Optional
"auctionPeriodSecs": 3600, // Optional
"deadlineBufferSecs": 120 // Optional
}
]
}
Next response is more pseudo JSON to illustrate format
Response:
{
"messageType": "PRICE_RESPONSE",
"message": {
"bestOrder": DutchOrder,
"allPossibleOrders": [DutchOrder]
}
}
{
"type": "DUTCH_LIMIT",
"order": {
"quoteId": "uniqueQuoteId123",
"requestId": "7b19d45d-590d-4d8d-b314-8bd22d449cdd",
"startTimeBufferSecs": 60,
"auctionPeriodSecs": 3600,
"deadlineBufferSecs": 120,
"slippageTolerance": "0.5",
"permitData": {
"domain": {
"name": "Permit2",
"chainId": 97,
"verifyingContract": "0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768"
}
...
},
"orderInfo": {
"reactor": "0xCfe2a565072f85381775Eb12644d297bf0F66773",
"swapper": "0x1111111111111111111111111111111111111111",
"nonce": "7786519691593674576434758944928673618219620429447126630005716426566992061",
"deadline": "1712069517",
"additionalValidationContract": "0x0000000000000000000000000000000000000000",
"additionalValidationData": "0x00",
"decayStartTime": "1712065797",
"decayEndTime": "1712069397",
"exclusiveFiller": "0x3541a2456E40324Be9c3488755f61e984dECEDf6",
"exclusivityOverrideBps": "100",
"input": {
"token": "0x9f063C8ED6cF9c454993677fd53b2AF572c91d6F",
"startAmount": "5000000000000000000",
"endAmount": "10000000000000000000"
},
"outputs": [
{
"token": "0x1F3dAD92459B65f98a206D55dbE9F21673C2815A",
"startAmount": "5000000000000000000",
"endAmount": "5000000000000000000",
"recipient": "0xfa4E489A8bB0F7D3a13Ab5352f72981A24c7Cb84"
}
]
},
"encodedOrder": "0x0000..."
}
}
For communication we gonna use WebSockets. When connection to WebSocket add header x-mm-secret=(check with us) for authorization.
CONNECT wss://sgp1.test.x.pancakeswap.com/quote-service/order-book?networkId=1
Where networkId is required parameter. And start streaming order books of tokens you support.
Include you fee into OrderBook level. For gas usage, you can use baseTokenGas field in each OB, so we deduct that amount from output.
Send messages with some frequency. The TTL for OrderBook is 10 sec. When the market maker submits their order book to the quote api, the api assumes that order book is sorted by the amount in ascending order. Hence, the api will treat the first level in bid/ask array as the minimum amount that the market maker is willing to start filling. On the other hand, the last level in the bid/ask array will be the maximum amount.
Here is an example of CAKE-USDT order book:
{
"orderBook": [
{
// MUST BE IN ASCENDING ORDER
"bid": [
// level 1
["10","1.68"], // amount in CAKE, rate to USDT
// level 2
["20","1.67"],
// level 3
["30","1.66"],
...
],
// MUST BE IN ASCENDING ORDER
"ask": [
["10","1.68"], // amount in CAKE, rate to USDT
["20","1.67"],
["30","1.66"],
...
],
// CAKE
"baseToken": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82",
"baseTokenGas": "0.1",// gas cost in baseToken (optional)
//USDT
"quoteToken": "0x55d398326f99059ff775485246999027b3197955",
"minimum": "10"// should match your bid/ask min amount
},
{
// MUST BE IN ASCENDING ORDER
"bid": [
...
],
// MUST BE IN ASCENDING ORDER
"ask": [
// level 1
["10","0.595"], // amount in USDT, rate to CAKE
// level 2
["20","0.591"],
// level 3
["30","0.602"],
],
// USDT
"baseToken": "0x55d398326f99059ff775485246999027b3197955",
"baseTokenGas": "0.16",// gas cost in baseToken (optional)
// CAKE
"quoteToken": "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82",
"minimum": "10"// should match your bid/ask min amount
}
],
"quoteId": "mm-timestamp-orn-usdt"
}
Given the above order book example, here is how we send out quotes on CAKE-USDT
Choose the correct bid/ask
bid on CAKE(baseToken) - USDT(quoteToken)ask on CAKE(quoteToken) - USDT(baseToken)bid on CAKE(quoteToken) - USDT(baseToken)ask on CAKE(baseToken) - USDT(quoteToken)Calculate the result amount
Given the case: User wants to swap an exact input of CAKE to USDT, the api takes the bid on CAKE(baseToken) - USDT(quoteToken)
Here is the pseudo code on the calculation:
sizeFilled = 0
resultAmount = 0
for (i = 0; i < levels.length; i++) {
levelSize = levels[i][0]
levelPrice = levels[i][1]
partFillSize = size - sizeFilled
if (levelSize >= size) {
resultAmount += levelPrice * partFillSize / size
break
}
resultAmount += (levelPrice * (levelSize - sizeFilled)) / size
sizeFilled = levelSize
}
if (resultAmount > 0) {
return resultAmount
}
Add gas
If user wants to swap an exact input of 15 CAKE to USDT, the api takes the bid on CAKE(baseToken) - USDT(quoteToken), the result is 25.15 USDT. The api will take the baseTokenGas on CAKE(quoteToken) - USDT(baseToken) and add to the result amount. The api returns quote:
25.15 - 0.16 = 24.99 USDT
If user wants to swap an exact output of CAKE to 15 USDT, the api takes the ask on CAKE(quoteToken) - USDT(baseToken), the result is 8.905 CAKE. The api will take the baseTokenGas on CAKE(baseToken) - USDT(quoteToken) and add to the result amount. The api returns quote:
8.905 + 0.1 = 9.005 CAKE
BSC Testnet:
0xCfe2a565072f85381775Eb12644d297bf0F667730x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768BSC Mainnet:
0xDB9D365b50E62fce747A90515D2bd1254A16EbB90x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768Sepolia:
0xbfCC755375250C1EA9722EB1B8d97076f681627C0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768Arbitrum:
0x35db01D1425685789dCc9228d47C7A5C049388d80x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768Etherum:
0x35db01D1425685789dCc9228d47C7A5C049388d80x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768Install:
yarn add @pancakeswap/permit2-sdk @pancakeswap/pcsx-sdk
Here is an example on constructing an exclusive order.
import { REACTOR_ADDRESS_MAPPING, ExclusiveDutchOrder, ExclusiveDutchOrderInfoJSON } from '@pancakeswap/pcsx-sdk'
const chainId = 56 // or 97 depend on bsc mainnet or testnet
const orderInfo: ExclusiveDutchOrderInfoJSON = {
reactor: REACTOR_ADDRESS_MAPPING[chainId],
swapper: "0x0...",
nonce: 1, // depends on the permit2
deadline: 1712042224, // unix time
additionalValidationContract: "0x0000000000000000000000000000000000000000", // if any
additionalValidationData: "0x00", // if any
decayStartTime: 1712042164, // unix time
decayEndTime: 1712042224, // unix time
exclusiveFiller: "0x...", // the filler contract address
exclusivityOverrideBps: 100,
input: {
token: "0x...", // input token address
startAmount: 10000000000000000, // in smallest unit
endAmount: 50000000000000000, // in smallest unit
},
outputs: [
{
token: "0x...", // output token address
startAmount: 10000000000000000n, // in smallest unit
endAmount: 10000000000000000n, // in smallest unit
recipient: "0x...",
},
],
};
const order = ExclusiveDutchOrder.fromJSON(orderInfo, chainId);
Generate the signature and encode order:
const permitData = order.permitData();
const signature = await swapper.signTypedData({
domain: {
name: permitData.domain.name,
version: permitData.domain.version,
verifyingContract: permitData.domain.verifyingContract,
chainId,
},
types: permitData.types,
primaryType: "PermitWitnessTransferFrom",
message: {
...permitData.values,
},
});
const encodedOrder = order.encode();
Approve permit2 address on user spending:
ERC20(0x...).forceApprove(
0x..., // swapper address
0x31c2f6fcff4f8759b3bd5bf0e1084a055615c768,
type(uint256).max
);
User submit order through :
POST <https://sgp1.test.x.pancakeswap.com/order-handler/order>
Post body:
{
"encodedOrder": "0x0...",
"signature": "0x0....",
"chainId": 56 // or 97 depend on bsc mainnet or testnet
}
Response:
{
"hash": "0x..."
}
The hash is unique id of the order by each chain and will be included in the fill event and used to check the order status.