34. Transfer between two satellites
A transfer between two satellites moves tokens from a wallet on one satellite chain to a recipient on another. Two chains each do half of the movement and cannot act atomically: the sending chain burns and sends a proof of the burn to the receiving chain, which mints, and each reports its own half. The reference chain authorizes the transfer once and records the change of ownership when both reports have arrived and agree.
The request is the same as for a transfer on one satellite. This guide covers what differs. Read 31 first.
At a glance
Triggered by
Investor (sender), through its ONCHAINID. Or a Token Agent on its behalf
Actors
Investor (sender), Identity Owner, Investor (recipient), Venue or application (as spender), Platform
Contracts
ModularCompliance, compliance modules, Token, TREXRegistry, the ERC-7786 gateways of the two routes, T-REX Lite on both satellites
Preconditions
Result
The executed amount has moved from the sender's bridged position on one satellite to the recipient's bridged position on the other, and from the position of the sender's identity to the position of the recipient's identity
Actors and roles
Investor (sender)
Holds the position on the sending satellite and executes the transfer there, unless a spender does.
A balance on T-REX Lite on the sending satellite
Identity Owner
The sender in its capacity as controller of its ONCHAINID, which requests the validation.
An ACTION key of the identity the sending wallet is linked to
Investor (recipient)
Receives the tokens on the other satellite. Does not transact.
A wallet there, linked to an eligible identity
Venue or application
Executes for the sender, when the validation names it as spender.
Acceptance by the asset's spender policy
Platform
Relays the request and follows both reports to the settlement.
Whatever keys the sender has authorized
Sequence
The burn always executes first. The two reports travel separately and can be delivered to the reference chain in either order.
Step by step
Step 1. Request the validation
Who: Identity Owner (ACTION key), through the ONCHAINID the sending wallet is linked to. Or a Token Agent (AGENT)
Call: ModularCompliance.requestTransferValidation(from, to, requestedMin, requestedMax, spender), with from on one satellite and to on another
Before the call: The pre-checks of 31, step 1, passed, for both chains: Token.isChainOpen is true and ModularCompliance.isIssuancePaused is false for the sender's chain and for the recipient's chain.
What the contract checks: The checks of 31, step 2, with these differences.
Both chains must be open for issuance: neither is paused, and each has a reconciliation window. Otherwise
ValidationIssuancePausedorReconciliationWindowNotSet, naming the chain.Both chains must be open on the token. Otherwise
ChainNotOpen, naming the chain.
What changes: As in 31, with three differences. The validation is recorded as expecting two reports. Its reconciliation window is the larger of the two chains' windows. The token sends the same validation, under the same validationId, to its peer on each chain, and records the gateway used toward each: it emits ValidationRoutePinned and ProtocolMessageSent twice.
Step 2. Execute on the two satellites
Who: Investor (sender), or the spender named in the validation, on the sending satellite
Call: The sender's transfer on T-REX Lite of the sending satellite [[TBC: how a transfer on T-REX Lite names a recipient that is on another satellite]]
Before the call: The validation has been delivered to T-REX Lite on the sending satellite and is stored in its compliance contract.
What the contract checks: On the sending satellite, T-REX Lite executes only if a stored validation matches: the parties match, the amount is inside [amountMin, amountMax], and the execution is strictly before expiry. The validation is consumed there, once. The receiving satellite does not check the validation again. It mints only against a proof of burn that comes, through the trusted gateway, from the asset's T-REX Lite on the sending satellite: without a validation, that burn could not have happened.
What changes: The burn comes first. On the sending satellite, the sender's balance is burned, and T-REX Lite sends a proof of the burn, with the exact amount, to T-REX Lite on the receiving satellite. The receiving satellite mints that amount to the recipient. Each T-REX Lite sends its own settlement notification to the reference chain: the sending chain reports the burn, with from and an empty to. The receiving chain reports the mint, with to and an empty from. Both carry the validationId and the same amount.
Step 3. Record the first report
Who: An ERC-7786 gateway delivers the notification to the Token. No actor transacts
Call: Token.receiveMessage(receiveId, sender, payload), called by the gateway
Before the call: One of the two satellites has executed its half.
What the contract checks: The checks of 31, step 4, with one difference in how the report is matched.
The report is the burn: it carries
from, noto, and comes from the sender's chain through the gateway pinned for that chain. Or it is the mint: it carriesto, nofrom, and comes from the recipient's chain through the gateway pinned for that chain. Anything else reverts withSettlementLegMismatch.
What changes: The compliance records the amount and the wallet this report carries, for the other report to match, and emits ValidationLegConfirmed(validationId, chainKey, amount). Ownership does not move yet.
The burn
AwaitingMint
The amount leaves the bridged position of from and is held in transit under the validationId. The reservation on the wallet is released. Events ReleasedFromValidation and HeldInTransit(fromKey, validationId, fromWallet, amount).
The mint
AwaitingBurn
Nothing moves. The reservation on the wallet stays.
In both cases the pending amounts of the two identities stay reserved. From this point the validation can no longer be discarded: one satellite has executed its half, irreversibly.
Step 4. Settle on the second report
Who: An ERC-7786 gateway delivers the notification to the Token. No actor transacts
Call: Token.receiveMessage(receiveId, sender, payload), called by the gateway
Before the call: The other satellite has executed its half.
What the contract checks: The checks of step 3, and:
This half was not already reported. Otherwise nothing is applied, the compliance emits
ReplayedSettlementand the token pauses itself.The amount equals the amount of the first report. Otherwise
SettlementAmountMismatch.
What changes: The compliance releases the pending amounts of the two identities and, if the burn is the second report, the reservation on the sending wallet. It moves the executed amount from the position of the sender's identity to the position of the recipient's identity, unless they are the same. On the token, the amount held in transit, or the amount still on the sender's wallet, is credited to the bridged position of to, and the token emits BridgedTransfer(fromKey, toKey, validationId, from, to, amount). The validation becomes Settled and the compliance emits ValidationSettled(validationId, chainKey, amount), with the chain of the report that completed it. The TRACKER modules are told.
State after the flow
ModularCompliance.statusOf(validationId)isSettled.Token.bridgedBalanceOf(from)has decreased andToken.bridgedBalanceOf(to)has increased by the executed amount.Token.inTransitOf(validationId)is zero, andToken.totalInTransit()no longer counts it.Token.totalBridged()andToken.totalSupply()are unchanged throughout.ModularCompliance.positionOfhas moved by the executed amount between the two identities, unless they are the same.Nothing is reserved any longer for this validation.
Failure modes
Any revert of the request
Same causes as in 31. The window, pause and route checks apply to both chains (step 1)
See 31. The error names the chain
The delivery of a report reverts with SettlementLegMismatch
The report carries both wallets, the wrong wallet, or comes from the wrong chain (step 3, check 1)
An anomaly of the satellite. The Issuer investigates: 36
The delivery of the second report reverts with SettlementAmountMismatch
The two chains report different amounts under one validation (step 4, check 2)
An anomaly. The validation stays awaiting its second report until it is resolved: 35
The delivery of a report reverts with GatewayNotPinned
The report came through another gateway than the one the validation went out by toward that chain
The report is delivered through the pinned gateway
The status stays AwaitingMint
The receiving chain has not reported. The sender's tokens are burned on the satellite and held in transit on the register, still owned by the sender's identity
Wait. If the report never arrives, the Validation Keeper resolves the validation, and the amount returns to the sender's wallet on the register: 35
The status stays AwaitingBurn
The report of the sending chain has not arrived. The burn itself happened, since the receiving chain minted against its proof
Wait. If the report never arrives, the keeper resolves the validation and issuance is paused for the sending chain
The Validation Keeper cannot discard the validation
One report has arrived. A validation with one half executed is never discarded
Intended. It is settled by the second report, or resolved
The token paused itself
A half that was already reported was delivered again, or a report arrived for a resolved validation (step 4, check 1)
The recipient sees a balance on its satellite and cannot move it
The register has not settled: one report is missing
Wait for the settlement. An onward transfer needs a validation, capped at the position the register records
Common misunderstandings
Wrong: "The tokens cross from one satellite to the other."
Right: No token travels. The sending chain destroys its side and sends a proof of the burn. The receiving chain creates its side against that proof. Both act under one validation issued by the reference chain, and both report to it. The register is where the transfer is recorded.
Wrong: "The transfer is done when the burn is reported."
Right: The burn is half of it. The register takes the amount out of the sender's wallet and holds it in transit: no wallet holds it, and it still belongs to the sender's identity. Ownership moves when the mint is reported too, for the same amount.
Wrong: "The two reports must arrive in order."
Right: The burn executes before the mint, but the two reports travel separately and are accepted in either order. If the mint is reported first, nothing moves on the register until the burn is reported, and the whole movement is then applied at once.
Wrong: "If the second report never arrives, the tokens are lost."
Right: After a second reconciliation window past the release time, the Validation Keeper resolves the validation. When the burn was reported, the amount held in transit returns to the wallet it left on the register. When only the mint was reported, issuance is paused for the sending chain and the Issuer investigates.
Integration notes
Follow the transfer with
ModularCompliance.statusOf(validationId):Pending, thenAwaitingMintorAwaitingBurn, thenSettled.Index
ValidationLegConfirmedfor the first report andValidationSettledfor the second. On the token, indexHeldInTransitandBridgedTransfer.Show an amount in transit as still owned by the sender and on its way.
Token.inTransitOf(validationId)gives it.The release time of the validation uses the larger of the two reconciliation windows. Read it in
validationOf(validationId).releaseAt.Read
Token.pinnedRouteFor(validationId, chainKey)for each chain to know which gateway each report must come through.The caller of a transaction pays for the message it sends, directly or through a paymaster: the identity for the validation, on the reference chain, and the caller of the transfer for the burn, its report and the proof of burn, on the sending satellite. The mint on the receiving satellite and its report happen inside the delivery of the proof of burn.
[[TBC: who pays for a message that T-REX Lite sends while a delivery is executed]]Between two wallets of one identity on two satellites, the path is the same. The rules about distribution are not asked, and the position of the identity does not change.
Related flows
31 Transfer on a satellite : the request and the checks this guide builds on
35 Discard or resolve a validation : when one report never arrives
36 Handle a late reconciliation or a halt : when a report cannot be accounted for
33 Open an asset on a satellite : opening both chains
30 Delegate a position out to a satellite : how the position reached the first satellite
Last updated