Toolchain
Per-chain configuration lives in
contracts/config/chains.json. The deploy script is contracts/script/Deploy.s.sol.
Deployment rules
- 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
Create2FactoryTronand the0x41prefix.
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.parseJsonAddressreverts onnull, and cheatcode reverts are not try/catchable. Usevm.keyExistsJsonplus a length check instead.- Foundry 1.5+ treats
address(this)inside aScriptcontract as a fatal error. Deploy.t.solwrites test fixtures intocontracts/deployments.jsonandcontracts/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.The pool is immutable and ownerless
The pool is immutable and ownerless
No admin, no upgrade path, no pause. The custody claim depends on it entirely.
distribute() is permissionless
distribute() is permissionless
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.
The forwarder stays re-callable
The forwarder stays re-callable
Late payments to spent addresses are an expected event, not an edge case. One-shot designs turn them into permanent loss.
token is a runtime argument
token is a runtime argument
Never an immutable. Making it an immutable would make the forwarder address token-specific and break currency-agnostic acceptance.
Treasury and partner are computed on gross
Treasury and partner are computed on gross
Any fee or reimbursement carved out must come from the merchant share, never off the top, or the partner’s 0.25% silently shrinks.
Init code commits the payout configuration
Init code commits the payout configuration
This is what makes client-side re-derivation a real check rather than a formality.
Testing
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.