Fees
Every fee on a trade or withdrawal, its unit, when it is charged, and where to read it
The quote is the authoritative number for what a trade costs. Read feeBreakdown and
totalCostIncFees before the user commits (see Quotes & smart routing).
After the trade, GET /execution/orders reports fees.quoted (the quote-time snapshot) and
fees.actual for reconciliation.
Fee kinds
| Fee | Unit | Charged | Quote field |
|---|---|---|---|
| Venue trading fee | Venue rate per market | When the order fills on the venue | feeBreakdown.venueFees, fills[].venueFee |
| Venue settlement fee | Share of payout or profit | At settlement, on venues that charge this way | Netted from estimatedPayout, fills[].settlementFee |
| Builder fee | USD | With the order, on venues that use builder codes | feeBreakdown.builderFee |
| App fee (yours) | Basis points of routed notional | Buys, and sells when you set a sell rate. See App fee | feeBreakdown.appFee, appFeeBips |
| Referral fee | Basis points of routed notional | Buys, and sells when you set a sell rate. See Referral fee | feeBreakdown.referralFee, referral |
| Bridge fee | USD | When funds move across chains for the trade | feeBreakdown.bridgeFees |
| Execution gas | USD | Network gas for execution | feeBreakdown.executionGas |
| Setup costs | USD | One time, per wallet and chain | feeBreakdown.setupCosts with deepEstimate=true. Hyperliquid activation (about $1.23 when bridging to a new HyperCore address: Hyperliquid's 1 USDC plus Relay's ~$0.20; budgeted as $1.25) is counted on every quote that routes to Hyperliquid, with or without deepEstimate |
| Withdrawal fee | Token atomic units | On withdrawal | feeRaw on the withdrawal preview |
feeBreakdown.totalCost is price times shares plus venue, builder, bridge, and gas fees. It
excludes app and referral fees. totalCostIncFees includes everything.
Venue fees
Each venue sets its own taker fee. GET /venue-markets returns it per market as feeRate,
feeExponent, and feeBasis (fill, fill_in_game_only, winnings, or exit). null means
sufwan-local-test does not have the fee, not that it is free. Use these fields to render fee-inclusive price
levels, and use the quote for the real cost. Formulas and a per-venue table:
Venue fees.
Builder fee
Polymarket and Hyperliquid attribute order flow to a builder. Set your own builder code per venue in the admin dashboard. Without one, the platform default applies. See Builder codes.
App fee
Your app fee is the fee you charge your users. Configure rates per app in the admin dashboard under
Fees. It applies to buys, in basis points of the routed notional. Sells are free unless you set
a sell rate (Fallback sell fee) under Fees; a sell is then charged on gross proceeds
(shares × price, before the venue fee) and the fee comes out of the proceeds. The quote shows it in
feeBreakdown.appFee and nets it out of estimatedNetProceeds, which is gross proceeds minus venue
fees, builder fees, your app fee and any user-paid referral.
Fee recipient wallets. Set where your share is paid under Fees too: one EVM address (it
covers every EVM chain), a Solana address, and a HyperCore address. Each venue pays out on the chain
it settles on, so a trade whose fee settles where you have no wallet pays you nothing. On a
self-custody trade it is refused instead (no_fee_recipient); see
Self-custody trading.
Sell fees are collected on Polymarket, Hyperliquid, predict.fun, Limitless, Myriad, Novig and
ProphetX. A sell leg on any other venue carries no fee, with a sell_fee_unsupported_venue
warning on the quote. A sell that fills on more than one venue carries no app fee or user-paid
referral, with a sell_fee_multi_venue warning. A managed Polymarket sell of shares held outside
the user's deposit wallet (a legacy Safe or proxy) carries no app fee or user-paid referral, with a
sell_fee_unsupported_wallet warning. A managed Novig or ProphetX sell settles on Solana, where a
user-paid referral cannot be paid: the app fee still applies, but the referral is priced at 0 (and
not deducted from estimatedNetProceeds), with a sell_referral_unsupported_venue warning. An
app-paid referral is unaffected. Warnings use the usual
{ venue, venueMarketOutcomeId, reason } shape.
On a managed buy the quote reserves the app fee out of maxSpend. Partial fill: the app fee
is collected on the amount actually filled. feeBreakdown.appFee is the maximum if the buy fills
in full.
Where a managed buy's fee comes from. The fee is collected after the fill, from the user's managed wallet, on the chain of the venue that filled: Polygon for Polymarket, BNB Chain for predict.fun. You don't need to keep funds on that chain. When a buy is funded from another chain, the bridge carries the reserved fee along with the trade, and the fee is collected where it lands:
- From the managed wallet on the venue's chain, in the stablecoin that is there (for example USDT on BNB Chain). This is used whenever that wallet already holds the whole fee, and it confirms within seconds.
- From the Polymarket deposit wallet, in pUSD, when the buy's funds went there. This
transfer can only be sent once the fill has settled on-chain, so the fee usually confirms
within a minute. Until then the charge reads
failedwith a "waits for on-chain settlement" reason, and it is retried automatically.
fees.actual.appFee.status reads under_collected when less than the full fee was collected,
including when nothing was (collectedRaw: "0").
- A partial collection because the user's wallet was short is final.
- On Hyperliquid the partner and platform shares go out as two separate transfers. If one has not
gone out yet, a retry sends it and the fee moves to
confirmed. - A charge that collected nothing is retried automatically, up to 5 times over about 2.5 hours, and is final after that.
fees.actual.appFee.finalizedAt is the time the fee stopped changing: collected in full,
skipped, or no longer retried. It is absent (never null) while the fee may still change, so a
$0 under_collected without finalizedAt is still being retried.
Per-trade override. A request that carries your x-app-api-key can replace the rate for one
trade with appFeeBips (0 to 10000; 0 waives it) on the quote or a direct order. The quote then
reads feeBreakdown.appFeeCategory: "override". Without an API key the request returns 400 with
a message starting app_fee_override_requires_api_key. The override works on sells
too. The platform share applies to an overridden fee the same way.
Venue-specific rates. To charge a different rate per venue, send appFeeBipsByVenue on the
quote or a direct order, with the same x-app-api-key rule. Keys are venue names and
each value is 0 to 10000 bips. A venue's rate is resolved in this order:
appFeeBipsByVenue[venue]appFeeBips- your configured rate for the category, then
all
So appFeeBipsByVenue: { "polymarket": 20, "hyperliquid": 10 } charges 20 bips on the legs that
fill on Polymarket and 10 on the legs that fill on Hyperliquid. A 0 in the map waives the fee on
that venue even when appFeeBips is set. On a quote:
feeBreakdown.appFeecharges each venue's share of the notional at its own rate.feeBreakdown.appFeeBipsis the highest rate among the venues that route fills.feeBreakdown.appFeeBipsByVenuelists the rate for each venue the route fills. It is present only when those rates differ.maxSpendis reserved at the highest rate among all candidate venues, since the fill venues are not known until the route is built. A venue with a lower rate therefore gets a slightly smaller budget, andmaxSpendis never exceeded.
A direct order fills on one venue, so its rate is the map entry for that venue. On a quote, the map
is a JSON string: a malformed map, an unknown venue or a rate that is not a whole number in 0..10000
returns 400 with a message starting app_fee_override_invalid. On POST /execution/orders the
map is an object: an unknown venue returns the same app_fee_override_invalid, but a rate that is
not a whole number in 0..10000 fails request validation first (400, Request validation failed).
A common split is a fixed total shared with a referrer: appFeeBips: 20 with no referrer, or
appFeeBips: 10 with referrerFeeBips: 10 when there is one. The user pays 20 bips either way.
Self-custody fills collect the app fee differently, and under approveMode: "user_broadcast" the
fee accrues as a balance you claim at the bridge provider. See
Self-custody trading.
Referral fee
A referral pays a third party out of the same trade. It is a second fee next to the app fee, in
basis points of the routed notional, and it goes entirely to referrer. sufwan-local-test takes no share of it.
Send these on the quote or on POST /execution/orders. They need x-app-api-key. Without it the
request returns 400 with a message starting referral_requires_api_key. The fill uses the
quote's referral and cannot change it.
| Param | Required | Use |
|---|---|---|
referrer | EVM address that receives the fee. Works on sells too, on the same venues as the app fee. | |
referrerFeeBips | With referrer | 1 to 10000. |
referrerFeeBipsByVenue | Per-venue referral rates, 0 to 10000, same precedence as appFeeBipsByVenue. 0 means no referral on that venue; at least one venue must end up above 0. A route or order that fills only venues at 0 carries no referral. With it, referrerFeeBips is optional. | |
referralPayer | "user" (default) or "app". | |
referralPayerAddress | With "app" | Your wallet that pays the referral. |
referralPayoutChainId | App-paid only. The chain the referrer is paid on. Must carry USDC. |
These are HTTP fields. getSmartRoute forwards referrer, referrerFeeBips, referralPayer, and referralPayerAddress. It does not forward referralPayoutChainId.
The quote echoes the accepted referral as
referral: { referrer, feeBips, payer, payerAddress?, quotedFeeUsd }. A user-paid referral also
appears in feeBreakdown.referralFee. An app-paid referral is not a user cost and never does.
User-paid
Partial fill: a user-paid referral is reserved from maxSpend and collected the same way as
the app fee.
App-paid
The user pays nothing. You pay the referrer from referralPayerAddress. Partial fill: an
app-paid referral pays the quoted amount. A failed fill pays nothing.
Without referralPayoutChainId, the payout goes on the trade's fee chain, or Polygon when that
chain is not EVM. Sending referralPayoutChainId with a user-paid referral returns 400 with
referral_invalid.
On HyperCore the referrer receives spot USDC on Hyperliquid. The first payout to a referrer with no Hyperliquid account costs an extra 1 USDC that Hyperliquid charges to create it. Your wallet must hold the payout plus that 1 USDC, or the transfer is refused.
Poll and sign that payout on Referrals. Signature types are on Self-custody signing.
Where a referral cannot be paid
A buy that fills only on Kalshi collects its fee from Solana USDC, where an EVM referrer cannot be paid. The quote carries a
warning with reason: "referral_unpaid_on_solana", the user is not charged the referral, and
nothing is paid.
A user-paid referral on a Hyperliquid fill is sent to the referrer on HyperCore. Hyperliquid
charges the sender 1 USDC to activate an address that has never received a HyperCore deposit. If
the referrer's address is not activated, the referral transfer either costs the user that 1 USDC
or, if they can't cover it, is rejected and the referral is not paid. The quote carries a warning
with reason: "referrer_not_activated_on_hypercore", and the trade still executes. Fund the
referrer's HyperCore address once, with any amount, to clear it.
To pay that activation fee yourself instead, send referralActivationSponsorAddress on the quote
(or the direct order). Before the fill, a pending signature appears beside the order's with
signerAddress set to that wallet, purpose: "hl_activation_sponsor" and optional: true: an
eip712 sendAsset of 0.000001 USDC to the referrer. Sign it with that wallet and post it on
POST /execution/fill/:quoteId/signatures together with the order's signature (with the SDK, your
signer callback receives it). After the fill it is sent before the referral transfer, only if the
referrer still has no HyperCore account. The wallet's HyperCore spot balance must hold at least
1.000001 USDC. Declining it is safe: the trade proceeds and the referral is attempted as it would be
without it. Self-custody Hyperliquid buys only, user-paid referral only.
If you drive signatures with your own client rather than the SDK, post the required and optional
signatures together, but never wait more than about 15 seconds on your paying wallet's signature (or any
optional request signed by a wallet other than the user's): post without it after that. The order
request expires, so the order's signature must not be held for it. An optional request the
user's own wallet signs, such as an hl_referral prompt, can wait for the user as before.
Securing the paying wallet's signer
The activation request reaches your backend through the user's browser, so the endpoint that signs it
is a signing service any user can call with whatever typed data they like, for example a
sendAsset of the paying wallet's whole balance to themselves, which they could post to
Hyperliquid directly. Sign only when every one of these holds, and otherwise refuse:
primaryTypeisHyperliquidTransaction:SendAsset.destinationequals the referrer you set on that quote yourself (compare case-insensitively).amountis exactly"0.000001".tokenis exactly"USDC:0x6d1e7cde53ba9467b783cb7c530ce054", Hyperliquid'sSYMBOL:tokenIdwire form for spot USDC.sourceDexanddestinationDexare both"spot", andfromSubAccountis"".
Better still, rebuild the typed data on your server from the quote you created and sign that,
rather than trusting the client's copy. The paying wallet must be an EOA holding the master key:
Hyperliquid agent (API) wallets cannot sign sendAsset, and a smart-contract wallet's signature
fails verification, so the activation payment is skipped.
Reconciling
GET /execution/orders reports fees.quoted.referralFeeRaw and fees.actual.referral per trade:
{ referrer, payer, feeBips, dueRaw, paidRaw, status }, where status is confirmed, failed,
expired, or skipped.
Withdrawal fees
POST /execution/withdraw/preview returns the fee as feeRaw and the amount the recipient gets as
receiveAmountRaw. Fees come out of the withdrawal amount. See
Funding & withdrawals.