Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
89 commits
Select commit Hold shift + click to select a range
47c4ed7
fix: resolved all issues in one: #176 Milestone escrow events, #175 M…
Levi-Ojukwu May 29, 2026
82dd596
Merge pull request #1 from TheCodingChef-eth/resolved-issues
TheCodingChef-eth May 29, 2026
06863e3
feat(contracts): implement create program
3m1n3nc3 May 31, 2026
1b10836
feat(contracts): add program read helpers
3m1n3nc3 May 31, 2026
791deb4
fix: pass env.current_contract_address() argument directly instead of…
3m1n3nc3 May 31, 2026
a98e9a6
feat(contracts): implement issue attestation
Delightech28 May 31, 2026
57ddc93
feat(contracts): add reputation profile helpers
OlaGreat May 31, 2026
5c324e1
style: fix rustfmt formatting in quid-reputation
OlaGreat May 31, 2026
e75e8f1
Merge pull request #204 from exronnin/contracts/create-program-progra…
Kaylahray May 31, 2026
72584e3
test(contracts): add reputation profile tests
teethaking Jun 1, 2026
7779ec5
style(contracts): fix rustfmt formatting in quid-reputation
teethaking Jun 1, 2026
8500bf6
feat(contracts): implement add milestone
3m1n3nc3 Jun 1, 2026
bb0531e
feat(contracts): add milestone read helper
3m1n3nc3 Jun 1, 2026
490f8b5
scaffold reputation
Birdmannn Jun 1, 2026
ec7e3ab
Merge pull request #1 from Birdmannn/feat-#198
Birdmannn Jun 1, 2026
e27cab1
fixed ci
Birdmannn Jun 1, 2026
eb87654
Merge remote-tracking branch 'origin/feat-#186' into feat-#186
Birdmannn Jun 1, 2026
8aa671d
removed unimportant file
Birdmannn Jun 1, 2026
0888280
reputation profile model and storage keys
Birdmannn Jun 1, 2026
94cf1a9
clippy
Birdmannn Jun 1, 2026
44c9ded
feat(contracts): add attestation read helpers
Faromzy Jun 1, 2026
b1e2b1a
milestone escrow authorization and refund tests
Birdmannn Jun 1, 2026
11940cd
Merge remote-tracking branch 'upstream/main' into feat-#185
Birdmannn Jun 1, 2026
bc48f0a
fixed conflicts
Birdmannn Jun 1, 2026
fcd071f
revoke attestation authorization flow
Birdmannn Jun 1, 2026
190a0a1
update
Jun 1, 2026
6a26928
feat(contracts): implement record rejection
Nonso-Eze Jun 1, 2026
42e5fce
Merge pull request #211 from Birdmannn/feat-#186
Kaylahray Jun 1, 2026
ac03ab4
Merge pull request #214 from Birdmannn/feat-#185
Kaylahray Jun 1, 2026
461d191
Merge pull request #209 from Mistersmile4585/contracts/milestone-read…
Kaylahray Jun 1, 2026
79d04c9
fix: resolve merge conflicts with main attestation work
OlaGreat Jun 1, 2026
2a8e4ce
fix: remove unused super::* import in test.rs
OlaGreat Jun 1, 2026
59dc45e
fix: correct tuple destructuring in milestone-escrow tests
OlaGreat Jun 1, 2026
d662929
fixed workflow error
Levi-Ojukwu Jun 1, 2026
22ffff1
Merge pull request #2 from TheCodingChef-eth/resolved-issues
TheCodingChef-eth Jun 1, 2026
20937e4
Merge branch 'main' into main
TheCodingChef-eth Jun 1, 2026
a4e494b
Merge remote-tracking branch 'upstream/main' into feat-#198
Birdmannn Jun 1, 2026
3f1b29f
Merge branch 'main' into main
Georgebingi Jun 1, 2026
fd211ef
clippy
Birdmannn Jun 1, 2026
8c9380f
fix1
Jun 1, 2026
3209275
Merge remote-tracking branch 'upstream/main' into feat-#187
Birdmannn Jun 1, 2026
dea7f5c
Merge branch 'main' of https://github.com/Georgebingi/Quid
Jun 1, 2026
a4cfea8
fixed ci
Birdmannn Jun 1, 2026
fc0dd04
fix
Jun 1, 2026
149a212
feat(contracts): add reputation events
Agencybuilds Jun 1, 2026
aa0acd7
feat(contracts): implement milestone approval auth, payout, program c…
SaintSammi Jun 1, 2026
beaf1db
Merge pull request #218 from SaintSammi/project-requirements
Kaylahray Jun 1, 2026
176584f
Merge pull request #206 from OlaGreat/contracts/profile-read-and-helpers
Kaylahray Jun 1, 2026
ed3f955
Merge branch 'main' into contracts/record-rejection
bigvictoh Jun 1, 2026
d89073d
Merge remote-tracking branch 'upstream/main' into feat-#198
Birdmannn Jun 2, 2026
8f8d721
fixed compile issues
Birdmannn Jun 2, 2026
ab5de65
Merge remote-tracking branch 'upstream/main' into feat-#187
Birdmannn Jun 2, 2026
b97c888
fixed errorrs
Birdmannn Jun 2, 2026
94143fe
Merge pull request #216 from Georgebingi/main
Kaylahray Jun 2, 2026
022a195
feat(contracts): add reputation errors
Fatimasanusi Jun 2, 2026
9058ee3
style: fix cargo fmt formatting
Delightech28 May 31, 2026
68ea924
Merge branch 'main' into main
TheCodingChef-eth Jun 2, 2026
4172022
Merge branch 'main' into contracts/issue-attestation
Delightech28 Jun 2, 2026
a214bed
Merge and resolve conflicts
Delightech28 Jun 2, 2026
d2e8f64
Resolve merge conflict and fix formatting/clippy issues
Delightech28 Jun 2, 2026
62c8aa2
Merge branch 'main' into contracts/attestation-read-and-count
Faromzy Jun 3, 2026
12d47d6
Merge pull request #212 from Birdmannn/feat-#187
Kaylahray Jun 3, 2026
7c736be
Merge pull request #220 from Fatimasanusi/contracts/reputation-error-…
Kaylahray Jun 3, 2026
92c2628
Merge branch 'main' into contracts/reputation-profile-tests
nonchalanttee22-lgtm Jun 4, 2026
79fff47
Update lib.rs
nonchalanttee22-lgtm Jun 4, 2026
c0ba2f4
Update lib.rs
nonchalanttee22-lgtm Jun 4, 2026
4fe2d68
fixed
Delightech28 Jun 4, 2026
2689af5
Merge branch 'main' into contracts/issue-attestation
Delightech28 Jun 4, 2026
79cbec2
Merge pull request #213 from Faromzy/contracts/attestation-read-and-c…
Kaylahray Jun 4, 2026
9aade26
Merge pull request #205 from Delightech28/contracts/issue-attestation
Kaylahray Jun 4, 2026
d5a3652
Merge pull request #219 from bigvictoh/contracts/record-rejection
Kaylahray Jun 4, 2026
ed68a11
Merge pull request #201 from TheCodingChef-eth/main
Kaylahray Jun 4, 2026
3fc7e88
Merge branch 'main' into contracts/reputation-events
Kaylahray Jun 4, 2026
9e45252
Merge pull request #217 from Agencybuilds/contracts/reputation-events
Kaylahray Jun 4, 2026
f42f597
Merge branch 'main' into contracts/reputation-profile-tests
Kaylahray Jun 4, 2026
068050e
Merge pull request #208 from nonchalanttee22-lgtm/contracts/reputatio…
Kaylahray Jun 4, 2026
f7241d8
Merge remote-tracking branch 'upstream/main' into feat-#198
Birdmannn Jun 5, 2026
15d0bec
feat(contracts): implement increment success entrypoint
bbkenny May 31, 2026
6ed2a80
style: fix rustfmt trailing whitespaces and tuple alignment
bbkenny May 31, 2026
176ee7c
fix: resolve clippy warnings for unused variables and deprecated publish
bbkenny Jun 1, 2026
fba2c4f
fix: resolve delimiter and formatting errors in milestone escrow and …
bbkenny Jun 5, 2026
863bcbe
fix: resolve clippy warnings for unused variables and deprecated methods
bbkenny Jun 5, 2026
9cd0745
Merge pull request #207 from bbkenny/fix/issue-194
Kaylahray Jun 5, 2026
1726a9e
fixed errors
Birdmannn Jun 6, 2026
eedac87
Merge remote-tracking branch 'upstream/main' into feat-#198
Birdmannn Jun 6, 2026
c74105e
ci
Birdmannn Jun 6, 2026
3b7640f
Merge pull request #215 from Birdmannn/feat-#198
Kaylahray Jun 6, 2026
4723c1d
feat(contracts): implement upsert profile entrypoint closes #193
Jun 6, 2026
4ee78e0
fix: remove trailing newlines for cargo fmt compliance
Jun 6, 2026
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
7 changes: 7 additions & 0 deletions quid-contract/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions quid-contract/contracts/quid-milestone-escrow/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ use soroban_sdk::contracterror;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum MilestoneEscrowError {
InvalidState = 1,
InvalidAmount = 2,
ProgramNotFound = 3,
MilestoneNotFound = 4,
NotAuthorized = 5,
}
321 changes: 313 additions & 8 deletions quid-contract/contracts/quid-milestone-escrow/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,42 +1,347 @@
#![no_std]

use soroban_sdk::{contract, contractimpl, Env};
use soroban_sdk::{contract, contractevent, contractimpl, token, Address, Env, String};

mod error;
pub mod types;
mod types;

use error::MilestoneEscrowError;
use types::{DataKey, Milestone, MilestoneStatus, Program, ProgramStatus};

#[contractevent(topics = ["program", "created"])]
pub struct ProgramCreatedEvent {
pub program_id: u64,
pub sponsor: Address,
pub recipient: Address,
}

#[contractevent(topics = ["program", "status"])]
pub struct ProgramStatusChangedEvent {
pub program_id: u64,
pub status: ProgramStatus,
}

use types::{DataKey, MilestoneStatus, ProgramStatus};
#[contractevent(topics = ["milestone", "added"])]
pub struct MilestoneAddedEvent {
pub program_id: u64,
pub milestone_id: u64,
pub amount: i128,
}

#[contractevent(topics = ["milestone", "paid"])]
pub struct MilestonePaidEvent {
pub program_id: u64,
pub milestone_id: u64,
pub amount: i128,
pub recipient: Address,
}

#[contractevent(topics = ["program", "cancelled"])]
pub struct ProgramCancelledEvent {
pub program_id: u64,
pub sponsor: Address,
pub refund_amount: i128,
}

#[contract]
pub struct QuidMilestoneEscrowContract;

#[contractimpl]
impl QuidMilestoneEscrowContract {
// ── Status helpers ────────────────────────────────────────────────────

pub fn get_program_status(env: Env) -> ProgramStatus {
env.storage()
.persistent()
.instance()
.get(&DataKey::ProgramStatus)
.unwrap_or_default()
}

pub fn set_program_status(env: Env, status: ProgramStatus) {
env.storage()
.persistent()
.instance()
.set(&DataKey::ProgramStatus, &status);
}

pub fn get_milestone_status(env: Env) -> MilestoneStatus {
env.storage()
.persistent()
.instance()
.get(&DataKey::MilestoneStatus)
.unwrap_or_default()
}

pub fn set_milestone_status(env: Env, status: MilestoneStatus) {
env.storage()
.persistent()
.instance()
.set(&DataKey::MilestoneStatus, &status);
}

pub fn get_program_count(env: Env) -> u64 {
env.storage()
.instance()
.get(&DataKey::ProgramCount)
.unwrap_or(0_u64)
}

// ── Core methods ──────────────────────────────────────────────────────

pub fn create_program(
env: Env,
sponsor: Address,
recipient: Address,
token: Address,
total_amount: i128,
reviewer: Option<Address>,
metadata_cid: Option<String>,
) -> Result<u64, MilestoneEscrowError> {
sponsor.require_auth();

if total_amount <= 0 {
return Err(MilestoneEscrowError::InvalidAmount);
}

token::Client::new(&env, &token).transfer(
&sponsor,
env.current_contract_address(),
&total_amount,
);

let mut count: u64 = env
.storage()
.instance()
.get(&DataKey::ProgramCount)
.unwrap_or(0);
count += 1;
env.storage().instance().set(&DataKey::ProgramCount, &count);

let program_id = count;
let created_at = env.ledger().timestamp();

let program = Program {
id: program_id,
sponsor: sponsor.clone(),
recipient: recipient.clone(),
reviewer,
token,
total_amount,
allocated_amount: 0,
released_amount: 0,
milestone_count: 0,
metadata_cid,
created_at,
status: ProgramStatus::Active,
};

env.storage()
.persistent()
.set(&DataKey::Program(program_id), &program);

ProgramCreatedEvent {
program_id,
sponsor,
recipient,
}
.publish(&env);

ProgramStatusChangedEvent {
program_id,
status: ProgramStatus::Active,
}
.publish(&env);

Ok(program_id)
}

pub fn get_program(env: Env, program_id: u64) -> Result<Program, MilestoneEscrowError> {
env.storage()
.persistent()
.get(&DataKey::Program(program_id))
.ok_or(MilestoneEscrowError::ProgramNotFound)
}

pub fn add_milestone(
env: Env,
program_id: u64,
title: String,
amount: i128,
due_at: u64,
metadata_cid: String,
) -> Result<u64, MilestoneEscrowError> {
let mut program = Self::get_program(env.clone(), program_id)?;
program.sponsor.require_auth();

if program.status != ProgramStatus::Active {
return Err(MilestoneEscrowError::InvalidState);
}

if amount <= 0 {
return Err(MilestoneEscrowError::InvalidAmount);
}

let allocated_amount = program
.allocated_amount
.checked_add(amount)
.ok_or(MilestoneEscrowError::InvalidAmount)?;
if allocated_amount > program.total_amount {
return Err(MilestoneEscrowError::InvalidAmount);
}

let milestone_id = program.milestone_count + 1;
let milestone = Milestone {
id: milestone_id,
program_id,
title,
amount,
due_at,
metadata_cid,
status: MilestoneStatus::Pending,
};

program.allocated_amount = allocated_amount;
program.milestone_count = milestone_id;

env.storage()
.persistent()
.set(&DataKey::Milestone(program_id, milestone_id), &milestone);
env.storage()
.persistent()
.set(&DataKey::Program(program_id), &program);

MilestoneAddedEvent {
program_id,
milestone_id,
amount,
}
.publish(&env);

Ok(milestone_id)
}

pub fn get_milestone(
env: Env,
program_id: u64,
milestone_id: u64,
) -> Result<Milestone, MilestoneEscrowError> {
env.storage()
.persistent()
.get(&DataKey::Milestone(program_id, milestone_id))
.ok_or(MilestoneEscrowError::MilestoneNotFound)
}

pub fn approve_milestone(
env: Env,
program_id: u64,
milestone_id: u64,
approver: Address,
) -> Result<(), MilestoneEscrowError> {
approver.require_auth();

let mut program = Self::get_program(env.clone(), program_id)?;

let is_sponsor = approver == program.sponsor;
let is_reviewer = program.reviewer.as_ref() == Some(&approver);
if !is_sponsor && !is_reviewer {
return Err(MilestoneEscrowError::NotAuthorized);
}

if program.status != ProgramStatus::Active {
return Err(MilestoneEscrowError::InvalidState);
}

let mut milestone = Self::get_milestone(env.clone(), program_id, milestone_id)?;
if milestone.status != MilestoneStatus::Pending {
return Err(MilestoneEscrowError::InvalidState);
}

let paid_amount = milestone.amount;

token::Client::new(&env, &program.token).transfer(
&env.current_contract_address(),
&program.recipient,
&paid_amount,
);

milestone.status = MilestoneStatus::Paid;
env.storage()
.persistent()
.set(&DataKey::Milestone(program_id, milestone_id), &milestone);

program.released_amount = program
.released_amount
.checked_add(paid_amount)
.ok_or(MilestoneEscrowError::InvalidAmount)?;

let recipient = program.recipient.clone();

MilestonePaidEvent {
program_id,
milestone_id,
amount: paid_amount,
recipient,
}
.publish(&env);

if program.released_amount >= program.total_amount {
program.status = ProgramStatus::Completed;
ProgramStatusChangedEvent {
program_id,
status: ProgramStatus::Completed,
}
.publish(&env);
}

env.storage()
.persistent()
.set(&DataKey::Program(program_id), &program);

Ok(())
}

pub fn cancel_program(
env: Env,
program_id: u64,
sponsor: Address,
) -> Result<(), MilestoneEscrowError> {
sponsor.require_auth();

let mut program = Self::get_program(env.clone(), program_id)?;

if sponsor != program.sponsor {
return Err(MilestoneEscrowError::NotAuthorized);
}

if program.status != ProgramStatus::Active {
return Err(MilestoneEscrowError::InvalidState);
}

let refund_amount = program.total_amount - program.released_amount;
if refund_amount > 0 {
token::Client::new(&env, &program.token).transfer(
&env.current_contract_address(),
&program.sponsor,
&refund_amount,
);
}

program.status = ProgramStatus::Cancelled;
env.storage()
.persistent()
.set(&DataKey::Program(program_id), &program);

ProgramCancelledEvent {
program_id,
sponsor: program.sponsor.clone(),
refund_amount,
}
.publish(&env);

ProgramStatusChangedEvent {
program_id,
status: ProgramStatus::Cancelled,
}
.publish(&env);

Ok(())
}
}

mod test;
Loading
Loading