
Wizard
Confidential B2B Campaign Factory using Privy and Chainlink Workflows on Base
Demo
About
The problem: Cross-brand co-marketing ("spend $10 at Brand A, earn Y redeemable at Brand Z") gets negotiated constantly and built bilaterally, and every new partner pair means a new integration. That's months of engineering per partnership, upfront cost turned into permanent CapEx, and lock-in to whichever partner's API you built first. It also forces two competitors to share raw customer transaction data or stand up a joint PII pipeline just to answer "did this customer qualify?". And even when the integration exists, someone still reconciles receipts by hand at period end, against a ledger of record that belongs to one of the two companies, so neither side gets a proof the other has to believe. Blockchain doesn't obviously fix this. Public chains are a non-starter: purchase data is personal data, it can't sit on a public ledger, and per-customer claim events leak the customer graph. Permissioned chains demand infrastructure most brands don't run, and still leave the ledger in somebody's custody. What we built: Wizard replaces the bilateral integration with one shared on-chain factory plus confidential verification in a hardware enclave. A master `CampaignFactory` on Base Sepolia is the campaign clone generator: every campaign is an EIP-1167 clone of a shared `CampaignEscrow`, with terms fixed at launch, such as reward mechanic (percent rateBps or flat value), minimum spend, per-user cap with a calendar-anchored reset window (lifetime / day / week / month / year, span-N periods, anchor weekday, reset time-of-day), per-transaction cap, campaign-wide issuance cap, allowed days of the week (UTC bitmask), campaign start/end window, redeemable cashback vs. discount proof-of-savings, and a 2-party operating-cost split in basis points. Purchase payloads don't go to a partner's server or a shared database. They're submitted to a Chainlink CRE Confidential Workflow by HTTP trigger into an attested TEE enclave. Inside the enclave the workflow reads the campaign's terms on-chain from the factory at request time (so new campaigns are picked up with zero workflow redeploys), evaluates the payload against them using the same clamp order the contract uses (per-tx cap → per-user cap → campaign-wide cap), and holds the one partner-held secret the Vault DON releases into the enclave: a master key used to derive a single-use claim nullifier `H(HMAC(master, campaignId) || userAnchor || timestamp)`. Raw payload facts, such as spend amount, item detail, prior earnings never leave the enclave. Only the verdict does (runtime logs currently exist, in production they wouldn’t). On approval the enclave submits an execution proof via `evm.write` through the CRE Forwarder to the campaign's escrow on Base Sepolia. The escrow is not a passive log: it independently re-derives the earn and reverts `InvalidReport` unless the delivered points match bitwise, mints the ERC-1155 reward to the customer's wallet, and burns the nullifier, so a forged, replayed, or float-corrupted report cannot settle. Company B never sees Company A's customer records or transaction logs. It sees a settled on-chain claim against an escrow whose terms it agreed to, and it authorizes redemption on its own campaigns. Customer identity is a Privy embedded wallet created from an email OTP, so the customer manages no seed phrase and the merchant stores no password; the deposit handshake (each company funding its share of the operating deposit) is verified on-chain by exact sender, recipient, and wei amount before the campaign contract is ever deployed. Why the alternatives don't work: Bilateral APIs: CapEx per partner, vendor lock-in, and full data sharing. CDP/loyalty SaaS (Segment, Braze, mParticle, LoyaltyLion): they unify customer data beautifully by requiring the data to move into a shared processor, which is exactly the thing two competitors can't agree to. Affiliate/offer networks and receipt-parsing reward apps: they can verify a purchase without a bilateral integration, but the trust sits in the network's own database, reconciliation is still manual, and settlement isn't programmatic. Public-chain settlement: leaks personal data. Permissioned chains: infrastructure burden, and a single custodian. Zero-knowledge proofs: the right cryptographic answer and the wrong 2026 economics at retail volumes, where there are proving cost, circuit work per campaign shape, plus you still need a trusted path for the input data. Confidential workflows are the practical middle: the privacy claim is honest and bounded (pseudonymous-but-linkable, not zero-knowledge; the enclave's trust assumption is hardware attestation), and the settlement side gets a proof that neither brand has to be trusted to produce. Where it goes (how the app would look like in production): (1) N-party campaigns. The contracts already accept an arbitrary participant set and per-participant fee shares; the wizard exercises the 2-party case today. (2) One reward token across many campaigns, so a brand's points are a single fungible balance instead of campaign-scoped coupon tokens (the ERC-1155 minter role is already per-escrow, so this is an authorization change, not an architecture change). (3) Batch settlement at the merchant ingress to allow one DON consensus round for N receipts instead of N rounds, with a multi-claim report format on the escrow; this is what makes retail-volume economics work at all. (4) A merchant SDK that mirrors the rule gates client-side so obviously-ineligible transactions are never submitted (advisory only, the enclave stays authoritative). (5) Redemption privacy: `batchRedeem(bytes32[] commitments)` so the chain enforces supply conservation without learning who redeemed what. (6) Production gas: an ERC-4337 paymaster (e.g. CDP or Coinbase Developer Platform) sponsoring gas and settling in USDC, so companies never hold ETH, plus per-campaign workflow deployments for pause/upgrade isolation and per-campaign blast radius on trigger keys. (7) Production identity and authorization. Privy access tokens verified server-side and wallets bound to authenticated company accounts which also kills the demo's first-mover deposit-spoofing hole plus policies as enforced spend limits, and B's wallet as the escrow's authorized redeemer. Beyond that, Privy key quorums are what turn the platform's single relay key into a real org authorization model: an m-of-n threshold over authorization keys and user identities, enforced inside Privy's TEE infrastructure, with one level of quorum nesting so a "Brand Finance" quorum can sit inside an "Org Admins" quorum. Concretely, the demo's redemption authority (one EOA) becomes a 2-of-2 quorum, the reward company's wallet and the platform's server key must both co-sign, so neither side can unilaterally move reward funds, and the genuinely high-value operations (launch with a new fee split, raise a nearly-exhausted redeem cap, migrate a live campaign) become quorum-gated rather than single-signer. This composes with the on-chain story rather than duplicating it: Privy gates who may instruct the wallet, the escrow still gates what the chain will accept. How it makes money: Primary: a per-settled-redemption platform fee on volume tiers (e.g. $0.02 → $0.012 as monthly redeemed volume goes 5k → 625k+; more detail in project README.md), billed monthly in USDC. The per-claim cost is nearly fixed (DON consensus is the dominant term, not gas), so the discount is real margin management, not a pricing lever. Secondary lines: a dedicated-workflow premium (own registry entry, keys, and execution lane; the prerequisite for per-campaign pause), settlement-speed tiers (Standard 2,000tx/30min as the default batch, up to Real-time per-transaction at a ~2.5× multiplier), a flat campaign-migration fee for amending a live campaign (pause → redeploy with amended terms → migrate ledger → resume, $50–250), an opt-in per-campaign analytics add-on ($20–50/campaign/month, gated on explicit company consent, built from the verdict lifecycle data the platform already observes for billing), and an optional managed ingestion service for companies with no message broker. Operating deposits are custody, not revenue. Unspent balances are refunded, net of any covered debt.
How it's made
Stack at a glance: Solidity 0.8.28 + Foundry + OpenZeppelin v5.1 (EIP-1167 clones, ERC-1155) for the contracts; Chainlink CRE (`@chainlink/cre-sdk 1.18`, TypeScript) for confidential verification; bun + Hono + postgres.js + zod for the backend; Vite + React + TypeScript for the frontend; Postgres 16 in Docker locally and Neon in the deployed environment; Base Sepolia for settlement; Privy for identity and wallet signing. Deployment is split frontend/backend on Vercel with Neon as the database. Everything is wired through one `Makefile (make db-up, make test, make typecheck)` so a fresh clone runs with three commands. The request path, end to end: A purchase starts as a JSON payload `{campaignId, userAnchor, merchantId, amountSpent, timestamp, earnedInWindow}`. The backend relays it, signs it, sends using CRE HTTP trigger, which fires an attested enclave. Inside the enclave, the workflow reads the campaign's terms live from the factory on-chain (`evmClient.callContract` → factory `campaigns(id)` → escrow), so new campaigns need zero workflow redeploys; if the campaign has a campaign-wide cap it also reads `campaignTotalEarned()` from the escrow rather than trusting any caller-supplied number. It then evaluates eligibility: window, min-spend, day-of-week, per-tx cap, per-user cap, campaign cap; in exactly the clamp order the contract uses, derives the claim nullifier from a Vault-released secret, and crosses back to the DON for consensus. The DON signs the report and `writeReport` pushes it through the Chainlink forwarder to the escrow's `onReport`, which re-derives the earn itself and reverts `InvalidReport` unless the delivered points match bitwise, mints the ERC-1155 reward, and burns the nullifier. The frontend then confirms by reading the escrow's on-chain ledger rather than trusting any HTTP response. Chainlink CRE and how it's actually used: This is the part I'd call the heart of the project. The workflow is a single HTTP-triggered `cre.handlerInTee(...)` handler running on `{ tee: 'nitro', regions: ['us-west-2'] }` (currently deployed to Chainlink private registry), serving every campaign from one binary via a campaigns map in config. Three CRE capabilities do the work: `runtime.getSecret()` pulls `CAMPAIGN_NULLIFIER_MASTER` from the Vault DON straight into the enclave (never exposed to node operators or workflow-DON memory); the EVM client does the on-chain term reads; and `runtime.usingTheDons()` → `donRuntime.report({encoderName: 'evm', signingAlgo: 'ecdsa', hashingAlgo: 'keccak256'})` → `writeReport()` produces the DON-consensus, DON-signed report. The confidentiality is structural: raw purchase facts (`amountSpent`, `items`, `earnedInWindow`) are evaluated inside the enclave and never leave it, only the verdict and a one-way nullifier are written on-chain. Trigger auth is ECDSA: every request must carry an authorized-key signature (`KEY_TYPE_ECDSA_EVM`), so merchants authenticate to the platform with API keys and never touch a key. What CRE bought us: Before confidential workflows, the honest options for cross-brand verification were "share the data" (a non-starter between competitors) or "stand up a ZK proving pipeline" (real cryptographic cost per campaign shape, plus a trusted input path). CRE lets a purely stateless evaluator hold a partner-held secret and produce a settlement-grade proof, without the platform ever seeing the raw data. It also gave the project its cleanest architectural constraint: because the enclave can read on-chain state, the campaign rules live in one place (the factory), and the workflow never needs redeployment to learn new campaigns. Privy and how it's actually used: Privy covers the entire identity-and-money surface. Customers and companies onboard with email OTP and get an embedded wallet, meaning no seed phrase, and no password stored anywhere. Two flows ride on it: the deposit handshake, where each company connects its Privy wallet and sends its share of the operating deposit via `eth_sendTransaction` (40:60 split → A sends 0.004 ETH, B 0.006 ETH), and the backend verifies each deposit on-chain, like the exact sender, exact recipient, exact wei, confirmed receipt, all before the campaign contract is even deployed; and the reward redemption, which is `personal_sign` over a human-readable statement ("Redeem 1.40 points from 0x… on campaign #5 (DayOfWeek Test) as Globex Books"), verified server-side by recovering the signer. The escrow does not exist until both deposits land, which makes Privy the actual activation switch for a campaign rather than a login screen. What was particularly hacky: 1. Hand-rolling the CRE trigger JWT. Before the SDK exposed a client for firing deployed workflows, I reverse-engineered the gateway's signing scheme and reimplemented it byte-identically: `alg: 'ETH' JWT, digest = sha256` of the body with keys sorted lexicographically at every nesting level, `r||s||recoveryId` base64url with yParity normalization. It's the difference between `workflows.execute` returning ACCEPTED and returning nothing at all. 2. Discovering the report encoding the hard way. Wrapping the payload in `onReport(...)` calldata made the escrow see a 356-byte "report" that failed its length check; the working shape is a bare `abi.encode(nullifier, recipient, amountSpentWei, eligible, pointsWei)`. 3. A verdict transport that doesn't exist on Vercel. Execution status lives behind the `cre` CLI, which can't run in a lambda. So the frontend was rewired to confirm claims by watching the escrow's own `Claim` events, with a backend probe that detects CRE-CLI availability and skips polling when it's absent. The on-chain ledger became the source of truth. 4. Two id namespaces. Postgres row ids and the factory's sequential on-chain campaign ids are different numbers, and the workflow only knows the latter. So the payload route forwards the on-chain id, and the campaigns list verifies factory membership (escrow address match) to hide stale rows from superseded factory generations. 5. The contract suite enforces calendar-anchored cap windows (day/week/N-week/month/year) with Hinnant's civil-date algorithm implemented in uint256 to avoid an oracle, and the escrow advertises `IReceiver` via a bare `0x805f2132` interface id (inherited `IERC165` functions are excluded from `type(IReceiver).interfaceId`) Tools and AI disclosure: Built with Hermes Desktop as the harness, using GLM 5.3 Flash (code writing, README) and DeepSeek V4 Flash 0731 (technical spec and demo outline consolidation), full breakdown in `AI_ATTRIBUTION.md`. AI did the writing, cross-layer inconsistency checking between frontend/CRE/contracts, contract logic, test cases, and frontend boilerplate; I did the project and rule design, scope boundaries, naming, test-case design, production roadmap, and the CRE workflow deployment. Testnet ETH came from the ETHGlobal faucet. Local state runs Postgres 16 in Docker (`docker compose up -d, port 5433`); the deployed app runs on Vercel with Neon Postgres. Verification, so the claims are checkable: 67 Foundry tests (claim, caps, window resets including N-week anchors and DOW gates, redeem, redeemer whitelist, factory wiring), 27 workflow tests (3 demo campaigns × 5 payloads), and a backend suite covering launch validation, DOW-mask mapping, and wei conversion — all run by `make test`. The earn→redeem loop is live on Base Sepolia, with the relay authorized per-campaign via the factory's `setCampaignRedeemer` passthrough.