# How Akash Handles Escrow Payments and Failure Scenarios

> Discover how Akash handles escrow payments and failure scenarios. Learn about its dual-object system, automatic overdrawn states, and explicit error returns for robust payment security.

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

---

**Akash Network uses a dual-object escrow system where Accounts hold user deposits and Payments stream funds to providers block-by-block, with automatic state transitions to Overdrawn when balances are insufficient and explicit error returns for all failure modes.**

The `x/escrow` module in the `akash-network/node` repository implements a deterministic payment system that protects both deployment owners and providers. This module manages **Akash escrow payments** through two primary objects—**Accounts** and **Payments**—which track funds and state transitions across the lifecycle of a deployment.

## Core Escrow Architecture

The escrow subsystem guarantees that providers are paid only when a deployment runs correctly and that users cannot lose funds if a provider fails to deliver. The logic lives in [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) and is driven by a **keeper** that manages two core objects persisted in the Cosmos SDK KV-store.

### The Account Object

An **Account** holds the total funds a user deposits for a deployment or bid. It tracks the owner, deposited funds, and settlement state.

- **State machine**: `Open → Overdrawn → Closed`
- **Key fields**: `Funds` (available balance), `Transferred` (total moved in), `SettledAt` (last block height processed)

### The Payment Object

A **Payment** tracks the rate-based payment stream for a specific provider. It links to an Account and accrues funds block-by-block based on a predefined rate.

- **State machine**: `Open → Overdrawn → Closed`
- **Key fields**: `Rate` (per-block payment amount), `Balance` (accrued but unwithdrawn funds), `Owner` (provider address)

## The Escrow Lifecycle

Both Accounts and Payments are **settled** on every state-changing operation (deposit, withdraw, close). Settlement attempts to apply any accrued payment based on block height; if the account lacks sufficient balance, the state moves to **Overdrawn**.

### Account Creation and Deposits

**`AccountCreate`** initializes an Account with zeroed transferred and settled balances, registers the owner, and stores the deposits. If the account ID already exists, it returns `ErrAccountExists` (see implementation around line 113 in [`keeper.go`](https://github.com/akash-network/node/blob/main/keeper.go)).

**`AccountDeposit`** moves funds from the depositor’s bank account to the escrow module, updates `Funds`, then triggers settlement. Attempting to deposit into a closed account returns `ErrAccountClosed` (guarded around line 43).

### Payment Creation and Streaming

**`PaymentCreate`** validates that the parent account is not overdrawn and ensures the rate is non-zero. It stores a new Payment linked to the account. Key failure modes include:
- `ErrAccountOverdrawn` – if the account lacks funds (checked around line 518)
- `ErrPaymentRateZero` – if the rate is zero (checked around line 534)
- `ErrPaymentExists` – if the payment ID is duplicate

### Settlement and Withdrawal Logic

**`accountSettle`** computes how many **full blocks** have elapsed since the last settlement (`SettledAt`). It then:
1. Calculates the owed amount (`rate * heightDelta`)
2. Deducts the owed amount from the account’s **Funds**
3. If funds go negative, the account (and all its payments) become **Overdrawn** and further payment creation is blocked

See the implementation around `accountSettle` (line 50) and `accountSettleFullBlocks` (line 82) in [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go).

**`PaymentWithdraw`** settles the account, picks the target payment, transfers any accrued balance to the provider, and updates state. It returns `ErrPaymentClosed` if the payment is not open (guarded around line 82).

**`PaymentClose`** ensures any remaining balance is withdrawn and forces the payment’s state to `Closed`. It returns `ErrPaymentClosed` if already closed (guarded around line 637).

**`AccountClose`** settles any outstanding payments, withdraws all payment balances, then marks the account `Closed`. It returns `ErrAccountClosed` if the account is already closed (guarded around line 300).

## Failure Scenarios and Error Handling

When an account becomes **Overdrawn**, the system still permits **payment withdrawals** (to recover any accrued balance) but blocks **new payment creation** and **account closure** until the overdrawn condition is resolved—typically by the provider returning funds or manual admin intervention.

| Scenario | Why it happens | Resulting state | Error returned |
|----------|----------------|-----------------|----------------|
| **Insufficient balance for a payment** | The escrow account cannot cover `rate * heightDelta`. | Account → **Overdrawn**; all linked payments → **Overdrawn**. | `ErrAccountOverdrawn` |
| **Attempt to create a duplicate account** | `AccountCreate` finds an existing KV entry. | No state change. | `ErrAccountExists` |
| **Closing an already closed account** | `AccountClose` sees `StateClosed`. | No state change. | `ErrAccountClosed` |
| **Depositing into a closed account** | `AccountDeposit` checks `StateClosed`. | No state change. | `ErrAccountClosed` |
| **Creating a payment with zero rate** | `PaymentCreate` validates `rate.IsZero()`. | No state change. | `ErrPaymentRateZero` |
| **Duplicate payment creation** | `PaymentCreate` finds existing payment ID. | No state change. | `ErrPaymentExists` |
| **Closing a payment that is already closed** | `PaymentClose` encounters `StateClosed`. | No state change. | `ErrPaymentClosed` |
| **Withdrawing from a closed payment** | `PaymentWithdraw` checks `StateOpen`. | No state change. | `ErrPaymentClosed` |

## Practical Implementation Examples

The following snippets illustrate the typical workflow using the keeper (the handler layer wraps these calls for gRPC/REST endpoints).

### 1. Create an escrow account for a bid

```go
// ctx – sdk.Context, keeper – escrow.Keeper, owner – sdk.AccAddress
id := escrowid.Account{
    Scope:  escrowid.ScopeBid,
    Owner:  owner.Bytes(),
    DSeq:   dseq,
    GSeq:   gseq,
    OSeq:   oseq,
}
deposits := []etypes.Depositor{
    {
        Owner:   owner.String(),
        Height:  ctx.BlockHeight(),
        Source:  deposit.SourceBalance,
        Balance: sdk.NewDecCoinFromCoin(sdk.NewCoin("uakt", sdk.NewInt(1_000_000))),
    },
}
err := keeper.AccountCreate(ctx, id, owner, deposits)

```

*Creates a fresh account; fails with `ErrAccountExists` if the same `id` already exists.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `AccountCreate`

### 2. Deposit additional funds

```go
more := []etypes.Depositor{{
    Owner:   owner.String(),
    Height:  ctx.BlockHeight(),
    Source:  deposit.SourceBalance,
    Balance: sdk.NewDecCoinFromCoin(sdk.NewCoin("uakt", sdk.NewInt(500_000))),
}}
err = keeper.AccountDeposit(ctx, id, more)

```

*Moves tokens from the user’s bank account into escrow; may trigger settlement and cause an overdrawn state.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `AccountDeposit`

### 3. Create a payment stream for a provider

```go
payID := escrowid.Payment{
    Account: id,
    Provider: provAddr.Bytes(),
}
rate := sdk.NewDecCoinFromCoin(sdk.NewCoin("uakt", sdk.NewInt(10))) // 10 uakt per block
err = keeper.PaymentCreate(ctx, payID, provAddr, rate)

```

*Registers a payment; will error with `ErrAccountOverdrawn` or `ErrPaymentRateZero` as appropriate.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `PaymentCreate`

### 4. Withdraw accrued payment (usually called each block)

```go
err = keeper.PaymentWithdraw(ctx, payID)

```

*Calculates owed amount, transfers it to the provider, and updates payment state.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `PaymentWithdraw`

### 5. Close a payment (e.g., when lease ends)

```go
err = keeper.PaymentClose(ctx, payID)

```

*Ensures any remaining balance is withdrawn and marks the payment `Closed`.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `PaymentClose`

### 6. Close the escrow account (when deployment is fully terminated)

```go
err = keeper.AccountClose(ctx, id)

```

*Settles all payments, withdraws any leftover funds, and finally puts the account in `Closed` state.*  
Source: [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) – `AccountClose`

## Key Source Files

| File | Role | Direct link |
|------|------|-------------|
| [`x/escrow/keeper/keeper.go`](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) | Core escrow logic – account/payment lifecycle, settlement, error handling. | [keeper.go](https://github.com/akash-network/node/blob/main/x/escrow/keeper/keeper.go) |
| [`x/escrow/module.go`](https://github.com/akash-network/node/blob/main/x/escrow/module.go) | Module wiring – registers handlers, querier, and genesis logic. | [module.go](https://github.com/akash-network/node/blob/main/x/escrow/module.go) |
| [`x/escrow/genesis.go`](https://github.com/akash-network/node/blob/main/x/escrow/genesis.go) | Genesis validation & initialization of escrow state. | [genesis.go](https://github.com/akash-network/node/blob/main/x/escrow/genesis.go) |
| [`x/market/handler/keepers.go`](https://github.com/akash-network/node/blob/main/x/market/handler/keepers.go) | Higher‑level market module that calls escrow keeper for bids/deployments. | [market/keepers.go](https://github.com/akash-network/node/blob/main/x/market/handler/keepers.go) |
| [`x/market/handler/handler_test.go`](https://github.com/akash-network/node/blob/main/x/market/handler/handler_test.go) | Comprehensive tests demonstrating overdrawn, settlement, and failure scenarios. | [handler_test.go](https://github.com/akash-network/node/blob/main/x/market/handler/handler_test.go) |

## Summary

- **Dual-object model**: **Accounts** hold user deposits while **Payments** stream funds to providers based on per-block rates.
- **Deterministic settlement**: The `accountSettle` function calculates owed amounts using block height deltas (`rate * heightDelta`), automatically transitioning states to **Overdrawn** when funds are insufficient.
- **Explicit failure modes**: Every error condition—duplicate accounts, closed states, zero rates, insufficient balances—returns a specific typed error (e.g., `ErrAccountOverdrawn`, `ErrPaymentRateZero`) rather than panicking.
- **Recovery paths**: Overdrawn accounts block new payment creation but allow withdrawals, ensuring providers can recover accrued funds while protecting users from further charges.

## Frequently Asked Questions

### What happens when an Akash escrow account runs out of funds?

When an account's balance cannot cover the calculated settlement amount (`rate * blocks_elapsed`), the `accountSettle` function transitions the account state to **Overdrawn** and propagates this state to all linked payments. While new payment creation is blocked with `ErrAccountOverdrawn`, providers can still withdraw any previously accrued balances.

### Can a provider withdraw funds if the deployment is still active?

Yes. The `PaymentWithdraw` method can be called at any time while the payment is in `StateOpen`. It triggers settlement for the account, calculates the accrued amount based on blocks elapsed since the last settlement, and transfers those funds to the provider's wallet. This is typically invoked periodically (e.g., every block) by the market module.

### What is the difference between an Account and a Payment in Akash escrow?

An **Account** represents the total escrowed funds for a specific deployment or bid scope, tracking the owner's deposited balance and overall state. A **Payment** represents a specific rate-based payment stream from that account to a single provider. While the Account holds the aggregate funds, Payments calculate per-block accrual rates and manage the actual transfer of value to providers.

### How does Akash prevent duplicate escrow entries?

The keeper methods `AccountCreate` and `PaymentCreate` both perform existence checks against the KV-store before initialization. `AccountCreate` returns `ErrAccountExists` if the composite ID (scope, owner, deployment sequence) is already present, while `PaymentCreate` returns `ErrPaymentExists` if a payment for that provider and account combination already exists.