Wallet-signed agreements
Pact
A Nimiq Pay Mini App that turns an informal deal into terms both sides sign with their wallet — then tracks the work and the payments against it.
Deployed. Wallet signing and payments run inside Nimiq Pay; in a regular browser the app offers a full demo instead.
- Period
- Sep – Oct 2026
- Status
- Live
- Role
- Product design, Full-stack engineering
- Context
- Nimiq Mini Apps Competition — Cycle II special reward.

Screenshot of the running product.
The problem
Why this needed to exist
Most disputes over small jobs are not about dishonesty. Nobody wrote down what was agreed, and two people remember it differently.
The existing answers are the wrong shape. A contract template is too heavy for a 200 USDT job. A chat thread is not a record anyone can point at. An escrow service takes custody and a fee, and lives outside the wallet where the money already is.
Who it’s for
- Freelancers and their clients
- Creators taking brand work
- Small sellers
- Friends holding each other to a commitment
The solution
How it works
You describe the deal in your own words. Pact extracts the parties, deliverable, amount, currency, deadline, payment condition and milestones into structured terms you can edit, and a Smart Review flags anything that could be read two ways — "several videos", a missing deadline, uncapped revisions — with a concrete fix for each.
Both sides then sign a Blake2b fingerprint of the exact terms with their Nimiq wallet. Payments are made from the agreement, and the shared timeline records everything both parties see.
From a sentence to a sealed agreement
- 01DescribePlain-language deal
- 02StructureAI extracts terms (Zod-validated)
- 03ReviewAmbiguities flagged with fixes
- 04SealBoth wallets sign the digest
- 05PayMemo carries the Pact id
- 06ConfirmServer reads the chain
Implemented
What’s actually built
The Seal is a signature
Each party calls nimiq.sign() over a canonical digest of the terms. The server verifies the signature and derives the signer’s address from the public key — it never accepts an address a client claims.
The brand mark is the fingerprint
The ring around every agreement renders the digest: each of its 32 hex characters becomes one tick. Two people can compare seals across two phones, and a changed agreement visibly changes shape.
Payments carry the agreement on-chain
NIM payments go out with data = PACT:<id>:<milestone>, so the payment’s purpose is written into the Nimiq ledger itself. USDT settles as an ERC-20 transfer on the chosen chain.
Confirmed means the network said so
A wallet returns a transaction reference, recorded as sent. The server reads the network and confirms only if the transaction exists, succeeded, went to the agreed recipient for the agreed amount with this agreement’s memo.
Renegotiation that re-seals
Either side can propose changes to a sealed pact. Accepting them moves the fingerprint and clears both signatures, so it cannot return to sealed until both people sign what it now says.
Public records, by consent
A published agreement gets a link anyone can open to check the terms, fingerprint and signatures — without delivery notes, transaction references or full wallet addresses.
Campaigns
A brand posts a brief with a fixed reward and a number of spots. Taking one up creates an ordinary Pact. Spots are claimed atomically, one per person, and return if an agreement is declined.
Issues, not verdicts
Either side can raise one of four real problems in their own words. It records a disagreement; it does not adjudicate one, and it moves no money.
Interface
Screens from the real product
Edit a term after both people signed, and the agreement says so — the signatures clear, because a signature only matches the exact terms it was made over. The welcome screen: describe a deal, both sides sign, paid when the network confirms. Campaigns: a brand posts work with a fixed reward; taking one up creates an ordinary Pact.
My role
What I did
Concept, product design, the PACT Seal identity, and the full implementation: the Next.js app, the wallet integration layer, the AI Pact Builder with a deterministic fallback, the Postgres schema and the test suite.
Responsibilities
- Product design
- Full-stack engineering
- Wallet integration
- Brand (the PACT Seal)
Stack (verified from source)
- Next.js 15
- TypeScript
- @nimiq/mini-app-sdk
- Supabase Postgres
- Gemini / Anthropic
- Zod
- EVM (USDT)
Decisions
Technical and design choices
- 01
No escrow, and say so
The Mini Apps framework gives an app no way to hold or conditionally release funds. Pact never claims to: payments go wallet to wallet, and a build-time honesty check fails if words like "escrow" or "guarantee payment" appear in user-facing copy without a negation.
- 02
One redaction boundary
Everything a non-participant can see passes through a single function that builds a separate record type field by field, so a column added later cannot publish itself. A test greps the serialised payload for wallet addresses.
- 03
Allowlists for visibility
Visibility checks are written as "is SHAREABLE or PUBLIC", never "is not PRIVATE", so a row from before the column existed withholds instead of publishing. This was a real bug caught in testing.
- 04
Reputation is a view, not a table
Trust metrics are a database view over what actually happened, and always show the denominator — "4 of 5 delivered on time", never a bare 80%.
Trade-offs
Challenges along the way
- 01
A provider that resolves with errors
The Nimiq provider resolves with an error object instead of throwing. A call site that forgets to check would report a payment as sent when it was not, so every provider call goes through one unwrap() function.
- 02
Money without floats
USDT has 6 decimals, not 18, and gas is paid in the chain’s native token. Amounts are minor-unit strings end to end, and milestone splits always sum back to the total.
- 03
Judges on laptops
Wallet features only work inside Nimiq Pay. Outside it, the app detects the missing provider and offers a complete demo instead of failing.
Results
What can be checked
Cycle II special reward
One of five extra picks chosen from 207 submissions to the Nimiq Mini Apps Competition, Cycle II — a $100 reward.
- miniappscompetition.com (opens in a new tab)
Competition submission
Listed in the official Nimiq Mini Apps Competition Cycle II submissions.
- Repository (opens in a new tab)
Tests
135 tests run without a test framework — Node executes the TypeScript directly — covering signature replay, digest stability, milestone arithmetic and redaction.
Lessons
What I took away
- When a product cannot do something users expect — like hold funds — the strongest design move is to state it plainly on the screen where they would assume it.
- Making the security property visible (the Seal) did more for trust than any copy could.
What’s next
Planned — not built
Planned work
- A live, end-to-end payment confirmation run on mainnet (verification has been exercised against recorded mainnet responses and a fake chain)
- Optional trust-minimised settlement if the Mini Apps framework adds conditional release
Current limits, stated plainly
- Pact never holds funds and cannot reverse a payment.
- The AI helps word an agreement; it is not legal advice.