Skip to content
ghost-work

ghost-work

Your rate is nobody's business

Created on 21st February 2026

•

ghost-work

ghost-work

Your rate is nobody's business

The problem ghost-work solves

Freelancing is broken in a very specific way that nobody talks about: everyone can see everyone else's deal.

When you hire a freelancer through an on-chain contract today whether it's on Ethereum, Solana, or any public L1, the entire world can see what you're paying them. Your competitor can look up your dev budget. Other freelancers on the same project can see what each other earns. An auditor reviewing your books gets full access to every individual payment, name, and rate.

This isn't a theoretical problem. It's why most serious freelance payments still happen through bank wires and PDFs because the moment you put a contractor agreement on a public blockchain, you've published salary data to the world.

GhostWork fixes this using Canton's sub-transaction privacy. Not with access control lists. Not with encryption wrappers. At the protocol level, each participant's node physically receives only the data they're authorized to see.

Here's what that actually looks like in practice:

  • The Client (Ethereum Foundation) sees all their contracts and payment records, they're a signatory on everything they created
  • Freelancer Nidhi sees only her own contract and payments. She has zero knowledge that Akash even exists on the platform, his data was never sent to her participant node
  • Freelancer Akash sees only his own contract. Nidhi's rates, project descriptions, and payment history are physically absent from his node
  • Auditor Eve sees aggregate totals only, "2 contracts, $7,000 total paid" but zero individual details. No names, no rates, no project descriptions

This isn't filtering. It's not hiding rows in a database. The data literally does not exist on nodes that shouldn't have it. That's the difference between access control and actual privacy.

What People Can Use It For

  • Freelance platforms that want to stop rate-comparison shopping between contractors
  • Enterprise contractor management where different departments shouldn't see each other's vendor deals
  • Compliance auditing where auditors need to verify totals without seeing individual records (think SOC 2 aggregate reporting)
  • Any B2B payment workflow where counterparty confidentiality matters be it invoice financing, consulting fees, advisory retainers

The guided demo walks you through the full lifecycle in about 5 minutes: send a proposal, accept it, submit milestones, approve payments, generate an audit, switching between accounts at each step to prove what each party can and cannot see.

Challenges I ran into

1. ARM64 on macOS

I'm building on an M1 Mac. The Canton/Daml SDK ships Linux x86_64 binaries. The cn-quickstart Docker image is Ubuntu-based. Getting all three to play nice together cost me almost a full day.

The fix was building a custom Docker dev image (cantonlance-dev) with JDK 21, Node.js 20, and the ARM64 Daml SDK (daml-sdk-3.4.10-linux-aarch64.tar.gz). Once I had that, everything ran natively without emulation. But the initial repo setup docs assume x86_64, and there's zero mention of ARM anywhere.

2. The Canton JSON Ledger API v2 Response Format

The documentation for the JSON Ledger API v2 is... sparse. I spent hours figuring out that POST /v2/state/active-contracts returns a nested structure like:

{
  "contractEntry": {
    "JsActiveContract": {
      "createdEvent": {
        "templateId": "packageId:Module:Template",
        "createArgument": { ... },
        "contractId": "..."
      }
    }
  }
}

There's no example of this in the docs. I had to reverse-engineer it by logging raw responses and walking through the nesting.

3. Daml's Signatory Model

Coming from Solidity, my first instinct was to have the client create a ProjectContract directly with both parties as signatories. Daml said no — you can't create a contract that requires someone else's signature without their consent.

This forced me into the propose-accept pattern, which is actually a much better design

-- Client creates a proposal (only client is signatory)
template ProjectProposal
  with
    client : Party
    freelancer : Party
    ...
  where
    signatory client
    observer freelancer

    choice AcceptProposal : ContractId ProjectContract
      controller freelancer  -- freelancer must consent
      do
        create ProjectContract with ...  -- now both are signatories

What felt like a limitation turned out to be the right abstraction. Proposals show up in the freelancer's "Incoming Proposals" and they explicitly accept or decline. Much more honest than auto-enrolling someone in a contract.

4. DevNet Sequencer Outage

I had the app working on local sandbox and tried to deploy to Canton DevNet. Got the validator running, onboarded to the network, set up JWT auth, everything looked good. Then the DevNet sequencers went down. The validator couldn't register the synchronizer domain, so no transactions could be submitted.

I pivoted to making the local sandbox experience bulletproof instead. The app detects which environments are available on startup and lets you switch between them. The DevNet code path still exists and works, it's just waiting for the sequencers to come back.

5. Sandbox Auth: The Token Header That Wasn't

Canton's local sandbox mode doesn't need authentication. Sounds simple. But if you send an Authorization header with an empty token, the Pekko HTTP server throws Illegal header warnings into the logs constantly. The fix was conditionally skipping the header entirely in sandbox mode:

if (this.config.mode === "local") {
  // Sandbox doesn't require auth — skip Authorization header to avoid
  // Pekko HTTP "Illegal header" warnings from colons in token
} else {
  headers["Authorization"] = `Bearer ${partyConfig.token}`;
}

Small thing, but it took a while to figure out why the logs were full of warnings.

Use of AI tools and agents

I used Claude Code as a pair programming partner throughout the build. Here's how:

What Claude handled well:

  • Scaffolding React components from rough descriptions, I'd describe the UI I wanted and it would generate the JSX, then I'd tweak styling and logic
  • Refactoring across multiple files simultaneously, when I changed the Daml contract to add paymentTimestamp, Claude updated the test file, the API client, and the frontend store in one pass
  • Catching type errors before build, it would flag missing fields in TypeScript interfaces before I even ran tsc

What I had to do myself:

  • All Daml contract design decisions, the privacy model, signatory/observer assignments, and the propose-accept pattern were my architectural choices
  • Canton API integration, Claude doesn't know the JSON Ledger API v2 response format, so I had to debug the actual HTTP responses and teach it the nesting structure
  • DevNet deployment, SSH tunnels, JWT configuration, validator onboarding this was all manual infrastructure work
  • UX decisions which party sees what, the demo guide flow, the "not connected" landing page design

The workflow was roughly: I'd design the approach, write the critical integration code, then use Claude to parallelize the UI implementation across multiple files. It saved significant time on repetitive frontend tasks, but every Daml contract line and every Canton API call was hand-verified against the actual ledger responses.

Tracks Applied (3)

Devtopia

Canton Developer Toolkit for Privacy-First dApps Every privacy-first dApp on Canton hits the same five walls. There is ...Read More

Best Privacy-Focused dApp Using Daml

GhostWork is a private freelancer payment protocol that demonstrates Canton's sub-transaction privacy through a real-wor...Read More
Canton Network

Canton Network

Best Canton Dev Tooling & DevX Accelerator

GhostWork doubles as a complete reference implementation for building privacy-first dApps on Canton. Coming from Ethereu...Read More
Canton Network

Canton Network

Technologies used

Cheer Project

Cheering for a project means supporting a project you like with as little as 0.0025 ETH. Right now, you can Cheer using ETH on Arbitrum, Optimism and Base.

Discussion

Builders also viewed

See more projects on Devfolio