EBSI-Integrated Deployment Guide
Overview
The deployment system has been refactored to support environment-based conditional deployment with proper separation of concerns. The operator-facing flow now runs in stages: deploy contracts, bootstrap protocol wiring, and optionally seed test data.
Architecture
DeployConfig.s.sol (base configuration)
ProtocolAddresses.s.sol (shared actor addresses)
↓
SetupEBSIInfrastructure.s.sol (abstract, all EBSI logic)
├─ DeployProtocol.s.sol (deploy contracts + write manifest)
│ ↓
│ BootstrapBase.s.sol (abstract, all wiring logic)
│ ↓
│ BootstrapProtocol.s.sol (loads manifest, wires protocol)
│ ↓
│ SeedDemoData.s.sol (optional test data seed)
↓
DeployEBSI.s.sol (standalone EBSI deployment wrapper)
Key Components
-
DeployConfig.s.sol- Network configuration management
- EBSI configuration via
getEBSIConfig() - Custom errors following
ContractName__RevertReasonpattern - Support for Anvil (default) and Besu networks
-
SetupEBSIInfrastructure.s.sol(Abstract Base)- Abstract base contract for EBSI functionality
- Handles EBSI infrastructure deployment
- Manages beacon deployment for
CompanyWallet - Template registration in
ProxyTemplateRegistry - WalletFactory integration with EBSI
- JSON serialization for EBSI contracts
-
DeployProtocol.s.sol(Main Deployment)- Inherits from
SetupEBSIInfrastructure - Orchestrates DEUSS suite deployment
- Modular deployment functions for registries, token, core contracts, and utils
- Writes deployment manifests to
./deployments/ - Leaves protocol wiring and role grants for bootstrap
- Inherits from
-
BootstrapProtocol.s.sol(Bootstrap Stage)- Loads the latest usable deployment manifest
- Initialises actor addresses from the active network config
- Sets
timelockControllerstate variable from the loaded manifest - Applies idempotent protocol wiring and minimal environment setup
- Sets up TimelockController roles (PROPOSER / EXECUTOR / CANCELLER for the actor)
- Grants the standard governance/admin roles
- Configures token, assets, currencies, and WalletFactory/EBSI bindings
- Transfers protocol ownership to the TimelockController at the end of the flow
-
SeedDemoData.s.sol(Test Data Seed)- Single operator-facing entrypoint for development and demo data
- Runs after bootstrap using the shared deployment manifest
- Replaces the older individual mock-data scripts
-
DeployEBSI.s.sol(Standalone EBSI)- Thin wrapper around
SetupEBSIInfrastructure - Used by
make deploy_ebsitarget - Standalone EBSI infrastructure deployment only
- Thin wrapper around
-
DeployConstants.sol(Constants Library)- Centralized constants for all deployment scripts
- Network configuration constants
- EBSI / wallet template configuration
- CREATE2 deployment salts
- Time-based constants
-
DeployTypes.sol(Type Definitions)- Struct definitions for all deployment data
- EBSI configuration and contract structures
- Network configuration types
- Factory, registry, and core contract structures
Script Organization
Main Scripts (script/)
deploy/DeployProtocol.s.sol- Main deployment script for the DEUSS suite (deploy only)bootstrap/BootstrapProtocol.s.sol- Bootstrap / wiring script driven by the latest manifesttest_data/SeedDemoData.s.sol- Operator-facing test/demo data seed scriptlib/DeploymentArtifacts.s.sol- Shared manifest read/write/resolve helpers used by all scriptsSetupEBSIInfrastructure.s.sol- Abstract base for EBSI functionalityDeployEBSI.s.sol- Standalone EBSI deployment wrapperDeployConfig.s.sol- Configuration and network managementDeployConstants.sol- Centralized constants libraryDeployTypes.sol- Type definitions and structs
Role Management
The standard operator flow does not use standalone role-setting scripts. BootstrapProtocol.s.sol grants the default operational roles during bootstrap, and later changes must be executed by the current contract owner or timelock through the target contract's role or admin functions.
Most protocol contracts use OwnableRolesExtension.grantRoles(...) / revokeRoles(...) for owner-gated role changes. Contract-specific admin roles then manage runtime configuration such as marketplace thresholds, asset allowlists, module registrations, and entity type allowlists.
Mock Data Scripts (script/test_data/)
Test data creation scripts for development and testing:
BaseMockData.s.sol- Shared mock data helpers and datasets, including one deterministic issuer company wallet owner per seeded bond and three deterministic buyer wallet ownersSeedDemoData.s.sol- Consolidated seed flow for wallets, currencies, bonds, issuer scoring records, offers, and marketplace deals
Operational Scripts (script/ops/, script/unpause/)
ops/GenerateBondTransferEvent.s.sol- Idempotent local Anvil helper that prepares a demo bond transfer scenario and emits an ERC-6909Transferevent. The wrapped export workflow is documented in../tools/log-export.md.UnpauseToken.s.sol- Owner/timelock-facing helper for unpausing the deployed ERC-6909 token from a deployment manifest.
Constants and Configuration
DeployConstants.sol provides centralized constants:
- Network Configuration: Anvil and Besu network settings
- EBSI Templates: CompanyWallet template names, versions, and metadata
- Repository URIs: GitHub and audit report URLs
- CREATE2 Salts: Deterministic deployment salts for factories
- Time Constants: Validity periods and expiry thresholds for marketplace operations
- EIP-1967 Slot: Proxy implementation storage slot
Production timing defaults are also centralized there:
| Area | Setting | Default |
|---|---|---|
| Marketplace | Direct marketplace payment expiry | 4 days / 96 hours |
| Marketplace | Redemption payment expiry | 5 days / 120 hours |
| Marketplace | Interest-discovery payment expiry | 4 days / 96 hours |
| Marketplace | Minimum offer and counter-offer lifetime | 1 day / 24 hours |
| Marketplace | Dispute buffer period | 3 days / 72 hours |
| Orderbook | Payment expiry | 3 days / 72 hours |
| Orderbook | Minimum non-zero order expiry | 30 minutes |
| Orderbook | Dispute buffer period | 3 days / 72 hours |
The lower-level deployer helpers and CREATE2/beacon deployment pattern are documented in deployer/DeploymentHelpers.md.
Environment Variables
Required Variables
Create a .env file based on the template below:
# Network Configuration
BESU_NETWORK_ID=1337
BESU_RPC_URL=http://localhost:8545
BESU_DEPLOYER_PRIVATE_KEY=0x...
BESU_ADMIN_PRIVATE_KEY=0x...
BESU_DEPLOY_ENV=dev
BESU_TIMELOCK_ADMIN_PRIVATE_KEY=0x...
BESU_TIMELOCK_ACTOR_PRIVATE_KEY=0x...
# Timelock Configuration (optional, defaults to 1 day)
# Set to 0 for testnet deployments where you don't want to wait for the delay
# PRODUCTION: never use 0 — a zero-delay timelock defeats governance entirely
# See the launch gates in docs/security/operational-model.md
TIMELOCK_MIN_DELAY=0
# EBSI Configuration
DEPLOY_EBSI=true # true=deploy EBSI, false=connect to existing
# Required when DEPLOY_EBSI=false
EBSI_PROXY_FACTORY=
EBSI_PROXY_REGISTRY=
EBSI_DID_BOND_REGISTRY=did:ebsi:bond-registry
EBSI_DID_ENTITY_REGISTRY=did:ebsi:entity-registry
# Beacon Configuration (optional)
DEPLOY_BEACONS=true # true=deploy new, false=use existing
BEACON_CW= # Required if DEPLOY_BEACONS=false
# Bootstrap Configuration (optional, defaults apply when absent)
BOOTSTRAP_UNPAUSE_TOKEN=true # Unpause the token after bootstrap
BOOTSTRAP_ALLOWED_CURRENCIES=EUR # Comma-separated currency codes to allowlist
BOOTSTRAP_REGISTER_ESCROW_ENTITY=true # Register EscrowManager as a protocol entity
BOOTSTRAP_COMPANY_ENTITY_NAME=COMPANY # Entity type name for COMPANY_ENTITY
BOOTSTRAP_COMPANY_ENTITY_CAPS=0 # Entity capability bitmask
Note: Despite the variable name, EBSI_DID_BOND_REGISTRY is currently used as the existing DID registry address when DEPLOY_EBSI=false.
Deployment Scenarios
Scenario 1: Own Testnet (Full Deployment)
Deploy everything including EBSI infrastructure.
# Set environment
export DEPLOY_EBSI=true
# Run deployment
make setup_full
# Or simply
make setup
What happens:
- Deploys complete EBSI infrastructure (PolicyRegistry, DidRegistry, ProxyFactory, etc.)
- Deploys the CompanyWallet beacon
- Registers the CompanyWallet template in
ProxyTemplateRegistry - Runs
DeployProtocol.s.solto deploy the DEUSS suite (registries, token, marketplace modules, utils) - Writes deployment artifacts to
./deployments/ - Runs
BootstrapProtocol.s.solto wire protocol dependencies, grant roles, configure token/assets, and connect WalletFactory to EBSI - Does not seed demo data unless you use
make setup_full_test_dataormake setup_test_data
Scenario 2: EBSI Chain (Existing Infrastructure)
Connect to existing EBSI contracts.
# Set environment
export DEPLOY_EBSI=false
export EBSI_PROXY_FACTORY=0x...
export EBSI_PROXY_REGISTRY=0x...
export EBSI_DID_BOND_REGISTRY=0x...
# Run deployment
make setup_ebsi_existing
What happens:
- Loads existing EBSI infrastructure addresses
- Deploys or loads the CompanyWallet beacon, depending on
DEPLOY_BEACONS - Registers the CompanyWallet template in the existing
ProxyTemplateRegistry - Runs
DeployProtocol.s.solto deploy the DEUSS suite - Runs
BootstrapProtocol.s.solto wire the protocol, grant roles, configure token/assets, and connect WalletFactory to the existing EBSI/core modules - Uses
make setup_ebsi_existing_test_dataif demo data is needed
Existing Deployments
For already deployed environments that have not yet handed ownership to the TimelockController, you can rerun the bootstrap stage against the existing deployment manifest to apply the current wiring flow:
make bootstrap
After bootstrap has transferred ownership to the TimelockController, owner-gated bootstrap steps can no longer be rerun with the deployer key alone. Run make mock_data only when you need development/demo data on an already bootstrapped environment.
Scenario 3: Reuse Existing Beacons
Useful for upgrades or testing without redeploying the CompanyWallet beacon.
# Set environment
export DEPLOY_EBSI=true # or false
export DEPLOY_BEACONS=false
export BEACON_CW=0x...
# Run deployment
make setup
Makefile Targets
Main Targets
make setup- Standard deployment (make deploy+make bootstrap)make setup_full- Explicit full deployment with EBSI infrastructuremake setup_ebsi_existing- Deploy to existing EBSI (validates env vars)make setup_test_data- Standard deployment plus test data seed
Component Targets
make deploy- Deploy DEUSS contracts only (viascript/deploy/DeployProtocol.s.sol)make bootstrap- Apply protocol wiring, default role grants, and transfer ownership to the TimelockController (viascript/bootstrap/BootstrapProtocol.s.sol)make deploy_ebsi- Deploy EBSI infrastructure only (viaDeployEBSI.s.sol)make mock_data- Run test data seed against an already bootstrapped deployment (viascript/test_data/SeedDemoData.s.sol)make generate_bond_transfer_event- Run the local transfer-event generator against an already deployed and bootstrapped Anvil network (viascript/ops/GenerateBondTransferEvent.s.sol)make verify_contracts- Verify already-deployed contracts using existing broadcast files (retryable, no redeployment)
Role Management
There is no set_roles Make target in the current workflow. BootstrapProtocol.s.sol grants the default operational roles automatically. Later role changes are contract-owner/timelock operations against the deployed contracts.
The relevant role surfaces are documented in roles.md. Production operations should respect the active timelock delay for the current owner before calling owner-gated role functions.
Production warning.
BootstrapProtocol.s.solgrants the default operational role bundles to a single admin actor and, on testnets, may run withTIMELOCK_MIN_DELAY=0. This is not a production-safe role/ownership layout. Before opening to users, run the launch gates in the operational model: non-zero timelock delay, owners transferred to a governance multisig behind the timelock, critical roles split across distinct signers/services matched to their latency and blast radius (notably fastFREEZE_ROLEseparate from governedSEIZURE_ROLE,FORCE_TRANSFER_ROLEassigned toTimelockController, andWALLET_TRANSFERmultisig-controlled with timelock where viable), and deployment manifests reconciled against the recommended role/owner model.
Mock Data Scripts
The mock_data target runs the consolidated test data flow against an already bootstrapped deployment. The setup_test_data, setup_full_test_data, and setup_ebsi_existing_test_data targets run deployment/bootstrap plus the same seed flow.
BaseMockData.s.sol- Shared helpers and datasets, including per-bond issuer indexes and per-deal buyer indexesSeedDemoData.s.sol- Creates test wallets, currencies, bonds, issuer scoring records, offers, pending deals, paid deals, and settled successful deals
Custom Errors
All validation errors follow the pattern ContractName__RevertReason:
DeployConfig Errors
DeployConfig__EBSIProxyFactoryNotSet()- EBSI_PROXY_FACTORY not configuredDeployConfig__EBSIProxyRegistryNotSet()- EBSI_PROXY_REGISTRY not configuredDeployConfig__EBSIDidRegistryNotSet()- EBSI_DID_BOND_REGISTRY not configuredDeployConfig__BeaconCWNotSet()- BEACON_CW not configured
Deployment Flow
Complete Flow Diagram
1. Read Environment Config (DeployConfig.s.sol)
├─ getNetworkConfig() - Load network settings
├─ getEBSIConfig() - Load EBSI configuration
└─ Validate required variables
2. Setup EBSI (SetupEBSIInfrastructure.s.sol)
├─ Deploy or Load EBSI Infrastructure
│ ├─ If DEPLOY_EBSI=true
│ │ ├─ Deploy PolicyRegistry
│ │ ├─ Deploy DidRegistry
│ │ ├─ Deploy ProxyTemplateRegistry
│ │ ├─ Deploy ProxyFactory
│ │ └─ Deploy Tir
│ └─ If DEPLOY_EBSI=false
│ └─ Load from environment variables
│
├─ Deploy or Load Beacons
│ ├─ If DEPLOY_BEACONS=true
│ │ ├─ Deploy CompanyWallet implementation
│ │ └─ Deploy CompanyWallet UpgradeableBeacon
│ └─ If DEPLOY_BEACONS=false
│ └─ Load CompanyWallet beacon from environment variables
│
└─ Register Templates in ProxyTemplateRegistry
└─ Register CompanyWallet template
3. Deploy DEUSS Contracts (DeployProtocol.s.sol)
├─ Deploy Registries
│ ├─ BondRegistry (via BRDeployer)
│ ├─ EntityRegistry (via ERDeployer)
│ ├─ PolicyRegistry (via PolicyRegistryDeployer)
│ └─ WalletFactory (standalone deployment, configured later in bootstrap)
│
├─ Deploy Core Contracts
│ ├─ AssetManager (via AssetManagerDeployer)
│ ├─ Marketplace (via MarketplaceDeployer, initialized with EntityRegistry)
│ ├─ OrderbookMarketplace (via OrderbookMarketplaceDeployer)
│ └─ EscrowManager (via EscrowManagerDeployer)
│
├─ Deploy Token
│ └─ ERC-6909 token proxy (via TokenDeployer)
│ └─ initialize(..., EscrowManager) atomically protects EscrowManager custody
│
└─ Deploy Utils
├─ Multicall3
└─ MarketplaceLens
4. Save Deployment JSON
├─ Serialize all contract addresses
├─ Include EBSI contracts and CompanyWallet beacon
├─ Include MarketplaceLens proxy, beacon, and implementation
├─ Write to ./deployments/{chainId}_{env}_latest.json
└─ Write block snapshot to ./deployments/{chainId}_{env}_{block}.json
> **Utility ownership note.** `MarketplaceLens` is read-only, but it is deployed as an
> upgradeable beacon proxy. The deployment manifest stores its proxy, beacon, and
> implementation so bootstrap can transfer both proxy and beacon ownership to
> `TimelockController` with the rest of the protocol-owned surface.
5. Bootstrap Protocol (BootstrapProtocol.s.sol)
├─ Load latest usable deployment manifest
├─ Set timelockController from manifest
├─ Set up TimelockController roles (PROPOSER / EXECUTOR / CANCELLER → timelockControllerActor)
├─ Wire core dependencies
│ └─ Marketplace receives EntityRegistry during deployment; bootstrap wires the remaining core dependencies
├─ Grant EntityRegistry roles → admin (ADMIN_ROLE | ONBOARDING | GUARD | ...)
├─ Grant BondRegistry operational roles → admin (PUBLISHER | CANCEL | CLOSE | SCORING | ...)
├─ Grant BondRegistry critical recovery role → TimelockController (ISSUER_RECOVERY)
├─ Leave BondRegistry BURNER unset
├─ Grant marketplace operational roles → admin (ADMIN | PAYMENT_HANDLER | ARBITRATOR | FREEZE_ROLE | ...)
├─ Grant marketplace seizure roles → TimelockController (SEIZURE_ROLE)
├─ Wire WalletFactory to EBSI
│ ├─ Grant TRUSTED_ISSUER_ROLE to WalletFactory
│ ├─ setFactory(proxyFactory)
│ ├─ setRegistry(proxyTemplateRegistry)
│ ├─ setDid(entityRegistryDID)
│ └─ setWalletTemplateForType(COMPANY_WALLET_TYPE, ...)
│
├─ Configure token and assets
│ ├─ setMultiToken(erc6909)
│ │ └─ one-time BondRegistry token wiring; later calls revert after the lock is set
│ ├─ grant FORCE_TRANSFER_ROLE → TimelockController
│ ├─ optionally unpause token
│ ├─ AssetManager.setAsset(erc6909, ERC6909, true, false)
│ └─ EscrowManager custody is already protected by DEUSSToken initialization
│
└─ Ensure baseline entity types, currencies, escrow entity, and timelock protocol entity
6. Mock Data (test_data/ folder, optional)
├─ BaseMockData.s.sol
└─ SeedDemoData.s.sol
├─ Ensure company wallets
│ ├─ registerEntity → adminKey (ONBOARDING role)
│ ├─ setEntityStatus → adminKey (GUARD role)
│ ├─ setEntityAuthority / setEntityManager → adminKey (ADMIN_ROLE path)
│ └─ createWallet / registerAccount → adminKey (ADMIN_ROLE; immediate bootstrap path)
├─ Ensure currencies → adminKey (CURRENCY role)
├─ Publish test bonds → adminKey (PUBLISHER role)
├─ Append issuer scoring records → adminKey (SCORING role)
├─ Issue / cancel test bonds → adminKey (PUBLISHER / CANCEL role)
└─ Seed marketplace and orderbook offers / deals
├─ Marketplace.resolvePayment → adminKey (PAYMENT_HANDLER role)
└─ OrderbookMarketplace.markTradePaid → adminKey (PAYMENT_HANDLER role)
Output JSON Structure
The deployment creates a comprehensive JSON file with all addresses:
{
"governance": {
"TimelockController": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
}
},
"core": {
"AssetManager": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"EscrowManager": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"Marketplace": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"OrderbookMarketplace": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
}
},
"registries": {
"BondRegistry": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"EntityRegistry": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"PolicyRegistry": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
},
"WalletFactory": {
"contractAddress": "0x...",
"beaconAddress": "0x..."
}
},
"wallet": {
"CompanyWallet": {
"beaconAddress": "0x...",
"implementationAddress": "0x..."
}
},
"multiToken": {
"deussToken": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
}
},
"utils": {
"Multicall3": {
"contractAddress": "0x..."
},
"MarketplaceLens": {
"contractAddress": "0x...",
"beaconAddress": "0x...",
"implementationAddress": "0x..."
}
},
"ebsi": {
"DidRegistry": {
"contractAddress": "0x..."
},
"ProxyTemplateRegistry": {
"contractAddress": "0x..."
},
"ProxyFactory": {
"contractAddress": "0x..."
},
"PolicyRegistry": {
"contractAddress": "0x..."
},
"Tir": {
"contractAddress": "0x..."
}
}
}
Testing the Deployment
Verify Environment Configuration
# Check environment variables
echo $DEPLOY_EBSI
echo $EBSI_PROXY_FACTORY
Test Scenarios
-
Local Anvil (Full)
# Start anvilanvil# In another terminalmake setup_full -
Besu Testnet (Full)
# Ensure .env is configured with Besu settingsmake setup_full NETWORK=besu -
EBSI Chain (Existing Infrastructure)
# Configure EBSI addresses in .envmake setup_ebsi_existing
Troubleshooting
Error: EBSI addresses not set
Problem: Running setup_ebsi_existing without configuring EBSI addresses
Solution:
export EBSI_PROXY_FACTORY=0x...
export EBSI_PROXY_REGISTRY=0x...
export EBSI_DID_BOND_REGISTRY=0x...
Error: DeployConfig__EBSIProxyFactoryNotSet
Problem: DEPLOY_EBSI=false but EBSI addresses not configured
Solution: Set all required EBSI environment variables
Error: Template already exists
Problem: Trying to register the CompanyWallet template when it already exists in ProxyTemplateRegistry
Solution: This is expected behavior - templates persist in the registry. Either:
- Use existing templates (don't redeploy EBSI)
- Deploy to a fresh chain/network
Error: Bootstrap fails
Problem: BootstrapProtocol.s.sol or SeedDemoData.s.sol cannot find a usable deployment manifest
Solution: Ensure deployment completed successfully and the JSON file exists at ./deployments/{chainId}_{env}_latest.json
Error: Mock data scripts fail
Problem: SeedDemoData.s.sol fails due to missing bootstrap state or permissions
Solution:
- Ensure contracts were deployed successfully
- Run
make bootstrapbeforemake mock_data - Do not rerun
make bootstrapafter ownership has already been transferred unless the current owner/timelock is performing the owner-gated calls - Verify environment variables are set correctly
Best Practices
- Use environment files - Keep separate
.env.dev,.env.demo,.env.prod - Never commit private keys - Always use
.env(gitignored) - Verify addresses - Double-check EBSI addresses before deployment
- Test locally first - Always test on Anvil before deploying to testnets
- Save deployment artifacts - Keep JSON outputs for future reference
- Document custom DIDs - Track which DIDs are used for which contracts
Migration from Old System
If migrating from the old deployment system:
- Environment variables - Add the current EBSI-related variables to
.env - Makefile - Use
setup,bootstrap,mock_data,setup_test_data,setup_full, orsetup_ebsi_existingdepending on the environment - Scripts - Individual mock-data scripts have been consolidated into
SeedDemoData.s.sol - Roles - Default operational role grants are now part of bootstrap
- Constants - All hardcoded constants are centralized in
DeployConstants.sol - Types - All deployment structs are defined in
DeployTypes.sol
Key Changes from Previous Versions
DeployDEUSSSuite.s.sol→DeployProtocol.s.sol+BootstrapProtocol.s.solfor the standard operator flow- Individual mock-data scripts →
SeedDemoData.s.sol(consolidated seed flow) EBSIDeploymentBase.s.sol→SetupEBSIInfrastructure.s.sol(renamed for clarity)- Constants extraction - All constants moved to
DeployConstants.sol - Type definitions - All structs moved to
DeployTypes.sol - JSON structure - Updated to reflect current deployment output format
Support
For issues or questions:
- Check this guide
- Verify environment configuration
- Review linter errors:
make lint - Check deployment logs in terminal output