OrbitDocs

Allowance vaults

How Orbit bills on a schedule without ever holding the subscriber's funds.

An allowance vault is made of two pieces of onchain state that work together:

PieceLives inSet byControls
Token allowanceThe token's SACSubscriber, via approveHow much Orbit can move in total, and until which ledger
VaultDataOrbit contract storageSubscriber, via create_vaultHow often the merchant can pull, and how much per pull

Tokens only move when both agree.

Lifecycle

Pull eligibility

A pull succeeds only when all of these hold:

now >= last_pull + interval_seconds OR last_pull = 0
amount_per_interval <= allowance(user, orbit) AND amount_per_interval <= balance(user)

The first condition is checked by Orbit. The second is enforced by the token contract inside transfer_from.

Storage

#[contracttype]
pub struct VaultKey {
    pub user: Address,
    pub merchant: Address,
}

#[contracttype]
pub struct VaultData {
    pub token: Address,
    pub amount_per_interval: i128,
    pub interval_seconds: u64,
    pub last_pull_timestamp: u64,
}
  • There is one vault per (user, merchant) pair, stored in persistent storage.
  • A subscriber can hold vaults with any number of merchants, each with its own terms.
  • Calling create_vault again for the same pair overwrites the terms and sets last_pull_timestamp back to 0.

Sizing the allowance

Approve amount_per_interval x cycles and choose a live_until_ledger that covers them:

cycles            = 12
amount            = 290000000            # 29 USDC
allowance         = 3480000000           # 348 USDC
ledgers per cycle ~ 2592000 / 5 = 518400

A short allowance that you top up on renewal gives subscribers more control than one large, long-lived allowance. The dashboard can prompt for re-approval when the allowance is about to run out.

Cancelling

Subscribers cancel on the token, not on Orbit:

stellar contract invoke --id $TOKEN --source subscriber --network testnet -- \
  approve --from $SUBSCRIBER --spender $ORBIT --amount 0 --live_until_ledger 0

After this, every pull_funds for that subscriber fails inside the SAC, whatever vault terms are stored.

On this page