Tenzro Testnet is live —request testnet TNZO

Settlement

The settlement engine provides payment settlement for immediate settlements, escrow-based transactions, micropayment channels, and batch processing.

Settlement Types

Immediate Settlement

Funds transferred directly from payer to payee on-chain. Used for one-time payments and high-value transactions.

Escrow-Based Settlement

Funds locked in escrow until proof is provided. Used for AI inference payments where proof of computation is verified before release.

Micropayment Channels

Off-chain payment channels for per-token billing. Used for streaming AI inference where payment occurs per token generated.

Batch Settlement

Atomic processing of multiple settlements in a single transaction. Used for aggregating many small payments to reduce gas costs.

Immediate Settlement

let settlement = SettlementRequest {
    settlement_id: "settle_abc123".to_string(),
    payer: "tenzro1abc...xyz".to_string(),
    payee: "tenzro1def...uvw".to_string(),
    amount: 100.0,
    asset: "TNZO".to_string(),
    settlement_type: SettlementType::Immediate,
    metadata: HashMap::from([
        ("service".to_string(), "inference".to_string()),
    ]),
    proof: None,
};

let receipt = engine.settle(&settlement).await?;
// Receipt includes settlement_tx hash on Tenzro Ledger

Escrow-Based Settlement

Escrow is a consensus-mediated on-chain primitive. Funds are locked at a deterministically-derived vault address by the Native VM; only the original payer can later release funds to the payee or refund them to themselves. Writes flow through three signed transaction types submitted via the standard transaction pipeline.

Native-VM selectors

SelectorOperationGas
0x01000010CreateEscrow75,000
0x01000011ReleaseEscrow60,000
0x01000012RefundEscrow50,000

Deterministic identifiers

  • escrow_idSHA-256("tenzro/escrow/id/v1" || payer || nonce_le), derived by the VM and emitted in the receipt log of CreateEscrow.
  • vault addressAddress(SHA-256("tenzro/escrow/vault/v1" || escrow_id)). Has no private key. Release/refund payouts go through a single auditable privileged-VM helper that calls state.set_balance directly.

Authorization invariants (enforced by VM)

  • CreateEscrow.from must equal the signing payer (verified at mempool admission). The VM never trusts a payer field in the payload.
  • ReleaseEscrow is rejected unless tx.from == escrow.payer, the escrow is in Funded state, not expired, and the proof verifies against the recorded release_conditions.
  • RefundEscrow is rejected unless tx.from == escrow.payer AND (the escrow is expired OR release_conditions ∈ {Timeout, Custom}).

Escrow Flow

  1. Build & sign — Payer builds a CreateEscrow transaction with payee, amount, asset, expiry, and release conditions. The node hybrid-signs with both the classical Ed25519 key and the ML-DSA-65 (FIPS 204) post-quantum key.
  2. Submit — Submit via tenzro_signAndSendTransaction (atomic server-side hybrid sign + submit) or eth_sendRawTransaction with the full pre-signed payload (signature, public_key, pq_signature, pq_public_key, timestamp).
  3. Lock — Native VM debits payer, credits derived vault, persists EscrowAccount{Funded} to CF_SETTLEMENTS, emits log with escrow_id.
  4. Service — Provider delivers AI inference or other service.
  5. Proof — Provider/payer assembles release proof matching the recorded release_conditions.
  6. Release / Refund — Payer signs ReleaseEscrow (vault → payee) or RefundEscrow (vault → payer, after expiry) and submits.
// Submit a CreateEscrow transaction. Ambient auth: TENZRO_BEARER_JWT
// + TENZRO_DPOP_PROOF must be exported; the node resolves the signer
// from the DPoP-bound JWT's MPC wallet.
let tx_type = serde_json::json!({
    "type": "CreateEscrow",
    "data": {
        "payee": payee_address,
        "amount": amount.to_string(),
        "asset_id": "TNZO",
        "expires_at": expires_at_ms,
        "release_conditions": { "type": "Timeout" },
    },
});

let tx_hash: String = rpc.call("tenzro_signAndSendTransaction", json!({
    "from": payer_address,
    "to":   payee_address,
    "value": 0,
    "gas_limit": 75_000,
    "gas_price": 1_000_000_000,
    "nonce": nonce,
    "chain_id": chain_id,
    "tx_type": tx_type,
})).await?;

// Read the escrow back once the tx finalizes
let escrow: EscrowAccount = rpc
    .call("tenzro_getEscrow", json!([escrow_id_hex]))
    .await?;

Persistence + hydration

EscrowManager writes through to RocksDB CF_SETTLEMENTS under three prefixes:

  • escrow:<escrow_id> — full EscrowAccount record
  • escrow_payer:<address_hex> — Vec<escrow_id> index
  • escrow_payee:<address_hex> — Vec<escrow_id> index

On construction, the manager scans escrow: and rebuilds in-memory indices. All mutations use KvStore::write_batch_sync (fsync on commit). Restart-safe.

Micropayment Channels

Micropayment channels enable off-chain per-token billing for streaming AI inference, settling on-chain only when the channel closes.

Channel Lifecycle

1. Open Channel

let channel_id = manager.open_channel(
    "tenzro1payer...abc".to_string(),
    "tenzro1provider...xyz".to_string(),
    1000.0, // Initial deposit (TNZO)
    "TNZO".to_string(),
    7200, // Timeout (2 hours)
).await?;

2. Update State (Off-chain)

// For each token generated
manager.update_channel_state(
    &channel_id,
    0.001, // Cost per token
).await?;

// State updated locally, no on-chain transaction

3. Close Channel (On-chain Settlement)

let settlement_tx = manager.close_channel(&channel_id).await?;
// Final state settled on Tenzro Ledger
// Remaining balance returned to payer
// Provider receives accumulated payments

Example: Streaming Inference

// Open channel for 1000 TNZO
let channel = manager.open_channel(
    payer, provider, 1000.0, "TNZO", 7200
).await?;

// Generate 50,000 tokens at $0.001 per token
for token in generated_tokens {
    manager.update_channel_state(&channel.id, 0.001).await?;
}
// Total: 50 TNZO charged (off-chain)

// Close channel
let tx = manager.close_channel(&channel.id).await?;
// On-chain: 50 TNZO to provider, 950 TNZO refund to payer

Nanopayment Batching

NanopaymentBatcher aggregates many sub-cent payments into periodic on-chain settlements, saving gas by replacing N individual transactions with a single batch settlement.

  • Configurable Window: Default 60-second batch window with configurable minimum and maximum batch sizes
  • Cryptographic Signatures: Each nanopayment is individually signed and stored off-chain until settlement
  • Threshold Triggers: Settlement fires when the batch window expires or the batch size threshold is reached
  • Atomic Settlement: All payments in a batch are settled atomically — if any fails, the entire batch rolls back
  • Gas Savings: Instead of N on-chain transactions, 1 batch settlement covers all accumulated payments
use tenzro_settlement::nanopayments::NanopaymentBatcher;

let batcher = NanopaymentBatcher::new(
    settlement_engine,
    BatchConfig {
        window_secs: 60,
        min_batch_size: 10,
        max_batch_size: 1000,
    },
);

// Add nanopayments (accumulated off-chain)
batcher.add_payment(payer, payee, 100_000, "inference-token-42")?;
batcher.add_payment(payer, payee, 150_000, "inference-token-43")?;

// Flush manually or wait for window expiry
let settlement = batcher.flush().await?;
println!("Settled {} payments, total: {}", settlement.count, settlement.total);

Payment-Settlement Wiring

The PaymentSettlementCallback trait bridges the payment gateway to the settlement engine. When a payment is verified — whether via MPP credential, x402 payload, or AP2 authorization — the callback triggers on-chain settlement and returns the receipt to the payment layer for inclusion in the HTTP response.

// Payment gateway automatically settles via callback
impl PaymentSettlementCallback for TenzroSettlement {
    async fn on_payment_verified(
        &self,
        payer: &Address,
        payee: &Address,
        amount: u128,
        asset: &str,
        receipt_id: &str,
    ) -> Result<SettlementReceipt> {
        self.engine.settle_immediate(payer, payee, amount, asset).await
    }
}

Batch Settlement

Batch processing enables atomic settlement of multiple payments in a single transaction, reducing gas costs and ensuring atomicity.

let settlements = vec![
    SettlementRequest {
        settlement_id: "settle_1".to_string(),
        payer: "tenzro1abc...".to_string(),
        payee: "tenzro1provider1...".to_string(),
        amount: 10.0,
        asset: "TNZO".to_string(),
        settlement_type: SettlementType::Immediate,
        metadata: HashMap::new(),
        proof: None,
    },
    SettlementRequest {
        settlement_id: "settle_2".to_string(),
        payer: "tenzro1abc...".to_string(),
        payee: "tenzro1provider2...".to_string(),
        amount: 15.0,
        asset: "TNZO".to_string(),
        settlement_type: SettlementType::Immediate,
        metadata: HashMap::new(),
        proof: None,
    },
];

// Process atomically: all succeed or all fail
let receipts = batch_processor.process_batch(settlements).await?;

Fee Collection

The FeeCollector manages network fees on all settlements:

  • Network Fee: 0.5% on all settlements (configurable)
  • Collection: Fees automatically deducted and routed to treasury
  • Multi-Asset: Fees collected in the payment asset (TNZO, USDC, etc.)
  • Transparency: All fees recorded on-chain in settlement receipts
// Example: 100 TNZO settlement with 0.5% fee
Amount: 100.0 TNZO
Network Fee: 0.5 TNZO (0.5%)
To Payee: 99.5 TNZO
To Treasury: 0.5 TNZO

// Receipt includes fee breakdown
{
  "settlement_id": "settle_abc123",
  "amount": "100.0",
  "fee": "0.5",
  "net_amount": "99.5",
  "fee_recipient": "treasury"
}

Settlement Receipt

Every settlement generates a cryptographically signed receipt:

{
  "settlement_id": "settle_abc123",
  "payer": "tenzro1abc...xyz",
  "payee": "tenzro1def...uvw",
  "amount": "100.0",
  "asset": "TNZO",
  "settlement_type": "Immediate",
  "settlement_tx": "0x789abc...",
  "fee": "0.5",
  "net_amount": "99.5",
  "timestamp": "2026-03-20T12:30:45Z",
  "block_height": 1207,
  "metadata": {
    "service": "inference",
    "request_id": "req_xyz789"
  },
  "signature": "0xdef456..."
}

Verification

Settlement receipts can be verified via the Web Verification API:

# Verify settlement receipt
curl -X POST http://localhost:8080/verify/settlement \
  -H "Content-Type: application/json" \
  -d '{
    "receipt": {
      "settlement_id": "settle_abc123",
      "amount": "100.0",
      "proof": "0xabc..."
    }
  }'

# Response
{
  "valid": true,
  "settlement_confirmed": true
}

Integration with Payments

Settlement is automatically triggered by payment protocol verification:

// MPP payment verification triggers settlement
let receipt = mpp_server.verify_credential(&credential).await?;

// Settlement happens automatically:
// 1. Funds transferred from payer to payee
// 2. Network fee collected
// 3. Settlement receipt generated
// 4. Receipt included in MPP receipt

// Payment receipt includes settlement_tx
{
  "receipt_id": "rcpt_xyz789",
  "amount": "0.05 TNZO",
  "settlement_tx": "0x789abc...", // On-chain settlement
  "session_token": "sess_mno345"
}

Channel disputes

When two channel participants disagree on the off-chain state at close-time (e.g. the payer's view of the cumulative debit differs from the payee's claim), either party can open a dispute. The network records the challenger's evidence, runs a configurable timeout for the counterparty to respond, and finalizes the channel balance on-chain when the dispute resolves.

# Inspect a single dispute by id
tenzro dispute status <dispute_id>

# Every dispute ever opened against a given channel
tenzro dispute list-by-channel --channel-id <channel_id>

Underlying RPCs: tenzro_getDispute { dispute_id } and tenzro_listDisputesByChannel { channel_id }. MicropaymentChannelManager persists every dispute under CF_CHANNELS / dispute:<dispute_id>; opens, responses, and resolutions are atomic write-batches that survive node restart.