> For the complete documentation index, see [llms.txt](https://docs.t-rex.network/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.t-rex.network/guides/cross-chain/34.-transfer-between-two-satellites.md).

# 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](/guides/cross-chain/31.-transfer-on-a-satellite.md) 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** | The asset is open on both satellites, each with its reconciliation window (see [33](/guides/cross-chain/33.-open-an-asset-on-a-satellite.md)); issuance is paused for neither chain; the sending wallet holds a bridged position (see [30](/guides/cross-chain/30.-delegate-a-position-out-to-a-satellite.md)); the recipient's wallet on the other satellite is linked to an identity that is eligible for the asset |
| **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

| Actor                | Role in this flow                                                                                   | What they hold                                                  |
| -------------------- | --------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| 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

```mermaid
sequenceDiagram
    participant Identity as Sender ONCHAINID
    participant MC as ModularCompliance
    participant Token
    participant LiteA as T-REX Lite on satellite A
    participant LiteB as T-REX Lite on satellite B

    Identity->>MC: requestTransferValidation(from, to, min, max, spender)
    MC->>MC: checks, record, reserve amountMax
    MC->>Token: dispatch toward satellite A
    Token->>LiteA: compliance validation
    MC->>Token: dispatch toward satellite B
    Token->>LiteB: compliance validation
    LiteA->>LiteA: consume the validation, burn the sender's balance
    LiteA->>LiteB: proof of burn, with the amount
    LiteA->>Token: settlement notification: from, amount
    Token->>MC: handleSettlement
    MC->>Token: holdInTransit(from, amount, validationId, reserved)
    Note over MC,Token: status AwaitingMint, amount held in transit
    LiteB->>LiteB: mint the same amount to the recipient
    LiteB->>Token: settlement notification: to, amount
    Token->>MC: handleSettlement
    MC->>MC: same amount, release, move the positions
    MC->>Token: settleValidation(from, to, amount, validationId)
    Note over MC,Token: status Settled
```

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](/guides/cross-chain/31.-transfer-on-a-satellite.md), 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](/guides/cross-chain/31.-transfer-on-a-satellite.md), step 2, with these differences.

1. Both chains must be open for issuance: neither is paused, and each has a reconciliation window. Otherwise `ValidationIssuancePaused` or `ReconciliationWindowNotSet`, naming the chain.
2. Both chains must be open on the token. Otherwise `ChainNotOpen`, naming the chain.

**What changes:** As in [31](/guides/cross-chain/31.-transfer-on-a-satellite.md), 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](/guides/cross-chain/31.-transfer-on-a-satellite.md), step 4, with one difference in how the report is matched.

1. The report is the burn: it carries `from`, no `to`, and comes from the sender's chain through the gateway pinned for that chain. Or it is the mint: it carries `to`, no `from`, and comes from the recipient's chain through the gateway pinned for that chain. Anything else reverts with `SettlementLegMismatch`.

**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 first report is | Status         | On the token                                                                                                                                                                                                                                 |
| ------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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:

1. This half was not already reported. Otherwise nothing is applied, the compliance emits `ReplayedSettlement` and the token pauses itself.
2. 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)` is `Settled`.
* `Token.bridgedBalanceOf(from)` has decreased and `Token.bridgedBalanceOf(to)` has increased by the executed amount.
* `Token.inTransitOf(validationId)` is zero, and `Token.totalInTransit()` no longer counts it.
* `Token.totalBridged()` and `Token.totalSupply()` are unchanged throughout.
* `ModularCompliance.positionOf` has moved by the executed amount between the two identities, unless they are the same.
* Nothing is reserved any longer for this validation.

## Failure modes

| Symptom                                                                   | Cause                                                                                                                                                           | Fix                                                                                                                                                                                                               |
| ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Any revert of the request                                                 | Same causes as in [31](/guides/cross-chain/31.-transfer-on-a-satellite.md). The window, pause and route checks apply to both chains (step 1)                    | See [31](/guides/cross-chain/31.-transfer-on-a-satellite.md). 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](/guides/cross-chain/36.-handle-a-late-reconciliation-or-a-halt.md)                                                                                     |
| 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](/guides/cross-chain/35.-discard-or-resolve-a-validation.md)                                                                |
| 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](/guides/cross-chain/35.-discard-or-resolve-a-validation.md) |
| 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)                                           | [36](/guides/cross-chain/36.-handle-a-late-reconciliation-or-a-halt.md)                                                                                                                                           |
| 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`, then `AwaitingMint` or `AwaitingBurn`, then `Settled`.
* Index `ValidationLegConfirmed` for the first report and `ValidationSettled` for the second. On the token, index `HeldInTransit` and `BridgedTransfer`.
* 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](/guides/cross-chain/31.-transfer-on-a-satellite.md) : the request and the checks this guide builds on
* [35 Discard or resolve a validation](/guides/cross-chain/35.-discard-or-resolve-a-validation.md) : when one report never arrives
* [36 Handle a late reconciliation or a halt](/guides/cross-chain/36.-handle-a-late-reconciliation-or-a-halt.md) : when a report cannot be accounted for
* [33 Open an asset on a satellite](/guides/cross-chain/33.-open-an-asset-on-a-satellite.md) : opening both chains
* [30 Delegate a position out to a satellite](/guides/cross-chain/30.-delegate-a-position-out-to-a-satellite.md) : how the position reached the first satellite


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.t-rex.network/guides/cross-chain/34.-transfer-between-two-satellites.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
