contracts/src/. Audit brief: contracts/AUDIT_BRIEF.md.
The pool rails — the default path
MerchantPool
One per merchant per chain. Immutable and ownerless: no admin, no upgrade, no pause. Constructor arguments include recipient, treasury, partner, and the basis-point splits, so all of them are part of the CREATE2 preimage.Deposited(paymentId, token, payer, amount), Distributed(...), Claimed(token, account, amount).
No
receive, no fallback, no token hook. A plain ERC-20 transfer into the pool does not execute pool code; the balance is read at distribute time. This is the single most consequential fact about the contract.distribute() carves the 50 bps distributor reward from the merchant share at MerchantPool.sol:203.
InvoiceForwarder
Per-invoice, at a CREATE2 address salted onpaymentId. Init code commits the destination pool.
Sweep-only. No refund(), no window, no Refunded event. Remains callable after the first sweep so late payments are recoverable — an earlier refunded[token] latch was removed precisely because it could strand funds.
MerchantRegistry
Roughly 30 lines. The trust contract. Ownerless andmsg.sender-scoped, so a merchant attests their own payout recipient and nobody else can.
effectiveRecipient(address) is identity-default — it returns the merchant themselves when no explicit recipient is set, which means even an unattested merchant verifies correctly.
TRON variants
MerchantPoolTron, InvoiceForwarderTron, RouterFeeTron, InvoiceSplitterTron, SplitterFactoryTron, and Create2FactoryTron.
Create2FactoryTron exists because TRON has no canonical Arachnid factory. The 0x41 derivation prefix is the other TRON-specific difference. See TRON.
Legacy and Phase 2
RouterFee
Roughly 470 lines. The original atomic per-payment splitter. No longer the default path and dropped from all merchant-facing reads, but still deployed.maxIn invariant: if liquidity is thin or price slips, the swap reverts and the reentrancy guard unwinds the whole transaction atomically.
Never redeploy
RouterFee or MerchantRegistry at new addresses without a migration plan. Existing attestations and integrations point at the current ones.InvoiceSplitter
The per-invoice CREATE2 splitter from the pre-pool design. Superseded by the pool model.Design constraints that must not change
Rejected architectures
Evaluated in full and rejected. Do not re-propose without reading the reasoning.
The clone’s extra 10,115 gas over constructor-sweep buys perpetual recoverability. That is cheap insurance.
Toolchain
- solc 0.8.24, Cancun EVM,
via_iron, optimizer runs 1,000,000 - OpenZeppelin v5.1.0 and forge-std v1.9.4, pinned via submodules
- Roughly 96 unit and invariant tests plus 25 fork tests that skip without environment configuration