- the builder fee, paid by a taker. Each positive builder fee is credited in
full to the taker order’s application with payout kind
BUILDER_FEE; - the maker add-on, paid by a maker. Each positive maker add-on is credited
in full to the maker order’s application with payout kind
BUILDER_MAKER_FEE.
REVENUE_SHARE. When a taker from one frontend
fills a maker from another, each frontend is credited the fee its own user
paid.
Payout wallet
Each enrolled frontend has one payout wallet on Sei. It can be any EVM address you control: an ordinary wallet (EOA) or a smart-contract wallet such as a Safe multisig. Monaco does not require a Safe. To enroll, send your payout address and your frontend’s name to [email protected]. Monaco sets it as your payout wallet.- An ordinary wallet logs in the way every wallet does, by signing Monaco’s login message with its private key, and then calls the frontend endpoints directly.
- A Safe has no private key, so it logs in with a contract signature its owners approve. See Multisig payout wallets and the BuilderCodes with a Safe guide.
Attribution
Revenue share and the builder fee follow the application that submitted the taker order. The matching engine stamps each resulting trade with that authenticated application’s id and looks up its current BuilderCodes enrollment when the fill settles. The maker add-on follows the application that submitted the maker order (see Maker add-on).- A maker application’s enrollment earns no revenue share for that fill; it earns only the maker add-on its own user paid.
- An unenrolled taker application, or one without a usable payout destination, receives no BuilderCodes credit.
- Liquidation slices do not charge or credit the ordinary builder fee. Fills with no positive Monaco protocol take receive no revenue-share credit.
- An enrolled application’s ordinary builder fee is a separate revenue stream.
It is separate from the revenue share and credited exactly to the payout
wallet enrolled when the taker command is sequenced. Other application-fee
paths, including copy-trading fees, do not create
BUILDER_FEEpayouts.
Revenue share
While the revenue share is on, an enrolled application earns a share of Monaco’s protocol fee on each taker fill it routes, computed per fill in the quote token’s RAW atomic units.buildercodesBudgetBpsongetConfig()is your application’s configured target share. The target can differ per client application.- Each payout row’s
appliedBpsis the realized whole-bps rate for that fill. It can be lower than the target, so reconcile earnings from payout rows, not from the target. - A fill with no positive Monaco protocol fee pays no revenue share, and amounts truncate toward zero, so a dust share produces no payout row.
Maker add-on
The maker add-on is an additional fee your frontend charges its own users when their order fills as the maker, in basis points of the fill’s notional. It is the maker-side counterpart of the builder fee:- Who pays. The maker, from their own balance, in the quote token, on top of Monaco’s maker fee or rebate. A maker earning a rebate still receives the rebate and pays the add-on separately.
- Who is credited. The maker order’s application, in full, as a
BUILDER_MAKER_FEEpayout. - Cap and switches. The same live cap (
builderFeeCapBps, never above 100 bps) and the same switches as the builder fee: nothing is charged whilebuilderFeeEnabledisfalseor payout enrollment is incomplete. - Rate in force. An order stores the maker add-on in force when it is placed and is charged that rate when it fills, however long it rests. A change applies to orders placed after it takes effect, never to orders already resting. A replace is a new placement and takes the current rate.
- Exempt. Liquidation orders and copy-trade orders store no maker add-on, and a resting order filled by a liquidation pays none on that fill. TWAP slices and triggered take-profit or stop-loss orders take the rate in force when they enter the book.
builderMakerFeeBps on getConfig().
Frontend endpoints
All seven endpoints require an authenticated, non-delegated wallet session. No program switch gates reads or claims. Setting the builder fee needs the revenue share or builder fees switched on; while both are off it is refused with403
on REST and PERMISSION_DENIED on gRPC.
Program config, builder-fee controls and change history, payout history, and
summary are application-scoped. The application comes from the session, and
the authenticated wallet must match its current payout address. The request
cannot select another application or wallet.
The stored builder fee and maker add-on may be set while builder fees are
disabled, but a side that changes must not exceed the live cap (never above
100 bps). One call sets both sides and is one change. A frontend may make two
real changes in a rolling 24 hours; administrator changes do not count. Setting
the current values is a successful no-op that consumes no change, even when the
allowance is exhausted or the live cap has fallen below a stored value.
A newly stored fee or maker add-on applies once the matching engine’s next
BuilderCodes enrollment refresh sequences it, within about 30 seconds by
default, not instantly; a new maker add-on applies to orders placed after that. Sequencer
ordering is authoritative for the builder-fee rate, gate and enrollment: an order processed before
one of those sequenced changes uses the earlier state, and a later order uses
the new state. The charged fee is capped at the live cap and is zero while
builder fees are disabled or payout enrollment is incomplete. The builder fee is
charged on taker fills; the remainder of an order that rests on the book pays
its frontend’s maker add-on, not the builder fee, when it later fills as maker.
Each positive charged fee is credited to the application’s payout balance as a
BUILDER_FEE payout, and each positive maker add-on as a BUILDER_MAKER_FEE
payout. The change log is newest first and includes both frontend wallet changes
and administrator changes, one row per change with the old and new value of each
side (oldBps/newBps and oldMakerBps/newMakerBps); an administrator row
exposes the operator’s email in actor.
setBuilderFee requires an explicit bps (0 clears it). makerBps is
optional: omit it to leave the maker add-on unchanged, or send 0 to clear it.
Invalid or above-cap changes to either side return 400 / INVALID_ARGUMENT,
and nothing is written. A missing or nonmatching
payout address fails the ownership gate first with 403 /
PERMISSION_DENIED; incomplete enrollment reached after ownership passes,
or an exhausted rolling allowance, returns 409 / FAILED_PRECONDITION.
The change-limit response carries details.reason: "builder_fee_change_limit"
and details.nextChangeAllowedAt; gRPC includes the next allowed time in its
message. Change history is cursor-paginated with pageSize 1–100 (default 20)
and pageToken; an empty nextPageToken ends the walk, and invalid
pagination returns 400 / INVALID_ARGUMENT.
Both operations require the session application to be a frontend. Missing authentication returns 401 /
UNAUTHENTICATED; a failed ownership check, or setting the fee while both fee switches are off, returns 403 /
PERMISSION_DENIED; a missing application returns 404 / NOT_FOUND;
internal failures return 500 / INTERNAL. Change-history reads draw the
account’s read budget (429 / RESOURCE_EXHAUSTED when enforced).
Maintenance can refuse the write before it runs with 503 /
UNAVAILABLE.
The no-op exception also applies when interpreting the generated REST
reference’s live-cap descriptions: repeating the stored value is accepted
even when that value exceeds a subsequently lowered cap.
Reward balance and claim are wallet-scoped. The server derives the reserved
bucket from the authenticated wallet, and current enrollment is not required.
This distinction lets an old payout wallet inspect and claim its retained
balance after an address rotation, even though it can no longer read the
application’s current config or payout history.
Multisig payout wallets
A payout address that is a smart-contract wallet — a Safe or another multisig — authenticates with a contract signature (EIP-1271) rather than apersonal_sign from a private key. The login flow is otherwise the sequence
every wallet follows: request a challenge, have the wallet authorize the
returned message, and submit the result to POST /api/v1/auth/verify. Monaco
asks the wallet contract’s isValidSignature whether it accepts the challenge
and issues a session when the contract says yes.
Because collecting a threshold of owner approvals is a coordination round
between people rather than a single click, challenges stay valid for 30 minutes
instead of the usual 5 whenever contract-wallet login is enabled. The longer
window applies to every address, not only eligible ones: /auth/challenge
is unauthenticated and returns expiresAt, so an eligibility-specific lifetime
would let anyone probe which addresses hold a payout bucket by comparing
lifetimes. The nonce is still single-use and the signed message still authorizes
one specific session public key, so the longer window widens only how long the
wallet has to answer, never what the challenge can authorize.
For a step-by-step script that logs a Safe in, checks its earnings, sets
builder fees, and withdraws to the Safe, see
BuilderCodes with a Safe.
Contract-wallet login is scoped to addresses that hold a reserved payout
bucket, which is what makes a multisig able to claim and withdraw what it
earned. It is not general smart-contract-wallet support for trading accounts.
Four conditions must all hold, and any of them failing leaves the address on the
ordinary signature path:
- the deployment has a chain RPC endpoint configured (
SEI_RPC_URL). Without it the contract-wallet path is never constructed, so the flag below has nothing to switch on; contract_wallet_auth_enabledis on;- the address holds a reserved BuilderCodes payout bucket — including one retained after a payout-address rotation;
- the address has contract code deployed on chain.
The challenge itself looks the same either way — the server issues one message
and decides how to verify it at
/auth/verify — so which path a wallet took
is visible in the verification outcome, not in the challenge. A wallet that is
silently falling back will fail verification rather than receive a different
challenge.contract_wallet_auth_enabled defaults to false. While it is off, every
login is verified by key recovery and a multisig payout wallet cannot
authenticate.
Enrollment still accepts any nonzero address, so a payout address pointed at a
contract wallet keeps accruing its share in the meantime — that balance is
retained, not lost, and becomes claimable once the conditions above are met.RAW-unit fields
BuilderCodes payout amounts and balances use RAW atomic units, represented as base-10 integer strings. Convert them with the token’s configured decimals:1000000 is 1 token when that token has six decimals. Do
not add amounts for different tokens.
RAW-unit fields include:
- payout history
amount - summary
byToken[].totalAmount,byTokenAndKind[].totalAmountandbuilderFeeByToken[].totalAmount - reward balance
available - claim request
amount - claim response
payoutBalanceandtradingBalance
Payout-address rotation
Monaco changes a payout address on request. The change moves the money with the address:- shortly after the change, fills accrue to the new wallet’s reserved bucket, and in the same step the unclaimed balance in the old wallet’s bucket moves to the new wallet;
- each payout wallet belongs to one application: a wallet another application pays out to, or paid out to before, cannot be set;
- anything the old wallet had already claimed into its trading balance or withdrawn stays where it is;
- the old wallet can still call reward balance and claim whatever remains in its bucket;
- current application config, history, and summary belong to the new payout wallet because those reads require the current address.
Claim and withdraw
Read the wallet’s reserved balances, then claim a positive RAW amount for one token:409 / gRPC
FAILED_PRECONDITION; it is never partially filled. A missing or empty bucket
uses the same response because both mean there is no balance to move.
Unknown outcomes: do not retry blindly
An admitted claim can still be queued when a balance read runs. Wait until the matching engine is healthy and reconcile the payout and trading balances before issuing another claim. The TypeScript SDK marks ambiguous claim failuresretryable: false so generic retry middleware does not claim twice.
Two tagged 503 / UNAVAILABLE cases are decided before sequencer admission
and are safe to retry with backoff:
- maintenance refusal: REST
error: "OPERATIONS_BLOCKED"; gRPC metadatax-maintenance-blocked: true; - sequencer overload: REST
code: "OVERLOADED"; the same code appears in the gRPC status details.
429 / gRPC RESOURCE_EXHAUSTED
before it reaches the sequencer: nothing committed, so retry after the
interval in Retry-After.
