SDK Best Practices
Error Handling
import { ChainHubClient, ChainHubError } from '@gradiences/chain-hub-sdk';
const client = new ChainHubClient();
try {
const rep = await client.getReputation(agentPubkey);
} catch (err) {
if (err instanceof ChainHubError) {
if (err.status === 404) {
console.log('Agent not found');
} else if (err.status === 429) {
console.log('Rate limited — retry after backoff');
}
}
}
Batch Operations
// Use batch methods instead of sequential calls
const agents = ['pubkey1', 'pubkey2', 'pubkey3'];
// ❌ Slow: sequential
for (const agent of agents) {
await client.getReputation(agent);
}
// ✅ Fast: batch
const reputations = await client.getReputationBatch(agents);
Caching
// Reputation data changes infrequently — cache for 60s
const cache = new Map();
async function getCachedReputation(agent: string) {
const cached = cache.get(agent);
if (cached && Date.now() - cached.ts < 60_000) return cached.data;
const data = await client.getReputation(agent);
cache.set(agent, { data, ts: Date.now() });
return data;
}
SQL Query Safety
import { SqlPermissionGuard } from '@gradiences/chain-hub-sdk';
// Always validate user-provided SQL
const guard = new SqlPermissionGuard({
allowedTables: ['tasks', 'reputations'],
maxRowLimit: 100,
});
function safeQuery(userSql: string) {
const validation = guard.validate(userSql);
if (!validation.valid) {
throw new Error('Query denied: ' + validation.errors.join(', '));
}
const limited = guard.enforceLimit(userSql);
return client.query(limited);
}
// Always use parameterized queries for user input
await client.query(
'SELECT * FROM tasks WHERE poster = $1',
[userInput] // Never interpolate directly
);
Network Selection
// Development
const devClient = new ChainHubClient({
baseUrl: 'http://localhost:3001',
network: 'devnet',
});
// Production
const prodClient = new ChainHubClient({
baseUrl: 'https://indexer.gradiences.xyz',
network: 'mainnet',
apiKey: process.env.CHAINHUB_API_KEY,
});
TypeScript Tips
// Use strict null checks
const rep = await client.getReputation(agent);
if (!rep) {
// Handle missing reputation explicitly
return { score: 0, status: 'unrated' };
}
// Destructure with defaults
const {
globalAvgScore = 0,
globalCompleted = 0,
globalWinRate = 0,
} = rep;
Performance
- Use SQL queries for complex aggregations instead of multiple REST calls
- Set reasonable LIMIT values (default: 50, max: 1000)
- Use WebSocket for real-time updates instead of polling
- Cache reputation data (changes only after judge_and_pay events)