- A standalone bracket (
orderType: "bracket") protects an asset the funder already holds. It is a sell order with a fixedqtyand two legs. See Standalone brackets. - An attached bracket rides a
market,limit, ortwaporder and protects what that order receives. It is placed in the same quote, sign, and submit flow as the entry. See Attached brackets.
expireTime, both read back with their legs, and both work with minimal authorization.
Standalone brackets
SetorderType: "bracket" with side: "sell", a qty in the sold asset, and exactly two triggers with opposite triggerType: the upper leg is the take-profit, the lower leg the stop-loss. Each leg takes exactly one of notionalPrice (USD) or crossPrice (pair rate, target / contra). Legs have no limit price; whichever fires sells qty at market. expireTime is optional: set it for good-til-time, omit it for good-til-cancelled.
To protect 0.05 WETH on Base with a take-profit at $5,000 and a stop-loss at $3,000, expiring in a week:
"orderType": "bracket" with both legs under brackets and trigger null:
brackets array on every frame for the order.
A few rules specific to standalone brackets:
- Any number per asset. A funder may rest several brackets on the same asset at once, each with its own
qty. Protection is per bracket: a 0.02 WETH bracket and a 0.03 WETH bracket each sell their ownqtywhen they fire. - Neither trigger may already be met at placement. A leg whose trigger price has already been reached when the order is accepted is not fired; the order is closed with
closeReasonREASON_ORDER_TRIGGERED_ON_ENTRYand nothing is sold. Place the take-profit above and the stop-loss below the current price. - Keep
qtyin the funder wallet. The exit pulls the sold asset from the funder wallet when a leg fires. If the wallet holds less thanqtyat that moment, or the allowance was revoked, the exit fails and the order closes without selling. A resting bracket whose funder wallet has been emptied of the sold asset is cancelled before either leg fires, withcloseReasonREASON_INSUFFICIENT_FUNDER_BALANCE; see Automatic cancellation. - Expiry. With
expireTimethe bracket closesREASON_ORDER_EXPIREDat that instant if neither leg has fired. There is no execution buffer: a leg that fires in the final seconds beforeexpireTimecloses expired without executing, the same as astop-lossortake-profit.
forceMinimalAllowance, the authorization on the sold asset is sized to this bracket’s qty plus every other open order that sells the same asset, including your other brackets.
Attached brackets
An attached bracket adds a take-profit / stop-loss pair to an order at placement time. One quote returns two signing payloads, you sign both with the funder wallet, and one submit places the entry order with its protection attached. The pair protects what the entry receives: when a leg’s trigger is reached it sells the received asset, and the entry stops filling at that moment. For the target/contra and pricing-basis concepts, see Placing Orders; for standalone trigger orders, see Trigger Orders. Attached brackets are available on same-chain EVM and Solana orders withorderType market, limit, or twap, and require funderAddress on the quote request. On EVM the entry must receive an ERC-20: an entry buying the native gas asset cannot carry a bracket, so quote to the wrapped asset (WETH rather than ETH) instead. On Solana, bracket quotes are available only through POST /quote, not the streaming quote subscription. The pair is good-til-cancelled unless you give it its own expireTime inside the attachedBracket block; see Pair expiry. The guide below walks the EVM flow; the Solana differences are the signing primitive and the funding actions — the lifecycle is identical.
The attachedBracket block
Add attachedBracket to the quote request. It carries two legs, and each leg takes exactly one trigger price plus an optional limit price for the exit.
Each leg:
Three rules tie the legs together:
- Leg prices are on the asset the entry receives (the quote’s
toleg) — the target asset on a buy, the contra asset on a sell. - Both legs must use the same basis: both
notionalPriceor bothcrossPrice. - The
takeProfittrigger must be above thestopLosstrigger. - On a
limitentry, express the entry’s limit aslimitCrossPrice.limitNotionalPriceis rejected together with the block.
limitPrice exits as a limit order once it fires: the exit rests at that price and fills only there or better. It does not fall back to a market exit, so it can stay unfilled if the market keeps moving away — the classic stop-limit versus stop-market trade-off, and the choice is yours per leg. It matters most on a stop-loss leg, since by the time the exit rests, the other leg and the entry’s unfilled remainder are already cancelled. Omit limitPrice to exit at market.
For example, to buy WETH with 100 USDC on Base and protect the position with a take-profit at $5,000 and a stop-loss at $3,000:
limit or twap quote request unchanged; the entry’s own fields (limit price, duration, and so on) follow the usual rules for its type.
Two signatures from one quote
The quote response is the usual one — pricing, fees, and the entry’sevm signing and funding actions — plus an attachedBracket block for the pair:
evm.orderTypedData for the entry, once over attachedBracket.evm.orderTypedData for the pair.
The pair sells the received asset, so that asset may need its own funding actions before submit, exactly like the spent asset does for the entry: send attachedBracket.evm.approveTx if it is non-null, and if attachedBracket.evm.permitTypedData is non-null, sign it and echo the payload and signature back as attachedBracket.evmPermitTypedData / attachedBracket.evmPermitSignature on submit.
By default the authorization the quote returns for the received asset is unlimited: an approve for the maximum amount, or a Permit2 permit for the maximum amount with no expiration. If the wallet’s existing authorization already covers signedMaxFromAmount (and, for Permit2, outlives the pair), the quote returns none for the pair. With forceMinimalAllowance the pair’s authorization is sized instead to signedMaxFromAmount plus every other open order that sells the received asset, and a Permit2 permit’s expiration follows the pair’s expireTime, or stays unlimited when the pair has none.
The signature caps the protection. The pair’s signature authorizes selling up to signedMaxFromAmount of the received asset, a value derived from the entry’s expected output with headroom on top: currently the quoted output plus 5% for a market entry, twice the output implied by the limit price for a limit entry, and twice the quoted output for a twap entry. Protection is capped there: if the entry ends up receiving more than the signed maximum, the excess is not covered by the exits. Receiving less than the signed maximum is normal — the maximum is headroom, not a target. The trigger prices are not part of either signature; the legs ride the quote and submit requests themselves.
Submit
Submit the order with the sameattachedBracket legs plus the signature material echoed from the quote:
orderId plus the pair’s starting status:
On Solana
The flow is the same four steps — quote, fund, sign twice, submit — with Solana’s signing primitive and funding actions. See Solana for the base flow the entry follows. Quote. The response carries the entry’ssvm block as usual, plus the pair’s own signing material under attachedBracket.svm:
svm.nonce) and the pair’s (attachedBracket.svm.nonce). They are always different values; echo each one on its own block at submit.
Fund. The pair sells the received asset, and that asset’s token account needs its own one-time delegate approval, exactly like the spent asset’s does for the entry. Execute any svm.ataSetupIxs from the entry block first (token accounts must exist before delegation), then attachedBracket.svm.delegateIx when it is non-null. That delegation is for the maximum amount by default, or for signedMaxFromAmount plus the funder’s other open orders on the asset under forceMinimalAllowance; the quote returns it only when the token account doesn’t already delegate the required amount to Flash. When Definitive sponsors the approvals, both the entry’s and the pair’s ride one combined transaction on the entry-level svm.sponsoredDelegateTx — sign and echo it back as usual, and attachedBracket.svm.delegateIx is null.
Sign and submit. Produce a 64-byte Ed25519 signature over each message with the funder wallet: the entry’s svm.orderMessage as userSignature, and attachedBracket.svm.orderMessage as attachedBracket.userSignature (base58 or hex). The submit block echoes svmNonce instead of salt, and there are no permit fields on Solana:
Lifecycle
At submit the pair is not an order yet. It activates on the entry’s first fill.pending_activation— submitted, waiting for the entry’s first fill. There is nothing to protect yet, and no pair order to read — a GET for the pair returns not-found until activation, so track it through the entry’sattachedBracketobject or the stream.- Active — on the first fill the pair becomes a live order with its own
orderId, protecting what the entry has received so far. Later entry fills grow the protected amount, up to the signed maximum — do not assume the pair’s size is immutable. never_activated— the entry ended without ever filling (cancelled, or expired unfilled). The pair never becomes an order.
GET /v1/orders/{orderId}?funderAddress=0x… on the entry returns an attachedBracket object:
bracketOrderId is how you find it. Its own row reads as "orderType": "bracket", "side": "sell", and carries sourceEntryOrderId pointing back at the entry. It appears alongside your other orders — poll GET /v1/orders (same funderAddress query parameter) or stream the Orders WebSocket, whose entry frames carry attachedBracket with the status and bracketOrderId (the leg configuration rides the GET response only) — and you can cancel it by its own orderId (see Cancelling Orders). Reads can trail a placement or cancel by a second or two while the order projects; retry shortly rather than treating a just-placed order’s absence as an error.
Cancelling the entry after a fill does not cancel the pair; it keeps protecting whatever the entry received. Cancelling the entry before any fill means the pair never activates. The independence runs both ways: cancelling the pair leaves the entry working, unprotected.
When a leg fires
Three things happen at once when either trigger is reached:- The exit is placed. The pair sells the received asset — at market, or resting at the leg’s
limitPrice. - The other leg is cancelled. The two legs are one-cancels-other (OCO): only one exit ever executes.
- The entry stops filling. Any unfilled remainder of the entry is cancelled. If the entry has already finished, there is no remainder to cancel and only the sale happens.
Pair expiry
SetattachedBracket.expireTime on the quote request to give the pair its own good-til-time expiry. It must be in the future and, when the entry has an expireTime, no earlier than it: a pair that ended before its entry finished filling would protect nothing. The pair’s signature carries it as the deadline, and once the pair is an order it closes REASON_ORDER_EXPIRED at that instant if neither leg has fired. If the deadline passes while the pair is still pending_activation and the entry fills afterwards, the pair activates and closes expired at once. There is no execution buffer: a leg that fires in the final seconds before expireTime closes expired without executing. Omit the field and the pair stays live until it fills or you cancel it.
The entry’s attachedBracket.expiresAt and the pair order’s own expiresAt both read the value back.
An exit can execute across more than one transaction. If an entry fill lands after a leg has fired — a fill already in flight when the trigger was reached — it is sold as it lands.
Activation is at the first fill
A resting entry keeps resting through a price gap; attaching a bracket does not change that. The pair activates on the entry’s first fill, and a leg whose trigger is already true at that moment fires immediately. Concretely: place a limit buy at $3,900 with a take-profit at $4,400 and a stop-loss at $3,000, and if the price gaps straight down through the entry price and the stop, the entry fills into the falling market and the stop-loss sells the position at once. You can buy into a falling market and be stopped out immediately — the protection engages the moment there is something to protect, at whatever the market is by then. The same applies on the other side: a take-profit already true at first fill takes profit immediately.End-to-end example
Prerequisites. The example uses
viem. All requests go to https://flash.definitive.fi/v1 with your key in the x-definitive-api-key header. The public integrator key dpka_513a2bd7_57a2_46d2_927b_2a3857fe271b is safe for development. The onchain steps also need an RPC endpoint and the funder wallet’s key, supplied here through environment variables.qty is denominated in the contra asset (USDC), and the pair protects the WETH received. If the spent or received token needs an allowance, the quote returns approve calldata (or a Permit2 payload) for it; handle whichever comes back for both the entry and the pair, then sign both payloads and submit.
To track the entry after submit, poll
GET /orders or stream live updates over the Orders WebSocket. Once the pair activates it is an order of its own and shows up on the same surfaces.Trigger Orders
Single-leg stop, stop-loss, and take-profit orders.
Placing Orders
The full parameter reference for every order type.