The compliance ledger
The token knows balances per address. An investor holds tokens at several addresses, on the reference chain and on satellites, and every rule about the distribution of an asset asks about the investor, not about one address. The compliance therefore keeps its own ledger: what each identity owns in total, and what is promised to it and from it by movements that have not settled. Compliance modules read these numbers. They do not rebuild them.
What the ledger keeps
positionOf(identity)
What the identity owns in total: free, frozen and bridged, over its own address and every wallet linked to it, revoked wallets included.
pendingInOf(identity)
What open cross-chain validations promise to the identity.
pendingOutOf(identity)
What open cross-chain validations promise out of the identity.
ownerOf(address)
The identity under which the tokens of an address on the reference chain are counted.
positionGap()
The difference between the supply and the sum of all positions. Zero in normal operation.
All are functions of the ModularCompliance, declared in IComplianceLedger. Nothing outside the compliance writes them.
One number of the same family is kept by the token, next to the balance it reserves against: reservedOf(wallet), what open validations may still draw from one satellite wallet.
How it is kept
The ledger is updated from the calls the token already makes to its compliance after every movement. No one calls anything extra.
Mint
The recipient's identity goes up.
Burn
The holder's identity goes down.
Transfer, forced transfer
The sender's identity goes down, the recipient's goes up.
Transfer between two addresses of one identity
None.
Recovery of a lost wallet
None. The position stays with the same identity.
Delegation-out, recall
None. The position changes place, not owner.
Settlement of a transfer executed on a satellite
The sender's identity goes down, the recipient's goes up, by the amount that was executed.
The rule behind the table: a position moves when ownership moves, and only then.
Pending amounts
A transfer on a satellite is authorized before it is executed and recorded after. During that time the position has not moved, but the rules must already count the movement, or two transfers authorized at the same moment could jointly breach a limit.
When a validation is issued, the compliance adds its maximum amount to pendingOutOf of the sender's identity and to pendingInOf of the recipient's identity. When the validation settles or is discarded, both are released.
Caps what an identity may acquire
positionOf + pendingInOf of the recipient
Requires an identity to keep a minimum
positionOf - pendingOutOf of the sender
The maximum amount of the validation is used, because it is the worst case for any rule that adds amounts up. A movement between two addresses of one identity reserves nothing: relocating one's own tokens never uses up one's own room.
The ledger and the supply
The ledger satisfies one equation at all times:
sum of all positions + positionGap = totalSupply
positionGap is zero as long as every address that holds tokens resolves to the identity that owns them. That is the normal state, and the protocol is built to keep it:
An investor cannot break it. A wallet keeps resolving to its identity after its owner revokes it.
A circulating token cannot change its registry or its compliance.
Following the registry
One actor can change what an address resolves to while it holds tokens: a Registry Agent, by changing or deleting the local registration of that address. The ledger keeps up on its own.
It remembers. For each address on the reference chain, the ledger records the identity it last credited, readable with
ownerOf.It follows a change. When an agent registers the address under another identity, the next movement through that address first moves the address's whole balance from the old identity to the new one, then counts the movement. The event
WalletOwnerChangedrecords it.It keeps the last owner. When an agent deletes the registration and nothing else names the address, the remembered identity still answers. The position does not disappear.
The gap, and how it is closed
Two situations leave an amount that the ledger cannot attribute.
Tokens land on an address that the ledger has never credited and that resolves to no identity. This is only possible when the eligibility checks of the registry are disabled.
Counts the amount in the gap, positive.
PositionUnresolved
Tokens leave an address whose identity holds less in the ledger than the amount, after a registration was changed.
Takes what it can from the position and counts the rest in the gap, negative.
PositionUnderflow
Neither reverts. The cause lies in the registry, not in the movement, and blocking a burn or a forced transfer would make the repair harder.
The OWNER of the compliance closes a gap with fixPosition(from, to, amount). A zero address on one side stands for the gap: from zero to an identity gives unattributed tokens their owner, and from an identity to zero removes a stale amount. The event PositionFixed records it.
Why the compliance follows its token
The numbers are right because the compliance has seen every movement since the first mint of the token. There is no step that would seed a new compliance with existing positions.
For that reason, once a token has supply, its compliance cannot be replaced and cannot be bound to another token. The compliance of a circulating token is upgraded in place, and its rules change through its modules.
Who reads the ledger
A RULE module
Positions and pending amounts
Deciding the largest amount a movement may carry
The compliance itself
Pending amounts
Issuing and settling cross-chain validations
An application running a distribution or a vote
positionOf
Reaching each holder's whole position, wherever it is active
An Issuer, a transfer agent, a regulator
positionOf, positionGap
The list of holders by identity, and the check that it adds up to the supply
The balances of the token remain the record of where tokens are. The ledger is the same record grouped by owner.
Last updated