Files
2026-03-02 12:14:09 -08:00

151 lines
5.0 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract CactiAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant CACTI_OPERATOR_ROLE = keccak256("CACTI_OPERATOR_ROLE");
bool public isActive;
string public cactiApiUrl;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public cactiTxIds;
mapping(address => uint256) public nonces;
event CactiBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string sourceLedger,
string destLedger,
string cactiTxId
);
event CactiBridgeConfirmed(
bytes32 indexed requestId,
string indexed cactiTxId,
string sourceLedger,
string destLedger
);
constructor(address admin, string memory _cactiApiUrl) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(CACTI_OPERATOR_ROLE, admin);
cactiApiUrl = _cactiApiUrl;
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Cacti";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Cacti-Interoperability");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
(string memory sourceLedger, string memory destLedger) = abi.decode(destination, (string, string));
require(bytes(sourceLedger).length > 0 && bytes(destLedger).length > 0, "Invalid ledgers");
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
sourceLedger,
destLedger,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit CactiBridgeInitiated(requestId, msg.sender, token, amount, sourceLedger, destLedger, "");
return requestId;
}
function confirmCactiOperation(
bytes32 requestId,
string calldata cactiTxId,
string calldata sourceLedger,
string calldata destLedger
) external onlyRole(CACTI_OPERATOR_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
cactiTxIds[requestId] = cactiTxId;
emit CactiBridgeConfirmed(requestId, cactiTxId, sourceLedger, destLedger);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}