Skip to content

About

Rust Soroban smart contract (create/release/refund escrow) + a thin TypeScript client SDK wrapping it. No Postgres, no PHI, no healthcare domain logic at all — deliberately generic, same category as Trustless Work's repo.

Topics

Resources

Contributing

Stars

22 stars

Watchers

0 watching

Forks

Repository files navigation

docforum-escrow

License Status Stack Network

🔗 Live on testnet: CAHQ4J3T23WSIBBG6SI6EAYR5Q2HIPKG5YMC4SMZBOTYA6MYK2VADEV5 — current deployment (Phase E4: adds the refund_after timeout refund), verified with real calls, not just uploaded. History and details: docs/testnet-deployments.md.

A Soroban (Stellar) smart contract for conditional payment escrow — lock funds, and release them to the payee only when release is authorized, or return them to the payer on refund. Paired with a TypeScript client SDK, @docforum/escrow-sdk.

This repo is deliberately generic and domain-agnostic. It has no database, no server, and no knowledge of patients, doctors, referrals, or any other healthcare concept — it is a reusable create/release/refund escrow primitive, in the same category as other Stellar ecosystem escrow projects (e.g. Trustless Work). Anything domain-specific — what condition triggers a release, who the parties are — lives in the consuming application, not here.

Table of contents

Why this repo exists, honestly

Two reasons, stated plainly rather than blended together:

  1. Product reason. docforum-core's facility-fulfillment flow needs a way to hold payment until a facility confirms it did the work, without trusting the facility's self-report alone.
  2. Ecosystem reason. This is the one piece of the DocForum organization that's actually built on Stellar, as opposed to an application that merely accepts Stellar payments — see docforum-core's ADR 0002 for why that distinction shaped how the org is structured, and docs/adr/0001-generic-escrow-not-healthcare-specific.md for why the contract itself stays domain-blind.

Contract surface

Function Status Description
create_escrow(payer, payee, token, amount, condition_ref, releaser, refund_after) -> escrow_id ✅ Implemented Pulls amount of token from payer (requires payer auth), stores an opaque condition_ref, sets status Funded. Rejects non-positive amounts, and any refund_after that isn't strictly in the future.
get_status(escrow_id) -> EscrowStatus ✅ Implemented Read-only. Errors on an unknown id.
get_escrow(escrow_id) -> EscrowData ✅ Implemented Read-only: everything stored about the escrow, including releaser and refund_after, so payees can check the deal before relying on it.
release(escrow_id, caller) -> Result<()> ✅ Implemented Restricted to the escrow's designated releaser; moves funds to payee. Never gated by refund_after.
refund(escrow_id, caller) -> Result<()> ✅ Implemented Returns funds to payer. The releaser may always call it; once refund_after has passed, anyone may — still only back to payer (see ADR 0004).
EscrowStatus = Funded | Released | Refunded

refund_after is optional: an escrow created without one behaves exactly as it did before the timeout existed — releaser-only refunds, forever. That's the answer to "what if the releaser's key is lost?" (threat model T2): instead of locking funds permanently, a deadline lets anyone return them to the payer. See ADR 0004 for the decision and what it means for payees.

Design rule, enforced deliberately: the contract decides who may call release/refund, never why. condition_ref is an opaque string the contract stores but never interprets — all domain interpretation (what counts as "fulfilled," who's authorized to attest to it) is the consuming application's job. See ADR 0001.

How it's used

sequenceDiagram
    participant Core as docforum-core (payments module)
    participant SDK as @docforum/escrow-sdk
    participant Contract as docforum-escrow contract

    Core->>SDK: createEscrow(payer, payee, token, amount, conditionRef)
    SDK->>Contract: create_escrow(...)
    Contract-->>SDK: escrow_id (status: Funded)
    Note over Core: order fulfilled, confirmed off-chain
    Core->>SDK: release(escrowId)
    SDK->>Contract: release(escrow_id, caller)
    Contract-->>SDK: status: Released — funds sent to payee
Loading

docforum-core imports @docforum/escrow-sdk as a library dependency — it never calls this contract as a running network service, and this repo never crosses back into docforum-core's database or domain model.

Repo layout

contracts/escrow/
  src/lib.rs        Contract implementation
  tests/            Integration tests (soroban-sdk testutils) — required for any contract change
  Cargo.toml
sdk/
  src/index.ts      @docforum/escrow-sdk — TypeScript client (not yet implemented, see issue #4)
  package.json
docs/adr/           Architecture decision records
.github/            CI workflow, issue/PR templates

Getting started

Requires a Rust toolchain with the wasm32v1-none target.

git clone https://github.com/DocForum/docforum-escrow.git
cd docforum-escrow/contracts/escrow

# Run the test suite (28 tests, all passing today)
cargo test

# Build the deployable contract
cargo build --target wasm32v1-none --release
# → target/wasm32v1-none/release/docforum_escrow.wasm

Already deployed to Stellar testnet — see docs/testnet-deployments.md for the contract id, transaction links, and the exact stellar CLI commands to redeploy. Mainnet is out of scope until the Phase E4 security review is complete — see hard rule 4.

The TypeScript SDK (sdk/) is still a placeholder — see issue #4.

Documentation

Doc Purpose
ARCHITECTURE_ESSENTIALS.md Stack, contract surface, hard rules (no PHI ever, tests required for contract changes, testnet-only until security review).
ROADMAP.md Phase E1–E4 breakdown and current status.
AGENTS.md / CLAUDE.md Rules for coding agents working in this repo — read README.md + ARCHITECTURE_ESSENTIALS.md first; you should rarely need docforum-core's docs to work here.
docs/adr/0001-generic-escrow-not-healthcare-specific.md Why the contract stays domain-blind.

Project status

Phase E1 (contract skeleton): done. create_escrow and get_status are implemented, tested, and deployed live on testnet — not just locally tested. Phases E2 (release/refund), E3 (TypeScript SDK), and E4 (security review, blocking for any mainnet use) are tracked as open issues:

Issue Phase Complexity
#1 Deploy to testnet — done, see above E1 Trivial
#2 Implement release() E2 High
#3 Implement refund() E2 High
#4 SDK: wrap create_escrow/get_status E3 Medium
#5 ADR: npm vs GitHub Packages E3 Trivial
#6 Threat model + external security review E4 High

Full breakdown: ROADMAP.md.

Contributing

Issues are scoped as small, independent units with a stated complexity (Trivial/Medium/High) — see the good-first-issue template. Any contract logic change must include tests in contracts/escrow/tests/ in the same PR — see AGENTS.md hard rules. This repo targets the Stellar ecosystem's Drips Wave program; see docforum-core's ADR 0002 for the readiness plan.

Questions, or want to talk through an issue before picking it up? Join the DocForum contributor chat on Telegram.

License

Apache License 2.0

About

Rust Soroban smart contract (create/release/refund escrow) + a thin TypeScript client SDK wrapping it. No Postgres, no PHI, no healthcare domain logic at all — deliberately generic, same category as Trustless Work's repo.

Topics

Resources

Contributing

Stars

22 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages