TP/SL orders are off-chain until triggered
When you create a TP or SL order, it is not submitted to the Injective network. Instead:- You sign a
SignedTakerIntentwith your taker private key. - The signed intent is submitted to the RFQ indexer, which stores it off-chain.
- The executor monitors the mark price. When your trigger condition is met, it requests normal RFQ liquidity and submits
AcceptSignedIntentonchain with the resulting maker quote.
- No onchain record of the pending order exists before it fires.
- If the indexer goes down, your trigger order won’t execute until it recovers.
- Your signed intent has a deadline (max 30 days). If it expires before the trigger fires, the order is dead.
Settlement can fail silently
When a trigger fires, the executor must request maker quotes and submit a validAcceptSignedIntent transaction. Several things can prevent successful execution:
No maker quotes available
If no maker is quoting at the moment the trigger fires - because they’re offline, have insufficient balance, or have withdrawn liquidity - there are no quotes to fill against. The executor cannot construct a valid settlement transaction.Contract rejection
Even with quotes, the onchain settlement can fail if:- The maker quote has expired by the time the transaction lands
- The maker’s balance is insufficient
- The
lane_versionhas advanced (another intent already settled on this lane)
worst_price gap in fast-moving markets
This is the most important edge case for protective orders.
When you create a TP/SL, you sign a worst_price — the worst execution price you’ll accept. This value is fixed at creation time. At execution time, the contract validates the maker’s quote price against your worst_price and checks the trigger against the live mark price.
In a fast-moving market, this creates a gap scenario:
Stop loss example:
- You open a long at $5.00 and set a stop loss at $4.80 with
worst_price = $4.75. - The market drops sharply. The mark price blows through $4.80 and reaches $4.60 before the executor can land the transaction.
- At execution time, the trigger condition (mark ≤ $4.80) is satisfied. But no maker is willing to quote at $4.75 or better when the mark price is at $4.60 — the asset is now worth less than your floor.
- The settlement fails. Your stop loss did not execute.
- You open a short at $5.00 and set a take profit at $4.80 with
worst_price = $4.85. - The market drops rapidly through $4.80 to $4.50.
- At execution time, makers are quoting around $4.50. Your
worst_priceof $4.85 is the minimum you’ll accept (for a short, this is the ceiling). Quotes at $4.50 are actually more favorable than $4.85, so this fills successfully.
worst_price gaps hurt when the market moves through your trigger level and keeps going in the adverse direction. For stop losses, this means the market moved too far against you. For take profits on the wrong side, it means you might miss a fill — but the more common case is that take profits fill fine because the price moved in your favor.
This is expected behavior under the signed intent model. The worst_price is your hard limit — it exists to protect you from a fill at an unacceptable price. The tradeoff is that in a gap scenario, you get no fill rather than a bad fill.
No retry mechanism in v1
If a triggered TP/SL fails to settle - for any of the reasons above - the executor flags it as failed but does not retry. The intent is effectively dead. This means:- A stop loss that fails during a flash crash will not re-attempt once conditions stabilize.
- A take profit that fails due to quote expiry will not try again with fresh quotes.
- You must monitor your intent status and manually re-create failed orders.
Creating TP/SL without an open position
The signed intent mechanism does not enforce that you have an open position at creation time. You can sign and submit a TP/SL for a market where you have no position.- The indexer accepts the intent and stores it.
- The contract rejects at execution time, because the settlement tries to close a position that doesn’t exist.
One active TP/SL per trigger type per market
The lane mechanism enforces a one-intent-per-lane rule. A lane is defined as(taker, market_id, subaccount_nonce), and each lane has a lane_version.
When you create a new TP/SL for a lane that already has an active intent:
- The new intent must use the current
lane_version. - But the old intent also references the current
lane_version. - When either intent settles or you cancel the lane, the
lane_versionincrements, invalidating the other.
SDK users vs. frontend users
The frontend wraps these mechanics with guardrails that hide most failure modes:
If you are building a programmatic trading system, you accept responsibility for:
- Setting appropriate
worst_pricevalues that account for potential gaps - Monitoring intent lifecycle (created → triggered → settled or failed)
- Re-creating intents after failures or cancellations
- Querying position state before creating protective orders
- Managing
lane_versionandepochto avoid stale intents

