Skip to content

Commit f2cc018

Browse files
author
O.Omokaro
committed
feat: pivot to escrow contract for Stellar Checkout
Replace token streaming contract with escrow functionality: - create_escrow, release, refund, get_escrow functions - Escrow struct with Active/Released/Refunded status - Timeout-based refund protection - Tests for create, release, refund, and timeout scenarios - Remove stream, math modules
1 parent 8d34375 commit f2cc018

13 files changed

Lines changed: 346 additions & 519 deletions

CLAUDE.md

Lines changed: 23 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,41 @@
1-
# OrbitStream Contracts
1+
# Stellar Checkout Contracts
22

33
## Project
4-
Soroban smart contract for OrbitStream — a token streaming/payroll platform on Stellar blockchain.
4+
Soroban smart contract for Stellar Checkout — escrow functionality for dispute-prone payments.
55

66
## Stack
7-
- Rust
8-
- Soroban SDK
7+
- Rust (edition 2021)
8+
- Soroban SDK v21
99
- Stellar Testnet / Mainnet
1010

1111
## What This Contract Does
12-
Handles salary payroll streams on Stellar. Employers create streams to employees,
13-
employees claim accrued tokens anytime (pull-based).
14-
15-
## Core Data Structure
16-
- Stream: id, employer, employee, token, rate_per_second, deposited, withdrawn,
17-
start_time, end_time, pause_time, paused_duration, status
12+
Manages escrowed payments for marketplace and freelance transactions. Buyers deposit funds
13+
that are locked until released by seller or refunded after timeout.
1814

1915
## Contract Functions
20-
### Employer
21-
- create_stream(employer, employee, token, rate_per_second, start_time, end_time, deposit)
22-
- top_up(stream_id, amount)
23-
- pause_stream(stream_id)
24-
- resume_stream(stream_id)
25-
- cancel_stream(stream_id)
26-
- update_rate(stream_id, new_rate)
27-
28-
### Employee
29-
- claim(stream_id)
30-
- claim_partial(stream_id, amount)
31-
32-
### Read
33-
- get_stream(stream_id)
34-
- get_claimable(stream_id)
35-
- get_streams_by_employer(employer)
36-
- get_streams_by_employee(employee)
16+
- `create_escrow(buyer, seller, token, amount, timeout_seconds) -> escrow_id` — lock funds
17+
- `release(escrow_id)` — seller releases funds to themselves
18+
- `refund(escrow_id)` — buyer claims refund after timeout
19+
- `get_escrow(escrow_id) -> Escrow` — read escrow details
3720

38-
## Claimable Math
39-
elapsed = min(now, end_time) - start_time - paused_duration
40-
accrued = elapsed * rate_per_second
41-
claimable = min(accrued, deposited) - withdrawn
21+
## Data Structure
22+
Escrow: id, buyer, seller, token, amount, status (Active/Released/Refunded), created_at, timeout_at
4223

4324
## File Structure
4425
src/
45-
lib.rs - contract entry point
46-
stream.rs - Stream struct + StreamStatus enum
26+
lib.rs - contract entry point (StellarCheckoutEscrow)
27+
escrow.rs - Escrow struct + EscrowStatus enum
4728
storage.rs - read/write contract storage
48-
math.rs - claimable calculation
49-
events.rs - all event definitions
29+
events.rs - EscrowCreated, EscrowReleased, EscrowRefunded
5030
errors.rs - custom error types
5131
tests/
52-
test_create.rs
53-
test_claim.rs
54-
test_pause_resume.rs
55-
test_cancel.rs
32+
test_create_escrow.rs
33+
test_release.rs
34+
test_refund.rs
5635

5736
## Key Rules
58-
- No business logic outside of contract (no user profiles, no analytics)
59-
- All state changes must emit events
60-
- Protect against overflow/underflow everywhere
61-
- Only employer can pause/cancel/top_up/update_rate
62-
- Only employee can claim
63-
- Token is USDC on Stellar by default, but contract is multi-token
37+
- Only buyer can create escrow and request refund
38+
- Only seller can release funds
39+
- Refund only valid after timeout
40+
- All state changes emit events
41+
- Protect against invalid amounts and timeouts

src/errors.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,10 @@ use soroban_sdk::contracterror;
33
#[contracterror]
44
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
55
pub enum ContractError {
6-
StreamNotFound = 1,
6+
EscrowNotFound = 1,
77
Unauthorized = 2,
8-
InsufficientBalance = 3,
9-
InvalidStream = 4,
10-
OverflowError = 5,
11-
UnderflowError = 6,
12-
InvalidTimeRange = 7,
13-
StreamAlreadyPaused = 8,
14-
StreamNotPaused = 9,
15-
StreamCancelled = 10,
16-
InvalidAmount = 11,
17-
InvalidRate = 12,
8+
EscrowAlreadySettled = 3,
9+
TimeoutNotReached = 4,
10+
InvalidAmount = 5,
11+
InvalidTimeout = 6,
1812
}

src/escrow.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use soroban_sdk::{contracttype, Address};
2+
3+
#[derive(Clone, Copy, PartialEq, Eq)]
4+
#[contracttype]
5+
pub enum EscrowStatus {
6+
Active = 0,
7+
Released = 1,
8+
Refunded = 2,
9+
}
10+
11+
#[derive(Clone)]
12+
#[contracttype]
13+
pub struct Escrow {
14+
pub id: u64,
15+
pub buyer: Address,
16+
pub seller: Address,
17+
pub token: Address,
18+
pub amount: u128,
19+
pub status: EscrowStatus,
20+
pub created_at: u64,
21+
pub timeout_at: u64,
22+
}

src/events.rs

Lines changed: 19 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,40 @@
1-
use soroban_sdk::{contracttype, Address, Env, Symbol, Val};
1+
use soroban_sdk::{contracttype, Address, Env, Symbol};
22

33
#[contracttype]
4-
pub struct StreamCreatedEvent {
5-
pub stream_id: u64,
6-
pub employer: Address,
7-
pub employee: Address,
4+
pub struct EscrowCreatedEvent {
5+
pub escrow_id: u64,
6+
pub buyer: Address,
7+
pub seller: Address,
88
pub token: Address,
9-
pub rate_per_second: u128,
10-
pub deposited: u128,
11-
pub start_time: u64,
12-
pub end_time: u64,
13-
}
14-
15-
#[contracttype]
16-
pub struct StreamTopUpEvent {
17-
pub stream_id: u64,
189
pub amount: u128,
10+
pub timeout_at: u64,
1911
}
2012

2113
#[contracttype]
22-
pub struct StreamClaimedEvent {
23-
pub stream_id: u64,
24-
pub employee: Address,
14+
pub struct EscrowReleasedEvent {
15+
pub escrow_id: u64,
16+
pub seller: Address,
2517
pub amount: u128,
2618
}
2719

2820
#[contracttype]
29-
pub struct StreamPausedEvent {
30-
pub stream_id: u64,
31-
}
32-
33-
#[contracttype]
34-
pub struct StreamResumedEvent {
35-
pub stream_id: u64,
36-
}
37-
38-
#[contracttype]
39-
pub struct StreamCancelledEvent {
40-
pub stream_id: u64,
41-
}
42-
43-
#[contracttype]
44-
pub struct StreamRateUpdatedEvent {
45-
pub stream_id: u64,
46-
pub new_rate: u128,
47-
}
48-
49-
pub fn emit_stream_created(env: &Env, event: StreamCreatedEvent) {
50-
env.events()
51-
.publish((Symbol::new(env, "stream_created"),), event);
52-
}
53-
54-
pub fn emit_stream_top_up(env: &Env, event: StreamTopUpEvent) {
55-
env.events()
56-
.publish((Symbol::new(env, "stream_top_up"),), event);
57-
}
58-
59-
pub fn emit_stream_claimed(env: &Env, event: StreamClaimedEvent) {
60-
env.events()
61-
.publish((Symbol::new(env, "stream_claimed"),), event);
62-
}
63-
64-
pub fn emit_stream_paused(env: &Env, event: StreamPausedEvent) {
65-
env.events()
66-
.publish((Symbol::new(env, "stream_paused"),), event);
21+
pub struct EscrowRefundedEvent {
22+
pub escrow_id: u64,
23+
pub buyer: Address,
24+
pub amount: u128,
6725
}
6826

69-
pub fn emit_stream_resumed(env: &Env, event: StreamResumedEvent) {
27+
pub fn emit_escrow_created(env: &Env, event: EscrowCreatedEvent) {
7028
env.events()
71-
.publish((Symbol::new(env, "stream_resumed"),), event);
29+
.publish((Symbol::new(env, "escrow_created"),), event);
7230
}
7331

74-
pub fn emit_stream_cancelled(env: &Env, event: StreamCancelledEvent) {
32+
pub fn emit_escrow_released(env: &Env, event: EscrowReleasedEvent) {
7533
env.events()
76-
.publish((Symbol::new(env, "stream_cancelled"),), event);
34+
.publish((Symbol::new(env, "escrow_released"),), event);
7735
}
7836

79-
pub fn emit_stream_rate_updated(env: &Env, event: StreamRateUpdatedEvent) {
37+
pub fn emit_escrow_refunded(env: &Env, event: EscrowRefundedEvent) {
8038
env.events()
81-
.publish((Symbol::new(env, "stream_rate_updated"),), event);
39+
.publish((Symbol::new(env, "escrow_refunded"),), event);
8240
}

0 commit comments

Comments
 (0)