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

  1. 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
  1. 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?

  1. 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:

  1. Generic Types:
    • CoinTypeA: First token type in the pool
    • CoinTypeB: Second token type in the pool
  2. clock: &Clock ( passes as string object ID)
    • Reference to Sui's clock object
    • Used for timestamp validation and event tracking
    • Required for protocol operations
  3. 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
  4. 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
  5. a2b: bool
    • Direction of the swap
    • true: Borrow Token B (swap from A to B)
    • false: Borrow Token A (swap from B to A)
  6. by_amount_in: bool
    • Specifies how the amount parameter should be interpreted
    • true: amount is the input amount
    • false: amount is the output amount
  7. 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
  8. 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:

  1. Generic Types:
    • CoinTypeA: First token type in the pool
    • CoinTypeB: Second token type in the pool
  2. protocol_config: &GlobalConfig
    • Reference to protocol's global configuration
    • Used for version checking and protocol validation
  3. pool: &mut Pool<CoinTypeA, CoinTypeB>
    • Mutable reference to the pool object
    • Will be modified to update balances and state
  4. 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
  5. 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
  6. receipt: FlashSwapReceipt<CoinTypeA, CoinTypeB>
    • Receipt from the original flash_swap call
      • Contains:
        • pool_id: ID of the pool
        • a2b: Direction of the original swap
        • pay_amount: Amount that must be repaid

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 progress
  • ENoFlashSwapInProgress: Trying to repay without an active flash swap
  • EInvalidPriceLimit: Price limit is outside allowed range
  • EInsufficientAmount: Amount is zero or too small

Important Considerations for Token Handling

  1. 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
  2. Repayment Verification:
    • Always verify the receipt matches the pool
    • Ensure you're repaying the correct token
    • Include all fees in the repayment amount
  3. Error Handling:
    • Handle cases where the borrowed token is zero
    • Verify the repayment amount matches the receipt
    • Check for sufficient balance before repayment