Circle Internet Financial
Circle Internet Financial Logo

Oct 01, 2024

August 15, 2024

How to Swap ETH for USDC with Circle's Smart Contract Platform

what you’ll learn

Circle’s Smart Contract Platform allows you to seamlessly swap ETH to USDC via a smart contract. Learn how.

How to Swap ETH for USDC with Circle's Smart Contract Platform
  • Write a smart contract that executes a swap from ETH to USDC on Uniswap V3.
  • Compile smart contract and extract ABI JSON and bytecode using Remix IDE.
  • Deploy and call smart contract function via SDK to perform token swap.

Prerequisites

Before we begin, ensure you have the following:

  1. Remix IDE: For writing and compiling the smart contract.
  2. Circle Web3 Services Account: For managing the smart contract interaction to perform swaps.
  3. Created a Developer Controlled Wallet: For deploying smart contracts and executing contract functions.

Writing the Smart Contract

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);
        }
    }
}

Compiling the Smart Contract

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:

Create a New File:

  • In the Remix IDE, create a new file and paste the smart contract code provided above.

Compile the Contract:

  • Select the appropriate Solidity compiler version (0.8.26) from the compiler tab.
  • Click on the "Compile" button to compile the contract.

Copy the ABI and Bytecode:

  • Once the contract is compiled, navigate to the "Compilation Details" section.
  • Copy the ABI JSON and bytecode. These will be needed for deploying the contract using Circle's SDK.

Why is this step necessary?

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.

Deploying the Smart Contract

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:

  • name: The name of the contract.
  • description: A brief description of the contract.
  • walletId: Your Circle wallet ID.
  • Blockchain: The blockchain network (e.g., MATIC-AMOY)
  • fee: The fee configuration.
  • constructorParameters: The address to set as the initial owner of the contract. For this guide, it should be the address of your wallet, but it can be any address.
  • entitySecretCiphertext: Your encrypted entity secret.
  • abiJSON: The ABI of the contract in JSON format you copied from Remix IDE in the step above. Note that the ABI JSON must be stringified (i.e., quote escaped). To stringify the JSON, you can use the JsonFormatter's Stringify tool.
  • bytecode: The compiled bytecode of the contract you copied from Remix IDE in the step above. The bytecode must be prefixed with 0x.

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"
    }
  }
}

Interacting with the Deployed Contract

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:

  • walletId: Your Circle wallet ID.
  • contractAddress: The address of the deployed contract.
  • abiFunctionSignature: The function signature to call.
  • abiParameters: The parameters for the function.
  • fee: The fee configuration.

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'
       }
     }
   });

Conclusion

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

Related posts

Now Available: Native USDC on Sui

Now Available: Native USDC on Sui

October 8, 2024
Migration Guide: Bridged to Native USDC on Sui

Migration Guide: Bridged to Native USDC on Sui

October 8, 2024
Web3 Services: September 2024 Updates

Web3 Services: September 2024 Updates

October 1, 2024