Designing transfer restrictions a regulator will accept
Xora Labs RWA · 9 min read
The requirement is not to block bad transfers. It is to prove, years later, that every transfer that happened was permitted at the time it happened.
The requirement is evidentiary, not preventive
Engineers approach transfer restrictions as an access-control problem: decide whether this transfer is allowed, then allow or revert. That is necessary and it is the easy half.
The half that decides whether the deployment survives is reconstruction. Somebody will ask, in two years, why a specific transfer between two specific addresses was permitted on a specific date. "The contract allowed it" is not an answer. The answer has to name the rule that was in force, the claims the holder held, and the version of the ruleset that was live.
Rules as data, not as code paths
The common mistake is encoding the rules as branches in the token contract. It works on day one and fails on the first amendment, because changing a rule then means an upgrade, and an upgrade is a governance event that has to be explained.
Rules should be data: a versioned set of conditions the compliance contract evaluates. When a rule changes, a new version is published and the previous one remains readable. The chain then contains its own history of what was permitted when.
Record the reason, not only the result
A revert tells you a transfer failed. It does not tell you which condition failed, and reverts are not stored.
Emit the decision. For an allowed transfer, emit the ruleset version and which conditions were satisfied. For a blocked one, emit the specific condition that failed. The gas cost is real and it is trivial next to the cost of not being able to answer the question later. This is also what makes support tractable: an investor asking why their transfer failed can be answered from logs rather than by re-running the state in a fork.
The conditions worth supporting
- Jurisdiction. Of both sender and receiver, with an explicit answer when they differ.
- Investor classification. Accredited, qualified, professional, retail, with the claim issuer recorded.
- Lock-up and vesting. Time-based, and defined from a per-holder acquisition date rather than a global start.
- Holder caps. Maximum number of holders, and maximum position per holder. Both are common statutory thresholds and both need to be checked before the transfer, not after.
- Transfer windows. Blackout periods around corporate actions and reporting dates.
The cases that are usually missed
- Rule changes applied to existing holders. If a cap tightens, existing holders may exceed it. The design must state whether they are grandfathered or frozen, because the contract will behave one of those ways whether or not it was decided.
- Claim expiry. Accreditation lapses. A holder who was eligible at purchase may not be at sale, and a system that only checks at issuance has a gap.
- Forced transfers and the rules. A court-ordered transfer may violate an ordinary restriction. That path needs to exist and needs to be recorded differently from a normal transfer.
- Partial freezes. Freezing a portion of a balance rather than an account is frequently required and is often retrofitted badly.
What we build to
Rules versioned and stored as data. Every decision emitted with its reason. Every privileged action recorded with the identity that authorised it. State that can be exported into a report without an archive node and a custom script.
None of that is exotic engineering. It is just designed for the day somebody asks, rather than for the day it ships.
Working on something in this area? Tell us about it.