Deployment Sequence
Deploy the Identity Registry
Deploy
IdentityRegistryUpgradeable as a UUPS proxy. Initialize with your Gnosis Safe multisig as gnosisSafe (receives DEFAULT_ADMIN_ROLE) and a named legal entity as legalOperator (receives LEGAL_OPERATOR_ROLE, required by BaFin for upgrade authorization).After initialization, grant roles:KYC_PROVIDER_ROLEto your licensed KYC service viaaddKYCProvider(address, name)REGISTER_OPERATOR_ROLEto your register operator viaaddRegisterOperator(address, name)COMPLIANCE_ROLEto your compliance officer(s) viaaddComplianceOfficer(address)
Deploy the Token Implementation
Deploy
CommodityTokenUpgradeable as a standalone contract (not behind a proxy). This serves as the shared implementation that all token proxies will point to. Do not call initialize on the implementation - the constructor calls _disableInitializers().Deploy the Factory
Deploy
CommodityTokenFactoryUpgradeable as a UUPS proxy. Initialize with:_identityRegistry- address from Step 1_commodityTokenImplementation- address from Step 2_admin- TrussetDAO Gnosis Safe address_platformWallet- Trusset treasury address for fee collection
Create Tokens
Call
createCommodityToken or createSimpleCommodityToken on the factory. Each call deploys a new ERC1967Proxy and initializes the token with its own mainIssuer, gramsPerToken, maxSupply, and compliance settings.The mainIssuer address receives full operational control. The upgradeAuthority is set to the factory’s admin (TrussetDAO).Deploy Compliance Module (optional)
Deploy
BasicComplianceModule with the token contract address from Step 4, defaultMaxHolding (set 0 for unlimited), and defaultMinHolding.Register the module on the identity registry via addComplianceModule(moduleAddress).BasicComplianceModule takes tokenContract as an immutable constructor parameter. Deploy it after the token exists, or use deterministic deployment (CREATE2) to predict the token address in advance.Deploy Sale Infrastructure (optional)
Deploy
CommodityTokenSaleFactory with the platform wallet. Create sale contracts via createSaleContract(tokenAddress, issuerAddress).For mint-on-purchase sales, the sale contract must be registered as an authorized minter on the token: call configureIssuer(saleContractAddress, true, false, 0) on the token as the main issuer.Token Configuration
After deployment, the main issuer (or delegates) should configure the token for their use case.Fees
KYC and Transfer Restrictions
Redemption
Sub-Issuers and Delegates
Price Oracle
The suite expects a price oracle implementingIPriceOracle:
block.timestamp - timestamp > maxOracleAge, the transaction reverts with OraclePriceStale.
Sale Configuration
Creating a Sale Round
mintOnPurchase sales, ensure the sale contract has minter permissions on the token before the sale starts. For pre-funded sales, call fundSale(saleId, amount) after creating the sale round.
Payment Handling
ETH sales: buyers send ETH with theirpurchase call. The contract forwards payment to the issuer and refunds any excess.
ERC-20 sales: buyers must approve the sale contract for the total payment amount (including fees) before calling purchase. The contract pulls payment via safeTransferFrom.
Identity Registry Configuration
Verifying Investors
The KYC provider verifies investors before they can receive or transfer tokens:batchVerifyIdentities (up to 500 per call). The function returns successCount and failedIndices for partial-failure handling.
Adding Claims
Compliance Modules
After deployingBasicComplianceModule:
IComplianceModule.canTransfer. Register them on the identity registry alongside or instead of BasicComplianceModule.
Reserve Management
The issuer maintains reserve attestation data on-chain:totalGrams still satisfies totalSupply * gramsPerToken <= totalGrams * 1e18. You cannot decrease reserves below what the current supply requires.
isReserveCompliant() is a public view function that returns whether the reserve ratio holds - useful for monitoring and off-chain verification.