Composability means combining elements of different smart contracts to create a new system. Learn how to use the power of composable smart contracts with USDC.
Composable Smart Contracts with USDC
Let’s learn how to use the power of composable smart contracts with USDC. We’ll talk about what composability means, why it’s so powerful, and we’ll write our own smart contract that interacts with Circle’s USDC contract. By the end, you’ll understand just how important composability is, and you’ll have working code to take forward into your own apps.
What Is Composability in Smart Contracts?
Composability in smart contracts means the ability to combine elements of different smart contracts to create a new system. With composability, you combine the functionality of existing smart contracts with your own code to create a new application.
Often composability is compared to Lego building blocks — where several Legos can be combined into a new, more complex shape. Likewise, you can combine several smart contracts into a new, more complex application. Composability works well in Web3 because smart contracts on most blockchains are public so anyone can interact with them.
This concept of composability is extremely powerful, and it’s what makes Web3 magic possible. Composability has enabled much of the innovation we’ve seen in Web3, including decentralized games, NFTs, and DeFi.
Companies have utilized existing assets (for example, Bored Ape Yacht Club NFTs) to create metaverses such as OTHERWORLD. Decentralized exchanges and loan protocols such as Compound and AAVE are possible because of the thousands of ERC-20 contracts that exist on Ethereum and other blockchains. The same applies to NFT marketplaces which interact with ERC-721 and ERC-1155 contracts, powering non-fungible and semi-fungible tokens.
For more information on composability, check out this guide from the Ethereum foundation.
Tutorial: Interacting with the USDC Smart Contract
Let’s see exactly how composability works in practice, and how powerful it can be by interacting with the USDC smart contract on Ethereum.
Understanding USDC and the ERC-20 Standard
USDC is a digital dollar, also known as a stablecoin from Circle that is backed 1:1 by the US dollar. Its trust and transparency have made it the lifeblood of decentralized finance on many of the most popular blockchains, including Ethereum, Solana, Polygon, Avalanche, and Stellar.
Like most tokens, USDC is powered by smart contracts. These contracts implement a critical standard: ERC-20. This standard is responsible for the interoperability of various fungible tokens on Ethereum and other EVM-based blockchains.
The ERC-20 standard defines that a token (such as USDC) must have certain values defined such as name, ticker, and number of decimals, and must implement certain functions such as balanceOf (which checks the balance of the token in a particular address) and transfer (which allows the owner of a token to transfer it to another address).
Functions that every contract that implements ERC-20 must contain
To learn more about the ERC-20 standard and the purpose of the various functions, check out some helpful documentation available on OpenZeppelin.
Because Circle’s USDC contracts implement the ERC-20 standard, we know that it has to have these functions publicly available. That means we can use these functions from our own smart contract — which is exactly what we’re going to do to learn about composable contracts and how to work with USDC smart contracts at scale.
Creating Composable Contracts
Let’s implement something simple but extremely powerful: a smart contract that interacts with the live USDC contract and inquires about the USDC balance of a wallet.
Once you complete this tutorial, you’ll know how to create Hardhat projects, write composable contracts that implement Solidity interfaces, and have all the tools required to work with USDC contracts and implement complex applications.
Step 1: Install npm and Node
We will be using Hardhat to develop, test, and deploy our smart contracts. In order to run Hardhat, we need Node and npm installed on our local machines. If you don’t have them, you can download them here.
Confirm they are installed by running the following commands:
$ node -v
$ npm -v
Step 2: Create a Hardhat Project
Now let’s create a Hardhat project. Hardhat is an industry-standard IDE for developing, testing, and deploying contracts on Ethereum and EVM-based blockchains. Hardhat comes integrated with the tools and libraries you need to create smart contracts.
Open your terminal once again and run the following commands:
$ mkdir usdc-balance && cd usdc-balance
$ npm init -y
$ npm install -–save-dev hardhat
$ npx hardhat
Running the last command will prompt Hardhat to ask you a series of questions. Simply choose the default option in all cases including Create a Javascript Project.
Once the Hardhat project is created, make sure everything is running:
$ npx hardhat test
We’re done with the setup! Let’s move on to configure a few non-Hardhat components.
Step 3: Install MetaMask and Acquire Goerli ETH
In order to deploy a contract to a public blockchain like Ethereum, we need a wallet to authorize and sign off the transaction, and some ETH to pay for gas.
Let’s first create a wallet. There are plenty of free, self-custodial wallet solutions available in the market today. We’ll use MetaMask. If you don’t have it, you can download it as a browser extension.
If this is your first time using MetaMask, you’ll need to perform a series of steps that ends with the generation of a wallet recovery phrase. IMPORTANT: Store this safely and never share it with anyone.
Once your wallet is set up, it should look something like this:
From the top-left menu, toggle to Show test networks and switch the network to Goerli.
Goerli is an Ethereum testnet that behaves just like its parent production mainnet but uses test money, Goerli ETH, to power its transactions. You can obtain Goerli ETH from a faucet.
While Goerli is our choice here, you can follow this tutorial using any testnet where an official USDC contract is deployed. Here is a list of supported networks.
Once you have the Goerli ETH, you should see something like this:
Step 4: Write the USDC Balance Contract
Let’s return to our Hardhart project and open it in a code editor or IDE such as Visual Studio Code or Sublime Text.
In Hardhat projects, the contracts written in Solidity reside in the contracts folder (which in turn, resides in the root folder of our project). We also have a scripts folder where we write simulation and deployment code in JavaScript. In this step, we will only be dealing with the former.
In the contracts folder, create a new file called UsdcBalance.sol and add the following code:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "hardhat/console.sol";
interface MainUsdcContractInterface {
// Define signature of balanceOf
function balanceOf(address owner) external view returns (uint);
}
// Contract that interacts with main USDC contract to get balance of a wallet
contract UsdcBalance {
// Address of USDC contract
address public usdcContractAddress;
MainUsdcContractInterface usdcContract;
// Create a pointer to the USDC contract
constructor(address _usdcContractAddress) {
usdcContractAddress = _usdcContractAddress;
usdcContract = MainUsdcContractInterface(usdcContract);
}
// Function to get USDC balance
function getUdscBalance() public view returns (uint) {
uint balance = usdcContract.balanceOf(msg.sender);
return balance;
}
}
Let’s go through this contract step by step.
The first thing we do is create a Solidity interface that defines the signature of the function we intend to call from the main USDC contract. Since USDC uses the ERC-20 standard, we are guaranteed that it implements the balanceOf function.
We copy the signature of this function and add it to our interface.
In our main USDC balance contract, we create a pointer to the main USDC contract. We do so by instantiating our interface with the deployed address of the USDC contract.
The rest of the code is simple enough to piece together. We invoke the main USDC contract through our pointer and ask it to call the balanceOf function. The return value of this function, by definition, will be the USDC balance of our wallet.
Compile the contracts using:
$ npx hardhat compile
Step 5: Configure Hardhat Project
Let’s now configure our Hardhat project to make sure it can use our MetaMask wallet to sign and conduct transactions.
In the root folder of your project, you will find a file called hardhat.config.js. Replace its contents with the following:
require("@nomicfoundation/hardhat-toolbox");
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: "0.8.4",
networks: {
goerli: {
url: "< GOERLI RPC URL >",
accounts: ["< WALLET PRIVATE KEY >"],
}
},
};
You will need an RPC endpoint to send requests to Goerli. You can create this for free using a service like Infura or Alchemy.
You can get the private key for your wallet address from MetaMask. Click on your Account Settings and export the private key. Like with the wallet recovery phrase, take care to never make this information public as it will lead to the permanent loss of all funds.
Step 6: Deploy and Test Contract
Now let’s write a script that deploys our contract and interacts with the official USDC contract on Goerli (or the test network of your choice).
In the scripts folder, create a new file called run.js and add the following code:
const hre = require("hardhat");
async function main() {
// Get wallet address
const [owner] = await hre.ethers.getSigners();
// Define the official USDC contract address
usdcContractAddress = "0x07865c6E87B9F70255377e024ace6630C1Eaa37F"
const contract = await hre.ethers.deployContract("UsdcBalance", [usdcContractAddress]);
await contract.waitForDeployment();
console.log("USDC Balance contract deployed to: ", contract.target, "\n");
// Get wallet's USDC balance
let usdcBalance = await contract.connect(owner).getUsdcBalance();
console.log(`USDC Balance of ${owner.address}: `, usdcBalance);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.log(error);
process.exit(1);
});
Run the code using:
$ npx hardhat run scripts/run.js -–network goerli
If all goes well, you should see output that looks like this:
USDC Balance contract deployed to: 0x2B05Cb8fDaEDd01e777828fF6ac916A3E657828A
USDC Balance of 0x426b156dD2932A17629AA24A751C1A8C3fcdaC19: 51468456888
Remember to add a decimal before 6 digits. The balance of this wallet, therefore, is approximately $51.46k.
You’ve now used composability! You created your own contract that used the functionality already available in the USDC contract. While we just covered getting the USDC balance here, you can take this forward using the same paradigm to perform more complex operations such as approvals and transfers — and to build your own systems!
As just one example, Credix is using USDC and composable smart contracts to enable cross-border institutional lending and borrowing. Their app allows institutional investors to provide capital to (and access returns from) global emerging markets. And it does all this without the overhead of banks by using USDC to stay on-chain and decentralized.
Conclusion
Composability is one of the greatest powers of Web3. It makes possible a huge array of products that perform complex operations without the need of a centralized third party. By using it with Circle’s USDC smart contract, you can build your own systems that integrate with and are powered by USDC.
Programmable Wallets and Smart Contract Platform application programming interfaces (“API”) are offered by Circle Technology Services, LLC (“CTS”). CTS is not a regulated financial services company and the APIs do not include financial, investment, tax, legal, regulatory, accounting, business, or other advice. For additional details, please click here to see the Circle Developer terms of service.