Builder Codes
Builder Codes let an application charge its own fee on the orders it routes. The SDK covers the whole lifecycle: the user's approval, the per-order attachment, the approval and reward reads, and the builder's claim.
import { TxFlow } from 'txflow-typescript-sdk'
const sdk = new TxFlow('testnet')Fee units
The same fee appears in three different representations, so convert deliberately:
| Where | Field | Format | Example |
|---|---|---|---|
approveBuilderFee | maxFeeRate | Percentage string, greater than 0% and at most 1% | '0.02%' |
order attachment | builder.f | Integer in tenths of a basis point, rate = f / 100_000 | 10 is 0.01% |
sdk.info.maxBuilderFee | return value | Integer in tenths of a basis point, 0 when unapproved | 20 is 0.02% |
sdk.info.approvedBuilders | maxFeeRate | Percentage string | '0.02%' |
Approve a builder
approveBuilderFee must be signed by the user's owner wallet. An authorized trading agent cannot approve on the owner's behalf.
sdk.exchange.setWallet(ownerWallet)
const result = await sdk.exchange.approveBuilderFee({
builder: '0x2e4D75df2eE2653EA7a113cC1Fa36D45E923061F',
maxFeeRate: '0.02%',
})
if (result.status !== 'ok') {
throw new Error(String(result.response))
}Submitting the action again for the same builder updates the rate instead of consuming another approval slot. Protocol limits — 20 builders per user, and the rejected builder addresses — are listed under approveBuilderFee.
Attach a builder to orders
After the approval is on-chain, order actions may carry one action-level builder object. It is signed together with the orders, so the SDK's normal agent signer applies:
sdk.exchange.setPrivateKey(agentPrivateKey)
await sdk.exchange.placeLimitOrder({
asset: 1,
side: 'buy',
px: '60000',
sz: '0.001',
tif: 'gtc',
builder: {
b: '0x2e4D75df2eE2653EA7a113cC1Fa36D45E923061F',
f: 10,
},
})placeMarketOrder accepts the same optional builder. For batches, placeOrder takes it as a third argument and applies it once to the whole action:
await sdk.exchange.placeOrder(
[
{ asset: 1, isBuy: true, limitPx: '60000', sz: '0.001', orderType: { limit: { tif: 'gtc' } }, reduceOnly: false },
{ asset: 2, isBuy: false, limitPx: '3000', sz: '0.01', orderType: { limit: { tif: 'gtc' } }, reduceOnly: false },
],
'na',
{ b: '0x2e4D75df2eE2653EA7a113cC1Fa36D45E923061F', f: 10 },
)Read approvals
const user = '0xOwnerAddress'
const builder = '0x2e4D75df2eE2653EA7a113cC1Fa36D45E923061F'
const approvals = await sdk.info.approvedBuilders({ user })
const approvedRate = await sdk.info.maxBuilderFee({ user, builder })Both methods return either the data or an error object, so narrow the result before using it:
if (!Array.isArray(approvals)) {
throw new Error(approvals.message)
}
for (const approval of approvals) {
console.log(approval.builder, approval.maxFeeRate)
}
if (typeof approvedRate !== 'number') {
throw new Error(approvedRate.message)
}
const canAttach = approvedRate >= 10approvedBuilders returns a top-level array sorted by builder address. maxBuilderFee returns 0 when the builder has no active approval.
Builder state and rewards
builderState describes the builder's own account, so pass the builder address in user:
const state = await sdk.info.builderState({ user: builderAddress })
if ('message' in state) {
throw new Error(state.message)
}
console.log(state.claimableRewards, state.eligible)| Field | Meaning |
|---|---|
accruedRewards | Total fees earned so far |
claimableRewards | Currently unclaimed balance |
claimedRewards | Already claimed total |
eligible | Whether the builder currently accrues fees |
perpsAccountValue | Available perp-account balance backing eligibility |
isStandardAccount | Whether the account type can act as a builder |
activeUserCount | Users currently approving the builder |
Builder fees are additional to exchange fees and accrue only while the builder holds at least 100 USDC in its perpetual account.
Builder analytics dashboard
Six additional Info reads power a Builder's analytics dashboard. Like builderState, they take the builder wallet address in user, and return aggregate metrics rather than the on-chain account snapshot.
| Method | Use |
|---|---|
builderMeOverview({ user }) | Lifetime users, volume, earned fees, claim totals, rank, and weekly stats |
builderMeDailySeries({ user, metric?, startDate?, endDate? }) | Daily fee or user-count series with cumulative totals |
builderMeShare({ user, dimension?, window? }) | Fee share by trading user or instrument |
builderMeEarned({ user, startTime?, endTime?, page?, pageSize? }) | Paginated earnings aggregated by order |
builderMeClaimed({ user, startTime?, endTime?, page?, pageSize? }) | Paginated reward claim history |
builderMeClaimable({ user }) | Current claimable balance and lifetime earned/claimed totals |
const builderAddress = '0xBuilderAddress'
const [overview, claimable, earned] = await Promise.all([
sdk.info.builderMeOverview({ user: builderAddress }),
sdk.info.builderMeClaimable({ user: builderAddress }),
sdk.info.builderMeEarned({
user: builderAddress,
startTime: Date.now() - 7 * 24 * 60 * 60 * 1000,
page: 0,
pageSize: 50,
}),
])In builderMeEarned, each row's user field is the masked trading-user address, not the builder. Multiple fills for the same order are merged into one row; total counts orders, not fills.
Wire schemas and field descriptions are in the Info API Builder section.
claimBuilderRewards takes no parameters and moves the whole claimable balance. It must be signed by an agent authorized by the builder account, and fails when the claimable balance is zero:
const claim = await sdk.exchange.claimBuilderRewards()
if (claim.status !== 'ok') {
console.error(claim.response)
}Revoke an approval
sdk.exchange.setWallet(ownerWallet)
await sdk.exchange.revokeBuilderFee({
builder: '0x2e4D75df2eE2653EA7a113cC1Fa36D45E923061F',
})This is owner-signed like the approval, and fails with Builder not approved when no active approval exists. After propagation the approval's maxFeeRate is 0.
Signer requirements
| Method | Signer |
|---|---|
sdk.exchange.approveBuilderFee | The user's owner wallet |
sdk.exchange.revokeBuilderFee | The user's owner wallet |
sdk.exchange.placeOrder with builder | The user's trading agent, as for any order |
sdk.exchange.claimBuilderRewards | An agent authorized by the builder account |
sdk.info.* builder reads | None |
Switch signers with setWallet or setPrivateKey before an owner-signed action, or keep separate clients for the owner wallet, the trading agent, and the builder's agent. See Authentication.
The two approval actions use direct EIP-712 signing, with distinct ApproveBuilderFee and RevokeBuilderFee primary types. Browser wallets sign with their connected chain as domain.chainId, and the SDK forwards that value as the payload-level signatureChainId; the business chainId inside the signed struct remains unchanged. claimBuilderRewards continues to use the tagged Agent flow. Protocol details are under Builder-fee actions.
Standalone functions
Every builder method has a tree-shakable equivalent:
import {
approveBuilderFee,
approvedBuilders,
builderMeClaimable,
builderMeClaimed,
builderMeDailySeries,
builderMeEarned,
builderMeOverview,
builderMeShare,
builderState,
claimBuilderRewards,
createExchangeContext,
createInfoContext,
maxBuilderFee,
} from 'txflow-typescript-sdk'
const info = createInfoContext('testnet')
const state = await builderState(info, { user: builderAddress })
const overview = await builderMeOverview(info, { user: builderAddress })
const exchange = createExchangeContext({
network: 'testnet',
privateKey: builderAgentPrivateKey,
})
await claimBuilderRewards(exchange)claimBuilderRewards takes only the context; the other functions use fn(context, params).
Protocol reference
builderMeOverview— builder dashboard overviewbuilderMeDailySeries— daily fee or user seriesbuilderMeShare— fee share breakdownbuilderMeEarned— order-level earningsbuilderMeClaimed— claim historybuilderMeClaimable— claimable balanceapproveBuilderFee— approval constraints and signingrevokeBuilderFee— revocation semanticsclaimBuilderRewards— claim behavior- Attach a builder to an order — wire shape and fee caps
Next steps
- Exchange API — the full signed method catalog
- Info API — typed read patterns