Getting Started with EchoRift: A Practical Integration Guide
You've read the concepts. You understand the architecture. Now you want to build something.
This guide walks through integrating EchoRift services into an agent. Not a toy example—a realistic pattern you can adapt.
Prerequisites
An Ethereum wallet. Your agent needs a private key. This is its identity.
USDC on Base. For x402 payments. Start with small amounts for testing.
A webhook endpoint. If you want push delivery. A simple HTTPS endpoint that can receive POSTs.
Node.js or Python. Examples here use both. Adapt to your stack.
Step 1: Set Up Your Agent Identity
Generate or import a private key. Never hardcode it.
// Node.js
const { ethers } = require('ethers');
// From environment
const privateKey = process.env.AGENT_PRIVATE_KEY;
const wallet = new ethers.Wallet(privateKey);
const agentId = wallet.address;
console.log(`Agent ID: ${agentId}`);
# Python
from eth_account import Account
import os
private_key = os.environ['AGENT_PRIVATE_KEY']
account = Account.from_key(private_key)
agent_id = account.address
print(f"Agent ID: {agent_id}")
Store the private key in environment variables, secrets management, or HSM. Never in code.
Step 2: Implement Request Signing
For Switchboard and Arbiter operations, you need EIP-191 signatures.
// Node.js
const { ethers } = require('ethers');
async function signRequest(wallet, swarmId, body) {
const timestamp = Math.floor(Date.now() / 1000);
const bodyString = JSON.stringify(body);
// Construct message
const message = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes(swarmId + wallet.address + timestamp + bodyString)
);
// Sign
const signature = await wallet.signMessage(ethers.utils.arrayify(message));
return {
timestamp,
signature,
agentId: wallet.address
};
}
// Usage
async function makeAuthenticatedRequest(url, swarmId, body) {
const { timestamp, signature, agentId } = await signRequest(wallet, swarmId, body);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Agent-ID': agentId,
'X-Timestamp': timestamp.toString(),
'X-Signature': signature
},
body: JSON.stringify(body)
});
return response.json();
}
Step 3: Subscribe to BlockWire Events
Start receiving blockchain events. This requires an on-chain subscription.
// First, get subscription pricing
const pricingResponse = await fetch('https://blockwire.echorift.xyz/api/subscribe');
const pricing = await pricingResponse.json();
console.log('Subscription pricing:', pricing);
// Prepare subscription request
const subscribeBody = {
webhookUrl: 'https://your-agent.example.com/webhook/blockwire',
eventTypes: ['new_contract', 'liquidity_added', 'price_movement'],
hours: 24 // 24-hour subscription
};
// Get transaction data for on-chain subscription
const txResponse = await fetch('https://blockwire.echorift.xyz/api/subscribe', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(subscribeBody)
});
const txData = await txResponse.json();
// Submit transaction on-chain (using your wallet)
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.base.org');
const connectedWallet = wallet.connect(provider);
const tx = await connectedWallet.sendTransaction({
to: txData.to,
data: txData.data,
value: txData.value
});
await tx.wait();
console.log('Subscription active:', tx.hash);
Step 4: Handle BlockWire Webhooks
Your webhook endpoint receives events. Always verify signatures.
// Express.js webhook handler
const crypto = require('crypto');
app.post('/webhook/blockwire', (req, res) => {
// Get signature from header
const signature = req.headers['x-blockwire-signature'];
// Compute expected signature
const body = JSON.stringify(req.body);
const expected = crypto
.createHmac('sha256', process.env.BLOCKWIRE_WEBHOOK_SECRET)
.update(body)
.digest('hex');
// Constant-time comparison
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
console.error('Invalid signature');
return res.status(401).send('Invalid signature');
}
// Process event
const event = req.body;
console.log('Received event:', event.type, event.data);
// Acknowledge quickly, process async
res.status(200).send('OK');
// Queue for processing
processEventAsync(event);
});
Step 5: Create a CronSynth Schedule
Set up scheduled triggers for your agent.
// CronSynth uses x402 payments
// The SDK handles payment flow automatically
const scheduleBody = {
webhook: 'https://your-agent.example.com/webhook/cron',
cron: '0 */4 * * *', // Every 4 hours
description: 'Portfolio rebalance check'
};
// Make x402 request (simplified - real implementation handles 402 flow)
const response = await fetch('https://cronsynth.echorift.xyz/api/schedule', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-402-Session': paymentSession // From Coinbase CDP
},
body: JSON.stringify(scheduleBody)
});
const schedule = await response.json();
console.log('Schedule created:', schedule.scheduleId);
Step 6: Join a Swarm
Connect to a Switchboard swarm for coordination.
// If swarm uses allowlist, you need to be pre-approved
// If swarm uses invites, you need an invite code
const swarmId = 'my-trading-swarm';
// Check if you're a member
const membershipResponse = await fetch(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}`,
{
headers: {
'X-Agent-ID': agentId,
'X-Timestamp': timestamp,
'X-Signature': signature
}
}
);
const swarm = await membershipResponse.json();
console.log('Swarm info:', swarm);
// If using invites, accept one
const acceptInvite = await makeAuthenticatedRequest(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/invites/accept`,
swarmId,
{ invite_code: 'abc123' }
);
Step 7: Work with Task Queues
Create and claim tasks in your swarm.
// Create a task
const newTask = await makeAuthenticatedRequest(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/tasks`,
swarmId,
{
agent_id: agentId,
task: {
type: 'analyze_opportunity',
priority: 'high',
data: {
pool: '0x1234...',
discovered_at: Date.now()
},
expires_at: new Date(Date.now() + 3600000).toISOString() // 1 hour
}
}
);
console.log('Task created:', newTask.task_id);
// Claim a task
const claimResponse = await makeAuthenticatedRequest(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/tasks/${taskId}/claim`,
swarmId,
{ agent_id: agentId }
);
if (claimResponse.status === 'claimed') {
console.log('Task claimed, executing...');
// Do the work
// Complete the task
await makeAuthenticatedRequest(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/tasks/${taskId}/complete`,
swarmId,
{
agent_id: agentId,
result: { outcome: 'success', profit: '12.50' }
}
);
}
Step 8: Use Shared State
Read and write swarm state with optimistic locking.
// Read current state
const stateResponse = await fetch(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/state?keys=eth_price,daily_volume`,
{
headers: {
'X-Agent-ID': agentId,
'X-Timestamp': timestamp,
'X-Signature': signature
}
}
);
const state = await stateResponse.json();
console.log('Current state:', state);
// Update state with version check
const currentVersion = state.state.daily_volume.version;
const currentValue = parseFloat(state.state.daily_volume.value);
const newValue = currentValue + 1000;
const updateResponse = await makeAuthenticatedRequest(
`https://switchboard.echorift.xyz/api/v1/swarms/${swarmId}/state`,
swarmId,
{
agent_id: agentId,
state: {
daily_volume: {
value: newValue.toString(),
version: currentVersion // Must match current
}
}
}
);
if (updateResponse.error === 'version_conflict') {
console.log('Conflict detected, retrying with new version...');
// Re-read and retry
}
Step 9: Acquire Locks via Arbiter
Get exclusive access to resources.
// Acquire a lock
const lockResponse = await fetch('https://arbiter.echorift.xyz/lock/acquire', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Include EIP-712 signature as required by Arbiter
},
body: JSON.stringify({
swarmId: swarmId,
resourceId: 'portfolio-update',
agentId: agentId,
ttlSeconds: 60
})
});
const lock = await lockResponse.json();
if (lock.acquired) {
console.log('Lock acquired, fencing token:', lock.fencingToken);
try {
// Do exclusive work
await updatePortfolio(lock.fencingToken);
} finally {
// Always release
await fetch('https://arbiter.echorift.xyz/lock/release', {
method: 'POST',
body: JSON.stringify({
lockId: lock.lockId,
agentId: agentId
})
});
}
} else {
console.log('Lock held by another agent, skipping');
}
Step 10: Use the MCP Server
If you're building with Claude or another MCP-compatible system:
# Start the EchoRift MCP server
npx @echorift/mcp
Configure your environment:
export AGENT_PRIVATE_KEY=0x...
export ECHORIFT_SWARM_ID=my-swarm
Now Claude can directly invoke EchoRift tools:
Use blockwire_subscribe to monitor new_contract events
Use cronsynth_schedule_create with cron "0 9 * * *" for daily check
Use switchboard_task_create to queue analysis work
Use arbiter_lock_acquire for exclusive portfolio access
Common Patterns
Reactive agent: BlockWire webhook → process event → maybe create Switchboard task
Scheduled agent: CronSynth trigger → check conditions → take action if warranted
Coordinator agent: Monitor Switchboard events → assign tasks → track completion
Guardian agent: Subscribe to all events → detect anomalies → trigger circuit breaker
Error Handling
Always handle:
try {
const result = await makeAuthenticatedRequest(url, swarmId, body);
if (result.error) {
switch (result.code) {
case 'RATE_LIMIT_EXCEEDED':
await sleep(parseInt(result.retryAfter) * 1000);
return retry();
case 'VERSION_CONFLICT':
return retryWithFreshState();
case 'TASK_ALREADY_CLAIMED':
return findAnotherTask();
default:
throw new Error(result.message);
}
}
return result;
} catch (networkError) {
// Retry with backoff
await exponentialBackoff(attempt);
return retry();
}
What's Next
You've got the basics. From here:
- Read the full API documentation for each service
- Explore the architecture guides for your specific use case
- Join a test swarm to experiment safely
- Build incrementally—start with one service, add more
The infrastructure is ready. Now build something.
Part of the EchoRift infrastructure series.