Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions docs/7683/solver-integration.md
Original file line number Diff line number Diff line change
@@ -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.
Comment thread
kss-t1 marked this conversation as resolved.

### 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);
```
174 changes: 174 additions & 0 deletions examples/sdk/fetchAndVerifyProofOfRead.ts
Original file line number Diff line number Diff line change
@@ -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> <direction>
* - 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<ReadProofsResponse> {
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<VerifiedProofReturn | undefined> {
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 <requester_address> <direction: "ARB_TO_BASE" | "BASE_TO_ARB">',
);
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);