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:
| Token | Backing | Decimals |
|---|---|---|
| zkUSD | US Dollar | 6 |
| zkEUR | Euro | 6 |
| zkPLN | Polish Zloty | 6 |
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:
// 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:
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:
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:
function pause() external onlyOwner;
function unpause() external onlyOwner;Use cases:
- Critical bug discovery
- Regulatory requirement
- Emergency maintenance
Roles & Permissions
| Role | Capabilities |
|---|---|
| Owner | Pause/unpause, upgrade contracts, set roles |
| Minter | Mint new tokens (requires fiat deposit) |
| Burner | Burn tokens (for redemption) |
| Compliance | Freeze, blacklist, seize |
| Pool | Process 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:
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:
- EMT contract transfers tokens to pool
- Pool creates encrypted note commitment
- Note added to Merkle tree
How Unshield Works
When you unshield, you prove ownership of a note and withdraw publicly:
await client.unshield({
recipient: '0x...', // Public EOA
amount: parseUnits('500', 6),
tokenId: BigInt(DEPLOYMENTS.remote.tokens.zkUSD),
});Under the hood:
- ZK proof verifies note ownership
- Nullifier prevents double-spend
- EMT contract transfers tokens from pool to recipient
Using EMT Tokens
Get Token Addresses
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
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
const tokenId = BigInt(DEPLOYMENTS.remote.tokens.zkUSD);
const privateBalance = client.getBalanceByTokenId(tokenId);Transfer (Public)
await walletClient.writeContract({
address: tokens.zkUSD,
abi: EMT_ABI,
functionName: 'transfer',
args: [recipient, amount],
});Transfer (Private)
await client.transfer({
recipient: 'zks1...',
amount: parseUnits('100', 6),
tokenId: BigInt(tokens.zkUSD),
});Events
EMT tokens emit events for all compliance actions:
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.