Skip to main content
BuilderCodes gives frontends two per-trade payout streams. An enrolled application can earn Monaco revenue share on taker-side fills and can charge its own builder fee. Each positive builder fee charged to a taker is credited in full to the enrolled application’s claimable payout balance with payout kind BUILDER_FEE. Revenue-share credits use kind REVENUE_SHARE.
The revenue share is switched by buildercodes_enabled, which defaults to false. While it is off, no revenue share is paid. Reads and claims keep working, so a balance already credited stays claimable. Ordinary builder-fee charging and crediting is controlled separately by builder_fee_enabled.

Attribution

Attribution follows 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.
  • A maker application’s enrollment does not earn BuilderCodes revenue for that fill.
  • 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 excluded from the revenue waterfall 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_FEE payouts.

Revenue waterfall

The waterfall runs independently for every fill, using deterministic integer math in the quote token’s RAW atomic units. For one fill, let gross be Monaco’s non-negative protocol take:
The maker fee is signed: a maker rebate is negative and reduces gross; a positive maker fee increases it. The application’s additional taker fee is not part of gross. The sequenced deduction order is:
  1. Monaco floor. Reserve floor(gross × monaco_floor_bps / 10000) for the treasury, capped at gross. This is a retention guard, not another transfer.
  2. BuilderCodes. While the revenue share is on, target floor(gross × buildercodes_budget_bps / 10000), capped at the amount left after the Monaco floor. While it is off, this step takes nothing. The realized rate can therefore be lower than the configured budget when the floor leaves too little; each payout row reports that realized whole-bps value as appliedBps.
  3. Gas reserve. Convert gas_reserve_fixed from a decimal quote-token amount to RAW units, then take at most the amount still available. A small fill can pay this only partially.
  4. TraderCodes. Use the remaining amount as the base for PitPass TraderCodes rates. Any part not paid as a TraderCodes reward remains with the treasury.
The Monaco floor and the gas reserve apply to every fill, whether or not the revenue share is on. The TraderCodes base is the mathematical remainder after the configured BuilderCodes and gas terms. It does not depend on whether those transfers actually occur. If an ineligible application receives no BuilderCodes credit, or a missing destination prevents a gas transfer, the unpaid term stays with the treasury instead of being added back to the TraderCodes base. Later steps cannot invade earlier ones. Gas cannot reduce the Monaco floor or the BuilderCodes credit, and TraderCodes cannot reduce any earlier carve. Integer division truncates toward zero, so a dust BuilderCodes result of zero produces no payout row.

Rollout configuration

These are the compiled defaults when a feature-flag document omits the keys: An active sequenced treasury account is also required for revenue-share and gas payouts. Ordinary builder-fee credits do not depend on this account. On initial warmup, an unset or unresolved treasury_multisig_address leaves no treasury account and therefore produces no revenue-share or gas payout. After an account has been installed, a configured-but-unresolved refresh retains that last identity, whereas explicitly removing the address through the admin treasury endpoint stops Monaco protocol-take attribution once the matching engine applies the removal. To shut down BuilderCodes revenue sharing, turn off buildercodes_enabled rather than removing the treasury address. Configuration and account changes are sequenced into matching-engine state so verification replay uses the same values as live settlement. When buildercodes_enabled is turned off, the revenue-share credit stops before any balance mutation or audit record is emitted, and that amount stays in the TraderCodes base. The Monaco floor and the gas reserve still apply. Ordinary builder-fee credits continue whenever builder_fee_enabled and enrollment make the fee chargeable. Existing BuilderCodes balances and payout history are kept, and frontend reads and claims keep working.

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 with 403 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 may be set while builder fees are disabled, but a real change must not exceed the live cap (never above 100 bps). A frontend may make two real changes in a rolling 24 hours; administrator changes do not count. Setting the current value is a successful no-op that consumes no change, even when the allowance is exhausted or the live cap has fallen below that stored value. A newly stored fee reaches order requests within seconds. Sequencer ordering is authoritative for the builder-fee 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. Builder fees are charged on taker fills only; the remainder of an order that rests on the book pays no 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. The change log is newest first and includes both frontend wallet changes and administrator changes; an administrator row exposes the operator’s email in actor. setBuilderFee requires an explicit bps (0 clears it). Invalid or above-cap changes return 400 / INVALID_ARGUMENT. 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 a personal_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. 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_enabled is 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.
See the TypeScript BuilderCodes reference, the gRPC BuilderCodes reference, and the BuilderCodes group under the REST API reference.

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:
For example, RAW 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 and byTokenAndKind[].totalAmount
  • reward balance available
  • claim request amount
  • claim response payoutBalance and tradingBalance
gas_reserve_fixed is the exception: it is a decimal amount in ordinary quote units. The matching engine scales it to the traded pair’s quote-token decimals and truncates sub-atomic precision.

Payout-address rotation

Changing a payout address affects future credits after the new enrollment view is sequenced. It does not move or merge balances already earned:
  • future fills accrue to the new wallet’s reserved bucket;
  • earlier credits remain in the old wallet’s bucket;
  • the old wallet can still call reward balance and claim;
  • current application config, history, and summary belong to the new payout wallet because those reads require the current address.
Do not clear a payout address while an application is enrolled. The admin control plane enforces this invariant and requires a payout address before enrollment.

Claim and withdraw

Read the wallet’s reserved balances, then claim a positive RAW amount for one token:
A claim is a sequenced, ledger-only move from the wallet’s reserved BuilderCodes bucket into its trading balance under the authenticated application. It has no on-chain transaction or gas cost. After a successful claim, use the ordinary withdrawal flow to move funds off the exchange. An over-claim is rejected in full with REST 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

Any untagged REST 5xx — not only 503 — an untagged gRPC UNAVAILABLE, or a transport failure that returned no response at all is an unknown claim outcome. The claim may commit after the caller loses its response, and there is no idempotency key. Treat it as pending; do not automatically retry and do not use an immediate unchanged balance read as proof that it failed.
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 failures retryable: 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 metadata x-maintenance-blocked: true;
  • sequencer overload: REST code: "OVERLOADED"; the same code appears in the gRPC status details.
A claim also spends the per-user movement budget that withdrawals and collateral transfers share. Over that budget, once it is enforced, the claim is refused with REST 429 / gRPC RESOURCE_EXHAUSTED before it reaches the sequencer: nothing committed, so retry after the interval in Retry-After.