Successfully centralized all fee calculation logic into a dedicated fee_service module that provides:
- Unified fee calculation interface - Single source of truth for all fee logic
- Corridor-based fee configurations - Country-to-country specific fee rules
- Detailed fee breakdowns - Complete transparency of all fee components
- No logic duplication - All fee calculations route through centralized service
The central fee calculation engine with the following key functions:
// Primary entry point - calculates complete fee breakdown
pub fn calculate_fees_with_breakdown(
env: &Env,
amount: i128,
corridor: Option<&FeeCorridor>,
) -> Result<FeeBreakdown, ContractError>
// Backward compatible - calculates only platform fee
pub fn calculate_platform_fee(
env: &Env,
amount: i128,
) -> Result<i128, ContractError>
// Batch processing - aggregates fees for multiple transactions
pub fn calculate_batch_fees(
env: &Env,
amounts: &[i128],
corridor: Option<&FeeCorridor>,
) -> Result<FeeBreakdown, ContractError>FeeCorridor - Country-to-country fee configuration:
pub struct FeeCorridor {
pub from_country: String, // ISO 3166-1 alpha-2 code
pub to_country: String, // ISO 3166-1 alpha-2 code
pub strategy: FeeStrategy, // Fee calculation strategy
pub protocol_fee_bps: Option<u32>, // Optional protocol fee override
}FeeBreakdown - Complete fee transparency:
pub struct FeeBreakdown {
pub amount: i128, // Original transaction amount
pub platform_fee: i128, // Platform fee charged
pub protocol_fee: i128, // Protocol fee charged
pub total_fees: i128, // Sum of all fees
pub net_amount: i128, // Amount after fees
pub strategy_used: FeeStrategy, // Strategy applied
pub corridor_applied: Option<FeeCorridor>, // Corridor if used
}The service supports three fee calculation strategies:
-
Percentage - Fee based on percentage of amount (basis points)
- Example:
FeeStrategy::Percentage(250)= 2.5%
- Example:
-
Flat - Fixed fee regardless of amount
- Example:
FeeStrategy::Flat(100)= 100 units
- Example:
-
Dynamic - Tiered fees based on amount ranges
- Example:
FeeStrategy::Dynamic(400)= 4% base - <1000: 4%, 1000-10000: 2%, >10000: 1%
- Example:
No changes required - existing initialization works with new service.
Before:
let strategy = get_fee_strategy(&env);
let fee = calculate_fee(&env, &strategy, amount)?;After:
let fee = fee_service::calculate_platform_fee(&env, amount)?;Before:
let protocol_fee_bps = get_protocol_fee_bps(&env);
let protocol_fee = remittance.amount
.checked_mul(protocol_fee_bps as i128)?
.checked_div(10000)?;
let payout_amount = remittance.amount
.checked_sub(remittance.fee)?
.checked_sub(protocol_fee)?;After:
let fee_breakdown = fee_service::calculate_fees_with_breakdown(
&env,
remittance.amount,
None,
)?;
let payout_amount = fee_breakdown.net_amount;
let protocol_fee = fee_breakdown.protocol_fee;// Calculate fees without corridor
pub fn calculate_fee_breakdown(
env: Env,
amount: i128,
) -> Result<FeeBreakdown, ContractError>
// Calculate fees with corridor-specific rules
pub fn calculate_fee_breakdown_with_corridor(
env: Env,
amount: i128,
corridor: FeeCorridor,
) -> Result<FeeBreakdown, ContractError>// Set corridor configuration (admin only)
pub fn set_fee_corridor(
env: Env,
caller: Address,
corridor: FeeCorridor,
) -> Result<(), ContractError>
// Get corridor configuration
pub fn get_fee_corridor(
env: Env,
from_country: String,
to_country: String,
) -> Option<FeeCorridor>
// Remove corridor configuration (admin only)
pub fn remove_fee_corridor(
env: Env,
caller: Address,
from_country: String,
to_country: String,
) -> Result<(), ContractError>// Get complete fee breakdown for a transaction
let breakdown = client.calculate_fee_breakdown(&10000);
println!("Amount: {}", breakdown.amount); // 10000
println!("Platform Fee: {}", breakdown.platform_fee); // 250 (2.5%)
println!("Protocol Fee: {}", breakdown.protocol_fee); // 100 (1%)
println!("Total Fees: {}", breakdown.total_fees); // 350
println!("Net Amount: {}", breakdown.net_amount); // 9650// Set up US -> Mexico corridor with lower fees
let corridor = FeeCorridor {
from_country: String::from_str(&env, "US"),
to_country: String::from_str(&env, "MX"),
strategy: FeeStrategy::Percentage(150), // 1.5% instead of default 2.5%
protocol_fee_bps: Some(50), // 0.5% protocol fee
};
client.set_fee_corridor(&admin, &corridor);
// Calculate fees using corridor
let breakdown = client.calculate_fee_breakdown_with_corridor(
&10000,
&corridor,
);
println!("Platform Fee: {}", breakdown.platform_fee); // 150 (1.5%)
println!("Protocol Fee: {}", breakdown.protocol_fee); // 50 (0.5%)
println!("Net Amount: {}", breakdown.net_amount); // 9800// Get existing corridor configuration
let corridor = client.get_fee_corridor(
&String::from_str(&env, "US"),
&String::from_str(&env, "MX"),
);
if let Some(corr) = corridor {
println!("Corridor exists: {} -> {}", corr.from_country, corr.to_country);
}- Single source of truth - All fee logic in one module
- Easier maintenance - Changes in one place affect entire system
- Reduced bugs - No duplicate logic to keep in sync
- Complete breakdowns - Users see all fee components
- Audit trail - Fee calculations are traceable
- Validation - Built-in consistency checks
- Corridor support - Country-specific fee rules
- Multiple strategies - Percentage, flat, or dynamic fees
- Protocol fees - Separate treasury fees
- Overflow protection - All arithmetic uses checked operations
- Validation - Fee breakdowns self-validate for consistency
- Type safety - Rust's type system prevents errors
The fee service includes comprehensive unit tests:
#[test]
fn test_fee_breakdown_validation()
fn test_calculate_fees_percentage()
fn test_calculate_fees_with_corridor()
fn test_batch_fees()
fn test_flat_fee_strategy()
fn test_dynamic_fee_strategy()
fn test_zero_amount_rejected()
fn test_negative_amount_rejected()All existing tests continue to pass, ensuring backward compatibility.
- No breaking changes - Existing contracts continue to work
- Gradual adoption - New features can be adopted incrementally
- Backward compatible - Old API methods still function
- Use
calculate_fee_breakdown()for transparency - Configure corridors for cross-border optimization
- Leverage detailed breakdowns in UI/UX
Before: Fee calculation logic appeared in:
src/fee_strategy.rs- Strategy-based calculationsrc/lib.rs- Protocol fee calculation inconfirm_payout- Inline calculations scattered across codebase
After: All fee logic centralized in:
src/fee_service.rs- Single module with all calculations
- Clear separation - Fee logic isolated from business logic
- Documented - Comprehensive inline documentation
- Tested - Full test coverage of all scenarios
- Type-safe - Strong typing prevents errors
- No overhead - Centralization doesn't add computational cost
- Efficient - Batch operations optimize multiple calculations
- Cached - Storage reads minimized through smart design
- Overflow protection - All arithmetic uses checked operations
- Validation - Input validation at service boundary
- Consistency checks - Fee breakdowns self-validate
- Admin controls - Corridor management requires admin auth
Potential future additions to the fee service:
- Time-based fees - Different rates for different times
- Volume discounts - Lower fees for high-volume users
- Currency-specific fees - Different rates per currency
- Fee caps - Maximum fee limits
- Fee floors - Minimum fee amounts
The fee calculation service refactor successfully:
✅ Centralizes all fee logic into a dedicated module
✅ Supports corridor-based fee configurations
✅ Returns full fee breakdowns for transparency
✅ Eliminates all duplicated fee logic
✅ Maintains backward compatibility
✅ Improves code maintainability
✅ Enhances security and correctness
The refactor meets all acceptance criteria and provides a solid foundation for future fee-related enhancements.