Transfer Rules Documentation
Overview
The TransferRules contract is a comprehensive system that manages token type determination and transfer restrictions for the RestrictedLockupToken. It implements a two-layer approach to regulatory compliance through configurable rules managed by the Transfer Admin. Authorization for administrative actions is handled through a pre-deployed external AccessControl
contract used across the ecosystem.
🏗️ Architecture
The TransferRules contract provides two main functionalities:
- Token Type Rules - Determine token type based on investor identity
- Transfer Rules - Control transfer restrictions based on token type and recipient identity
TransferRules Contract
├── Token Type Rules (Identity → Token Type)
│ ├── setTokenTypeRule(region, accreditation, tokenType, requiresAmlKyc)
│ ├── getTokenTypeRule(region, accreditation)
│ └── determineTokenType(wallet, identityRegistry)
│
└── Transfer Rules (Token Type + Recipient → Restrictions)
├── setTransferRule(tokenType, recipientRegion, recipientAccreditation, rule)
├── checkTransferAllowed(tokenType, mintTimestamp, recipient, identityRegistry)
└── transferRuleFor(tokenType, recipientRegion, recipientAccreditation)
🏷️ Token Type Rules
Purpose
Token Type Rules map investor identity (region + accreditation) to appropriate token types during minting.
Data Structure
struct TokenTypeRule {
uint256 tokenType; // Token type to assign (1=RegS, 2=RegD, etc.)
bool requiresAmlKyc; // Whether AML/KYC is required for this type
bool isActive; // Whether this rule is currently active
}
Configuration Functions
// Set a single rule
transferRules.setTokenTypeRule(
840, // region (US)
2, // accreditation (Accredited)
2, // tokenType (RegD)
true // requiresAmlKyc
);
// Get a configured rule
TokenTypeRule memory rule = transferRules.getTokenTypeRule(840, 2);
// Batch set multiple rules
transferRules.batchSetTokenTypeRules(
[840, 0], // regions
[2, 0], // accreditations
[2, 1], // tokenTypes
[true, true] // requiresAmlKyc flags
);
// Remove a rule
transferRules.removeTokenTypeRule(840, 2);
Automatic Type Determination
// Called by RestrictedLockupToken during minting
uint256 tokenType = transferRules.determineTokenType(
walletAddress,
identityRegistry
);
Example Rules
Region | Accreditation | Token Type | AML/KYC Required | Description |
---|---|---|---|---|
840 (US) | 2 (Accredited) | 2 (RegD) | Yes | US Accredited → RegD |
0 (Any) | 0 (Any) | 1 (RegS) | Yes | Default → RegS |
840 (US) | 1 (Retail) | 3 (RegCF) | Yes | US Retail → RegCF |
🔐 Transfer Rules
Purpose
Transfer Rules define restrictions for transferring tokens based on token type, mint timestamp, and recipient identity.
Data Structure
struct TransferRule {
uint256 lockDurationSeconds; // Holding period from mint timestamp
bool requiresAmlKyc; // Whether recipient must have AML/KYC
bool isActive; // Whether this rule is currently active
}
Configuration Functions
// Set a transfer rule
transferRules.setTransferRule(
2, // tokenType (RegD)
840, // recipientRegion (US)
0, // recipientAccreditation (Any)
TransferRule({
lockDurationSeconds: 180 days,
requiresAmlKyc: true,
isActive: true
})
);
// Get a transfer rule
TransferRule memory rule = transferRules.transferRuleFor(2, 840, 0);
// Batch set multiple rules
transferRules.batchSetTransferRules(
[2, 1], // tokenTypes
[840, 0], // recipientRegions
[0, 0], // recipientAccreditations
[rule1, rule2] // TransferRule structs
);
// Remove a rule
transferRules.removeTransferRule(2, 840, 0);
Transfer Validation
// Called by RestrictedLockupToken during transfers
uint8 restrictionCode = transferRules.checkTransferAllowed(
tokenType,
mintTimestamp,
recipientAddress,
identityRegistry
);
// Get unlock timestamp for a holding
uint256 unlockTime = transferRules.getUnlockTimestamp(
tokenType,
mintTimestamp,
recipientAddress,
identityRegistry
);
Example Rules
Token Type | Recipient Region | Recipient Accreditation | Lock Duration | AML/KYC | Description |
---|---|---|---|---|---|
2 (RegD) | 840 (US) | 0 (Any) | 180 days | Yes | RegD → US: 6 months |
1 (RegS) | 0 (Any) | 0 (Any) | 365 days | Yes | RegS → Any: 12 months |
4 (Institutional) | 0 (Any) | 4 (Institutional) | 0 days | Yes | Institutional → Institutional: No hold |
🔄 Process Flows
Token Minting Flow
Transfer Validation Flow
⚙️ Validation Logic
Token Type Determination
When determineTokenType()
is called:
- Get Identity: Retrieve investor's region and accreditation from IdentityRegistry
- Find Rule: Look up TokenTypeRule for (region, accreditation) combination
- Return Type:
- If rule found and active: return
rule.tokenType
- If no rule found: return
TOKEN_TYPE_GENERIC (0)
- If rule found and active: return
Transfer Validation
When checkTransferAllowed()
is called:
- Get Recipient Identity: Retrieve recipient's region, accreditation, and AML/KYC status
- Find Transfer Rule: Look up TransferRule for (tokenType, recipientRegion, recipientAccreditation)
- Check Conditions:
- Rule exists and is active
- Holding period satisfied:
mintTimestamp + lockDurationSeconds <= block.timestamp
- AML/KYC requirement met (if required)
- Return Result:
SUCCESS (0)
: All conditions metNO_RULE_FOR_RECIPIENT (15)
: No matching rule foundHOLDING_PERIOD_NOT_MET (14)
: Lock duration not elapsedRECIPIENT_NOT_QUALIFIED (16)
: AML/KYC requirement not met
🔧 Administrative Functions
Access Control
- Transfer Admin: Can configure all rules (token type and transfer rules) via external
AccessControl
- Contract Admin: Can upgrade the TransferRules contract via external
AccessControl
Key Functions
// Token Type Rules
function setTokenTypeRule(uint256 region, uint256 accreditation, uint256 tokenType, bool requiresAmlKyc) external onlyTransferAdmin;
function removeTokenTypeRule(uint256 region, uint256 accreditation) external onlyTransferAdmin;
function batchSetTokenTypeRules(uint256[] regions, uint256[] accreditations, uint256[] tokenTypes, bool[] requiresAmlKycFlags) external onlyTransferAdmin;
// Transfer Rules
function setTransferRule(uint256 tokenType, uint256 recipientRegion, uint256 recipientAccreditation, TransferRule memory rule) external onlyTransferAdmin;
function removeTransferRule(uint256 tokenType, uint256 recipientRegion, uint256 recipientAccreditation) external onlyTransferAdmin;
function batchSetTransferRules(uint256[] tokenTypes, uint256[] recipientRegions, uint256[] recipientAccreditations, TransferRule[] rules) external onlyTransferAdmin;
// View Functions
function getTokenTypeRule(uint256 region, uint256 accreditation) external view returns (TokenTypeRule memory);
function transferRuleFor(uint256 tokenType, uint256 recipientRegion, uint256 recipientAccreditation) external view returns (TransferRule memory);
🚨 Error Scenarios
Common Restriction Codes
- SUCCESS (0): Transfer allowed
- HOLDING_PERIOD_NOT_MET (14): Required holding period not elapsed
- NO_RULE_FOR_RECIPIENT (15): No transfer rule defined for combination
- RECIPIENT_NOT_QUALIFIED (16): AML/KYC requirement not met
Troubleshooting
- No rule found: Verify rules are configured for the specific combination
- Holding period: Check
mintTimestamp + lockDurationSeconds
vs current time - AML/KYC issues: Confirm recipient's AML/KYC status in IdentityRegistry
- Rule inactive: Ensure rules have
isActive = true
📚 Integration
With RestrictedLockupToken
- Token calls
determineTokenType()
during minting - Token calls
checkTransferAllowed()
during transfers - Token respects restriction codes and blocks invalid transfers
With IdentityRegistry
- TransferRules queries recipient identity for validation
- Supports region, accreditation, and AML/KYC status checks
- Works with configurable AML/KYC validity periods
This two-layer system provides flexible, regulatory-compliant token management while maintaining clear separation of concerns between type determination and transfer restrictions.