Skip to main content
BuilderCodes gives frontends three per-trade payout streams. An enrolled application can earn Monaco revenue share on taker-side fills, and can charge its own users a builder fee on each side of a fill:
  • 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 credits use kind REVENUE_SHARE. When a taker from one frontend fills a maker from another, each frontend is credited the fee its own user paid.
The revenue share can be switched off. getConfig() reports it as buildercodesEnabled; while it is false, no revenue share is credited. Reads and claims keep working, so a balance already credited stays claimable. Builder-fee charging is reported separately as builderFeeEnabled.

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. Choose a Safe when you want several people to approve moving the earnings. Choose an ordinary wallet for the simplest setup. Each payout wallet belongs to one frontend.

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_FEE payouts.

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.
  • buildercodesBudgetBps on getConfig() is your application’s configured target share. The target can differ per client application.
  • Each payout row’s appliedBps is 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.
When the revenue share is switched off, revenue-share credits stop. Builder-fee and maker add-on credits continue whenever builder fees are on and the application is enrolled, and existing balances, payout history, reads and claims are unaffected.

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_FEE payout.
  • Cap and switches. The same live cap (builderFeeCapBps, never above 100 bps) and the same switches as the builder fee: nothing is charged while builderFeeEnabled is false or 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.
The maker add-on is set with the builder fee, through the same endpoint, and read back as 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 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 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 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. 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_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, byTokenAndKind[].totalAmount and builderFeeByToken[].totalAmount
  • reward balance available
  • claim request amount
  • claim response payoutBalance and tradingBalance

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.
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.