Payments
Contractor payroll and payouts
Payroll lets an employer keep a contractor roster, draft a monthly pay run in USDC and authorise each line from their own wallet. FurlPay screens recipients, issues the data to sign and reads the result back from the chain. Payroll is non-custodial: FurlPay never holds the payroll funds and never signs the transfer.
Status: implemented, execution blocked by an external dependency
FurlPayRouter contract deployed on the run's network and its address configured. No router address is configured in production, so production cannot issue a signable pay run. The router contract has not been independently audited.Contractor roster
Each contractor record holds a name, email, optional role, two-letter country code, payout network, payout wallet address and a default amount in USDC.
- Networks: Arbitrum, Arc, Base and Polygon. Payout addresses are EVM addresses.
- CSV import: validation is all-or-nothing. Any invalid row imports nothing and every error is listed. Emails already on the roster are skipped, never overwritten.
- Recipients come from the roster. A pay run can only pay addresses the employer saved beforehand, never an address supplied in the run request.
Contractor self-service
- The employer sends an invitation: a single-use link valid for seven days. Only a hash of the token is stored, and the token travels in the URL fragment so it does not reach server logs.
- The contractor accepts while signed in with the same email as the roster entry. That links their account.
- The contractor sets their own payout address by signing a registration message with that address (EIP-191). The address is screened first; a flagged address or a screening outage keeps the current address.
- Once a contractor has linked their account, their identity and the address their wallet proved can no longer be changed by the employer.
A pay run, step by step
1. Draft and screen
A run names a cycle (YYYY-MM), one network and 1 to 100 roster lines. Creating it screens every recipient. If any recipient is flagged, or could not be screened, the whole run is blocked until the employer cancels it or excludes those recipients with a written note. Excluded recipients are recorded, never cleared.
2. Submit
Submitting turns the draft into one payment intent per line, with EIP-712 data for the employer's wallet. Exactly one request can move a draft forward. In production this step is refused when no router address is configured for the network.
3. Sign
The employer signs each line. The server checks every signature off-chain the same way the router will: the EIP-712 digest must recover to the payer, and malleable signatures are refused, so a stored signature is one the router can execute.
4. Execute and record
The employer's wallet submits the batch to the router. The transaction hash the browser reports is treated only as a pointer. A line is marked executed only after the server re-reads the receipt and finds a PaymentExecuted event from the configured router whose payment ID, payer, recipient, amount, fee and nonce match what was signed, at the network's required depth, in a block that is still canonical. A partially executed run stays submitted; it is never reported as paid.
| Parameter | Type | Description |
|---|---|---|
| draftrequired | status | Saved and screened; nothing issued to sign. |
| blockedrequired | status | At least one recipient was flagged or could not be screened. |
| submittingrequired | status | Intents are being issued. |
| submittedrequired | status | Intents issued; lines are being signed or executed. |
| completedrequired | status | Every line was found executed on-chain. |
| cancelledrequired | status | Cancelled by the employer. |
Fees
- Collected on-chain: 35 basis points per line, rounded down, paid by the employer on top of the line.
- Quoted only: a batch minimum of 1.50 USDC and a seat fee of 19 USDC per active contractor, once per month. Billing is not connected, so these are recorded as quotes and charge nobody.
Tax records
An employer can record that they hold a payee-signed W-9 or W-8BEN. This is a record, not an e-signature system. The tax ID, legal name and address are encrypted at rest; only the last four characters of the tax ID are stored in the clear, and no API response decrypts them.
The tax summary is a 1099-NEC threshold watch for US contractors, totalling what was sent in submitted runs per calendar year. It cannot confirm what settled, it files nothing with any tax authority, and it is not tax advice.
Reconciliation
A scheduled job checks that every executed line's fee is journaled exactly once, re-reads recently executed lines from the chain, and reports runs that have been partially executed for more than a day. Findings become alerts. Nothing is corrected automatically: reversing a journal is an operator decision.
API
| Method | Path | Description |
|---|---|---|
| GET / POST / PATCH | /api/payouts/payroll/contractors | List, add or update roster entries |
| POST | /api/payouts/payroll/contractors/import | CSV roster import, all-or-nothing |
| POST | /api/payouts/payroll/contractors/{id}/invite | Create a seven-day single-use invitation |
| POST | /api/payouts/payroll/contractors/{id}/tax | Record a W-9 or W-8BEN the employer holds |
| POST | /api/payouts/payroll/invite | Contractor side: preview or accept an invitation |
| GET / PATCH | /api/payouts/payroll/me | Contractor side: own profiles; set the payout address with a signature |
| GET / POST / PATCH | /api/payouts/payroll/batches | List runs; create a draft; submit, sign, cancel, reissue, exclude unscreened, record execution |
| GET | /api/payouts/payroll/tax-summary?year= | 1099-NEC threshold watch |
| GET / POST | /api/payouts/payroll | Saved roster; run payroll for a roster in the request body |
All routes require a signed-in session and are rate limited per account. The contractor invitation page is /payroll/join.
Setup
- Supabase configured, with migrations
0042_payroll_and_merchant_extensions,0044_payroll_contractor_self_serviceand0048_payroll_exclusions_and_fee_ledgerapplied. - A router address per network:
FURLPAY_ROUTER_ADDRESS_ARBITRUM,_ARC,_BASE,_POLYGON. The unsuffixedFURLPAY_ROUTER_ADDRESSis read for Arbitrum only; a router is never assumed to exist at the same address on another network. FURLPAY_TAX_ENCRYPTION_KEYfor sealed tax records.- Node endpoints for each payroll network.
- The
merchant-reconcilescheduled job, which also runs payroll reconciliation.
Not offered
