MultiversX Atomic Lend-Execute-Verify Pattern
Lend assets, execute callback, verify repayment — all atomically in one transaction.
When to Use
- Flash loans, atomic swaps, temporary grants
- NOT for cross-shard operations (breaks atomicity)
Security Checklist
- Reentrancy guard — prevent nested operations
- Shard validation — target must be same shard
- Endpoint validation — callback must not be built-in
- Repayment verification — check contract balance after callback
- Guard cleanup — always clear the flag
Core Flow
#[endpoint(atomicOperation)]
fn atomic_operation(&self, asset: TokenIdentifier, amount: BigUint, target: ManagedAddress, callback: ManagedBuffer) {
self.require_not_ongoing();
self.require_same_shard(&target);
require!(!callback.is_empty() && !self.blockchain().is_builtin_function(&callback), "Invalid endpoint");
let fee = &amount * self.fee_bps().get() / 10_000u64;
let balance_before = self.blockchain().get_sc_balance(&asset.clone().into(), 0);
self.operation_ongoing().set(true);
self.tx().to(&target).raw_call(callback).single_esdt(&asset, 0, &amount).sync_call();
let balance_after = self.blockchain().get_sc_balance(&asset.into(), 0);
require!(balance_after >= balance_before + &fee, "Repayment insufficient");
self.operation_ongoing().set(false);
}
Reentrancy Guard
#[storage_mapper("operationOngoing")]
fn operation_ongoing(&self) -> SingleValueMapper<bool>;
fn require_not_ongoing(&self) {
require!(!self.operation_ongoing().get(), "Operation already in progress");
}
Shard Validation
fn require_same_shard(&self, target: &ManagedAddress) {
let target_shard = self.blockchain().get_shard_of_address(target);
let self_shard = self.blockchain().get_shard_of_address(&self.blockchain().get_sc_address());
require!(target_shard == self_shard, "Must be same shard");
}
Why: Cross-shard calls execute in different blocks, breaking atomicity.
Anti-Patterns
- Checking storage value instead of actual
get_sc_balancefor repayment - No shard validation (cross-shard sync_call becomes async silently)
- Built-in function callbacks bypassing repayment logic