Настройка приймання платежів в USDC
USDC — це ERC-20 токен з однією принципіальною відмінністю від більшості токенів: Centre (тепер Circle) може заморозити будь-яку адресу та конфіскувати засоби через функцію blacklist. Це не гіпотетична можливість — її застосовували при санкційних блокуваннях. Для бізнесу це означає KYC-сумісність, для розробника — потрібно розуміти що приймаєш не просто «стейблкоін», а регульований інструмент з on-chain compliance.
Контракти USDC по мережах
Circle розгорнув нативний USDC (не bridged) на кількох мережах — це важливо, тому що нативний USDC напрямки mintується/burnується через Cross-Chain Transfer Protocol (CCTP), а bridged версії несуть додаткові ризики bridge контракту.
| Мережа | Адреса контракту | Тип |
|---|---|---|
| Ethereum | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
Native |
| Polygon | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |
Native (новий) |
| Arbitrum One | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
Native |
| Base | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
Native |
| Solana | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
Native |
Для більшості проектів Polygon, Arbitrum або Base — оптимальний вибір з точки зору gas fees для користувачів.
Базова схема приймання платежів
Принципіальна відмінність від приймання ETH: USDC вимагає двохшагового процесу — спочатку користувач викликає approve(), потім ваш контракт викликає transferFrom(). Або використовується EIP-3009 (transferWithAuthorization) — підпис замість approve транзакції.
Варіант 1: Унікальна адреса на кожен платіж
Генеруйте HD гаманець (BIP-32/44), для кожного платежу — нова адреса. Мониторьте Transfer(from, to, value) подію ERC-20 на цих адресах. Просто, без смарт контракту, але sweep транзакції витрачають газ (потрібен ETH/MATIC на адресі для оплати газу при переводі USDC).
from web3 import Web3
from eth_account import Account
import secrets
def generate_payment_address(order_id: str, master_key: bytes) -> dict:
# Детермінована дериація від order_id
child_key = derive_child_key(master_key, order_id)
account = Account.from_key(child_key)
return {
"address": account.address,
"order_id": order_id,
"expires_at": int(time.time()) + 3600 # 1 година
}
Варіант 2: Єдиний контракт-шлюз
Користувач робить approve(gateway_contract, amount), потім викликає pay(order_id, amount). Контракт забирає USDC та емітує подію.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract USDCGateway is Ownable {
IERC20 public immutable usdc;
event PaymentReceived(
bytes32 indexed orderId,
address indexed payer,
uint256 amount
);
constructor(address _usdc) Ownable(msg.sender) {
usdc = IERC20(_usdc);
}
function pay(bytes32 orderId, uint256 amount) external {
require(amount > 0, "Zero amount");
usdc.transferFrom(msg.sender, address(this), amount);
emit PaymentReceived(orderId, msg.sender, amount);
}
function withdraw(address to, uint256 amount) external onlyOwner {
usdc.transfer(to, amount);
}
}
Варіант 3: EIP-3009 (gasless approve)
USDC підтримує transferWithAuthorization — користувач підписує EIP-712 сообщение off-chain, ваш backend або контракт викликає транзакцію від його імені. Користувач платить газ тільки один раз (замість approve + transfer).
import { signTypedData } from 'viem/accounts';
const authorization = await signTypedData({
domain: { name: 'USD Coin', version: '2', chainId: 137, verifyingContract: USDC_ADDRESS },
types: {
TransferWithAuthorization: [
{ name: 'from', type: 'address' },
{ name: 'to', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'validAfter', type: 'uint256' },
{ name: 'validBefore', type: 'uint256' },
{ name: 'nonce', type: 'bytes32' },
]
},
primaryType: 'TransferWithAuthorization',
message: { from, to: GATEWAY, value: amount, validAfter: 0, validBefore: deadline, nonce: randomBytes32 }
});
Мониторинг та підтвердження
Мониторьте подію Transfer на USDC контракті з фільтром по адресі отримувача:
usdc_contract = w3.eth.contract(address=USDC_ADDRESS, abi=ERC20_ABI)
# Polling підхід
event_filter = usdc_contract.events.Transfer.create_filter(
fromBlock='latest',
argument_filters={'to': GATEWAY_ADDRESS}
)
def check_payments():
for event in event_filter.get_new_entries():
order_id = match_order(event['args']['from'], event['args']['value'])
if order_id:
confirmations = get_confirmations(event['blockNumber'])
if confirmations >= REQUIRED_CONFIRMATIONS:
mark_order_paid(order_id, event['transactionHash'])
Кількість підтверджень: для Polygon — 128+ (фіналізація ~4 хвилини з урахуванням checkpoint на Ethereum), для Arbitrum — 1 блок достатньо для більшості платежів, для Ethereum mainnet — 12-15 блоків.
Типові проблеми
Сума не збігається. Користувач відправив трохи менше (помилка округлення на UI). Зберігайте tolerance: abs(received - expected) < dust_threshold.
Replay атаки. Один Transfer може відповідати кільком замовленням по сумі. Прив'язуйте txHash до замовлення, не тільки суму.
USDC blacklist. Якщо адреса користувача в blacklist — transferFrom ревертнеться. Потрібна обробка помилки з зрозумілим сообщением.
Газ для sweep. При схемі з унікальними адресами потрібен ETH/MATIC для оплати sweep транзакції. Держіть резервний гаманець для топ-апу газу.
Процес впровадження
Вибір мережі → деплой або настройка gateway → інтеграція webhook мониторингу → тестування на testnet (USDC Faucet на Sepolia/Mumbai) → аудит логіки підтверджень → production деплой.
Терміни 2-3 дні включають: настройку мониторингу, деплой gateway контракту (опціонально), інтеграцію з backend, end-to-end тестування.







