Skip to main content

Toolchain

Per-chain configuration lives in contracts/config/chains.json. The deploy script is contracts/script/Deploy.s.sol.

Deployment rules

vm.startBroadcast deploys through the canonical CREATE2 factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C, inherited from forge-std, not through the EOA. Predict addresses using the factory, never vm.addr(key).
  • The salt is keccak256("cryptocheckout.v1"), pinned permanently.
  • Init code must be byte-identical across EVM chains so a merchant’s pool address is the same everywhere.
  • TRON uses Create2FactoryTron and the 0x41 prefix.
Mainnet deploys require explicit human GO, per chain. This is guardrail number one. No agent or automation may broadcast to a mainnet.

Foundry gotchas

These have each cost real time.
  • vm.parseJsonAddress reverts on null, and cheatcode reverts are not try/catchable. Use vm.keyExistsJson plus a length check instead.
  • Foundry 1.5+ treats address(this) inside a Script contract as a fatal error.
  • Deploy.t.sol writes test fixtures into contracts/deployments.json and contracts/deployments/. The per-chain directory is gitignored; the merged top-level file is not.
Always run git checkout -- contracts/deployments.json after forge test. A polluted copy breaks _shared/contracts.ts and the /verify page at runtime. CI fails if it is dirty.

Invariants to preserve

Any change must keep all of these true.
No admin, no upgrade path, no pause. The custody claim depends on it entirely.
Named the regulatory linchpin. Any gate on it — including a debt check — converts a bounded fee into a withholding power and undoes the non-custody position.
Late payments to spent addresses are an expected event, not an edge case. One-shot designs turn them into permanent loss.
Never an immutable. Making it an immutable would make the forwarder address token-specific and break currency-agnostic acceptance.
Any fee or reimbursement carved out must come from the merchant share, never off the top, or the partner’s 0.25% silently shrinks.
This is what makes client-side re-derivation a real check rather than a formality.

Testing

Fork tests skip when their environment variables are absent, so a green local run does not mean the fork suite ran. Any change to address derivation must re-run the full fork and invariant suite and verify a real deploy-and-sweep on Base Sepolia before it is considered done.

Commit discipline

  • Red-test commit before green-implementation commit, as separate commits, never collapsed.
  • Subject patterns: feat(ws<N>) Phase X: <what>, test(ws<N>) Phase X: <what>, fix(ws<N>) <FINDING-ID>: <what>, docs(...): <what>.
  • Never --no-verify. Never amend after a hook failure — investigate the failure.

Branching

main is the single source of truth and the production line. Pushing to main auto-deploys production, so never push directly. Every change lands via a pull request against main; the PR is the deploy gate. Short-lived branches are feat/<name>, fix/<name>, or docs/<name>, deleted on merge. Use git worktrees for isolation, branching from main. There is no integration branch.