Skip to main content
This page covers operational and security patterns for running commodity token deployments in production. These recommendations apply to all contracts in the Commodity Token License suite.

Access Control

Use multisigs for all privileged roles. The factory admin, token mainIssuer, identity registry DEFAULT_ADMIN_ROLE, and LEGAL_OPERATOR_ROLE should all be Gnosis Safe multisigs with appropriate signer thresholds. A single compromised EOA controlling any of these roles can drain or freeze the entire token. Separate the main issuer from operational delegates. The mainIssuer role should be a cold multisig used only for high-impact operations: transferring the issuer role, setting the upgrade authority, and configuring sub-issuers. Day-to-day operations (pausing, freezing, whitelist management) should go through delegates. This limits the blast radius of a compromised operational key. Set mint limits on sub-issuers. Any address configured as a minter via configureIssuer should have a non-zero mintLimit proportional to expected activity. A sub-issuer with mintLimit = 0 (unlimited) has the same minting power as the main issuer. Review and adjust limits periodically. Rotate KYC provider keys. The KYC_PROVIDER_ROLE on the identity registry can verify arbitrary identities. If a provider key is compromised, revoke it immediately via removeKYCProvider and re-grant to a new address.

Reserve Management

Update attestations on a regular cadence. Call updateReserveAttestation at least as frequently as your custody provider issues vault reports. The on-chain reserveAttestationURI should always point to the most recent third-party attestation. Stale attestation URIs undermine investor confidence even if the reserve ratio is technically valid. Never call updateReserveAttestation with a totalGrams value you cannot prove. The contract enforces that the reserve ratio holds, but it cannot verify that the grams actually exist in a vault. The attestation URI and vault location hash are your proof layer - ensure they reference verifiable, auditable documents. Monitor isReserveCompliant() off-chain. Set up automated monitoring that calls this view function and alerts if it ever returns false. While the contract prevents minting into a non-compliant state, manual reserve updates that incorrectly lower totalGramsReserve could create a compliance gap.

Oracle Configuration

Set maxOracleAge conservatively. The default of 3600 seconds (1 hour) is appropriate for most commodity markets. For highly volatile assets or during market stress, consider reducing this to 900-1800 seconds. An oracle that has not updated within maxOracleAge will cause all cash redemptions and oracle-priced sales to revert. Use a dedicated oracle per token. Even if multiple tokens reference the same underlying commodity, deploy separate oracle instances. This prevents a single oracle failure from cascading across tokens and allows per-token staleness configuration. Test oracle failure modes. Before going live, verify that your system handles OraclePriceStale reverts gracefully. Cash redemption and oracle-priced sale purchases will fail when the oracle is stale - your frontend and backend should surface this to users rather than showing generic transaction errors.

Upgrade Governance

Review proposed implementations before accepting. When TrussetDAO proposes an upgrade via proposeUpgrade, the issuer should audit the new implementation contract before calling acceptUpgrade. Verify the bytecode matches a known, audited version. Never accept an upgrade without reviewing the code. Use rejectUpgrade explicitly. If you do not intend to upgrade, call rejectUpgrade to clear the pending implementation. Leaving a pending upgrade indefinitely creates confusion about the contract’s intended state. Test upgrades on a fork first. Before accepting a production upgrade, deploy the new implementation on a mainnet fork and verify that all state is preserved, all functions behave correctly, and the storage layout is compatible.

Identity and Compliance

Set both soft and hard expiry on identities. The soft expiry triggers SOFT_EXPIRED status, which still allows transfers but signals that KYC needs renewal. The hard expiry blocks transfers entirely. Use soft expiry as an early warning (e.g., 30 days before hard expiry) so investors have time to re-verify. Use claims for granular attestations. Rather than encoding all compliance data into the kycHash, use the claims system for specific attestations (ACCREDITATION, SANCTIONS_CHECK, PEP_CHECK). This allows revoking individual attestations without invalidating the entire identity. Configure compliance modules before enabling KYC on the token. If you add a compliance module to the identity registry after tokens are already circulating with KYC enabled, existing transfers that were previously valid may start failing. Add and configure modules before going live, or coordinate the rollout carefully. Handle batchVerifyIdentities partial failures. The function returns failedIndices for entries that could not be processed (invalid data, duplicate identities, or gas exhaustion). Your integration layer should retry failed entries in a subsequent call rather than silently dropping them.

Sale Operations

Fund USDC before enabling cash redemption. The token contract checks IERC20(usdcToken).balanceOf(address(this)) during cash redemption execution. If the balance is insufficient, the burn request execution reverts with InsufficientUSDCLiquidity but the request remains in APPROVED state and can be retried after funding. Use slippage protection on purchases. The maxPayment parameter in purchase protects buyers from oracle price spikes between transaction submission and execution. For oracle-priced sales, recommend that frontends set maxPayment to 1-2% above the displayed price. Deactivate completed sales. After a sale round ends or sells out, call updateSale to set active = false. For pre-funded sales, call withdrawUnsoldTokens to recover remaining tokens. Leaving inactive sales in an active state wastes gas on validation checks for purchases that will fail anyway.

Operational Monitoring

Key on-chain values to monitor continuously:
  • isReserveCompliant() - should always return true
  • totalSupply vs maxSupply - approaching the cap requires either increasing maxSupply or stopping mints
  • paused state - unexpected pauses indicate an incident
  • frozen accounts - track freezes for compliance reporting
  • Oracle timestamp freshness - stale oracles block redemptions and sales
  • lockedBalances - high locked balances indicate pending burn requests that may need attention
  • Compliance module canTransfer rejections - monitor for unexpected transfer failures that could indicate misconfiguration

Emergency Procedures

Pause first, investigate second. If you detect suspicious activity (unauthorized mints, compromised keys, oracle manipulation), call pause() on the token immediately. This halts all transfers, mints, and burns. The identity registry has its own pause function for registry-level emergencies. Freeze targeted accounts. If the issue is isolated to specific addresses, use freezeAccount on the token and freezeAddress on the identity registry. Freezing is less disruptive than a full pause and allows legitimate activity to continue. Cancel pending requests. Use cancelRequest(requestId, isMint) to cancel any pending mint or burn requests from compromised sub-issuers. For burn requests, cancellation unlocks the user’s tokens.