Skip to main content

Program Escrow Insurance Reserve

Last updated: 16 August 2026

Documents a contract that does not exist

The only Soroban contract in this project is grainhack-escrow (initialise, fund, publish_root, claim, is_claimed, sweep), and it has never been deployed. The program-escrow and bounty-escrow surfaces this page describes — publish_program, lock_program_funds, single_payout, batch_payout, create_bounty and the rest — have no implementation in any repository, verified 16 August 2026.

This page is kept as a design record. Do not read it as a description of working software, and do not implement against it without checking what the contract actually exposes. See Payout path — what exists today.

Overview​

The Program Escrow Contract now supports an insurance reserve feature that allows programs to carve out a configurable portion of collected fees into a segregated on-chain reserve balance. This provides a lightweight self-insurance buffer without requiring a separate claims contract.

Architecture​

Fee Configuration​

The FeeConfig struct includes a new field:

/// Basis-point share of each collected fee that is carved out into the
/// on-chain insurance reserve instead of being forwarded to `fee_recipient`.
///
/// Range: `0` (disabled, default) – `MAX_FEE_RATE` (1,000 basis points = 10%).
pub insurance_reserve_bps: u32,

Fee Splitting Logic​

When fees are collected, they are split according to this formula:

total_fee = combined_fee_amount(gross, rate, fixed, enabled)
reserve_share = ceil(total_fee * insurance_reserve_bps / BASIS_POINTS)
recipient_share = total_fee - reserve_share

Invariant: reserve_share + recipient_share == total_fee (no value leakage or double-counting)

Storage​

The insurance reserve balance is stored separately from the program's remaining_balance using:

  • Storage Key: DataKey::InsuranceReserve
  • Storage Type: Instance storage (shares contract TTL)
  • Data Type: i128 (native token units)

API Functions​

View Functions​

get_insurance_reserve_balance(env: Env) -> i128​

Returns the current insurance reserve balance in native token units.

  • Authorization: None (read-only)
  • Returns: Current reserve balance, defaults to 0 if not initialized

get_fee_config(env: Env) -> FeeConfig​

Returns the current fee configuration including insurance_reserve_bps.

  • Authorization: None (read-only)
  • Returns: Complete fee configuration

Admin Functions​

update_fee_config(..., insurance_reserve_bps: Option<u32>)​

Updates the fee configuration. The insurance_reserve_bps parameter is validated:

  • Range: 0 to MAX_FEE_RATE (1,000 basis points = 10%)
  • Authorization: Admin only
  • Validation: Must not exceed MAX_FEE_RATE
  • Error: InvalidInsuranceReserveBps (704) if validation fails

withdraw_insurance_reserve(env: Env, target: Address, amount: i128)​

Withdraws funds from the insurance reserve.

  • Authorization: Admin only (same level as emergency_withdraw)
  • Parameters:
    • target: Destination address
    • amount: Amount to withdraw (must be > 0)
  • Validation:
    • Amount must be positive
    • Amount must not exceed current reserve balance
  • Effects:
    • Decrements reserve balance
    • Transfers tokens to target address
    • Emits InsuranceReserveWithdrawnEvent
  • Errors:
    • InvalidAmount if amount ≤ 0
    • InsufficientInsuranceReserve (705) if amount exceeds balance

Integration Points​

The insurance reserve is automatically integrated into all fee collection operations:

  1. Program Lock Fees: Applied during init_program with initial liquidity
  2. Single Payout Fees: Applied during single_payout operations
  3. Batch Payout Fees: Applied during batch_payout operations

Zero-Impact Behavior​

When insurance_reserve_bps == 0:

  • Full fee amount goes to fee_recipient
  • Reserve balance remains unchanged
  • No performance overhead

Events​

InsuranceReserveWithdrawnEvent​

Emitted when funds are withdrawn from the reserve:

pub struct InsuranceReserveWithdrawnEvent {
pub version: u32,
pub admin: Address, // Admin who authorized withdrawal
pub target: Address, // Destination address
pub amount: i128, // Amount withdrawn
pub balance_before: i128, // Reserve balance before withdrawal
pub balance_after: i128, // Reserve balance after withdrawal
pub timestamp: u64, // Ledger timestamp
}

Security Considerations​

Arithmetic Safety​

  • Overflow Protection: All arithmetic operations use checked_add() and checked_mul()
  • Ceiling Division: Reserve calculation uses ceiling division to prevent value leakage
  • Invariant Enforcement: reserve_share + recipient_share == total_fee is guaranteed

Access Control​

  • Admin Authorization: Only admin can update configuration or withdraw funds
  • Rate Limits: insurance_reserve_bps cannot exceed MAX_FEE_RATE (10%)
  • Validation: All inputs validated before state changes

Audit Trail​

  • Withdrawal Events: All reserve withdrawals emit audit events
  • Fee Events: Standard fee collection events include reserve information
  • Balance Tracking: Reserve balance tracked independently from program funds

Error Codes​

CodeErrorDescription
704InvalidInsuranceReserveBpsReserve rate exceeds MAX_FEE_RATE
705InsufficientInsuranceReserveWithdrawal amount exceeds reserve balance

Usage Examples​

Configure Reserve Rate​

// Set 5% (500 basis points) reserve rate
client.update_fee_config(
&None, // lock_fee_rate unchanged
&None, // payout_fee_rate unchanged
&None, // lock_fixed_fee unchanged
&None, // payout_fixed_fee unchanged
&None, // fee_recipient unchanged
&None, // fee_enabled unchanged
&Some(500_u32), // insurance_reserve_bps = 5%
);

Query Reserve Balance​

let balance = client.get_insurance_reserve_balance();
println!("Insurance reserve: {} tokens", balance);

Withdraw Reserve Funds​

// Admin withdraws 1000 tokens from reserve
client.withdraw_insurance_reserve(
&target_address,
&1000_i128,
);

Testing​

Comprehensive test coverage is provided in contracts/program-escrow/src/test_insurance_reserve.rs:

  • Field initialization and persistence
  • Split invariant verification across all fee paths
  • Accumulation accuracy across multiple operations
  • Query function correctness
  • Withdrawal authorization and validation
  • Zero-basis-point passthrough behavior
  • Edge cases and error conditions