Interact with the RIP7755Unified Contract

The RIP7755Unified contract is a smart contract that enables cross-chain requests, verification, and execution. It allows for requesting, fulfilling, and canceling cross-chain calls, ensuring decentralized functionality across multiple blockchains.

Disclaimer: Ensure that you understand the contract’s purpose before interacting with it. Only connect your wallet and submit transactions if you fully comprehend the implications. Read the documentation below for more details.

How the Contract Works

This contract provides the following key functionalities:

Contract Code

The following is the full Solidity code for the contract:

// SPDX-License-Identifier: MIT pragma solidity 0.8.24; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/utils/SafeERC20.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721.sol"; import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/ReentrancyGuard.sol"; /** * @title RIP7755Unified * @notice This contract combines the functionalities of RIP7755Verifier, IPrecheckContract, and RIP7755Source into a unified structure * for cross-chain request initiation, verification, fulfillment, cancellation, and storage proof validation. */ contract RIP7755Unified is ReentrancyGuard { using Address for address payable; using SafeERC20 for IERC20; enum CrossChainCallStatus { None, Requested, Canceled, Completed } struct Call { address to; bytes data; uint256 value; } struct CrossChainRequest { address requester; Call[] calls; address originationContract; uint256 originChainId; uint256 destinationChainId; address verifyingContract; address rewardAsset; uint256 rewardAmount; uint256 finalityDelaySeconds; uint256 nonce; uint256 expiry; address precheckContract; bytes precheckData; } struct FulfillmentInfo { uint96 timestamp; address filler; } mapping(bytes32 => CrossChainCallStatus) private _requestStatus; mapping(bytes32 => FulfillmentInfo) private _fulfillmentInfo; mapping(bytes32 => bytes) private _storageProofs; // Store proof data uint256 private _nonce; address private constant NATIVE_ASSET = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; event CrossChainCallRequested(bytes32 indexed requestHash, CrossChainRequest request); event CrossChainCallCanceled(bytes32 indexed requestHash); event CallFulfilled(bytes32 indexed requestHash, address indexed fulfilledBy); event StorageProofSubmitted(bytes32 indexed requestHash, bytes proofData); // New event for storage proofs event ProofValidationAttempt(bytes32 indexed requestHash, bool success); event RewardDistributed(address indexed recipient, address indexed asset, uint256 amount); // Log rewards distributed error InvalidValue(uint256 expected, uint256 received); error InvalidStatus(CrossChainCallStatus expected, CrossChainCallStatus actual); error CannotCancelRequestBeforeExpiry(uint256 currentTimestamp, uint256 expiry); error InvalidCaller(address caller, address expectedCaller); error ExpiryTooSoon(); error InvalidChainId(); error InvalidVerifyingContract(); error CallAlreadyFulfilled(); error ProofValidationFailed(); error InvalidRewardAsset(address asset); modifier onlyRequester(bytes32 requestHash, address caller) { require( _requestStatus[requestHash] == CrossChainCallStatus.Requested, "Invalid request status" ); require( caller == _fulfillmentInfo[requestHash].filler, "Unauthorized caller" ); _; } function requestCrossChainCall(CrossChainRequest memory request) external payable nonReentrant { request.nonce = ++_nonce; request.requester = msg.sender; request.originationContract = address(this); request.originChainId = block.chainid; if (request.rewardAsset == NATIVE_ASSET) { require(msg.value == request.rewardAmount, "Invalid reward amount"); } else { IERC20(request.rewardAsset).safeTransferFrom( msg.sender, address(this), request.rewardAmount ); } require( request.expiry >= block.timestamp + request.finalityDelaySeconds, "Expiry too soon" ); bytes32 requestHash = keccak256(abi.encode(request)); _requestStatus[requestHash] = CrossChainCallStatus.Requested; emit CrossChainCallRequested(requestHash, request); } function fulfillCrossChainCall( CrossChainRequest calldata request, FulfillmentInfo calldata fillInfo, bytes calldata storageProofData, address payTo ) external payable nonReentrant onlyRequester(keccak256(abi.encode(request)), msg.sender) { require( block.chainid == request.destinationChainId, "Invalid chain ID" ); require( address(this) == request.verifyingContract, "Invalid verifying contract" ); // Check fulfillment info require(fillInfo.filler == msg.sender, "Invalid filler"); bytes32 requestHash = keccak256(abi.encode(request)); // Ensure the call hasn't already been fulfilled require( _fulfillmentInfo[requestHash].timestamp == 0, "Call already fulfilled" ); // Store the fulfillment info _fulfillmentInfo[requestHash] = FulfillmentInfo({ timestamp: uint96(block.timestamp), filler: msg.sender }); // Optional: Store proof data for further validation or future reference _storageProofs[requestHash] = storageProofData; // Execute the calls for (uint256 i = 0; i < request.calls.length; i++) { Address.functionCallWithValue( request.calls[i].to, request.calls[i].data, request.calls[i].value ); } // Send the reward to the fulfiller distributeReward(payTo, request.rewardAsset, request.rewardAmount); emit StorageProofSubmitted(requestHash, storageProofData); emit CallFulfilled(requestHash, msg.sender); } function cancelCrossChainCall(CrossChainRequest calldata request) external nonReentrant { bytes32 requestHash=keccak256(abi.encode(request)); require(msg.sender==request.requester, "Caller is not requester" ); require( _requestStatus[requestHash]==CrossChainCallStatus.Requested, "Invalid request status" ); require(block.timestamp>= request.expiry, "Request not yet expired"); _requestStatus[requestHash] = CrossChainCallStatus.Canceled; distributeReward(request.requester, request.rewardAsset, request.rewardAmount); emit CrossChainCallCanceled(requestHash); } function getFulfillmentInfo(bytes32 requestHash) external view returns (FulfillmentInfo memory) { return _fulfillmentInfo[requestHash]; } function getRequestStatus(bytes32 requestHash) external view returns (CrossChainCallStatus) { return _requestStatus[requestHash]; } function hashRequest(CrossChainRequest calldata request) external pure returns (bytes32) { return keccak256(abi.encode(request)); } function distributeReward(address recipient, address rewardAsset, uint256 rewardAmount) internal { if (rewardAsset == NATIVE_ASSET) { payable(recipient).sendValue(rewardAmount); } else if (IERC165(rewardAsset).supportsInterface(type(IERC20).interfaceId)) { IERC20(rewardAsset).safeTransfer(recipient, rewardAmount); } else if (IERC165(rewardAsset).supportsInterface(type(IERC721).interfaceId)) { IERC721(rewardAsset).safeTransferFrom(address(this), recipient, rewardAmount); } else { revert InvalidRewardAsset(rewardAsset); } emit RewardDistributed(recipient, rewardAsset, rewardAmount); } // Optional: Proof validation logic to verify storage proofs (customizable) function validateProof(bytes32 requestHash, bytes memory providedProof) internal view returns (bool) { bytes memory storedProof = _storageProofs[requestHash]; // Example: Perform a merkle root comparison or storage check require(storedProof.length > 0, "No proof stored"); // Implement logic for validating the provided proof // This can involve Merkle proof verification, or cross-chain block header validation bool isValid = keccak256(storedProof) == keccak256(providedProof); return isValid; } }
...

Connect MetaMask to Interact

Click the button below to connect your MetaMask wallet. Once connected, you can interact with the contract, request cross-chain calls, and more.

Important Notes: