Theme
Language 中文

TaskMarket

A Toronto marketplace for owned local data. Restaurants, landlords and other owners sell aggregated data about their own business, buyers see a sample first, and every delivery is checked against a schema and a privacy floor before payment settles. Backend rewritten in Rust.

Live · Pivoted to Owned Data Founder · Core Developer

Context

TaskMarket started as a task marketplace for international students, with USDC escrow on Base. In September 2026 I refocused it around one question: can the settlement be checked by code? For errands it mostly can’t. For data it can, because a delivery either matches the agreed schema and privacy rules or it doesn’t.

So TaskMarket is now a Toronto marketplace for owned local data. A restaurant, a landlord or a driver sells aggregated data about their own business. Buyers see a sample before they pay, and nothing settles until the delivery passes an automatic check. Scraped data is out of scope; only the owner of the data can list it.

Screenshots are 3840×2160, as on a 32-inch 4K monitor. The data-market screens come from a local instance seeded with the project’s synthetic sample products, labelled as samples. The task screens come from the live site, where open tasks are generated examples.

Landing: see the sample first, then buy the real data.
Landing: see the sample first, then buy the real data.
Data market: each listing shows region, granularity, update cadence and price.
Data market: each listing shows region, granularity, update cadence and price.
Product page: a sample preview of real rows, next to the purchase panel.
Product page: a sample preview of real rows, next to the purchase panel.

Approach

Settlement that code can verify

Every listing carries a JSON Schema and an aggregation floor, for example “every row must cover at least five stores”, so no single business can be identified. After the buyer pays (by invoice or USDC), the seller uploads the delivery as JSON or CSV:

  • The rows are validated against the schema with Ajv, and the floor is checked row by row.
  • Pass: the order settles and the buyer can download the file.
  • Fail: the delivery is rejected with row-level errors, and the seller can fix it and deliver again.
  • Each delivery is stored with a SHA-256 hash of its payload, and delivery files are only downloadable by the buyer, never as public static files.

Sellers sign a consent template with their wallet before listing, and revoking it takes their listings down.

Schema reference: every field, its type and its constraints, visible before purchase.
Schema reference: every field, its type and its constraints, visible before purchase.
Order record: the first delivery was rejected (aggregation floor, postal-code format), the second passed and settled.
Order record: the first delivery was rejected (aggregation floor, postal-code format), the second passed and settled.
Listing flow: basics, schema and sample, then the consent signature.
Listing flow: basics, schema and sample, then the consent signature.
Seller dashboard: listings, orders waiting for payment or delivery, and history.
Seller dashboard: listings, orders waiting for payment or delivery, and history.

Collection tasks for the gaps

Where data doesn’t exist yet, it can be collected in person. The original task marketplace became collection tasks in seven categories: footfall counts, storefront checks, mystery shopping, surveys, price checks, verification and street promotion. Tasks still run on a bid book, so the poster compares price, timing and track record instead of taking the first reply, and USDC payments can still go through the escrow contract on Base.

Collection tasks: seven categories, with live bid heat on every task.
Collection tasks: seven categories, with live bid heat on every task.
Task detail: the bid book, the brief and the poster profile.
Task detail: the bid book, the brief and the poster profile.

A Rust backend, proven against the old one

I rewrote the Express backend in Rust (axum, sqlx, socketioxide) as a drop-in replacement: same database, same JSON, same Socket.IO events, so the front end did not change. The old Node code served as the spec:

  • A parity harness replays 933 scenarios against both servers and compares status codes and bodies, plus 48 Socket.IO checkpoints.
  • Before the switch, the Rust server answered live endpoints identically on a copy of the production database.
  • It went live on September 10, 2026, behind a one-line flag that can switch back to Node.

The only intended behaviour change was a fix: an order could previously be auto-confirmed after it had been cancelled or disputed, which could have released escrowed funds.

Fast public pages

The public routes are server-rendered on Cloudflare Pages Functions, with structured data that marks each listing as a Dataset. Splitting the bundle cut first-load JavaScript from 257 KB to 122 KB gzipped, and wallet code now loads only when someone opens a wallet flow.

Results & What I Learned

Live at taskmarket.pages.dev. The data market is open. It deliberately starts empty, because sample listings don’t belong in a market that promises real data. A pilot with local restaurants started in September 2026.

  • Choose a market where settlement can be verified. Disputes are the expensive part of any marketplace. A schema and a privacy floor turn “was this good enough?” into a check that runs in milliseconds.
  • Rewrite against a parity harness, not against memory. The old server was the specification. 933 replayed scenarios made the switch a configuration change rather than a leap of faith, and surfaced one real money bug along the way.
  • An empty shelf beats a fake one. Seeding demo listings would make the market look busier and make every listing less believable.

Front end: React 19 · Vite 7 · Tailwind 4 · Cloudflare Pages Functions (SSR)

Back end: Rust · axum · sqlx · SQLite · socketioxide · Ajv (delivery validation)

On-chain: Solidity escrow on Base · viem

Live: taskmarket.pages.dev