diff --git a/docs/7683/solver-integration.md b/docs/7683/solver-integration.md new file mode 100644 index 000000000..eb62e446f --- /dev/null +++ b/docs/7683/solver-integration.md @@ -0,0 +1,157 @@ +# t1 intents protocol: solver integration + +Our intent protocol implements the ERC 7683 specification. + +The workflow is the same for intents opening, filling and settlement. + + +There is only a difference in the process for settlement of solvers on origin chain. + +Solvers need to require a so called Proof of Read, and then use a merkle proof to get repaid. + +## Workflow + +Here is the full workflow from intent open to settlement (t1 specific steps with 🔴). + +The settler contract implementation is `T1ERC7683.sol`. + +1 - Intent is opened on source chain settler contract whether on chain by user or gasslessly by solver + +2 - Solver listens for open event on source chain settler contract and fill on destination chain settler contract + +🔴 3 - Solver calls a verify function on source chain settler contract + +🔴 4 - Solver listens for new Proof of Read commitment event on source chain XChainReader contract + +🔴 5 - Solver calls t1 offchain API to get the merkle proof data + +🔴 6 - Solver calls settlement function on source chain settler contract and get repaid + +## Implementation + +Here is how you integrate from step 3. + +### 3 - Solver call a verify function on source chain settler contract + +Location: source chain + +Contract: T1ERC7683 (see deployment file for address and ABI) + +Timing: should be called once intent is filled on destination chain + +```solidity + /// @param destinationDomain The chain id of the destination chain + /// @param gasLimit The gas limit for the read operation + /// @param orderId The ID of the order to verify fillment + /// @return requestId The ID of the read request + function verifySettlement( + uint32 destinationDomain, + uint256 gasLimit, + bytes32 orderId + ) + payable + returns (bytes32 requestId); +``` + +### 4 - Solver listen for new Proof of Read commitment event on source chain XChainReader contract + +Location: source chain + +Contract: T1XChainReader (see deployment file for address and ABI) + +Timing: should be listened to once `verifySettlement` has been called + +```solidity + /// @param batchIndex The index where the Proof of Read batch has been committed + event ProofOfReadRootCommitted(uint256 batchIndex); +``` + +### 5 - Solver call t1 offchain API to get the merkle proof data + +Location: offchain + +Timing: once a new `ProofOfReadRootCommitted` event has been emitted + +Base URL: `https://api.v05.t1protocol.com` + +Endpoint: `/api/read-proofs` + +Method: `GET` + +**Query Parameters:** +| Parameter | Type | Optional | Description | Value suggested | +|-----------|------|----------|-------------|---------| +| `address` | string | No | Address that called `verifySettlement` | - | +| `direction` | string | Yes | Direction of the read: `"L1_TO_L2"` \| `"L2_TO_L1"` | - | +| `page` | number | Yes | Page number to fetch | `1` | +| `page_size` | number | Yes | Items per page | `100` | + +**Example Request:** +```bash +curl "https://api.v05.t1protocol.com/api/read-proofs?address=0x123...&direction=L1_TO_L2&page=1&page_size=100" +``` + +The HTTP call will return the following structure as a response : + +```typescript +interface ReadProofsResponse { + results: Result[]; + page: number; + page_size: number; + total: number; +} + +interface Result { + source_tx_hash: string; + message_type: number; + block_number: number; + message_hash: string; + tx_sender: string; + direction: Direction; + claim_info: ClaimInfo; +} + +enum Direction { + L1_TO_L2 = 'L1_TO_L2', + L2_TO_L1 = 'L2_TO_L1', +} + +interface ClaimInfo { + from: string; + to: string; + value: string; + nonce: string; + message: string; + x_chain_read_result: string; + request_id: string; + handle_read_result_with_proof_calldata: string; + proof: { + batch_index: string; + merkle_proof: string; + }; +} + +``` + +Solver will then iterate on the `results` array and find the one that matches its request. + +Solver can check values like `source_tx_hash` or `tx_sender` for that. + +Solver will then get the value in `claim_info.handle_read_result_with_proof_calldata` from the matching result for next step. + +See more: https://docs.t1protocol.com/api/xChainRead/overview + +### 6 - Solver call settlement function on source chain settler contract and get repaid + +Location: source chain + +Contract: T1ERC7683 (see deployment file for address and ABI) + +Timing: should be called once new `ProofOfReadRootCommitted` event has been emitted and merkle proof has been fetched from API + +```solidity + /// @param encodedProofOfRead The encoded proof of read which is formatted as following: + /// abi.encode(uint256 batchIndex, bytes32 requestId, uint256 position, bytes result, bytes proof) + /// This is the value returned in previous step as `handle_read_result_with_proof_calldata` + function handleReadResultWithProof(bytes encodedProofOfRead); +``` diff --git a/examples/sdk/fetchAndVerifyProofOfRead.ts b/examples/sdk/fetchAndVerifyProofOfRead.ts new file mode 100644 index 000000000..a828638c5 --- /dev/null +++ b/examples/sdk/fetchAndVerifyProofOfRead.ts @@ -0,0 +1,174 @@ +/** + * t1 Protocol - Proof of Read Verification Example Script + * + * This script demonstrates how to fetch and verify cross-chain read proofs using the t1 Protocol. + * It retrieves Merkle proofs for cross-chain read operations from t1's API and verifies them on-chain via t1 XChainReader contract. + * + * Before running this script, ensure you have: + * 1. Node.js and npm installed + * 2. Installed dependencies (ethers.js) + * 3. A valid Ethereum address that has performed cross-chain reads through t1 Protocol + * + * Usage: node fetchAndVerifyProofOfRead + * - requester_address: The Ethereum address that initiated cross-chain reads + * - direction: Either "ARB_TO_BASE" or "BASE_TO_ARB" for the cross-chain direction + * + * Example: node fetchAndVerifyProofOfRead 0x1234567890123456789012345678901234567890 ARB_TO_BASE + */ + +import { ethers } from 'ethers'; + +enum Direction { + ARB_TO_BASE = 'L1_TO_L2', + BASE_TO_ARB = 'L2_TO_L1', +} + +interface ClaimInfo { + from: string; + to: string; + value: string; + nonce: string; + message: string; + x_chain_read_result: string; + request_id: string; + handle_read_result_with_proof_calldata: string; + proof: { + batch_index: string; + merkle_proof: string; + }; +} + +interface Result { + source_tx_hash: string; + message_type: number; + block_number: number; + message_hash: string; + tx_sender: string; + direction: Direction; + claim_info: ClaimInfo; +} + +interface ReadProofsResponse { + results: Result[]; + page: number; + page_size: number; + total: number; +} + +interface VerifiedProofReturn { + requestId: string; + result: string; +} + +const XCHAINREADER_ABI = [ + 'function verifyProofOfRead(bytes encodedProofOfRead) external view returns (bytes32, bytes)', +]; +const API_BASE_URL = 'https://api.v05.t1protocol.com/api'; +const ARB_RPC_URL = 'https://arbitrum-sepolia-rpc.publicnode.com'; +const BASE_RPC_URL = 'https://base-sepolia-rpc.publicnode.com'; +const ARB_XCHAINREADER_ADDRESS = '0x42d389A9007e446b92C0ce7bd8F42Ea10292881B'; +const BASE_XCHAINREADER_ADDRESS = '0x3821b214B4c9D053fa744dc2B355E2039696dFb7'; + +async function getMerkleProofs( + address: string, + direction: Direction, + page: number = 1, + pageSize: number = 100, +): Promise { + const url = `${API_BASE_URL}/read-proofs?address=${address}&direction=${direction}&page=${page}&page_size=${pageSize}`; + + console.log(`Fetching Merkle proofs from: ${url}`); + + const response = await fetch(url); + + if (!response.ok) { + throw new Error( + `Failed to fetch proofs: ${response.status} ${response.statusText}`, + ); + } + + const { data } = await response.json(); + return data; +} + +async function verifyProofOfRead( + direction: Direction, + proofCalldata: string, +): Promise { + const provider = new ethers.JsonRpcProvider( + direction === Direction.ARB_TO_BASE ? ARB_RPC_URL : BASE_RPC_URL, + ); + const contract = new ethers.Contract( + direction === Direction.ARB_TO_BASE + ? ARB_XCHAINREADER_ADDRESS + : BASE_XCHAINREADER_ADDRESS, + XCHAINREADER_ABI, + provider, + ); + + try { + const response = await contract.verifyProofOfRead(proofCalldata); + return { requestId: response[0], result: response[1] }; + } catch (error) { + console.error('Error verifying proof:', error); + return undefined; + } +} + +async function main() { + const address = process.argv[2]; + const direction = process.argv[3]; + + if ( + !address || + (direction !== 'ARB_TO_BASE' && direction !== 'BASE_TO_ARB') + ) { + console.error( + 'Usage: npm run dev ', + ); + process.exit(1); + } + + const parsedDirection = + direction === 'ARB_TO_BASE' ? Direction.ARB_TO_BASE : Direction.BASE_TO_ARB; + + try { + console.log(`Getting Merkle proofs for address: ${address}`); + + const response = await getMerkleProofs(address, parsedDirection); + + if (response.results.length === 0) { + console.log('No proofs found for this address.'); + return; + } + + console.log(`Found ${response.results.length} proof(s)`); + + for (let i = 0; i < response.results.length; i++) { + const item = response.results[i]; + + console.log(`\nVerifying proof ${i + 1}/${response.results.length}:`); + console.log(`- Request ID: ${item.claim_info.request_id}`); + console.log(`- Source TX Hash: ${item.source_tx_hash}`); + console.log(`- Batch Index: ${item.claim_info.proof.batch_index}`); + + const res = await verifyProofOfRead( + parsedDirection, + item.claim_info.handle_read_result_with_proof_calldata, + ); + + if (!res) { + console.log('Proof verification FAILED'); + } else { + console.log('Proof verification SUCCESSFUL'); + } + } + + console.log('\nProof verifications completed'); + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +main().catch(console.error);