> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# BuilderCodes

> Attribute taker flow, inspect per-trade revenue share, and claim frontend payouts

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

<Warning>
  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`.
</Warning>

## 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 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
credits continue whenever builder fees are on and the application is enrolled,
and existing balances, payout history, reads and claims are unaffected.

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

| Purpose | TypeScript SDK | REST | gRPC |
| - | - | - | - |
| Current program state | `sdk.buildercodes.getConfig()` | `GET /api/v1/buildercodes/config` | `GetBuildercodesConfig` |
| Set the stored builder fee | `sdk.buildercodes.setBuilderFee({ bps })` | `PUT /api/v1/buildercodes/builder-fee` | `SetBuilderFee` |
| Builder-fee change history | `sdk.buildercodes.listBuilderFeeChanges(params)` | `GET /api/v1/buildercodes/builder-fee/changes` | `ListBuilderFeeChanges` |
| Per-trade payout history, optionally one payout `kind` | `sdk.buildercodes.listPayouts(params)` | `GET /api/v1/buildercodes/payouts` | `ListBuildercodePayouts` |
| Per-token payout totals, and per token and `kind` | `sdk.buildercodes.getPayoutSummary()` | `GET /api/v1/buildercodes/payouts/summary` | `GetBuildercodePayoutSummary` |
| Reserved payout balances | `sdk.buildercodes.getRewardsBalance()` | `GET /api/v1/buildercodes/rewards/balance` | `GetBuildercodeRewardsBalance` |
| Claim into a trading balance | `sdk.buildercodes.claimBuildercodeRewards(params)` | `POST /api/v1/buildercodes/rewards/claim` | `ClaimBuildercodeRewards` |

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 is charged once the matching engine's next BuilderCodes
enrollment refresh sequences it, within about 30 seconds by default. 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. 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.

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

See the [TypeScript BuilderCodes reference](/sdk/typescript/buildercodes), the
[gRPC BuilderCodes reference](/grpc/protos/buildercodes), and the BuilderCodes
group under the [REST API reference](/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:

```text theme={null}
display amount = RAW amount / 10^token_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`

## 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:

```typescript theme={null}
const { balances } = await sdk.buildercodes.getRewardsBalance();
const usdc = balances.find((balance) => balance.token === usdcAddress);

if (usdc) {
  const result = await sdk.buildercodes.claimBuildercodeRewards({
    token: usdc.token,
    amount: usdc.available,
  });

  console.log(result.payoutBalance, result.tradingBalance);
}
```

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](/sdk/typescript/vault) 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

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

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](/developers/rate-limits)
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`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.