Skip to content

feat(contracts): cross-contract settlement router with auth-chained escrow -> reputation -> loyalty atomicity - #50

Merged
meshackyaro merged 2 commits into
workman-labs:developmentfrom
DeborahOlaboye:feat/settlement-router-atomic-escrow-reputation-loyalty
Aug 23, 2026
Merged

feat(contracts): cross-contract settlement router with auth-chained escrow -> reputation -> loyalty atomicity#50
meshackyaro merged 2 commits into
workman-labs:developmentfrom
DeborahOlaboye:feat/settlement-router-atomic-escrow-reputation-loyalty

Conversation

@DeborahOlaboye

Copy link
Copy Markdown
Contributor

Summary

Closes #38.

Introduces contracts/settlement-router, which atomically drives escrow release, reputation attestation, and loyalty emission from a single settle(appointment_id, rating, attestation_hash) call, so a completed appointment settles as one indivisible on-chain unit instead of three independently callable (and independently spoofable) entrypoints.

  • settle proves the appointment is Funded in escrow before doing anything else, then calls escrow.confirm_completion, reputation.submit_attestation, and loyalty-token.mint in one transaction. Any Err from a sub-contract, or a pause on its side, panics the whole invocation — nothing partially commits.
  • Idempotent per appointment_id, checked before any cross-contract call, and enforced twice over: this router's own Settled marker, plus escrow.confirm_completion independently refusing a second call once the appointment is no longer Funded.
  • reputation gains an opt-in set_router/get_router (admin-only). Once set, submit_attestation additionally requires that router's own authorization alongside the client's — closing the gap where any appointment_id could be attested with no proof it was ever funded or completed on-chain. A contract address can only satisfy that requirement by directly executing the call, so this cannot be forged by an externally-owned account.
  • loyalty-token.mint needs no code change — pointing its existing minter role at this router (set_minter) is what gates it, the same migration loyalty-emissions already models.
  • Reward amounts are a fixed, admin-configured RewardConfig — never taken from a settle caller's own arguments, so a caller cannot name their own mint amount.
  • Guarded by the existing shared SCOPE_SETTLEMENT (no new pause scope introduced).

See the crate-level docs in contracts/settlement-router/src/lib.rs (settlement flow, idempotency, reputation gating, loyalty minting, and the "Deploying under partial rollout" wiring order) and the new ### settlement-router section in README.md for the full design writeup, storage layout, and CLI usage.

Test plan

  • cargo test --workspace — 289 tests pass, including 14 new settlement-router tests (happy path, idempotent replay, rejected unfunded/cancelled/disputed/already-completed appointments, atomic revert when reputation/loyalty-token/the router itself is paused, reward-config validation, zero-reward skip) and new reputation router-gating tests (legacy behavior preserved when unset, direct calls rejected once a router is configured).
  • cargo fmt --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo build --workspace --release --target wasm32v1-none (optimized WASM build)

…scrow -> reputation -> loyalty atomicity

Adds contracts/settlement-router, which atomically drives escrow release,
reputation attestation, and loyalty emission from one settle() call instead
of three independently callable (and independently spoofable) entrypoints.
reputation gains an opt-in set_router/get_router gate so submit_attestation
can no longer be forged for an appointment that was never funded and
completed on-chain; loyalty-token needs no code change since minting is
already gated purely by re-pointing its existing minter role at the router.

Closes workman-labs#38
@meshackyaro

Copy link
Copy Markdown
Contributor

🎯 Excellent work on the cross-contract settlement router

This PR is exactly how you build a multi-contract orchestrator in Soroban. The design is thoughtful, the implementation is clean, and the testing is genuinely comprehensive.

What stands out:

  1. Clear atomicity semantics. settle is deliberately "fail shut" — any sub-contract error aborts the entire transaction. No partial commits, no reconciliation needed. That's the right choice, and it's well-documented.

  2. Reputation gating is elegant. By leveraging Soroban's requirement that a contract address can only satisfy require_auth() for itself, you've closed the original "any appointment_id" hole without inventing new trust assumptions. The opt-in nature of set_router preserves backward compatibility perfectly.

  3. Idempotency done right. Writing the Settled marker before any cross-contract call, combined with escrow::confirm_completion's own guard, is defense in depth. A replay is rejected before touching anything else.

  4. Test coverage is excellent. 14 tests spanning happy path, idempotency, all rejection states, atomic failure modes, and the reputation router gate. Every test name tells you exactly what it's verifying.

  5. Documentation is load-bearing. The crate-level docs (740 lines) walk through the settlement flow, authorization model, idempotency, reputation gating, and — most importantly — the "Deploying under partial rollout" sequencing. That's exactly the right level of detail for an operator who has to ship this.

  6. Governance integration is proper. Reusing governance-guard and the shared SCOPE_SETTLEMENT means a broadcast pause reaches this router with the same call it already sends everywhere else. No new entrypoint surface to maintain.

  7. No shortcuts. The error enum properly translates from sub-contracts, storage durability is thoughtful, the Rust idioms are sound. This feels like production code, not a first pass.

The "fails shut" trade-off is intentional and correct, but it does mean operators must follow the deployment sequencing in the README, or a completed appointment's payout gets gated on sub-contract liveness. That's already clearly documented — just wanted to flag it as a load-bearing requirement before mainnet deployment.

Ready to merge. Well done!

@meshackyaro
meshackyaro merged commit 583803d into workman-labs:development Aug 23, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-Contract Settlement Router with Auth-Chained Escrow → Reputation → Loyalty Atomicity

2 participants