James Bachini

Solidity Interface Examples | How To Connect The Lego Bricks Of DeFi

Solidity Interfaces

Solidity interfaces allow developers to call external contracts from within their own smart contract. This enables us to build on top of the existing DeFi ecosystem. In this tutorial we will be looking at how solidity interfaces work and going through some example code for common tasks.

  1. Declaring, Importing & Expanding Interfaces
  2. How To Find A Specific Solidity Interface
  3. Depositing Funds To Aave Interface Example
  4. Chainlink Price Feed Interface Example
  5. Uniswap v3 Swap Interface Example
  6. Conclusion
James On YouTube

Declaring, Importing & Expanding Interfaces

Interfaces are declared outside and before the contract itself. Here’s a simple example of some Solidity code that declares an interface and then calls it from the contract:-

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

interface IERC20 {
  function balanceOf(address account) external view returns (uint256);
}

contract MyContract {
  function check() external view returns (uint256) {
    address _userAddress = 0x47173B170C64d16393a52e6C480b3Ad8c302ba1e;
    address _tokenAddress = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
    uint256 balance = IERC20(_tokenAddress).balanceOf(_userAddress);
    return balance;
  }
}

We can also import and expand Solidity interfaces using libraries such as OpenZeppelin and also many DeFi protocols maintain their own interfaces that we can import directly from the repository. Here is an example of importing a standard ERC20 interface using OpenZeppelin and then expanding it with the deposit and withdraw functions that we use for WETH (wrapped Ethereum).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWEth is IERC20 {
  function withdraw(uint256 wad) external;
  function deposit() external payable;
}

contract MyContract {
  function check() external view returns (uint256) {
    address _userAddress = 0x47173B170C64d16393a52e6C480b3Ad8c302ba1e;
    address _tokenAddress = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
    uint256 balance = IWEth(_tokenAddress).balanceOf(_userAddress);
    return balance;
  }
}

With the above code we have access to all the normal ERC20 functions like balanceOf as well as the Weth specific functions like deposit();


How To Find A Specific Solidity Interface

There are a few ways which we will go through in roughly the order I would search for an interface that I wanted to use:-

Github Repos

The first place I would start is the official Github repository for the protocol I’m looking to integrate. There is often a contract with “I” placed in front of the contract name. For example IVault.sol will be the interface for the Vault.sol contract.

There are also plenty of 3rd party repositories that contain interfaces such as this excellent collection from Beefy Finance: https://github.com/beefyfinance/beefy-contracts/tree/master/contracts/BIFI/interfaces

Convert An ABI

If you can’t find an interface you can often find an ABI either in the Github repository or from Etherscan or the block explorer for the network in question. If you are compiling the contract yourself and you need an ABI/interface for your own code you can usually find it in the manifest/ or artifacts/ directories after compiling the code.

There’s a good tool for converting ABI’s to interfaces here: https://gnidan.github.io/abi-to-sol/

If you are looking for a contract address on Google make sure it’s not sending you to the governance token contract. You need the protocol contracts and not the token contract most of the time. One way to see how a protocol works is to just do a transaction on testnet and then take a look at it in etherscan and see what contracts and functions you are interacting with. You can also decode the input data to see how that is formatted.

etherscan input data decode
Here we can see a Metamask swap is utilising 1inch dex aggregator from the input data

Use Foundry’s Cast Tool

There’s a full Foundry Tutorial if you want to learn more but you can create an interface using cast with the following command and the contract address. Note that this requires that the contract is verified in etherscan.

cast interface 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984

Depositing Funds To Aave Interface Example

In this example we are going to set up a half-baked interface for Aave and use it to deposit some funds. Note that we don’t need to set up all the functions in the contract, just the ones that we think we are going to need. This code is for tokens held within the contract, if you wanted to deposit user funds you would need the user to approve first.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.14;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IWeth is IERC20 {
  function withdraw(uint256 wad) external;
  function deposit() external payable;
}

interface IAave {
  function deposit(address asset,uint256 amount,address onBehalfOf,uint16 referralCode) external;
  function borrow(address asset,uint256 amount,uint256 interestRateMode,uint16 referralCode,address onBehalfOf) external;
  function repay(address asset,uint256 amount,uint256 rateMode,address onBehalfOf) external returns (uint256);
  function withdraw(address asset,uint256 amount,address to) external returns (uint256);
  function getUserAccountData(address user) external view returns (uint256 totalCollateralETH, uint256 totalDebtETH, uint256 availableBorrowsETH, uint256 currentLiquidationThreshold, uint256 ltv, uint256 healthFactor);
}

contract MyContract {
  function deposit() external payable { // send me 100000 wei
    address _aaveAddress = 0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9;
    address _tokenAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    IWeth(_tokenAddress).deposit{value:100000}();
    IERC20(_tokenAddress).approve(_aaveAddress, 100000);
    IAave(_aaveAddress).deposit(_tokenAddress,100000,address(this),0);
  }
}

In this example we want to use the Chainlink oracle service to get a price feed for ETHUSD. Note there are a number of different price feeds that this code will work for across different blockchains.

Check here for the full list: https://docs.chain.link/docs/reference-contracts/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

interface EACAggregatorProxy {
  function latestAnswer() external view returns (int256);
}

contract MyContract {
  function ETHUSD() external view returns (uint256) {
    address _chainlinkAddress = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
    int256 ethPriceInt = EACAggregatorProxy(_chainlinkAddress).latestAnswer();
    uint256 ethDollarPrice = uint256(ethPriceInt) / 10e7;
    return ethDollarPrice;
  }
}

Uniswap v3 Swap Interface Example

This example is a little more complex because we need to input quite a lot of information into the function. Fortunately Uniswap makes this easier by providing a periphery interface we can use.

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.14;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract SwapTokens {
  address public _uinswapV3RouterAddress = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
  address private _daiAddress = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
  address private _wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; 

  function buyWeth(uint amountUSD) internal {
    uint256 deadline = block.timestamp + 15;
    uint24 fee = 3000;
    address recipient = address(this);
    uint256 amountIn = amountUSD; // includes 18 decimals
    uint256 amountOutMinimum = 0;
    uint160 sqrtPriceLimitX96 = 0;
    require(IERC20(_daiAddress).approve(address(_uinswapV3RouterAddress), amountIn), 'DAI approve failed');
    
    ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
      _daiAddress,
      _wethAddress,
      fee,
      recipient,
      deadline,
      amountIn,
      amountOutMinimum,
      sqrtPriceLimitX96
    );
    ISwapRouter(_uinswapV3RouterAddress).exactInputSingle(params);
  }
}

Conclusion

These examples show how we can use Solidity interfaces to integrate our smart contracts with external contracts and protocols. One of the most exciting things about building on Ethereum is that we have a whole ecosystem of decentralised exchanges, tokens, borrowing and lending platforms, derivatives and more that developers can mash together to create new and interesting things.


Get The Blockchain Sector Newsletter, binge the YouTube channel and connect with me on Twitter

The Blockchain Sector newsletter goes out a few times a month when there is breaking news or interesting developments to discuss. All the content I produce is free, if you’d like to help please share this content on social media.

Thank you.

James Bachini

Disclaimer: Not a financial advisor, not financial advice. The content I create is to document my journey and for educational and entertainment purposes only. It is not under any circumstances investment advice. I am not an investment or trading professional and am learning myself while still making plenty of mistakes along the way. Any code published is experimental and not production ready to be used for financial transactions. Do your own research and do not play with funds you do not want to lose.