SMART CONTRACTS
TabRouter
TabRouter is the contract that settles ordinary Tab payments. A buyer signs an EIP-712 message; the contract verifies it, pulls the funds, sends them to the seller, and takes the 1% fee.
Constants
PLATFORM_FEE_BPS = 100— 1% in basis points.BPS_DENOMINATOR = 10_000PAYMENT_TYPEHASH= keccak256 of"Payment(address from,address to,uint256 amount,uint256 fee,uint256 nonce,uint256 deadline)"
State
IERC20 STABLE— the stablecoin for this deployment (immutable).address platformTreasury— fee recipient (owner-mutable).mapping(address => mapping(uint256 => bool)) usedNonces— replay-protection set.uint256 totalVolume, totalFeesCollected— lifetime accumulators.
The settlement function
function relayPayment(
address from,
address to,
uint256 amount,
uint256 fee,
uint256 nonce,
uint256 deadline,
bytes calldata signature
) external nonReentrant;The function performs, in order:
- Reject zero addresses, zero amounts, and a wrong fee (
fee != calculateFee(amount)). - Reject expired or replayed signatures.
- Reconstruct the EIP-712 digest and recover the signer.
- Mark the nonce consumed and bump the counters.
safeTransferFromamount to the recipient and fee to the treasury.- Emit
PaymentRelayed(from, to, amount, fee, nonce, digest).
Events
event PaymentRelayed(
address indexed from,
address indexed to,
uint256 amount,
uint256 fee,
uint256 nonce,
bytes32 indexed digest
);
event TreasuryUpdated(address indexed oldTreasury, address indexed newTreasury);Errors
Custom errors keep gas down on revert paths: InvalidAmount, InvalidFee, ZeroAddress, ExpiredDeadline, NonceAlreadyUsed, InvalidSignature.
Building the signature client-side
import { ethers } from "ethers";
const domain = {
name: "Tab Router",
version: "1",
chainId,
verifyingContract: ROUTER_ADDRESS,
};
const types = {
Payment: [
{ name: "from", type: "address" },
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "fee", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" },
],
};
const value = { from, to, amount, fee, nonce, deadline };
const signature = await signer.signTypedData(domain, types, value);Who can call relayPayment?
Anyone. The signature is the authorization. If a Tab relayer is unreachable, the buyer (or anyone they hand the signed blob to) can submit the transaction directly. The router treats all callers identically — only the signer matters.