Skip to content

EMT Stablecoins

MiCA-compliant Electronic Money Tokens with built-in compliance features.

What are EMT Tokens?

EMT (Electronic Money Token) stablecoins are fully-backed digital euros, dollars, and other fiat currencies designed for the EU regulatory framework:

TokenBackingDecimals
zkUSDUS Dollar6
zkEUREuro6
zkPLNPolish Zloty6

Key Features

  • 1:1 Fiat Backing - Each token backed by reserves held at regulated institutions
  • MiCA Compliance - Meets EU Electronic Money Token requirements
  • Issuer Controls - Freeze, blacklist, seize capabilities for compliance
  • Privacy Compatible - Can be shielded into the privacy pool

Architecture

┌──────────────────────────────────────────────────────────┐
│                     EMT Stablecoin                        │
│  ┌─────────────────────────────────────────────────────┐ │
│  │                Standard ERC-20                       │ │
│  │  transfer() · approve() · balanceOf() · etc.        │ │
│  └─────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────┐ │
│  │              Compliance Features                     │ │
│  │  freeze() · blacklist() · seize() · pause()         │ │
│  └─────────────────────────────────────────────────────┘ │
│  ┌─────────────────────────────────────────────────────┐ │
│  │              Privacy Pool Integration               │ │
│  │  shield() · processTransfer() · processUnshield()   │ │
│  └─────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘

Compliance Features

Account Freezing

Temporarily prevent an account from transferring tokens:

solidity
// Issuer can freeze accounts
function freeze(address account) external onlyCompliance;
function unfreeze(address account) external onlyCompliance;

// Check freeze status
function isFrozen(address account) external view returns (bool);

Use cases:

  • Suspected fraud investigation
  • Court order compliance
  • Temporary holds during verification

Blacklisting

Permanently block addresses from using the token:

solidity
function blacklist(address account) external onlyCompliance;
function removeFromBlacklist(address account) external onlyCompliance;
function isBlacklisted(address account) external view returns (bool);

Use cases:

  • Sanctioned entities (OFAC, EU sanctions)
  • Known fraud addresses
  • Addresses associated with illicit activity

Asset Seizure

Transfer tokens from one address to another under legal authority:

solidity
function seize(
    address from,
    address to,
    uint256 amount
) external onlyCompliance;

Use cases:

  • Court-ordered asset recovery
  • Bankruptcy proceedings
  • Law enforcement actions

WARNING

Seizure requires valid legal authorization. All seizure events are logged on-chain for transparency.

Pause/Unpause

Emergency stop for all token transfers:

solidity
function pause() external onlyOwner;
function unpause() external onlyOwner;

Use cases:

  • Critical bug discovery
  • Regulatory requirement
  • Emergency maintenance

Roles & Permissions

RoleCapabilities
OwnerPause/unpause, upgrade contracts, set roles
MinterMint new tokens (requires fiat deposit)
BurnerBurn tokens (for redemption)
ComplianceFreeze, blacklist, seize
PoolProcess shield/transfer/unshield operations

Privacy Pool Integration

How Shield Works

When you shield EMT tokens, they move from your public wallet to the privacy pool:

typescript
import { shieldTo } from '@zkprivacy/sdk';

// Shield 1000 zkUSD to your private address
await shieldTo(config, {
  recipient: 'zks1...',
  amount: '1000',
  tokenAddress: DEPLOYMENTS.remote.tokens.zkUSD,
  walletClient,
});

Under the hood:

  1. EMT contract transfers tokens to pool
  2. Pool creates encrypted note commitment
  3. Note added to Merkle tree

How Unshield Works

When you unshield, you prove ownership of a note and withdraw publicly:

typescript
await client.unshield({
  recipient: '0x...',  // Public EOA
  amount: parseUnits('500', 6),
  tokenId: BigInt(DEPLOYMENTS.remote.tokens.zkUSD),
});

Under the hood:

  1. ZK proof verifies note ownership
  2. Nullifier prevents double-spend
  3. EMT contract transfers tokens from pool to recipient

Using EMT Tokens

Get Token Addresses

typescript
import { DEPLOYMENTS } from '@zkprivacy/sdk';

const { tokens } = DEPLOYMENTS.remote;
console.log('zkUSD:', tokens.zkUSD);
console.log('zkEUR:', tokens.zkEUR);
console.log('zkPLN:', tokens.zkPLN);

Check Public Balance

typescript
import { createPublicClient, http } from 'viem';
import { EMT_ABI } from '@zkprivacy/sdk';

const balance = await publicClient.readContract({
  address: tokens.zkUSD,
  abi: EMT_ABI,
  functionName: 'balanceOf',
  args: [account],
});

Check Private Balance

typescript
const tokenId = BigInt(DEPLOYMENTS.remote.tokens.zkUSD);
const privateBalance = client.getBalanceByTokenId(tokenId);

Transfer (Public)

typescript
await walletClient.writeContract({
  address: tokens.zkUSD,
  abi: EMT_ABI,
  functionName: 'transfer',
  args: [recipient, amount],
});

Transfer (Private)

typescript
await client.transfer({
  recipient: 'zks1...',
  amount: parseUnits('100', 6),
  tokenId: BigInt(tokens.zkUSD),
});

Events

EMT tokens emit events for all compliance actions:

solidity
event Frozen(address indexed account);
event Unfrozen(address indexed account);
event Blacklisted(address indexed account);
event RemovedFromBlacklist(address indexed account);
event Seized(address indexed from, address indexed to, uint256 amount);
event Paused(address account);
event Unpaused(address account);

Compliance vs Privacy

Balance of Rights

EMT tokens balance user privacy with regulatory requirements:

  • Normal operations: Fully private within the pool
  • Court orders: Compliance layer (UKRC) enables authorized decryption
  • Sanctioned addresses: Blocked at shield/unshield boundaries

The privacy pool cannot be directly frozen or seized - only the shield/unshield endpoints interact with compliance features. Once shielded, tokens operate privately until unshielded.

Released under the MIT License.