> ## 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](/docs/products/orderbook/create-listings) or reviewing [filled orders](/docs/products/orderbook/fill-orders), you may need to cancel them. Learn how to cancel NFT orders using both soft (gasless) and hard (on-chain) cancellation methods.

<Info>
  See [Getting Started](/docs/products/orderbook/overview#getting-started) for prerequisites and installation. For cancellation, you also need the Order ID(s) and must own the orders.
</Info>

## 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        |

<Info>
  **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](/docs/products/orderbook/overview#order-lifecycle) documentation.
</Info>

## 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.
</Warning>

### 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.
</Warning>

### 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

<Info>
  **Batch Limits:** You can cancel up to **20 orders** in a single soft cancel transaction. For more than 20, batch them into multiple requests.
</Info>

```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;
}
```

<Info>
  **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.
</Info>

## 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

<Info>
  **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.
</Info>

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Listings" icon="tag" href="/docs/products/orderbook/create-listings">
    Learn how to create NFT listings
  </Card>

  <Card title="Fill Orders" icon="cart-shopping" href="/docs/products/orderbook/fill-orders">
    Buy NFTs by filling orders
  </Card>

  <Card title="Order Management" icon="list-check" href="/docs/products/orderbook/order-management">
    Query and manage order status
  </Card>

  <Card title="Bulk Operations" icon="layer-group" href="/docs/products/orderbook/bulk-operations">
    Cancel multiple orders efficiently
  </Card>

  <Card title="Fees" icon="percent" href="/docs/products/orderbook/fees">
    Understand gas costs for hard cancels
  </Card>
</CardGroup>
