Client Integration Guide
Draft Documentation
API interfaces and code examples are subject to change. Test thoroughly before production use. Join OMA3 Discord for implementation support.
Learn how to query the OMATrust registry and verify services in your application, whether you're building a web app, mobile app, AI agent, or smart contract.
Quick Start
Installation
npm install thirdweb ethers
Basic Setup
import { createThirdwebClient, getContract, readContract } from 'thirdweb';
import { defineChain } from 'thirdweb/chains';
import { ethers } from 'ethers';
// Create client
const client = createThirdwebClient({
clientId: process.env.THIRDWEB_CLIENT_ID
});
// Define OMAchain Testnet
const omachainTestnet = defineChain({
id: 66238,
rpc: 'https://rpc.testnet.chain.oma3.org'
});
// Get contracts
const registry = getContract({
client,
chain: omachainTestnet,
address: '0xb493465Bcb2151d5b5BaD19d87f9484c8B8A8e83'
});
const resolver = getContract({
client,
chain: omachainTestnet,
address: '0x7946127D2f517c8584FdBF801b82F54436EC6FC7'
});
Querying Services
Get Service by DID
async function getService(did: string, majorVersion: number) {
const app = await readContract({
contract: registry,
method: 'function getApp(string, uint8) view returns (tuple(...))',
params: [did, majorVersion]
});
return {
did: app.did,
version: `${app.versionHistory[app.versionHistory.length - 1].major}.${app.versionHistory[app.versionHistory.length - 1].minor}.${app.versionHistory[app.versionHistory.length - 1].patch}`,
interfaces: Number(app.interfaces),
dataUrl: app.dataUrl,
dataHash: app.dataHash,
status: Number(app.status),
minter: app.minter
};
}
Get All Active Services
async function listActiveServices(startIndex = 0, pageSize = 20) {
const result = await readContract({
contract: registry,
method: 'function getAppsByStatus(uint8, uint256) view returns (tuple(...
)[], uint256)',
params: [0, startIndex] // 0 = Active status
});
return {
apps: result[0],
nextStartIndex: Number(result[1])
};
}
Search by Traits
async function searchByTraits(traits: string[], matchMode: 'any' | 'all' = 'any') {
// Hash traits
const traitHashes = traits.map(t => ethers.id(t)); // keccak256
// Query registry (example - actual implementation depends on indexer)
const apps = await registry.getAppsByTraits(traitHashes, matchMode);
return apps;
}
// Examples
const gamingApps = await searchByTraits(['gaming']);
const paidMcpServers = await searchByTraits(['api:mcp', 'pay:x402'], 'all');
Fetching Metadata
From DataUrl
async function fetchMetadata(dataUrl: string) {
const response = await fetch(dataUrl);
if (!response.ok) {
throw new Error(`Failed to fetch metadata: ${response.status}`);
}
return await response.json();
}
// Example
const app = await getService('did:web:example.com', 1);
const metadata = await fetchMetadata(app.dataUrl);
console.log(metadata.name); // "My Service"
console.log(metadata.endpoint.url); // "https://api.example.com"
From On-Chain (if available)
const metadataContract = getContract({
client,
chain: omachainTestnet,
address: '0x13aD113D0DE923Ac117c82401e9E1208F09D7F19'
});
const metadataJson = await readContract({
contract: metadataContract,
method: 'function getMetadataJson(string) view returns (string)',
params: [did]
});
const metadata = JSON.parse(metadataJson);
Verification
Verify Data Integrity
async function verifyDataIntegrity(app: App) {
// 1. Fetch metadata
const response = await fetch(app.dataUrl);
const jsonText = await response.text();
// 2. Compute hash
const computedHash = ethers.id(jsonText); // keccak256
// 3. Compare
if (computedHash.toLowerCase() === app.dataHash.toLowerCase()) {
return { valid: true, message: 'Data integrity verified' };
} else {
return {
valid: false,
message: 'Hash mismatch - metadata may have been modified',
expected: app.dataHash,
computed: computedHash
};
}
}
Check Attestations
async function checkAttestations(did: string, ownerAddress: string, dataHash: string) {
const didHash = ethers.id(did);
// Check DID ownership
const ownerVerified = await readContract({
contract: resolver,
method: 'function checkDID(bytes32, address) view returns (bool)',
params: [didHash, ownerAddress]
});
// Check dataHash attestation
const dataVerified = await readContract({
contract: resolver,
method: 'function checkDataHashAttestation(bytes32, bytes32) view returns (bool)',
params: [didHash, dataHash]
});
return {
ownerVerified,
dataVerified,
trustLevel: ownerVerified && dataVerified ? 'HIGH' :
ownerVerified ? 'MEDIUM' : 'LOW'
};
}
Complete Verification Flow
async function verifyServiceCompletely(did: string, majorVersion: number) {
// 1. Get app data
const app = await getService(did, majorVersion);
// 2. Check data integrity
const integrity = await verifyDataIntegrity(app);
// 3. Check attestations
const attestations = await checkAttestations(did, app.minter, app.dataHash);
// 4. Fetch metadata
const metadata = await fetchMetadata(app.dataUrl);
return {
app,
metadata,
integrity,
attestations,
trustScore: calculateTrustScore({ integrity, attestations }),
safeToUse: integrity.valid && attestations.ownerVerified
};
}
function calculateTrustScore({ integrity, attestations }) {
let score = 0;
if (integrity.valid) score += 40;
if (attestations.ownerVerified) score += 30;
if (attestations.dataVerified) score += 30;
return score; // 0-100
}
Use Case Examples
Website Trust Badge
Display OMATrust verification on your site:
// React component
export function TrustBadge({ did }: { did: string }) {
const [verification, setVerification] = useState(null);
useEffect(() => {
verifyServiceCompletely(did, 1).then(setVerification);
}, [did]);
if (!verification) return <div>Checking verification...</div>;
return (
<div className="trust-badge">
{verification.trustScore >= 80 ? (
<>
<CheckCircle className="text-green-500" />
<span>OMATrust Verified ({verification.trustScore}/100)</span>
</>
) : (
<>
<AlertCircle className="text-yellow-500" />
<span>Partially Verified ({verification.trustScore}/100)</span>
</>
)}
</div>
);
}
API Directory
Build a searchable directory of APIs:
async function buildApiDirectory() {
// Get all services with API interface
let allApis = [];
let startIndex = 0;
while (true) {
const result = await listActiveServices(startIndex, 100);
// Filter for API interface (bit 2)
const apis = result.apps.filter(app => app.interfaces & 2);
allApis.push(...apis);
if (result.nextStartIndex === 0) break;
startIndex = result.nextStartIndex;
}
// Fetch metadata for each
const apisWithMetadata = await Promise.all(
allApis.map(async (app) => ({
...app,
metadata: await fetchMetadata(app.dataUrl).catch(() => null)
}))
);
return apisWithMetadata;
}
AI Agent Service Discovery
# Python example for AI agents
import requests
from web3 import Web3
from eth_utils import keccak
def discover_mcp_servers():
"""Find all MCP servers in OMATrust registry"""
# Connect to registry
w3 = Web3(Web3.HTTPProvider('https://rpc.testnet.chain.oma3.org'))
registry = w3.eth.contract(address=REGISTRY_ADDRESS, abi=REGISTRY_ABI)
# Search for api:mcp trait
mcp_trait_hash = keccak(text='api:mcp')
# Get apps with this trait
# (Note: may need indexer for efficient trait search)
apps = registry.functions.getAppsByTraits([mcp_trait_hash], 'any').call()
mcp_servers = []
for app in apps:
# Fetch metadata
metadata = requests.get(app['dataUrl']).json()
if 'mcp' in metadata and 'endpoint' in metadata:
mcp_servers.append({
'did': app['did'],
'name': metadata['name'],
'endpoint': metadata['endpoint']['url'],
'tools': metadata['mcp'].get('tools', []),
'resources': metadata['mcp'].get('resources', [])
})
return mcp_servers
# Use discovered servers
servers = discover_mcp_servers()
for server in servers:
print(f"MCP Server: {server['name']} at {server['endpoint']}")
print(f" Tools: {len(server['tools'])}")
Smart Contract Integration
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IOMA3AppRegistry.sol";
import "./IOMA3Resolver.sol";
contract ServiceVerifier {
IOMAAppRegistry public registry;
IOMA3Resolver public resolver;
constructor(address _registry, address _resolver) {
registry = IOMAAppRegistry(_registry);
resolver = IOMA3Resolver(_resolver);
}
function verifyAndUseService(string memory did, uint8 major) external {
// Get service data
App memory app = registry.getApp(did, major);
// Verify ownership
bytes32 didHash = keccak256(bytes(did));
require(
resolver.checkDID(didHash, app.minter),
"Service owner not verified"
);
// Verify status
require(app.status == 0, "Service not active");
// ✅ Service verified - safe to use
useService(app.dataUrl);
}
function useService(string memory endpoint) internal {
// Your logic here
}
}
Caching Strategies
Browser LocalStorage
interface CachedService {
app: App;
metadata: any;
verification: VerificationResult;
cachedAt: number;
}
const CACHE_TTL = 3600000; // 1 hour
async function getServiceCached(did: string, major: number): Promise<CachedService> {
const cacheKey = `omatrust:${did}:${major}`;
const cached = localStorage.getItem(cacheKey);
if (cached) {
const data = JSON.parse(cached);
if (Date.now() - data.cachedAt < CACHE_TTL) {
return data;
}
}
// Fetch fresh data
const service = await verifyServiceCompletely(did, major);
const toCache = { ...service, cachedAt: Date.now() };
localStorage.setItem(cacheKey, JSON.stringify(toCache));
return toCache;
}
Server-Side Caching
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function getServiceCached(did: string, major: number) {
const cacheKey = `service:${did}:${major}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const service = await verifyServiceCompletely(did, major);
await redis.setex(cacheKey, 3600, JSON.stringify(service)); // 1 hour TTL
return service;
}
Rate Limiting
Client-Side Throttling
import pLimit from 'p-limit';
const limit = pLimit(5); // Max 5 concurrent requests
async function fetchServicesWithRateLimit(dids: string[]) {
return Promise.all(
dids.map(did =>
limit(() => getService(did, 1))
)
);
}
Batching Requests
async function getMultipleServices(queries: Array<{did: string, major: number}>) {
// Batch RPC calls using multicall pattern
const calls = queries.map(q => ({
contract: registry,
method: 'function getApp(string, uint8) view returns (...)',
params: [q.did, q.major]
}));
// Execute in parallel (if RPC supports it)
const results = await Promise.all(
calls.map(call => readContract(call))
);
return results;
}
Error Handling
Graceful Degradation
async function getServiceSafely(did: string, major: number) {
try {
const app = await getService(did, major);
// Try to fetch metadata
try {
const metadata = await fetchMetadata(app.dataUrl);
return { app, metadata, error: null };
} catch (metadataError) {
// Metadata failed but we have on-chain data
return {
app,
metadata: null,
error: 'Metadata unavailable - using on-chain data only'
};
}
} catch (error) {
// Service not found or RPC error
return {
app: null,
metadata: null,
error: error.message
};
}
}
Retry Logic
async function fetchWithRetry<T>(
fn: () => Promise<T>,
retries = 3,
delay = 1000
): Promise<T> {
try {
return await fn();
} catch (error) {
if (retries === 0) throw error;
await new Promise(resolve => setTimeout(resolve, delay));
return fetchWithRetry(fn, retries - 1, delay * 2); // Exponential backoff
}
}
// Usage
const app = await fetchWithRetry(() => getService(did, major));