Stablecoin

Ootle / Concepts / Stablecoin

Stablecoin

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.

Note

Everything on this page is derived from code in this repository. It describes a design pattern, not any particular deployed token. Third-party stablecoin projects are external work: they are linked as background only, and are never treated as authoritative for how Ootle behaves.


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.


The design tension

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.


Choosing the resource type

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.


The architecture

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

  • 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.

Issuance

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.

Tip

The mint is deliberately the leaky step. It is also the auditing hook: because the amount is revealed exactly once, at the boundary, the issuer and the auditor can reconcile what entered circulation without ever seeing where it went. See Privacy for why a commitment cannot be summed back into a supply figure.


Holder-to-holder transfers

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=∑outsum text{in} = sum text{out}∑in=∑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.


Redemption

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,) { /* ... */ }

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.

Caution

The burn reveals a value, not a holder. An observer learns that n units left circulation, not who redeemed them. That is the intended shape: supply is accountable, the account is not.


The two auditability knobs

Supply tracking

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.

Note

The native token is the special case that shows the rule from the other side: the genesis TARI resource disables total-supply tracking and denies Burn, which is what lets its burn-to-mint bridge mint UTXOs without a matching supply update. See Tokenomics.

View keys

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 RV=E−p⋅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

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.


A reference lifecycle

  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.

Summary

  • 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 – 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.


References and further reading

  • 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.

Note

External, unofficial. A third-party stablecoin implementation exists outside this repository at tari-project/stable-coin. It is not part of this codebase, is not covered by this documentation, and its design and audit status are not statements about Ootle.