Broker codes
A broker is anyone who routes orders for someone else — a front end, a trading bot, a terminal. A broker code lets you charge your own fee on the orders you route, collected on-chain, with no agreement with MetaFlux.
The fee is additive. It is charged on top of the exchange fee, not carved out of it, and it goes to you in full.
TL;DR
- The trader approves you once, naming a maximum rate.
- You set a rate on each order, at or below that maximum.
- The taker pays your fee on every fill.
- You claim the accrued balance whenever you want.
You cannot charge a trader who has not approved you. You cannot charge more than they approved. Both checks run before the order is accepted.
The two ceilings
Every broker fee passes two independent limits. The lower one wins.
| Limit | Set by | Default | Bounds |
|---|---|---|---|
| Protocol cap | Governance | 8 bps | 1–100 bps |
| Per-trader cap | The trader | none until approved | 0 – protocol cap |
The protocol cap is a global ceiling on the whole exchange. The per-trader cap is the number that trader approved for you specifically. An order naming a rate above either one is rejected before it rests — it does not fill and then refund.
Both ceilings are read live, on every order. If governance lowers the protocol cap below a rate a trader already approved, the lower cap applies immediately. The stored approval is not rewritten.
Approval
The trader signs one action:
{
"type": "approve_broker_fee",
"params": { "builder": "0x<your address>", "max_bps": 7 }
}
max_bps is whole basis points. Approving 0 is meaningful: it makes you a
recognized broker who charges nothing, which is different from not being approved
at all.
Read a trader's approvals with the approved_brokers query.
The action type is approve_broker_fee. Some older builder names stay, on
purpose. Read this before you file an inconsistency.
Both action names work. Send approve_broker_fee in new code.
approve_builder_fee is still accepted, and always will be. A committed block
keeps the exact JSON that the trader submitted, and every node reads those blocks
again when it replays the chain. An accepted action name is therefore never
withdrawn.
Committed data fixes these two names, so they do not move:
| Name | Where you meet it |
|---|---|
builder | the parameter of approve_broker_fee, and the broker block on an order |
approve_builder_fee | the accepted alias of the action name |
The READ names did move, because no committed block fixes them. A query type
is chosen at read time. broker_state and approved_brokers are the names to
use. broker_state and approved_brokers still answer, and the reply echoes
back the name you sent.
The EIP-712 type string still reads ApproveBuilderFee. You send
approve_broker_fee, but you sign
MetaFluxTransaction:ApproveBuilderFee(string metafluxChain,address builder,uint16 maxFeeBps,uint64 nonce).
This mismatch is deliberate. Do not report it as a bug. The type string is
hashed into every signature ever made for this action. Change one byte of it and
every one of those signatures stops verifying. A broker spelling can only
arrive later as a SECOND type string, selected per request. It can never be an
edit to this one. See
typed-data signing.
Charging
Attach a builder block to the order:
"builder": { "fee": 5, "user": "0x<your address>" }
| Field | Meaning |
|---|---|
fee | Your rate for this order, in whole bps |
user | Your address |
The EIP-712 submit_order type string names the same two values builderFee
and builderUser. Your signing library reads them; you do not send them.
Then, on every fill of that order:
taker pays = base taker fee + broker fee
The two are separate debits. The broker fee is notional × rate, rounded
toward zero, and credited to your address in full.
A zero-rate broker is still validated. Setting "fee": 0 is a no-op
charge, but the address must still be real and still approved. This keeps the
attribution path identical whether or not you charge.
Makers never pay a broker fee. Only the taker side of a fill is charged.
Where the fee sits in the waterfall
This is the part that surprises people. Your fee is outside the exchange's fee split, not inside it.
taker pays = base_taker_fee + broker_fee ← two separate debits
base_taker_fee → referrer share, if the trader has a referrer
→ the remainder is split by the protocol
broker_fee → the broker, in full
So your fee does not dilute the referrer share, does not feed the protocol split, and is not reduced by either. Equally, the exchange's own fee is not reduced by yours — the trader pays both.
Claiming
Fees accrue to a running balance. Claim it with:
{ "type": "claim_broker_rewards" }
Both names are accepted. claim_broker_rewards is the name to send, and
claim_builder_rewards still decodes and always will — the same second-name
rule approve_broker_fee follows.
Read the balance before you claim it. The action reports no amount. Query
broker_state with your address. That
read keeps the builder spelling and is the only way to show a claimable
figure. After the claim the balance is 0, so a read afterwards cannot tell you
what moved.
The whole accrued balance moves into your spendable collateral and the entry is
removed. The call is idempotent: claiming again with nothing accrued claims
0 and is not an error, so a retry after a timeout is safe.
There is no minimum, no schedule, and no expiry.
Limits and failure modes
| Situation | Result |
|---|---|
| Trader never approved you | Order rejected |
| Your rate exceeds their approval | Order rejected |
| Your rate exceeds the protocol cap | Order rejected |
| Broker address is zero | Order rejected |
Your rate is 0, and you are approved | Accepted, nothing charged |
| Nothing accrued when you claim | Claims 0, not an error |
Every rejection happens before the order rests, so a misconfigured broker code never produces a partially charged fill.
See also
- Fees — the base taker and maker schedule, and the protocol split
- Order types — where the broker fields sit on an order