Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 27 additions & 22 deletions contracts/payment-vault-contract/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use soroban_sdk::{Address, Env, token};
use crate::storage;
use crate::types::{BookingRecord, BookingStatus};
use crate::error::VaultError;
use crate::events;
use crate::storage;
use crate::types::{BookingRecord, BookingStatus};
use soroban_sdk::{token, Address, Env};

pub fn initialize_vault(
env: &Env,
admin: &Address,
token: &Address,
oracle: &Address
oracle: &Address,
) -> Result<(), VaultError> {
// 1. Check if already initialized
if storage::has_admin(env) {
Expand All @@ -23,16 +23,32 @@ pub fn initialize_vault(
Ok(())
}

pub fn set_my_rate(env: &Env, expert: &Address, rate_per_second: i128) -> Result<(), VaultError> {
expert.require_auth();

if rate_per_second <= 0 {
return Err(VaultError::InvalidAmount);
}

storage::set_expert_rate(env, expert, rate_per_second);
events::expert_rate_updated(env, expert, rate_per_second);

Ok(())
}

pub fn book_session(
env: &Env,
user: &Address,
expert: &Address,
rate_per_second: i128,
max_duration: u64,
) -> Result<u64, VaultError> {
// Require authorization from the user creating the booking
user.require_auth();

// Fetch the expert's rate
let rate_per_second =
storage::get_expert_rate(env, expert).ok_or(VaultError::ExpertRateNotSet)?;

// Validate rate
if rate_per_second <= 0 {
return Err(VaultError::InvalidAmount);
Expand Down Expand Up @@ -89,8 +105,7 @@ pub fn finalize_session(
oracle.require_auth();

// 2. Get booking and verify it exists
let booking = storage::get_booking(env, booking_id)
.ok_or(VaultError::BookingNotFound)?;
let booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?;

// 3. Verify booking is in Pending status
if booking.status != BookingStatus::Pending {
Expand Down Expand Up @@ -134,17 +149,12 @@ pub fn finalize_session(
/// 24 hours in seconds
const RECLAIM_TIMEOUT: u64 = 86400;

pub fn reclaim_stale_session(
env: &Env,
user: &Address,
booking_id: u64,
) -> Result<(), VaultError> {
pub fn reclaim_stale_session(env: &Env, user: &Address, booking_id: u64) -> Result<(), VaultError> {
// 1. Require user authorization
user.require_auth();

// 2. Get booking and verify it exists
let booking = storage::get_booking(env, booking_id)
.ok_or(VaultError::BookingNotFound)?;
let booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?;

// 3. Verify the caller is the booking owner
if booking.user != *user {
Expand Down Expand Up @@ -177,17 +187,12 @@ pub fn reclaim_stale_session(
Ok(())
}

pub fn reject_session(
env: &Env,
expert: &Address,
booking_id: u64,
) -> Result<(), VaultError> {
pub fn reject_session(env: &Env, expert: &Address, booking_id: u64) -> Result<(), VaultError> {
// 1. Require expert authorization
expert.require_auth();

// 2. Get booking and verify it exists
let booking = storage::get_booking(env, booking_id)
.ok_or(VaultError::BookingNotFound)?;
let booking = storage::get_booking(env, booking_id).ok_or(VaultError::BookingNotFound)?;

// 3. Verify the caller is the expert in the booking
if booking.expert != *expert {
Expand All @@ -212,4 +217,4 @@ pub fn reject_session(
events::session_rejected(env, booking_id, "Expert declined session");

Ok(())
}
}
3 changes: 2 additions & 1 deletion contracts/payment-vault-contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ pub enum VaultError {
BookingNotPending = 5,
InvalidAmount = 6,
ReclaimTooEarly = 7,
}
ExpertRateNotSet = 8,
}
20 changes: 17 additions & 3 deletions contracts/payment-vault-contract/src/events.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
use soroban_sdk::{Address, Env, symbol_short};
#![allow(deprecated)]
use soroban_sdk::{symbol_short, Address, Env};

/// Emitted when a new booking is created
pub fn booking_created(env: &Env, booking_id: u64, user: &Address, expert: &Address, deposit: i128) {
pub fn booking_created(
env: &Env,
booking_id: u64,
user: &Address,
expert: &Address,
deposit: i128,
) {
let topics = (symbol_short!("booked"), booking_id);
env.events().publish(topics, (user.clone(), expert.clone(), deposit));
env.events()
.publish(topics, (user.clone(), expert.clone(), deposit));
}

/// Emitted when a session is finalized
Expand All @@ -22,3 +30,9 @@ pub fn session_rejected(env: &Env, booking_id: u64, reason: &str) {
let topics = (symbol_short!("reject"), booking_id);
env.events().publish(topics, reason);
}

/// Emitted when an expert updates their rate
pub fn expert_rate_updated(env: &Env, expert: &Address, rate: i128) {
let topics = (symbol_short!("rate_upd"), expert.clone());
env.events().publish(topics, rate);
}
20 changes: 10 additions & 10 deletions contracts/payment-vault-contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ mod contract;
mod error;
mod events;
mod storage;
mod types;
#[cfg(test)]
mod test;
mod types;

use soroban_sdk::{contract, contractimpl, Address, Env, Vec};
use crate::error::VaultError;
use crate::types::BookingRecord;
use soroban_sdk::{contract, contractimpl, Address, Env, Vec};

#[contract]
pub struct PaymentVaultContract;
Expand All @@ -22,21 +22,25 @@ impl PaymentVaultContract {
env: Env,
admin: Address,
token: Address,
oracle: Address
oracle: Address,
) -> Result<(), VaultError> {
contract::initialize_vault(&env, &admin, &token, &oracle)
}

/// Set an expert's own rate per second
pub fn set_my_rate(env: Env, expert: Address, rate_per_second: i128) -> Result<(), VaultError> {
contract::set_my_rate(&env, &expert, rate_per_second)
}

/// Book a session with an expert
/// User deposits tokens upfront based on rate_per_second * max_duration
pub fn book_session(
env: Env,
user: Address,
expert: Address,
rate_per_second: i128,
max_duration: u64,
) -> Result<u64, VaultError> {
contract::book_session(&env, &user, &expert, rate_per_second, max_duration)
contract::book_session(&env, &user, &expert, max_duration)
}

/// Finalize a session (Oracle-only)
Expand All @@ -61,11 +65,7 @@ impl PaymentVaultContract {

/// Reject a pending session (Expert-only)
/// Experts can reject a pending booking, instantly refunding the user
pub fn reject_session(
env: Env,
expert: Address,
booking_id: u64,
) -> Result<(), VaultError> {
pub fn reject_session(env: Env, expert: Address, booking_id: u64) -> Result<(), VaultError> {
contract::reject_session(&env, &expert, booking_id)
}

Expand Down
26 changes: 21 additions & 5 deletions contracts/payment-vault-contract/src/storage.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
use soroban_sdk::{contracttype, Address, Env};
use crate::types::{BookingRecord, BookingStatus};
use soroban_sdk::{contracttype, Address, Env};

#[contracttype]
#[derive(Clone)]
pub enum DataKey {
Admin,
Token,
Oracle,
Booking(u64), // Booking ID -> BookingRecord
BookingCounter, // Counter for generating unique booking IDs
UserBookings(Address), // User Address -> Vec<u64> of booking IDs
Booking(u64), // Booking ID -> BookingRecord
BookingCounter, // Counter for generating unique booking IDs
UserBookings(Address), // User Address -> Vec<u64> of booking IDs
ExpertBookings(Address), // Expert Address -> Vec<u64> of booking IDs
ExpertRate(Address), // Expert Address -> rate per second (i128)
}

// --- Admin ---
Expand Down Expand Up @@ -53,7 +54,9 @@ pub fn get_next_booking_id(env: &Env) -> u64 {
.get(&DataKey::BookingCounter)
.unwrap_or(0);
let next = current + 1;
env.storage().instance().set(&DataKey::BookingCounter, &next);
env.storage()
.instance()
.set(&DataKey::BookingCounter, &next);
next
}

Expand Down Expand Up @@ -119,3 +122,16 @@ pub fn get_expert_bookings(env: &Env, expert: &Address) -> soroban_sdk::Vec<u64>
.get(&DataKey::ExpertBookings(expert.clone()))
.unwrap_or(soroban_sdk::Vec::new(env))
}

// --- Expert Rates ---
pub fn set_expert_rate(env: &Env, expert: &Address, rate: i128) {
env.storage()
.persistent()
.set(&DataKey::ExpertRate(expert.clone()), &rate);
}

pub fn get_expert_rate(env: &Env, expert: &Address) -> Option<i128> {
env.storage()
.persistent()
.get(&DataKey::ExpertRate(expert.clone()))
}
Loading