From 50748ff4db46965c600cbd1db4f5222173def81b Mon Sep 17 00:00:00 2001 From: Leothosine Date: Sat, 30 May 2026 09:19:15 +0100 Subject: [PATCH 1/3] feat(storage): add EscrowDisputeHistory key --- .../contract/token/src/storage_types.rs | 65 +++++-------------- 1 file changed, 16 insertions(+), 49 deletions(-) diff --git a/veritixpay/contract/token/src/storage_types.rs b/veritixpay/contract/token/src/storage_types.rs index b4d22c1..fb35421 100644 --- a/veritixpay/contract/token/src/storage_types.rs +++ b/veritixpay/contract/token/src/storage_types.rs @@ -1,87 +1,54 @@ -use soroban_sdk::{contracttype, Address, Env, IntoVal, TryFromVal, Val}; +use soroban_sdk::{contracttype, Address, Env, IntoVal, TryFromVal, Val}; -pub const BALANCE_LIFETIME_THRESHOLD: u32 = 518400; // ~30 days +pub const BALANCE_LIFETIME_THRESHOLD: u32 = 518400; pub const BALANCE_BUMP_AMOUNT: u32 = 535000; -pub const ALLOWANCE_LIFETIME_THRESHOLD: u32 = 518400; // ~30 days +pub const ALLOWANCE_LIFETIME_THRESHOLD: u32 = 518400; pub const ALLOWANCE_BUMP_AMOUNT: u32 = 535000; pub const INSTANCE_LIFETIME_THRESHOLD: u32 = 518400; pub const INSTANCE_BUMP_AMOUNT: u32 = 535000; -/// Threshold and bump for long-lived persistent records (escrow, split, dispute, recurring, freeze). -pub const PERSISTENT_LIFETIME_THRESHOLD: u32 = 518400; // ~30 days +pub const PERSISTENT_LIFETIME_THRESHOLD: u32 = 518400; pub const PERSISTENT_BUMP_AMOUNT: u32 = 535000; #[derive(Clone)] #[contracttype] -pub struct AllowanceDataKey { - pub from: Address, - pub spender: Address, -} +pub struct AllowanceDataKey { pub from: Address, pub spender: Address } #[derive(Clone)] #[contracttype] -pub struct AllowanceValue { - pub amount: i128, - pub expiration_ledger: u32, -} +pub struct AllowanceValue { pub amount: i128, pub expiration_ledger: u32 } #[derive(Clone)] #[contracttype] pub enum DataKey { - Admin, - Allowance(AllowanceDataKey), - Balance(Address), - Metadata, - TotalSupply, - EscrowCount, - Escrow(u32), - RecurringCount, - Recurring(u32), - SplitCount, - Split(u32), - DisputeCount, - Dispute(u32), - // Tracks the active dispute ID for a given escrow (None = no open dispute). - EscrowDispute(u32), - - // Stores per-address freeze status. - Freeze(Address), + Admin, Allowance(AllowanceDataKey), Balance(Address), Metadata, TotalSupply, + EscrowCount, Escrow(u32), RecurringCount, Recurring(u32), + SplitCount, Split(u32), DisputeCount, Dispute(u32), + EscrowDispute(u32), EscrowDisputeHistory(u32), Freeze(Address), } pub fn read_persistent_record(e: &Env, key: &DataKey, missing_message: &'static str) -> T -where - T: TryFromVal, -{ +where T: TryFromVal { let storage = e.storage().persistent(); - let value = storage - .get::(key) - .unwrap_or_else(|| panic!("{}", missing_message)); + let value = storage.get::(key).unwrap_or_else(|| panic!("{}", missing_message)); storage.extend_ttl(key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); value } pub fn write_persistent_record(e: &Env, key: &DataKey, value: &T) -where - T: IntoVal, -{ +where T: IntoVal { let storage = e.storage().persistent(); storage.set(key, value); storage.extend_ttl(key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); } -/// Bumps the instance storage TTL. Call this on any entrypoint that reads or -/// writes instance-stored data (admin, metadata, counters, total supply). pub fn bump_instance(e: &Env) { - e.storage() - .instance() - .extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); + e.storage().instance().extend_ttl(INSTANCE_LIFETIME_THRESHOLD, INSTANCE_BUMP_AMOUNT); } -pub fn read_counter(e: &Env, key: &DataKey) -> u32 { - e.storage().instance().get(key).unwrap_or(0) -} +pub fn read_counter(e: &Env, key: &DataKey) -> u32 { e.storage().instance().get(key).unwrap_or(0) } pub fn increment_counter(e: &Env, key: &DataKey) -> u32 { let next = read_counter(e, key) + 1; e.storage().instance().set(key, &next); next -} +} \ No newline at end of file From 9b2800647d92b4e9023ac070fc9d94a5944e31a9 Mon Sep 17 00:00:00 2001 From: Leothosine Date: Sat, 30 May 2026 09:19:17 +0100 Subject: [PATCH 2/3] feat(dispute): add EscrowDisputeHistory and get_dispute_history_for_escrow --- veritixpay/contract/token/src/dispute.rs | 181 ++++++----------------- 1 file changed, 46 insertions(+), 135 deletions(-) diff --git a/veritixpay/contract/token/src/dispute.rs b/veritixpay/contract/token/src/dispute.rs index b5e3b0f..bdf2e33 100644 --- a/veritixpay/contract/token/src/dispute.rs +++ b/veritixpay/contract/token/src/dispute.rs @@ -1,182 +1,93 @@ -use crate::balance::{receive_balance, spend_balance}; +use crate::balance::{receive_balance, spend_balance}; use crate::escrow::get_escrow; -use crate::storage_types::{increment_counter, write_persistent_record, DataKey, PERSISTENT_BUMP_AMOUNT, PERSISTENT_LIFETIME_THRESHOLD}; -use soroban_sdk::{contracttype, symbol_short, Address, Env, Symbol}; +use crate::storage_types::{ + increment_counter, write_persistent_record, DataKey, PERSISTENT_BUMP_AMOUNT, + PERSISTENT_LIFETIME_THRESHOLD, +}; +use soroban_sdk::{contracttype, symbol_short, vec, Address, Env, Symbol, Vec}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub enum DisputeStatus { - Open, - ResolvedForBeneficiary, - ResolvedForDepositor, -} +pub enum DisputeStatus { Open, ResolvedForBeneficiary, ResolvedForDepositor } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct DisputeRecord { - pub id: u32, - pub escrow_id: u32, - pub claimant: Address, - pub resolver: Address, - pub status: DisputeStatus, + pub id: u32, pub escrow_id: u32, pub claimant: Address, + pub resolver: Address, pub status: DisputeStatus, +} + +fn append_dispute_history(e: &Env, escrow_id: u32, dispute_id: u32) { + let key = DataKey::EscrowDisputeHistory(escrow_id); + let mut ids: Vec = e.storage().persistent().get(&key).unwrap_or_else(|| vec![e]); + ids.push_back(dispute_id); + e.storage().persistent().set(&key, &ids); + e.storage().persistent().extend_ttl(&key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); } -/// Opens a dispute against an existing escrow. pub fn open_dispute(e: &Env, claimant: Address, escrow_id: u32, resolver: Address) -> u32 { claimant.require_auth(); - let escrow = get_escrow(e, escrow_id); - - if escrow.released || escrow.refunded { - panic!("InvalidState: Cannot open dispute on a settled escrow"); - } - - if claimant != escrow.depositor && claimant != escrow.beneficiary { - panic!("Unauthorized: Only depositor or beneficiary can open a dispute"); - } - if resolver == claimant { - panic!("InvalidResolver: resolver cannot be the claimant"); - } - if resolver == escrow.depositor { - panic!("InvalidResolver: resolver cannot be the depositor"); - } - if resolver == escrow.beneficiary { - panic!("InvalidResolver: resolver cannot be the beneficiary"); - } - - // Prevent multiple open disputes for the same escrow. - // NOTE: All validation must complete before incrementing the counter so that - // rejected calls do not consume a dispute ID and leave gaps in the sequence. - if e.storage() - .persistent() - .has(&DataKey::EscrowDispute(escrow_id)) - { - panic!("DisputeAlreadyOpen: An open dispute already exists for this escrow"); - } - - // Increment only after all validation passes — counter must not advance on rejected calls. + if escrow.released || escrow.refunded { panic!("InvalidState: Cannot open dispute on a settled escrow"); } + if claimant != escrow.depositor && claimant != escrow.beneficiary { panic!("Unauthorized: Only depositor or beneficiary can open a dispute"); } + if resolver == claimant { panic!("InvalidResolver: resolver cannot be the claimant"); } + if resolver == escrow.depositor { panic!("InvalidResolver: resolver cannot be the depositor"); } + if resolver == escrow.beneficiary { panic!("InvalidResolver: resolver cannot be the beneficiary"); } + if e.storage().persistent().has(&DataKey::EscrowDispute(escrow_id)) { panic!("DisputeAlreadyOpen: An open dispute already exists for this escrow"); } let count = increment_counter(e, &DataKey::DisputeCount); - - let record = DisputeRecord { - id: count, - escrow_id, - claimant: claimant.clone(), - resolver, - status: DisputeStatus::Open, - }; - + let record = DisputeRecord { id: count, escrow_id, claimant: claimant.clone(), resolver, status: DisputeStatus::Open }; let dispute_key = DataKey::Dispute(count); let escrow_dispute_key = DataKey::EscrowDispute(escrow_id); e.storage().persistent().set(&dispute_key, &record); - e.storage() - .persistent() - .extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); e.storage().persistent().set(&escrow_dispute_key, &count); - e.storage() - .persistent() - .extend_ttl(&escrow_dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); - - e.events().publish( - (symbol_short!("dispute_opened"), escrow_id, claimant.clone()), - (), - ); - + e.storage().persistent().extend_ttl(&escrow_dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + append_dispute_history(e, escrow_id, count); + e.events().publish((symbol_short!("dispute_opened"), escrow_id, claimant.clone()), ()); count } -/// Private helper: settle an escrow by outcome without requiring depositor/beneficiary auth. -/// The resolver has already been authenticated by `resolve_dispute`. fn settle_escrow_by_outcome(e: &Env, escrow_id: u32, release_to_beneficiary: bool) { let mut escrow = get_escrow(e, escrow_id); - - if escrow.released || escrow.refunded { - panic!("AlreadySettled: escrow is already settled"); - } - + if escrow.released || escrow.refunded { panic!("AlreadySettled: escrow is already settled"); } if release_to_beneficiary { escrow.released = true; write_persistent_record(e, &DataKey::Escrow(escrow_id), &escrow); spend_balance(e, e.current_contract_address(), escrow.amount); receive_balance(e, escrow.beneficiary.clone(), escrow.amount); - e.events().publish( - ( - symbol_short!("escrow_released"), - escrow_id, - escrow.beneficiary.clone(), - ), - escrow.amount, - ); + e.events().publish((symbol_short!("escrow_released"), escrow_id, escrow.beneficiary.clone()), escrow.amount); } else { escrow.refunded = true; write_persistent_record(e, &DataKey::Escrow(escrow_id), &escrow); spend_balance(e, e.current_contract_address(), escrow.amount); receive_balance(e, escrow.depositor.clone(), escrow.amount); - e.events().publish( - ( - symbol_short!("escrow_refunded"), - escrow_id, - escrow.depositor.clone(), - ), - escrow.amount, - ); + e.events().publish((symbol_short!("escrow_refunded"), escrow_id, escrow.depositor.clone()), escrow.amount); } } -/// Resolves an open dispute. Only the designated resolver can call this. -/// Settlement does not require beneficiary/depositor auth. pub fn resolve_dispute(e: &Env, resolver: Address, dispute_id: u32, release_to_beneficiary: bool) { resolver.require_auth(); - let dispute_key = DataKey::Dispute(dispute_id); - let mut dispute: DisputeRecord = e - .storage() - .persistent() - .get(&dispute_key) - .expect("Dispute not found"); - e.storage() - .persistent() - .extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); - - if dispute.status != DisputeStatus::Open { - panic!("AlreadyResolved: This dispute has already been resolved"); - } - - if dispute.resolver != resolver { - panic!("UnauthorizedResolver: Only the designated resolver can resolve this"); - } - + let mut dispute: DisputeRecord = e.storage().persistent().get(&dispute_key).expect("Dispute not found"); + e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + if dispute.status != DisputeStatus::Open { panic!("AlreadyResolved: This dispute has already been resolved"); } + if dispute.resolver != resolver { panic!("UnauthorizedResolver: Only the designated resolver can resolve this"); } settle_escrow_by_outcome(e, dispute.escrow_id, release_to_beneficiary); - - dispute.status = if release_to_beneficiary { - DisputeStatus::ResolvedForBeneficiary - } else { - DisputeStatus::ResolvedForDepositor - }; - + dispute.status = if release_to_beneficiary { DisputeStatus::ResolvedForBeneficiary } else { DisputeStatus::ResolvedForDepositor }; e.storage().persistent().set(&dispute_key, &dispute); - e.storage() - .persistent() - .extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); - e.storage() - .persistent() - .remove(&DataKey::EscrowDispute(dispute.escrow_id)); - - e.events().publish( - (symbol_short!("dispute_resolved"), dispute_id, resolver), - release_to_beneficiary, - ); + e.storage().persistent().extend_ttl(&dispute_key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + e.storage().persistent().remove(&DataKey::EscrowDispute(dispute.escrow_id)); + e.events().publish((symbol_short!("dispute_resolved"), dispute_id, resolver), release_to_beneficiary); } -/// Helper to read a dispute record. pub fn get_dispute(e: &Env, dispute_id: u32) -> DisputeRecord { let key = DataKey::Dispute(dispute_id); - let record = e - .storage() - .persistent() - .get(&key) - .expect("Dispute not found"); - e.storage() - .persistent() - .extend_ttl(&key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); + let record = e.storage().persistent().get(&key).expect("Dispute not found"); + e.storage().persistent().extend_ttl(&key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT); record } + +pub fn get_dispute_history_for_escrow(e: &Env, escrow_id: u32) -> Vec { + let key = DataKey::EscrowDisputeHistory(escrow_id); + e.storage().persistent().get(&key).unwrap_or_else(|| vec![e]) +} \ No newline at end of file From fdf3d0e0ffffc473654b4796d50327d88d801733 Mon Sep 17 00:00:00 2001 From: Leothosine Date: Sat, 30 May 2026 09:19:19 +0100 Subject: [PATCH 3/3] feat(dispute): expose get_dispute_history_for_escrow in contract --- veritixpay/contract/token/src/contract.rs | 346 +++++----------------- 1 file changed, 69 insertions(+), 277 deletions(-) diff --git a/veritixpay/contract/token/src/contract.rs b/veritixpay/contract/token/src/contract.rs index e5ac271..cad3297 100644 --- a/veritixpay/contract/token/src/contract.rs +++ b/veritixpay/contract/token/src/contract.rs @@ -1,26 +1,12 @@ -use crate::admin::{check_admin, has_admin, read_admin, transfer_admin, write_admin}; +use crate::admin::{check_admin, has_admin, read_admin, transfer_admin, write_admin}; use crate::allowance::{read_allowance, spend_allowance, write_allowance}; -use crate::balance::{ - decrease_supply, increase_supply, read_balance, read_total_supply, receive_balance, - spend_balance, -}; -use crate::dispute::{get_dispute as dispute_get, open_dispute, resolve_dispute, DisputeRecord}; -use crate::escrow::{ - admin_settle_escrow as escrow_admin_settle, create_escrow as escrow_create, - get_escrow as escrow_get, refund_escrow as escrow_refund, release_escrow as escrow_release, - EscrowRecord, -}; +use crate::balance::{decrease_supply, increase_supply, read_balance, read_total_supply, receive_balance, spend_balance}; +use crate::dispute::{get_dispute as dispute_get, get_dispute_history_for_escrow, open_dispute, resolve_dispute, DisputeRecord}; +use crate::escrow::{admin_settle_escrow as escrow_admin_settle, create_escrow as escrow_create, get_escrow as escrow_get, refund_escrow as escrow_refund, release_escrow as escrow_release, EscrowRecord}; use crate::freeze::{freeze_account, is_frozen as read_frozen_status, unfreeze_account}; -use crate::metadata::{ - read_decimal, read_name, read_symbol, validate_metadata, write_metadata, TokenMetadata, -}; -use crate::recurring::{ - cancel_recurring, execute_recurring, get_recurring, setup_recurring, RecurringRecord, -}; -use crate::splitter::{ - cancel_split as split_cancel, create_split as split_create, distribute as split_distribute, - get_split as split_get, SplitRecord, SplitRecipient, -}; +use crate::metadata::{read_decimal, read_name, read_symbol, validate_metadata, write_metadata, TokenMetadata}; +use crate::recurring::{cancel_recurring, execute_recurring, get_recurring, setup_recurring, RecurringRecord}; +use crate::splitter::{cancel_split as split_cancel, create_split as split_create, distribute as split_distribute, get_split as split_get, SplitRecord, SplitRecipient}; use crate::validation::{require_not_frozen_account, require_positive_amount}; use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, String, Vec}; @@ -29,275 +15,81 @@ pub struct VeritixToken; #[contractimpl] impl VeritixToken { - // --- Admin & metadata --- - - /// Sets admin and metadata. Panics if already initialized. pub fn initialize(e: Env, admin: Address, name: String, symbol: String, decimal: u32) { - if has_admin(&e) { - panic!("already initialized"); - } - + if has_admin(&e) { panic!("already initialized"); } admin.require_auth(); - - let meta = TokenMetadata { - name, - symbol, - decimal, - }; - validate_metadata(&meta); - write_admin(&e, &admin); - write_metadata(&e, meta); + let meta = TokenMetadata { name, symbol, decimal }; + validate_metadata(&meta); write_admin(&e, &admin); write_metadata(&e, meta); } - - /// Rotates the contract administrator. Requires current admin auth. - pub fn set_admin(e: Env, new_admin: Address) { - transfer_admin(&e, new_admin); - } - - /// Admin-only. Reclaims tokens from an address and destroys them. + pub fn set_admin(e: Env, new_admin: Address) { transfer_admin(&e, new_admin); } pub fn clawback(e: Env, admin: Address, from: Address, amount: i128) { - check_admin(&e, &admin); - require_positive_amount(amount); - - // Deduct balance without redistributing, effectively burning the tokens - spend_balance(&e, from.clone(), amount); - decrease_supply(&e, amount); - - // Emit transparency event - e.events() - .publish((symbol_short!("clawback"), admin, from), amount); + check_admin(&e, &admin); require_positive_amount(amount); + spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); + e.events().publish((symbol_short!("clawback"), admin, from), amount); } - - // --- Freeze controls --- - - pub fn freeze(e: Env, target: Address) { - let admin = read_admin(&e); - check_admin(&e, &admin); - freeze_account(&e, admin, target); + pub fn freeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); freeze_account(&e, admin, target); } + pub fn unfreeze(e: Env, target: Address) { let admin = read_admin(&e); check_admin(&e, &admin); unfreeze_account(&e, admin, target); } + pub fn mint(e: Env, admin: Address, to: Address, amount: i128) { + check_admin(&e, &admin); require_positive_amount(amount); require_not_frozen_account(&e, &to); + receive_balance(&e, to.clone(), amount); increase_supply(&e, amount); + e.events().publish((symbol_short!("mint"), admin, to), amount); } - - pub fn unfreeze(e: Env, target: Address) { - let admin = read_admin(&e); - check_admin(&e, &admin); - unfreeze_account(&e, admin, target); + pub fn transfer(e: Env, from: Address, to: Address, amount: i128) { + from.require_auth(); require_positive_amount(amount); + require_not_frozen_account(&e, &from); require_not_frozen_account(&e, &to); + spend_balance(&e, from.clone(), amount); receive_balance(&e, to.clone(), amount); + e.events().publish((symbol_short!("transfer"), from, to), amount); } - - // --- Mint / burn & supply tracking --- - - /// Admin-only. Mints new tokens to a specific address. - pub fn mint(e: Env, admin: Address, to: Address, amount: i128) { - check_admin(&e, &admin); - require_positive_amount(amount); - receive_balance(&e, to.clone(), amount); - increase_supply(&e, amount); - e.events() - .publish((symbol_short!("mint"), admin, to), amount); + pub fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) { + spender.require_auth(); require_positive_amount(amount); + require_not_frozen_account(&e, &from); require_not_frozen_account(&e, &to); + spend_allowance(&e, from.clone(), spender.clone(), amount); + spend_balance(&e, from.clone(), amount); receive_balance(&e, to.clone(), amount); + e.events().publish((symbol_short!("transfer"), from, to), amount); } - - /// Caller burns their own tokens. pub fn burn(e: Env, from: Address, amount: i128) { - require_not_frozen_account(&e, &from); - require_positive_amount(amount); - from.require_auth(); - spend_balance(&e, from.clone(), amount); - decrease_supply(&e, amount); + from.require_auth(); require_positive_amount(amount); require_not_frozen_account(&e, &from); + spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); e.events().publish((symbol_short!("burn"), from), amount); } - - /// Spender burns tokens from an account using their allowance. pub fn burn_from(e: Env, spender: Address, from: Address, amount: i128) { - require_not_frozen_account(&e, &from); - require_not_frozen_account(&e, &spender); - require_positive_amount(amount); - spender.require_auth(); - spend_allowance(&e, from.clone(), spender.clone(), amount); - spend_balance(&e, from.clone(), amount); - decrease_supply(&e, amount); - e.events() - .publish((symbol_short!("burn"), spender, from), amount); - } - - // --- Transfers & allowance --- - - /// Standard token transfer between two addresses. - pub fn transfer(e: Env, from: Address, to: Address, amount: i128) { - require_not_frozen_account(&e, &from); - require_positive_amount(amount); - from.require_auth(); - spend_balance(&e, from.clone(), amount); - receive_balance(&e, to.clone(), amount); - e.events() - .publish((symbol_short!("transfer"), from, to), amount); - } - - /// Transfer tokens on behalf of a user via allowance. - pub fn transfer_from(e: Env, spender: Address, from: Address, to: Address, amount: i128) { - require_not_frozen_account(&e, &from); - require_not_frozen_account(&e, &spender); - require_positive_amount(amount); - spender.require_auth(); + spender.require_auth(); require_positive_amount(amount); require_not_frozen_account(&e, &from); spend_allowance(&e, from.clone(), spender.clone(), amount); - spend_balance(&e, from.clone(), amount); - receive_balance(&e, to.clone(), amount); - e.events() - .publish((symbol_short!("transfer"), from, to), amount); + spend_balance(&e, from.clone(), amount); decrease_supply(&e, amount); + e.events().publish((symbol_short!("burn"), from), amount); } - - /// Sets an allowance for a spender. - /// Frozen accounts cannot create new approvals. pub fn approve(e: Env, from: Address, spender: Address, amount: i128, expiration_ledger: u32) { - require_not_frozen_account(&e, &from); - from.require_auth(); + from.require_auth(); require_positive_amount(amount); write_allowance(&e, from.clone(), spender.clone(), amount, expiration_ledger); - e.events() - .publish((symbol_short!("approve"), from, spender), amount); - } - - // --- Read-only views --- - - pub fn total_supply(e: Env) -> i128 { - read_total_supply(&e) - } - - pub fn balance(e: Env, id: Address) -> i128 { - read_balance(&e, id) - } - - pub fn allowance(e: Env, from: Address, spender: Address) -> i128 { - read_allowance(&e, from, spender).amount - } - - pub fn admin(e: Env) -> Address { - read_admin(&e) - } - - pub fn is_frozen(e: Env, id: Address) -> bool { - read_frozen_status(&e, &id) - } - - pub fn decimals(e: Env) -> u32 { - read_decimal(&e) - } - - pub fn name(e: Env) -> String { - read_name(&e) - } - - pub fn symbol(e: Env) -> String { - read_symbol(&e) - } - - // --- Escrow --- - - pub fn create_escrow(e: Env, depositor: Address, beneficiary: Address, amount: i128, expiry_ledger: u32) -> u32 { - escrow_create(&e, depositor, beneficiary, amount, expiry_ledger) - } - - pub fn release_escrow(e: Env, caller: Address, escrow_id: u32) { - escrow_release(&e, caller, escrow_id) - } - - pub fn refund_escrow(e: Env, caller: Address, escrow_id: u32) { - escrow_refund(&e, caller, escrow_id) - } - - pub fn get_escrow(e: Env, escrow_id: u32) -> EscrowRecord { - escrow_get(&e, escrow_id) - } - - /// Returns the current number of escrows created (monotonically increasing counter). - pub fn escrow_count(e: Env) -> u32 { - crate::storage_types::bump_instance(&e); - crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::EscrowCount) - } - - /// Admin escape hatch: forcibly settles a stuck escrow when the normal - /// beneficiary or depositor is frozen. Sends funds to `recipient`. - pub fn admin_settle_escrow(e: Env, admin: Address, escrow_id: u32, recipient: Address) { - escrow_admin_settle(&e, admin, escrow_id, recipient) - } - - // --- Dispute --- - - pub fn open_dispute(e: Env, claimant: Address, escrow_id: u32, resolver: Address) -> u32 { - open_dispute(&e, claimant, escrow_id, resolver) - } - - pub fn resolve_dispute( - e: Env, - resolver: Address, - dispute_id: u32, - release_to_beneficiary: bool, - ) { - resolve_dispute(&e, resolver, dispute_id, release_to_beneficiary) - } - - pub fn get_dispute(e: Env, dispute_id: u32) -> DisputeRecord { - dispute_get(&e, dispute_id) - } - - /// Returns the current number of disputes created (monotonically increasing counter). - pub fn dispute_count(e: Env) -> u32 { - crate::storage_types::bump_instance(&e); - crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::DisputeCount) - } - - // --- Splitter --- - - pub fn create_split( - e: Env, - sender: Address, - recipients: Vec, - total_amount: i128, - ) -> u32 { - split_create(&e, sender, recipients, total_amount) - } - - pub fn distribute(e: Env, caller: Address, split_id: u32) { - split_distribute(&e, caller, split_id) - } - - pub fn cancel_split(e: Env, caller: Address, split_id: u32) { - split_cancel(&e, caller, split_id) - } - - pub fn get_split(e: Env, split_id: u32) -> SplitRecord { - split_get(&e, split_id) - } - - /// Returns the current number of splits created (monotonically increasing counter). - pub fn split_count(e: Env) -> u32 { - crate::storage_types::bump_instance(&e); - crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::SplitCount) - } - - // --- Recurring Payments --- - - pub fn setup_recurring( - e: Env, - payer: Address, - payee: Address, - amount: i128, - interval: u32, - ) -> u32 { - setup_recurring(&e, payer, payee, amount, interval) - } - - pub fn execute_recurring(e: Env, recurring_id: u32) { - execute_recurring(&e, recurring_id) - } - - pub fn cancel_recurring(e: Env, caller: Address, recurring_id: u32) { - cancel_recurring(&e, caller, recurring_id) - } - - pub fn get_recurring(e: Env, recurring_id: u32) -> RecurringRecord { - get_recurring(&e, recurring_id) - } - - /// Returns the current number of recurring payments created (monotonically increasing counter). - pub fn recurring_count(e: Env) -> u32 { - crate::storage_types::bump_instance(&e); - crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::RecurringCount) - } -} + e.events().publish((symbol_short!("approve"), from, spender), amount); + } + pub fn total_supply(e: Env) -> i128 { read_total_supply(&e) } + pub fn balance(e: Env, id: Address) -> i128 { read_balance(&e, id) } + pub fn allowance(e: Env, from: Address, spender: Address) -> i128 { read_allowance(&e, from, spender).amount } + pub fn admin(e: Env) -> Address { read_admin(&e) } + pub fn is_frozen(e: Env, id: Address) -> bool { read_frozen_status(&e, &id) } + pub fn decimals(e: Env) -> u32 { read_decimal(&e) } + pub fn name(e: Env) -> String { read_name(&e) } + pub fn symbol(e: Env) -> String { read_symbol(&e) } + pub fn create_escrow(e: Env, depositor: Address, beneficiary: Address, amount: i128, expiry_ledger: u32) -> u32 { escrow_create(&e, depositor, beneficiary, amount, expiry_ledger) } + pub fn release_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_release(&e, caller, escrow_id) } + pub fn refund_escrow(e: Env, caller: Address, escrow_id: u32) { escrow_refund(&e, caller, escrow_id) } + pub fn get_escrow(e: Env, escrow_id: u32) -> EscrowRecord { escrow_get(&e, escrow_id) } + pub fn escrow_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::EscrowCount) } + pub fn admin_settle_escrow(e: Env, admin: Address, escrow_id: u32, recipient: Address) { escrow_admin_settle(&e, admin, escrow_id, recipient) } + pub fn open_dispute(e: Env, claimant: Address, escrow_id: u32, resolver: Address) -> u32 { open_dispute(&e, claimant, escrow_id, resolver) } + pub fn resolve_dispute(e: Env, resolver: Address, dispute_id: u32, release_to_beneficiary: bool) { resolve_dispute(&e, resolver, dispute_id, release_to_beneficiary) } + pub fn get_dispute(e: Env, dispute_id: u32) -> DisputeRecord { dispute_get(&e, dispute_id) } + pub fn dispute_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::DisputeCount) } + pub fn get_dispute_history_for_escrow(e: Env, escrow_id: u32) -> Vec { get_dispute_history_for_escrow(&e, escrow_id) } + pub fn create_split(e: Env, sender: Address, recipients: Vec, total_amount: i128) -> u32 { split_create(&e, sender, recipients, total_amount) } + pub fn distribute(e: Env, caller: Address, split_id: u32) { split_distribute(&e, caller, split_id) } + pub fn cancel_split(e: Env, caller: Address, split_id: u32) { split_cancel(&e, caller, split_id) } + pub fn get_split(e: Env, split_id: u32) -> SplitRecord { split_get(&e, split_id) } + pub fn split_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::SplitCount) } + pub fn setup_recurring(e: Env, payer: Address, payee: Address, amount: i128, interval: u32) -> u32 { setup_recurring(&e, payer, payee, amount, interval) } + pub fn execute_recurring(e: Env, recurring_id: u32) { execute_recurring(&e, recurring_id) } + pub fn cancel_recurring(e: Env, caller: Address, recurring_id: u32) { cancel_recurring(&e, caller, recurring_id) } + pub fn get_recurring(e: Env, recurring_id: u32) -> RecurringRecord { get_recurring(&e, recurring_id) } + pub fn recurring_count(e: Env) -> u32 { crate::storage_types::bump_instance(&e); crate::storage_types::read_counter(&e, &crate::storage_types::DataKey::RecurringCount) } +} \ No newline at end of file