🔗 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.
- Why this repo exists
- Contract surface
- How it's used
- Repo layout
- Getting started
- Documentation
- Project status
- Contributing
- License
Two reasons, stated plainly rather than blended together:
- 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. - Ecosystem reason. This is the one piece of the
DocForumorganization that's actually built on Stellar, as opposed to an application that merely accepts Stellar payments — seedocforum-core's ADR 0002 for why that distinction shaped how the org is structured, anddocs/adr/0001-generic-escrow-not-healthcare-specific.mdfor why the contract itself stays domain-blind.
| 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.
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
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.
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
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.wasmAlready 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.
| 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. |
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 |
|---|---|---|
| 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.
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.