Calculates complete fee breakdown for a transaction amount.
pub fn calculate_fee_breakdown(
env: Env,
amount: i128,
) -> Result<FeeBreakdown, ContractError>Parameters:
env- Contract environmentamount- Transaction amount to calculate fees for
Returns:
FeeBreakdown- Complete breakdown of all fees
Example:
let breakdown = client.calculate_fee_breakdown(&10000);
assert_eq!(breakdown.amount, 10000);
assert_eq!(breakdown.platform_fee, 250); // 2.5%
assert_eq!(breakdown.protocol_fee, 100); // 1%
assert_eq!(breakdown.total_fees, 350);
assert_eq!(breakdown.net_amount, 9650);Calculates fees using corridor-specific configuration.
pub fn calculate_fee_breakdown_with_corridor(
env: Env,
amount: i128,
corridor: FeeCorridor,
) -> Result<FeeBreakdown, ContractError>Parameters:
env- Contract environmentamount- Transaction amountcorridor- Corridor configuration with country codes and fee rules
Returns:
FeeBreakdown- Breakdown using corridor-specific rates
Example:
let corridor = FeeCorridor {
from_country: String::from_str(&env, "US"),
to_country: String::from_str(&env, "MX"),
strategy: FeeStrategy::Percentage(150), // 1.5%
protocol_fee_bps: Some(50), // 0.5%
};
let breakdown = client.calculate_fee_breakdown_with_corridor(
&10000,
&corridor,
);
assert_eq!(breakdown.platform_fee, 150);
assert_eq!(breakdown.protocol_fee, 50);
assert_eq!(breakdown.net_amount, 9800);Configures fee rules for a country-to-country corridor.
pub fn set_fee_corridor(
env: Env,
caller: Address,
corridor: FeeCorridor,
) -> Result<(), ContractError>Parameters:
env- Contract environmentcaller- Admin address (requires authentication)corridor- Corridor configuration to set
Authorization: Admin only
Example:
let corridor = FeeCorridor {
from_country: String::from_str(&env, "US"),
to_country: String::from_str(&env, "MX"),
strategy: FeeStrategy::Percentage(150),
protocol_fee_bps: Some(50),
};
client.set_fee_corridor(&admin, &corridor)?;Retrieves corridor configuration for a country pair.
pub fn get_fee_corridor(
env: Env,
from_country: String,
to_country: String,
) -> Option<FeeCorridor>Parameters:
env- Contract environmentfrom_country- Source country code (ISO 3166-1 alpha-2)to_country- Destination country code (ISO 3166-1 alpha-2)
Returns:
Some(FeeCorridor)- Corridor configuration if existsNone- No corridor configured for this pair
Example:
let corridor = client.get_fee_corridor(
&String::from_str(&env, "US"),
&String::from_str(&env, "MX"),
);
if let Some(corr) = corridor {
println!("Found corridor: {} -> {}", corr.from_country, corr.to_country);
}Removes corridor configuration for a country pair.
pub fn remove_fee_corridor(
env: Env,
caller: Address,
from_country: String,
to_country: String,
) -> Result<(), ContractError>Parameters:
env- Contract environmentcaller- Admin address (requires authentication)from_country- Source country codeto_country- Destination country code
Authorization: Admin only
Example:
client.remove_fee_corridor(
&admin,
&String::from_str(&env, "US"),
&String::from_str(&env, "MX"),
)?;Complete breakdown of all fees for a transaction.
pub struct FeeBreakdown {
pub amount: i128,
pub platform_fee: i128,
pub protocol_fee: i128,
pub total_fees: i128,
pub net_amount: i128,
pub strategy_used: FeeStrategy,
pub corridor_applied: Option<FeeCorridor>,
}Fields:
amount- Original transaction amountplatform_fee- Platform fee chargedprotocol_fee- Protocol fee charged (sent to treasury)total_fees- Sum of platform_fee + protocol_feenet_amount- Amount after all fees (amount - total_fees)strategy_used- Fee strategy that was appliedcorridor_applied- Corridor configuration if used
Validation:
The breakdown includes a validate() method that ensures:
total_fees = platform_fee + protocol_feenet_amount = amount - total_fees- All amounts are non-negative
Configuration for country-to-country fee rules.
pub struct FeeCorridor {
pub from_country: String,
pub to_country: String,
pub strategy: FeeStrategy,
pub protocol_fee_bps: Option<u32>,
}Fields:
from_country- Source country code (ISO 3166-1 alpha-2, e.g., "US")to_country- Destination country code (ISO 3166-1 alpha-2, e.g., "MX")strategy- Fee calculation strategy for this corridorprotocol_fee_bps- Optional protocol fee override (if None, uses global setting)
Fee calculation strategy enum.
pub enum FeeStrategy {
Percentage(u32), // Basis points (250 = 2.5%)
Flat(i128), // Fixed amount
Dynamic(u32), // Tiered based on amount
}Variants:
-
Percentage(bps) - Fee as percentage of amount
bps- Basis points (1 bps = 0.01%, max 10000 = 100%)- Example:
Percentage(250)= 2.5%
-
Flat(amount) - Fixed fee regardless of transaction size
amount- Fixed fee amount- Example:
Flat(100)= 100 units
-
Dynamic(base_bps) - Tiered fees based on amount ranges
base_bps- Base fee in basis points- Tiers:
- Amount < 1000: base_bps
- 1000 ≤ Amount < 10000: base_bps / 2
- Amount ≥ 10000: base_bps / 4
- Example:
Dynamic(400)= 4% for small, 2% for medium, 1% for large
These functions are used internally by the contract but not exposed as public methods.
Calculates only the platform fee (backward compatible).
pub fn calculate_platform_fee(
env: &Env,
amount: i128,
) -> Result<i128, ContractError>Used by create_remittance() to calculate the fee when creating a new remittance.
Aggregates fees for multiple transactions.
pub fn calculate_batch_fees(
env: &Env,
amounts: &[i128],
corridor: Option<&FeeCorridor>,
) -> Result<FeeBreakdown, ContractError>Used for batch settlement operations to calculate total fees across multiple remittances.
The fee service returns standard ContractError variants:
InvalidAmount- Amount is zero, negative, or invalidInvalidFeeBps- Fee basis points exceed maximum (10000)Overflow- Arithmetic overflow in calculationNotInitialized- Contract not properly initialized
// Before user commits to transaction, show fee breakdown
let breakdown = client.calculate_fee_breakdown(&amount);
display_to_user(&format!(
"Amount: {}\n\
Platform Fee: {}\n\
Protocol Fee: {}\n\
Total Fees: {}\n\
You will receive: {}",
breakdown.amount,
breakdown.platform_fee,
breakdown.protocol_fee,
breakdown.total_fees,
breakdown.net_amount
));// Check if corridor exists for this country pair
let corridor = client.get_fee_corridor(&from_country, &to_country);
let breakdown = if let Some(corr) = corridor {
// Use corridor-specific rates
client.calculate_fee_breakdown_with_corridor(&amount, &corr)?
} else {
// Use default rates
client.calculate_fee_breakdown(&amount)?
};// Set up multiple corridors
let corridors = vec![
FeeCorridor {
from_country: String::from_str(&env, "US"),
to_country: String::from_str(&env, "MX"),
strategy: FeeStrategy::Percentage(150),
protocol_fee_bps: Some(50),
},
FeeCorridor {
from_country: String::from_str(&env, "US"),
to_country: String::from_str(&env, "PH"),
strategy: FeeStrategy::Percentage(200),
protocol_fee_bps: Some(75),
},
];
for corridor in corridors {
client.set_fee_corridor(&admin, &corridor)?;
}Old way (before refactor):
// Fee was calculated internally, no breakdown available
let remittance_id = client.create_remittance(&sender, &agent, &amount, &None);
let remittance = client.get_remittance(&remittance_id);
// Only remittance.fee was availableNew way (after refactor):
// Get complete breakdown before creating remittance
let breakdown = client.calculate_fee_breakdown(&amount);
// Show breakdown to user
display_fees(&breakdown);
// Then create remittance
let remittance_id = client.create_remittance(&sender, &agent, &amount, &None);- Always show fee breakdown to users before they commit to a transaction
- Use corridors for cross-border transactions to optimize fees
- Cache corridor lookups if making multiple calculations for same country pair
- Validate amounts before calling fee calculation functions
- Handle errors gracefully and provide clear error messages to users
- Fee calculations are O(1) - constant time
- Corridor lookups are O(1) - single storage read
- Batch calculations are O(n) where n is number of amounts
- No network calls or external dependencies
- All arithmetic uses checked operations to prevent overflow
- Corridor management requires admin authentication
- Fee breakdowns self-validate for consistency
- Input validation at service boundary
- Type-safe Rust implementation prevents common errors
- Initial release of centralized fee service
- Support for corridor-based configurations
- Complete fee breakdown functionality
- Three fee strategies: Percentage, Flat, Dynamic