This page covers operational patterns, security considerations, and common pitfalls when running the Stock Token suite in production.
Key Management
Use a multi-sig for DEFAULT_ADMIN_ROLE. The admin role controls all other role assignments. A compromised admin key means full control over the token. Deploy a Gnosis Safe with an appropriate threshold (e.g. 3-of-5) and assign it as the admin during initialization.
Separate the legal operator. LEGAL_OPERATOR_ROLE authorizes contract upgrades. Under eWpG, this must be a named legal entity. Use a dedicated multi-sig or institutional custody solution for this role - do not combine it with the issuer key.
Rotate sub-issuer keys periodically. Sub-issuers have minting authority. Even with caps in place, a compromised sub-issuer key can mint up to the cap limit. Rotate keys on a schedule that matches your risk tolerance, and set caps as tight as operationally feasible.
Controller keys need strict access controls. Controllers can freeze tokens and execute force transfers. These are high-impact regulatory actions. Store controller keys in hardware security modules (HSMs) or institutional custody with multi-party authorization.
Sub-issuer Cap Strategy
Sub-issuer caps track net outstanding supply - tokens minted minus tokens redeemed by that sub-issuer. This means a sub-issuer who mints 1,000 and redeems 500 has 500 outstanding against their cap.
Set caps based on the sub-issuer’s authorized issuance volume, not on total supply targets. If a sub-issuer needs to exceed their cap, the issuer must explicitly raise it via setSubIssuerCap(). This creates an auditable on-chain record of cap changes.
For multi-tranche offerings, assign each tranche a separate sub-issuer with a cap matching the tranche size. This prevents one tranche from accidentally over-issuing into another’s allocation.
Transfer Compliance Patterns
Always pre-check with canTransfer in your frontend. The function is a free view call off-chain and prevents users from submitting transactions that will revert. Display the human-readable reason string to the user so they understand why a transfer was blocked.
Handle SOFT_EXPIRED KYC status proactively. SOFT_EXPIRED does not block transfers, but it signals that the KYC provider should schedule a refresh. Monitor KYCStatusChanged events and trigger renewal workflows before the hard expiry blocks transfers entirely.
Authorized contracts bypass all identity checks. When you whitelist a contract via addAuthorizedContract, that contract can send and receive tokens without identity verification. Only whitelist contracts you control and have audited. Monitor the AuthorizedContractAdded event and maintain an inventory of authorized contracts with their purposes.
Stock Split Operations
Stock splits are operationally complex because they require a complete holder list and affect dependent systems.
Maintain an off-chain holder registry. The contract does not store a list of holders. You must track this via Transfer events or your backend database. If any holder is omitted from the split call, their balance will not be adjusted, creating an inconsistency that requires manual correction.
Pause dependent contracts before splitting. Orderbooks, lending pools, and AMM pools that reference this token must be paused or notified before the split executes. After the split, update any cached price data, collateral valuations, or order quantities in those systems. Listen for the StockSplit(numerator, denominator, newTotalSupply) event as the coordination signal.
Floor division creates dust. The split calculation uses (balance * numerator) / denominator with floor division. For non-evenly-divisible balances, this means some holders may receive slightly less than the exact ratio. The issuer is responsible for handling these remainders off-chain if precision matters for the use case.
Only forward splits are supported. Reverse splits (where numerator < denominator) would destroy fractional shares due to integer division. If you need a reverse split, redeem all tokens and re-issue at the new ratio, or implement a custom migration contract.
Freezing Tokens vs. Freezing Addresses
The suite provides two distinct freezing mechanisms that serve different purposes.
Token-level freezing (StockToken.freezeTokens) locks a specific amount of tokens on an account for a specific token. The holder retains their remaining transferable balance. Use this for partial holds, pledged collateral, or regulatory liens on specific positions.
Address-level freezing (IdentityRegistry.freezeAddress) blocks all transfers for that identity across every token using the same registry. Use this for full compliance freezes, AML investigations, or sanctions enforcement.
Both mechanisms can be active simultaneously. A holder can have 500 tokens frozen at the token level and still be unfrozen at the identity level - or vice versa. Your compliance workflow should account for both states when determining what actions a holder can take.
Force Transfer Considerations
Force transfers (forceTransfer) are regulatory-grade actions. The recipient must be identity-verified even for forced transfers - this prevents using force transfer as a backdoor to move tokens to unverified addresses.
If the sender has frozen tokens, the frozen amount is reduced by the transfer amount (or to zero if the transfer exceeds the frozen amount). This means a force transfer can “consume” a regulatory hold. Document this behavior in your compliance procedures so auditors understand the interaction.
Upgrade Safety
Contract upgrades require LEGAL_OPERATOR_ROLE authorization. Before upgrading:
- Deploy the new implementation to a test environment and run your full test suite.
- Verify the storage layout is compatible with the existing proxy. UUPS proxies will brick if the storage layout changes incompatibly.
- Have the upgrade reviewed by your security auditor.
- Execute the upgrade through the legal operator multi-sig with appropriate governance approval.
A bricked proxy (from an incompatible storage layout) permanently locks all tokens. Use OpenZeppelin’s upgrade safety tooling to validate storage compatibility before every upgrade.
Compliance Module Deployment
The BasicComplianceModule is controlled by the token contract address via the onlyToken modifier. This means compliance configuration calls must originate from the token contract itself. If your deployment needs the issuer to configure compliance rules directly, you will need either a governance proxy pattern or wrapper functions on the token contract that forward calls to the module.
When setting holding limits, consider that the maximum holding check applies to the recipient’s balance after the transfer. A recipient at 99,000 tokens with a 100,000 cap can only receive 1,000 more tokens. Set limits with headroom for operational flexibility.
Lockup periods are absolute timestamps, not durations. When setting a lockup, calculate the Unix timestamp for the desired expiry. A lockup set to a timestamp in the past will revert.
Monitoring and Alerting
At minimum, monitor these events in production:
| Event | Alert priority | Action |
|---|
StockSplit | High | Notify dependent systems, verify all holders were included |
ForceTransfer | High | Log for regulatory audit trail, notify compliance team |
TokensFrozen / TokensUnfrozen | Medium | Update compliance dashboard |
AddressFrozen / AddressUnfrozen | Medium | Update compliance dashboard, notify affected holder |
IdentityRevoked | Medium | Check for outstanding token positions that need resolution |
KYCStatusChanged to SOFT_EXPIRED | Low | Trigger KYC renewal workflow |
KYCStatusChanged to HARD_EXPIRED | Medium | Holder can no longer transfer, escalate to KYC provider |
SubIssuerCapUpdated | Low | Audit trail for cap changes |
BatchIssueCompleted where processed < total | Medium | Retry remaining recipients |
Gas Optimization
Batch operations (batchIssue, batchVerifyIdentities) include gas-aware early termination. If gas drops below the minimum threshold per item, processing stops and the function returns the count of successfully processed entries. Always check the return value and retry remaining items in a follow-up transaction.
For batchIssue, the maximum batch size is 200 recipients with a minimum gas reserve of 80,000 per item. For batchVerifyIdentities, the maximum is 500 entries with 50,000 per item. Size your batches based on your target chain’s block gas limit.