
FactorFlow
Unpaid invoices become tokenized assets: AI explains, deterministic code prices, Hedera settles.
Demo
About
FactorFlow is a marketplace for invoice factoring, live at https://factorflow.online The problem is old and enormous. A business delivers goods, sends an invoice, and waits sixty or ninety days to be paid. Meanwhile it has payroll. Factoring solves this — somebody buys the invoice at a discount and collects the full amount later — but it is a relationship business built on paper, phone calls and trust, and small suppliers are priced out of it because nobody will underwrite a €15,000 receivable by hand. FactorFlow turns a receivable into an asset a stranger can price and buy, and does it without anyone at the platform ever reading the invoice. The path, end to end: A seller signs in with a wallet — a one-shot challenge signed with EIP-191, no password, no account to lose — and uploads an invoice. The document is encrypted in the browser with AES-GCM under a key generated in that tab and never transmitted. What the platform stores is ciphertext and a digest it computed over the bytes it actually received, never the digest an uploader claimed. Nobody at the platform can open it, and neither can a backup or a leak. The document is opened in exactly one place: a Chainlink CRE workflow running inside an attested enclave. It checks the digest before it uses the key, decrypts, checks the invoice's own arithmetic against its line items, reads its terms, and returns six features between zero and one, a confidence, two verdicts and a commitment. No text, no debtor name, no amounts. A feature vector cannot be turned back into an invoice, which is why the platform can hold the result in the clear while still being unable to read what produced it. A published, deterministic model turns those features into PD, LGD, expected loss, a grade and a reserve price. The benchmark rate comes from live lending markets — Aave V3 and Compound V3 on Ethereum, read through The Graph — so the discount is anchored to what capital actually costs this week rather than to a number somebody typed. The reserve is the model's, not the seller's: nobody prices their own paper here. Approved receivables are minted on Hedera as tokens whose supply is the face value in minor units, so a holder's balance is the notional they own in cents. They are offered in batch auctions where investors bid with constraints — at most this price, up to this much, no worse than grade C, no more than a share of my money on one debtor. A min-cost max-flow solver clears the batch and issues an allocation certificate, and an independent verifier recomputes every constraint from the inputs, so a bidder does not have to believe the venue. Transfers settle on Hedera and are confirmed a second time against the public mirror — a different source, which is the only thing that makes a second confirmation worth having. The check asks what the transaction moved, not merely whether it succeeded: a transfer that credited the wrong account, or the right one with the wrong amount, fails the step. When the debtor finally pays, an operator records it and the money is divided exactly in minor units among the investors whose transfers completed and the issuer for whatever it never sold. Every unit paid is handed to somebody; none is invented. A payment short of the face still divides and still closes the receivable as defaulted, because an investor reading their position has to be able to tell a receivable that came good from one that did not. There is a second kind of customer: a program. Two endpoints sell risk assessments to machines over x402. An agent asks, is refused with HTTP 402 and a price, pays the quoted HBAR on Hedera with the quote's nonce in the transaction memo, and asks again. It is a prototype and says so on its own front page: testnet only, synthetic invoices, no legal assignment of receivables, no production KYC. The transactions are real transactions on a real network carrying no real money. The Chainlink CRE workflow runs in CRE's simulator, because this repository has not been granted deployment access — which is stated rather than implied.
How it's made
One Go 1.26 process and one PostgreSQL database, deployed as a 43 MB distroless container behind Caddy. The same binary serves the JSON API and the React interface, so the deployment is one container beside a database rather than a fleet. Architecture: a modular monolith with boundaries enforced by a test rather than by review. The platform layer never imports a domain module and a domain module never imports another; there is exactly one deliberate exception, auction → risk, because an investor bids "at most grade C" and a grade is the risk model's published vocabulary. The reverse would let pricing depend on demand. The test fails the build if anybody crosses a line, including in comments. Money is never a float. Every amount is an integer in minor units with an exact decimal rate beside it, and a cent that goes missing is a failing test rather than a rounding convention. Writes survive being retried: idempotency keys, optimistic concurrency, a transactional outbox, and an audit trail written inside the caller's transaction so what happened and what was recorded cannot disagree. Errors are RFC 9457 problem documents with trace ids. Frontend: React 18 + TypeScript + Vite, decimals rendered without ever touching a float. Wallets are discovered over EIP-6963 rather than window.ethereum — with two extensions installed the last to load used to win, and the other was unreachable. Document encryption is WebCrypto in the seller's tab. PARTNER TECHNOLOGIES Hedera (HTS) does the asset leg: one receivable is one token, supply is the face value in minor units. But the decisive property was not the token standard — it was the public mirror. Verifying a Hedera transaction takes an HTTPS request and no credentials, which meant the platform could confirm its own settlements against a source it does not control, and — see below — could accept machine payments with no third party in the loop at all. Chainlink CRE gives the confidential assessment somewhere to happen that is not this server. The handler is Go compiled to WASM (GOOS=wasip1) running under a TEE constraint, pulling both its platform token and the document key from the Vault DON. The privacy boundary is drawn by what the platform is *given*, not by what it promises: there is no endpoint on this platform that can produce the document key, in either direction. The Graph supplies the benchmark from Aave V3 and Compound V3 on Ethereum. Two protocols rather than one, because a median over a single venue is that venue's rate with extra steps — and both publish the same standardized schema, so one query text serves both. The snapshot records which subgraphs answered at which block, so a published price traces back to rows. A subgraph that fails takes the whole fetch with it: dropping the venue that did not answer would move the benchmark with nothing on the snapshot to say why, so pricing fails closed. x402 sells answers to programs. The agent is real — it holds its own Hedera account, reads the 402, transfers the quoted HBAR and comes back with the transaction id. THE HACKY PARTS WORTH MENTIONING No x402 facilitator. Everyone assumes you need a third party to attest that a payment happened. On Hedera you do not: the platform reads the public mirror and checks five things, each of which is a way a customer could otherwise be served an answer it did not buy — the network says the transaction succeeded; it credited this account; it credited at least the quoted price; it was submitted by the payer the proof names; and its memo carries the nonce this quote was issued under. That last one is the trick: putting the nonce in the transaction memo binds a payment to one question, which is what stops a payment for one query buying the answer to another, and stops the same payment buying two. Two failure modes are deliberately not refusals, and both were found by running it for real. A mirror that has not indexed the payment yet answers 503 and the quote stays payable, because a customer whose money is already on the network must not lose it to a few seconds of lag. And a stranger's proof against a quote in flight leaves the quote alone — otherwise anyone could burn somebody else's paid quote by sending one request. A derived nonce instead of an exchanged one. The enclave and the platform both compute the run nonce from the invoice id and the ciphertext digest, so they arrive at the same value without exchanging it, and a verifier holding the stored assessment can recompute the commitment without asking anybody for state. The model writes prose, never numbers. An LLM writes the sentences under a price it cannot touch — by the time it is asked, the score, grade, premiums and reserve are already stored. What it writes is checked against the set of numbers that assessment published, in every shape a writer would use them (0.036325, 3.63%, 3.6), and a figure that is not in that set discards the whole narration, not the sentence carrying it. Its prompt contains six features and their contributions: no document text, no debtor, no party. The privacy rule is not enforced by asking politely — the content is not there. Negation is not a substring. The enclave read "No dispute has been raised" as a disputed receivable, because it matched on the word. That single word cost a whole grade — $150 on the demo invoice — before the clause reader learned to look for no/not/never/without/free. The demo dataset is produced by driving the real services with a clock that gets moved, not by writing rows. The finished batch was genuinely uploaded, priced, minted, cleared after its window closed, settled and repaid — a week of history ago. Settlement destination is decided per transfer, not per process. Configuring a chain says the platform can settle on one; it does not say every participant can receive on one. A transfer to an investor with no Hedera account went to the network anyway and came back TOKEN_NOT_ASSOCIATED_TO_ACCOUNT — a real fee for a settlement that could never complete. Found by deploying, not by testing. Deployment ships the image over ssh rather than building on the box. The droplet is 1 vCPU and 2 GB; compiling Go beside an npm build there is an out-of-memory kill at the worst possible moment. `docker save | gzip | ssh 'docker load'` moves 43 MB and takes seconds. Around 700 tests across the server and the browser. Integration tests run against a real PostgreSQL through testcontainers, never a mock, because half of what a repository does lives in SQL, constraints and transactions.