Agent skill

Usdc Integration

by ccashwell in ccashwell/evm-cortex

A skill your agent uses when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances.

MITAuto-check passedBackend & APIs

Install Usdc Integration

skills CLI
$ npx skills add ccashwell/evm-cortex --skill usdc-integration -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install ccashwell/evm-cortex usdc-integration --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/ccashwell/evm-cortex.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/usdc-integration .claude/skills/usdc-integration && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
usdc-integration
GitHub stars
131
Token cost
~6.2k tokens
SKILL.md length
1,050 words
Files
1
Skills in repo
89
Repo updated
First seen
Licence
MIT

At a glance

A skill your agent uses when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances.

  • Works in 3 steps: Blocklist: Any address can be… → Pause: All USDC transfers can be halted… → Upgrade: The implementation can change.…
  • Integrating USDC into smart contracts
  • SKILL.md covers The 6-Decimal Rule, Production Contract Addresses, Native USDC vs Bridged Variants and FiatTokenV2_2 Architecture, plus 5 more sections
  • Instructions only: no scripts, shell commands, URLs or credentials in SKILL.md

What it does

Usdc Integration is an agent skill from ccashwell/evm-cortex. Use when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances. Covers the 6-decimal rule, production contract addresses on all major chains, native vs bridged USDC variants, FiatTokenV22 proxy architecture, permit (EIP-2612), blocklist behavior, and safe integration patterns.

Its SKILL.md is about 6.2k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Backend & APIs, covering Smart contracts, Third-party API integration and Crypto and DeFi analysis. It works with Circle USDC and Arbitrum. The repository describes itself as: Ethereum protocol engineering squad for AI coding assistants. The licence is MIT.

When your agent uses it

  • Integrating USDC into smart contracts
  • Handling stablecoin transfers
  • Checking balances

Example prompts

  • “/usdc-integration”

Workflow steps

3 steps, taken from the first numbered list in SKILL.md.

  1. Blocklist: Any address can be blocklisted at any time. If a blocklisted address holds a position in your protocol, transfer and…
  2. Pause: All USDC transfers can be halted globally. Liquidation mechanisms that depend on USDC transfers will fail during a pause.
  3. Upgrade: The implementation can change. Interface compatibility is maintained, but new behaviors (additional checks, storage changes) can…

What it can do on your machine

Read from SKILL.md and the folder at commit f8f3301. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    No scripts in the folder and no shell commands in SKILL.md (its code samples are solidity and bash).

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • faucet.circle.com
    • developers.circle.com
    • eips.ethereum.org
    • github.com
    • docs.openzeppelin.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Usdc Integration loads about 6.2k tokens when it runs. Until then it costs about 86 tokens; SKILL.md has 1,050 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~86
When it runs · the whole SKILL.md, loaded when a task matches
~6.2k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from ccashwell/evm-cortex at commit f8f3301, republished under its MIT licence (© ccashwell). 1,050 words, ~6,220 tokens.

Download SKILL.mdSave it as .claude/skills/usdc-integration/SKILL.md (or your agent's skills folder).
name
usdc-integration
description
Use when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances. Covers the 6-decimal rule, production contract addresses on all major chains, native vs bridged USDC variants, FiatTokenV2_2 proxy architecture, permit (EIP-2612), blocklist behavior, and safe integration patterns.

USDC Integration for Smart Contracts

The 6-Decimal Rule

USDC uses 6 decimals on every chain. Not 18. This is the single most common integration bug.

solidity
// 1 USDC = 1_000_000 (1e6)
uint256 constant USDC_UNIT = 1e6;

// $100 USDC
uint256 amount = 100 * 1e6; // 100_000_000

// $0.01 USDC (one cent)
uint256 oneCent = 1e4; // 10_000

// WRONG — this is 1 trillion USDC ($1,000,000,000,000)
uint256 catastrophic = 1e18;
Decimal Conversion

When protocols mix USDC (6 decimals) with 18-decimal tokens (WETH, DAI, most ERC-20s), explicit scaling is required:

solidity
uint256 constant SCALE_FACTOR = 1e12; // 18 - 6 = 12

// Scale 6 → 18 (lossless)
uint256 wad = usdcAmount * SCALE_FACTOR;

// Scale 18 → 6 (LOSES up to 1e12 - 1 wei of precision)
uint256 usdc = wadAmount / SCALE_FACTOR;

Never scale in a single arithmetic expression without isolating the conversion. Multiply before dividing to preserve precision:

solidity
// BAD — precision loss compounds
uint256 result = (usdcAmount * price) / 1e18;

// BETTER — scale USDC to 18 decimals first, then divide
uint256 result = (usdcAmount * SCALE_FACTOR * price) / 1e18;

// BEST — use a helper that makes intent explicit
uint256 result = _toWad(usdcAmount) * price / 1e18;

Production Contract Addresses

Mainnet — Native (Circle-issued) USDC

These are the canonical addresses issued directly by Circle via CCTP. All are FiatTokenV2_2 proxies.

ChainAddressChain ID
Ethereum0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB481
Base0x833589fCD6eDb6E08f4c7C32D4f71b54bdA029138453
Arbitrum One0xaf88d065e77c8cC2239327C5EDb3A432268e583142161
Optimism0x0b2C639c533813f4Aa9D7837CAf62653d097Ff8510
Polygon PoS0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359137
Avalanche C-Chain0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E43114
SolanaEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v—
Testnet Addresses
ChainAddress
Ethereum Sepolia0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
Base Sepolia0x036CbD53842c5426634e7929541eC2318f3dCF7e
Arbitrum Sepolia0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d
Optimism Sepolia0x5fd84259d66Cd46123540766Be93DFE6D43130D7
Polygon Amoy0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582
Avalanche Fuji0x5425890298aed601595a70AB815c96711a31Bc65

Testnet faucet: https://faucet.circle.com

Verifying Addresses Onchain

Never hardcode addresses from documentation alone. Verify with cast:

bash
# Confirm USDC contract exists and is a proxy
cast code 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --rpc-url mainnet | head -c 40

# Check decimals
cast call 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "decimals()(uint8)" --rpc-url mainnet
# → 6

# Check symbol
cast call 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 "symbol()(string)" --rpc-url mainnet
# → "USDC"

Native USDC vs Bridged Variants

Always use native Circle-issued USDC. Bridged variants are deprecated and lack features like permit, blocklist enforcement, and CCTP support.

Deprecated Bridged Tokens — DO NOT USE
TokenChainAddressStatus
USDbCBase0xd9aAEc86B65D86f6A7B5B1b0c42FFA531710b6CaDeprecated
USDC.eArbitrum0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8Deprecated
USDC.eAvalanche0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664Deprecated
USDC.ePolygon0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174Deprecated
USDC.eOptimism0x7F5c764cBc14f9669B88837ca1490cCa17c31607Deprecated
Detecting Native vs Bridged at Runtime
solidity
/// @notice Validates that a USDC address is the native Circle-issued token
/// @dev Checks for EIP-2612 permit support as a heuristic — bridged variants lack it
function _validateNativeUSDC(address token) internal view {
    // Native USDC supports EIP-2612 permit via DOMAIN_SEPARATOR
    (bool success,) = token.staticcall(abi.encodeWithSignature("DOMAIN_SEPARATOR()"));
    if (!success) revert NotNativeUSDC(token);
}

For deploy-time validation, pass the expected chain-specific address as a constructor argument and verify decimals() == 6 plus symbol matches.

FiatTokenV2_2 Architecture

USDC is not a simple ERC-20. It is a proxied, upgradeable, regulated stablecoin.

Proxy Structure
FiatTokenProxy (storage + delegatecall)
  └── FiatTokenV2_2 (implementation logic)
        ├── ERC-20 (transfer, approve, transferFrom, balanceOf, allowance)
        ├── EIP-2612 (permit — gasless approvals via signature)
        ├── EIP-3009 (transferWithAuthorization, receiveWithAuthorization)
        ├── Blocklist (Circle can block specific addresses)
        ├── Pause (Circle can halt all transfers globally)
        └── Upgrade (Circle can swap the implementation)
Key Administrative Roles
RoleCapability
AdminUpgrade implementation, change admin
Master MinterConfigure minters, set minting allowances
BlocklisterAdd/remove addresses from blocklist
PauserPause and unpause all transfers
RescuerRecover tokens accidentally sent to the USDC contract
Implications for Protocol Design
  1. Blocklist: Any address can be blocklisted at any time. If a blocklisted address holds a position in your protocol, transfer and transferFrom to/from that address will revert.
  2. Pause: All USDC transfers can be halted globally. Liquidation mechanisms that depend on USDC transfers will fail during a pause.
  3. Upgrade: The implementation can change. Interface compatibility is maintained, but new behaviors (additional checks, storage changes) can be introduced.

Safe Integration Patterns

Basic Deposit/Withdraw
solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

/// @title USDCVault
/// @notice Minimal vault demonstrating safe USDC integration
contract USDCVault is ReentrancyGuard {
    using SafeERC20 for IERC20;

    IERC20 public immutable USDC;

    mapping(address user => uint256 balance) public balances;

    error ZeroAmount();
    error InsufficientBalance(uint256 available, uint256 requested);

    event Deposited(address indexed user, uint256 amount);
    event Withdrawn(address indexed user, address indexed recipient, uint256 amount);

    constructor(address usdc_) {
        USDC = IERC20(usdc_);
    }

    /// @notice Deposit USDC into the vault
    /// @param amount Amount of USDC in 6-decimal units
    function deposit(uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();

        balances[msg.sender] += amount;
        emit Deposited(msg.sender, amount);

        USDC.safeTransferFrom(msg.sender, address(this), amount);
    }

    /// @notice Withdraw USDC to a specified recipient
    /// @dev Allows withdrawal to a different address in case msg.sender is blocklisted
    /// @param recipient Address to receive USDC
    /// @param amount Amount of USDC in 6-decimal units
    function withdraw(address recipient, uint256 amount) external nonReentrant {
        if (amount == 0) revert ZeroAmount();
        uint256 bal = balances[msg.sender];
        if (bal < amount) revert InsufficientBalance(bal, amount);

        balances[msg.sender] = bal - amount;
        emit Withdrawn(msg.sender, recipient, amount);

        USDC.safeTransfer(recipient, amount);
    }
}
Permit Integration (Gasless Approvals)

USDC natively supports EIP-2612 permit, allowing users to approve and deposit in a single transaction without a prior approve call.

solidity
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";

/// @notice Deposit with a signed permit, enabling single-tx approve + deposit
/// @param amount USDC amount (6 decimals)
/// @param deadline Timestamp after which the permit signature expires
/// @param v Recovery byte of the permit signature
/// @param r First 32 bytes of the permit signature
/// @param s Second 32 bytes of the permit signature
function depositWithPermit(
    uint256 amount,
    uint256 deadline,
    uint8 v,
    bytes32 r,
    bytes32 s
) external nonReentrant {
    if (amount == 0) revert ZeroAmount();

    // permit may revert if signature is invalid or already used
    try IERC20Permit(address(USDC)).permit(
        msg.sender, address(this), amount, deadline, v, r, s
    ) {} catch {
        // Permit may fail if:
        // 1. Approval already exists (front-run or user pre-approved)
        // 2. Signature was already used
        // Proceed with transferFrom — it will fail if allowance is insufficient
    }

    balances[msg.sender] += amount;
    emit Deposited(msg.sender, amount);

    USDC.safeTransferFrom(msg.sender, address(this), amount);
}

The try/catch around permit is intentional. If a permit signature is front-run (someone else submits it first), the approval still exists and transferFrom succeeds. Reverting on a failed permit would brick the transaction unnecessarily.

EIP-3009: transferWithAuthorization

USDC also supports EIP-3009 for authorized transfers. Unlike permit + transferFrom, this combines authorization and transfer atomically:

solidity
interface IFiatTokenV2 {
    function transferWithAuthorization(
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    function receiveWithAuthorization(
        address from,
        address to,
        uint256 value,
        uint256 validAfter,
        uint256 validBefore,
        bytes32 nonce,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;
}

receiveWithAuthorization enforces msg.sender == to, preventing front-running of the authorization. Prefer it over transferWithAuthorization when the recipient is your contract.

Handling Blocklist Scenarios

The Problem

A user deposits USDC into your protocol. Later, Circle blocklists their address. Now:

  • USDC.transfer(blockedUser, amount) reverts
  • The user cannot withdraw
  • If the user has a liquidatable position, liquidation may also revert
Defense Patterns
solidity
/// @notice Withdraw to an alternative recipient if the depositor is blocklisted
/// @dev Access-controlled so only the original depositor can redirect
function withdraw(address recipient, uint256 amount) external nonReentrant {
    if (amount == 0) revert ZeroAmount();
    uint256 bal = balances[msg.sender];
    if (bal < amount) revert InsufficientBalance(bal, amount);

    balances[msg.sender] = bal - amount;
    emit Withdrawn(msg.sender, recipient, amount);

    USDC.safeTransfer(recipient, amount);
}

For lending protocols where liquidation is critical:

solidity
/// @notice Liquidate a position, sending seized USDC to the liquidator
/// @dev If the direct transfer fails (blocklist), escrow the funds
function liquidate(address borrower, uint256 repayAmount) external nonReentrant {
    // ... checks and effects ...

    uint256 seizedCollateral = _calculateSeizedCollateral(repayAmount);
    balances[borrower] -= seizedCollateral;

    // Attempt direct transfer; fall back to escrow on failure
    try IERC20(address(USDC)).transfer(msg.sender, seizedCollateral) {
        emit Liquidated(borrower, msg.sender, seizedCollateral);
    } catch {
        pendingWithdrawals[msg.sender] += seizedCollateral;
        emit LiquidatedToEscrow(borrower, msg.sender, seizedCollateral);
    }
}

/// @notice Claim escrowed funds from a failed liquidation transfer
function claimEscrow(address recipient) external nonReentrant {
    uint256 amount = pendingWithdrawals[msg.sender];
    if (amount == 0) revert NothingToClaim();
    pendingWithdrawals[msg.sender] = 0;
    USDC.safeTransfer(recipient, amount);
}
Handling Global Pause
solidity
/// @notice Check if USDC is currently paused
/// @dev Useful for UIs or circuits that need to know transfer availability
function isUSDCPaused() public view returns (bool) {
    (bool success, bytes memory data) = address(USDC).staticcall(
        abi.encodeWithSignature("paused()")
    );
    return success && abi.decode(data, (bool));
}

Decimal Conversion Library

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

/// @title USDCLib
/// @notice Helpers for USDC decimal conversions
library USDCLib {
    uint256 internal constant USDC_DECIMALS = 6;
    uint256 internal constant USDC_UNIT = 1e6;
    uint256 internal constant WAD = 1e18;
    uint256 internal constant SCALE_FACTOR = 1e12;

    /// @notice Convert USDC amount (6 decimals) to WAD (18 decimals)
    /// @dev Lossless — always safe
    function toWad(uint256 usdcAmount) internal pure returns (uint256) {
        return usdcAmount * SCALE_FACTOR;
    }

    /// @notice Convert WAD (18 decimals) to USDC amount (6 decimals)
    /// @dev Truncates — loses up to 999_999_999_999 wei (< $0.000001)
    function fromWad(uint256 wadAmount) internal pure returns (uint256) {
        return wadAmount / SCALE_FACTOR;
    }

    /// @notice Convert WAD to USDC, rounding up
    /// @dev Use when the protocol should not lose value (e.g., debt calculations)
    function fromWadRoundUp(uint256 wadAmount) internal pure returns (uint256) {
        return (wadAmount + SCALE_FACTOR - 1) / SCALE_FACTOR;
    }

    /// @notice Construct a USDC amount from whole dollars
    function dollars(uint256 amount) internal pure returns (uint256) {
        return amount * USDC_UNIT;
    }

    /// @notice Construct a USDC amount from dollars and cents
    function dollarsAndCents(uint256 wholeDollars, uint256 cents) internal pure returns (uint256) {
        return wholeDollars * USDC_UNIT + cents * 1e4;
    }
}

Testing with USDC on Forks

Fork Mainnet Setup
solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Test} from "forge-std/Test.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract USDCForkTest is Test {
    IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48);

    address alice = makeAddr("alice");
    address bob = makeAddr("bob");

    function setUp() public {
        vm.createSelectFork("mainnet");

        // deal() works for setting USDC balances on forks
        deal(address(USDC), alice, 1_000_000 * 1e6); // $1M
        deal(address(USDC), bob, 500_000 * 1e6);      // $500K
    }

    function test_usdcDecimals() public view {
        assertEq(USDC.decimals(), 6);
    }

    function test_transfer() public {
        vm.prank(alice);
        USDC.transfer(bob, 100 * 1e6); // $100

        assertEq(USDC.balanceOf(bob), 600_000 * 1e6);
    }
}
Testing Blocklist Behavior
solidity
function test_blocklistedAddressCannotReceive() public {
    address blocklister = 0x5dB0115f3B72d19cEa34dD697cf412Ff86dc7E1b;
    address victim = makeAddr("victim");

    deal(address(USDC), alice, 100 * 1e6);

    // Blocklist the victim address
    vm.prank(blocklister);
    (bool success,) = address(USDC).call(
        abi.encodeWithSignature("blacklist(address)", victim)
    );
    assertTrue(success);

    // Transfer to blocklisted address reverts
    vm.prank(alice);
    vm.expectRevert();
    USDC.transfer(victim, 50 * 1e6);
}
Testing Permit Signatures
solidity
function test_permitAndDeposit() public {
    uint256 alicePk = 0xA11CE;
    address aliceAddr = vm.addr(alicePk);
    deal(address(USDC), aliceAddr, 1000 * 1e6);

    uint256 amount = 500 * 1e6;
    uint256 deadline = block.timestamp + 1 hours;

    // Build permit digest
    bytes32 domainSeparator = IERC20Permit(address(USDC)).DOMAIN_SEPARATOR();
    bytes32 structHash = keccak256(abi.encode(
        keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
        aliceAddr,
        address(vault),
        amount,
        IERC20Permit(address(USDC)).nonces(aliceAddr),
        deadline
    ));
    bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));

    (uint8 v, bytes32 r, bytes32 s) = vm.sign(alicePk, digest);

    vm.prank(aliceAddr);
    vault.depositWithPermit(amount, deadline, v, r, s);

    assertEq(vault.balances(aliceAddr), amount);
}
Multichain Fork Testing
solidity
function test_usdcOnMultipleChains() public {
    // Ethereum
    uint256 ethFork = vm.createFork("mainnet");
    vm.selectFork(ethFork);
    assertEq(
        IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).decimals(),
        6
    );

    // Base
    uint256 baseFork = vm.createFork("base");
    vm.selectFork(baseFork);
    assertEq(
        IERC20(0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913).decimals(),
        6
    );

    // Arbitrum
    uint256 arbFork = vm.createFork("arbitrum");
    vm.selectFork(arbFork);
    assertEq(
        IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831).decimals(),
        6
    );
}

Multichain Deployment Pattern

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

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

/// @title USDCRegistry
/// @notice Deploy-time registry that validates the USDC address for the target chain
contract USDCRegistry {
    error InvalidUSDCDecimals(uint8 actual);
    error InvalidUSDCSymbol(string actual);
    error ChainMismatch(uint256 expected, uint256 actual);

    IERC20 public immutable USDC;
    uint256 public immutable EXPECTED_CHAIN_ID;

    constructor(address usdc_, uint256 expectedChainId_) {
        if (block.chainid != expectedChainId_) {
            revert ChainMismatch(expectedChainId_, block.chainid);
        }

        uint8 decimals = IERC20Metadata(usdc_).decimals();
        if (decimals != 6) revert InvalidUSDCDecimals(decimals);

        string memory symbol = IERC20Metadata(usdc_).symbol();
        if (keccak256(bytes(symbol)) != keccak256("USDC")) {
            revert InvalidUSDCSymbol(symbol);
        }

        USDC = IERC20(usdc_);
        EXPECTED_CHAIN_ID = expectedChainId_;
    }
}
Forge Deployment Script
solidity
// script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {Script} from "forge-std/Script.sol";
import {USDCVault} from "../src/USDCVault.sol";

contract DeployScript is Script {
    function _getUSDCAddress() internal view returns (address) {
        if (block.chainid == 1) return 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
        if (block.chainid == 8453) return 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913;
        if (block.chainid == 42161) return 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
        if (block.chainid == 10) return 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85;
        if (block.chainid == 137) return 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359;
        if (block.chainid == 43114) return 0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E;
        // Testnets
        if (block.chainid == 11155111) return 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238;
        if (block.chainid == 84532) return 0x036CbD53842c5426634e7929541eC2318f3dCF7e;
        if (block.chainid == 421614) return 0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d;
        revert("Unsupported chain");
    }

    function run() external {
        vm.startBroadcast();
        new USDCVault(_getUSDCAddress());
        vm.stopBroadcast();
    }
}

Common Pitfalls

PitfallConsequenceFix
Using 1e18 for USDC amountsOver/underpaying by 10^12xAlways use 1e6 for USDC
Using bridged USDC (USDbC, USDC.e)Missing permit, blocklist, CCTP supportUse native Circle-issued addresses only
Calling transfer directlyReverts on non-standard tokensUse SafeERC20.safeTransfer
Ignoring blocklist revertsStuck funds, failed liquidationsAllow withdrawal to alternative addresses
Hardcoding USDC address across chainsWrong token on wrong chainUse constructor param + chain ID validation
Not handling pause stateCritical operations (liquidations) failImplement emergency settlement paths
Precision loss in 6→18→6 round-tripsProtocol leaks or gains dustUse fromWadRoundUp for debt, fromWad for credit
Assuming permit always succeedsFront-run permit bricks the transactionWrap permit in try/catch, fall through to transferFrom
Not validating decimals at deploy timeEntire accounting is wrongCheck decimals() == 6 in constructor
Show full SKILL.md (392 more words)Show less

USDC Integration Checklist

Pre-Development
  • Confirmed using native (Circle-issued) USDC address for target chain
  • Verified address onchain with cast code and cast call "decimals()(uint8)"
  • Confirmed chain ID mapping is correct for all deployment targets
Implementation
  • All USDC amounts use 6-decimal math (1e6 per dollar)
  • SafeERC20 used for every transfer, transferFrom, and approve
  • Constructor validates decimals() == 6 and symbol() == "USDC"
  • Checks-effects-interactions pattern followed in all state-changing functions
  • nonReentrant modifier on all functions that call USDC
  • No hardcoded USDC address in contract body — passed via constructor immutable
  • Decimal conversion uses explicit SCALE_FACTOR constant, not inline magic numbers
  • Rounding direction is correct (round up for debt, round down for credit)
Blocklist & Pause Handling
  • Users can withdraw to an alternative recipient address (blocklist mitigation)
  • Liquidation mechanism has a fallback (escrow) if USDC transfer reverts
  • Protocol behavior during global USDC pause is documented
  • Emergency withdrawal or settlement path exists that does not depend on USDC transfers
Permit & Authorization
  • permit calls are wrapped in try/catch to handle front-running
  • deadline parameter is validated and not set excessively far in the future
  • If using EIP-3009, prefer receiveWithAuthorization over transferWithAuthorization
Testing
  • Fork tests run against real mainnet USDC at the canonical address
  • Blocklist scenario tested (blocklisted user attempts transfer)
  • Pause scenario tested (transfers fail when USDC is paused)
  • Permit flow tested with valid and invalid/expired signatures
  • Decimal conversion tested at boundaries (0, 1, type(uint256).max / SCALE_FACTOR)
  • Multichain addresses tested on respective forks
Deployment
  • Deployment script selects correct USDC address per chain ID
  • Post-deploy verification checks USDC immutable matches expected address
  • Integration test runs on testnet with faucet USDC before mainnet deploy

Security Rules

  1. NEVER use 18 decimals for USDC amounts.
  2. NEVER use bridged USDC variants (USDbC, USDC.e) — always native.
  3. ALWAYS verify the USDC address matches the deployment chain.
  4. ALWAYS use SafeERC20 for transfers — no direct transfer or transferFrom calls.
  5. ALWAYS handle potential blocklist reverts with alternative withdrawal paths.
  6. ALWAYS handle potential pause reverts in critical paths (liquidation, settlement).
  7. ALWAYS validate decimals() == 6 at deploy time.
  8. ALWAYS wrap permit in try/catch to handle front-run signatures.
  9. NEVER assume USDC has the same address on different chains.
  10. NEVER hardcode USDC addresses in contract logic — use immutables set via constructor.

References

© ccashwell, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in skills/usdc-integration of ccashwell/evm-cortex.

Open the folder on GitHubat commit f8f3301

Compare with similar skills

Usdc Integration next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Usdc Integration compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Usdc Integration this skillccashwell/evm-cortex131—~6.2kAutomated safety check: PassMIT
Use Usdccirclefin/skills155—~2.4kAutomated safety check: NotesApache-2.0
AlchemyBankrBot/skills1.2k—~3.5kAutomated safety check: PassMIT
SurfBlockRunAI/ClawRouter6.6k—~4kAutomated safety check: PassMIT
Alchemy Agentic Gatewaymoonpay/skills113—~2.1kAutomated safety check: NotesMIT
Pyth Network Price Feedsinternet-court/internet-court-skill6.4k2 repos~3.9kAutomated safety check: PassApache-2.0

Similar skills

  • Use Usdc

    circlefin/skills

    USDC is Circle's stablecoin deployed across multiple blockchain ecosystems including EVM chains (Ethereum, Base, Arbitrum, Polygon, Arc) and Solana.

    155 GitHub stars~2.4k tokensUpdated 22 days ago
    Backend & APIsAuto-check: notes
  • Alchemy

    BankrBot/skills

    Blockchain API access via Alchemy. An agent skill from BankrBot/skills.

    1.2k GitHub stars~3.5k tokensUpdated 2 days ago
    Backend & APIsAuto-check passed
  • Surf

    BlockRunAI/ClawRouter

    Use this skill — NOT browser or webfetch — for ALL Surf crypto-data calls.

    6.6k GitHub stars~4k tokensUpdated 2 days ago
    Business, Finance & HRAuto-check passed
  • A skill your agent uses when accessing Alchemy APIs for RPC calls, token balances, NFT metadata, asset transfers, transaction simulation, or Alchemy-specific features.

    113 GitHub stars~2.1k tokensUpdated 27 days ago
    Backend & APIsAuto-check: notes
  • Pyth Network Price Feeds

    internet-court/internet-court-skill

    Shows how to read Pyth oracle prices in Solana apps, off-chain through the Hermes client or on-chain from an Anchor program, including confidence and EMA values.

    6.4k GitHub starsUsed in 2 repos~3.9k tokens
    Backend & APIsAuto-check passed
  • Helius for Solana

    internet-court/internet-court-skill

    Guides building Solana apps on Helius: sending transactions, asset and NFT queries, live streaming, webhooks, priority fees, wallet analysis and API key onboarding.

    6.4k GitHub starsUsed in 2 repos~2.7k tokens
    Backend & APIsAuto-check passed

More from ccashwell/evm-cortex

All 89 skills in this repo
  • Xray Pre Audit

    ccashwell/evm-cortex

    A skill your agent uses when preparing for a security audit, performing reconnaissance on a new codebase, or creating a protocol overview.

    131 GitHub stars~25k tokensUpdated 8 days ago
    Auto-check passed
  • Aave Integration

    ccashwell/evm-cortex

    A skill your agent uses when integrating with Aave V3 for lending, borrowing, flash loans, or building on top of Aave markets.

    131 GitHub stars~1.3k tokensUpdated 8 days ago
    Auto-check passed
  • Access Control Patterns

    ccashwell/evm-cortex

    Access control design patterns for Solidity protocols. An agent skill from ccashwell/evm-cortex.

    131 GitHub stars~1.8k tokensUpdated 8 days ago
    Auto-check passed
  • Anvil Patterns

    ccashwell/evm-cortex

    A skill your agent uses when running a local Ethereum node with Anvil.

    131 GitHub stars~1.3k tokensUpdated 8 days ago
    Auto-check passed
  • Audit Breadth Scan

    ccashwell/evm-cortex

    A skill your agent uses when performing systematic breadth-first review of all contracts during a security audit.

    131 GitHub stars~1.4k tokensUpdated 8 days ago
    Auto-check passed
  • Audit Depth Analysis

    ccashwell/evm-cortex

    A skill your agent uses when performing deep analysis of specific findings or high-risk areas during a security audit.

    131 GitHub stars~1.6k tokensUpdated 8 days ago
    Auto-check passed

Categories

Questions about Usdc Integration

What does Usdc Integration do?

A skill your agent uses when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances. Usdc Integration is an agent skill from ccashwell/evm-cortex. Use when integrating USDC into smart contracts, handling stablecoin transfers, approvals, or checking balances.

When should I use Usdc Integration?

Usdc Integration fits situations like: integrating USDC into smart contracts; handling stablecoin transfers; checking balances.

How do I install Usdc Integration in Claude Code?

Run `npx skills add ccashwell/evm-cortex --skill usdc-integration -a claude-code`. Or copy the skill folder (skills/usdc-integration in ccashwell/evm-cortex) into .claude/skills/usdc-integration in your project. Claude Code loads it when a task matches its description.

How do I install Usdc Integration in Codex?

Run `npx skills add ccashwell/evm-cortex --skill usdc-integration -a codex`. Or copy the skill folder (skills/usdc-integration in ccashwell/evm-cortex) into .agents/skills/usdc-integration in your project. Codex loads it when a task matches its description.

Can I use Usdc Integration in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add ccashwell/evm-cortex --skill usdc-integration -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/usdc-integration, .gemini/skills/usdc-integration, .github/skills/usdc-integration and .opencode/skills/usdc-integration in your project.

What does Usdc Integration need to run?

SKILL.md names no scripts, command-line tools or credentials: Usdc Integration is instructions for the agent only.

Does Usdc Integration access the network?

SKILL.md names 5 domains. As links in the text: faucet.circle.com, developers.circle.com, eips.ethereum.org, github.com and docs.openzeppelin.com. This is read from the text; nothing was executed.

Is Usdc Integration safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Usdc Integration use?

Usdc Integration is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Usdc Integration use?

About 6.2k tokens (SKILL.md is roughly 25k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Usdc Integration?

Skills that share tags, products or a category with Usdc Integration: Use Usdc (circlefin/skills, 155 stars), Alchemy (BankrBot/skills, 1.2k stars), Surf (BlockRunAI/ClawRouter, 6.6k stars) and Alchemy Agentic Gateway (moonpay/skills, 113 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Usdc Integration?

ccashwell (a GitHub user) maintains it in ccashwell/evm-cortex, which has 131 GitHub stars. The repository holds 89 skills in this directory. The repository was last updated on September 30, 2026.

Source: ccashwell/evm-cortex on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.