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

Implement T-REX Lite on a new chain

This page is for a team that brings T-REX assets to a chain, EVM or not, by implementing T-REX Lite there: the token that represents an asset on a satellite chain. It gives the wire format, the five message types, every check the reference chain applies to what a satellite sends, and what the implementer still has to obtain.

Scope. T-REX Lite has no code in the protocol repositories and no published specification. This page is derived from the contracts of the reference chain, which define every message they send to a satellite and accept from one, and from T-REX Lite. What neither defines is listed under "What is not specified".

What T-REX Lite is

On an EVM satellite, T-REX Lite is a plain ERC-20 with a linked compliance contract that stores the validations it received. It holds no identity, no eligibility configuration and no rule, and it decides nothing.

  • Supply. It mints and burns on messages that arrive through the ERC-7786 gateway.

  • Transfer. transfer, or transferFrom by a named spender, executes only against a stored validation, consumes it and sends a settlement notification.

  • Recall. It burns locally and sends a burn proof.

  • Deployment. The T-REX Lite factory of the satellite deploys it, on a message through the gateway.

Five obligations are the specification of any implementation, on any chain.

Obligation
What it guarantees

Accept messages only through the trusted gateway, from its peers. A validation or an instruction counts only if its author is the asset's token on the reference chain. A proof of burn counts only if its author is the asset's T-REX Lite on another satellite.

Nobody else can authorize a transfer or create tokens on the satellite.

Execute a transfer only against a validation, and consume it. The sender, the recipient and the spender match the validation. The amount is inside its range. The execution happens strictly before its expiry. A validation is used once.

No transfer happens that the reference chain has not authorized, and none happens twice.

Report every execution. After a transfer, it sends a settlement notification naming the validation and the exact amount.

The register records the change of ownership.

Create and destroy only on instruction, or with proof. It mints on a mint instruction from the reference chain, or on a proof of burn from another satellite. When it burns, it sends the proof of it.

The balances on the satellite always equal what the register records as bridged there.

Refuse everything else. A transfer with no matching validation does not execute.

A holder cannot move tokens outside the rules by acting on the satellite.

The messages

MessageTypesLib is the wire specification. Every message is an ABI-encoded envelope around a body.

  • Envelope. Three 32-byte head words (the type code, the version 1, the offset of the body), then the length of the body and its bytes. decode reads it with abi.decode(payload, (Message, uint8, bytes)) and refuses another version with UnsupportedMessageVersion.

  • Body. For types 0, 3 and 4, abi.encode of one struct. The three structs have dynamic fields, so a body starts with the 32-byte offset 0x20, followed by the tuple. It has no type prefix and no version of its own. For types 1 and 2, the token forwards the bytes the agent passes, and the code defines no encoding for them.

  • Transport. The token calls sendMessage on its ERC-7786 gateway with its peer as recipient, an empty list of attributes and no value.

Code
Type
Direction
Body
Sent

0

COMPLIANCE_VALIDATION

Reference chain to satellite

ComplianceValidation

At issuance, once per satellite chain involved

1

MINT_INSTRUCTION

Reference chain to satellite

No struct in the code

For a delegation-out. One-way

2

RECALL_INSTRUCTION

Reference chain to satellite

No struct in the code

When an agent orders a recall

3

SETTLEMENT_NOTIFICATION

Satellite to reference chain

SettlementNotification

After each execution, by each chain that executed a half

4

BURN_PROOF

Satellite to reference chain

BurnProof

After a burn for a recall

No type is defined for a message from one satellite to another. A new type is appended to the enum, so these codes never change.

COMPLIANCE_VALIDATION (0)

Sent by: ModularCompliance.requestTransferValidation. It encodes the struct with abi.encode and calls Token.dispatchComplianceValidation(chainKey, validationId, body), which only the bound compliance can call. The token wraps the body in the envelope and sends it to its peer. For a transfer between two satellites, the same body goes to both chains.

The receiver decodes it with abi.decode(body, (ComplianceValidation)), as decodeValidation does, and stores it until it is consumed or expires. reconciliationWindow is in seconds: the time the reference chain keeps the reservation after expiry.

MINT_INSTRUCTION (1) and RECALL_INSTRUCTION (2)

Sent by: Token.dispatchMintInstruction(chainKey, body) and Token.dispatchRecallInstruction(chainKey, body), both restricted to AGENT. The token forwards body as given and keeps no record of the instruction.

The receiver of a mint instruction mints a position that the register already records as bridged to a wallet on its chain, and sends nothing back. On a recall instruction it burns the amount from the named wallet and answers with a BURN_PROOF. If the wallet no longer holds the amount, it reports the failure.

SETTLEMENT_NOTIFICATION (3)

Sent by: T-REX Lite, after it executed against a validation. The payload is what encodeSettlement returns: abi.encode(Message.SETTLEMENT_NOTIFICATION, VERSION, abi.encode(notification)).

Execution

from

to

Sent from

Transfer on one satellite, or to a recipient on the reference chain

The validation's from

The validation's to

The chain of from

Burn half of a transfer between two satellites

The validation's from

Empty

The chain of from

Mint half of a transfer between two satellites

Empty

The validation's to

The chain of to

from and to are the bytes of the validation, unchanged: the reference chain compares their keccak256 with the hashes it stored at issuance. amount is the exact amount executed.

BURN_PROOF (4)

Sent by: T-REX Lite, after a burn for a recall, started by the holder or by a recall instruction. The payload is what encodeBurnProof returns. burnedWallet is the wallet that was burned, as an interoperable address. nativeWallet is the address to credit on the reference chain, of the same identity.

What the reference chain checks on delivery

The gateway calls Token.receiveMessage(receiveId, sender, payload). The checks run in this order. A revert undoes everything, including the record of the delivery, so the same message can be delivered again.

#
Check
If it fails

1

The gateway is in the network's TrustedGatewayRegistry.

ERC7786RecipientUnauthorizedGateway

2

sender is, byte for byte, peerFor(chainKey) for the chain it names.

InteroperableAddressParsingError when sender is not an ERC-7930 version 1 address. Otherwise SenderNotPeer

3

The envelope decodes, with a known type and version 1.

A revert, or UnsupportedMessageVersion

4

This gateway has not delivered this receiveId before.

MessageAlreadyReceived

5

The type is 3 or 4.

MessageTypeNotInbound

Then, for a settlement notification:

#
Check
If it fails

6

The body decodes as a SettlementNotification.

A revert

7

The gateway is the one this validation went out through toward that chain: pinnedRouteFor(validationId, chainKey). With no pinned route, it is the chain's current route.

GatewayNotPinned, or GatewayNotRouted

8

The token is not paused.

EnforcedPause

9

The validationId was issued.

No revert: ReplayedSettlement, and the token pauses itself

10

The wallets and the origin chain match one row of the table above. For a transfer between two satellites, a report with both wallets matches none.

SettlementLegMismatch

11

amountMin <= amount <= amountMax.

SettlementOutOfBounds

12

This half was not already applied, and the validation is not Resolved.

No revert: ReplayedSettlement, and the token pauses itself

13

For the second of two halves: amount equals the amount of the first.

SettlementAmountMismatch

14

The bridged position recorded for from covers the amount.

InsufficientBridgedBalance

A notification for a validation that the Validation Keeper discarded passes the same checks and is applied. The compliance emits LateReconciliation, and pauses issuance for the origin chain when the executed amount exceeds what the rules allow at that moment.

Or, for a burn proof:

#
Check
If it fails

6

The gateway is the chain's current route.

GatewayNotRouted

7

The body decodes as a BurnProof, and nativeWallet is not zero.

A revert, or ZeroAddress

8

nativeWallet belongs to the same identity as burnedWallet.

[[TBC: the error of this check]]

9

The bridged position of burnedWallet covers the amount.

InsufficientBridgedBalance

A halt stops the asset. On checks 9 and 12 of a settlement, nothing is applied and the token pauses every movement between holders until an agent has investigated: see 36 Handle a late reconciliation or a halt. A satellite that reports one execution twice stops the asset.

Consuming a validation

A validation authorizes one transfer. expiry is a timestamp in seconds: the reference chain's block time at issuance plus the validity window. The reference chain verifies part of what consuming means and trusts T-REX Lite for the rest.

On the satellite
The reference chain

The sender is from and the recipient is to.

Verifies: check 10.

The amount is inside [amountMin, amountMax], bounds included.

Verifies: check 11.

The validation is used once.

Verifies, as an emergency: a second report halts the token, check 12.

The execution is strictly before expiry.

Trusts the satellite. A notification carries no time, and a settlement is applied whatever the clock says.

The caller is from, or spender through transferFrom. An empty spender means from alone.

Trusts the satellite. A notification names no caller, and no module runs on the satellite: the SPENDER modules accepted the spender at issuance.

The sender holds the amount.

Verifies on its own ledger: check 14. At issuance, amountMax is capped at what the register records as available for from.

The compliance stores MessageTypesLib.hashValidation(validation), an ERC-712 struct hash without a domain, in validationOf(validationId).hash. No message carries it. The NatSpec of the stored hash calls it the identifier the satellite consumes: a satellite that uses it recomputes it from the body.

A transfer between two satellites

The compliance expects two reports when to is on another satellite than from. The flow is in 34 Transfer between two satellites.

  • The sending chain consumes the validation, burns the sender's balance, and sends a proof of burn to the asset's T-REX Lite on the receiving chain. It reports the burn.

  • The receiving chain mints the same amount to the recipient against that proof. It does not check the validation again. It reports the mint.

The burn always comes first. The reference chain accepts the two reports in either order. The first report is recorded with ValidationLegConfirmed: a burn puts the amount in transit on the register (status AwaitingMint), a mint moves nothing (status AwaitingBurn). The second report completes the transfer if it repeats the amount, with ValidationSettled.

When one report never arrives, the Validation Keeper calls resolveStuckValidation, two reconciliation windows after expiry at the earliest. If only the burn was reported, the amount in transit returns to the sender's wallet on the register. If only the mint was reported, issuance is paused for the sending chain. A report that arrives afterward halts the token.

Addresses and identity

  • Wallets are ERC-7930 interoperable addresses, version 1, in binary form: a 2-byte version, a 2-byte chain type, the length of the chain reference on 1 byte, the chain reference, the length of the address on 1 byte, the address. An EVM address on chain id 137 is 0x00010000018914 followed by its 20 bytes.

  • Only the canonical form is accepted. WalletKeyLib.parse refuses bytes after the address and, on an EVM chain, a chain reference of several bytes that starts with a zero byte: NonCanonicalInteroperableAddress. The register keys a wallet by the keccak256 of these bytes.

  • On an EVM satellite, T-REX Lite sits at the token's own address. With no registered peer, peerFor returns that address for a registered chain of type 0x0000. See Deterministic deployment (CREATE3).

  • On another kind of chain, the Issuer registers the peer with Token.setPeer(chainKey, peer). Until then the chain is not open. The gateway presents exactly the registered bytes as sender.

  • Identity stays on the reference chain. A wallet of the satellite is linked to an ONCHAINID through the IdentityFactory: see 07 Link a wallet to an identity. The wallet sends a link proposal from its own chain, through a gateway that the IdentityFactory trusts for that chain. This list is separate from the TrustedGatewayRegistry. The gateway presents the wallet itself as sender, and the payload is abi.encode(wallet, identity, expiry) with the types (bytes, address, uint256). A validation that names a wallet with no link reverts with UnverifiedWallet, so a chain has no usable wallet until this path works. [[TBC: the contract and the function the wallet calls on its own chain]]

On a non-EVM chain

The reference chain speaks one format, ABI encoding, and an implementation on another kind of chain reproduces it. See Non-EVM satellites.

  • Decode the envelope and the ComplianceValidation tuple: 32-byte big-endian words, offsets for the bytes fields, content padded on the right.

  • Encode SettlementNotification and BurnProof the same way, in the same envelope, with version 1.

  • Keep token and nativeWallet as 20-byte EVM addresses: they are on the reference chain.

What is not specified

The implementer obtains these from the protocol team.

  • [[TBC: the names and the signatures of T-REX Lite's own functions, the T-REX Lite factory and the interface of the linked compliance contract]]

  • [[TBC: the content of the mint instruction, and the function of T-REX Lite that receives it]]

  • [[TBC: the content of a recall instruction]]

  • [[TBC: the recall function of T-REX Lite, its name and its arguments]]

  • [[TBC: the message that reports a failed recall]]

  • [[TBC: the format of the proof of burn between two satellites, and how T-REX Lite is told its gateway and its peers on other satellites]]

  • [[TBC: how a transfer on T-REX Lite names a recipient that is on another satellite, or on the reference chain]]

  • [[TBC: the call on the reference chain that has T-REX Lite deployed on a satellite]]

  • [[TBC: who pays for a message that T-REX Lite sends while a delivery is executed]]

  • [[TBC: what the receiving satellite does with its copy of the validation, and whether its mint report takes the validationId and the bytes of to from that copy or from the proof of burn]]

Test against the reference chain

The interop tests of the T-REX repository simulate a satellite, in test/integration/interop and test/integration/helpers/InteropSuiteTest.sol. ERC7786GatewayMock stands in for the gateway of one satellite chain: a send only queues, and the test relays, repeats or drops each message. The token's own address plays T-REX Lite and sends payloads built with MessageTypesLib.encodeSettlement and encodeBurnProof. _decodeQueuedValidation decodes an outbound validation as a satellite does. Feed the bytes your implementation produces through the same mock. The mock attributes every message to an EVM chain id and to the address that queued it: it tests the payload bytes of any implementation, under an EVM chain id.

Conformance checklist

  • A message is accepted only through the trusted gateway and from a peer.

  • The envelope is read as (uint8, uint8, bytes), and every message sent carries version 1.

  • A transfer executes only against a stored validation: from, to, the caller, the inclusive range, strictly before expiry. Any other transfer reverts.

  • A validation is consumed once.

  • Every execution is reported once, with the validationId, the exact amount and the wallet bytes of the validation.

  • Between two satellites, the burn comes first, each chain reports its own half with the other wallet empty, and both amounts are equal.

  • Tokens are minted only on a mint instruction or on a proof of burn, and every burn is proven.

  • A burn proof carries the nativeWallet to credit on the reference chain, which is not zero. The reference chain checks that it belongs to the identity of burnedWallet.

  • Every wallet is written in canonical ERC-7930 form.