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:
**Cross-Chain Request**: Initiates a request for executing functions on a different blockchain.
**Fulfillment**: Completes a request on the destination chain, verifying the proof and transferring any
rewards to the caller.
**Cancellation**: Allows the requester to cancel a pending request if it is no longer needed.
**Reward Distribution**: Automatically distributes the reward to the party fulfilling the request.
**Storage Proofs**: Stores proof data for further validation, ensuring request fulfillment is
legitimate.
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;
}
}
...
Copy Contract Code
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.
Connect MetaMask
Submit a Cross-Chain Request
Important Notes:
Ensure your MetaMask wallet is connected to the correct network before submitting a request.
Check the chain ID, expiry time, and reward amount carefully before confirming the transaction.
Review the gas fees displayed in MetaMask when submitting the transaction to avoid overspending.