Stablecoins
Covered in this page
Section titled “Covered in this page”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:
- Which
ResourceTypethe units use — this is what fixes the privacy properties. - Who may mint and burn — an
AccessRuleon the resource and on the issuing component. - 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
Section titled “The design tension”A stablecoin has two requirements that pull in opposite directions:
| Requirement | Who needs it | What it wants |
|---|---|---|
| Transactional privacy | Holders | Balances and transfers should not be public. |
| Provable supply | The issuer | The 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
Section titled “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.
| Type | Amounts hidden | Owners hidden | Supply boundary | Fit for a stablecoin |
|---|---|---|---|---|
| Public Fungible | No | No | Trivially auditable | A deposit receipt or a fully transparent instrument. Offers no privacy at all. |
| Confidential | Yes | No | Revealed at mint/burn | Balances hidden, but funds sit in named vaults — an observer sees which account moved, just not how much. |
| Stealth | Yes | Yes | Revealed at mint/burn | The 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
Section titled “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
Section titled “Issuance”Creating supply is the one step that must be public. The builder wires the authority together:
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.
Holder-to-holder transfers
Section titled “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 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
Section titled “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:
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.
The two auditability knobs
Section titled “The two auditability knobs”Supply tracking
Section titled “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.
View keys
Section titled “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
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”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
nfollowed by a stealth transfer of approximatelynin 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
Section titled “A reference lifecycle”- 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.
- Issue.
mint()produces a revealed amount and incrementstotal_supply; the bucket is immediately converted to stealth UTXOs. - Circulate. Holders transfer UTXOs privately. The chain sees commitments and proofs, never amounts or identities.
- Redeem. A holder (or the auditor, with the view key) burns a UTXO against a
CommitmentValueProof;total_supplydecrements and the value is disclosed once. - 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
Section titled “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: .
- 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
Section titled “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 fromcrates/engine/src/runtime/working_state.rs. - Supply-tracking switch on the native token: Tokenomics.
- The cryptography underneath: Privacy.
- Stealth transfers in practice: Stealth Transfers.