Skip to main content
This reference spec is for blockchain partners looking to develop and deploy a USDC-backed stablecoin that integrates with xReserve. It describes the recommended capabilities your smart contract should have, including the state to track, the callable functions to expose, and the administrative functions typically restricted to the contract owner. This spec is a recommended reference implementation, not a mandatory template. You should adapt the logic in this spec to your blockchain’s smart contract language and environment, and flag any notable deviations to Circle during integration so both teams understand them.
View a reference contract that mints and burns USDC-backed stablecoins in the Circle xReserve Contracts GitHub repository.

Deployment requirements

USDC-backed stablecoins are backed 1:1 by an equivalent amount of USDC held in Circle’s xReserve contract. Your contract mints them after verifying a deposit attestation, and users burn them when they want to withdraw USDC or other USDC-backed stablecoins on a supported blockchain. When implementing your USDC-backed stablecoin contract, ensure that you:
  • Follow the decimal precision of native USDC, which is 6 decimal places. If your blockchain token standard uses a different decimal precision, you must handle the decimal conversion in the onchain contract so that it still aligns with 6-decimal USDC.
  • Use uint256 for balances and amounts by default. If your runtime forces a smaller width such as uint128 or uint64, select the equivalent integer type and implement overflow and underflow safeguards for that width.

State and configuration requirements

The following table lists the variables your token contract should track and the invariants that must remain true after every call that changes state.
VariableDescriptionRequired Invariant
uint32 domainCircle-assigned identifier for your blockchain.Value must match the domain ID that Circle assigned to your blockchain. It must be immutable after deployment.
mapping(bytes32 => uint256) balancesTracks balances for each USDC-backed stablecoin account.Must be non-negative.
mapping(bytes32 => bool) usedNoncesTracks consumed deposit intents (for replay protection).Must be false before mint and set true atomically during mint.
mapping(address => bool) xReserveAttestersAllowlist of xReserve attester addresses (in bytes20 form).Only the owner can configure this list.
uint256 minBurnSizeMinimum burn amount for withdrawals.The owner must implement and configure this amount.
uint256 totalSupply (optional onchain)Aggregate minted minus burned balance.Must equal the sum of all account balances. Can be omitted if the complete USDC-backed token supply is indexed and exposed offchain through an API.

Core contract functions

Your USDC-backed stablecoin contract should expose these external functions.

mint(bytes depositIntent, bytes depositAttestation, uint256 feeAmount)

Mints USDC-backed stablecoins after verifying a deposit attestation. For the canonical DepositIntent message format, see the Technical Guide. Preconditions The following conditions must be met before you move state or the call will revert:
  • ECDSA.recover(hash(payload), signature) must resolve to an address whose bytes20 is true in the xReserveAttesters allowlist.
  • depositIntent.magic must be 0x5a2e0acd.
  • depositIntent.version must be 1.
  • depositIntent.amount must be greater than zero.
  • depositIntent.remoteDomain must match this USDC-backed stablecoin contract’s domain.
  • depositIntent.remoteToken must match the identifier for this USDC-backed stablecoin contract.
  • depositIntent.localToken and depositIntent.localDepositor must not be zero addresses.
  • depositIntent.amount must be at least depositIntent.maxFee.
  • depositIntent.maxFee must be greater than or equal to the feeAmount passed in.
  • usedNonces[depositIntent.nonce] must be false.
State transitions When all preconditions are met, state must be updated in this order:
  1. Set usedNonces[depositIntent.nonce] = true.
  2. Add depositIntent.amount - feeAmount to the recipient’s balance.
  3. Add feeAmount to the relayer’s balance if a relayer is present.
  4. Increase totalSupply by depositIntent.amount.
Postconditions After the mint completes, make sure:
  • totalSupply equals exactly the previous supply + depositIntent.amount.
  • The sum of balances increased by exactly depositIntent.amount.
  • Your contract emits a mint event: mint(recipient, relayer, amount, feeAmount, remoteDomain, remoteToken).

burn(uint256 amount, uint32 destinationDomain, bytes32 destinationRecipient)

Burns USDC-backed stablecoins so xReserve can release USDC on the destination.
Recommendation: Keep this signature where possible. Using the same parameter order and types simplifies downstream integrations that expect this interface.
For information on withdrawals, burn intents, and burn intent signatures, see the Technical Guide. Preconditions: The following conditions must be met before you move state or the call will revert:
  • amount must be greater than zero.
  • The caller (msg.sender) must hold at least amount.
  • amount must meet or exceed minBurnSize.
State transitions: When all preconditions are met, state must be updated in this order:
  1. Decrement the caller’s balance by amount.
  2. Decrement totalSupply by amount.
Postconditions After the mint completes, make sure:
  • Your contract emits a burn event: burn(depositor, domain, amount, destinationDomain, destinationRecipient).

Administrative functions

Access to these administrative functions should be restricted to the contract owner or an equivalent privileged role controlled by your blockchain team.

setAttester(bytes32 attester, bool enabled)

Enables or disables an attester address in the allowlist. Set enabled to true to approve an xReserve attester. Configure more than one approved attester so xReserve can rotate attester keys without downtime.

setMinBurnSize(uint256 minBurnSize)

Specifies the minimum amount to burn.