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, ortransferFromby 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.
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.decodereads it withabi.decode(payload, (Message, uint8, bytes))and refuses another version withUnsupportedMessageVersion.Body. For types 0, 3 and 4,
abi.encodeof one struct. The three structs have dynamic fields, so a body starts with the 32-byte offset0x20, 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
sendMessageon its ERC-7786 gateway with its peer as recipient, an empty list of attributes and no value.
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.
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:
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:
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.
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
0x00010000018914followed by its 20 bytes.Only the canonical form is accepted.
WalletKeyLib.parserefuses 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 thekeccak256of these bytes.On an EVM satellite, T-REX Lite sits at the token's own address. With no registered peer,
peerForreturns that address for a registered chain of type0x0000. 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 assender.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 theIdentityFactorytrusts for that chain. This list is separate from theTrustedGatewayRegistry. The gateway presents the wallet itself assender, and the payload isabi.encode(wallet, identity, expiry)with the types(bytes, address, uint256). A validation that names a wallet with no link reverts withUnverifiedWallet, 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
ComplianceValidationtuple: 32-byte big-endian words, offsets for thebytesfields, content padded on the right.Encode
SettlementNotificationandBurnProofthe same way, in the same envelope, with version1.Keep
tokenandnativeWalletas 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 version1.A transfer executes only against a stored validation:
from,to, the caller, the inclusive range, strictly beforeexpiry. 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
nativeWalletto credit on the reference chain, which is not zero. The reference chain checks that it belongs to the identity ofburnedWallet.Every wallet is written in canonical ERC-7930 form.