Choose the three parties
The account that sends the successful creation transaction becomes the client and funds the agreement. The contractor accepts and submits work. The arbitrator, selected by the client, may award the entire disputed payment to either party.
The three addresses must differ. Contractor and arbitrator must be nonzero and cannot be the escrow contract. There is no onchain arbitrator acknowledgement step or fee. Agree on participation and terms before funding.
Try locally with Docker
From the repository's main branch, use the Docker-backed Make targets. Dependency installation is the explicit networked step. The GUI starts a disposable Anvil chain, deploys escrow and its read projection, serves the checked-in CAM bundle, and prints the browser URL. The default listener is 127.0.0.1:5174. This guide is documentation; it does not provide a hosted escrow app or attest to a public deployment.
The escrow fixture requires the root manifest to match the hash published by its host contract. The viewer also checks the ABI and UI resource hashes.
git clone https://github.com/numpde/dapp32.git
cd dapp32
make dapps/deps/install
make js/deps/install
make cam/conformance/check
make escrow/local/demo/gui/up
Browse without a wallet
The printed URL starts with the local client as the address being viewed. That identity selects role-specific views; it cannot sign transactions. Read navigation works without a wallet. Preparing and simulating a write does not fund or change an agreement.
Use a fixture wallet
Sending requires an injected browser wallet connected to the fixture chain and account. Connecting replaces the viewed identity with the wallet address. For contractor or arbitrator actions, switch accounts in the wallet, wait for the refreshed view, check the account and chain, then prepare again. In a separate browser wallet profile, add chain ID 31337 with RPC URL http://127.0.0.1:5174/rpc (or the printed GUI origin followed by /rpc if you changed the port). Import the local fixture keys from compose/escrow/local/deploy.yml: PRIVATE_KEY is client/root owner; the named contractor, arbitrator, and finalizer keys select the other three roles. Deployment funds all four accounts for local transactions. These repeated-pattern keys are public and unsafe on any other chain; keep real funds and valuable keys out of this disposable chain.
After connection, wallets that report account and network changes update the viewer automatically and clear unsent prepared calls. A different wallet network leaves the viewer's configured chain and RPC unchanged and disables sending until the wallet returns to that chain. Disconnecting keeps the last successful read-only view. Submitted transaction hashes and receipt tracking survive wallet changes. If your provider cannot report changes, use Switch wallet to reconnect after changing accounts or networks.
Other local modes
make escrow/local/demo/terminal/up deploys a fresh disposable fixture, then reads its real local RPC state and prepares writes, but does not sign or submit them. make escrow/local/scenario/run automatically exercises all nine terminal settlement paths and complete withdrawal through the viewer stack.
The GUI target attempts teardown when its foreground command exits. Successful teardown removes the local fixture state. After an interrupted run, or if the fixture remains running, use make escrow/local/demo/gui/down and check that it has stopped. A fakechain preview, where used in development, is an in-memory fixture; it does not prove real RPC, wallet approval, transaction confirmation, or deployment behavior.
Create and fund
- Choose a reference unique to the client in this escrow contract. It cannot be reused after closure. Lookup uses the exact text, including case and spaces; the limit is 128 UTF-8 bytes.
- Enter contractor and arbitrator addresses, a positive amount, and four durations. AmountField accepts native-asset or wei input: 1 native asset equals 1018 wei. Creation sends exactly the specified amount; gas fees are separate.
- Enter whole-second durations from 1 through 31,536,000 (365 days). Choose the immutable arbitration-timeout recipient:
1 for client or 2 for contractor.
- Supply the agreed terms URI and exact-file SHA-256 digest as described below.
- Prepare the transaction, inspect amount and destination, then submit with the wallet. Wait for confirmation and the refreshed agreement view before treating it as funded.
Save the agreement ID shown after creation, together with the network and escrow contract address. The ID is derived from the client and exact reference; it does not commit to all agreement terms. Reopen by ID or by client/reference on that same contract and network.
Act before the deadline, finalize after it
Each phase starts at the successful transaction's block time. Ordinary actions require a block time strictly before its deadline. At or after the deadline, anyone can send the corresponding timeout transaction. Timeouts never execute automatically. Unavailable actions and their fields remain visible but disabled for the address being viewed. Availability comes from the contract at the last read and can change before execution; the contract checks it again when the transaction runs.
Active phases and whole-payment outcomes
| State and clock start |
Before deadline |
At or after deadline |
| Funded · creation |
Contractor accepts; client may cancel for client credit. |
Acceptance timeout credits client. |
| Accepted · acceptance |
Contractor submits committed work evidence. |
Work timeout credits client. |
| Submitted · submission |
Client approves for contractor credit, or disputes with evidence. |
Review timeout credits contractor. |
| Disputed · dispute |
Arbitrator awards the entire amount to client or contractor. |
Arbitration timeout credits the recipient fixed at creation. |
V1 has no partial awards, multiple milestones, deadline extensions, appeals, amendments, or resubmission. A terminal state cannot reopen.
Documents: publish and hash the exact bytes
Terms, work submission, and dispute evidence each require a URI and a nonzero SHA-256 digest. Publish the agreed file where the parties can retrieve it and enter its URI. The digest field offers three ways to supply SHA-256 of the exact document bytes:
- Enter a digest manually: paste
0x followed by 64 hexadecimal characters. Invalid format is shown beside the field without changing your entry.
- Calculate from file: choose the exact saved file. It stays on your device; this does not upload it or prove that the URI serves it.
- Calculate from URL: explicitly read the current HTTP(S) URI and hash the returned bytes after HTTP decoding. The form does not download documents while you type.
Calculations allow up to 8 MiB and have a 15-second deadline. URL access requires browser CORS permission, sends no credentials or referrer, and rejects redirects. If it fails, choose a file or enter the digest manually; the viewer does not switch methods automatically. For external calculation on Linux, for example:
sha256sum "terms.txt"
# Paste 0x followed by the hash, without the filename.
A successful calculation replaces the digest. Failure or cancellation keeps the current digest. Editing the URI keeps the digest too, but clears the previous calculation's association with that URI and cancels work tied to the old source. Check both fields before preparing the transaction.
Hash the file, not its URI text; changing even whitespace changes the digest. This field calculates a new digest, rather than verifying bytes against an existing expected digest. A file hash does not prove that the URI serves that file; a URL hash describes the response now, not what a mutable URL will serve later. Each URI is limited to 512 UTF-8 bytes. The contract stores the URI and digest without retrieving or checking the file or matching the digest. Verify retrieved bytes independently against the stored digest.
Onchain references are public. Encrypt sensitive material before publication and hash the exact published bytes. Agree separately on how authorized parties obtain decryption keys; do not put secrets in URLs or agreement fields.
Settlement is followed by withdrawal
Cancellation, approval, arbitration, or timeout closes the agreement and adds its entire amount to one party's withdrawable credit. It does not send funds to that party's wallet. Connect the credited account, open its account-credit view, and prepare withdrawTo. Browsing as that account without its wallet does not authorize withdrawal.
The credit owner sends a separate transaction that withdraws their complete aggregated credit to the entered recipient. This is their unwithdrawn credit from settlements in this escrow contract on this network. Credit in other escrow contracts or networks is separate. The recipient may differ from the credit owner, must be able to receive the native asset, and cannot be zero or the escrow contract. Check that address before signing. A failed transfer reverts the withdrawal.
If a wallet rejects a request or a view refresh fails, inspect the transaction status and reopen the agreement or credit view before retrying. Preparation, submission, confirmation, settlement credit, and withdrawal are separate steps.
Trust and publication boundaries
CamEscrow is immutable and has no owner, pause, fee, sweep, upgrade, or recovery path. Its read-only CamEscrowUI maps core-provided actions into stable route names. CamRoot has an owner who can change the CAM pointer and address bindings; integrity pins protect byte identity.
For public-network planning and verification, use the Escrow deployment guide and shared release workflow.
Deploy your own instance
This is an example app guide. Use the app deployment guide for its contract settings, authority handoffs and verification, and the shared deployment guide for the release workflow. Deployment selects your chain and publication; this page supplies no running public instance.