Complete guide to cancelling NFT orders with soft (gasless) and hard (on-chain) cancellation
After creating listings or reviewing filled orders, you may need to cancel them. Learn how to cancel NFT orders using both soft (gasless) and hard (on-chain) cancellation methods.
See Getting Started for prerequisites and installation. For cancellation, you also need the Order ID(s) and must own the orders.
Immutable offers a centralized orderbook but doesn’t act as an intermediary during trades. Instead, trades settle through a smart contract, ensuring secure peer-to-peer trading.To understand cancellation, first understand how trades execute:
Transaction submitted - Signed transaction sent to settlement contract
Settlement executes - Contract swaps assets if all details valid
This creates two cancellation opportunities:
Cancel Type
Intercepts At
Gas Cost
Definitive?
Soft Cancel
Step 2 (orderbook)
Free
No - 90s race condition
Hard Cancel
Step 5 (settlement contract)
Costs gas
Yes - guaranteed
Why Two Methods?
Soft cancel prevents the orderbook from providing transaction details (Step 2). Gasless but has race condition.
Hard cancel blacklists the order in the settlement contract (Step 5). Costs gas but is definitive—even if someone has a signed transaction, it will fail.
For complete order status progression, see the Order Lifecycle documentation.
Understanding pending_cancellationsOrders in pending_cancellations have an active transaction payload already issued to a buyer. These orders may still execute even though your cancel was accepted by the orderbook.This happens when:
Buyer started fulfillment process before your cancel
Buyer received transaction details (Step 2) before cancel
Buyer has 90 seconds to submit the signed transaction
If you see orders in pending_cancellations, consider using a hard cancel for guaranteed cancellation.
Critical: Race Condition WindowThe execution window is 90 seconds from when the orderbook provides transaction details (Step 2), NOT from when you initiated the cancel.Timeline:
T=0s Buyer starts fulfillment, gets transaction dataT=30s You soft cancel the orderT=60s Order marked as cancelled in orderbookT=90s Transaction data expires
During the 90-second window, your asset may still be exchanged at the previously agreed price if the buyer submits their signed transaction.The race condition exists for LESS than 90 seconds from when the cancel was accepted—but you don’t control when the buyer started Step 2.
Batch Limits: You can cancel up to 20 orders in a single soft cancel transaction. For more than 20, batch them into multiple requests.
// Cancel up to 20 orders at onceconst `orderIds` = [ 'order-1', 'order-2', 'order-3', /* ... up to 20 ... */];const { result } = await sdk.cancelOrders(`orderIds`, `userAddress`);
async function validateHardCancel(orderId: string, `txReceipt`: any) { console.log('Transaction confirmed, waiting for orderbook to process...'); let attempts = 0; while (attempts < 20) { const { result: order } = await sdk.getListing(orderId); if (order.status.name === 'CANCELLED') { console.log('Hard cancel processed!', { cancellationType: order.status.cancellation_type, // 'ON_CHAIN' }); return true; } await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s attempts++; } console.log('⏳ Still processing on-chain events...'); return false;}
Async Status Updates: After the transaction confirms, Immutable services must detect the on-chain event and update the off-chain orderbook. This usually takes a few seconds but can take longer during network congestion.
Choose the right cancellation method based on your scenario:
function decideCancellationType(order: any, `userType`: 'casual' | 'trader' | 'bot') { // Get order value in USD (example) const orderValueUSD = calculateUSDValue(order.buy.amount); // High-value orders: always hard cancel if (orderValueUSD > 1000) { return 'hard'; } // Bots need certainty: always hard cancel if (`userType` === 'bot') { return 'hard'; } // Check if order is expiring soon const `expiresIn` = new Date(order.end_at).getTime() - Date.now(); if (`expiresIn` < 60000) { // Less than 1 minute return 'soft'; // Will expire naturally, save gas } // Active traders willing to pay gas if (`userType` === 'trader' && orderValueUSD > 100) { return 'hard'; } // Default: soft cancel for casual users return 'soft';}