Files
smom-dbis-138/contracts/bridge/atomic/AtomicX402SourceAdapter.sol
2026-07-08 10:02:12 -07:00

120 lines
4.0 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {AtomicBridgeCoordinator} from "./AtomicBridgeCoordinator.sol";
interface ILegacyBurnableERC20 is IERC20 {
function burn(uint256 amount) external;
}
interface IMintableERC20 is IERC20 {
function mint(address to, uint256 amount) external;
}
interface IAtomicObligationEscrowAddress {
function obligationEscrow() external view returns (address);
}
/**
* @title AtomicX402SourceAdapter
* @notice Normalizes Chain 138 cUSDC/cUSDC_V2 input into cUSDC_V2 before creating
* an atomic x402 obligation. Legacy cUSDC is burned by this adapter after
* transfer-in; cUSDC_V2 is escrowed as the coordinator assetIn.
*/
contract AtomicX402SourceAdapter {
using SafeERC20 for IERC20;
uint64 public immutable sourceChain;
uint64 public immutable destinationChainSelector;
ILegacyBurnableERC20 public immutable legacyCUSDC;
IMintableERC20 public immutable cUSDCV2;
IERC20 public immutable cWUSDCV2;
AtomicBridgeCoordinator public immutable coordinator;
address public immutable escrow;
event SourceIntentCreated(
bytes32 indexed obligationId,
address indexed payer,
address indexed sourceToken,
address recipient,
uint256 amountIn,
uint256 minAmountOut
);
error UnsupportedSourceToken(address token);
error ZeroAddress();
error ZeroAmount();
constructor(
address legacyCUSDC_,
address cUSDCV2_,
address cWUSDCV2_,
address coordinator_,
uint64 sourceChain_,
uint64 destinationChainSelector_
) {
if (
legacyCUSDC_ == address(0) || cUSDCV2_ == address(0) || cWUSDCV2_ == address(0)
|| coordinator_ == address(0)
) {
revert ZeroAddress();
}
legacyCUSDC = ILegacyBurnableERC20(legacyCUSDC_);
cUSDCV2 = IMintableERC20(cUSDCV2_);
cWUSDCV2 = IERC20(cWUSDCV2_);
coordinator = AtomicBridgeCoordinator(coordinator_);
sourceChain = sourceChain_;
destinationChainSelector = destinationChainSelector_;
escrow = IAtomicObligationEscrowAddress(coordinator_).obligationEscrow();
IERC20(cUSDCV2_).forceApprove(escrow, type(uint256).max);
}
function createIntent(
address sourceToken,
uint256 amountIn,
uint256 minAmountOut,
address recipient,
uint256 deadline,
bytes32 routeId
) external returns (bytes32 obligationId) {
if (amountIn == 0 || minAmountOut == 0) revert ZeroAmount();
if (recipient == address(0)) revert ZeroAddress();
_normalizeToV2(sourceToken, amountIn);
obligationId = coordinator.createIntentFor(
AtomicBridgeCoordinator.CreateIntentParams({
sourceChain: sourceChain,
destinationChain: destinationChainSelector,
assetIn: address(cUSDCV2),
assetOut: address(cWUSDCV2),
amountIn: amountIn,
minAmountOut: minAmountOut,
recipient: recipient,
deadline: deadline,
routeId: routeId
}),
address(this),
msg.sender
);
emit SourceIntentCreated(obligationId, msg.sender, sourceToken, recipient, amountIn, minAmountOut);
}
function _normalizeToV2(address sourceToken, uint256 amountIn) internal {
if (sourceToken == address(legacyCUSDC)) {
IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
legacyCUSDC.burn(amountIn);
cUSDCV2.mint(address(this), amountIn);
return;
}
if (sourceToken == address(cUSDCV2)) {
IERC20(sourceToken).safeTransferFrom(msg.sender, address(this), amountIn);
return;
}
revert UnsupportedSourceToken(sourceToken);
}
}