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

# Cancel Orders

> Complete guide to cancelling NFT orders with soft (gasless) and hard (on-chain) cancellation

After [creating listings](https://docs.immutable.com/docs/products/orderbook/create-listings.md) or reviewing [filled orders](https://docs.immutable.com/docs/products/orderbook/fill-orders.md), you may need to cancel them. Learn how to cancel NFT orders using both soft (gasless) and hard (on-chain) cancellation methods.

> [!NOTE]
>
> See [Getting Started](https://docs.immutable.com/docs/products/orderbook/overview.md#getting-started) for prerequisites and installation. For cancellation, you also need the Order ID(s) and must own the orders.

## Trade Execution Architecture

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:

```mermaid theme={null}
sequenceDiagram
    Buyer->>Orderbook: 1. Request fulfillment
    Note over Orderbook: 2. Prepare payload (90s)
    Orderbook-->>Buyer: Transaction details
    Buyer->>Contract: 3. Submit signed tx
    Contract->>Contract: 4. Execute trade
    Note over Orderbook: Soft Cancel: Step 2
    Note over Contract: Hard Cancel: Step 4
```

### 5-Step Trade Flow

1. **Buyer selects order** - User chooses a listing via marketplace
2. **Orderbook prepares payload** - Immutable generates transaction details for settlement contract
3. **Buyer signs transaction** - User reviews and signs
4. **Transaction submitted** - Signed transaction sent to settlement contract
5. **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 |

> [!NOTE]
>
> **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](https://docs.immutable.com/docs/products/orderbook/overview.md#order-lifecycle) documentation.

## Soft Cancel (Gasless)

Soft cancellation is free and instant—the order is marked as cancelled off-chain, preventing new `fulfillments`.

### How It Works

1. User signs a message proving they own the order (EIP-712)
2. Orderbook marks order as cancelled
3. Orderbook stops generating transaction details for this order
4. Order becomes unfillable by new buyers

### Basic Example

```typescript theme={null}
import { orderbook } from '@imtbl/sdk';

async function softCancelOrders(`orderIds`: string[]) {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  const `userAddress` = await signer.getAddress();

  const { result } = await sdk.cancelOrders(`orderIds`, `userAddress`);

  console.log({
    successful: result.successful_cancellations,
    pending: result.`pending_cancellations`,      // ⚠️ May still execute
    failed: result.failed_cancellations,
  });
}
```

### Response Structure

The response categorizes cancellations into three arrays:

```typescript theme={null}
{
  "result": {
    "successful_cancellations": [
      "018a8c71-d7e4-e303-a2ef-318871ef7756",
      "458a8c71-d7e4-e303-a2ef-318871ef7778"
    ],
    "`pending_cancellations`": [
      "238a8c71-d7e4-e303-a2ef-318871ef7778"  // ⚠️ Active transaction in progress
    ],
    "failed_cancellations": [
      {
        "order": "458a8c71-d7e4-e303-a2ef-318871ef7790",
        "reason_code": "FILLED"  // Already filled
      }
    ]
  }
}
```

> [!WARNING]
>
> **Understanding `pending_cancellations`**
>
> Orders 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:
>
> 1. Buyer started fulfillment process before your cancel
> 2. Buyer received transaction details (Step 2) before cancel
> 3. Buyer has 90 seconds to submit the signed transaction
>
> If you see orders in `pending_cancellations`, consider using a [hard cancel](#hard-cancel-on-chain) for guaranteed cancellation.

### The 90-Second Race Condition

> [!WARNING]
>
> **Critical: Race Condition Window**
>
> The 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 data
> T=30s   You soft cancel the order
> T=60s   Order marked as cancelled in orderbook
> T=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.

### Validating Soft Cancel

Poll the order to confirm status transitions to `CANCELLED`:

```typescript theme={null}
async function validateCancellation(orderId: string) {
  let attempts = 0;

  while (attempts < 10) {
    const { result: order } = await sdk.getListing(orderId);

    if (order.status.name === 'CANCELLED') {
      console.log('Order cancelled!', {
        cancellationType: order.status.cancellation_type, // 'OFF_CHAIN'
      });
      return true;
    }

    if (order.status.name === 'FILLED') {
      console.log('❌ Order was filled before cancel took effect');
      return false;
    }

    await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1s
    attempts++;
  }

  console.log('⏳ Cancellation still processing...');
  return false;
}
```

### Limits

> [!NOTE]
>
> **Batch Limits:** You can cancel up to **20 orders** in a single soft cancel transaction. For more than 20, batch them into multiple requests.

```typescript theme={null}
// Cancel up to 20 orders at once
const `orderIds` = [
  'order-1', 'order-2', 'order-3', /* ... up to 20 ... */
];

const { result } = await sdk.cancelOrders(`orderIds`, `userAddress`);
```

## Hard Cancel (On-Chain)

Hard cancellation provides **definitive cancellation** by updating the settlement contract's blacklist. Costs gas but eliminates race conditions.

### How It Works

1. User sends transaction to settlement contract
2. Contract adds order to blacklist
3. Any future fulfillment attempts for this order will fail on-chain
4. Even if someone has a signed transaction, contract rejects it

### When to Use Hard Cancel

| Scenario | Recommended Cancel Type |
| - | - |
| High-value NFT (>\$1000) | Hard cancel |
| Suspicious buyer activity | Hard cancel |
| Algorithmic bot trading | Hard cancel |
| Order has `pending_cancellations` | Hard cancel |
| Regular user, low-value item | Soft cancel |
| Want to save gas | Soft cancel |

```mermaid theme={null}
flowchart TD
    A[Cancel Order] --> B{Value >$1000?}
    B -->|Yes| C[Hard Cancel]
    B -->|No| D{Bot/Trader?}
    D -->|Yes| C
    D -->|No| E{Pending txs?}
    E -->|Yes| C
    E -->|No| F[Soft Cancel]
```

### Basic Example

```typescript theme={null}
async function hardCancelOrders(`orderIds`: string[]) {
  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();

  // Prepare on-chain cancellation
  const { `signableAction` } = await sdk.prepareOrderCancellations(`orderIds`);

  // Execute on-chain (costs gas)
  const `txResponse` = await signer.sendTransaction({
    to: `signableAction`.to,
    data: `signableAction`.data,
  });

  console.log('Transaction sent:', `txResponse`.hash);

  const receipt = await `txResponse`.wait();
  console.log('Hard cancel confirmed in block', receipt.blockNumber);

  return receipt;
}
```

### Response

Hard cancel returns transaction details:

```typescript theme={null}
{
  "type": 2,
  "chainId": 31337,
  "to": "0x0165878A594ca255338adfa4d48449f69242Eb8F", // Settlement contract
  "data": "0xfd9f1e14f7f8cb7730d62bf4b15ecff270857...",
  "gasLimit": { "hex": "0xd821" },
  "hash": "0x3ad4833ff47ddef5982746935cdcf555631676e097e4e64218c593664f478e7a"
}
```

### Validating Hard Cancel

Poll the order after transaction confirms:

```typescript theme={null}
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;
}
```

> [!NOTE]
>
> **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.

## Complete Cancellation Flow

Full example with error handling and user choice:

```typescript theme={null}
import { Orderbook } from '@imtbl/orderbook';
import { Environment } from '@imtbl/config';
import { ethers } from 'ethers';

async function cancelOrderFlow(
  orderIds: string[],
  cancelType: 'soft' | 'hard' = 'soft'
) {
  <SdkInit />

  const provider = new ethers.providers.Web3Provider(window.ethereum);
  const signer = provider.getSigner();
  const userAddress = await signer.getAddress();

  try {
    if (cancelType === 'soft') {
      // Soft cancel (gasless)
      console.log('Initiating soft cancel...');

      const { result } = await sdk.cancelOrders(`orderIds`, `userAddress`);

      console.log({
        successful: result.successful_cancellations.length,
        pending: result.pending_cancellations.length,
        failed: result.failed_cancellations.length,
      });

      // Warn about pending cancellations
      if (result.pending_cancellations.length > 0) {
        console.warn('⚠️ Some orders have pending transactions:',
          result.pending_cancellations
        );
        console.warn('Consider hard cancel for guaranteed cancellation');

        // Optionally upgrade to hard cancel
        const shouldUpgrade = confirm(
          'Some orders may still execute. Upgrade to hard cancel (costs gas)?'
        );

        if (shouldUpgrade) {
          return cancelOrderFlow(result.pending_cancellations, 'hard');
        }
      }

      // Validate cancellations
      for (const orderId of result.successful_cancellations) {
        await validateCancellation(orderId);
      }

      return result;

    } else {
      // Hard cancel (costs gas)
      console.log('Initiating hard cancel...');

      const { signableAction } = await sdk.prepareOrderCancellations(orderIds);

      // Show gas estimate to user
      const gasEstimate = await provider.estimateGas({
        to: signableAction.to,
        data: signableAction.data,
      });

      const gasPrice = await provider.getGasPrice();
      const estimatedCost = gasEstimate.mul(gasPrice);

      console.log('Estimated gas cost:', ethers.utils.formatEther(estimatedCost), 'IMX');

      // Execute transaction
      const txResponse = await signer.sendTransaction({
        to: signableAction.to,
        data: signableAction.data,
      });

      console.log('Transaction sent:', txResponse.hash);
      const receipt = await txResponse.wait();
      console.log('Confirmed in block', receipt.blockNumber);

      // Validate cancellation
      for (const orderId of orderIds) {
        await validateHardCancel(orderId, receipt);
      }

      return { receipt, orderIds };
    }

  } catch (error: any) {
    console.error('Cancellation failed:', error.message);

    if (error.message.includes('not owner')) {
      console.error('❌ You do not own these orders');
    } else if (error.message.includes('FILLED')) {
      console.error('❌ Order already filled');
    } else if (error.message.includes('gas')) {
      console.error('❌ Insufficient gas for hard cancel');
    }

    throw error;
  }
}
```

## Decision Matrix: Soft vs Hard

Choose the right cancellation method based on your scenario:

```typescript theme={null}
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';
}
```

## Error Handling

Common errors during cancellation:

| Error | Cause | Solution |
| - | - | - |
| `NOT_OWNER` | User doesn't own the order | Verify ownership |
| `ORDER_FILLED` | Order already filled | Remove from UI |
| `ORDER_CANCELLED` | Order already cancelled | Remove from UI |
| `INSUFFICIENT_GAS` | Not enough gas for hard cancel | Request funding |
| `TRANSACTION_FAILED` | Hard cancel transaction reverted | Check network, retry |

## Best Practices

1. **Check `pending_cancellations`:** Warn users about race conditions
2. **Show gas estimates:** For hard cancels, display gas cost before execution
3. **Poll for status:** Don't assume immediate cancellation
4. **Batch cancels:** Group multiple soft cancels into one request (max 20)
5. **Upgrade path:** Offer hard cancel if soft cancel returns pending
6. **Expiration check:** If order expires soon, soft cancel sufficient
7. **Value-based logic:** Use hard cancel for high-value orders automatically

## Understanding Gas

> [!NOTE]
>
> **What is Gas?**
>
> Gas is the computational fee required to perform blockchain transactions. It serves two purposes:
>
> 1. **Incentivize `Validators`:** Pays network `validators` to include your transaction
> 2. **Prevent Spam:** Makes it costly to abuse the network
>
> **Who Pays Gas in Orderbook:**
>
> * **Creating orders:** Free for both buyers and sellers (gasless signatures)
> * **Filling orders:** Buyers pay gas to execute the trade
> * **Canceling orders:**
>   * Soft cancel: Free (gasless)
>   * Hard cancel: Sellers pay gas for on-chain cancellation
>
> This is why soft cancels are recommended for casual users—they preserve the gasless experience.

## Next Steps

> [card] [Create Listings](https://docs.immutable.com/docs/products/orderbook/create-listings.md)
>
> Learn how to create NFT listings

> [card] [Fill Orders](https://docs.immutable.com/docs/products/orderbook/fill-orders.md)
>
> Buy NFTs by filling orders

> [card] [Order Management](https://docs.immutable.com/docs/products/orderbook/order-management.md)
>
> Query and manage order status

> [card] [Bulk Operations](https://docs.immutable.com/docs/products/orderbook/bulk-operations.md)
>
> Cancel multiple orders efficiently

> [card] [Fees](https://docs.immutable.com/docs/products/orderbook/fees.md)
>
> Understand gas costs for hard cancels


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