Circle’s Smart Contract Platform allows you to seamlessly swap ETH to USDC via a smart contract. Learn how.
Circle's Smart Contract Platform simplifies the process of programmatically deploying smart contracts and calling their functions via an SDK that includes wallet and gas abstraction infrastructure. Here’s what we’ll accomplish:
Before we begin, ensure you have the following:
We will use a pre-written smart contract that interacts with Uniswap to perform token swaps. This guide will show you how to swap ETH for USDC. When ETH is deposited in the contract for swapping, it is converted to Wrapped ETH (WETH), which is an ERC20 token that can be swapped for USDC using Uniswap's protocol. Below is the contract code:
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.20;
pragma abicoder v2;
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
contract SwapExamples {
ISwapRouter public immutable swapRouter;
// Token contract addresses used for the WETH to USDC swap
address public constant WETH9 = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619;
address public constant USDC = 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359;
uint24 public constant poolFee = 3000;
constructor(ISwapRouter _swapRouter) {
swapRouter = _swapRouter;
}
// @notice swapExactInputSingle swaps a fixed amount of WETH9 for a maximum possible amount of USDC
// using the WETH9/USDC 0.3% pool by calling `exactInputSingle` in the swap router.
// @dev The calling address must approve this contract to spend at least `amountIn` worth of its WETH9 for this function to succeed.
// @param amountIn The exact amount of WETH9 that will be swapped for USDC.
// @return amountOut The amount of USDC received.
function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
// msg.sender must approve this contract
// Transfer the specified amount of WETH9 to this contract.
TransferHelper.safeTransferFrom(WETH9, msg.sender, address(this), amountIn);
// Approve the router to spend WETH9.
TransferHelper.safeApprove(WETH9, address(swapRouter), amountIn);
// Naively set amountOutMinimum to 0. In production, use an oracle or other data source to choose a safer value for amountOutMinimum.
// We also set the sqrtPriceLimitx96 to be 0 to ensure we swap our exact input amount.
ISwapRouter.ExactInputSingleParams memory params =
ISwapRouter.ExactInputSingleParams({
tokenIn: WETH9,
tokenOut: USDC,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountIn: amountIn,
amountOutMinimum: 0,
sqrtPriceLimitX96: 0
});
// The call to `exactInputSingle` executes the swap.
amountOut = swapRouter.exactInputSingle(params);
}
// @notice swapExactOutputSingle swaps a minimum possible amount of WETH9 for a fixed amount of USDC.
// @dev The calling address must approve this contract to spend its WETH9 for this function to succeed. As the amount of input WETH9 is variable,
// the calling address will need to approve for a slightly higher amount, anticipating some variance.
// @param amountOut The exact amount of USDC to receive from the swap.
// @param amountInMaximum The amount of WETH9 we are willing to spend to receive the specified amount of USDC.
// @return amountIn The amount of WETH9 actually spent in the swap.
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transfer the specified amount of WETH9 to this contract.
TransferHelper.safeTransferFrom(WETH9, msg.sender, address(this), amountInMaximum);
// Approve the router to spend the specified `amountInMaximum` of WETH9.
// In production, you should choose the maximum amount to spend based on oracles or other data sources to achieve a better swap.
TransferHelper.safeApprove(WETH9, address(swapRouter), amountInMaximum);
ISwapRouter.ExactOutputSingleParams memory params =
ISwapRouter.ExactOutputSingleParams({
tokenIn: WETH9,
tokenOut: USDC,
fee: poolFee,
recipient: msg.sender,
deadline: block.timestamp,
amountOut: amountOut,
amountInMaximum: amountInMaximum,
sqrtPriceLimitX96: 0
});
// Executes the swap returning the amountIn needed to spend to receive the desired amountOut.
amountIn = swapRouter.exactOutputSingle(params);
// For exact output swaps, the amountInMaximum may not have all been spent.
// If the actual amount spent (amountIn) is less than the specified maximum amount, we must refund the msg.sender and approve the swapRouter to spend 0.
if (amountIn < amountInMaximum) {
TransferHelper.safeApprove(WETH9, address(swapRouter), 0);
TransferHelper.safeTransfer(WETH9, msg.sender, amountInMaximum - amountIn);
}
}
}
Before deploying the smart contract, we need to compile it to obtain the ABI (Application Binary Interface) and bytecode. We will use Remix IDE for this purpose.
Open Remix IDE:Compiling the smart contract generates the ABI and bytecode, which are essential for deploying and interacting with the contract on the blockchain. The ABI defines the contract's interface, allowing us to call its functions, while the bytecode is the compiled version of the contract that gets deployed to the blockchain.
To deploy the compiled contract using Circle's SDK, follow these steps:
Install and Import Required Packages
First, ensure you have the necessary packages installed:
npm install @circle-fin/smart-contract-platform @circle-fin/developer-controlled-wallets --save
These packages provide the necessary tools to interact with Circle's Smart Contract Platform and handle developer-controlled wallets.
Then, import the required packages in your project:
const circleContractSdk = require('@circle-fin/smart-contract-platform');
const developerControlledWallets = require('@circle-fin/developer-controlled-wallets');
Prepare the deployment parameters:
Deploy the contract:
const response = await circleContractSdk.deployContract({
name: 'Swap Contract',
description: 'Contract for swapping WETH9 to USDC using Uniswap',
walletId: '046b6c7f-0b8a-43b9-b35d-6489e6daee91',// Replace with your walletId
blockchain: 'MATIC-AMOY',
fee: {
type: 'level',
config: {
feeLevel: 'MEDIUM'
}
},
constructorParameters: ['0xYourWalletAddress'], // Replace with your wallet address
entitySecretCiphertext: '0NtD3d3+nmgb4GqYQXzAjKF8h5Zq6sHM2k/...', // Replace with your entitySecretCipher text
abiJSON: '[...]', // Replace with actual stringified ABI JSON
bytecode: '0x...' // Replace with actual bytecode prefixed with 0x
});
Upon successfully deploying a smart contract, you will receive a response that includes a contractId and transactionId.
{
"data": {
"contractId": "0189db84-72b7-7fcc-832b-5bf886b9a0ef",
"transactionId": "7b989c65-9678-56d8-a998-d295b8b04535"
}
}
Check the deployment status:
const response = await circleContractSdk.getContract({
id: '0189db84-72b7-7fcc-832b-5bf886b9a0ef'
});
{
"data": {
"contract": {
"id": "0189db84-72b7-7fcc-832b-5bf886b9a0ef",
"deploymentTransactionId": "7b989c65-9678-56d8-a998-d295b8b04535",
"name": "Swap Contract",
"description": "Contract for swapping WETH9 to USDC using Uniswap",
"contractInputType": "BYTECODE",
"createDate": "2023-08-09T18:17:17Z",
"updateDate": "2023-08-09T18:17:17Z",
"archived": false,
"contractAddress": "0x1e124d7384cd34448ea5907bd0052a79355ab5eb",
"blockchain": "MATIC-AMOY",
"status": "COMPLETE",
"deployerAddress": "0x1bf9ad0cc2ad298c69a2995aa806ee832788218c",
"txHash": "0x241c4df6f08f9ed2b569c9f9b1cc48fb6074ffffaeee7552e716ce059161a743",
"abiJSON": "[\n\t{\n\t\t\"inputs\": [],\n\t\t\"stateMutability\": \"nonpayable\",\n\t\t\"type\": \"constructor\"\n\t},\n\t{\n\t\t\"anonymous\": false,",
"functions": [
{
"name": "swapExactInputSingle",
"type": "function",
"inputs": [
{
"name": "amountIn",
"type": "uint256"
}
],
"stateMutability": "nonpayable"
}
],
"verificationStatus": "UNVERIFIED"
}
}
}
Once the contract is deployed, you can interact with it to perform token swaps. Here’s how to call the swapExactInputSingle function:
Prepare the interaction parameters:
Call the function:
const response = await circleDeveloperSdk.createContractExecutionTransaction({
walletId: 'ce714f5b-0d8e-4062-9454-61aa1154869b', // Replace with your wallet ID
contractAddress: '0x2f3A40A3db8a7e3D09B0adfEfbCe4f6F81927557', // Replace with your contract address
abiFunctionSignature: 'swapExactInputSingle(uint256)',
abiParameters: [1000], // Example amountIn value
fee: {
type: 'level',
config: {
feeLevel: 'MEDIUM'
}
}
});
We demonstrated how to deploy a smart contract using Circle's Smart Contract Platform and interact with it to perform a token swap from ETH to USDC using Uniswap. By leveraging Circle's SDK, you can easily manage your smart contracts and execute transactions on the blockchain.
*Circle Technology Services, LLC (“CTS”) is a software provider and does not provide regulated financial or advisory services. You are solely responsible for services you provide to users, including obtaining any necessary licenses or approvals and otherwise complying with applicable laws. For additional details, please click here to see the Circle Developer terms of service.