For the complete documentation index, see llms.txt. This page is also available as Markdown.

Transfer validation

A transfer on a satellite executes only against a validation issued on the reference chain. The functions that issue, track, settle and close validations are part of ModularCompliance. This page lists them. The model is in Authorization before execution and The dual-state lifecycle.

At a glance

Source

contracts/compliance/modular/TransferValidation.sol, inherited by ModularCompliance

Interfaces

ITransferValidation, ISettlementHandler

Works with

The Token, which reserves the amount and sends the message, and the TREXRegistry, which resolves wallets

Roles

AGENT to request for a holder, COMPLIANCE_MANAGER for the windows, VALIDATION_KEEPER to discard and resolve

Request a validation

function requestTransferValidation(
    bytes calldata from,
    bytes calldata to,
    uint256 requestedMin,
    uint256 requestedMax,
    bytes calldata spender
) external returns (uint256 validationId);

from, to and spender are ERC-7930 interoperable addresses in canonical form. spender is empty when the sender executes itself. The function checks the range, answers five questions in this order, then writes.

Question
Checks
Reverts with

Is the range valid?

requestedMin is at most requestedMax

InvalidRequestedRange

Where does it go?

from is on a satellite. The transfer has two legs when to is on another satellite

SenderNotOnSatellite, NonCanonicalInteroperableAddress

May the caller ask?

from resolves to an identity. The caller is that identity, or holds AGENT

UnverifiedWallet, NotAuthorizedForWallet

By when?

A validity window is set. For each satellite involved, issuance is not paused and a reconciliation window is set. The reservation lasts for the larger of the two windows

ValidityWindowNotSet, ValidationIssuancePaused, ReconciliationWindowNotSet

How much?

to is eligible (isWalletVerified). The maximum is capped at what the sending wallet has available, then at what the RULE modules allow. The rules are not asked when both wallets belong to one identity

UnverifiedWallet, ZeroValue when nothing is available, EmptyValidationRange when the cap falls below the minimum

May this spender execute?

Every SPENDER module accepts a named spender

ValidationSpenderRefused

On success the compliance stores the validation, reserves amountMax on the sending wallet (Token.reserveForValidation), counts it in the pending amounts of both identities, emits TransferValidationIssued, and has the token send one COMPLIANCE_VALIDATION message to each satellite involved. Guide: 31 Transfer on a satellite.

A request is allowed while the token is paused.

Status of a validation

statusOf(validationId) returns one of ten values. It reverts with UnknownValidation for an id that was never issued.

Status
Meaning

Pending

Issued, amount reserved, no report received

AwaitingMint

Two legs: the burn was reported, the mint is owed. The amount is in transit on the token

AwaitingBurn

Two legs: the mint was reported, the burn is owed

Settled

Every expected report arrived. Reservation released, positions updated

Expired

Computed, never stored: Pending and past releaseAt

Discarded

The Validation Keeper released the reservation of an expired validation

DiscardedAwaitingMint, DiscardedAwaitingBurn

Discarded, then one leg arrived late. The other is owed

LateReconciled

Every report arrived after the discard and was applied

Resolved

The Validation Keeper gave up on a pair whose other leg never arrived. A leg that arrives afterwards halts the token

validationOf(validationId) returns the stored record.

Field
Content

hash

The ERC-712 struct hash of the ComplianceValidation that was sent

amountMin, amountMax

The final bounds, inclusive

expiry

The deadline of the satellite: no execution at or after it

releaseAt

expiry plus the reconciliation window: from then on the validation can be discarded

fromChainKey, toChainKey

The chain of each wallet

fromKey, toKey

The keccak256 of each wallet, which a report is matched against

fromWallet

The sending wallet, as bytes

twoLegs

True when the two wallets are on two different satellites

fromIdentity, toIdentity

The identities resolved at issuance

relocation

True when both wallets belong to one identity

status

The stored status

executedAmount

The amount of the first report, which the second must repeat

legWallet

For two legs, the wallet the first report carried

Other reads: lastValidationId(), defaultValidityWindow(), reconciliationWindowOf(chainKey), isIssuancePaused(chainKey).

Settlement

handleSettlement(originChainKey, notification) is called by the token when a SETTLEMENT_NOTIFICATION passes the delivery checks. It reverts with AddressNotATokenBoundToComplianceContract for another caller.

Case
Result

The validation is unknown, the leg was already applied, or the validation is Resolved

Nothing is applied. ReplayedSettlement is emitted and the function tells the token to pause itself

The wallets or the origin chain do not match the validation

Reverts with SettlementLegMismatch

The amount is outside [amountMin, amountMax]

Reverts with SettlementOutOfBounds

Second leg with another amount than the first

Reverts with SettlementAmountMismatch

First of two legs

Recorded with ValidationLegConfirmed. A burn puts the amount in transit on the token

Last expected report

Positions and token ledger updated, ValidationSettled

Report for a discarded validation

Applied, with LateReconciliation. Issuance is paused for the origin chain when the amount exceeds what the rules allow at that moment

Every report that reaches the compliance emits SettlementNotified.

Windows and issuance

Function
Access
Reverts with

setDefaultValidityWindow(duration)

COMPLIANCE_MANAGER

ZeroDuration. Emits DefaultValidityWindowSet

setReconciliationWindow(chainKey, duration)

COMPLIANCE_MANAGER

ZeroDuration. Emits ReconciliationWindowSet

setIssuancePaused(chainKey, paused)

COMPLIANCE_MANAGER

Nothing. Emits ValidationIssuancePaused or ValidationIssuanceUnpaused when the state changes

The Validation Keeper

Function
Access
Reverts with

discardExpiredValidations(validationIds)

VALIDATION_KEEPER

UnknownValidation, ValidationNotDiscardable unless the validation is Pending, ValidationNotReleasable before releaseAt. Releases the reservation and emits ValidationDiscarded

resolveStuckValidation(validationId)

VALIDATION_KEEPER

UnknownValidation, ValidationNotStuck unless one leg of two arrived, ValidationNotYetResolvable before the date the error carries. Returns an amount held in transit to the wallet it was burned from, and emits ValidationResolved

Guides: 35 Discard or resolve a validation, 36 Handle a late reconciliation or a halt.

Events

Event
Emitted when

TransferValidationIssued(uint256 indexed validationId, bytes from, bytes to, bytes spender, uint256 amountMin, uint256 amountMax, uint64 expiry, uint64 reconciliationWindow)

A validation is issued

SettlementNotified(bytes32 indexed originChainKey, uint256 indexed validationId, bytes from, bytes to, uint256 amount)

A report from a satellite reaches the compliance

ValidationLegConfirmed(uint256 indexed validationId, bytes32 indexed chainKey, uint256 amount), ValidationSettled (same arguments)

The first of two reports is recorded. Every report arrived

LateReconciliation(uint256 indexed validationId, bytes32 indexed chainKey)

A report is applied after the discard

ReplayedSettlement(uint256 indexed validationId, bytes32 indexed chainKey)

A report is refused and the token halts

ValidationDiscarded(uint256 indexed validationId)

An expired validation is discarded

ValidationResolved(uint256 indexed validationId, uint256 returnedAmount)

A half-reported transfer is closed

ValidationIssuancePaused(bytes32 indexed chainKey), ValidationIssuanceUnpaused (same argument)

Issuance for a chain stops, resumes

DefaultValidityWindowSet(uint64 duration), ReconciliationWindowSet(bytes32 indexed chainKey, uint64 duration)

A window is set

Last updated