Skip to content

Stablecoins

This page assembles a stablecoin out of primitives Ootle already has:

  • Why a stablecoin is a resource plus an issuance component, not a new protocol feature.
  • The design tension between auditable supply and private transfers, and how the resource model resolves it.
  • Choosing a resource type — public, confidential, or stealth — and what each choice costs.
  • The issue → transfer → redeem lifecycle, and the proof each step needs.
  • The two auditability knobs: supply tracking and view keys.
  • A precise answer to where the privacy is, and where it leaks.

A stablecoin is a resource, not a primitive

Section titled “A stablecoin is a resource, not a primitive”

There is no stablecoin module in Ootle, and there does not need to be. A stablecoin is a resource — the definition of an asset — plus a component that holds the authority to create and destroy its units. Everything a stablecoin is on Ootle is expressed in three decisions:

  1. Which ResourceType the units use — this is what fixes the privacy properties.
  2. Who may mint and burn — an AccessRule on the resource and on the issuing component.
  3. Who may see what — the resource’s view key, if any, and where the flow is forced to reveal.

The peg itself — what the token is worth, and which reserves stand behind it — is not modelled by the protocol. Ootle stores the resource and its supply; it holds no price feed, and it cannot attest that a reserve exists. Backing is an issuer-side claim, published off-protocol.


A stablecoin has two requirements that pull in opposite directions:

RequirementWho needs itWhat it wants
Transactional privacyHoldersBalances and transfers should not be public.
Provable supplyThe issuerThe total number of units in circulation should be checkable.

Ootle resolves the conflict by separating them in time and in place: transfers are private; the supply boundary is not.

That is not a convention layered on top — it falls out of how supply is tracked. A resource keeps its total_supply in its resource substate and increments or decrements it as value crosses the boundary. Because stealth outputs commit to their amounts, the engine cannot reconstruct supply by summing balances — so the value entering or leaving circulation has to be visible at the moment it crosses. Inside the boundary, nothing is.


The ResourceType is the single most consequential choice. It is set when the resource is created and it does not change.

TypeAmounts hiddenOwners hiddenSupply boundaryFit for a stablecoin
Public FungibleNoNoTrivially auditableA deposit receipt or a fully transparent instrument. Offers no privacy at all.
ConfidentialYesNoRevealed at mint/burnBalances hidden, but funds sit in named vaults — an observer sees which account moved, just not how much.
StealthYesYesRevealed at mint/burnThe payments-grade choice: funds are UTXOs with one-time addresses, so neither amount nor counterparty is readable.

A payments stablecoin wants stealth: privacy fails the moment an observer can link a transfer to a person, and a confidential vault leaks exactly that. A stablecoin meant to be transparent by design — a regulated deposit token with published balances — can use a public fungible resource and skip the cryptography entirely. See Templates and Assets for the full comparison of the four types.


Four parties, and the authority never travels with the funds:

Anatomy of a stablecoin on OotleIssuer componentmint() — gated by an AccessRuleburn() — gated by an AccessRuleview key fixed at resource creationHolds authority, not funds.Stealth resource: USDXResourceType::Stealthdivisibility 6total_supply (tracked)view_key (optional)Auditor / regulatorholds the view private key precovers amounts: V = E − pRcannot spend (no spend key)reconciles outputs vs supplyrulesauditsHOLDERSHolder Astealth UTXOC = mask·G + value·HHolder Bstealth UTXOone-time addressEvery output carriesa ViewableBalanceProofthat the auditor can verifyHolder-to-holder transfers are stealth transfers: amounts and owners are both hidden.Issuer authorityHidden value (stealth)Selective disclosure
  • The issuer component holds mint and burn authority. Because that authority is an AccessRule, it can be a single key, a multisig, or a badge held by another component — the pattern scales from one operator to a governed issuer without changing the resource. See Authorization and Access.
  • The resource substate is the asset definition plus the tracked total_supply. It names no owner; ownership lives in the UTXOs.
  • Holders hold stealth UTXOs. There is no registry that says who holds what.
  • The auditor holds a view key: enough to decrypt amounts, not enough to spend.

Creating supply is the one step that must be public. The builder wires the authority together:

crates/template_lib/src/resource/builder/stealth.rs
let initial_bucket = ResourceBuilder::stealth()
.with_token_symbol("USDX")
.with_divisibility(6)
// Only whoever satisfies this rule may ever create supply.
.mintable(rule!(resource(mint_badge)), LOCKED)
// Give the auditor the ability to decrypt amounts, and nothing else.
.with_view_key(auditor_public_key)
// Mint is revealed: total_supply is updated in the clear.
.initial_supply(1_000_000u64);

initial_supply returns a Bucket carrying a cleartext amount, and the engine increments total_supply by that amount. The funds are public at this instant. They become private the moment the bucket is converted into stealth UTXOs:

// Moving the minted supply out of the clear. Any revealed remainder comes back as a bucket.
let revealed_remainder = initial_bucket.stealth_transfer(mint_statement);

After that call the supply lives as stealth UTXOs, and the amounts are commitments.


This is where the privacy lives, and it is the ordinary stealth transfer:

  • The sender spends one or more stealth UTXOs and creates new ones.
  • The engine verifies the statement-level balance proof ∑in=∑out\sum \text{in} = \sum \text{out} over the commitments, so value is conserved.
  • Range proofs on the outputs ensure no single commitment hides a value outside [minimum_value_promise, 2^64 − 1], which is what stops a spender from committing to a negative amount.

Neither the amounts nor the parties are readable on-chain. The mechanics — condition trees, covenants, and the runnable example — are in Stealth Transfers.


Redeeming has to decrement total_supply, and to do that the engine must learn the value being removed. For a tracked stealth resource, burning a UTXO therefore requires a CommitmentValueProof per commitment — a proof that reveals the value and nothing else:

crates/template_lib/src/models/bucket.rs
pub fn burn_with_value_proofs(
&self,
value_proofs: BTreeMap<PedersenCommitmentBytes, CommitmentValueProof>,
) { /* ... */ }

Constructing that proof requires either the commitment’s mask (which belongs to the UTXO’s owner) or the secret view key (which belongs to the auditor). Redemption is therefore gated on one of those two parties — it is not something an arbitrary third party can trigger.


By default a resource tracks its total supply, which is what forces the revealed boundary. A token that does not need a supply figure can opt out:

ResourceBuilder::stealth()
.with_token_symbol("USDX")
.disable_total_supply_tracking();

With tracking off, burns need no value proof and burnt amounts stay hidden — at the cost of there being no on-chain supply number to point at. This single call is the trade-off between auditability and privacy distilled to one line.

A view key turns “private” into “private to the public, transparent to whoever you designate.” Every output of a view-keyed resource carries a ViewableBalanceProof and an ElGamal ciphertext; the holder of the view private key recovers each amount by

V=E−p⋅RV = E - p \cdot R

and reverse-looks-up the value in the small-value table. The auditor can then sum the outputs and compare against the tracked total_supply. View is separable from control: the auditor can verify every amount and still cannot move a single unit. That split is the compliance story, and it is a first-class property of the primitive rather than a bolt-on.


Where the privacy holds, and where it leaks

Section titled “Where the privacy holds, and where it leaks”
The privacy boundary of a stablecoinMINT — publicthe amount is revealed+ total_supplya Bucket comes backin the clearsupply is stored, not summedTRANSFERS — privatestealth UTXOsamounts hiddenowners hiddenbalance proof Σin = Σoutrange proofs on every outputREDEEM — revealedCommitmentValueProof− total_supplyneeds the mask (owner)or the view key (auditor)or track no supply at allValue is revealed only where it crosses the supply boundary. Between those points it is a normal stealth resource.Always revealed, in every case:the transaction fee, and any call to a component that checks the caller’s identity (it forces a signature).

Read the diagram as a boundary, not a label: the asset type sets the ceiling, the workflow sets the floor. A stealth stablecoin whose redemption path is a public component call is only as private as that call.

Hidden:

  • Amounts, on every holder transfer.
  • Counterparties, because funds never sit in a named vault.

Revealed by design:

  • Every mint and, unless tracking is disabled, every burn — the supply boundary.
  • Every transaction fee (fees must be paid in a known amount).

Leakage the workflow can cause:

  • Identity-linked calls. Interacting with a component that authorizes on the caller’s public key attaches a signature to the transaction, linking it to you even when the outputs hide their recipients.
  • Value and timing correlation. A revealed mint of n followed by a stealth transfer of approximately n in the same block is a correlation an observer can make.
  • Public-side hops. Any step that routes funds through a public resource — a fee top-up, a swap against a public pool — leaves a plaintext trace.

The design rule that falls out of this: keep revealed amounts to the fee and the supply boundary, spend only stealth inputs, and route privileged operations through the issuer component rather than through public state.


  1. Deploy. The issuer component is published and holds the mint/burn badge; the stealth resource is created with a view key and supply tracking on.
  2. Issue. mint() produces a revealed amount and increments total_supply; the bucket is immediately converted to stealth UTXOs.
  3. Circulate. Holders transfer UTXOs privately. The chain sees commitments and proofs, never amounts or identities.
  4. Redeem. A holder (or the auditor, with the view key) burns a UTXO against a CommitmentValueProof; total_supply decrements and the value is disclosed once.
  5. Audit. The auditor decrypts output amounts with the view key and reconciles them against total_supply — a full reconciliation with no spending power and no public disclosure.

  • A stablecoin on Ootle is a stealth resource plus a component holding mint/burn authority — the peg and its reserves are off-protocol, and the protocol holds no price.
  • Transfers are private; the supply boundary is revealed. That split is forced by how supply is tracked, not layered on by convention.
  • Mint returns a revealed bucket; converting it to stealth UTXOs is where privacy begins.
  • Redeem needs a CommitmentValueProof, which requires the owner’s mask or the auditor’s view key.
  • disable_total_supply_tracking() trades the on-chain supply figure for fully hidden burns.
  • View keys give audit without spending power: V=E−pRV = E - pR.
  • The asset type sets the privacy ceiling; the workflow decides how much of it you actually keep — fees and identity-checking calls leak in every configuration.

Next: the resource types and built-in templates these pieces are built from — Templates and Assets. For the general patterns a private application reuses, see Privacy in Applications.


  • Resource builders: crates/template_lib/src/resource/builder/stealth.rs, crates/template_lib/src/resource/builder/mod.rs.
  • Bucket operations: crates/template_lib/src/models/bucket.rs.
  • Supply tracking: crates/engine_types/src/resource.rs (total_supply, increase_total_supply), driven from crates/engine/src/runtime/working_state.rs.
  • Supply-tracking switch on the native token: Tokenomics.
  • The cryptography underneath: Privacy.
  • Stealth transfers in practice: Stealth Transfers.