How do I borrow a flash loan? A deep dive

Examples in this document are for demonstration purposes only. Use them only on test networks.

1 Overview

To borrow a flash loan, you need to create and deploy a Flash Borrower smart contract that borrows a flash loan, performs a business operation, and returns the loan with the borrowing fee. This guide demonstrates how to write and deploy a simple flash borrower smart contract that borrows and returns a flash loan. Smart contracts are written in Solidity and can be deployed and tested using Remix IDE.

In the diagram below, we present a high-level interaction flow between the smart contracts

Flash loans are borrowed and used as follows

  1. Alice invokes the flash lending process by calling the FlashLoan method on the Equalizer flash loan provider smart contract

  2. The flash loan provider smart contract triggers the flash loan transfer from the vault to the flash borrower smart contract built and deployed by Alice

  3. The assets are transferred to the flash borrower smart contract

  4. The flash loan provider smart contract triggers the business logic operation on the flash borrower smart contract

  5. The flash borrower smart contract uses the loan to perform business operations on a decentralized exchange

  6. Flash loan provider smart contract triggers the transfer of assets from the flash borrower back to the vault

All operations are performed within a single blockchain transaction, hence the name Flash Loan.

If the principal and the fee are not returned, the transaction will revert and all funds will be restored to the vault.

2 Simple flash borrowing smart contract

Below, we present a simple flash borrower method that borrows a flash loan, emits an event and returns the flash loan:

/*
*  FlashBorrowerExample is a simple method that
*  borrows and returns a flash loan.
*/
contract FlashBorrowerExample is IERC3156FlashBorrower {
    uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    // @dev ERC-3156 Flash loan callback
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external override returns (bytes32) {
        
        // Set the allowance to payback the flash loan
        IERC20(token).approve(msg.sender, MAX_INT);
        
        // Build your trading business logic here
        // e.g., sell on uniswapv2
        // e.g., buy on uniswapv3

        // Return success to the lender, he will transfer get the funds back if allowance is set accordingly
        return keccak256('ERC3156FlashBorrower.onFlashLoan');
    }
}

The full smart contract example is presented in Appendix A.1 of this document.

3 Deploy the smart contract

For testing purposes, we strongly suggest deploying the test smart contract on one of the supported blockchain test nets.

This section summarizes how to deploy and interact with a smart contract using Remix IDE.

Visit the Remix - Ethereum IDE website Remix is an online tool for writing and interacting with smart contracts.

Create smart contract Click on New File and name it FlashBorrowerExample. Copy the smart contract from Appendix A.1 or the one we deployed on the Rinkeby Testnet.

Compile the smart contract Solidity is a compiled language meaning smart contracts must be compiled before we deploy them on a blockchain. In the sidebar select SOLIDITY COMPILER and compile the smart contract by clicking on the Compile button.

Deploy the smart contract to the Rinkeby test network After we In the sidebar select DEPLOY & RUN TRANSACTIONS as presented below. For the demo purpose, we deployed a simple Flash Borrower Example smart contract on the Rinkeby Testnet. If you just want to interact with the smart contract, skip this step.

Flash Borrower example smart contract address on Rinkeby Testnet is 0x5b4b021d2abcf6116d782884bc5cd414027aeff2

Next, select Environment: Injected Web3 as presented below. This will open your Metamask plugin and ask you to connect your wallet. Click on Deploy to deploy the smart contract. You will be prompted to sign a transaction.

Make sure you are connected to the Rinkeby testnet.

4 Interact with a smart contract via Etherscan

In the How do I borrow a flash loan? guide you can learn how to interact with the smart contract. To call your smart contract, replace the receiver address with the address of the contract you deployed.

5 Interact with a smart contract using Ethers

For testing purposes, we strongly suggest deploying the test smart contract on one of the supported blockchain test nets.

Below we present a JavaScript snippet you can use to interact with the Flash Borrower smart contract.

// Load the ethers librariy
const {
  ethers
} = require("ethers");
// Load the ABI. The flashloan Provider interface can be found on
// https://github.com/Equalizer-Finance/equalizer-smart-contracts-v1/blob/master/contracts/interfaces/IERC3156FlashLender.sol
const providerAbi = require("./abi.json");
// JSON-RPC endpoint
const jsonRpcURL = 'https://rinkeby.infura.io/v3/<API-KEY>';

// Private key of the account that will borrow a flash loan
// Important! Never share your private key with anyone!
const privateKey = '<YOUR-PRIVATE-KEY-GOES-HERE>';

// the address of the FlashLoan Receiver that implements the interface 
// https://github.com/Equalizer-Finance/equalizer-smart-contracts-v1/blob/master/contracts/interfaces/IERC3156FlashBorrower.sol

// example receiver here: https://rinkeby.etherscan.io/address/0x9B38b85e22C94F4a57b4507216B7E444b667B872#code
const flashLoanReceiverAddress = '0x41EC9B0C0f0a354e3d7d9f1982643eEd0a8C2EB1';

// the address of the flashloan provider - rinkeby example
const flashLoanProviderAddress = '0x3649380F6ce217Ae1d76FCc4092d3c84EB45f7d1';

// the address of the borrowed token - rinkeby USDT example below
const tokenAddress = '0xbE6C02195A28d163A1DF2e05B0596416d326b190';

// can be extracted from vaultFactory with tokenAddress
const vaultOfToken = '0xCfB9243003EAB8676634e891162a6f7c74855E8f';

(async () => {
  // init the provider
  const provider = new ethers.providers.JsonRpcProvider(jsonRpcURL);
  // init the wallet
  const wallet = new ethers.Wallet(privateKey, provider);

  // init the contract of flashloan provider with wallet connected to it
  const flashLoanProviderContract = new ethers.Contract(flashLoanProviderAddress, providerAbi, wallet);
  // const receiverContract = new ethers.Contract (flashLoanReceiverAddress, )
  try {

    // check max loan amount for the token
    const maxAmount = await flashLoanProviderContract.maxFlashLoan(tokenAddress);
    console.log(maxAmount);
    // make sure u set allowance for the provider for the vault
    // get a loan

    console.log(await (await flashLoanProviderContract.flashLoan(flashLoanReceiverAddress, tokenAddress, 100, "0x", {
      gasLimit: 1000000,
      gasPrice: 1000000000
    })).wait());

  } catch (e) {
    console.log(e);
    // Error handling
  }
})();

Appendix

A.1 Flash Borrower smart contract example

// SPDX-License-Identifier: MIT
pragma solidity 0.8.4;

interface IERC20 {
    function approve(address spender, uint256 amount) external returns (bool);
}

interface IERC3156FlashBorrower {
    /**
     * @dev Receive a flash loan.
     * @param initiator The initiator of the loan.
     * @param token The loan currency.
     * @param amount The amount of tokens lent.
     * @param fee The additional amount of tokens to repay.
     * @param data Arbitrary data structure, intended to contain user-defined parameters.
     * @return The keccak256 hash of "ERC3156FlashBorrower.onFlashLoan"
     */
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external returns (bytes32);
}

/*
*  FlashBorrowerExample is a simple smart contract that enables
*  to borrow and returns a flash loan.
*/
contract FlashBorrowerExample is IERC3156FlashBorrower {
    uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

    // @dev ERC-3156 Flash loan callback
    function onFlashLoan(
        address initiator,
        address token,
        uint256 amount,
        uint256 fee,
        bytes calldata data
    ) external override returns (bytes32) {
        
        // Set the allowance to payback the flash loan
        IERC20(token).approve(msg.sender, MAX_INT);
        
        // Build your trading business logic here
        // e.g., sell on uniswapv2
        // e.g., buy on uniswapv3

        // Return success to the lender, he will transfer get the funds back if allowance is set accordingly
        return keccak256('ERC3156FlashBorrower.onFlashLoan');
    }
}

A.2 Flash Loan Provider ABI

[
  {
    "inputs": [
      {
        "internalType": "contract VaultFactory",
        "name": "_vaultFactory",
        "type": "address"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "anonymous": false,
    "inputs": [
      {
        "indexed": true,
        "internalType": "address",
        "name": "receiver",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "fee",
        "type": "uint256"
      },
      {
        "indexed": false,
        "internalType": "uint256",
        "name": "treasuryFee",
        "type": "uint256"
      }
    ],
    "name": "FlashLoan",
    "type": "event"
  },
  {
    "inputs": [],
    "name": "CALLBACK_SUCCESS",
    "outputs": [
      {
        "internalType": "bytes32",
        "name": "",
        "type": "bytes32"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      }
    ],
    "name": "flashFee",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "contract IERC3156FlashBorrower",
        "name": "receiver",
        "type": "address"
      },
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      },
      {
        "internalType": "uint256",
        "name": "amount",
        "type": "uint256"
      },
      {
        "internalType": "bytes",
        "name": "data",
        "type": "bytes"
      }
    ],
    "name": "flashLoan",
    "outputs": [
      {
        "internalType": "bool",
        "name": "",
        "type": "bool"
      }
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [
      {
        "internalType": "address",
        "name": "token",
        "type": "address"
      }
    ],
    "name": "maxFlashLoan",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "vaultFactory",
    "outputs": [
      {
        "internalType": "contract VaultFactory",
        "name": "",
        "type": "address"
      }
    ],
    "stateMutability": "view",
    "type": "function"
  }
]

Last updated