# How the Akash Network Market Module Handles Bid Settlement and Lease Closure Events

> Understand how the Akash Network market module manages bid settlement and lease closure using escrow-triggered hooks for seamless state updates.

- Repository: [Akash Network/node](https://github.com/akash-network/node)
- Tags: internals
- Published: 2026-02-24

---

**The Akash Network market module processes bid settlement and lease closure through escrow-triggered hooks that cascade from group-level account closures to individual bid and lease state updates.**

The `akash-network/node` repository implements a sophisticated market mechanism where the **escrow module** drives settlement logic by invoking specific hooks in the market module. When escrow accounts or payments close due to fund exhaustion or completion, the market module executes a deterministic sequence of state transitions to finalize bids and leases. This architecture ensures that financial finality in the escrow layer automatically propagates to the market layer’s order book and lease registry.

## Settlement Architecture Overview

The bid settlement and lease closure flow operates through three coordinated steps triggered by escrow events:

1. **Escrow Account Closure** (`OnEscrowAccountClosed`) – Handles group-level shutdowns when a deployment runs out of funds
2. **Escrow Payment Closure** (`OnEscrowPaymentClosed`) – Handles individual bid settlement when a specific lease payment closes
3. **Keeper State Updates** – Updates internal state machines for bids, orders, and leases while emitting corresponding events

This hook-based design decouples financial settlement from market state management while ensuring atomic consistency between the escrow and market modules.

## Group-Level Closure via OnEscrowAccountClosed

When an escrow **account** closes—typically because a deployment has exhausted its funds—the escrow module invokes `hooks.OnEscrowAccountClosed` in [`x/market/hooks/hooks.go`](https://github.com/akash-network/node/blob/main/x/market/hooks/hooks.go) (lines 30–66).

The hook resolves the deployment ID, closes the active deployment, and iterates through all associated groups. For each group that can be closed, it executes:

```go
func (h *hooks) OnEscrowAccountClosed(ctx sdk.Context, obj etypes.Account) error {
    // Resolve deployment ID from escrow account
    // Close deployment if still active
    // Iterate groups and call dkeeper.OnCloseGroup
    // Call mkeeper.OnGroupClosed for each group
}

```

The `OnGroupClosed` method in [`x/market/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/market/keeper/keeper.go) (lines 78–109) then iterates through every **order** in the group. For each order, it runs `processClose`, which cascades to `OnBidClosed` and `OnLeaseClosed` for every bid attached to that order. This ensures that when a deployment’s escrow account drains, all associated market entities transition to closed states automatically.

## Bid-Level Settlement via OnEscrowPaymentClosed

Individual **bid settlement** occurs when an escrow **payment** closes, signaling the end of a specific lease. The escrow module triggers `hooks.OnEscrowPaymentClosed` in [`x/market/hooks/hooks.go`](https://github.com/akash-network/node/blob/main/x/market/hooks/hooks.go) (lines 70–115), which implements the core settlement logic:

```go
func (h *hooks) OnEscrowPaymentClosed(ctx sdk.Context, obj etypes.Payment) error {
    id, _ := mv1.LeaseIDFromPaymentID(obj.ID)
    bid   := h.mkeeper.GetBid(ctx, id.BidID())
    order := h.mkeeper.GetOrder(ctx, id.OrderID())
    lease := h.mkeeper.GetLease(ctx, id)

    // Close order
    if err := h.mkeeper.OnOrderClosed(ctx, order); err != nil { 
        return err 
    }

    // Close bid
    if err := h.mkeeper.OnBidClosed(ctx, bid); err != nil { 
        return err 
    }

    // Close lease with appropriate reason
    if obj.State.State == etypes.StateOverdrawn {
        err = h.mkeeper.OnLeaseClosed(ctx, lease,
            mv1.LeaseInsufficientFunds, 
            mv1.LeaseClosedReasonInsufficientFunds)
    } else {
        err = h.mkeeper.OnLeaseClosed(ctx, lease,
            mv1.LeaseClosed, 
            mv1.LeaseClosedReasonUnspecified)
    }
    return err
}

```

This method resolves the **Lease ID** from the payment ID, retrieves the associated bid, order, and lease objects, and orchestrates their closure. The closure **reason** depends on the escrow payment’s final state: `StateOverdrawn` triggers an insufficient-funds closure, while normal completion triggers a standard lease closure.

## State Machine Updates in the Market Keeper

The market keeper in [`x/market/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/market/keeper/keeper.go) implements three primary methods to handle the actual state transitions:

### OnBidClosed

The `OnBidClosed` method (lines 100–123) finalizes bid settlement by:

- Setting the bid state to `BidClosed` (unless already closed or lost)
- Invoking the escrow keeper to close the bid’s escrow account
- Emitting an `EventBidClosed` event for external consumers

### OnOrderClosed

The `OnOrderClosed` method (lines 124–146) handles order finalization:

- Updates the order state to `OrderClosed` if not already closed
- Emits `EventOrderClosed` to notify subscribers of the order book change

### OnLeaseClosed

The `OnLeaseClosed` method (lines 147–176) implements the final lease settlement logic:

- Validates the lease is not already in a terminal state (closed or insufficient funds)
- Updates the lease’s `State`, `ClosedOn` timestamp, and `Reason` fields
- Persists the lease to update all internal indexes
- Emits `EventLeaseClosed` with the appropriate closure reason

These keeper methods ensure that the market module’s internal state remains consistent with the escrow module’s financial finality, providing a complete audit trail through emitted events.

## Practical Implementation Examples

### Simulating Payment Closure in Tests

To verify that bid settlement triggers correctly when escrow payments close, you can simulate the hook invocation in test scenarios:

```go
func TestPaymentCloseTriggersLeaseClosure(t *testing.T) {
    ctx := sdk.NewContext(...)

    // Create a lease in active state
    lease := createTestLease(ctx, keeper, bidID)

    // Simulate escrow payment close with overdrawn state
    payment := etypes.Payment{
        ID:    lease.ID.ToEscrowPaymentID(),
        State: etypes.State{State: etypes.StateOverdrawn},
    }

    // Invoke the market hook as the escrow module would
    err := marketHooks.OnEscrowPaymentClosed(ctx, payment)
    require.NoError(t, err)

    // Verify lease reflects insufficient-funds closure
    l, ok := keeper.GetLease(ctx, lease.ID)
    require.True(t, ok)
    assert.Equal(t, mv1.LeaseInsufficientFunds, l.State)
    assert.Equal(t, mv1.LeaseClosedReasonInsufficientFunds, l.Reason)
}

```

### Direct Bid Closure Pattern

When implementing manual bid management or cleanup operations, use the keeper’s closure method directly:

```go
func closeBid(ctx sdk.Context, k keeper.IKeeper, bidID mv1.BidID) error {
    bid, ok := k.GetBid(ctx, bidID)
    if !ok {
        return fmt.Errorf("bid %s not found", bidID)
    }
    // Updates state, closes escrow account, emits EventBidClosed
    return k.OnBidClosed(ctx, bid)
}

```

## Summary

- The **escrow module** drives market settlement through the `OnEscrowAccountClosed` and `OnEscrowPaymentClosed` hooks in [`x/market/hooks/hooks.go`](https://github.com/akash-network/node/blob/main/x/market/hooks/hooks.go)
- **Group-level closures** cascade from deployment escrow accounts through orders to individual bids and leases via `OnGroupClosed`
- **Bid settlement** resolves the lease from the payment ID and orchestrates closure of the order, bid, and lease with appropriate state reasons
- The market keeper’s `OnBidClosed`, `OnOrderClosed`, and `OnLeaseClosed` methods enforce state consistency and emit `EventBidClosed`, `EventOrderClosed`, and `EventLeaseClosed` events
- Closure reasons distinguish between normal completion (`LeaseClosed`) and fund exhaustion (`LeaseInsufficientFunds`) based on the escrow payment’s `StateOverdrawn` status

## Frequently Asked Questions

### What triggers bid settlement in the Akash market module?

Bid settlement triggers when the escrow module closes a payment associated with an active lease. The escrow module invokes `OnEscrowPaymentClosed`, which resolves the bid-to-lease relationship and calls the market keeper’s `OnBidClosed` method to finalize the state transition and emit closure events.

### How does the market module distinguish between normal lease closure and insufficient funds closure?

The market module checks the escrow payment’s state within `OnEscrowPaymentClosed`. If `obj.State.State == etypes.StateOverdrawn`, the module calls `OnLeaseClosed` with `LeaseInsufficientFunds` and `LeaseClosedReasonInsufficientFunds`. Otherwise, it uses `LeaseClosed` with `LeaseClosedReasonUnspecified`, allowing external consumers to differentiate between natural completion and premature termination due to depleted funds.

### Where is the group-level closure logic implemented when a deployment runs out of funds?

Group-level closure logic resides in [`x/market/hooks/hooks.go`](https://github.com/akash-network/node/blob/main/x/market/hooks/hooks.go) within the `OnEscrowAccountClosed` function (lines 30–66). This hook iterates through all groups associated with the closed escrow account and invokes `OnGroupClosed` in the market keeper, which subsequently processes every order and bid within those groups to ensure complete market cleanup.

### What events are emitted during the bid settlement and lease closure process?

The market module emits three distinct events during settlement: `EventBidClosed` when `OnBidClosed` finalizes the bid state, `EventOrderClosed` when `OnOrderClosed` completes the order lifecycle, and `EventLeaseClosed` when `OnLeaseClosed` records the final lease disposition. These events provide a complete audit trail for block explorers and external monitoring systems tracking deployment lifecycles.