---
title: Transactions
type: docs
weight: 3
---

Transactions track the results of inventory operations in Hytale. Every modification to an `ItemContainer` returns a Transaction object that describes what happened.

## Transaction Interface

All transactions implement the base `Transaction` interface:

```java
public interface Transaction {
    // Did the operation succeed?
    boolean succeeded();

    // Was a specific slot modified?
    boolean wasSlotModified(short slot);
}
```

## Transaction Types

### SlotTransaction

Tracks changes to a single slot:

```java
SlotTransaction transaction = container.removeItemStackFromSlot((short) 0);

if (transaction.succeeded()) {
    // What was in the slot before
    ItemStack before = transaction.getSlotBefore();

    // What is in the slot now
    ItemStack after = transaction.getSlotAfter();

    // The item that was removed/output
    ItemStack output = transaction.getOutput();

    // Which slot was affected
    short slot = transaction.getSlot();

    // What type of action (ADD, REMOVE, REPLACE)
    ActionType action = transaction.getAction();
}
```

### ItemStackSlotTransaction

Extended slot transaction with additional details:

```java
ItemStackSlotTransaction transaction = container.setItemStackForSlot(
    (short) 0,
    new ItemStack("iron_sword", 1)
);

if (transaction.succeeded()) {
    short slot = transaction.getSlot();
    ItemStack before = transaction.getSlotBefore();
    ItemStack after = transaction.getSlotAfter();

    // Check options used
    boolean wasFiltered = transaction.isFilter();
    boolean wasAllOrNothing = transaction.isAllOrNothing();
}
```

### ItemStackTransaction

Tracks operations that may affect multiple slots:

```java
ItemStack toAdd = new ItemStack("stone", 128);
ItemStackTransaction transaction = container.addItemStack(toAdd);

if (transaction.succeeded()) {
    // Items that couldn't fit (null if all fit)
    ItemStack remainder = transaction.getRemainder();

    // Original item we tried to add
    ItemStack query = transaction.getQuery();

    // List of all slot transactions that occurred
    List<ItemStackSlotTransaction> slotTransactions = transaction.getSlotTransactions();

    for (ItemStackSlotTransaction slotTx : slotTransactions) {
        getLogger().at(Level.INFO).log("Modified slot " + slotTx.getSlot());
    }
}
```

### MoveTransaction

Tracks moving items between containers:

```java
MoveTransaction<SlotTransaction> transaction = storage.moveItemStackFromSlotToSlot(
    (short) 0,
    32,
    hotbar,
    (short) 0
);

if (transaction.succeeded()) {
    // Transaction for removing from source
    SlotTransaction removeTransaction = transaction.getRemoveTransaction();

    // Transaction for adding to destination
    SlotTransaction addTransaction = transaction.getAddTransaction();

    // The destination container
    ItemContainer destination = transaction.getOtherContainer();

    // Direction of the move
    MoveType moveType = transaction.getMoveType();
}
```

### ListTransaction

Wraps multiple transactions:

```java
List<ItemStack> items = List.of(
    new ItemStack("stone", 64),
    new ItemStack("wood", 64)
);

ListTransaction<ItemStackTransaction> transaction = container.addItemStacks(items);

if (transaction.succeeded()) {
    List<ItemStackTransaction> results = transaction.getList();

    for (ItemStackTransaction result : results) {
        if (result.succeeded()) {
            ItemStack remainder = result.getRemainder();
            // ...
        }
    }
}
```

## ActionType

Operations are categorized by action type:

```java
public enum ActionType {
    SET,      // Items set (add=true, remove=false, destroy=true)
    ADD,      // Items added to slot (add=true, remove=false, destroy=false)
    REMOVE,   // Items removed from slot (add=false, remove=true, destroy=false)
    REPLACE   // Slot contents replaced (add=true, remove=true, destroy=false)
}

// Check action characteristics
if (action.isAdd()) { /* operation adds items */ }
if (action.isRemove()) { /* operation removes items */ }
if (action.isDestroy()) { /* operation destroys slot contents */ }
```

## MoveType

Direction of move operations:

```java
public enum MoveType {
    MOVE_TO_SELF,   // Items being moved TO this container
    MOVE_FROM_SELF  // Items being moved FROM this container
}
```

## Common Patterns

### Check Before Modify

```java
// Safe pattern: verify first, then execute
public boolean safeTransfer(ItemContainer from, ItemContainer to, String itemId, int amount) {
    ItemStack toRemove = new ItemStack(itemId, amount);

    // Check both operations can succeed
    if (!from.canRemoveItemStack(toRemove)) {
        return false;  // Not enough items
    }

    if (!to.canAddItemStack(toRemove)) {
        return false;  // No space
    }

    // Execute removal
    ItemStackTransaction removeResult = from.removeItemStack(toRemove);
    if (!removeResult.succeeded()) {
        return false;
    }

    // Execute addition
    ItemStack removed = removeResult.getQuery();
    ItemStackTransaction addResult = to.addItemStack(removed);

    // Handle any remainder
    ItemStack remainder = addResult.getRemainder();
    if (!ItemStack.isEmpty(remainder)) {
        // Put remainder back
        from.addItemStack(remainder);
    }

    return true;
}
```

### Handling Remainders

```java
public void giveItemSafe(Player player, String itemId, int quantity) {
    ItemContainer storage = player.getInventory().getStorage();
    ItemStack item = new ItemStack(itemId, quantity);

    ItemStackTransaction result = storage.addItemStack(item);

    if (!result.succeeded()) {
        player.sendMessage(Message.raw("Inventory full!"));
        return;
    }

    ItemStack remainder = result.getRemainder();
    if (!ItemStack.isEmpty(remainder)) {
        player.sendMessage(Message.raw(
            "Only received " + (quantity - remainder.getQuantity()) + " items, " +
            "inventory full!"
        ));
    }
}
```

### Atomic Operations

```java
// allOrNothing=true ensures partial operations don't happen
public boolean buyItem(Player player, String itemId, int price, int quantity) {
    Inventory inv = player.getInventory();
    ItemContainer storage = inv.getStorage();

    ItemStack currency = new ItemStack("gold_coin", price);
    ItemStack item = new ItemStack(itemId, quantity);

    // Check both operations can succeed fully
    if (!storage.canRemoveItemStack(currency)) {
        player.sendMessage(Message.raw("Not enough gold!"));
        return false;
    }

    if (!storage.canAddItemStack(item)) {
        player.sendMessage(Message.raw("Inventory full!"));
        return false;
    }

    // Remove currency with allOrNothing=true
    ItemStackTransaction removeResult = storage.removeItemStack(currency, true, true);
    if (!removeResult.succeeded()) {
        return false;
    }

    // Add item
    ItemStackTransaction addResult = storage.addItemStack(item, true, false, true);
    if (!addResult.succeeded()) {
        // Rollback: return the currency
        storage.addItemStack(currency);
        return false;
    }

    return true;
}
```

### Tracking Changes

```java
// Use wasSlotModified to check specific slots
public void onContainerChange(ItemContainer.ItemContainerChangeEvent event) {
    Transaction transaction = event.transaction();

    // Check if hotbar slot 0 was affected
    if (transaction.wasSlotModified((short) 0)) {
        getLogger().at(Level.INFO).log("First slot was modified!");
    }

    // Check all hotbar slots
    for (short i = 0; i < 9; i++) {
        if (transaction.wasSlotModified(i)) {
            getLogger().at(Level.INFO).log("Hotbar slot " + i + " modified");
        }
    }
}
```

## Transaction Options

Many operations accept optional parameters:

| Parameter | Description | Default |
|-----------|-------------|---------|
| `allOrNothing` | If true, operation fails if not all items can be processed | `false` |
| `fullStacks` | If true, only add to empty slots (not partial stacks) | `false` |
| `exactAmount` | If true, must remove exact quantity requested | `true` |
| `filter` | If true, respect slot filters | `true` |

```java
// Default behavior
container.addItemStack(item);

// With options
container.addItemStack(item, true, false, true);  // allOrNothing, fullStacks, filter
container.removeItemStack(item, true, true);       // allOrNothing, filter
```

## Best Practices

{{< callout type="info" >}}
**Transaction Tips:**
- Always check `succeeded()` before accessing results
- Handle remainders when adding items
- Use `canAddItemStack()`/`canRemoveItemStack()` for pre-validation
- Use `allOrNothing=true` for critical operations
- Check `wasSlotModified()` to track specific slot changes
{{< /callout >}}

```java
// Good: Check success and handle remainder
ItemStackTransaction tx = container.addItemStack(item);
if (tx.succeeded()) {
    ItemStack remainder = tx.getRemainder();
    if (!ItemStack.isEmpty(remainder)) {
        // Handle leftover items
    }
}

// Bad: Assume success
container.addItemStack(item);  // Might fail silently!
```

## Transaction API Reference

### Transaction Interface

| Method | Returns | Description |
|--------|---------|-------------|
| `succeeded()` | `boolean` | True if operation succeeded |
| `wasSlotModified(short)` | `boolean` | True if specific slot was modified |

### SlotTransaction

| Method | Returns | Description |
|--------|---------|-------------|
| `getSlot()` | `short` | The affected slot index |
| `getAction()` | `ActionType` | Type of action performed |
| `getSlotBefore()` | `ItemStack?` | Contents before operation |
| `getSlotAfter()` | `ItemStack?` | Contents after operation |
| `getOutput()` | `ItemStack?` | Items removed/output |
| `isAllOrNothing()` | `boolean` | allOrNothing parameter used |
| `isExactAmount()` | `boolean` | exactAmount parameter used |
| `isFilter()` | `boolean` | filter parameter used |

| Static | Type | Description |
|--------|------|-------------|
| `FAILED_ADD` | `SlotTransaction` | Pre-built failed add transaction |

### ItemStackSlotTransaction (extends SlotTransaction)

| Method | Returns | Description |
|--------|---------|-------------|
| `isAddToExistingSlot()` | `boolean` | True if added to existing stack |
| `getQuery()` | `ItemStack?` | Original item requested |
| `getRemainder()` | `ItemStack?` | Items that couldn't fit |

### ItemStackTransaction

| Method | Returns | Description |
|--------|---------|-------------|
| `getAction()` | `ActionType?` | Type of action performed |
| `getQuery()` | `ItemStack?` | Original item requested |
| `getRemainder()` | `ItemStack?` | Items that couldn't fit |
| `isAllOrNothing()` | `boolean` | allOrNothing parameter used |
| `isFilter()` | `boolean` | filter parameter used |
| `getSlotTransactions()` | `List<ItemStackSlotTransaction>` | All slot transactions |

| Static | Type | Description |
|--------|------|-------------|
| `FAILED_ADD` | `ItemStackTransaction` | Pre-built failed add transaction |

### MoveTransaction<T extends Transaction>

| Method | Returns | Description |
|--------|---------|-------------|
| `getRemoveTransaction()` | `SlotTransaction` | Transaction for removal |
| `getMoveType()` | `MoveType` | Direction of move |
| `getOtherContainer()` | `ItemContainer` | The other container involved |
| `getAddTransaction()` | `T` | Transaction for addition |
| `toInverted(ItemContainer)` | `MoveTransaction<T>` | Create inverted view for other container |

### ListTransaction<T extends Transaction>

| Method | Returns | Description |
|--------|---------|-------------|
| `getList()` | `List<T>` | All transactions in list |
| `size()` | `int` | Number of transactions |

| Static | Type | Description |
|--------|------|-------------|
| `EMPTY_SUCCESSFUL_TRANSACTION` | `ListTransaction<?>` | Pre-built empty success |
| `EMPTY_FAILED_TRANSACTION` | `ListTransaction<?>` | Pre-built empty failure |
| `getEmptyTransaction(boolean)` | `ListTransaction<T>` | Get empty success/failure |

### ActionType Enum

| Value | isAdd | isRemove | isDestroy | Description |
|-------|-------|----------|-----------|-------------|
| `SET` | true | false | true | Set slot contents |
| `ADD` | true | false | false | Add items to slot |
| `REMOVE` | false | true | false | Remove items from slot |
| `REPLACE` | true | true | false | Replace slot contents |

### MoveType Enum

| Value | Description |
|-------|-------------|
| `MOVE_TO_SELF` | Items moving to this container |
| `MOVE_FROM_SELF` | Items moving from this container |
