Writing
Build log2026-04-12 · 10 min

Letting a Payroll Protocol Sign Without Holding Keys (Remlo on Tempo + Solana)

Payroll automation has a key-custody problem. Employers want the protocol to run payouts on their behalf — but they can't hand over the wallet that funds it. Here's the architecture I shipped for Remlo: policy-gated server wallets, scoped instruction whitelists, and permissionless settlement that holds under a fully compromised server.

The constraint

Remlo runs payroll and agent payments across Tempo Moderato (chain ID 42431) and Solana devnet. Three primitives — payroll, escrow, and reputation — share one custody model: the protocol executes, but the protocol does not custody the funds. The first version is live today.

The hard requirement: an employer should be able to fund a treasury on Monday and have payroll auto-execute every Friday without ever re-signing. But Remlo, the operator, must never be able to drain that treasury — even if our infrastructure is fully compromised.

Threat modelAssume the Remlo backend is fully compromised, our signing service is rooted, and an attacker has every secret in our environment. What can they do? The answer needs to be: not much.

The architecture

Privy server wallets, policy-gated per instruction

On both chains, the Remlo server signs through Privy server wallets rather than raw keys. The wallets are configured with policies that whitelist exactly the contract addresses and instructions they're allowed to call. Anything else — a transfer to an attacker address, a call to a different program — gets rejected at the wallet layer, not by us.

On the Solana escrow program (remlo_escrow, deployed at 2CY3JQfkXpyTT8QBiHfKnashxGJ37ctDvqcgi7ggWiAA), only post_verdict requires our privileged signer. The three settlement instructions — settle, refund, expired_refund — are permissionless. The PDA holds the funds, and the program rejects any settle that doesn't match the recorded verdict.

Permissionless settlement is the load-bearing piece

Even if the Remlo signer never posts the verdict, the escrow doesn't freeze. If validators are configured for multi-party consensus and the rule resolves, anyone can broadcast the verdict transaction. If the validator deadline passes, expired_refund returns funds to the requester permissionlessly. There is no path where our signer holds funds hostage.

The same model on Tempo

On Tempo, the PayrollBatcher contract takes one signed batch and atomically distributes USDC.e to N employees. The treasury contract (PayrollTreasury) only releases funds to the batcher, and the batcher only accepts batch instructions from the employer's configured signer. ERC-8004 feedback writes on payment settlement go through the same Privy server wallet — which is policy-gated to the ReputationRegistry contract only.

What this gets you, in customer terms

  • Compliance: the employer can prove their funds were never accessible to Remlo. Every payment carries a 32-byte ISO 20022 memo (messageType, employerId, employeeId, payPeriod) so compliance pipelines can index without parsing tx logs.
  • Auditability: every settled payment writes an ERC-8004 giveFeedback entry on Tempo or one of four SAS schemas on Solana. The protocol's actions are cryptographically traceable.
  • Agent payment: the same execution rail powers /api/mpp/agent/pay. An external agent pays $0.05 USDC and Remlo broadcasts a recipient transfer from a named employer's treasury — capped by the per-transaction and per-day spend limits the employer set in advance. The agent never touches the employer's key.

The three-chain 402 challenge

Agents talking to Remlo see a single 402 response listing every payment rail in parallel: Tempo MPP, Base x402, Solana x402. Whatever wallet the calling agent has, one of the three is probably it.

HTTP/1.1 402 Payment Required
WWW-Authenticate: Payment realm="www.remlo.xyz", method="tempo",
                      chainId="42431", currency="0x20C00000...",
                      recipient="0xC9231...", amount="0.01"
Content-Type: application/json

{
  "x402Version": 2,
  "accepts": [
    { "scheme": "exact", "network": "eip155:8453",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "10000", "payTo": "0xC9231..." },
    { "scheme": "exact", "network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
      "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "amount": "10000", "payTo": "BxoTaz3..." }
  ]
}

One subtlety I had to learn the hard way: state-mutating endpoints that touch Tempo treasury balances stay Tempo-only. Accepting payment in Base USDC for a Tempo state mutation creates a settlement asymmetry that doesn't roll back cleanly if the on-chain action reverts. The 402 challenge for payroll execute and fiat off-ramp only lists Tempo.

What I'd revisit

  • [What I'd revisit on signing — e.g., dropping Privy entirely for an Anchor multisig pattern on Solana? Fill from your real iteration notes.]
  • [The fire-and-forget settlement pattern after the response goes out — what failure mode bit you?]
  • [Cross-chain reputation reconciliation between ERC-8004 and SAS — current gap, future direction.]

For engineers building similar things

If you're writing a protocol that acts on someone else's behalf, the question to lead with isn't which chain or which stablecoin. It's: what does my customer's threat model look like when my entire infrastructure is compromised? If the answer involves the words “monitoring” or “rotation,” you don't have a security model — you have an incident playbook. Push the trust boundary down into the contract.