This document describes the net settlement logic implementation for SwiftRemit, which offsets opposing transfers between the same two parties so only the net difference is executed on-chain.
In a remittance system, multiple transfers can occur between the same parties in opposite directions. Without netting:
- If A transfers 100 to B and B transfers 90 to A, two separate on-chain transfers occur
- This results in unnecessary gas costs and blockchain congestion
- Total transfer volume: 190 units
With net settlement:
- Only the net difference (10 from A to B) is executed on-chain
- Reduces gas costs and improves efficiency
- Total transfer volume: 10 units (95% reduction)
The netting module provides deterministic, order-independent net settlement calculations:
Key Functions:
compute_net_settlements(): Computes net balances between all party pairsvalidate_net_settlement(): Verifies mathematical correctness and fee preservationnormalize_pair(): Ensures deterministic address orderingcompare_addresses(): Lexicographic address comparison
Data Structures:
NetTransfer: Represents a net transfer after offsettingparty_a: Lexicographically smaller addressparty_b: Lexicographically larger addressnet_amount: Net amount (positive = A→B, negative = B→A)total_fees: Accumulated fees from all netted remittances
Function: batch_settle_with_netting()
Processes multiple remittances in a single transaction with net settlement optimization.
Parameters:
entries: Vec<BatchSettlementEntry>: List of remittance IDs to settle
Returns:
BatchSettlementResult: Contains list of successfully settled remittance IDs
Process Flow:
- Validate batch size (1-50 remittances)
- Load and validate all remittances
- Check for duplicates, expiry, and status
- Compute net settlements using netting algorithm
- Validate mathematical correctness
- Execute minimal set of net transfers
- Accumulate all fees
- Mark all remittances as completed
- Emit events for monitoring
The algorithm produces identical results for the same input, regardless of:
- Order of remittances in the batch
- Time of execution
- Previous state
This is achieved through:
- Lexicographic address ordering
- Consistent aggregation logic
- No random or time-dependent operations
Processing remittances in any order yields the same net result:
Batch 1: [A→B: 100, B→A: 90] = Net: A→B: 10
Batch 2: [B→A: 90, A→B: 100] = Net: A→B: 10
All participants receive correct amounts:
- Fees are preserved exactly (no rounding errors)
- Net amounts are mathematically correct
- No party gains or loses from netting
The implementation maintains accounting integrity:
- Total fees in = Total fees out
- All remittances marked as completed
- Settlement hashes prevent duplicates
- Events emitted for all operations
Original Remittances:
- R1: A→B: 100 (fee: 2.5)
- R2: B→A: 90 (fee: 2.25)
Net Settlement:
- Net Transfer: A→B: 10
- Total Fees: 4.75 (2.5 + 2.25)
Verification:
✓ All fees preserved
✓ Net amount correct (100 - 90 = 10)
✓ No rounding errors
The validate_net_settlement() function verifies:
- Total fees are preserved exactly
- No arithmetic overflow
- All calculations are consistent
- Checks for duplicate remittance IDs in batch
- Uses settlement hashes to prevent double execution
- Validates remittance status before processing
- All remittances require proper authorization
- Agent addresses validated before transfers
- Admin-only pause mechanism
- All arithmetic operations use checked math
- Returns
ContractError::Overflowon overflow - Safe i128 operations throughout
- Validates settlement expiry timestamps
- Prevents settlement of expired remittances
- Returns
ContractError::SettlementExpiredwhen expired
- Contract can be paused by admin
- All settlements blocked when paused
- Returns
ContractError::ContractPausedwhen paused
// Create opposing remittances
let id1 = contract.create_remittance(&alice, &bob, &100, &None);
let id2 = contract.create_remittance(&bob, &alice, &90, &None);
// Batch settle with netting
let mut entries = Vec::new(&env);
entries.push_back(BatchSettlementEntry { remittance_id: id1 });
entries.push_back(BatchSettlementEntry { remittance_id: id2 });
let result = contract.batch_settle_with_netting(&entries);
// Result: Single transfer of 10 from Alice to Bob// Create equal opposing remittances
let id1 = contract.create_remittance(&alice, &bob, &100, &None);
let id2 = contract.create_remittance(&bob, &alice, &100, &None);
let mut entries = Vec::new(&env);
entries.push_back(BatchSettlementEntry { remittance_id: id1 });
entries.push_back(BatchSettlementEntry { remittance_id: id2 });
let result = contract.batch_settle_with_netting(&entries);
// Result: No net transfer (complete offset), but fees still collected// Create triangle of remittances
let id1 = contract.create_remittance(&alice, &bob, &100, &None);
let id2 = contract.create_remittance(&bob, &charlie, &50, &None);
let id3 = contract.create_remittance(&charlie, &alice, &30, &None);
let mut entries = Vec::new(&env);
entries.push_back(BatchSettlementEntry { remittance_id: id1 });
entries.push_back(BatchSettlementEntry { remittance_id: id2 });
entries.push_back(BatchSettlementEntry { remittance_id: id3 });
let result = contract.batch_settle_with_netting(&entries);
// Result: Three net transfers (one per pair)
// A→B: 100, B→C: 50, C→A: 30| Scenario | Without Netting | With Netting | Reduction |
|---|---|---|---|
| 2 opposing transfers (100, 90) | 2 transfers | 1 transfer | 50% |
| 2 equal opposing transfers (100, 100) | 2 transfers | 0 transfers | 100% |
| 10 alternating transfers | 10 transfers | 1-2 transfers | 80-90% |
| 50 mixed transfers | 50 transfers | 5-25 transfers | 50-90% |
- Each avoided transfer saves ~10,000-50,000 gas units
- Batch processing reduces overhead
- Single transaction for multiple settlements
The implementation includes comprehensive unit tests:
-
Basic Functionality
test_net_settlement_simple_offset: Basic A→B, B→A offsettest_net_settlement_complete_offset: Equal opposing transferstest_net_settlement_multiple_parties: Triangle of transfers
-
Mathematical Correctness
test_net_settlement_fee_preservation: Verifies all fees preservedtest_net_settlement_mathematical_correctness: Complex multi-transfer validationtest_net_settlement_order_independence: Same result regardless of order
-
Edge Cases
test_net_settlement_empty_batch: Empty batch rejectiontest_net_settlement_exceeds_max_batch_size: Batch size limittest_net_settlement_duplicate_ids: Duplicate detection
-
Error Conditions
test_net_settlement_already_completed: Prevents double settlementtest_net_settlement_when_paused: Respects pause statetest_net_settlement_reduces_transfer_count: Verifies optimization
-
Performance
test_net_settlement_large_batch: Maximum batch size (50)- Stress tests for various scenarios
# Run all tests
cargo test
# Run only net settlement tests
cargo test net_settlement
# Run with output
cargo test -- --nocapture| Error Code | Error | Description |
|---|---|---|
| 3 | InvalidAmount | Empty batch or exceeds MAX_BATCH_SIZE |
| 6 | RemittanceNotFound | Remittance ID doesn't exist |
| 7 | InvalidStatus | Remittance not in Pending status |
| 8 | Overflow | Arithmetic overflow in calculations |
| 10 | InvalidAddress | Address validation failed |
| 11 | SettlementExpired | Settlement window expired |
| 12 | DuplicateSettlement | Duplicate ID or already settled |
| 13 | ContractPaused | Contract is paused |
The implementation emits comprehensive events for monitoring:
emit_settlement_completed(
env,
sender: Address,
recipient: Address,
token: Address,
amount: i128
)emit_remittance_completed(
env,
remittance_id: u64,
sender: Address,
agent: Address,
token: Address,
amount: i128
)// JavaScript/TypeScript example
const entries = [
{ remittance_id: 1n },
{ remittance_id: 2n },
{ remittance_id: 3n }
];
const result = await contract.batch_settle_with_netting({
entries: entries
});
console.log(`Settled ${result.settled_ids.length} remittances`);Monitor events to track net settlements:
settle.complete: Net transfer executedremit.complete: Individual remittance completed
- Batch Size: Use 10-30 remittances per batch for optimal gas efficiency
- Party Grouping: Group remittances by party pairs for maximum netting benefit
- Timing: Batch settlements during low-traffic periods
- Monitoring: Track netting efficiency metrics
The net settlement implementation is fully backwards compatible:
- Existing
confirm_payout()function unchanged - New
batch_settle_with_netting()is additive - No breaking changes to storage or types
- All existing tests pass
Potential improvements for future versions:
- Multi-Token Netting: Support netting across different tokens
- Time-Weighted Netting: Prioritize older remittances
- Partial Settlement: Allow partial batch settlement on errors
- Netting Analytics: Track netting efficiency metrics
- Automated Batching: Automatic batch creation based on criteria
The net settlement implementation provides:
✅ Deterministic, order-independent calculations ✅ Mathematical correctness with fee preservation ✅ Comprehensive security and validation ✅ Significant gas savings (50-90% reduction) ✅ Full backwards compatibility ✅ Extensive test coverage ✅ Production-ready code
The implementation reduces on-chain transfer volume while maintaining complete accounting integrity and security.