Skip to main content

Private Stablecoin Program

Overview

The Private Stablecoin Program is a standard for issuing and managing privacy-preserving stablecoins on the Aleo blockchain. Two stablecoins are live on mainnet using this implementation:

USDCx (usdcx_stablecoin.aleo)

Aleo and Circle partnership

usdcx_stablecoin.aleo is the first private stablecoin on Aleo, developed by Aleo in partnership with Circle. USDCx is backed 1:1 by native USDC locked in Circle xReserve smart contracts on Ethereum. When a user deposits USDC into xReserve, an equivalent amount of USDCx is minted on Aleo. More details at aleo.org/usdcx.

Circle xReserve is a non-custodial smart contract and interoperability infrastructure that provides deposit and minting attestations for USDCx on Aleo. It works alongside two complementary Circle services:

  • Circle Gateway — enables unified USDC balance functionality
  • Circle CCTP (Cross-Chain Transfer Protocol) — facilitates cross-blockchain asset transfers

Together, these make USDCx interoperable with USDC across supported blockchains without reliance on third-party bridges. Privacy features apply exclusively to USDCx while it remains on Aleo, bridging back to other blockchains removes these protections.

USAD (usad_stablecoin.aleo)

Aleo and Paxos Labs partnership

usad_stablecoin.aleo is a privacy-preserving stablecoin developed jointly by the Aleo Network Foundation (ANF) and Paxos Labs. USAD is backed by USDG (Global Dollar), a regulated, institutional-grade digital dollar issued through Paxos Labs' issuance framework. It is the first U.S. dollar stablecoin issued on a Layer 1 blockchain that combines smart contract capabilities with enhanced privacy. USAD is designed to keep sensitive information, including participant identities and transaction amounts, confidential, while remaining transparent to regulatory oversight. More details at aleo.org/usad.


Both programs share the same on-chain implementation and are designed to meet regulatory compliance requirements while leveraging Aleo's unique zero-knowledge privacy features. The program integrates three supporting components:

  • A multisig core program for admin operations requiring multi-signature approval
  • A freeze list program to enforce compliance controls on token holders
  • A Merkle tree program for efficient, privacy-preserving membership proofs

The key design goal of this stablecoin architecture is to allow fully private transfers while still enabling compliant freezing of addresses. Private senders prove they are not on the freeze list by submitting a Merkle proof of non-membership, without revealing their address on-chain.

Compliance and Privacy Design

Freeze List

The freeze list is managed by a companion program (usdcx_freezelist.aleo / usad_freezelist.aleo). Addresses on the freeze list are blocked from all public transfers. Private transfers enforce the same restriction without revealing the sender's address: the sender must supply a Merkle non-membership proof demonstrating their address is not a leaf in the freeze list tree.

ComplianceRecord

Every operation that crosses the public/private boundary (mint private, burn private, and public-to-private conversions) emits a ComplianceRecord to a designated compliance officer address. This allows the issuer to maintain an audit trail for regulatory purposes without exposing information publicly on-chain.

Multisig Administration

Admin operations are protected by usdcx_multisig_core.aleo (or usad_multisig_core.aleo), which requires multiple signers, identified by both Aleo addresses and ECDSA keys to authorize sensitive changes such as role updates and program initialization.

The underlying pattern — an Aleo smart contract wallet authorized by ECDSA signatures — is demonstrated in the virtual_wallet Leo example. This example is particularly useful for custodians who need to manage on-chain assets using existing ECDSA key infrastructure (e.g. HSMs or existing Ethereum signing keys) without exposing a native Aleo private key as the authority. It shows how to deploy a program that accepts secp256k1 ECDSA signatures as its authorization mechanism, using an ephemeral Aleo key for transaction signing while ECDSA keys retain actual spending authority.

Key Features

  • Public and private token balances
  • Role-based access control (admin, minter, burner)
  • Compliance via a freeze list with Merkle proof-based enforcement
  • Credential-gated private transfers: senders must prove they are not frozen
  • Compliance records emitted for every mint, burn, and public-to-private transfer
  • Pause mechanism for emergency halts
  • Multisig-protected administrative operations

How to Use the Private Stablecoin Program

Addresses with the MINTER_ROLE or ADMIN_ROLE can mint tokens publicly via mint_public or privately via mint_private. Addresses with BURNER_ROLE or ADMIN_ROLE can burn tokens via burn_public or burn_private.

Token holders can transfer publicly using transfer_public or privately using transfer_private. Private transfers require the sender to fetch the current freeze list Merkle tree via the Provable API, compute two adjacent MerkleProof structs that lexicographically bound their address (proving non-membership), and pass those proofs directly to transfer_private.

Public balances can be converted to private token records via transfer_public_to_private, and private records can be converted back via transfer_private_to_public. An allowance mechanism (approve_public, unapprove_public, transfer_from_public, transfer_from_public_to_private) allows third parties to spend tokens on behalf of owners.

Private Transfer Flow

Private transfers enforce freeze list compliance without revealing the sender's address on-chain. The sender must prove non-membership in the freeze list by supplying a pair of adjacent Merkle tree entries that lexicographically bound their address.

Freeze List API

To obtain the Merkle tree data needed for a private transfer, query the Provable Explorer API:

GET https://api.explorer.provable.com/v2/programs/{programID}/compliance/freeze-list
ParameterTypeDescription
programIDpath (string)The stablecoin program ID, e.g. usdcx_stablecoin.aleo

Response: An array of Merkle tree field elements representing the current freeze list. Use these to compute the two adjacent MerkleProof structs required by transfer_private.

Full API reference: Provable Explorer API v2 — Compliance Freeze List

Data Structures

Token Record

record Token {
owner: address,
amount: u128,
}

ComplianceRecord Record

record ComplianceRecord {
owner: address,
amount: u128,
sender: address,
recipient: address,
}

Emitted on every mint_private, burn_private, transfer_public_to_private, and transfer_from_public_to_private call, providing an audit trail for the compliance officer without revealing data publicly on-chain.

Credentials Record

record Credentials {
owner: address,
freeze_list_root: field,
}

Must be obtained via get_credentials before calling transfer_private. Certifies that the holder's address is not on the freeze list at the proven Merkle root.

TokenInfo Struct

struct TokenInfo {
name: u128, // ASCII text encoded as u128 bitstring
symbol: u128, // ASCII text encoded as u128 bitstring
decimals: u8,
supply: u128,
max_supply: u128,
}

TokenAllowance Struct

struct TokenAllowance {
account: address,
spender: address,
}

MerkleProof Struct

struct MerkleProof {
siblings: [field; 16],
leaf_index: u32,
}

Used in get_credentials and transfer_private to prove non-membership (or membership) in the freeze list. The tree is depth 16; siblings contains one sibling hash per level.

ChecksumEdition Struct

struct ChecksumEdition {
checksum: [u8; 32],
edition: u16,
}

AdminOp Struct

struct AdminOp {
op: u8,
threshold: u8,
aleo_signer: address,
ecdsa_signer: [u8; 20],
}

Mappings

mapping token_info: boolean => TokenInfo;
Stores the token metadata at key true. There is exactly one entry.

mapping balances: address => u128;
Maps each address to its public token balance.

mapping allowances: field => u128;
Maps the BHP256 hash of a TokenAllowance struct to the approved spending amount.

mapping address_to_role: address => u16;
Maps each address to its role bitmask.

mapping pause: boolean => boolean;
Stores the paused state at key true. When true, all minting, burning, and transfers are blocked.

Role Constants

Roles are stored as a bitmask in address_to_role. An address may hold multiple roles simultaneously.

RoleBitmaskDescription
MINTER_ROLE1u16Can call mint_public and mint_private
BURNER_ROLE2u16Can call burn_public and burn_private
ADMIN_ROLE8u16Can call all privileged functions including update_role

An address with ADMIN_ROLE can assign or revoke roles for other addresses. An admin cannot remove their own ADMIN_ROLE.

Functions

initialize()

Initializes the stablecoin program with its token metadata and sets the initial admin address. Can only be called once and only by the deployer address.

ParameterTypeDescription
namepublic u128Token name encoded as ASCII
symbolpublic u128Token symbol encoded as ASCII
decimalspublic u8Number of decimal places
max_supplypublic u128Maximum allowed supply
adminpublic addressInitial admin address

Returns: Future


update_role()

Assigns a new role bitmask to a target address. The caller must have ADMIN_ROLE. An admin cannot remove their own ADMIN_ROLE.

ParameterTypeDescription
accountpublic addressAddress to update
roleprivate u16New role bitmask to assign

Returns: Future


get_credentials()

Proves that the transaction signer is not on the freeze list by verifying two adjacent Merkle proofs against the current freeze list root. The function confirms the signer's address falls lexicographically between the two consecutive leaf entries, establishing non-membership. Returns a Credentials record required to call transfer_private.

ParameterTypeDescription
proofsprivate [MerkleProof; 2]Two adjacent proofs bounding the signer's address in the freeze list tree

Returns: Credentials (certifying non-membership at the proven root), Future


get_signing_op_id_for_deploy()

Utility function that computes the signing operation ID for a program deployment, used in multisig admin flows.

ParameterTypeDescription
checksumprivate [u8; 32]Deployment checksum
editionprivate u16Deployment edition

Returns: field — the BHP256 hash of the ChecksumEdition struct, used as the signing operation identifier.


mint_public() / mint_private()

Mints tokens to a recipient. Requires MINTER_ROLE or ADMIN_ROLE. The program must not be paused and the new supply must not exceed max_supply. mint_private emits a ComplianceRecord to the compliance officer.

ParameterTypeDescription
recipientpublic address / private addressRecipient address (private for mint_private)
amountpublic u128Number of tokens to mint

Returns: mint_publicFuture; mint_privateComplianceRecord, Token, Future


burn_public() / burn_private()

Burns tokens. Requires BURNER_ROLE or ADMIN_ROLE. The program must not be paused. burn_private emits a ComplianceRecord to the compliance officer.

ParameterTypeDescription
ownerpublic addressAddress whose tokens are burned (burn_public only)
input_recordTokenToken record to burn from (burn_private only)
amountpublic u128Number of tokens to burn

Returns: burn_publicFuture; burn_private → remaining Token, ComplianceRecord, Future


transfer_public() / transfer_public_as_signer()

Transfers tokens between public addresses. Both sender and recipient must not be on the freeze list. The program must not be paused. transfer_public_as_signer uses self.signer as the sender rather than self.caller, enabling use within program call chains while attributing the debit to the original transaction signer.

ParameterTypeDescription
recipientpublic addressRecipient address
amountpublic u128Amount to transfer

Returns: Future


approve_public() / unapprove_public()

approve_public grants a spender the ability to transfer tokens on behalf of the caller, increasing the allowance by the specified amount. unapprove_public reduces or revokes that allowance.

ParameterTypeDescription
spenderpublic addressAddress to authorize or de-authorize
amountpublic u128Amount to add to or subtract from the allowance

Returns: Future


transfer_from_public()

Transfers tokens from an owner to a recipient using a pre-approved allowance. Both owner and recipient must not be on the freeze list. The program must not be paused.

ParameterTypeDescription
ownerpublic addressAddress to debit
recipientpublic addressAddress to credit
amountpublic u128Amount to transfer

Returns: Future


transfer_public_to_private() / transfer_from_public_to_private()

Converts a public balance to a private Token record, emitting a ComplianceRecord to the compliance officer. transfer_from_public_to_private operates on behalf of an owner using a pre-approved allowance. Both require the sender/owner to not be on the freeze list and the program to not be paused.

ParameterTypeDescription
ownerpublic addressAddress to debit publicly (transfer_from_public_to_private only)
recipientprivate addressPrivate recipient address (not visible on-chain)
amountpublic u128Amount to convert

Returns: ComplianceRecord, Token, Future


transfer_private()

Transfers tokens between two private Token records. The sender must provide two adjacent MerkleProof structs proving their address is not on the current freeze list. The program must not be paused.

ParameterTypeDescription
recipientprivate addressRecipient address (not visible on-chain)
amountprivate u128Amount to transfer (not visible on-chain)
input_recordTokenSender's token record
proofsprivate [MerkleProof; 2]Merkle proofs certifying sender is not on the freeze list

Returns: sender's remaining Token, recipient's new Token, Future


transfer_private_to_public()

Converts a private Token record to a public balance. The program must not be paused.

ParameterTypeDescription
recipientpublic addressPublic recipient address
amountpublic u128Amount to convert
input_recordTokenSender's private token record

Returns: sender's remaining Token, Future