Skip to main content
Every address in the system is computed before it exists. Getting this wrong strands funds permanently, so the rules are strict.

The inputs

Because the pool’s payout configuration is part of its init code, the pool address commits to where the money goes. That is what makes client-side re-derivation meaningful.

Fixed constants

The CREATE2 salt is keccak256("cryptocheckout.v1"), pinned forever. A new salt is a new protocol.
Deployment goes through the canonical CREATE2 factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C, inherited from forge-std, not via the deployer EOA. Predict using the factory address, never vm.addr(key). TRON has no canonical Arachnid factory, so it uses Create2FactoryTron.

Same address across seven EVM chains

The init code is byte-identical across EVM chains, so a merchant’s pool has the same address on all seven. This is a product property, not an accident, and it constrains design:
  • Anything that makes init code chain-dependent breaks it.
  • A clone would embed the implementation address, making cross-chain identity contingent on that implementation landing at a byte-identical address everywhere. This is one of the four reasons the pool is not cloned.
TRON derives separately because of the 0x41 prefix.

Three predictors that must agree

The same derivation exists three times and all three must produce identical bytes: The browser port is pinned to the Solidity fixture vectors by test. It has to be — it is the load-bearing check in the trust model, and a drifted port would either block legitimate payments or fail to catch a tampered one. Update all three in lockstep. Never one alone.

Init-code changes are migrations

Every init-code change moves every CREATE2 address. Any in-flight invoice quoted under the old scheme must drain before cutover, or its customer pays to an address the new predictor no longer recognises.
The plan for the upcoming change is a single cutover bundling everything that touches init code:
  • Forwarder clone (EIP-1167)
  • Strip refund() from the forwarder
  • Strip the reward code from the pool
  • Add the gas-reimbursement parameter to the pool
Three separate migrations would be three chances to strand a customer payment. Each needs either a quote-freeze window or a versioned predictor.

The TRON gate

Re-prove the 0x41 derivation on Nile for any new init code before an address is shown as a QR. A prefix or bytecode mismatch strands funds with no recovery. This is a hard gate, not a checklist item.
It was proven on 2026-06-19 for the current init code: predicted equalled deployed for MerchantPoolTron and InvoiceForwarderTron, with a full pay, sweep, distribute, and claim cycle netting 99% through Create2FactoryTron. That proof does not carry over to changed bytecode.

Practical Foundry notes

  • vm.parseJsonAddress reverts on null, and cheatcode reverts are not catchable. Use vm.keyExistsJson plus a length check.
  • Foundry 1.5+ treats address(this) in a Script contract as fatal.
  • Deploy.t.sol writes fixtures into contracts/deployments.json. Always git checkout -- contracts/deployments.json after running tests — a polluted copy breaks the shared contracts module and the /verify page at runtime. CI fails if it is dirty.