// SPDX-License-Identifier: MIT pragma solidity 0.8.24; interface IERC20 { function balanceOf(address account) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); } /// @dev The one thing a deposit contract asks its factory: which token is being swept right now. interface IVestaDepositFactory { function sweepToken() external view returns (address); } /// @title VestaDeposit /// @notice One invoice, one address. This contract is deployed by /// VestaDepositFactory at an address derived from the invoice id, and /// its whole life happens inside the constructor: it splits whatever /// token balance is sitting at that address between the merchant and /// the Vesta fee wallet, then deletes itself so the same address can be /// used again if a further payment arrives. /// @dev There is no private key for this address and no other code can ever /// run at it: the destination addresses are baked into the CREATE2 /// init code, so the address itself commits to where the money goes. /// The token is deliberately NOT part of the init code — it is read from /// the factory at sweep time — so the same deposit address exists on /// every chain (see VestaDepositFactory) and a payment sent on the wrong /// network, or in the wrong token, can still be forwarded to the merchant. /// Stray ETH is forwarded to the merchant as well. contract VestaDeposit { constructor(address merchant, address feeWallet, uint16 feeBps) { address token = IVestaDepositFactory(msg.sender).sweepToken(); uint256 gross = IERC20(token).balanceOf(address(this)); uint256 fee = (gross * feeBps) / 10_000; uint256 merchantAmount = gross - fee; if (merchantAmount > 0) _send(token, merchant, merchantAmount); if (fee > 0) _send(token, feeWallet, fee); // Created and destroyed in the same transaction, so the account is // cleared (EIP-6780) and the address can be reused for a later payment. selfdestruct(payable(merchant)); } function _send(address token, address to, uint256 value) private { (bool ok, bytes memory data) = token.call( abi.encodeWithSelector(IERC20.transfer.selector, to, value) ); require(ok && (data.length == 0 || abi.decode(data, (bool))), "VestaDeposit: transfer failed"); } }