module flash_lender::example {
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin};
/// Shared object offering flash loans
public struct FlashLender<phantom T> has key {
id: UID,
to_lend: Balance<T>, // Available funds
fee: u64, // Fee per loan
}
/// Hot potato receipt - MUST be repaid
public struct Receipt<phantom T> {
flash_lender_id: ID,
repay_amount: u64,
}
/// Admin capability
public struct AdminCap has key, store {
id: UID,
flash_lender_id: ID,
}
const ELoanTooLarge: u64 = 0;
const EInvalidRepaymentAmount: u64 = 1;
const ERepayToWrongLender: u64 = 2;
const EAdminOnly: u64 = 3;
/// Create a flash lender
public fun new<T>(
to_lend: Balance<T>,
fee: u64,
ctx: &mut TxContext
): AdminCap {
let id = object::new(ctx);
let flash_lender_id = id.uid_to_inner();
let flash_lender = FlashLender { id, to_lend, fee };
// Share the lender so anyone can borrow
transfer::share_object(flash_lender);
// Return admin capability to creator
AdminCap { id: object::new(ctx), flash_lender_id }
}
/// Borrow funds - returns loan and receipt
public fun loan<T>(
self: &mut FlashLender<T>,
amount: u64,
ctx: &mut TxContext,
): (Coin<T>, Receipt<T>) {
assert!(self.to_lend.value() >= amount, ELoanTooLarge);
let loan = coin::take(&mut self.to_lend, amount, ctx);
let repay_amount = amount + self.fee;
let receipt = Receipt {
flash_lender_id: object::id(self),
repay_amount,
};
(loan, receipt)
}
/// Repay loan - consumes receipt
public fun repay<T>(
self: &mut FlashLender<T>,
payment: Coin<T>,
receipt: Receipt<T>
) {
let Receipt { flash_lender_id, repay_amount } = receipt;
assert!(object::id(self) == flash_lender_id, ERepayToWrongLender);
assert!(payment.value() == repay_amount, EInvalidRepaymentAmount);
coin::put(&mut self.to_lend, payment)
}
/// Admin can withdraw funds
public fun withdraw<T>(
self: &mut FlashLender<T>,
admin: &AdminCap,
amount: u64,
ctx: &mut TxContext,
): Coin<T> {
assert!(object::borrow_id(self) == &admin.flash_lender_id, EAdminOnly);
coin::take(&mut self.to_lend, amount, ctx)
}
}