Flash Loans
Execute uncollateralized flash loans on Scallop for arbitrage, liquidations, and collateral swaps.
Overview
Flash loans allow borrowing without collateral, as long as the loan is repaid within the same transaction. If not repaid, the entire transaction reverts.
Key Properties
- No Collateral Required: Borrow any available amount
- Same-Transaction Repay: Must repay before transaction ends
- Fee: Small fee added to repay amount
- Atomic: All-or-nothing execution
Return Type
borrow_flash_loan() returns tuple[TxResult, TxResult] — nested PTB result handles for the borrowed coin and flash loan receipt:
coin_result, receipt_result = tx.borrow_flash_loan(amount, coin_name)
# coin_result: TxResult handle for the borrowed coin
# receipt_result: TxResult handle for the flash loan receipt
These TxResult handles can be passed directly to subsequent calls like repay_flash_loan(), supply(), and liquidate().
Basic Flash Loan
Python
tx = builder.create_tx_block()
# Borrow 1000 USDC via flash loan
# Returns tuple[TxResult, TxResult]: (coin, receipt)
usdc_coin, flash_receipt = tx.borrow_flash_loan(
amount=1_000_000_000, # 1000 USDC (6 decimals)
coin_name="usdc"
)
# === Use the borrowed funds here ===
# Example: swap, liquidate, arbitrage, etc.
# Repay flash loan (amount + fee)
tx.repay_flash_loan(usdc_coin, flash_receipt, "usdc")
result = builder.sign_and_send_tx_block(tx)
print(f"Flash loan TX: {result.digest}")
TypeScript
const tx = builder.createTxBlock();
// Borrow
const [usdcCoin, flashReceipt] = await tx.borrowFlashLoan(1_000_000_000, 'usdc');
// ... use funds ...
// Repay
await tx.repayFlashLoan(usdcCoin, flashReceipt, 'usdc');
const result = await builder.signAndSendTxBlock(tx);
Flash Loan Fee
Flash loans have a small per-asset fee:
repay_amount = borrowed_amount + (borrowed_amount * fee_rate)
fee_rate = fee_numerator / fee_denominator # stored on-chain per asset;
# both SDKs return the ratio pre-computed
Indicative rates (USDC, SUI and most assets sit at 0.05%, with some at 0.05%–0.1%) — do not hard-code these in profitability math. Fees can be changed by governance; query them live before each run.
Query Live Fee Rates (TypeScript)
const scallopSDK = new Scallop({ networkType: 'mainnet' });
await scallopSDK.init();
const query = scallopSDK.client.query;
// Returns Record<coinName, number> — the fee RATIO is already computed
// (fee numerator divided by the on-chain denominator). There is no
// { fee, feeScale } object shape.
const fees = await query.getFlashLoanFees(['usdc', 'sui']);
const usdcRate = fees.usdc; // e.g. 0.0005 for 0.05%
console.log(`Live USDC flash-loan fee: ${(usdcRate * 100).toFixed(4)}%`);
// Use the live rate in profitability checks
const feeAmount = borrowAmount * usdcRate;
const repayTotal = borrowAmount + feeAmount;
Always call
getFlashLoanFeesthrough theScallopQueryinstance — the underlying repository helper is internal and not re-exported from the package root.
The Python SDK (sui-scallop-sdk >= 0.3.0a1) wraps the same query: query.get_flash_loan_fees(coin_names) returns dict[str, Decimal] (fee ratios), and query.get_flash_loan_fee(coin_name) returns a single Decimal.
Use Case 1: Arbitrage
Profit from price differences across DEXes:
tx = builder.create_tx_block()
# 1. Flash borrow 10,000 USDC
usdc_coin, receipt = tx.borrow_flash_loan(10_000_000_000, "usdc")
# 2. Buy SUI on DEX A (lower price)
sui_coin = dex_a.swap(usdc_coin, "usdc", "sui")
# 3. Sell SUI on DEX B (higher price)
usdc_profit = dex_b.swap(sui_coin, "sui", "usdc")
# 4. Repay flash loan (keep profit)
tx.repay_flash_loan(usdc_profit, receipt, "usdc")
# 5. Transfer remaining profit
tx.transfer_objects([remaining_usdc], wallet_address)
result = builder.sign_and_send_tx_block(tx)
A visible price gap is not profit — slippage, the loan fee, competition and two-sided depth all come out of it first. Sizing and pre-submit checks: arbitrage-examples.md.
Use Case 2: Liquidation
Liquidate without capital:
tx = builder.create_tx_block()
# 1. Flash borrow to repay target's debt
usdc_coin, receipt = tx.borrow_flash_loan(repay_amount, "usdc")
# 2. Liquidate underwater position
# Returns tuple[TxResult, TxResult]: (remaining_debt, collateral)
remaining_debt, collateral = tx.liquidate(
obligation_id=target_obligation_id,
repay_coin_idx=usdc_coin,
debt_coin_name="usdc",
collateral_coin_name="sui",
)
# 3. Swap some collateral to USDC for repayment
usdc_for_repay = dex.swap(collateral_portion, "sui", "usdc")
# 4. Repay flash loan
tx.repay_flash_loan(usdc_for_repay, receipt, "usdc")
# 5. Keep remaining collateral as profit
tx.transfer_objects([remaining_collateral], wallet_address)
result = builder.sign_and_send_tx_block(tx)
Advanced Patterns
Three further compositions are covered in flash-loan-patterns.md:
- Collateral swap — change collateral type without closing the position, using the borrowed asset as temporary scaffolding.
- Leverage loop — assemble a leveraged position atomically instead of looping deposit-and-borrow.
- Multi-asset flash loan — borrow several assets at once; each receipt must be repaid with its matching asset.
Error Handling
Transaction Will Fail If:
- Not Repaid: Flash loan not repaid before tx ends
- Wrong Receipt: Using wrong receipt for repay
- Insufficient Repay: Repay amount less than required
- Liquidity: Market doesn't have enough to lend
Error Codes
| Code | Error | Cause |
|---|---|---|
| 701 | FlashLoanNotRepaid |
Loan not repaid in same tx |
| 702 | FlashLoanAmountTooHigh |
Exceeds available liquidity |
| 703 | InvalidFlashLoanReceipt |
Wrong receipt used |
Safe Pattern
def safe_flash_loan(builder, operations_fn, amount, coin_name, wallet):
tx = builder.create_tx_block()
try:
# Borrow
coin, receipt = tx.borrow_flash_loan(amount, coin_name)
# Execute custom operations
result_coin = operations_fn(tx, coin)
# Repay
tx.repay_flash_loan(result_coin, receipt, coin_name)
return builder.sign_and_send_tx_block(tx)
except Exception as e:
# Transaction automatically reverts
raise Exception(f"Flash loan failed: {e}")
Profitability Check
Before executing, verify profitability:
def is_profitable(borrow_amount, expected_return, fee_rate, gas_cost=0.01):
"""Check if flash loan operation is profitable.
fee_rate MUST be pulled live (via queryFlashLoanFees in the TS SDK,
or a raw RPC read of the FlashLoanFee object). Do not pass a hard-coded value.
"""
fee = borrow_amount * fee_rate
total_cost = borrow_amount + fee + gas_cost
profit = expected_return - total_cost
return profit > 0, profit
Available Liquidity
Check available flash loan amount:
query = client.create_query()
# get_market_data() returns dict[str, MarketPool]
market = query.get_market_data()
for coin_name, pool in market.items():
print(f"{coin_name}: pool data available for flash loan check")
Note: The Python SDK's
get_market_data()implementation is still maturing. For detailed liquidity data, use the TypeScript SDK or Scallop app.
References
- Flash Loan Patterns - Advanced patterns
- Arbitrage Examples - Arbitrage strategies
- Error Codes - Error reference