Chapter 4: Payments and Streaming
This chapter covers how money moves from the org’s Safe to employees and vendors. It brings together the on-chain infrastructure from Chapter 2 with the financial document layer from Chapter 7 and the approval flows that bridge them.
Two Payment Mechanisms
Section titled “Two Payment Mechanisms”Streaming Payments (LlamaPay)
Section titled “Streaming Payments (LlamaPay)”For employee salaries. LlamaPay creates a continuous USDC stream from the Safe to the employee’s smart account. The stream accrues per-second. The employee claims accumulated funds on their own schedule.
Creation: Org admin calls createStream on LlamaPay via Zodiac Roles (“payroll_admin” role). The stream targets the employee’s smart account address (computed counterfactually before deployment).
Claiming: Employee calls withdraw on LlamaPay, either manually via the Openfort SDK or automatically via session key. Accrued USDC transfers from LlamaPay to the smart account.
Cancellation: Org admin or treasury_ops role calls cancelStream. Accrued-but-unclaimed funds remain available to the employee. Unstreamed funds return to the Safe automatically.
Discrete Payments (Payment Module)
Section titled “Discrete Payments (Payment Module)”For invoices, one-off transfers, off-ramp sends, and bridge sends. The Payment Module executes a single ERC20 transfer from the Safe.
Execution: An authorized caller (via Zodiac Roles) calls executePayment(token, recipient, amount, documentHash, paymentType). The module tells the Safe to transfer tokens and emits PaymentExecuted.
Document linking: The documentHash parameter creates the on-chain anchor for the financial document that authorized this payment. See On-Chain Anchoring.
On-Chain Anchoring
Section titled “On-Chain Anchoring”Every payment through the Safe includes a bytes32 document hash that links the on-chain transaction to the off-chain financial record. This creates a bidirectional link:
- Off-chain to on-chain. The Convex document stores the transaction hash after payment confirms. Anyone on the platform can verify “this invoice was paid by transaction 0xabc.”
- On-chain to off-chain. The on-chain event contains the document hash. Anyone inspecting the chain can verify “this payment corresponds to document with hash 0xdef” and can independently recompute the hash.
Hash Computation
Section titled “Hash Computation”The document hash is computed from a canonical subset of the document’s fields, serialized deterministically:
- Document ID, type, amount (USDC, raw integer), recipient address, issuer (org) address, timestamp of creation, line items (serialized with sorted keys)
The canonical format is JSON with sorted keys and no whitespace. The hash function is keccak256.
Why a canonical subset? Fields like status, fiat equivalent, and notes may change or are not relevant to the payment’s identity. The hash represents “what was agreed to be paid, to whom, for what” — the core financial facts.
Implementation
Section titled “Implementation”The document hash is passed as a parameter to the Safe module’s execution function. The module emits an event containing the hash alongside the payment details. No separate anchoring contract is needed. No IPFS. The hash rides alongside the payment transaction that is already happening. Cost: negligible (one extra bytes32 in calldata).
Protocol Optionality
Section titled “Protocol Optionality”This approach is designed to be protocol-capable without committing to being a protocol. The canonical document format is deterministic. The on-chain representation is generic (a bytes32). If Capxul later publishes the document format as an open standard, nothing in the architecture needs to change.
Claim Flow
Section titled “Claim Flow”The claim flow is the core employee interaction. It is the moment the employee converts accrued compensation into liquid funds.
Manual Claim
Section titled “Manual Claim”- Employee taps “Claim” in the Capxul app
- Openfort SDK constructs a UserOperation:
- First claim includes
initCodeto deploy the smart account (counterfactual deployment) - Calls LlamaPay’s
withdraw()function
- First claim includes
- Submitted via bundler, gas sponsored via paymaster
- Smart account deploys (first time only), claim executes
- Accrued USDC transfers from LlamaPay to the smart account
- The indexer detects the withdrawal event and creates a claim receipt in
financialDocuments - If auto-routing is enabled, the routing flow triggers (see Chapter 3: Routing Logic)
Automated Claim (Future)
Section titled “Automated Claim (Future)”The session key can also be scoped to LlamaPay for automated claiming on a schedule. This is architecturally supported but deferred until user demand is clear.
Spending Limits and Approval Flows
Section titled “Spending Limits and Approval Flows”How Spending Limits Work
Section titled “How Spending Limits Work”Spending limits define how much an approver can authorize in a single payment. They are a business rule, not a technical constraint.
Example: A junior admin can approve invoices up to 5,000 USDC. Above that requires a senior admin. Above 20,000 USDC requires the org owner.
Layered Enforcement
Section titled “Layered Enforcement”Primary enforcement (Convex). The approval flow in Convex checks the approver’s spending limit before submitting the on-chain transaction. If the invoice exceeds their limit, it escalates to a higher-authority approver in Convex. The transaction is never submitted on-chain. This handles the UX: escalation, multi-approver queuing, and notifications.
Safety net (Zodiac Roles). The Roles Modifier enforces the spending limit on-chain. If the Convex enforcement is bypassed (bug, direct contract call), the Roles Modifier rejects the transaction. This is the cryptographic guarantee.
Invoice Payment Flow
Section titled “Invoice Payment Flow”- Vendor submits invoice (or org creates one on vendor’s behalf)
- Invoice enters
submittedstatus infinancialDocuments - An approved spender reviews the invoice (
under_review) - If within their spending limit: they approve, the invoice advances to
approved, and the on-chain transaction is submitted automatically - If over their limit: the invoice escalates to a higher approver in Convex
- Once approved and submitted, the Payment Module executes on-chain
- Invoice advances to
payment_pending(tx submitted, awaiting confirmation) - The indexer detects
PaymentExecuted, matches by document hash, transitions topaid - A receipt is auto-generated in
financialDocuments
Fiat Payment Variant
Section titled “Fiat Payment Variant”When an org pays a fiat-only vendor, the flow diverges at step 4: instead of the Payment Module sending USDC to the vendor’s smart account, the backend initiates a fiat transaction (see Chapter 5: Org Off-Ramp). The provider deposit address receives the USDC, and the off-ramp provider converts and pays the vendor’s bank account.
Bridge Payment Variant
Section titled “Bridge Payment Variant”When a recipient provides a wallet address on another chain, the flow diverges similarly: the Payment Module sends USDC to the bridge contract, and the bridge provider delivers on the destination chain (see Chapter 6).
Stream-to-Payslip Reconciliation
Section titled “Stream-to-Payslip Reconciliation”Payment streams are continuous. Payslips are discrete. Reconciling one with the other requires clear rules.
Pay Period Configuration
Section titled “Pay Period Configuration”Each employee’s pay period is configured by the org: monthly, bi-weekly, or custom intervals. The system uses this to determine when to auto-generate payslips.
Payslip Generation Logic
Section titled “Payslip Generation Logic”At the end of each configured pay period, the system generates a payslip by reading the stream’s state:
- Gross accrued. Stream rate multiplied by period duration. Total compensation regardless of whether funds were claimed.
- Total claimed during period. Sum of all claim transactions with timestamps within the period boundaries.
- Unclaimed balance at period end. Gross accrued minus total claimed.
- Fiat equivalent. Calculated once at generation time using a single exchange rate snapshot, not a weighted average.
Worked Example
Section titled “Worked Example”Employee earns 3,000 USDC/month. Monthly pay period. Two claims during March.
| Event | Date | Amount | Running State |
|---|---|---|---|
| Stream accruing | Mar 1-31 | 3,000 USDC total | — |
| Claim #1 | Mar 12 | 800 USDC | Receipt generated |
| Claim #2 | Mar 23 | 600 USDC | Receipt generated |
| Payslip generated | Mar 31 | — | See below |
March payslip reads:
- Gross accrued: 3,000 USDC
- Total claimed: 1,400 USDC
- Unclaimed balance: 1,600 USDC
- Fiat equivalent: 4,800,000 NGN (at generation-time rate of 1,600 NGN/USDC)
- Document hash: keccak256 of canonical fields
- Associated receipts: links to the two claim receipts
Edge Cases
Section titled “Edge Cases”Employee never claims. Payslip still generates showing full accrual and zero claims. The compensation is real — it is accruing in the smart account.
Claims spanning period boundaries. A claim on April 2 is attributed to the April payslip, even if the funds accrued in March. The March payslip’s unclaimed balance remains as recorded at March 31. Payslips are immutable snapshots of state at period end. They do not retroactively update.
Stream rate changes mid-period. The payslip reflects the blended accrual. If the rate was 3,000 USDC/month for the first half and 3,600 USDC/month for the second half, the payslip shows approximately 3,300 USDC with line items breaking down each rate segment.
Amended payslips. If a payslip is incorrect, a new amendment document is created referencing the original via a supersedes field. The original remains with its hash intact. The amendment becomes the current version.