Lease Lifecycle

The states a compute lease moves through

A compute lease passes through creation, acceptance, and settlement. Each phase produces an on-chain block that the ledger validates. Two escape hatches complete the state machine: the consumer can cancel a lease no provider has accepted, and can force-settle an accepted lease the provider abandoned.

Lease state is tracked on a Lease record with a State field:

created ──── lease_accept ────► accepted ──── lease_settle ───────► settled
   │                               │
   │                               ├──── lease_force_settle ──────► unfulfilled
   │                               │
   │                               └──── local archive sweep ─────► expired

   └──── lease_cancel ────────► cancelled

settled, cancelled, unfulfilled and expired are terminal.

Phase 1: Lease creation (consumer)

The consumer creates a lease block on their account chain. This debits XUSD from their balance into escrow to pay for the requested resources.

{
  "type": "lease",
  "account": "<consumer pubkey>",
  "previous": "<frontier hash>",
  "balance": "<previous XUSD balance - cost>",
  "asset": "XUSD",
  "destination": "<provider pubkey>",
  "amount": "<cost in micro-XUSD>",
  "vcpus": 2,
  "memory_mb": 4096,
  "disk_gb": 50,
  "duration": 3600,
  "access_pub_key": "<ed25519 pubkey hex, 64 chars>",
  "certificate_hash": "<provider's performance certificate hash>"
}

The block is signed, PoW-solved, and broadcast to the network via gossip.

[!INFO] Cost must match the formula The ledger validates that amount exactly equals the deterministic cost computed from the resource dimensions, the duration, and the price multiplier carried by the referenced certificate. See Cost Model.

[!NOTE] The certificate is mandatory certificate_hash must name an unexpired performance certificate belonging to destination. This locks the price multiplier at offer time, and the provider must accept using the same certificate hash.

What happens on broadcast

The lease block creates an escrowed pending send to the provider. The provider's node sees this pending send and -- if configured with --provide -- automatically begins the acceptance flow. The escrow is not drained on acceptance: it stays alive until it is burned at settlement, refunded on cancel, or refunded on force-settle.

Phase 2: Lease acceptance (provider)

The provider creates a lease_accept block on their account chain. This stakes XUSD collateral and records timekeeper-attested start time.

{
  "type": "lease_accept",
  "account": "<provider pubkey>",
  "previous": "<frontier hash>",
  "balance": "<previous XUSD balance - stake>",
  "asset": "XUSD",
  "source": "<lease block hash>",
  "amount": "<stake in micro-XUSD>",
  "certificate_hash": "<same certificate hash as the lease block>",
  "attestations": [...],
  "locked_r": 2000,
  "locked_payout_cap": 1550,
  "locked_twap_milli": 751
}

[!NOTE] Stake calculation Stake = ceil(cost / 5), with a minimum of 1 µXUSD. The provider must have sufficient XUSD balance to cover the stake.

[!IMPORTANT] Emission parameters are locked at acceptance The accept block must carry locked_r, locked_payout_cap and locked_twap_milli — the emission rate parameters of the epoch covering the attested accept time. Settlement computes the XE emission from these locked values, so the provider's payout is fixed the moment it commits stake, independent of any later epoch transition. An accept with locked_r unset is rejected, and the values are checked against the epoch the state chain reports for the accept time.

Acceptance steps

  1. Accept policy — apply the provider's local accept policy (min/max duration, min/max cost, max concurrent leases). A zero-value policy is permissive.
  2. Balance check — verify sufficient XUSD for the stake.
  3. Gather attestations — request signed timestamps from trusted timekeepers (see Attestations). Attestations are mandatory.
  4. Resource check — verify available vCPUs, memory, and disk (including resources reserved by in-flight provisions).
  5. Provision VM — start the virtual machine with the requested resources. This happens before the accept block is committed — if provisioning fails, the lease is not accepted.
  6. Create block — build the lease_accept block with certificate_hash referencing the provider's performance certificate, and the locked emission parameters.
  7. Validate certificate — the ledger verifies the certificate exists in the gossip cache, matches the hash the consumer referenced, belongs to this provider, and is not expired at the attested start time.
  8. Broadcast — publish to the network via gossip.
  9. Send credentials — deliver VM access info to the consumer via the vm_credentials direct message.

Lease record

When the ledger processes a valid lease_accept, it creates a Lease record:

Field

Source

LeaseHash

Hash of the original lease block

State

accepted

Consumer

Lease block's account (creator)

Provider

Lease block's destination

VCPUs

From the lease block

MemoryMB

From the lease block

DiskGB

From the lease block

Duration

From the lease block (seconds)

Cost

Lease block's amount (XUSD)

Stake

lease_accept block's amount (XUSD)

StartTime

Median attested timestamp (unix nanos)

AccessPubKey

From the lease block (ed25519 hex, for SSH access)

CertificateHash

Provider's performance certificate hash

LockedR / LockedPayoutCap / LockedTWAP

Emission parameters from the lease_accept block

Settled

false

Phase 3: Settlement (provider)

After the lease duration expires, and before the settle grace window closes, the provider creates a lease_settle block. This mints the XE emission, returns the XUSD stake, and burns the consumer's escrow.

{
  "type": "lease_settle",
  "account": "<provider pubkey>",
  "previous": "<frontier hash>",
  "balance": "<previous XE balance + emission>",
  "asset": "XE",
  "source": "<lease block hash>",
  "amount": "<XE emission>",
  "attestations": [
    {
      "public_key": "<timekeeper pubkey hex>",
      "timestamp": 1709658000000000000,
      "signature": "<attestation signature hex>"
    }
  ]
}

Settlement steps

  1. Check expiry — the settleLoop() goroutine checks every 10 seconds whether now >= startTime + duration, and stops retrying once now > expiry + LeaseSettleGrace.
  2. Gather attestations — request signed timestamps for the settle time. Attestations are mandatory.
  3. Validate window — the ledger verifies that the attested median is at or after startTime + duration (in nanoseconds) and no later than expiry + LeaseSettleGrace. Self-reported timestamps are not accepted.
  4. Create block — build the lease_settle block with the XE emission.
  5. Broadcast — publish to the network.
  6. Burn escrow, return stake — the consumer's escrowed XUSD is burned and the provider's XUSD stake is credited back, both atomically with the block commit.
  7. Teardown VM — synchronously stop and clean up the virtual machine (not fire-and-forget).
  8. Orphan cleanup — the settle loop also tears down any VM whose lease has reached a terminal state but whose teardown failed.

[!IMPORTANT] XE emission The XE reward is not numerically equal to the XUSD cost. It is ceil(cost x R_capped / 1000) in micro-XE, where R_capped is derived from the emission parameters locked onto the lease at acceptance (locked_r, capped by locked_payout_cap and locked_twap_milli). The emission rate curve itself is part of the token economics model, not the lease code — see Economics.

[!WARNING] Settle window closes A settle whose attested median is past expiry + LeaseSettleGrace is rejected. The provider cannot settle after that; once a further LeaseForceSettleGap has elapsed the lease becomes the consumer's to force-settle (see below). The gap between the two windows keeps them disjoint.

Duration limits

Limit

Value

Human

Minimum

LeaseMinDuration (production default 60)

1 minute

Maximum

31,536,000 seconds

365 days

The ledger rejects lease blocks with durations outside this range.

[!IMPORTANT] Lease timings are network parameters The minimum duration, the settle grace window, the force-settle gap, the escrow expiry and the attestation skew are pinned by the genesis block (#524). The values quoted on this page are the production/mainnet defaults used when a genesis does not pin them. A network — for instance a compressed test network — may pin different values, so read the live figures from GET /node under lease_timing rather than treating them as protocol constants. The maximum duration is a true protocol constant.

Phase 4: Cancellation (consumer)

A lease that no provider has accepted can be cancelled by its consumer with a lease_cancel block. This deletes the escrowed pending send and refunds the full cost.

{
  "type": "lease_cancel",
  "account": "<consumer pubkey>",
  "previous": "<frontier hash>",
  "balance": "<previous XUSD balance + cost>",
  "asset": "XUSD",
  "source": "<lease block hash>"
}

Rules the ledger enforces:

  • Only the consumer (the lease block's creator) may cancel.
  • The lease must be in created state; a settled, cancelled or unfulfilled lease is already resolved.
  • The escrowed pending send must still exist.
  • The block's balance must equal the current XUSD balance plus the refunded cost.
  • No attestations are required — cancellation is not time-gated.

A cancel that races a provider's accept resolves deterministically cancel-wins: a node that already applied the accept unwinds it (refunding the provider's stake and restoring the lease to created) so the cancel can proceed, converging with nodes that applied the cancel first.

Phase 5: Force-settle (consumer)

If a provider accepts a lease and then never settles it, the consumer recovers their money with a lease_force_settle block on their own chain. It refunds the full escrowed cost and leaves the provider's stake burned as the penalty. The lease ends in unfulfilled state.

{
  "type": "lease_force_settle",
  "account": "<consumer pubkey>",
  "previous": "<frontier hash>",
  "balance": "<previous XUSD balance + cost>",
  "asset": "XUSD",
  "source": "<lease block hash>",
  "attestations": [...]
}

Rules the ledger enforces:

  • Only the consumer may force-settle, and only a lease in accepted state.
  • Timekeeper attestations are mandatory; the attested median must be at or after the eligibility point.
  • Eligibility: expiry + LeaseSettleGrace + LeaseForceSettleGap. On production timing that is 1 h 25 min after expiry (1 h grace + 25 min gap).
  • Upper bound: the attested median must be before expiry + LeaseEscrowExpiry (production default 365 days). Past that the refund window has closed.
  • The escrowed pending send must still exist.

The gap between the provider's settle window and the consumer's force-settle window is deliberately larger than twice the maximum attestation skew, so no single timestamp can satisfy both windows — lease_settle and lease_force_settle can never both be valid.

Abandoned leases

If both parties go offline and neither settles nor force-settles, the escrow is eventually garbage-collected. Once now >= expiry + LeaseEscrowExpiry + LeaseArchiveGap, each node independently deletes the escrowed pending send and marks the lease expired. This is a local, blockless derived-state operation: it emits no block and touches no account balance — total XUSD supply simply drops by the burnt escrow.

Automatic settlement

Every node runs a background settleLoop() goroutine that ticks every 10 seconds and performs four sweeps:

  1. Settle — for each accepted lease where this node is the provider and expiry <= now <= expiry + LeaseSettleGrace, call settleLease(), which gathers attestations, creates the lease_settle block, and tears down the VM.
  2. Force-settle — for each accepted lease where this node is the consumer and the force-settle window has opened, submit a lease_force_settle.
  3. Archive — garbage-collect the escrow of leases abandoned by both parties (see Abandoned leases).
  4. Orphan VM cleanup — tear down VMs whose leases have reached a terminal state but whose teardown failed.

[!TIP] No manual intervention needed Neither party needs to settle by hand. The settleLoop() handles settlement on the provider side and force-settlement on the consumer side, as long as the node is running.

Lease struct

The Lease struct tracks the full state of an active or settled lease:

type Lease struct {
    LeaseHash    string     // hash of the lease block
    State        LeaseState // created, accepted, settled, cancelled, unfulfilled, expired
    Consumer     string     // consumer account (lease block creator)
    Provider     string     // provider account (lease block destination)
    VCPUs        uint64
    MemoryMB     uint64
    DiskGB       uint64
    Duration     uint64 // seconds
    AccessPubKey string // ed25519 pubkey hex for SSH access
    Cost         uint64 // micro-XUSD paid by consumer
    Stake        uint64 // micro-XUSD staked by provider
    StartTime       int64  // unix nanos (median attested timestamp)
    CertificateHash string // provider's performance certificate
    Settled         bool

    // Emission parameters locked at lease_accept time.
    LockedR         uint64 // R_effective at accept, x1000
    LockedPayoutCap uint64 // payout cap at accept, x1000
    LockedTWAP      uint64 // TWAP at accept, milli-USD
}

GET /leases?state=... filters on the State field. The legacy Settled bool stays false for cancelled leases, so state — not Settled — is the field to branch on.

Sequence diagram

Consumer Node                  Network                  Provider Node
     │                           │                           │
     │  create lease block       │                           │
     │  (debit XUSD)             │                           │
     │ ─────────────────────────►│                           │
     │                           │  gossip lease block       │
     │                           │──────────────────────────►│
     │                           │                           │  detect pending send
     │                           │                           │  gather start attestations
     │                           │                           │  check resources available
     │                           │                           │  provision VM (before accept!)
     │                           │                           │  create lease_accept
     │                           │  gossip lease_accept      │  (stake + cert hash + locked R)
     │                           │◄──────────────────────────│
     │  receive VM credentials   │                           │  send credentials
     │◄──────────────────────────│───────────────────────────│
     │                           │                           │
     │       ... lease duration elapses ...                  │
     │                           │                           │
     │                           │                           │  settleLoop detects expiry
     │                           │                           │  gather settle attestations
     │                           │                           │  create lease_settle
     │                           │  gossip lease_settle      │  (mint XE, burn escrow,
     │                           │◄──────────────────────────│   recover stake)
     │                           │                           │  teardown VM (synchronous)