Skip to content

Response payloads

The aggregator candidate (agg)

The agg object is the aggregator's own quote, reproduced 1:1. It is the input to /v1/calldata, and its structure is a route description rather than a transaction.

Quote fields

FieldTypeMeaning
srcTokenstringToken sold; zero address for native
dstTokenstringToken bought; zero address for native
chainIdnumberChain this route is valid on, and the chain the encode will target
tradeTypestringAlways exactIn for an encodable quote
inputAmountstringTotal sold, smallest unit of srcToken
outputAmountstringTotal bought before slippage, smallest unit of dstToken
gasUseEstimatestringEstimated gas units for the whole route
gasUseEstimateUsdstringEstimated gas cost in USD, scaled by 1e18
priceImpactBpsnumberPrice impact across the route, basis points
aggregatorAddressstringRouter contract the encoded call targets
routesarraySplit legs, see below
swapFeeBpsnumberSwap fee charged on this trade, in basis points; echoes the request parameter. Defaults to 0 if no swapFeeBps was requested
swapFeestringSwap fee amount for this trade, in the same units as outputAmount. All amounts are rounded down; an amount below 1 unit rounds down to 0. Defaults to 0 if no swapFeeBps was requested

gasUseEstimateUsd is fixed-point: "9493065552309952" is 0.009493 USD. Divide by 1e18 before display.

outputAmount is the quoted amount before slippage. The minimum the transaction will accept is derived from it and slippageTolerance during encoding, and is not surfaced as a separate field.

Routes and paths

A quote splits the input across one or more routes, each of which is a sequence of hops.

FieldTypeMeaning
percentnumberShare of the input on this route, 0 to 100
inputAmountstringThis route's share, in absolute smallest units
outputAmountstringThis route's contribution to the total output
priceImpactBpsnumberImpact on this route alone
patharrayTokens traversed, in order, including the endpoints
poolsarrayPools traversed; one entry per hop, so path.length - 1 entries on a pool-based route

The inputAmount values across routes sum to the quote's inputAmount, and /v1/calldata enforces that invariant. percent is a rounded convenience figure; do arithmetic on the absolute amounts instead.

Each path entry carries address, decimals, and symbol. decimals is the only reliable way to render the intermediate amounts of a multi-hop route; symbol is informational and is not guaranteed unique.

A route with an empty pools array is legitimate: it describes a wrap or unwrap between a native asset and its wrapped form, which involves no pool. Such a payload is encoded unchanged.

Pools

FieldPresenceMeaning
addressalwaysPool identity: a 40-hex contract address, or a 64-hex pool ID for V4-style pools
typealwaysPool family, e.g. v3, univ3, univ4, propamm_pcs, stableswap
feealwaysStatic LP fee in pips; for dynamic-fee Infinity pools this is the dynamic-fee flag 8388608 (0x800000) rather than a rate
resolvedFeeusuallyThe fee actually charged on this hop, in pips
poolManagerV4-style poolsContract that owns the pool
provideralwaysLiquidity source, e.g. pancakeswap, uniswap, aerodrome
token0, token1alwaysPool's token pair, not necessarily in swap direction
inputAmountRFQ poolsAmount feeding this specific hop
hooksalways, nullableHook contract for V4-style pools; null when none
parametersalways, nullablePacked pool parameters; null when not applicable
tickSpacingCL poolsTick spacing for concentrated-liquidity pools

address is polymorphic by length. A 40-hex value is a deployed pool contract; a 64-hex value is a V4-style pool ID that identifies a pool inside a poolManager. Code that assumes 20-byte addresses will reject valid quotes on chains where Infinity or Uniswap V4 liquidity is routed through.

Read resolvedFee rather than fee when displaying the fee a swap actually pays. The two agree for V2, V3, and Stable pools; they diverge for dynamic-fee pools, where fee is a flag and resolvedFee is the rate the hook resolved at quote time.

hooks and parameters are emitted as explicit null when inapplicable, unlike most optional fields in this API, because they are passed through from the engine unchanged.

Aggregator-origin error codes

When the aggregator refuses a request, the gateway returns 502 UPSTREAM_ERROR and carries the engine's numeric code through. On /v1/calldata it arrives as upstream.code; on /v1/quote it appears inside message, for the reason given in Upstream detail.

upstream.codeMeaning
40100Chain not supported by the encoder
40101Invalid input
40102Invalid hex input
40104Invalid pool data in the request
40401Not found
42102Quote unavailable, no route could be produced
42103Invalid API key
42104Encoding failed
50001Internal error
50301, 50302, 50303Temporarily unavailable

42102 reports that no route exists for that pair and size at that moment, which is a market condition rather than a defect, and it is the code an integrator sees most often. Surface it to the user as "no route". A PancakeSwap X quote can still succeed on the same pair, so requesting agg,pcsx or auto rather than agg turns that failure into a single-candidate success.

The PancakeSwap X candidate (pcsx)

The pcsx object is a complete, signable Dutch-decay limit order together with the EIP-712 payload needed to sign it. Reproduce it 1:1.

Top-level fields

FieldTypePresenceMeaning
quoteIdstringalwaysThe engine's own quote identifier; not the gateway's usq_ id
requestIdstringalwaysCorrelation identifier for support requests
rfqVersionv1, v3 or v4alwaysOrder family; see Order families
slippageTolerancestringalwaysPercent, as a string: "0.5" is 0.5%
startTimeBufferSecsnumberalwaysDelay from quote time to the start of the decay window
auctionPeriodSecsnumberalwaysLength of the decay window
deadlineBufferSecsnumberalwaysGrace after the window before the order dies
permitDataobjectalwaysEIP-712 payload to sign
orderInfoobjectalwaysDecoded order body
encodedOrderstringalwaysABI-encoded order bytes; what you submit
orderHashstringalwaysHash of the order; the handle for /v1/status
recipientstringwhen setOutput recipient when it differs from the swapper
portionBipsnumberwhen a fee appliesFee share in basis points
portionAmountstringwhen a fee appliesFee amount in output units
portionRecipientstringwhen a fee appliesFee destination

slippageTolerance here is a percent string, while the unified candidate's slippageTolerance is a fraction number. "0.5" in pcsx and 0.005 on the candidate describe the same tolerance. The gateway performs that conversion for you on the way in; the two representations coexist in the response because each is verbatim for its own layer.

orderInfo

FieldTypePresenceMeaning
reactorstringalwaysSettlement contract that will execute the order
swapperstringalwaysAddress that signs and owns the order; equals the recipient you requested
noncestringalways256-bit nonce, decimal
deadlinestringalwaysUnix seconds; the order is unfillable after this
additionalValidationContractstringalwaysZero address when no extra validation applies
additionalValidationDatastringalways"0x" when none
decayStartTimestringv1, advisory on v3/v4Start of the decay window, unix seconds
decayEndTimestringv1, advisory on v3/v4End of the decay window, unix seconds
exclusiveFillerstringusuallyFiller with priority during the exclusivity window
exclusivityOverrideBpsstringusuallyPremium another filler must beat to override exclusivity; "100" is 1%
cosignerstringv3, v4Address that cosigns the order at submission
inputobjectalways{ token, startAmount, endAmount }
outputsarrayalways{ token, startAmount, endAmount, recipient, isPortion? }

Timing

The three buffer fields and the decay anchors are related arithmetically, with the quote's own creation time as the origin:

decayStartTime = quotedAt + startTimeBufferSecs
decayEndTime   = decayStartTime + auctionPeriodSecs
deadline       = decayEndTime + deadlineBufferSecs

Typical values are a 60-second start buffer, a 120-second auction, and a 12-second deadline buffer, giving an order that becomes fillable a minute after quoting and dies about three minutes after that. Those are defaults, not guarantees so you should still read the actual numbers from the payload.

The start buffer is why a submitted order sits at OPEN unfilled for roughly the first minute: no filler can act before decayStartTime.

Decay and amounts

The order is a Dutch auction over the output. outputs[i].startAmount is the amount the swapper would receive at decayStartTime, endAmount the amount at decayEndTime, and startAmount >= endAmount always holds. Between those points the acceptable amount decays linearly, so a filler that acts early pays more. The swapper is guaranteed no worse than endAmount, which is where the signed slippage tolerance is expressed.

For an exactIn order the input is fixed: input.startAmount equals input.endAmount. For exactOut the input side carries the decay instead.

The gateway's outputAmount on the candidate is the sum of startAmount across non-portion outputs, which makes it comparable to the aggregator's pre-slippage outputAmount. When comparing worst cases across engines, compare endAmount against the aggregator's slippage-adjusted minimum instead.

permitData and signing

permitData is a complete EIP-712 payload:

{
  "domain": {
    "name": "Permit2",
    "chainId": 1,
    "verifyingContract": "0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768"
  },
  "types": { "PermitWitnessTransferFrom": [ "…" ], "TokenPermissions": [ "…" ], "ExclusiveDutchOrder": [ "…" ], "OrderInfo": [ "…" ], "DutchOutput": [ "…" ] },
  "values": { "permitted": { "…": "…" }, "spender": "0x…", "nonce": "…", "deadline": "…", "witness": { "…": "…" } },
  "primaryType": "PermitWitnessTransferFrom"
}

values is the message. The field is named values, but EIP-712 signing libraries expect the struct under the key message. Pass permitData.values as the message; do not pass the permitData object as a whole. Getting this wrong produces a valid signature over the wrong struct, which the engine will reject at submission.

Read verifyingContract from the response. The Permit2 contract is not at the same address on every deployment, because some reactor families verify against their own Permit2 instance. Use the value in permitData.domain, never a hardcoded constant.

The domain has no version. Permit2's EIP-712 domain is name, chainId, and verifyingContract only. Passing version: undefined is harmless in libraries that drop undefined keys and fatal in those that do not therefore you should construct the domain from exactly the keys present.

Before signing, the swapper/recipient must have approved Permit2 (permitData.domain.verifyingContract) to spend the input token. This is a one-time approval per token per Permit2 instance, and it is separate from the per-order signature. An order signed without it will be submitted successfully and then fail its on-chain pre-flight.

The signature is a 65-byte secp256k1 signature over the typed data. Send it as signature alongside the unmodified candidate.

Portion outputs

When a fee applies, the order carries an additional output marked isPortion: true, and the top-level portionBips, portionAmount, and portionRecipient describe it. That output is part of what the swapper signs and part of what settles on chain, and it is excluded from the candidate's outputAmount so that cross-engine comparison reflects what reaches the recipient. When rendering the order, show the portion output separately rather than summing all outputs.

Order families

rfqVersion reports which family was built. v1 orders are time-anchored: the decay window in orderInfo is authoritative and is what the swapper signs. v3 and v4 orders are cosigned: a cosigner address is present, and the authoritative decay anchors are set at submission time by the settlement layer, so the decayStartTime and decayEndTime in the quote are advisory hints. This is visible downstream: the status response for a cosigned order omits decayStartTime and decayEndTime entirely.

For an integrator this changes little: display the advisory window as approximate on cosigned orders, and expect status responses without decay anchors. Signing, submission, and polling are identical across families.

The PancakeSwap X SDK

The SDK (@pancakeswap/pcsx-sdk) is the reference implementation of the order format. Integrating through the Unified Swap API does not require it: permitData gives you everything needed to sign, encodedOrder everything needed to submit, and orderHash everything needed to track.

The SDK is useful when you want to verify the payload instead of trusting it. ExclusiveDutchOrder.parse(encodedOrder, chainId) (and the ExclusiveDutchOrderV3 / ExclusiveDutchOrderV4 equivalents for cosigned families) decodes the bytes you are about to sign back into a structured order. From there, .hash() recomputes the order hash and .permitData() recomputes the typed-data payload. Checking both against the values in the response confirms that the payload you received is internally consistent and that encodedOrder really encodes the order the response describes.

The choice of parser class follows rfqVersion: v1 to ExclusiveDutchOrder, v3 to ExclusiveDutchOrderV3, v4 to ExclusiveDutchOrderV4. Parsing cosigned bytes with the V1 class fails with a misleading error, so branch on the field rather than trying each in turn.

For clients that hold nothing but an order hash, /v1/status is sufficient and the SDK adds little.