Search projects, hackathons, sponsors and tech

    ← All projects

    SealedChange

    Confidential PO amendments settle under the version both parties signed. Each delivery pays once.

    Demo

    About

    Manufacturing purchase orders change mid-flight: alloy surcharges, volume tolerances, freight adjustments. Every one of those amendments opens a window where the signed document, the ERP and the payment disagree with each other. Finance pays against whichever copy of the contract the clerk had open. A re-keyed goods receipt gets paid a seconds anewer document and looks like a fresh claim., alender or a counterparty that the correclymeans handing over the terms themsecebands, volume commitments. SealedChange closes that window. It converpurchase-order amendment into a typed,mutualandmakes two things structurally true. First, a price or tolerance change is applieversion both companies approved. Buyer and suppliEIP-712 message over a salted commit and an on-chain registry recovers both signatomesthe agreement's only settling version.From tpoch is permanently stale, so a report evaluandmentcannot settle even though that epoch was on Secondry cannever trigger a second payment. The escrownomicevent identifier derived from the sourceorderand the delivery line, and deliberatelnot frbrand-new approved amendment thereforecannotat hasalready been paid. That is an identityproper abookkeeping rule someone has to rememb The flnd sign an amendment; the buyer funds a cappedescroworganisation wallet that allowlists thescrowery is confirmed in the ERP; a confidential workfl, reads the scoped ERP record, and computes a determgerarithmetic only; a minimal report reaches the consumer contract, whichre-derives beneficiary and cap live from the registry and pays exactlyonce. There is no partial-pay branch and no discretionary override. Everyinvariant violation fails closed with a reason code and a payable amount ofzero. There are three roles. Buyer and supplier see the commercial terms theyalready hold. The auditor sees a public record consisting of the commitment,the epoch, the economic event hash, the source version commitment, thebeneficiary, the payable amount, the expiry and the verdict, and neverreceives unit price, tolerance, expected quantity, the commitment saltor ERP credentials. That split is enforced by what the server serialises, not by what a page chooses to render: the auditor's endpoint structurally has no field for the confidential values. Stated honestly, the amount, payee and timing are public on chain by design,and that combination can leak business metadata. Someone watching thesupplier's address learns roughly what was paid and when. SealedChange hidesthe commercial terms and the settlement policy, not the existence or the sizeof a settlement.

    How it's made

    STACK pnpm workspace. TypeScript for the app, SDK, CLI, MCP server and the CRE workflow; Solidity + Foundry for contracts; Next.js 16 (App Router) for the interface; viem for all chain access; zod for the typed domain model. THE EVALUATOR IS THE SINGLE SOURCE OF TRUTH `packages/domain` holds one pure function, `evaluateSettlement`, that turns a private policy plus a live ERP delivery record into a payable amount and a reason code. It never signs, never authorises a transfer, and has no partial-pay branch — every invariant violation returns eligible=false with payableAmount=0. The real Chainlink CRE `handlerInTee` path and the local adapter both reach it through the identical `runEvaluation` orchestration, so evaluation is bit-for-bit the same in simulation and in the confidential runtime. The confidentiality boundary is purely a property of which dependencies were injected, not a branch inside the logic. CONTRACTS AND THE KEY DESIGN DECISION PolicyEpochRegistry (EIP-712 approvals, monotonic epochs), AmendmentEscrow (prefunded, capped, atomic settle-and-transfer, reentrancy-guarded) and ReceiptConsumer (the only path into the escrow). The decision that makes the whole thing work is what `economicEventId` is derived from: keccak256 over (source tenant, PO id, delivery line id) and nothing else. It carries no epoch. That is what makes "a new amendment cannot re-pay an old delivery" an identity property rather than a bookkeeping rule. The escrow also re-derives beneficiary, cap and revocation live from the registry rather than trusting the report for anything but amount and event identity, so a tampered report cannot redirect funds. CHAINLINK CRE (primary) The workflow is written against the real `@chainlink/cre-sdk`: `cre.handlerInTee` for the confidential handler, `runtime.getSecret` for policy material the Vault DON releases only into an attested enclave, and the confidential-HTTP capability for the ERP read (the CRE WASM sandbox has no fetch/fs/crypto, so policy secrecy is CRE's provisioning guarantee, not something we re-implement). The confidential verdict is a necessary input to settlement, not decoration on top of it. Honestly: Confidential Workflows is invite-only private beta, so we have NOT run a live TEE execution. Local simulation is labelled "local adapter" everywhere it appears — in the UI's runtime panel, in the logs, in the docs. Simulation is evidence of simulation; we never call it attestation. PRIVY (secondary) Research surfaced that `@privy-io/server-auth` is deprecated, so the server adapter is built against `@privy-io/node` instead. We build real Privy Policy documents that allowlist exactly two destinations on one chain — the settlement token and the escrow — and nothing else. The notable piece: we also wrote a local evaluator implementing Privy's documented default-deny semantics ("a request satisfying no rule is DENIED"), and it consumes the exact same policy object that gets registered with Privy when credentials are present. So the credential-free path enforces the identical document rather than mocking it away. The app shows a real ALLOW (fund escrow) and a real DENY (pay the supplier directly, bypassing escrow) with the per-condition check that decided each — and the denied action never reaches a signing key. FRAPPE / ERPNEXT One `fetch`-based REST client against the documented `/api/resource/:doctype` contract with token auth. It runs unmodified against a live tenant or the bundled fixture. The settlement report commits to the document's `modified` stamp, so an ERP rollback to an older revision cannot be silently replayed as a fresh confirmation. THE HACKY BIT WORTH MENTIONING The web app is not a wireframe over a documented flow. On first request the server spawns a real anvil devnet, deploys PolicyEpochRegistry, AmendmentEscrow, ReceiptConsumer and a test token from the project's OWN compiled Foundry artifacts, and binds a Frappe-shaped fixture server. Every button then drives that for real: real EIP-712 signatures, real ERC-20 transfers, real reverts. If Foundry is missing the app says so instead of pretending to work. Consequence: a revert raised inside the escrow surfaces through the consumer as a bare 4-byte selector, because the consumer's ABI doesn't declare the escrow's errors. So we walk the viem error chain for revert data and decode it against all four contract ABIs — a duplicate settlement reads as `EventAlreadyConsumed(0xfb38…)` in the UI rather than `0x12bd301a`. CONFIDENTIALITY ENFORCED AT THE SERVER Two endpoints serve two projections of the same deal. `/api/deal` (buyer and supplier) carries the amendment terms those counterparties already hold. `/api/deal/public` (auditor) is built from named public fields only — the private keys are not hidden by the client, they are never serialised. A test asserts this with high-entropy sentinel values (a naive substring check gave a false positive against a millisecond timestamp, which is why the sentinels are 12 digits), and separately asserts the party view DOES carry them. The auditor component adds defence in depth by destructuring only named fields — never a spread, never JSON.stringify. PROVING THE REPLAY DEFENCE PROPERLY Re-running the evaluator on an already-settled delivery just returns ALREADY_CONSUMED, which proves the evaluator works but not the contract. So the demo also does a forged replay: it resubmits the exact report that already paid, with a fresh nonce, so the consumer's nonce check cannot catch it. The escrow reverts. That is the proof that the guarantee lives in the contract, not in bookkeeping. MONEY IS NEVER A FLOAT Every amount is an integer count of minor units carried as a decimal string across JSON and parsed to bigint at the evaluator boundary. The UI divides by 100 for display only. BEYOND THE UI The same capabilities ship as a typed SDK, an `npx` CLI and an MCP server, all wrapping one client so there is no second implementation to drift. Payment arithmetic, signatures and authorisation stay entirely outside model control. TESTING AND DEPLOYMENT 71 tests: 33 Foundry (unit, integration, 128k-call stateful fuzz invariants), plus deterministic evaluator vectors, ERP contract tests, a real-anvil SDK end-to-end settlement + replay rejection, and the full browser flow driven against a devnet. GitHub Actions runs all of them with Foundry installed. A hosted path exists: a second runtime mode connects to Sepolia instead of spawning anvil, with off-chain state in Supabase (RLS enabled with zero policies, so only the service-role key reaches the tables) and the ERP fixture re-served at Frappe's real path so the client stays unmodified. Mode is derived from configuration rather than a flag, so a half-configured deployment lists each missing variable by name instead of looking like it works. WHAT WE DROPPED ENSv2 was a candidate third integration. ENS's own docs state the Enhanced Access Control interfaces are "not yet final", and we could not independently verify the role methods are live against the published Sepolia addresses. Rather than ship a cosmetic integration for a logo, we documented the exclusion and shipped two sponsors properly.