Skip to content

feat(escrow): linear vesting stream escrows - #1767

Merged
mikewheeleer merged 2 commits into
Talenttrust:mainfrom
OkeA-dev:feat/streaming-milestones
Oct 25, 2026
Merged

mikewheeleer merged 2 commits into
Talenttrust:mainfrom
OkeA-dev:feat/streaming-milestones

Conversation

@OkeA-dev

Copy link
Copy Markdown
Contributor

Implements issue #1697: streaming milestones that vest linearly and let the freelancer withdraw accrued pay.

Closes #1697

Summary

  • Add create_stream(client, freelancer, token, amount, start, end) -> u64: creates a one-milestone escrow and stores DataKey::Stream { start, end, withdrawn, stopped_at }. Requires start < end and end - start >= 3600, else EscrowError::InvalidSchedule (appended at the end).
  • Add vested(id) -> i128: amount * (min(now, stop_or_end) - start) / (end - start) clamped to [0, amount], computed overflow-safely.
  • Add withdraw_vested(id): freelancer auth, requires escrow Funded and stream not stopped, requires fully funded; pays vested - withdrawn; when fully vested and withdrawn marks milestone released and escrow Completed. Emits ("stream_wd", id) -> amount.
  • Add stop_stream(id): client auth, requires Funded and not stopped, requires fully funded; sets stopped_at = now, pays vested-but-unwithdrawn to freelancer, refunds unvested remainder to client, marks status Cancelled. Emits ("stream_st", id) -> (to_freelancer, to_client).
  • release_milestone and cancel_contract return InvalidStatus on stream escrows (streams use their own functions).
  • Add get_stream(id).
  • EscrowError::InvalidSchedule = 20 appended at the end (no renumbering).

Security checklist

  • Can the freelancer withdraw more than vested? No. withdraw_vested computes vested from time/stop and pays only vested - withdrawn; checks reject if vested <= withdrawn or nothing to withdraw.
  • Can stopping a stream claw back vested pay? No. stop_stream pays vested - withdrawn to the freelancer (and only refunds unvested remainder to the client). Vested amounts are not clawed back.

WASM size

  • Before (origin/main c970981): 38,481 bytes
  • After (this PR): 46,372 bytes
  • Delta: +7,891 bytes (+20.5%) — under 128 KB

Full cargo test output

    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.36s
     Running unittests src/lib.rs (target/debug/deps/escrow-a002c7a24891b423)

running 13 tests
test test::initialize_only_once ... ok
test test::non_admin_cannot_void_rating ... ok
test test::full_happy_path ... ok
test test::cancel_unfunded_escrow_does_not_count_cancellation ... ok
test test::pause_blocks_mutations ... ok
test test::dispute_split_and_cancel ... ok
test test::ratings_issued_before_upgrade_return_rating_not_found ... ok
test test::dispute_outcomes_freelancer_client_and_exact_threshold ... ok
test test::rate_then_void_restores_reputation_and_prevents_rerating ... ok
test test::rejects_overfunding_and_double_release ... ok
test test::void_unrated_escrow_fails_with_rating_not_found ... ok
test test::rate_resolved_escrow_succeeds_once_and_cancelled_rejected ... ok
test test::void_one_of_three_ratings_preserves_others ... ok

test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.65s

Docs

  • README.md updated with new functions (create_stream, vested, withdraw_vested, stop_stream, get_stream) and InvalidSchedule error.

@mikewheeleer

Copy link
Copy Markdown
Contributor

hey @OkeA-dev 👋 this PR scans clean and CI is green, but it now has merge conflicts with main — please rebase on the latest main and push, and we'll merge it 🙌

# Conflicts:
#	contracts/escrow/src/lib.rs
@mikewheeleer
mikewheeleer merged commit 649581f into Talenttrust:main Oct 25, 2026
2 checks passed
@mikewheeleer

Copy link
Copy Markdown
Contributor

tidy work — thanks for this!

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.

Add streaming milestones that vest linearly and let the freelancer withdraw accrued pay

2 participants