Skip to main content
Program ID: 29QdPRQC8C5v6C8gMcBqtw9T4RxYyZ1wqThkEj3XJeQx Framework: Anchor 1.2.0 · Client: @anchor-lang/core (not @coral-xyz/anchor)

Accounts

Vault

PDA, seeds [b"vault", owner]. One per payer.
floor_limit is the floor, not the ceiling. The effective limit is the curve applied to it. With no stake and neutral reputation the two are the same number, which is what made the curve safe to add to an already-deployed program.

RiskConfig

PDA, seeds [b"risk"]. Singleton, one per deployment.
redeem_voucher requires this account. It must be initialised once per deployment before any voucher can redeem.

Instructions

Vault lifecycle

Enrol a device key and open a vault. Creates the vault PDA and its collateral token account, so deposit and redemption never have to reason about a missing account.Opens at stake 0, reputation 10_000, status active.
Lock collateral. Offline mode is a prepaid balance, not a promise to pay — you cannot spend offline what you have not already locked here.Refused on a frozen vault.
Open the 24-hour timelock. Vouchers still redeem normally during the delay — the timelock blocks the exit, not the payees.Refused on a frozen vault.
Withdraw once the timelock has elapsed. Consumes the request (unlock_at → 0), so each withdrawal needs its own timelock.Errors: WithdrawNotRequested, WithdrawTimelockActive, VaultFrozen.

Redemption

Instruction 0 of the transaction must be the secp256r1 precompile verifying this voucher’s 105 signed bytes.Checks, in order:
  1. version == 1, else BadVoucherVersion
  2. voucher.vault matches this vault
  3. Not expired
  4. amount <= computed_limit — the curve, not the static field — else AboveFloorLimit
  5. amount <= vault.balance, else InsufficientCollateral
  6. The precompile verified this device key over these bytes
  7. The replay window accepts the sequence
  8. Collateral moves via transfer_checked
Deliberately no is_active check: a freeze blocks the payer’s exit, not the payees.The merchant’s token account is created on demand — a merchant taking their first Nelo payment has never held USDC, and that must not be the thing that fails a sale.
Permissionless. Instructions 0 and 1 must be the precompile verifying each voucher.Freezes the vault on proof of a double-spend: two different vouchers at the same sequence, both validly signed by the enrolled device. An honest secure element never produces that pair.Refuses: identical vouchers (NotAConflict — that is a replay, and freezing on it would be a DoS against the payer), different sequences (NotSameSequence), forged signatures.

Trust Stake

Open and revise the platform risk parameters. update_risk_config is where the reserve model lands — as configuration, not a redeploy.Validated: authority != default, stake_reference != 0 (it divides), hard_cap != 0 (a zero cap refuses every voucher on the platform), haircut_bps <= 10_000, unstake_cooldown >= MIN_UNSTAKE_COOLDOWN_SECONDS.params.authority doubles as the rotation path.
The frequent operation, kept separate from the structural parameters so a routine price update cannot accidentally rewrite the hard cap.
Risk authority only — not even the vault’s own owner. Reputation you can set yourself is not reputation.Bounded at REPUTATION_MAX_BPS (20_000), else ReputationOutOfRange.
Post first-loss capital and buy a higher offline ceiling. Tokens sit under the vault PDA, not the owner’s wallet; the account is created on demand.Refused on a frozen vault and on amount == 0.
request_unstake opens the cooldown and the amount stops backing the limit immediately. unstake collects once elapsed, and consumes the request.Both refused on a frozen vault — stake is first-loss capital against exactly the event that froze it.Errors: InsufficientStake, UnstakeNotRequested, UnstakeCooldownActive.

Constants

Errors