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

# Bulk Operations

> Create multiple listings and fulfill multiple orders in single transactions for efficient marketplace operations

Learn how to batch multiple orderbook operations into single transactions, improving user experience and reducing gas costs. Essential for marketplace shopping carts and bulk listing management.

## Overview

Bulk operations enable users to:

* **Bulk Listings:** Create up to **20 listings** with one signature
* **Bulk Fulfillment:** Buy up to **50 orders** in one transaction (shopping cart)

**Benefits:**

* Fewer wallet confirmations (better UX)
* Lower gas costs (batched transactions)
* Faster marketplace operations
* Enable shopping cart functionality

<Info>
  See [Getting Started](/docs/products/orderbook/overview#getting-started) for prerequisites and installation. For bulk operations, you should also understand [creating individual listings](/docs/products/orderbook/create-listings) and [filling individual orders](/docs/products/orderbook/fill-orders).
</Info>

***

# Bulk Listing Creation

Create multiple NFT listings with a single signature, improving seller experience.

## Limits and Constraints

| Constraint           | Value                                              | Notes                                        |
| -------------------- | -------------------------------------------------- | -------------------------------------------- |
| **Maximum listings** | 20 per transaction                                 | Batch into multiple transactions if needed   |
| **Wallet type**      | EOA: 1 signature<br />Smart contract: N signatures | Passport wallets need multiple confirmations |
| **Approval**         | Per collection                                     | One-time approval per NFT collection         |

## How It Works

```mermaid theme={null}
sequenceDiagram
    User->>SDK: prepareBulkListings(20 items)
    SDK-->>User: completeListings()
    User->>Wallet: Sign message
    Wallet-->>User: Signature
    User->>SDK: completeListings(sig)
    SDK->>Orderbook: Submit all
    Orderbook-->>SDK: Results
```

The `prepareBulkListings()` call returns:

1. **Actions** - Approval transactions (if needed) + `signable` message
2. **completeListings()** method - Scoped method to submit signatures

```typescript theme={null}
const { actions, completeListings } = await sdk.prepareBulkListings({
  makerAddress: `userAddress`,
  listingParams: [...], // Array of listing configs
});
```

## Basic Example

Create multiple ERC-721 listings at once:

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

async function createBulkListings() {
  <SdkInit />

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

  // Define multiple listings
  const `listingParams` = [
    {
      sell: {
        type: 'ERC721',
        contractAddress: '0x123...',
        tokenId: '1',
      },
      buy: {
        type: 'NATIVE',
        amount: '1000000000000000000', // 1 IMX
      },
      makerFees: [{
        `recipientAddress`: MARKETPLACE_WALLET,
        amount: '10000000000000000', // 0.01 IMX
      }],
    },
    {
      sell: {
        type: 'ERC721',
        contractAddress: '0x123...',
        tokenId: '2',
      },
      buy: {
        type: 'NATIVE',
        amount: '2000000000000000000', // 2 IMX
      },
      makerFees: [{
        `recipientAddress`: MARKETPLACE_WALLET,
        amount: '20000000000000000', // 0.02 IMX
      }],
    },
    // ... up to 20 listings total
  ];

  // 1. Prepare bulk listings
  const { actions, completeListings } = await sdk.prepareBulkListings({
    makerAddress: `userAddress`,
    `listingParams`,
  });

  // 2. Handle approval transactions (one per collection)
  for (const action of actions) {
    if (action.type === orderbook.ActionType.TRANSACTION) {
      console.log('Requesting approval for collection...');
      const unsignedTx = await action.buildTransaction();
      const txResponse = await signer.sendTransaction(unsignedTx);
      await txResponse.wait();
      console.log('Approved!');
    }
  }

  // 3. Sign the bulk listing message
  const `signableAction` = actions.find(
    (action) => action.type === orderbook.ActionType.SIGNABLE
  );

  if (`signableAction`) {
    const signature = await signer._signTypedData(
      signableAction.message.domain,
      signableAction.message.types,
      signableAction.message.value
    );

    // 4. Submit all listings with the signature
    const results = await completeListings(signature);

    console.log('Created listings:', {
      successful: results.result.filter(r => r.success).length,
      failed: results.result.filter(r => !r.success).length,
    });

    return results;
  }
}
```

## Mixed Token Types

Create listings for both ERC-721 and ERC-1155 tokens:

```typescript theme={null}
const `listingParams` = [
  // ERC-721 listing
  {
    sell: {
      type: 'ERC721',
      contractAddress: '0xAAA...',
      tokenId: '123',
    },
    buy: {
      type: 'NATIVE',
      amount: '1000000000000000000',
    },
    makerFees: [],
  },
  // ERC-1155 listing
  {
    sell: {
      type: 'ERC1155',
      contractAddress: '0xBBB...',
      tokenId: '456',
      amount: '10', // Selling 10 copies
    },
    buy: {
      type: 'NATIVE',
      amount: '5000000000000000000', // 5 IMX total
    },
    makerFees: [],
  },
];
```

## Smart Contract Wallets (Passport)

<Warning>
  **[Passport](/docs/products/passport/wallet) & Smart Contract Wallets**

  When using smart contract wallets like [Passport](/docs/products/passport/wallet), users must sign **separate confirmations for each listing**. Unlike EOA wallets (MetaMask) which can sign once for all 20 listings, smart contract wallets require individual signatures.

  This is due to smart contract wallet architecture and cannot be batched.
</Warning>

## Response Structure

The `completeListings()` method returns success/failure for each listing:

```typescript theme={null}
const results = await completeListings(signature);

console.log(results.result);
// [
//   { success: true, order: {...}, `order_hash`: "0x..." },
//   { success: true, order: {...}, `order_hash`: "0x..." },
//   { success: false, reason: "INSUFFICIENT_BALANCE" },
// ]
```

Handle partial failures gracefully:

```typescript theme={null}
const successful = results.result.filter(r => r.success);
const failed = results.result.filter(r => !r.success);

console.log(`Created ${successful.length} listings`);

if (failed.length > 0) {
  console.warn(`❌ Failed to create ${failed.length} listings:`);
  failed.forEach((f, i) => {
    console.log(`- Listing ${i}: ${f.reason}`);
  });
}
```

***

# Bulk Order Fulfillment (Shopping Cart)

Buy multiple NFTs in a single transaction—the foundation of marketplace shopping carts.

## Limits and Constraints

| Constraint                  | Value                    | Notes                                    |
| --------------------------- | ------------------------ | ---------------------------------------- |
| **Maximum orders**          | 50 per transaction       | Batch into multiple if needed            |
| **Currency consistency**    | All orders same currency | Enforce on frontend                      |
| **Best-effort fulfillment** | Enabled by default       | Fills available orders even if some fail |

## How It Works

```mermaid theme={null}
flowchart TD
    A[50 Orders] --> B[Validate]
    B --> C[Fulfillable: 40]
    B --> D[Unfulfillable: 10]
    C --> E{Balance OK?}
    E -->|Yes| F[Execute]
    E -->|No| G[Partial Fill]
    F --> H[Results]
    G --> H
```

Bulk fulfillment handles common shopping cart scenarios:

* Some orders already filled by others
* Some orders cancelled
* Insufficient balance for all items
* Orders with mixed availability

The SDK provides:

1. **fulfillableOrders** - Can be executed
2. **unfulfillableOrders** - Cannot be executed (with reasons)
3. **sufficientBalance** - Whether user can afford fulfillable orders
4. **Actions** - Transactions to execute

## Basic Example

Simple shopping cart checkout:

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

  // Prepare bulk fulfillment
  const { result } = await sdk.fulfillBulkOrders({
    orderIds,
    takerAddress: buyerAddress,
    takerFees: [
      {
        recipientAddress: MARKETPLACE_WALLET,
        amount: '50000000000000000', // 0.05 IMX marketplace fee
      },
    ],
  });

  console.log('Cart Analysis:', {
    fulfillable: result.fulfillableOrders.length,
    unfulfillable: result.unfulfillableOrders.length,
    hasSufficientBalance: result.sufficientBalance,
  });

  // Check if user can afford fulfillable orders
  if (!result.sufficientBalance) {
    const totalCost = result.fulfillableOrders.reduce(
      (sum, order) => sum + BigInt(order.buy.amount),
      0n
    );
    console.error(`Insufficient balance. Need: ${ethers.utils.formatEther(totalCost)} IMX`);
    return;
  }

  // Execute all actions
  for (const action of result.actions) {
    if (action.type === orderbook.ActionType.TRANSACTION) {
      const unsignedTx = await action.buildTransaction();
      const txResponse = await signer.sendTransaction(unsignedTx);

      console.log(`Transaction sent: ${txResponse.hash}`);
      await txResponse.wait();
    }
  }

  console.log('Purchased', result.fulfillableOrders.length, 'NFTs!');
}
```

## Response Structure

The response categorizes orders:

```typescript theme={null}
{
  result: {
    fulfillableOrders: [
      {
        order_id: "order-1",
        buy: { type: "NATIVE", amount: "1000000000000000000" },
        sell: { type: "ERC721", contract_address: "0x...", token_id: "1" }
      },
      {
        order_id: "order-2",
        buy: { type: "NATIVE", amount: "2000000000000000000" },
        sell: { type: "ERC721", contract_address: "0x...", token_id: "2" }
      }
    ],
    unfulfillableOrders: [
      {
        order_id: "order-3",
        reason: "FILLED" // Already sold
      },
      {
        order_id: "order-4",
        reason: "CANCELLED" // Seller cancelled
      }
    ],
    sufficientBalance: true,
    actions: [
      { type: "TRANSACTION", purpose: "APPROVAL" },
      { type: "TRANSACTION", purpose: "FULFILL_ORDER" }
    ],
    expiration: "2024-01-15T10:30:00Z"
  }
}
```

## UX Strategies for Unavailable Items

When some cart items become unavailable, choose a user experience strategy:

### Strategy 1: All or Nothing

Require users to fix cart before checkout:

```typescript theme={null}
const { result } = await sdk.fulfillBulkOrders({
  orderIds: cartOrderIds,
  takerAddress: buyerAddress,
  takerFees: [],
});

if (result.unfulfillableOrders.length > 0) {
  // Show user which items are unavailable
  console.error('Some items are no longer available:');
  result.unfulfillableOrders.forEach(order => {
    console.log(`- Order ${order.order_id}: ${order.reason}`);
  });

  throw new Error('Please remove unavailable items from cart');
}

// All items available, proceed with checkout
executeActions(result.actions);
```

### Strategy 2: Best Effort

Automatically checkout available items without user intervention:

```typescript theme={null}
const { result } = await sdk.fulfillBulkOrders({
  orderIds: cartOrderIds,
  takerAddress: buyerAddress,
  takerFees: [],
});

// Show summary
console.log(`Purchasing ${result.fulfillableOrders.length} of ${cartOrderIds.length} items`);

if (result.unfulfillableOrders.length > 0) {
  console.log('The following items were removed from your cart:');
  result.unfulfillableOrders.forEach(order => {
    console.log(`- ${order.reason}`);
  });
}

// Proceed with available items
if (result.fulfillableOrders.length > 0) {
  await executeActions(result.actions);
}
```

### Strategy 3: Hybrid (Recommended)

Ask user to confirm before proceeding with partial cart:

```typescript theme={null}
const { result } = await sdk.fulfillBulkOrders({
  orderIds: cartOrderIds,
  takerAddress: buyerAddress,
  takerFees: [],
});

if (result.unfulfillableOrders.length > 0) {
  const message = `
    ${result.unfulfillableOrders.length} items are no longer available.
    Would you like to purchase the remaining ${result.fulfillableOrders.length} items?
  `;

  const shouldProceed = confirm(message);

  if (!shouldProceed) {
    console.log('Purchase cancelled by user');
    return;
  }
}

// User confirmed or all items available
await executeActions(result.actions);
```

## Insufficient Balance Handling

When user can't afford fulfillable items:

```typescript theme={null}
const { result } = await sdk.fulfillBulkOrders({
  orderIds: cartOrderIds,
  takerAddress: buyerAddress,
  takerFees: [],
});

if (!result.sufficientBalance) {
  // Calculate how much they need
  const totalCost = result.fulfillableOrders.reduce(
    (sum, order) => sum + BigInt(order.buy.amount),
    0n
  );

  // Get user's current balance
  const balance = await provider.getBalance(buyerAddress);

  const shortfall = totalCost - balance;

  console.error(`Insufficient Balance:
    Need: ${ethers.utils.formatEther(totalCost)} IMX
    Have: ${ethers.utils.formatEther(balance)} IMX
    Short: ${ethers.utils.formatEther(shortfall)} IMX
  `);

  // Optionally: suggest removing most expensive items
  const sortedByPrice = result.fulfillableOrders
    .sort((a, b) => BigInt(b.buy.amount) - BigInt(a.buy.amount));

  console.log('Consider removing:', sortedByPrice[0].order_id);
  return;
}

// Has sufficient balance, proceed
```

## Currency Consistency Requirement

<Warning>
  **All orders must use the same currency**

  You cannot mix NATIVE and ERC-20 orders in a single bulk fulfillment. Enforce this on your frontend:

  ```typescript theme={null}
  function validateCartCurrency(orders: Order[]): boolean {
    if (orders.length === 0) return true;

    const firstCurrency = orders[0].buy.type;
    const firstContract = orders[0].buy.contract_address;

    return orders.every(order => {
      if (order.buy.type !== firstCurrency) return false;
      if (firstCurrency === 'ERC20' && order.buy.contract_address !== firstContract) {
        return false;
      }
      return true;
    });
  }

  // Before checkout
  if (!validateCartCurrency(cartItems)) {
    throw new Error('All items must be priced in the same currency');
  }
  ```
</Warning>

## Partial Fills with Bulk Orders

Specify `amountToFill` for ERC-1155 partial fills:

```typescript theme={null}
const { result } = await sdk.fulfillBulkOrders({
  orderIds: ['order-1', 'order-2'],
  takerAddress: buyerAddress,
  takerFees: [],
  amountToFill: {
    'order-1': '5', // Buy 5 of available ERC-1155 items
    'order-2': undefined, // Full fill
  },
});
```

If requesting more than available, best-effort fills up to max:

```typescript theme={null}
// Order has 5 items available
amountToFill: {
  'order-1': '10', // Request 10
  // Will fill only 5 (max available)
}
```

## Complete Shopping Cart Example

Full implementation with all strategies:

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

interface CartItem {
  orderId: string;
  nftName: string;
  price: string;
  currency: string;
}

async function checkoutShoppingCart(cart: CartItem[]) {
  <SdkInit />

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

  try {
    // 1. Validate cart
    if (cart.length === 0) {
      throw new Error('Cart is empty');
    }

    if (cart.length > 50) {
      throw new Error('Maximum 50 items per checkout');
    }

    // 2. Calculate marketplace fee (1% of total)
    const totalValue = cart.reduce(
      (sum, item) => sum + BigInt(item.price),
      0n
    );
    const marketplaceFee = (totalValue * 100n) / 10000n;

    // 3. Prepare bulk fulfillment
    console.log('Analyzing cart...');

    const { result } = await sdk.fulfillBulkOrders({
      orderIds: cart.map(item => item.orderId),
      takerAddress: buyerAddress,
      takerFees: [{
        recipientAddress: MARKETPLACE_WALLET,
        amount: marketplaceFee.toString(),
      }],
    });

    // 4. Handle unavailable items
    if (result.unfulfillableOrders.length > 0) {
      console.warn(`⚠️ ${result.unfulfillableOrders.length} items unavailable:`);

      result.unfulfillableOrders.forEach(order => {
        const item = cart.find(c => c.orderId === order.order_id);
        console.log(`- ${item?.nftName}: ${order.reason}`);
      });

      // Ask user to confirm
      const shouldProceed = confirm(
        `Proceed with ${result.fulfillableOrders.length} available items?`
      );

      if (!shouldProceed) {
        console.log('Checkout cancelled');
        return;
      }
    }

    // 5. Check balance
    if (!result.sufficientBalance) {
      const fulfillableCost = result.fulfillableOrders.reduce(
        (sum, order) => sum + BigInt(order.buy.amount),
        0n
      );

      const balance = await provider.getBalance(buyerAddress);

      alert(`Insufficient balance
        Need: ${ethers.utils.formatEther(fulfillableCost)} IMX
        Have: ${ethers.utils.formatEther(balance)} IMX
      `);

      return;
    }

    // 6. Show final summary
    const fulfillableCost = result.fulfillableOrders.reduce(
      (sum, order) => sum + BigInt(order.buy.amount),
      0n
    );

    console.log('Final Checkout:', {
      items: result.fulfillableOrders.length,
      subtotal: ethers.utils.formatEther(fulfillableCost),
      marketplaceFee: ethers.utils.formatEther(marketplaceFee),
      total: ethers.utils.formatEther(fulfillableCost + marketplaceFee),
      expires: new Date(result.expiration),
    });

    // 7. Execute all actions
    console.log('Processing payment...');

    for (const action of result.actions) {
      if (action.type === orderbook.ActionType.TRANSACTION) {
        console.log(`Executing: ${action.purpose}`);

        const unsignedTx = await action.buildTransaction();
        const txResponse = await signer.sendTransaction(unsignedTx);

        console.log(`Transaction sent: ${txResponse.hash}`);
        const receipt = await txResponse.wait();
        console.log(`Confirmed in block ${receipt.blockNumber}`);
      }
    }

    console.log('Purchase complete!');

    return {
      purchased: result.fulfillableOrders.length,
      unavailable: result.unfulfillableOrders.length,
      transactionHash: result.actions[result.actions.length - 1].transactionHash,
    };

  } catch (error: any) {
    console.error('Checkout failed:', error.message);
    throw error;
  }
}
```

## Best Practices

### For Bulk Listings

1. **Batch wisely:** Keep under 20 listings per batch
2. **Handle approvals:** Cache approval status per collection
3. **Smart contract wallets:** Warn users about multiple signatures
4. **Error handling:** Gracefully handle partial failures
5. **Status updates:** Poll for PENDING → ACTIVE transitions

### For Bulk Fulfillment

1. **Currency enforcement:** Validate same currency on frontend
2. **Real-time availability:** Refresh cart items before checkout
3. **Balance checks:** Verify sufficient funds before transaction
4. **User communication:** Clearly explain unavailable items
5. **Expiration warning:** Show 3-minute countdown timer
6. **Retry logic:** Handle race conditions gracefully
7. **Gas estimation:** Show estimated gas cost for transparency

## Next Steps

<CardGroup cols={2}>
  <Card title="Create Listings" icon="tag" href="/docs/products/orderbook/create-listings">
    Single listing creation guide
  </Card>

  <Card title="Order Management" icon="list-check" href="/docs/products/orderbook/order-management">
    Query bulk-created orders
  </Card>

  <Card title="Fill Orders" icon="cart-shopping" href="/docs/products/orderbook/fill-orders">
    Single order fulfillment guide
  </Card>

  <Card title="Cancel Orders" icon="xmark" href="/docs/products/orderbook/cancel-orders">
    Soft and hard cancellation
  </Card>

  <Card title="Fees" icon="percent" href="/docs/products/orderbook/fees">
    Understanding marketplace fees
  </Card>
</CardGroup>
