@crossecute/protocol 0.0.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +25 -1
  2. package/abi/Blake2b256.json +1 -0
  3. package/abi/Blake2bScheme.json +1 -0
  4. package/abi/ChainRegistry.json +1 -0
  5. package/abi/Commitment.json +1 -0
  6. package/abi/Conduits.json +1 -0
  7. package/abi/DivergentConduits.json +1 -0
  8. package/abi/DivergentTransceiver.json +1 -0
  9. package/abi/DivergentTransceiverExtension.json +1 -0
  10. package/abi/Envelope.json +1 -0
  11. package/abi/Erc7930.json +1 -0
  12. package/abi/Executor.json +1 -0
  13. package/abi/IBootstrapAllowlist.json +1 -0
  14. package/abi/ICancel.json +1 -0
  15. package/abi/IChainRegistryRefs.json +1 -0
  16. package/abi/ICommitFinalize.json +1 -0
  17. package/abi/ICommitmentScheme.json +1 -0
  18. package/abi/IConduitReceiverReport.json +1 -0
  19. package/abi/IConduitTransceiver.json +1 -0
  20. package/abi/IERC7786GatewaySource.json +1 -0
  21. package/abi/IERC7786Recipient.json +1 -0
  22. package/abi/IExecute.json +1 -0
  23. package/abi/ILzOftConduitFactory.json +1 -0
  24. package/abi/IProviderIdTable.json +1 -0
  25. package/abi/IProxy.json +1 -0
  26. package/abi/IReceiverInit.json +1 -0
  27. package/abi/IRefValidator.json +1 -0
  28. package/abi/IReportFloat.json +1 -0
  29. package/abi/ITransmitterInit.json +1 -0
  30. package/abi/IVmDeriver.json +1 -0
  31. package/abi/KeccakScheme.json +1 -0
  32. package/abi/Move.json +1 -0
  33. package/abi/MoveValidator.json +1 -0
  34. package/abi/OpStackTransmitterBase.json +1 -0
  35. package/abi/OutboundBase.json +1 -0
  36. package/abi/OwnableTransmitter.json +1 -0
  37. package/abi/ProviderAddress.json +1 -0
  38. package/abi/ProviderAttribute.json +1 -0
  39. package/abi/ProviderChainId.json +1 -0
  40. package/abi/ProviderReceiver.json +1 -0
  41. package/abi/ProviderTransceiver.json +1 -0
  42. package/abi/Proxy.json +1 -0
  43. package/abi/ProxyDeployer.json +1 -0
  44. package/abi/ProxyInitCode.json +1 -0
  45. package/abi/ReceiverBase.json +1 -0
  46. package/abi/Roles.json +1 -0
  47. package/abi/RolesEnumerable.json +1 -0
  48. package/abi/SchemeFold.json +1 -0
  49. package/abi/Sha256Scheme.json +1 -0
  50. package/abi/StarknetDerive.json +1 -0
  51. package/abi/StarknetValidator.json +1 -0
  52. package/abi/SuiDerive.json +1 -0
  53. package/abi/TransceiverBase.json +1 -0
  54. package/abi/TransceiverCore.json +1 -0
  55. package/abi/TransceiverExtension.json +1 -0
  56. package/abi/TransmitterBase.json +1 -0
  57. package/abi/Treasury.json +1 -0
  58. package/abi/TronConduits.json +1 -0
  59. package/abi/TronTransceiver.json +1 -0
  60. package/abi/TronTransceiverExtension.json +1 -0
  61. package/abi/VmDeriver.json +1 -0
  62. package/abi/ZkSyncConduits.json +1 -0
  63. package/abi/ZkSyncTransceiver.json +1 -0
  64. package/abi/ZkSyncTransceiverExtension.json +1 -0
  65. package/abi/index.d.ts +63 -0
  66. package/abi/index.js +63 -0
  67. package/contracts/addressing/ChainKey.sol +45 -0
  68. package/contracts/addressing/ChainType.sol +50 -0
  69. package/contracts/addressing/Erc7930.sol +209 -0
  70. package/contracts/addressing/Move.sol +212 -0
  71. package/contracts/conduit/Conduits.sol +43 -0
  72. package/contracts/conduit/Proxy.sol +144 -0
  73. package/contracts/conduit/ProxyDeployer.sol +42 -0
  74. package/contracts/conduit/ProxyInitCode.sol +47 -0
  75. package/contracts/derivation/AddressDerive.sol +326 -0
  76. package/contracts/derivation/BitcoinDerive.sol +33 -0
  77. package/contracts/derivation/Blake2b256.sol +122 -0
  78. package/contracts/derivation/IVmDeriver.sol +35 -0
  79. package/contracts/derivation/StarknetDerive.sol +93 -0
  80. package/contracts/derivation/SuiDerive.sol +102 -0
  81. package/contracts/derivation/VmDeriver.sol +291 -0
  82. package/contracts/messaging/Call.sol +43 -0
  83. package/contracts/messaging/Commitment.sol +131 -0
  84. package/contracts/messaging/DeliveryGas.sol +18 -0
  85. package/contracts/messaging/Envelope.sol +123 -0
  86. package/contracts/messaging/Executor.sol +52 -0
  87. package/contracts/messaging/IErc7786.sol +68 -0
  88. package/contracts/messaging/Payload.sol +90 -0
  89. package/contracts/messaging/Roles.sol +113 -0
  90. package/contracts/messaging/inbound/ReceiverBase.sol +359 -0
  91. package/contracts/messaging/outbound/OutboundBase.sol +221 -0
  92. package/contracts/messaging/outbound/OwnableTransmitter.sol +23 -0
  93. package/contracts/messaging/outbound/TransmitterBase.sol +604 -0
  94. package/contracts/messaging/transceiver/DivergentConduits.sol +92 -0
  95. package/contracts/messaging/transceiver/DivergentTransceiver.sol +97 -0
  96. package/contracts/messaging/transceiver/IBootstrapAllowlist.sol +8 -0
  97. package/contracts/messaging/transceiver/TransceiverBase.sol +541 -0
  98. package/contracts/messaging/transceiver/TransceiverCore.sol +307 -0
  99. package/contracts/messaging/transceiver/TransceiverExtension.sol +278 -0
  100. package/contracts/protocols/ProviderAddress.sol +32 -0
  101. package/contracts/protocols/ProviderAttribute.sol +65 -0
  102. package/contracts/protocols/ProviderChainId.sol +115 -0
  103. package/contracts/protocols/ProviderReceiver.sol +24 -0
  104. package/contracts/protocols/ProviderTransceiver.sol +48 -0
  105. package/contracts/protocols/layerzero/ILzOftConduitFactory.sol +10 -0
  106. package/contracts/protocols/op-stack/OpStackTransmitterBase.sol +11 -0
  107. package/contracts/registry/ChainRegistry.sol +667 -0
  108. package/contracts/registry/IChainRegistryRefs.sol +81 -0
  109. package/contracts/registry/ICommitmentScheme.sol +46 -0
  110. package/contracts/registry/IRefValidator.sol +13 -0
  111. package/contracts/registry/Provenance.sol +25 -0
  112. package/contracts/schemes/Blake2bScheme.sol +18 -0
  113. package/contracts/schemes/KeccakScheme.sol +19 -0
  114. package/contracts/schemes/Sha256Scheme.sol +15 -0
  115. package/contracts/treasury/Treasury.sol +76 -0
  116. package/contracts/validators/MoveValidator.sol +24 -0
  117. package/contracts/validators/StarknetValidator.sol +20 -0
  118. package/package.json +34 -3
@@ -0,0 +1,667 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
5
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
6
+
7
+ import {IVmDeriver} from "../derivation/IVmDeriver.sol";
8
+ import {AddressDerive} from "../derivation/AddressDerive.sol";
9
+ import {Provenance} from "./Provenance.sol";
10
+ import {ProviderDeployment, Status} from "./IChainRegistryRefs.sol";
11
+ import {IRefValidator} from "./IRefValidator.sol";
12
+ import {ICommitmentScheme, SchemeFold} from "./ICommitmentScheme.sol";
13
+ import {Erc7930} from "../addressing/Erc7930.sol";
14
+ import {Move} from "../addressing/Move.sol";
15
+ import {PROXY_DEPLOYER, proxySalt} from "../conduit/ProxyDeployer.sol";
16
+
17
+ /// @notice A provider a registry is born knowing, with the CREATE2 inputs its contracts
18
+ /// deploy from.
19
+ struct ProviderSeed {
20
+ string name;
21
+ bytes32 salt;
22
+ bytes32 proxyInitCodeHash;
23
+ }
24
+
25
+ /// @notice What a registry is born knowing: enough for a transceiver on this chain to accept,
26
+ /// with no owner call, the bootstrap that creates its owner (the governor's conduit).
27
+ /// @dev Identical on every chain, so the registry lands at one CREATE2 address on each. Empty
28
+ /// on a chain configured by its owner from the start.
29
+ struct RegistrySeed {
30
+ /// The governor's home chain, as an ERC-7930 chain identifier.
31
+ bytes governorHome;
32
+ Provenance governorHomeGrade;
33
+ /// The account that deployed every seeded provider's transceiver on the home, and the
34
+ /// registry's initial `currentDeployer`. Required when a home and providers are seeded.
35
+ address deployer;
36
+ ProviderSeed[] providers;
37
+ }
38
+
39
+ /// @dev One slot.
40
+ struct ChainSupport {
41
+ address deployedBy;
42
+ Status status;
43
+ }
44
+
45
+ /// @title ChainRegistry
46
+ /// @notice One per chain, shared by every provider there: the directory of every chain this one
47
+ /// talks to, and of what can be known about addresses on each.
48
+ ///
49
+ /// @dev It answers questions about a chain, never about a counterpart: a transceiver's
50
+ /// location is per provider and lives on that provider's transceiver, while how well an
51
+ /// address on a chain can be known is the same for every provider, so it is answered here
52
+ /// once.
53
+ ///
54
+ /// @dev It holds a directory of chainKeys and message providers, and the per-chain policy a
55
+ /// transceiver consults before recording anything: `provenanceFor`, `isSuspended`,
56
+ /// `validateLocation`, `expectedTransceiver`, and `commitmentFor`. It holds no routes and
57
+ /// no counterparts, and no conduit reads it, so a compromised registry cannot misroute a
58
+ /// conduit's payload.
59
+ ///
60
+ /// @dev Fixed: no proxy and no upgrade path, and a transceiver's pointer to it is write-once.
61
+ /// A chain's grade is write-once too, and so is the deployer a provider is added on a chain
62
+ /// under; the plugins (validator,
63
+ /// deriver, derive params, commitment scheme) stay rebindable so their mistakes can be
64
+ /// fixed, and every change waits out the owning timelock.
65
+ contract ChainRegistry is Ownable {
66
+ using EnumerableSet for EnumerableSet.Bytes32Set;
67
+
68
+ /* ================================= storage ================================= */
69
+
70
+ /// Set of keccak256(canonical ERC-7930 chain identifier).
71
+ EnumerableSet.Bytes32Set private _chainKeys;
72
+ /// chainKey => the canonical chain identifier it hashes from. Kept after removal.
73
+ mapping(bytes32 => bytes) private _chainIdentifier;
74
+
75
+ /// Set of keccak256(message provider name).
76
+ EnumerableSet.Bytes32Set private _messageProviders;
77
+ /// messageProvider => the name it hashes from.
78
+ mapping(bytes32 => string) private _messageProviderName;
79
+ /// messageProvider => the CREATE2 inputs its transceiver and conduits deploy from.
80
+ mapping(bytes32 => ProviderDeployment) private _deployment;
81
+
82
+ /// The deployer a provider added from now on is recorded under. See `setCurrentDeployer`.
83
+ address public currentDeployer;
84
+ /// chainKey => messageProvider => who deployed that provider's transceiver there, and whether
85
+ /// sends may reach it. Packs into one slot.
86
+ mapping(bytes32 => mapping(bytes32 => ChainSupport)) private _support;
87
+
88
+ /// messageProvider => the transceiver on this chain that serves it.
89
+ mapping(bytes32 => address) private _localTransceiver;
90
+ /// The reverse: which provider a transceiver on this chain serves.
91
+ mapping(address => bytes32) private _providerOfTransceiver;
92
+
93
+ /// chainKey => the contract that computes addresses on that chain, so callers never
94
+ /// branch on VM.
95
+ mapping(bytes32 => IVmDeriver) private _deriverOf;
96
+ /// chainKey => the abi-encoded `(Scheme, bytes)` its deriver expects. Stored so
97
+ /// `expectedTransceiver` takes no inputs.
98
+ mapping(bytes32 => bytes) private _deriveParams;
99
+
100
+ /// chainKey => optional value-range validator for what ERC-7930 cannot express, e.g.
101
+ /// Starknet felts.
102
+ mapping(bytes32 => IRefValidator) private _validatorOf;
103
+ /// chainKey => what an address claim about this chain is worth, given at registration.
104
+ /// @dev `Unique` for chains this contract cannot recompute: Starknet (Pedersen), and
105
+ /// zkSync and Tron (different CREATE2).
106
+ mapping(bytes32 => Provenance) private _provenanceOf;
107
+ /// chainKey => whether transceivers refuse it. See `setSuspended`.
108
+ mapping(bytes32 => bool) private _suspended;
109
+
110
+ /// chainKey => messageProvider => abi-encoded `Move.MoveQualifier` for that provider's
111
+ /// transceiver there.
112
+ /// @dev A Move call target is `address::module::function`, so the address alone does not
113
+ /// identify it. Advisory: only a destination executor off-chain reads it.
114
+ mapping(bytes32 => mapping(bytes32 => bytes)) private _qualifiers;
115
+
116
+ /// chainKey => the primitive that chain's receiver hashes commitments with.
117
+ /// @dev A mapping rather than the `Scheme` enum, which is compiled into every locked
118
+ /// transmitter and cannot grow. Mutable because nothing enforces with it: a receiver
119
+ /// checks its own compiled fold.
120
+ mapping(bytes32 => ICommitmentScheme) private _commitmentSchemeOf;
121
+
122
+ /* ================================== events ================================= */
123
+
124
+ event ChainKeyAdded(bytes32 indexed chainKey, bytes chainIdentifier, Provenance provenance);
125
+ event ChainKeyRemoved(bytes32 indexed chainKey);
126
+ event MessageProviderAdded(bytes32 indexed messageProvider, string name);
127
+ event MessageProviderRemoved(bytes32 indexed messageProvider);
128
+ event LocalTransceiverSet(bytes32 indexed messageProvider, address transceiver);
129
+ event ProviderDeploymentSet(bytes32 indexed messageProvider, bytes32 salt, bytes32 proxyInitCodeHash);
130
+ event CurrentDeployerSet(address deployer);
131
+ event ProviderAddedOnChain(bytes32 indexed chainKey, bytes32 indexed messageProvider, address deployedBy);
132
+ event PendingRekeyed(bytes32 indexed chainKey, bytes32 indexed messageProvider, address deployedBy);
133
+ event MarkedLive(bytes32 indexed chainKey, bytes32 indexed messageProvider);
134
+ event DeriverSet(bytes32 indexed chainKey, address deriver);
135
+ event DeriveParamsSet(bytes32 indexed chainKey, uint8 scheme, bytes32 paramsHash);
136
+ event ValidatorSet(bytes32 indexed chainKey, address validator);
137
+ event SuspendedSet(bytes32 indexed chainKey, bool suspended);
138
+ event QualifierSet(bytes32 indexed chainKey, bytes32 indexed messageProvider, bytes32 qualifierHash);
139
+ event CommitmentSchemeSet(bytes32 indexed chainKey, address scheme);
140
+
141
+ /* ================================== errors ================================= */
142
+
143
+ /// @dev A write-once value, once recorded, is fixed: a provider deployment, a chain's grade,
144
+ /// the deployer a provider was added on a chain under, or a Move qualifier.
145
+ error AlreadySet();
146
+ /// @dev Only an `eip155` chain can be `Predetermined`: that is the grade the EVM CREATE2
147
+ /// prediction trusts.
148
+ error NotEvmChain();
149
+ error NoCounterpart();
150
+ /// @dev No salt recorded for this provider.
151
+ error NoProviderDeployment();
152
+ error ZeroSalt();
153
+ error ZeroDeployedBy();
154
+ /// @dev The call named a deployer other than `currentDeployer`: a registry that applied a
155
+ /// rotation in another order refuses instead of recording a different one.
156
+ error DeployerMismatch(address expected, address current);
157
+ /// @dev The provider was never added on this chain.
158
+ error NotOnChain(bytes32 chainKey, bytes32 messageProvider);
159
+ /// @dev Only a `Pending` entry can be re-keyed or marked live.
160
+ error NotPending(bytes32 chainKey, bytes32 messageProvider);
161
+ error ZeroInitCodeHash();
162
+ error UnknownChainKey();
163
+ error UnknownMessageProvider();
164
+ error EmptyName();
165
+ error NoDeriver();
166
+ error NoDeriveParams();
167
+ error DeriverChainMismatch();
168
+ error SchemeNotSupported();
169
+ /// @dev No primitive registered for this chain. Reverting beats returning a digest the
170
+ /// destination might never match; see `Commitment._hash`.
171
+ error NoCommitmentScheme();
172
+ error NoQualifier();
173
+
174
+ /* =============================== constructor =============================== */
175
+
176
+ /// @param owner_ This chain's timelock.
177
+ /// @dev Seeding goes through the same internal setters the owner uses later, so a seeded
178
+ /// entry is write-once exactly like one added afterwards.
179
+ constructor(address owner_, RegistrySeed memory seed) Ownable(owner_) {
180
+ if (seed.deployer != address(0)) _setCurrentDeployer(seed.deployer);
181
+ bytes32 home;
182
+ if (seed.governorHome.length != 0) home = _addChainKey(seed.governorHome, seed.governorHomeGrade);
183
+ for (uint256 i; i < seed.providers.length; ++i) {
184
+ ProviderSeed memory p = seed.providers[i];
185
+ bytes32 provider = _addMessageProvider(p.name);
186
+ _setProviderDeployment(provider, p.salt, p.proxyInitCodeHash);
187
+ if (home != bytes32(0)) _addProviderOnChain(home, provider, seed.deployer, Status.Live);
188
+ }
189
+ }
190
+
191
+ /* ================================ directory ================================ */
192
+
193
+ /// @notice Register a chain by its ERC-7930 chain identifier, with what an address claim
194
+ /// about it is worth.
195
+ /// @dev Takes the envelope, not a hash, so `parseStrict` rejects a non-canonical framing
196
+ /// before it becomes a permanent key.
197
+ /// @dev The grade is write-once and has no default, so whether the chain reports its
198
+ /// receivers is fixed when it is first registered, even across a removal. The same
199
+ /// grade again is a no-op.
200
+ /// @param identifier ERC-7930 bytes. A conduit envelope is accepted and reduced to
201
+ /// its chain identifier form.
202
+ function addChainKey(bytes calldata identifier, Provenance provenance)
203
+ external
204
+ onlyOwner
205
+ returns (bytes32 chainKey)
206
+ {
207
+ return _addChainKey(identifier, provenance);
208
+ }
209
+
210
+ function _addChainKey(bytes memory identifier, Provenance provenance) private returns (bytes32 chainKey) {
211
+ bytes memory canonical = Erc7930.toChainIdentifier(identifier);
212
+ chainKey = keccak256(canonical);
213
+
214
+ if (_chainIdentifier[chainKey].length != 0) {
215
+ if (_provenanceOf[chainKey] != provenance) revert AlreadySet();
216
+ } else {
217
+ if (provenance == Provenance.Predetermined && Erc7930.parseStrict(canonical).chainType != Erc7930.CT_EIP155)
218
+ {
219
+ revert NotEvmChain();
220
+ }
221
+ _chainIdentifier[chainKey] = canonical;
222
+ _provenanceOf[chainKey] = provenance;
223
+ }
224
+
225
+ if (_chainKeys.add(chainKey)) emit ChainKeyAdded(chainKey, canonical, provenance);
226
+ }
227
+
228
+ /// @notice Drop a chain from the directory.
229
+ /// @dev Stops onboarding only: `validateLocation` requires membership, so no transceiver
230
+ /// can record a new counterpart there. Its identifier and grade stay, so transceivers
231
+ /// keep bootstrapping to it and accepting its reports; removal must not strand
232
+ /// conduits. Cutting a chain off is `setSuspended`, which still applies after removal.
233
+ function removeChainKey(bytes32 chainKey) external onlyOwner {
234
+ if (!_chainKeys.remove(chainKey)) revert UnknownChainKey();
235
+ emit ChainKeyRemoved(chainKey);
236
+ }
237
+
238
+ /// @notice Refuse a chain, or stop refusing it.
239
+ /// @dev Every transceiver here reads this with the grade, in `_counterpartOn`, so a
240
+ /// suspended chain gets no bootstrap or report and is accepted as no origin. It can only
241
+ /// refuse, so lifting it is as safe as setting it. Conduits already standing on the
242
+ /// chain still send to each other directly. Applies to a removed chain too: any chain
243
+ /// ever registered can be cut off.
244
+ function setSuspended(bytes32 chainKey, bool suspended) external onlyOwner {
245
+ if (_chainIdentifier[chainKey].length == 0) revert UnknownChainKey();
246
+ _suspended[chainKey] = suspended;
247
+ emit SuspendedSet(chainKey, suspended);
248
+ }
249
+
250
+ /// @notice Whether transceivers refuse `chainKey`.
251
+ function isSuspended(bytes32 chainKey) external view returns (bool) {
252
+ return _suspended[chainKey];
253
+ }
254
+
255
+ /// @notice Register a message provider by name; the key is keccak256 of the name.
256
+ function addMessageProvider(string calldata name) external onlyOwner returns (bytes32 messageProvider) {
257
+ return _addMessageProvider(name);
258
+ }
259
+
260
+ function _addMessageProvider(string memory name) private returns (bytes32 messageProvider) {
261
+ if (bytes(name).length == 0) revert EmptyName();
262
+ messageProvider = keccak256(bytes(name));
263
+ if (_messageProviders.add(messageProvider)) {
264
+ _messageProviderName[messageProvider] = name;
265
+ emit MessageProviderAdded(messageProvider, name);
266
+ }
267
+ }
268
+
269
+ function removeMessageProvider(bytes32 messageProvider) external onlyOwner {
270
+ if (!_messageProviders.remove(messageProvider)) revert UnknownMessageProvider();
271
+ delete _messageProviderName[messageProvider];
272
+ emit MessageProviderRemoved(messageProvider);
273
+ }
274
+
275
+ /* ============================== configuration ============================== */
276
+
277
+ /// @notice Record the transceiver on this chain that serves one message provider. Zero
278
+ /// retires the provider's entry.
279
+ function setLocalTransceiver(bytes32 messageProvider, address transceiver_) external onlyOwner {
280
+ if (!_messageProviders.contains(messageProvider)) revert UnknownMessageProvider();
281
+
282
+ address prev = _localTransceiver[messageProvider];
283
+ if (prev != address(0)) delete _providerOfTransceiver[prev];
284
+
285
+ _localTransceiver[messageProvider] = transceiver_;
286
+ if (transceiver_ != address(0)) _providerOfTransceiver[transceiver_] = messageProvider;
287
+
288
+ emit LocalTransceiverSet(messageProvider, transceiver_);
289
+ }
290
+
291
+ /// @notice Record the CREATE2 inputs a provider's contracts share on every chain.
292
+ ///
293
+ /// @dev Write-once: changing the salt or initcode hash moves the provider's transceiver on
294
+ /// every chain and every conduit under it. The identical record again is a no-op.
295
+ ///
296
+ /// @dev Deploys nothing; it lets any chain reproduce the addresses. A record that does not
297
+ /// match the deployment yields predictions that match nothing.
298
+ function setProviderDeployment(bytes32 messageProvider, bytes32 salt, bytes32 proxyInitCodeHash)
299
+ external
300
+ onlyOwner
301
+ {
302
+ _setProviderDeployment(messageProvider, salt, proxyInitCodeHash);
303
+ }
304
+
305
+ function _setProviderDeployment(bytes32 messageProvider, bytes32 salt, bytes32 proxyInitCodeHash) private {
306
+ if (!_messageProviders.contains(messageProvider)) {
307
+ revert UnknownMessageProvider();
308
+ }
309
+ if (salt == bytes32(0)) revert ZeroSalt();
310
+ if (proxyInitCodeHash == bytes32(0)) revert ZeroInitCodeHash();
311
+
312
+ ProviderDeployment storage d = _deployment[messageProvider];
313
+ if (d.salt != bytes32(0)) {
314
+ if (d.salt != salt || d.proxyInitCodeHash != proxyInitCodeHash) revert AlreadySet();
315
+ return;
316
+ }
317
+
318
+ _deployment[messageProvider] = ProviderDeployment({salt: salt, proxyInitCodeHash: proxyInitCodeHash});
319
+ emit ProviderDeploymentSet(messageProvider, salt, proxyInitCodeHash);
320
+ }
321
+
322
+ function providerDeployment(bytes32 messageProvider) external view returns (ProviderDeployment memory) {
323
+ return _deployment[messageProvider];
324
+ }
325
+
326
+ /* ============================ deployer rotation ============================ */
327
+
328
+ /// @notice Change the deployer later `addProviderOnChain` calls must name.
329
+ /// @dev Touches no entry already recorded, so no existing address moves.
330
+ function setCurrentDeployer(address deployer) external onlyOwner {
331
+ _setCurrentDeployer(deployer);
332
+ }
333
+
334
+ function _setCurrentDeployer(address deployer) private {
335
+ if (deployer == address(0)) revert ZeroDeployedBy();
336
+ currentDeployer = deployer;
337
+ emit CurrentDeployerSet(deployer);
338
+ }
339
+
340
+ /// @notice Record that a provider's transceiver on `chainKey` is deployed by `expectedDeployer`,
341
+ /// `Pending` until `markLive`.
342
+ /// @dev Write-once. `expectedDeployer` must be `currentDeployer`, so a registry that applies
343
+ /// this before or after a rotation reverts instead of recording a different deployer
344
+ /// than its peers. The same deployer again is a no-op.
345
+ function addProviderOnChain(bytes32 chainKey, bytes32 messageProvider, address expectedDeployer)
346
+ external
347
+ onlyOwner
348
+ {
349
+ _addProviderOnChain(chainKey, messageProvider, expectedDeployer, Status.Pending);
350
+ }
351
+
352
+ function _addProviderOnChain(bytes32 chainKey, bytes32 messageProvider, address expectedDeployer, Status status)
353
+ private
354
+ {
355
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
356
+ if (!_messageProviders.contains(messageProvider)) revert UnknownMessageProvider();
357
+ if (_deployment[messageProvider].salt == bytes32(0)) revert NoProviderDeployment();
358
+ if (expectedDeployer == address(0)) revert ZeroDeployedBy();
359
+
360
+ ChainSupport storage s = _support[chainKey][messageProvider];
361
+ if (s.status != Status.None) {
362
+ if (s.deployedBy != expectedDeployer) revert AlreadySet();
363
+ return;
364
+ }
365
+ if (expectedDeployer != currentDeployer) revert DeployerMismatch(expectedDeployer, currentDeployer);
366
+
367
+ s.deployedBy = expectedDeployer;
368
+ s.status = status;
369
+ emit ProviderAddedOnChain(chainKey, messageProvider, expectedDeployer);
370
+ }
371
+
372
+ /// @notice Change the deployer a `Pending` entry is predicted under, for a chain added before
373
+ /// a rotation and not yet deployed.
374
+ /// @dev Never a `Live` entry: its transceiver exists and its address is fixed.
375
+ function rekeyPending(bytes32 chainKey, bytes32 messageProvider, address newDeployer) external onlyOwner {
376
+ ChainSupport storage s = _support[chainKey][messageProvider];
377
+ if (s.status != Status.Pending) revert NotPending(chainKey, messageProvider);
378
+ if (newDeployer == address(0)) revert ZeroDeployedBy();
379
+ s.deployedBy = newDeployer;
380
+ emit PendingRekeyed(chainKey, messageProvider, newDeployer);
381
+ }
382
+
383
+ /// @notice Allow sends to a provider's transceiver on `chainKey`.
384
+ /// @dev Call only after the deployment is checked without the deployer's help: code at the
385
+ /// predicted address, the implementation's code hash, and `owner()`, `chainRegistry()` and
386
+ /// `messageProvider()`. This contract cannot read another chain's code, so the check is
387
+ /// the proposers' and the timelock delay is its review window.
388
+ function markLive(bytes32 chainKey, bytes32 messageProvider) external onlyOwner {
389
+ ChainSupport storage s = _support[chainKey][messageProvider];
390
+ if (s.status != Status.Pending) revert NotPending(chainKey, messageProvider);
391
+ s.status = Status.Live;
392
+ emit MarkedLive(chainKey, messageProvider);
393
+ }
394
+
395
+ /// @notice Whether sends to `messageProvider`'s transceiver on `chainKey` are allowed.
396
+ function isLive(bytes32 chainKey, bytes32 messageProvider) external view returns (bool) {
397
+ return _support[chainKey][messageProvider].status == Status.Live;
398
+ }
399
+
400
+ /// @notice The state of `messageProvider` on `chainKey`.
401
+ function statusOf(bytes32 chainKey, bytes32 messageProvider) external view returns (Status) {
402
+ return _support[chainKey][messageProvider].status;
403
+ }
404
+
405
+ /// @notice Who deployed, or will deploy, `messageProvider`'s transceiver on `chainKey`; zero
406
+ /// where it was never added.
407
+ function deployedBy(bytes32 chainKey, bytes32 messageProvider) external view returns (address) {
408
+ return _support[chainKey][messageProvider].deployedBy;
409
+ }
410
+
411
+ /* ========================= PATH 2: salted derivation ======================= */
412
+
413
+ /// @notice Where a provider's transceiver lands on `chainKey`, recomputed from
414
+ /// `PROXY_DEPLOYER`, the deployer recorded for that chain, and the provider's
415
+ /// salt and initcode hash.
416
+ function predictTransceiver(bytes32 chainKey, bytes32 messageProvider) public view returns (address) {
417
+ ProviderDeployment memory d = _deployment[messageProvider];
418
+ if (d.salt == bytes32(0)) revert NoProviderDeployment();
419
+ _requireEvmDerivable(chainKey);
420
+ address by = _support[chainKey][messageProvider].deployedBy;
421
+ if (by == address(0)) revert NotOnChain(chainKey, messageProvider);
422
+ return AddressDerive.create2(PROXY_DEPLOYER, proxySalt(by, d.salt), d.proxyInitCodeHash);
423
+ }
424
+
425
+ /// @notice Where an owner's conduit lands on `chainKey`, before it exists: the
426
+ /// transceiver from the provider's salt, then the conduit from the transceiver.
427
+ /// @dev The conduit salt must match `TransceiverCore._conduitSalt`; it is written out
428
+ /// rather than imported, and `test/SaltedDeployment.t.sol` asserts the two agree.
429
+ /// @param homeChainKey The chain the conduit is homed on, which need not be `chainKey`.
430
+ function predictConduit(
431
+ bytes32 chainKey,
432
+ bytes32 messageProvider,
433
+ address owner,
434
+ bytes32 salt,
435
+ bytes32 homeChainKey
436
+ ) external view returns (address) {
437
+ ProviderDeployment memory d = _deployment[messageProvider];
438
+ if (d.salt == bytes32(0)) revert NoProviderDeployment();
439
+
440
+ address transceiver = predictTransceiver(chainKey, messageProvider);
441
+ return AddressDerive.create2(transceiver, keccak256(abi.encode(owner, salt, homeChainKey)), d.proxyInitCodeHash);
442
+ }
443
+
444
+ /// @dev Plain CREATE2 derivation holds only on a chain graded `Predetermined`.
445
+ function _requireEvmDerivable(bytes32 chainKey) private view {
446
+ if (!_isEvmDerivable(chainKey)) revert NoCounterpart();
447
+ }
448
+
449
+ function _isEvmDerivable(bytes32 chainKey) private view returns (bool) {
450
+ return uint8(provenanceFor(chainKey)) >= uint8(Provenance.Predetermined);
451
+ }
452
+
453
+ /// @notice What an address claim about `chainKey` is worth, as given at registration.
454
+ /// @dev Answers for a removed chain; reverts only for one never registered.
455
+ function provenanceFor(bytes32 chainKey) public view returns (Provenance) {
456
+ if (_chainIdentifier[chainKey].length == 0) revert UnknownChainKey();
457
+ return _provenanceOf[chainKey];
458
+ }
459
+
460
+ /// @notice Whether conduits on `chainKey` must report their own address home.
461
+ /// @dev True exactly where this contract cannot recompute addresses, so it cannot
462
+ /// disagree with the grades. Where a receiver landed is held by its transmitter, not
463
+ /// here. The home's side of `TransceiverExtension.addressesDiverge`.
464
+ function requiresReceiverCallback(bytes32 chainKey) external view returns (bool) {
465
+ return !_isEvmDerivable(chainKey);
466
+ }
467
+
468
+ /// @notice Check a location against everything this chain says about its addresses: the
469
+ /// ERC-7930 canonicity rules, that it is on `chainKey`, and the chain's validator.
470
+ /// @dev A transceiver calls this before recording a counterpart, so one validator per chain serves
471
+ /// every provider.
472
+ function validateLocation(bytes32 chainKey, bytes calldata interop) external view {
473
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
474
+ // `parseStrict` runs inside: rejects bad versions, length mismatches, trailing
475
+ // bytes, and non-minimal eip155 chain references.
476
+ if (Erc7930.chainKey(interop) != chainKey) revert UnknownChainKey();
477
+
478
+ IRefValidator v = _validatorOf[chainKey];
479
+ if (address(v) != address(0)) v.validateRef(interop);
480
+ }
481
+
482
+ /// @notice Attach a value-range validator to a chain.
483
+ function setValidator(bytes32 chainKey, IRefValidator validator) external onlyOwner {
484
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
485
+ _validatorOf[chainKey] = validator;
486
+ emit ValidatorSet(chainKey, address(validator));
487
+ }
488
+
489
+ /* ============================ Move qualifiers ============================= */
490
+
491
+ /// @notice Attach a qualified name to a provider's transceiver on a Move chain.
492
+ /// @dev Write-once: the same qualifier again is a no-op and a different one reverts
493
+ /// `AlreadySet`, since re-pointing a call target is re-pointing the counterpart.
494
+ function setQualifier(bytes32 chainKey, bytes32 messageProvider, Move.MoveQualifier calldata q) external onlyOwner {
495
+ bytes memory identifier = _chainIdentifier[chainKey];
496
+ if (identifier.length == 0) revert UnknownChainKey();
497
+ if (!_messageProviders.contains(messageProvider)) revert UnknownMessageProvider();
498
+ Move.validate(q, Erc7930.parseStrict(identifier).chainType);
499
+
500
+ bytes32 qh = Move.hash(q);
501
+ bytes memory existing = _qualifiers[chainKey][messageProvider];
502
+ if (existing.length != 0) {
503
+ if (Move.hash(abi.decode(existing, (Move.MoveQualifier))) != qh) revert AlreadySet();
504
+ return;
505
+ }
506
+ _qualifiers[chainKey][messageProvider] = abi.encode(q);
507
+ emit QualifierSet(chainKey, messageProvider, qh);
508
+ }
509
+
510
+ /// @notice The qualified name a destination executor needs to build the call.
511
+ function qualifier(bytes32 chainKey, bytes32 messageProvider) external view returns (Move.MoveQualifier memory q) {
512
+ bytes memory raw = _qualifiers[chainKey][messageProvider];
513
+ if (raw.length == 0) revert NoQualifier();
514
+ q = abi.decode(raw, (Move.MoveQualifier));
515
+ }
516
+
517
+ /* ========================== commitment preview ============================ */
518
+
519
+ /// @notice Teach this registry the primitive `chainKey`'s receiver hashes with.
520
+ /// @dev Rebindable: it redirects nothing, so a wrong primitive must be fixable. Zero
521
+ /// unregisters, and `commitmentFor` then reverts rather than answer wrongly.
522
+ function setCommitmentScheme(bytes32 chainKey, ICommitmentScheme scheme) external onlyOwner {
523
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
524
+ _commitmentSchemeOf[chainKey] = scheme;
525
+ emit CommitmentSchemeSet(chainKey, address(scheme));
526
+ }
527
+
528
+ /// @notice Preview the commitment `chainKey`'s receiver will require over `elements`.
529
+ /// @dev For `eth_call` by a signer checking a payload; nothing on-chain may enforce with
530
+ /// it. The fold is `SchemeFold`'s.
531
+ function commitmentFor(bytes32 chainKey, bytes[] calldata elements) external view returns (bytes32) {
532
+ ICommitmentScheme scheme = _commitmentSchemeOf[chainKey];
533
+ if (address(scheme) == address(0)) revert NoCommitmentScheme();
534
+ return SchemeFold.hashCalls(scheme, chainKey, elements);
535
+ }
536
+
537
+ /* ================= PATH 1 (uniform): per-chain derivation ================== */
538
+
539
+ /// @notice Point a chain at the contract that computes addresses on it.
540
+ function setDeriver(bytes32 chainKey, IVmDeriver deriver) external onlyOwner {
541
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
542
+ _deriverOf[chainKey] = deriver;
543
+ emit DeriverSet(chainKey, address(deriver));
544
+ }
545
+
546
+ /// @notice Store the derivation inputs for a chain, checked against its deriver now
547
+ /// rather than at the first resolve.
548
+ /// @param params abi.encode(VmDeriver.Scheme, bytes): shape is the scheme's business.
549
+ function setDeriveParams(bytes32 chainKey, bytes calldata params) external onlyOwner {
550
+ IVmDeriver d = _deriverOf[chainKey];
551
+ if (address(d) == address(0)) revert NoDeriver();
552
+
553
+ uint16 ct = Erc7930.parseStrict(_chainIdentifier[chainKey]).chainType;
554
+ (uint8 scheme,) = abi.decode(params, (uint8, bytes));
555
+ if (!d.supportsScheme(ct, scheme)) revert SchemeNotSupported();
556
+
557
+ _deriveParams[chainKey] = params;
558
+ emit DeriveParamsSet(chainKey, scheme, keccak256(params));
559
+ }
560
+
561
+ /// @notice The transceiver envelope expected on `chainKey`, recomputed now, whatever VM
562
+ /// that chain runs.
563
+ /// @dev A deriver is external code: without the chainKey re-check a wrong one could return
564
+ /// another chain's envelope for a transceiver to record as this chain's counterpart.
565
+ function expectedTransceiver(bytes32 chainKey) public view returns (bytes memory interop) {
566
+ IVmDeriver d = _deriverOf[chainKey];
567
+ if (address(d) == address(0)) revert NoDeriver();
568
+ bytes memory params = _deriveParams[chainKey];
569
+ if (params.length == 0) revert NoDeriveParams();
570
+
571
+ interop = d.deriveAddress(_chainIdentifier[chainKey], params);
572
+ if (Erc7930.chainKey(interop) != chainKey) revert DeriverChainMismatch();
573
+ }
574
+
575
+ /// @notice Every destination at once: the expected transceiver on each registered chain.
576
+ /// @dev Unconfigured or underivable chains yield an empty entry rather than reverting.
577
+ function expectedTransceivers() external view returns (bytes32[] memory keys, bytes[] memory interops) {
578
+ keys = _chainKeys.values();
579
+ uint256 n = keys.length;
580
+ interops = new bytes[](n);
581
+
582
+ for (uint256 i; i < n; ++i) {
583
+ if (address(_deriverOf[keys[i]]) == address(0)) continue;
584
+ if (_deriveParams[keys[i]].length == 0) continue;
585
+ // forge-lint: disable-next-line(calls-loop) a view self-call; failure is caught
586
+ try this.expectedTransceiver(keys[i]) returns (bytes memory io) {
587
+ interops[i] = io;
588
+ } catch {
589
+ // Leave empty: an underivable chain is expected, not exceptional.
590
+ }
591
+ }
592
+ }
593
+
594
+ /// @notice The stored derivation inputs for a chain.
595
+ function deriveParams(bytes32 chainKey) external view returns (bytes memory) {
596
+ return _deriveParams[chainKey];
597
+ }
598
+
599
+ /* ============================ configuration reads ========================== */
600
+
601
+ /// @notice The transceiver on this chain that serves `messageProvider`.
602
+ function localTransceiver(bytes32 messageProvider) external view returns (address) {
603
+ return _localTransceiver[messageProvider];
604
+ }
605
+
606
+ /// @notice Which provider a transceiver on this chain serves.
607
+ function providerOfTransceiver(address transceiver_) external view returns (bytes32) {
608
+ return _providerOfTransceiver[transceiver_];
609
+ }
610
+
611
+ function deriverOf(bytes32 chainKey) external view returns (IVmDeriver) {
612
+ return _deriverOf[chainKey];
613
+ }
614
+
615
+ function validatorOf(bytes32 chainKey) external view returns (IRefValidator) {
616
+ return _validatorOf[chainKey];
617
+ }
618
+
619
+ function commitmentSchemeOf(bytes32 chainKey) external view returns (ICommitmentScheme) {
620
+ return _commitmentSchemeOf[chainKey];
621
+ }
622
+
623
+ /* ============================== directory reads ============================ */
624
+
625
+ function chainKeyCount() external view returns (uint256) {
626
+ return _chainKeys.length();
627
+ }
628
+
629
+ function chainKeyAt(uint256 i) external view returns (bytes32) {
630
+ return _chainKeys.at(i);
631
+ }
632
+
633
+ function chainKeys() external view returns (bytes32[] memory) {
634
+ return _chainKeys.values();
635
+ }
636
+
637
+ function hasChainKey(bytes32 chainKey) external view returns (bool) {
638
+ return _chainKeys.contains(chainKey);
639
+ }
640
+
641
+ /// @notice The canonical ERC-7930 chain identifier a `chainKey` hashes from.
642
+ function chainIdentifier(bytes32 chainKey) external view returns (bytes memory) {
643
+ if (!_chainKeys.contains(chainKey)) revert UnknownChainKey();
644
+ return _chainIdentifier[chainKey];
645
+ }
646
+
647
+ function messageProviderCount() external view returns (uint256) {
648
+ return _messageProviders.length();
649
+ }
650
+
651
+ function messageProviderAt(uint256 i) external view returns (bytes32) {
652
+ return _messageProviders.at(i);
653
+ }
654
+
655
+ function messageProviders() external view returns (bytes32[] memory) {
656
+ return _messageProviders.values();
657
+ }
658
+
659
+ function hasMessageProvider(bytes32 messageProvider) external view returns (bool) {
660
+ return _messageProviders.contains(messageProvider);
661
+ }
662
+
663
+ function messageProviderName(bytes32 messageProvider) external view returns (string memory) {
664
+ if (!_messageProviders.contains(messageProvider)) revert UnknownMessageProvider();
665
+ return _messageProviderName[messageProvider];
666
+ }
667
+ }