A modular suite of ERC20 token contracts for Real World Asset (RWA) tokenization, built by Digishares. Each contract in the suite is a deployable, self-contained token that combines a base ERC20 implementation with configurable extensions (minting, burning, supply cap, and pausability).
The suite is built around a single abstract base contract, ABT20, and ships 12 concrete variants that cover every combination of optional features. Deployers select the variant that matches their asset's operational requirements and deploy it directly — no proxy, no upgradability, no post-deployment configuration required beyond role assignment at construction.
All contracts are compiled with Solidity 0.8.34 and depend on OpenZeppelin Contracts.
ABT20 (abstract)
├── ERC20 — standard fungible token
├── ERC20Permit — gasless approvals via EIP-2612 signatures
└── Multicall — batch multiple calls in one transaction
ABT20 adds one extension on top of OpenZeppelin's ERC20: a configurable decimals value set at deployment time. Standard ERC20 hardcodes decimals to 18; RWA tokens often represent whole units (shares, units of property, bonds) and may use 0 or other non-standard values.
Each concrete contract inherits ABT20 and layers optional extensions:
| Contract | Mint | Burn | Cap | Pause |
|---|---|---|---|---|
ABT20Basic |
||||
ABT20Mint |
✓ | |||
ABT20Burn |
✓ | |||
ABT20Pause |
✓ | |||
ABT20MintBurn |
✓ | ✓ | ||
ABT20MintPause |
✓ | ✓ | ||
ABT20BurnPause |
✓ | ✓ | ||
ABT20MintCap |
✓ | ✓ | ||
ABT20MintBurn |
✓ | ✓ | ||
ABT20MintBurnPause |
✓ | ✓ | ✓ | |
ABT20MintCapPause |
✓ | ✓ | ✓ | |
ABT20MintBurnCap |
✓ | ✓ | ✓ | |
ABT20MintBurnCapPause |
✓ | ✓ | ✓ | ✓ |
Contracts with minting expose:
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE)Only addresses holding MINTER_ROLE can issue new tokens. In non-capped variants, there is no upper bound on total supply — the minter can issue any amount.
Contracts with burning expose:
function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE)Important: This is a forced burn — the burner can destroy tokens from any address without that address's approval. This is intentional for RWA use cases that require regulatory clawback or forced redemption (e.g., AML compliance, asset maturity). Token holders should be made aware of this at onboarding. It differs from ERC20Burnable's
burnFrom(), which requires an allowance.
Uses OpenZeppelin's ERC20Capped. The maximum supply is set at deployment and enforced on every mint. It cannot be changed after deployment.
Contracts with pause expose:
function pause() external onlyRole(PAUSER_ROLE)
function unpause() external onlyRole(PAUSER_ROLE)When paused, all token transfers (including mint and burn) revert. This is enforced via an _update override with whenNotPaused.
All contracts except ABT20Basic use OpenZeppelin's AccessControlEnumerable, which supports on-chain enumeration of role members — useful for compliance dashboards.
| Role | Constant | Capability |
|---|---|---|
DEFAULT_ADMIN_ROLE |
0x00 |
Grant and revoke all roles |
MINTER_ROLE |
keccak256("MINTER_ROLE") |
Call mint() |
BURNER_ROLE |
keccak256("BURNER_ROLE") |
Call burn() (forced, no allowance required) |
PAUSER_ROLE |
keccak256("PAUSER_ROLE") |
Call pause() / unpause() |
Roles are granted at construction time. The DEFAULT_ADMIN_ROLE holder can grant or revoke roles after deployment using the standard AccessControl interface (grantRole, revokeRole).
Operational security: The
DEFAULT_ADMIN_ROLEshould be held by a multisig (e.g., Gnosis Safe) rather than an EOA in production. A compromised admin key can grant minting or burning rights to any address.
| Parameter | Type | Description |
|---|---|---|
name |
string |
ERC20 token name (also used as EIP-712 domain name for Permit) |
symbol |
string |
ERC20 token symbol |
decimals_ |
uint8 |
Token decimal places (commonly 0 for whole-unit RWA tokens, 18 for divisible assets) |
initialSupply |
uint256 |
Tokens minted to initialHolder on deployment |
initialHolder |
address |
Recipient of the initial supply |
| Parameter | Type | Description |
|---|---|---|
defaultAdmin |
address |
Receives DEFAULT_ADMIN_ROLE; can manage all other roles |
minter |
address |
Receives MINTER_ROLE (mint variants only) |
burner |
address |
Receives BURNER_ROLE (burn variants only) |
pauser |
address |
Receives PAUSER_ROLE (pause variants only) |
| Parameter | Type | Description |
|---|---|---|
cap |
uint256 |
Maximum total supply (must be ≥ initialSupply) |
This project uses Foundry.
curl -L https://foundry.paradigm.xyz | bash
foundryupforge installforge buildforge testforge test -vvvforge fmtforge snapshotReplace ABT20Basic and constructor args with your chosen variant:
forge script script/<ScriptName>.s.sol \
--rpc-url <your_rpc_url> \
--private-key <your_private_key> \
--broadcastanvil| Library | Version | Purpose |
|---|---|---|
| OpenZeppelin Contracts | v5.x | ERC20, AccessControl, Pausable, Multicall, ERC20Permit, ERC20Capped |
| forge-std | latest | Foundry testing utilities |
MIT