Using Flash Swaps on Bluefin
Overview
The flash swap functionality allows you to borrow tokens from a pool without collateral, use them in other operations, and repay them within the same transaction. This guide covers both TypeScript and Move implementations.
Understanding Token Borrowing and Repayment
- Token Borrowing:
- Users borrow only one token at a time, not both
- The borrowed token is determined by the a2b parameter:
// If a2b = true: Borrow Token B
let (balance_a, balance_b, receipt) = pool::flash_swap<CoinTypeA, CoinTypeB>(
// ... parameters with a2b = true ...
);
// balance_a will be zero
// balance_b will contain the borrowed Token B
// If a2b = false: Borrow Token A
let (balance_a, balance_b, receipt) = pool::flash_swap<CoinTypeA, CoinTypeB>(
// ... parameters with a2b = false ...
);
// balance_a will contain the borrowed Token A
// balance_b will be zero
- Receipt Structure
struct FlashSwapReceipt<phantom CoinTypeA, phantom CoinTypeB> {
pool_id: ID, // ID of the pool the tokens were borrowed from
a2b: bool, // Direction of the swap (which token was borrowed)
pay_amount: u64, // Amount that must be repaid (including fees)
}
What Must Be Repaid?
- Repayment Requirements:
- Must return the same token that was borrowed
- Amount to repay is stored in receipt.pay_amount
- Repayment amount includes:
- Original borrowed amount
- Pool fees
- Protocol fees
// 1. Borrow Token B
let (_, borrowed_token_b, receipt) = pool::flash_swap<CoinTypeA, CoinTypeB>(
// ... parameters with a2b = true ...
);
// 2. Use Token B in another protocol
let used_token_b = use_in_other_protocol(borrowed_token_b);
// 3. Repay Token B
pool::repay_flash_swap<CoinTypeA, CoinTypeB>(
protocol_config,
pool,
balance::zero<CoinTypeA>(), // No Token A to repay
used_token_b, // Repay Token B
receipt // Contains repayment amount
);
Visual Flow
Function Signatures and Parameters
1. Flash Swap Function
public fun flash_swap<CoinTypeA, CoinTypeB>(
clock: &Clock,
protocol_config: &GlobalConfig,
pool: &mut Pool<CoinTypeA, CoinTypeB>,
a2b: bool,
by_amount_in: bool,
amount: u64,
sqrt_price_max_limit: u128
) : (Balance<CoinTypeA>, Balance<CoinTypeB>, FlashSwapReceipt<CoinTypeA, CoinTypeB>)
Parameters:
- Generic Types:
CoinTypeA
: First token type in the poolCoinTypeB
: Second token type in the pool
- clock: &Clock ( passes as string object ID)
- Reference to Sui's clock object
- Used for timestamp validation and event tracking
- Required for protocol operations
- protocol_config: &GlobalConfig ( passes as string object ID)
- Reference to protocol's global configuration
- Contains protocol-wide settings and parameters
- Used for version checking and protocol validation
- pool: &mut Pool<CoinTypeA, CoinTypeB> ( passes as string object ID)
- Mutable reference to the pool object
- Contains pool state, balances, and configuration
- Modified during the flash swap operation
- a2b: bool
- Direction of the swap
- true: Borrow Token B (swap from A to B)
- false: Borrow Token A (swap from B to A)
- by_amount_in: bool
- Specifies how the amount parameter should be interpreted
true
: amount is the input amountfalse
: amount is the output amount
- amount: u64 (must be scaled to the required token decimals)
- The amount to be swapped
- Interpreted as input or output based on by_amount_in
- Must be greater than 0
- sqrt_price_max_limit: u128
- Maximum/minimum price limit for the swap
- In square root price format (Q64.64)
- Used for slippage protection
- can be fetched from chain/contracts
Returns:
- Balance: Balance of Token A (will be zero for a2b=true)
- Balance: Balance of Token B (will be zero for a2b=false)
FlashSwapReceipt
: Contains information needed for repayment
2. Repay Flash Swap Function
public fun repay_flash_swap<CoinTypeA, CoinTypeB>(
protocol_config: &GlobalConfig,
pool: &mut Pool<CoinTypeA, CoinTypeB>,
coin_a: Balance<CoinTypeA>,
coin_b: Balance<CoinTypeB>,
receipt: FlashSwapReceipt<CoinTypeA, CoinTypeB>
)
Parameters:
- Generic Types:
- CoinTypeA: First token type in the pool
- CoinTypeB: Second token type in the pool
- protocol_config: &GlobalConfig
- Reference to protocol's global configuration
- Used for version checking and protocol validation
- pool: &mut Pool<CoinTypeA, CoinTypeB>
- Mutable reference to the pool object
- Will be modified to update balances and state
- coin_a: Balance
- Balance of Token A to be repaid
- Should be zero if a2b was true in flash_swap
- Should contain repayment amount if a2b was false
- coin_b: Balance
- Balance of Token B to be repaid
- Should be zero if a2b was false in flash_swap
- Should contain repayment amount if a2b was true
- receipt: FlashSwapReceipt<CoinTypeA, CoinTypeB>
- Receipt from the original flash_swap call
- Contains:
pool_id
: ID of the poola2b
: Direction of the original swappay_amount
: Amount that must be repaid
- Contains:
- Receipt from the original flash_swap call
Returns:
- None
- Modifies the pool state directly
Integration Guide
1. TypeScript Implementation (using Mysten SDK)
async function executeFlashSwap(
client: SuiClient,
poolId: string,
configId: string,
clockId: string,
a2b: boolean,
byAmountIn: boolean,
amount: bigint,
sqrtPriceMaxLimit: bigint
) {
const tx = new TransactionBlock();
// 1. Call flash_swap
const [balanceA, balanceB, receipt] = tx.moveCall({
target: `${PACKAGE_ID}::pool::flash_swap`,
arguments: [
tx.object(clockId),
tx.object(configId),
tx.object(poolId),
tx.pure(a2b),
tx.pure(byAmountIn),
tx.pure(amount),
tx.pure(sqrtPriceMaxLimit)
],
typeArguments: [COIN_TYPE_A, COIN_TYPE_B]
});
// 2. Use the borrowed tokens (example: swap in another pool)
const [swappedBalance] = tx.moveCall({
target: `${OTHER_PACKAGE_ID}::pool::swap`,
arguments: [balanceA, balanceB],
typeArguments: [COIN_TYPE_A, COIN_TYPE_B]
});
// 3. Repay the flash swap
tx.moveCall({
target: `${PACKAGE_ID}::pool::repay_flash_swap`,
arguments: [
tx.object(configId),
tx.object(poolId),
swappedBalance,
tx.object(receipt)
],
typeArguments: [COIN_TYPE_A, COIN_TYPE_B]
});
return tx;
}
2. Move contracts integeration
module custom_module::flash_swap_example {
use sui::clock::{Self, Clock};
use bluefin_spot::pool::{Self, Pool, FlashSwapReceipt};
use bluefin_spot::config::{Self, GlobalConfig};
use sui::balance::{Self, Balance};
use sui::tx_context::{Self, TxContext};
public fun execute_flash_swap<CoinTypeA, CoinTypeB>(
clock: &Clock,
protocol_config: &GlobalConfig,
pool: &mut Pool<CoinTypeA, CoinTypeB>,
a2b: bool,
by_amount_in: bool,
amount: u64,
sqrt_price_max_limit: u128,
ctx: &mut TxContext
) {
// 1. Borrow tokens
let (balance_a, balance_b, receipt) = pool::flash_swap<CoinTypeA, CoinTypeB>(
clock,
protocol_config,
pool,
a2b,
by_amount_in,
amount,
sqrt_price_max_limit
);
// 2. Use the borrowed tokens (example: swap in another pool)
let (swapped_balance_a, swapped_balance_b) = use_borrowed_tokens<CoinTypeA, CoinTypeB>(
balance_a,
balance_b,
ctx
);
// 3. Repay the flash swap
pool::repay_flash_swap<CoinTypeA, CoinTypeB>(
protocol_config,
pool,
swapped_balance_a,
swapped_balance_b,
receipt
);
}
fun use_borrowed_tokens<CoinTypeA, CoinTypeB>(
balance_a: Balance<CoinTypeA>,
balance_b: Balance<CoinTypeB>,
ctx: &mut TxContext
): (Balance<CoinTypeA>, Balance<CoinTypeB>) {
// Implement your logic here
// Example: Swap in another pool, use in another protocol, etc.
(balance_a, balance_b)
}
}
Common Errors
Common errors to handle:
EFlashSwapInProgress
: Another flash swap is in progressENoFlashSwapInProgress
: Trying to repay without an active flash swapEInvalidPriceLimit
: Price limit is outside allowed rangeEInsufficientAmount
: Amount is zero or too small
Important Considerations for Token Handling
- Token Selection:
- Choose the correct token to borrow based on your use case
- Consider the direction of the swap (a2b)
- Remember that only one token can be borrowed at a time
- Repayment Verification:
- Always verify the receipt matches the pool
- Ensure you're repaying the correct token
- Include all fees in the repayment amount
- Error Handling:
- Handle cases where the borrowed token is zero
- Verify the repayment amount matches the receipt
- Check for sufficient balance before repayment
Updated 28 days ago