ROADMAP.md — docforum-escrow
Update this file on every contribution that starts/completes/blocks an item below.
Status: Phase E1 done. Phase E2 done and live on testnet. Phase E3's create_escrow/get_status/release/refund wrapping done; SDK distribution decided (issue #5 closed — GitHub Release tarball, ADR 0003). Phase E4 (security review, issue #6) in progress: threat model drafted (docs/threat-model.md) and the timeout-refund item closed (issue #11 — ADR 0004 + implementation, live on testnet); independent review not yet done — blocking for any mainnet use. Phase E6 (npm publishing) in progress: automated sdk-v* release workflow with provenance, changesets, and a superseding ADR landed; the one-time human npm org/trusted-publishing setup is still pending, so sdk-v0.3.0 is not yet on npm.
Phase E1 — Contract skeleton
- [x] Soroban project scaffold (
contracts/escrow), builds to wasm (cargo build --target wasm32v1-none --release) — 4 passing tests incontracts/escrow/tests/create_escrow.rs. - [x] Deploy to testnet as a no-op — done 2026-09-14. Contract
CABSYY5FZGCCZ3UTBQGC7S357D2FUFLUQUUGJCCFHKGEJZVIFK4SIS2Z, verified live (not just deployed):get_statuson an unknown id correctly errors, and a realcreate_escrowcall locked 0.5 testnet XLM and reported"Funded"back. Full record, including the transaction links:docs/testnet-deployments.md. Closes issue #1. - [x]
create_escrow— locks funds (calls the SEP-41 token contract'stransfer), assigns a sequentialescrow_id, stores payer/payee/amount/ opaquecondition_ref, statusFunded. Rejects non-positive amounts. Proven on testnet, not just in local tests — see above. - [x]
get_status— read-only status query, errors on unknown id. Proven on testnet, not just in local tests — see above.
Phase E2 — Release & refund
- [x]
release— restricted to the escrow'sreleaser(set atcreate_escrowtime, decision recorded indocs/adr/0002— a per-escrow address, not a single contract-level admin), moves funds to payee, only whileFunded. Closes issue #2. - [x]
refund— same auth/state rule, returns funds to payer. Closes issue #3. - [x] Full test suite in
contracts/escrow/tests/release_refund.rs: 7 tests — release happy path, release unauthorized, double-release, refund happy path, refund unauthorized, refund-after-release, release-after-refund. 11/11 tests passing repo-wide (4 existing + 7 new); wasm release build still succeeds. - [x] Redeployed to testnet with the new (Phase E2)
create_escrowsignature — done 2026-09-15. ContractCAXESEEAJILFFCN3PH2HHNMWAAHLEB64UTJHZ6IP7C5BBICT3S3VODSM, supersedes the Phase E1-only deployment (kept as history, not edited). Verified live: a realrelease(funds moved to payee), a realrefund(funds returned to payer), and a real unauthorized-caller rejection (Error(Contract, #3)) — not just the local test suite. Full record:docs/testnet-deployments.md.
Phase E3 — TypeScript SDK
- [x]
@docforum/escrow-sdkwrappingcreate_escrow/get_status/release/refund. Closes issue #4. Originally scoped to wrap onlycreate_escrow/get_status(release/refunddidn't exist yet when #4 was written) — since Phase E2 landed in the same work session, wrapping all four now avoided shipping an SDK immediately stale relative to the contract it wraps.sdk/src/generated/contract-client.ts— machine-generated viastellar contract bindings typescript --wasm ...against the local wasm build (not the live deployment, so it isn't stale relative to source). Never hand-edit; regenerate on contract signature changes.sdk/src/index.ts— the actual public surface: anEscrowClientclass accepting aKeypairdirectly as a signer (converted internally by@stellar/stellar-sdk'sKeypairSigner) — no manual XDR-signing plumbing exposed to consumers (e.g.docforum-core'spaymentsmodule).
- [x] Decided npm vs. GitHub Packages — closes issue #5. Interim: consumed via a tarball attached to a tagged GitHub Release (
sdk-v0.1.0), not GitHub Packages — that registry requires auth even for public packages, which would breaknpm cifor any external Wave contributor's fork. (A git-subdirectory dependency was tried first as a zero-infrastructure option but doesn't actually work against this npm version — see ADR revision note.) Real npm registry publish is the long-term goal once publishing credentials exist (an out-of-band human step, same category as the Drips Wave application itself). Verified end-to-end: installed and imported successfully fromdocforum-core/backend. Seedocs/adr/0003. - [x] Integration test against testnet from the SDK itself, not just the contract's own test suite.
sdk/tests/testnet-integration.test.ts— 3 tests, all passing against the live Phase E2 deployment: full release lifecycle, full refund lifecycle, unauthorized-caller rejection. Deliberately not wired intonpm test/CI (npm run test:integrationonly) — depends on testnet + Friendbot funding liveness, a different reliability profile than this org's other "integration" tests (e.g.docforum-core's self-contained embedded- Postgres booking test). CI runstypecheck+buildonly for the SDK.
Phase E4 — Security review (blocking for any mainnet use)
- [ ] External or community review before any non-testnet deployment. Tracked as issue #6.
- [ ] Documented threat model in
docs/. Part of issue #6. In progress: draft indocs/threat-model.md(self-written by the implementer, so it does not count as the review). Stays unchecked until an independent reviewer has gone through it. - [x] Timeout refund path when the releaser key is lost — issue #11. Decided in
docs/adr/0004(optionalrefund_afterset at creation, after which anyone may refund, still only topayer;releaseis never gated by it; alternatives compared and payee implications for T8 recorded), implemented incontracts/escrow/src/lib.rswith 17 tests incontracts/escrow/tests/timeout_refund.rscovering every edge case the issue lists. Breakingcreate_escrowchange → SDK bindings regenerated,@docforum/escrow-sdk0.3.0 (migration notes in ADR 0004), redeployed to testnet and verified live (docs/testnet-deployments.md, 2026-10-08 entry). Closes issue #11. The threat model's §6 checklist item for this is checked; the operational half of T2 (key backup / multisig releaser) remains open. - [x] Balance-delta check on
create_escrow+ malicious mock-token tests (issue #12). The contract's token balance must rise by exactlyamountacross the funding transfer or the escrow is rejected withBalanceMismatch(error code 6) — a lying transfer, fee-on-transfer, or rebasing token would otherwise createFundedescrows backed by nothing (threat model T5). Three test-only mock tokens intests/malicious_token.rs: lying transfer, short delivery (debits 300, delivers 270), and one that re-entersreleasefrom insidetransfer(T4). The re-entry test pinned the actual host behavior: Soroban rejects re-entry as an unrecoverable context error ("Contract re-entry is not allowed"), aborting the whole invocation — fail-closed. Post-creation clawback remains possible by design and is documented in T5 as accepted residual risk. Not yet on testnet — the current deployment predates it. Closes issue #12. - [ ] Rest of the
docs/threat-model.md§6 pre-mainnet checklist, scoped into issues: #8 write status before transfer + reject contract-self addresses (Trivial), #9 events (Medium), #10 storage TTL (Medium), #15 integrator trust-model docs (Trivial). - [ ] Related, not in the checklist: #13
get_escrowfull-record read (Medium), #14 offline SDK unit tests in CI (Medium).
Phase E6 — SDK & integrator tooling
- [x] Automated npm release workflow (
.github/workflows/release-sdk.yml), triggered by ansdk-v*tag: builds, typechecks, tests, runsnpm publish --provenance, and attaches the tarball to the GitHub Release (kept for existing consumers per ADR 0003). Uses npm trusted publishing (OIDC,id-token: write) so no long-lived token sits in secrets; refuses to publish if the tag doesn't matchpackage.json's version. Closes issue #16. - [x] Adopted changesets (
.changeset/,@changesets/cli) for version bumps and changelogs;sdk/package.jsongainedchangeset/version/releasescripts andpublishConfigwith provenance. - [x] Superseding ADR written:
docs/adr/0005supersedes ADR 0003's interim tarball-only decision now that npm publishing is going live. - [ ] One-time human steps (npm org creation, trusted-publisher setup for the release workflow) — documented in
CONTRIBUTING.md; not yet done, sosdk-v0.3.0is not yet published to npm.
Explicitly out of scope for this repo
- Any healthcare-specific logic (see README).
- Fiat on/off-ramp, KYC — that's
docforum-core's concern if/when it builds general payments (see prior architecture discussion), not this repo's.
Changelog
2026-10-17 — Issue #63 (Phase E6): added SDK
getEventspagination and typed lifecycle-event decoding, preserving unknown topic variants for forward compatibility. Added cursor-resumable serial polling with handler back-pressure and typed RPC-retention errors. Committed Soroban XDR event fixtures cover decoding, filters, unknown topics, cursor resume, retention errors, and stopping. Bumped the additive SDK surface from 0.3.0 to 0.4.0. SDK typecheck/build and contract tests/build pass; lifecycle events remain unavailable until issue #9 changes the contract.2026-09-09 — Repo created as part of the DocForum 3-repo org split.
2026-10-09 — Phase E6 started: added
.github/workflows/release-sdk.yml(tag-triggeredsdk-v*build/test/npm publish --provenance+ GitHub Release tarball, OIDC trusted publishing, tag/version guard), adopted changesets, wrotedocs/adr/0005superseding ADR 0003, and documented the one-time npm org/trusted-publishing human steps inCONTRIBUTING.md. npm publish itself still pending those human steps. Closes issue #16.2026-09-14 — Phase E1 implemented:
create_escrow/get_statusonsoroban-sdk27.0.6, 4 tests passing, wasm build verified. Testnet deploy intentionally left undone (needs a funded identity + CLI this environment doesn't have) — do not mark E1 fully done until that lands.2026-09-14 — Phase E1 fully closed: deployed to testnet (
CABSYY5FZGCCZ3UTBQGC7S357D2FUFLUQUUGJCCFHKGEJZVIFK4SIS2Z) via thestellarCLI, and verified live with a realcreate_escrowcall (0.5 testnet XLM, native SAC) followed byget_statusconfirming"Funded". Seedocs/testnet-deployments.md. Issue #1 closed.2026-09-14 — Added
.github/ISSUE_TEMPLATE/bug_report.md(identical copy across all three org repos — seedocforum-core's changelog for the sourcing note: a user-supplied bug-report structure plus drips.network's "Creating Meaningful Issues" guide).2026-09-14 — Replaced the placeholder
ci.yml(echo "TODO",pull_request-only, never once run — every commit so far went straight tomain) with a real workflow:cargo test+cargo build --target wasm32v1-none --releaseagainst the actual contract incontracts/escrow/, triggered on both push tomainand PRs, with a final check that the wasm artifact actually exists. SDK build/test intentionally left out of CI —sdk/package.json'sbuild/testscripts are stillecho TODOplaceholders (issue #4), nothing real to validate yet; add them to this workflow when #4 lands. Done ahead of a Drips Wave application: a reviewer or prospective contributor landing on this repo should see it actually exercising its own tests, not a stub.2026-09-15 — General workspace-audit fixes (applied identically across all three org repos, see
docforum-core's changelog for the full rationale): addedCONTRIBUTING.md(human onboarding —AGENTS.mdis agent-facing, and this repo's contract moves real funds, so it also notes reporting security issues privately rather than as a public issue, ahead of the planned Phase E4 review); enabled branch protection onmain(real CI check + 1 approval required to merge, force-push/ deletion disabled,enforce_adminsleftfalseso the maintainer isn't blocked). Topics were already added in the prior session.2026-09-15 — Implemented Phase E2:
release()andrefund(). Auth model decision recorded indocs/adr/0002— a per-escrowreleaseraddress, set atcreate_escrowtime, rather than a single contract-level admin (reasoning: no single point of control, matches who actually knows when to release/refund, stays generic per ADR 0001, multi-tenant ready). This changedcreate_escrow's signature (addedreleaser: Address) — a breaking change from the Phase E1 signature already live on testnet; that deployment record stays as accurate history, not retroactively edited. 7 new tests intests/release_refund.rscovering both functions' happy paths, unauthorized-caller rejection, double-release, refund-after-release, and release-after-refund. 11/11 tests passing, wasm release build verified.ARCHITECTURE_ESSENTIALS.mdcontract-surface table updated. Closes issues #2 and #3. Not yet done: redeploying to testnet with the new signature — Phase E2 is proven in the local test suite only so far, not live.2026-09-15 — Redeployed to testnet with the Phase E2 signature (
CAXESEEAJILFFCN3PH2HHNMWAAHLEB64UTJHZ6IP7C5BBICT3S3VODSM, supersedes the Phase E1-only deployment) and verifiedrelease/refundlive: real fund transfers on both paths, plus a real unauthorized-caller rejection (Error(Contract, #3)) confirmed via the contract's own diagnostic events, not just a local assertion. Closes the "not yet done" gap from the previous entry. Full record:docs/testnet-deployments.md.2026-09-15 — Built the TypeScript SDK (
@docforum/escrow-sdk), closing issue #4:sdk/src/generated/contract-client.ts(machine-generated from the local wasm build viastellar contract bindings typescript) plus a hand-writtenEscrowClientinsdk/src/index.tswrapping all four contract functions — expanded from #4's original create_escrow/get_status-only scope since Phase E2 landed in the same session, so there was no reason to ship an immediately-stale SDK. Accepts aKeypairdirectly as a signer (no manual XDR signing exposed). 3 real tests insdk/tests/testnet-integration.test.ts, all passing against the live Phase E2 deployment — full release lifecycle, full refund lifecycle, unauthorized-caller rejection — satisfying Phase E3's "integration test against testnet from the SDK itself" item. Deliberately kept out ofnpm test/CI (npm run test:integrationonly, CI runstypecheck+build) given its dependency on live testnet + Friendbot funding, a different reliability profile than this org's other self-contained integration tests.ARCHITECTURE_ESSENTIALS.mdupdated with the SDK's actual public surface.2026-09-15 — Closed issue #5: decided not GitHub Packages (its npm registry requires auth even for public packages — would break
npm cifor any external contributor's fork, exactly what the issue warned against). First tried an npm git-subdirectory dependency (github:DocForum/docforum-escrow#path:sdk) as a zero-infrastructure option — tested it againstdocforum-coreand it failed (npm 10.8.2 doesn't support the subdirectory selector the way assumed). Replaced with a tarball attached to a real tagged GitHub Release (sdk-v0.1.0, vianpm pack+gh release create) — verified working end-to-end: installed and imported successfully fromdocforum-core/backendwith zero auth. Added aprepare: npm run buildscript tosdk/package.json(unused by this specific approach, but harmless to keep for a future git- or workspace-based route). Real npm registry publish remains the long-term goal, blocked on publishing credentials this environment doesn't have. Seedocs/adr/0003.2026-09-15 — SDK v0.2.0 (breaking, per hard rule 3):
createEscrow,release, andrefundnow also return the transaction's hash ({ escrowId, txHash }/{ txHash }) instead of discarding it — needed bydocforum-core's payments module to persiststellarTxHash. All 3 live-testnet tests updated and still passing. Republished as GitHub Releasesdk-v0.2.0(see ADR 0003's release process);docforum-coreupdated to depend on the new tarball URL.2026-09-15 — Fixed two real bugs in
docs/adr/0003that brokedocforum-core's VitePress docs-site build entirely ("Element is missing end tag" — VitePress parses markdown through a Vue template compiler, which can misread certain angle-bracket placeholder text as an unclosed HTML tag): a placeholder phrase with a space inside angle brackets, and — found only after the first fix still didn't build — two separate bracketed placeholders inside one inline code span. Both rephrased to avoid angle-bracket placeholder syntax entirely rather than guessing at a safe form. Caught, and actually fixed for real (not just believed fixed), while checking the live docs site reflects this session's changes rather than assuming it did — verified by buildingdocforum-core's docs site locally against this exact file, twice, before trusting it.2026-09-26 — Updated the stale top status line (still said issue #5 was open; it closed 2026-09-15).
2026-10-01 — Drafted
docs/threat-model.mdfor Phase E4 (issue #6): invariants, actors, ten threats with severity, SDK and consumer integration notes, and a proposed pre-mainnet checklist. No contract changes. Self-written, so E4 stays open pending independent review.2026-10-08 — Closed issue #11 (Phase E4: timeout refund path when the releaser key is lost). ADR first:
docs/adr/0004picks the threat model's candidate — an optionalrefund_afterunix timestamp set atcreate_escrow, after which anyone mayrefund, always only back topayer— and compares it against a privileged unlock role, releaser rotation, and doing nothing; it also spells out what the deadline means for payees (T8): the commitment expires,get_escrowlets them read it, and the new risk is bounded to "delayed then reversed", never redirected. Implementation:refund_after: Option<u64>added tocreate_escrow(rejects a deadline not strictly in the future — newError::InvalidRefundAfter = 5),refundaccepts any caller once the deadline has passed (state rules unchanged: stillFundedonly),releasedeliberately not gated by the deadline (the releaser keeps its right; whoever lands first wins, the loser getsNotFunded), plus a read-onlyget_escrowso the deadline is inspectable on-chain. With norefund_after, behaviour is exactly what it was before. Tests: 17 new intests/timeout_refund.rscovering every edge case issue #11 lists (release in the same ledger the timeout passes — both orders; deadline already past at creation; already released/refunded before the timeout; no deadline at all) — 28/28 passing repo-wide, wasm release build verified. Breaking change:create_escrow's signature andEscrowData's layout both changed, so the contract was redeployed to testnet (CAHQ4J3T23WSIBBG6SI6EAYR5Q2HIPKG5YMC4SMZBOTYA6MYK2VADEV5, supersedes the Phase E2 deployment, kept as history) and verified live with a real non-releaser timeout refund after the deadline, a real release after the deadline, and real rejections for a pre-deadline stranger refund (#3), a past deadline at creation (#5) and a second refund (#4) — full record with tx links indocs/testnet-deployments.md. SDK: bindings regenerated from the new wasm (never hand-edited),@docforum/escrow-sdk0.2.0 → 0.3.0 (breaking, per hard rule 3; same 0.x-minor-for-breaking convention as 0.1.0 → 0.2.0), adding optionalrefundAfteroncreateEscrowand agetEscrow()accessor; migration steps fordocforum-coreare in ADR 0004. All 5 SDK integration tests pass against the new deployment (npm run test:integration, still kept out of CI).ARCHITECTURE_ESSENTIALS.md,README.mdanddocs/threat-model.md(§1 invariants, T2, T8, §6) updated. Not done: publishing thesdk-v0.3.0release tarball per ADR 0003 — that's the follow-up step beforedocforum-corecan consume it.2026-10-08 — Fixed
mainfailing to compile after #17 and #18 merged minutes apart: both added an error variant numbered 5, and the merge kept #18'sInvalidRefundAfter = 5and dropped #17'sBalanceMismatch. Restored it asBalanceMismatch = 6(5 is already live on testnet asInvalidRefundAfter), and addedrefund_after: Noneto #17's mock-token tests, written before #18 changedcreate_escrow's signature. 31/31 contract tests pass, wasm build succeeds. Not yet done: the current testnet deployment (source7a595d8) predates #17, so it doesn't include the balance-delta check — needs a redeploy.2026-10-08 — Restored two sets of ROADMAP entries lost in merge conflict resolution: #17's (issue #12, balance-delta check — Phase E4 item and its 2026-10-07 changelog entry, summarised here: contract rejects escrows whose token balance doesn't rise by exactly
amount, three malicious mock-token tests, re-entry confirmed fail-closed), and #16's (links to the threat-model issues #8–#10 and #13–#15; #11 and #12 were opened and closed in between). #16 merged with an empty diff because its branch's merge frommainkeptmain's ROADMAP.md wholesale.