pragma solidity ^0.8.28; contract Owned { address public owner; modifier onlyOwner() { require(msg.sender == owner, "ONLY_OWNER"); _; } constructor() { owner = msg.sender; } function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0), "ZERO_OWNER"); owner = newOwner; } } contract IdentityRegistry is Owned { struct Party { bytes32 partyId; bytes32 accountId; bytes32 jurisdiction; bool verified; bool eligible; bool frozen; } mapping(address => Party) public parties; event PartySet( address indexed wallet, bytes32 partyId, bytes32 accountId, bytes32 jurisdiction, bool verified, bool eligible, bool frozen ); function setParty( address wallet, bytes32 partyId, bytes32 accountId, bytes32 jurisdiction, bool verified, bool eligible, bool frozen ) external onlyOwner { parties[wallet] = Party({ partyId: partyId, accountId: accountId, jurisdiction: jurisdiction, verified: verified, eligible: eligible, frozen: frozen }); emit PartySet(wallet, partyId, accountId, jurisdiction, verified, eligible, frozen); } function setFrozen(address wallet, bool frozen) external onlyOwner { parties[wallet].frozen = frozen; emit PartySet( wallet, parties[wallet].partyId, parties[wallet].accountId, parties[wallet].jurisdiction, parties[wallet].verified, parties[wallet].eligible, frozen ); } function getParty(address wallet) external view returns (Party memory) { return parties[wallet]; } } contract RoleRegistry is Owned { mapping(bytes32 => mapping(address => bool)) public hasRole; event RoleSet(bytes32 indexed role, address indexed account, bool enabled); function setRole(bytes32 role, address account, bool enabled) external onlyOwner { hasRole[role][account] = enabled; emit RoleSet(role, account, enabled); } } contract PolicyRegistry is Owned { struct Policy { bool active; bool lockupActive; uint256 maxAutoApproveQuantity; } mapping(bytes32 => Policy) public policies; event PolicySet( bytes32 indexed policyId, bool active, bool lockupActive, uint256 maxAutoApproveQuantity ); function setPolicy( bytes32 policyId, bool active, bool lockupActive, uint256 maxAutoApproveQuantity ) external onlyOwner { policies[policyId] = Policy({ active: active, lockupActive: lockupActive, maxAutoApproveQuantity: maxAutoApproveQuantity }); emit PolicySet(policyId, active, lockupActive, maxAutoApproveQuantity); } function getPolicy(bytes32 policyId) external view returns (Policy memory) { return policies[policyId]; } } contract TransferControlModule is Owned { enum Decision { Approved, Rejected, PendingReview } struct ControlRequest { bytes32 instructionId; bytes32 messageDefinitionId; bytes32 lifecycleEvent; bytes32 assetId; bytes32 assetType; address assetContract; bytes32 networkId; address fromWallet; address toWallet; uint256 quantity; bytes32 policyId; } struct ControlDecision { Decision decision; bytes32 reasonCode; bytes32 policyId; bytes32 evidenceRef; } bytes32 public constant SECONDARY_TRANSFER = keccak256("SECONDARY_TRANSFER"); bytes32 public constant APPROVED = keccak256("APPROVED"); bytes32 public constant FROM_NOT_VERIFIED = keccak256("FROM_NOT_VERIFIED"); bytes32 public constant TO_NOT_VERIFIED = keccak256("TO_NOT_VERIFIED"); bytes32 public constant PARTY_FROZEN = keccak256("PARTY_FROZEN"); bytes32 public constant ASSET_NOT_ALLOWED = keccak256("ASSET_NOT_ALLOWED"); bytes32 public constant LOCKUP_ACTIVE = keccak256("LOCKUP_ACTIVE"); bytes32 public constant POLICY_BLOCKED = keccak256("POLICY_BLOCKED"); bytes32 public constant REVIEW_REQUIRED = keccak256("REVIEW_REQUIRED"); bytes32 public constant UNSUPPORTED_LIFECYCLE_EVENT = keccak256("UNSUPPORTED_LIFECYCLE_EVENT"); IdentityRegistry public identityRegistry; PolicyRegistry public policyRegistry; constructor(address identityRegistry_, address policyRegistry_) { identityRegistry = IdentityRegistry(identityRegistry_); policyRegistry = PolicyRegistry(policyRegistry_); } function evaluate(ControlRequest calldata request) external view returns (ControlDecision memory) { if (request.lifecycleEvent != SECONDARY_TRANSFER) { return _decision(Decision.Rejected, UNSUPPORTED_LIFECYCLE_EVENT, request.policyId); } IdentityRegistry.Party memory fromParty = identityRegistry.getParty(request.fromWallet); IdentityRegistry.Party memory toParty = identityRegistry.getParty(request.toWallet); PolicyRegistry.Policy memory policy = policyRegistry.getPolicy(request.policyId); if (!fromParty.verified) { return _decision(Decision.Rejected, FROM_NOT_VERIFIED, request.policyId); } if (!toParty.verified) { return _decision(Decision.Rejected, TO_NOT_VERIFIED, request.policyId); } if (fromParty.frozen || toParty.frozen) { return _decision(Decision.Rejected, PARTY_FROZEN, request.policyId); } if (!toParty.eligible) { return _decision(Decision.Rejected, ASSET_NOT_ALLOWED, request.policyId); } if (!policy.active) { return _decision(Decision.Rejected, POLICY_BLOCKED, request.policyId); } if (policy.lockupActive) { return _decision(Decision.Rejected, LOCKUP_ACTIVE, request.policyId); } if (request.quantity > policy.maxAutoApproveQuantity) { return _decision(Decision.PendingReview, REVIEW_REQUIRED, request.policyId); } return _decision(Decision.Approved, APPROVED, request.policyId); } function _decision(Decision decision, bytes32 reasonCode, bytes32 policyId) internal pure returns (ControlDecision memory) { return ControlDecision({ decision: decision, reasonCode: reasonCode, policyId: policyId, evidenceRef: keccak256("SYNTHETIC_EVIDENCE") }); } } contract TokenizedBond is Owned { string public name; string public symbol; uint8 public decimals = 0; uint256 public totalSupply; bytes32 public assetId; bytes32 public assetType; bytes32 public messageDefinitionId; bytes32 public networkId; bytes32 public policyId; bytes32 public constant TRANSFER_AGENT_ROLE = keccak256("TRANSFER_AGENT_ROLE"); bytes32 public constant SECONDARY_TRANSFER = keccak256("SECONDARY_TRANSFER"); bytes32 public constant INSUFFICIENT_POSITION = keccak256("INSUFFICIENT_POSITION"); TransferControlModule public controlModule; RoleRegistry public roleRegistry; mapping(address => uint256) public balanceOf; event Transfer(address indexed from, address indexed to, uint256 value); event TransferControlDecision( bytes32 indexed instructionId, address indexed fromWallet, address indexed toWallet, uint8 decision, bytes32 reasonCode, bytes32 policyId, bytes32 messageDefinitionId, bytes32 assetType, bytes32 networkId ); modifier onlyTransferAgent() { require( msg.sender == owner || roleRegistry.hasRole(TRANSFER_AGENT_ROLE, msg.sender), "ONLY_TRANSFER_AGENT" ); _; } constructor( string memory name_, string memory symbol_, bytes32 assetId_, bytes32 assetType_, bytes32 messageDefinitionId_, bytes32 networkId_, bytes32 policyId_, address controlModule_, address roleRegistry_, address initialHolder, uint256 initialSupply ) { name = name_; symbol = symbol_; assetId = assetId_; assetType = assetType_; messageDefinitionId = messageDefinitionId_; networkId = networkId_; policyId = policyId_; controlModule = TransferControlModule(controlModule_); roleRegistry = RoleRegistry(roleRegistry_); balanceOf[initialHolder] = initialSupply; totalSupply = initialSupply; emit Transfer(address(0), initialHolder, initialSupply); } function controlledTransfer( bytes32 instructionId, address fromWallet, address toWallet, uint256 quantity ) external onlyTransferAgent returns (uint8, bytes32) { if (balanceOf[fromWallet] < quantity) { emit TransferControlDecision( instructionId, fromWallet, toWallet, uint8(TransferControlModule.Decision.Rejected), INSUFFICIENT_POSITION, policyId, messageDefinitionId, assetType, networkId ); return (uint8(TransferControlModule.Decision.Rejected), INSUFFICIENT_POSITION); } TransferControlModule.ControlRequest memory request = TransferControlModule.ControlRequest({ instructionId: instructionId, messageDefinitionId: messageDefinitionId, lifecycleEvent: SECONDARY_TRANSFER, assetId: assetId, assetType: assetType, assetContract: address(this), networkId: networkId, fromWallet: fromWallet, toWallet: toWallet, quantity: quantity, policyId: policyId }); TransferControlModule.ControlDecision memory decision = controlModule.evaluate(request); if (decision.decision == TransferControlModule.Decision.Approved) { balanceOf[fromWallet] -= quantity; balanceOf[toWallet] += quantity; emit Transfer(fromWallet, toWallet, quantity); } emit TransferControlDecision( instructionId, fromWallet, toWallet, uint8(decision.decision), decision.reasonCode, decision.policyId, messageDefinitionId, assetType, networkId ); return (uint8(decision.decision), decision.reasonCode); } }