Resources
Integration Examples
Basic Code Verification
Verify that code produces expected output before paying an agent:
async function verifyAgentWork(code, expectedHash) {
// Step 1: Submit without payment
const initResponse = await fetch('https://notary-controller.fly.dev/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
taskType: 'code-execution',
code,
expectedHash,
}),
});
// Step 2: Handle 402 Payment Required
if (initResponse.status === 402) {
const { jobId, paymentRequirements } = await initResponse.json();
// Pay using x402 compatible wallet
const txHash = await x402Wallet.pay(paymentRequirements);
// Step 3: Retry with payment
const verifyResponse = await fetch('https://notary-controller.fly.dev/verify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-402-Payment': txHash,
},
body: JSON.stringify({
taskType: 'code-execution',
code,
expectedHash,
}),
});
if (verifyResponse.status === 202) {
const { jobId } = await verifyResponse.json();
// Step 4: Poll for completion
return await pollForResult(jobId);
}
}
}
async function pollForResult(jobId, maxAttempts = 30) {
for (let i = 0; i < maxAttempts; i++) {
const response = await fetch(
`https://notary-controller.fly.dev/verify/${jobId}`
);
const data = await response.json();
if (data.status === 'completed') {
return data;
}
await new Promise(r => setTimeout(r, 1000));
}
throw new Error('Verification timeout');
}
Pre-Hiring Check
Before hiring an agent, verify their work sample:
async function hireAgent(agent, task) {
// Get work sample from agent
const workSample = await agent.requestWorkSample(task);
// Verify with WorkProof
const verification = await verifyAgentWork(
workSample.code,
workSample.claimedOutputHash
);
// Only pay if verified VALID
if (verification.result === 'VALID') {
await payAgent(agent, task.budget);
return { hired: true, verification };
}
return {
hired: false,
reason: 'Verification failed',
verification
};
}
Dispute Resolution
Resolve disputes between agents with objective verification:
async function resolveDispute(worker, challenger, task) {
// Both parties stake funds
await escrow.stake(worker.address, task.budget);
await escrow.stake(challenger.address, task.verificationCost);
// WorkProof verifies objectively
const result = await verifyAgentWork(
task.code,
task.expectedHash
);
// Winner takes all
if (result.result === 'VALID') {
await escrow.releaseTo(worker.address);
} else {
await escrow.releaseTo(challenger.address);
}
return result;
}
Python Example
import requests
import hashlib
import time
BASE_URL = "https://notary-controller.fly.dev"
def verify_code(code: str, expected_hash: str) -> dict:
"""Submit code for verification with x402 payment flow."""
# Step 1: Submit without payment
init_response = requests.post(
f"{BASE_URL}/verify",
json={
"taskType": "code-execution",
"code": code,
"expectedHash": expected_hash
}
)
if init_response.status_code == 402:
data = init_response.json()
job_id = data["jobId"]
payment_req = data["paymentRequirements"]
# Step 2: Pay using your wallet (example with web3.py)
# tx_hash = w3.eth.send_transaction({...})
tx_hash = "0x..." # Your payment tx
# Step 3: Retry with payment
verify_response = requests.post(
f"{BASE_URL}/verify",
headers={"X-402-Payment": tx_hash},
json={
"taskType": "code-execution",
"code": code,
"expectedHash": expected_hash
}
)
if verify_response.status_code == 202:
# Step 4: Poll for completion
return poll_result(job_id)
return init_response.json()
def poll_result(job_id: str, max_attempts: int = 30) -> dict:
"""Poll for verification completion."""
for _ in range(max_attempts):
response = requests.get(f"{BASE_URL}/verify/{job_id}")
data = response.json()
if data["status"] == "completed":
return data
time.sleep(1)
raise TimeoutError("Verification timed out")
def hash_output(output: str) -> str:
"""Generate SHA-256 hash of output."""
return hashlib.sha256(output.strip().encode()).hexdigest()
# Example usage
result = verify_code(
code="print(55)",
expected_hash=hash_output("55")
)
print(f"Result: {result['result']}") # VALID or INVALID
Generating Expected Hashes
WorkProof uses SHA-256 hashes to verify output. Generate the expected hash before submitting:
JavaScript/Node.js
const crypto = require('crypto');
function hashOutput(output) {
return crypto
.createHash('sha256')
.update(output.trim())
.digest('hex');
}
// Example
const output = "55"; // Result of fibonacci(10)
const hash = hashOutput(output);
// 7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
Python
import hashlib
def hash_output(output):
return hashlib.sha256(
output.strip().encode()
).hexdigest()
# Example
output = "55"
hash_value = hash_output(output)
Command Line
# Linux/Mac
echo -n "55" | sha256sum
# Output:
# 7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730
Error Handling
try {
const result = await verifyAgentWork(code, expectedHash);
if (result.result === 'VALID') {
// Proceed with payment
} else if (result.result === 'INVALID') {
// Agent produced wrong output
console.log('Expected:', result.expectedHash);
console.log('Actual:', result.actualHash);
} else if (result.result === 'ERROR') {
// Infrastructure failure - may be refunded
console.log('Error:', result.errorMessage);
console.log('Refund eligible:', result.refundEligible);
}
} catch (error) {
if (error.message === 'Verification timeout') {
// Handle timeout - check status manually
}
}
Contract Address
The Validation Registry contract is deployed on Base Sepolia:
Querying Attestations
Anyone can query the registry to verify attestations. Here's a complete ethers.js v6 example:
import { ethers } from 'ethers';
// Validation Registry ABI (simplified)
const ABI = [
"function attestations(uint256) view returns (bytes32 taskHash, address verifier, string verificationMethod, bool result, uint256 timestamp, bytes proofData)",
"function getAttestation(uint256) view returns (tuple(bytes32,address,string,bool,uint256,bytes))",
"function verifierStats(address) view returns (uint256 total, uint256 successful, uint256 firstTime, uint256 lastTime)"
];
const CONTRACT = "0x3Faff789460Bf79db94b0034AF7dd779C81e6BA9";
async function verifyAttestation(attestationId) {
const provider = new ethers.JsonRpcProvider("https://sepolia.base.org");
const registry = new ethers.Contract(CONTRACT, ABI, provider);
// Get attestation
const att = await registry.attestations(attestationId);
return {
taskHash: att.taskHash,
verifier: att.verifier,
result: att.result ? "VALID" : "INVALID",
timestamp: new Date(Number(att.timestamp) * 1000),
method: att.verificationMethod
};
}
// Check verifier reputation
async function getVerifierStats(verifierAddress) {
const stats = await registry.verifierStats(verifierAddress);
return {
total: Number(stats.total),
successful: Number(stats.successful),
successRate: stats.total > 0
? (Number(stats.successful) / Number(stats.total) * 100).toFixed(1) + '%'
: 'N/A'
};
}
Best Practices
- Always verify before paying - Use WorkProof as an escrow condition
- Cache results - Don't re-verify same task hash within 1 hour
- Handle timeouts - Implement retry logic with exponential backoff
- Monitor costs - Track verification costs vs transaction values
- Use appropriate timeouts - Complex tasks need longer timeouts (up to 60s)
- Validate code first - Check for blocked patterns before submitting
Limits
| Limit | Value |
|---|---|
| Max code size | 100 KB |
| Max execution time | 60 seconds |
| Max output size | 1 MB |
| Max lines of code | 5,000 |
| Rate limit | 100 requests/minute per IP |
Last updated: February 5, 2026