Skip to main content

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:

  1. Token Type Rules - Determine token type based on investor identity
  2. 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

RegionAccreditationToken TypeAML/KYC RequiredDescription
840 (US)2 (Accredited)2 (RegD)YesUS Accredited → RegD
0 (Any)0 (Any)1 (RegS)YesDefault → RegS
840 (US)1 (Retail)3 (RegCF)YesUS 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 TypeRecipient RegionRecipient AccreditationLock DurationAML/KYCDescription
2 (RegD)840 (US)0 (Any)180 daysYesRegD → US: 6 months
1 (RegS)0 (Any)0 (Any)365 daysYesRegS → Any: 12 months
4 (Institutional)0 (Any)4 (Institutional)0 daysYesInstitutional → Institutional: No hold

🔄 Process Flows

Token Minting Flow

Transfer Validation Flow


⚙️ Validation Logic

Token Type Determination

When determineTokenType() is called:

  1. Get Identity: Retrieve investor's region and accreditation from IdentityRegistry
  2. Find Rule: Look up TokenTypeRule for (region, accreditation) combination
  3. Return Type:
    • If rule found and active: return rule.tokenType
    • If no rule found: return TOKEN_TYPE_GENERIC (0)

Transfer Validation

When checkTransferAllowed() is called:

  1. Get Recipient Identity: Retrieve recipient's region, accreditation, and AML/KYC status
  2. Find Transfer Rule: Look up TransferRule for (tokenType, recipientRegion, recipientAccreditation)
  3. Check Conditions:
    • Rule exists and is active
    • Holding period satisfied: mintTimestamp + lockDurationSeconds <= block.timestamp
    • AML/KYC requirement met (if required)
  4. Return Result:
    • SUCCESS (0): All conditions met
    • NO_RULE_FOR_RECIPIENT (15): No matching rule found
    • HOLDING_PERIOD_NOT_MET (14): Lock duration not elapsed
    • RECIPIENT_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

  1. SUCCESS (0): Transfer allowed
  2. HOLDING_PERIOD_NOT_MET (14): Required holding period not elapsed
  3. NO_RULE_FOR_RECIPIENT (15): No transfer rule defined for combination
  4. 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.