Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.remlo.xyz/llms.txt

Use this file to discover all available pages before exploring further.

PayrollTreasury is the on-chain vault that holds employer payroll capital on Tempo. Funds enter via deposit, sit under per employer accounting, and exit via PayrollBatcher (or, for emergency recoveries, via sweepUnaccounted).

How funds enter

The dashboard’s on-chain deposit widget is the canonical path. The widget is at components/treasury/OnChainDepositWidget.tsx. It signs an ERC-20 approve for the deposit amount, then calls PayrollTreasury.deposit(amount, memo) from the employer’s Privy embedded wallet. The deposit memo carries identity. The first 8 bytes must equal bytes8(keccak256(employerAdminWallet)). The contract enforces this at deposit time and reverts with MemoEmployerMismatch if the prefix doesn’t match the msg.sender’s expected employer ID. Bytes 8-31 are reserved for future pay period and cost center metadata.
function deposit(uint256 amount, bytes32 memo) external nonReentrant {
    bytes8 expected = bytes8(keccak256(abi.encodePacked(msg.sender)));
    if (bytes8(memo) != expected) revert MemoEmployerMismatch();
    // ... pulls amount via SafeTIP20.transferFromWithMemo, credits employer balance
}
This is audit fix M-1. Before the fix, an attacker could forward a tiny dust deposit with a forged memo and confuse off-chain accounting into crediting another employer’s books.

Per employer accounting

Every employer holds a EmployerAccount struct in storage:
struct EmployerAccount {
    uint256 balance;
    uint256 lockedBalance;  // locked while a batch is being prepared
    uint256 policyId;       // optional TIP-403 binding
}
PayrollBatcher calls transferToBatcher(employerId, total) (only callable by the configured Batcher address) which decrements the employer’s balance and transfers via SafeTIP20. Idle balance can be routed through YieldRouter if the employer has yield enabled and the strategy passes the audit allow list (fix H-5).

Aggregate accounting (audit fix C-1)

totalAccountedPayToken tracks the sum of all employer balances. The owner-only sweepUnaccounted function transfers any tokens above this aggregate to a recovery address. Before the fix, a buggy front end or accidental token send to the contract was unrecoverable. After the fix, dust is sweepable without touching legitimate employer balances.

Events

  • Deposited(employerId indexed, amount, memo) on every successful deposit.
  • Unlocked(employerId indexed, amount) when locked funds release back to free balance after a batch (audit fix M-5: previously this was silent; now there’s an audit trail).
  • BatcherSet(oldBatcher, newBatcher) on owner only setBatcher.
  • Swept(token, to, amount) on sweepUnaccounted.

Live deployment

Tempo Moderato (chainId 42431):
0xEC73B9762b13148C54De792d70a2DB48690fD1F7
View on explorer. The currency it holds is USDC.e at 0x20C000000000000000000000b9537d11c60E8b50. 6 decimals. All amounts in the Treasury and Batcher are atomic units (e.g., 1000000 is one dollar).

Why on-chain custody

If Remlo held customer funds in a centrally controlled wallet, a key compromise would expose every customer’s payroll. Custody on a per employer accounting contract means an attacker can drain at most one employer (and only if they compromise that employer’s specific admin wallet). Funds are the trust boundary. Remlo is a software layer.