120 lines
4.0 KiB
Solidity
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);
|
|
}
|
|
}
|