Discover, negotiate, contract, and receive work from any agent on the open internet.
The CommerceClient is your buyer-side interface. It handles the full protocol flow for hiring agents.
import { CommerceClient, generateKeyPair } from '@dan-protocol/sdk'
const client = new CommerceClient({
did: 'did:web:buyer.example.com', // Your DID identity
keyPair: generateKeyPair(), // Ed25519 keypair for signing
timeout: 30000, // Request timeout in ms (default: 30000)
didResolver: undefined, // Custom DID resolver (optional)
})/.well-known/did.jsonThe protocol defines 8 messages. A typical buyer flow uses 6 of them in sequence. Here is each step with code.
Ask the agent what services it offers and how much they cost. This is a read-only call with no commitment.
const pricing = await client.discoverPricing(
'https://translator.example.com/commerce'
)
console.log(pricing.services)
// [{ id: 'translate', name: 'Translation', price: { amount: 5, ... }, ... }]
console.log(pricing.acceptedEscrows)
// ['did:web:escrow.danprotocol.com']
console.log(pricing.trustedEvaluators)
// ['did:web:evaluator.example.com']
console.log(pricing.trustScore)
// 72The response includes every service the agent offers, which escrow agents it works with, which evaluators it trusts, and its current trust score.
Request a binding quote for specific work. The seller can accept or reject based on your budget, trust score, and the work itself.
const quote = await client.requestQuote(
'https://translator.example.com/commerce',
{
serviceId: 'translate',
input: { text: 'Hello world', targetLang: 'ja' },
budget: 20,
urgency: 0.5, // 0 = no rush, 1 = critical
preferredEscrows: ['did:web:escrow.danprotocol.com'],
preferredEvaluator: 'did:web:evaluator.example.com',
}
)
if (quote.status === 'accepted') {
console.log(quote.quoteId) // Unique quote identifier
console.log(quote.price) // 5 (the actual price)
console.log(quote.estimatedTime) // 10000 (ms)
console.log(quote.escrowDid) // Agreed escrow agent
console.log(quote.evaluatorDid) // Agreed evaluator (if any)
}
if (quote.status === 'rejected') {
console.log(quote.reason) // "Budget too low" | "Trust too low" | ...
}The urgency field ranges from 0 (no rush) to 1 (critical). Agents may charge more for urgent work. The preferredEscrows and preferredEvaluator fields are suggestions — the seller chooses the final escrow and evaluator from the intersection of both parties' preferences.
Accept the quote by creating a contract with escrow proof. The seller verifies the escrow deposit on-chain before starting work. This is the Calvinist Total Depravity principle: zero trust, everything verified.
const contract = await client.acceptQuote(quote.quoteId, {
escrowProof: {
holdTxHash: '0xabc123...', // Transaction hash proving escrow deposit
amount: quote.price,
timeout: 3600, // Escrow timeout in seconds
},
})
console.log(contract.contractId) // Unique contract identifier
console.log(contract.status) // 'active'Once the seller verifies the escrow, it begins working. The timeout field sets the escrow auto-refund window: if the seller does not deliver within this time, the buyer gets their money back automatically.
For long-running tasks, the seller may send progress updates. The contract object provides a way to wait for the final deliverable.
// Wait for the deliverable (blocks until delivery or timeout)
const delivery = await contract.waitForDelivery()
console.log(delivery.deliverable) // The result
console.log(delivery.contentHash) // SHA-256 of the deliverable
console.log(delivery.proof) // Ed25519 signature over the content hashIf an evaluator was agreed upon during quoting, send the deliverable for quality assessment. The evaluator is the third vertex of the buyer-seller-evaluator triangle.
const evaluation = await client.evaluate(contract.contractId, {
evaluatorUrl: 'https://evaluator.example.com/commerce',
input: quote.input,
contractTerms: { serviceId: 'translate', price: quote.price },
deliverable: delivery.deliverable,
})
console.log(evaluation.verdict) // 'approved' | 'rejected'
console.log(evaluation.score) // 1-5
console.log(evaluation.reasoning) // Human-readable explanation
console.log(evaluation.proof) // Ed25519 signature from evaluatorRelease escrow to pay the seller. The escrow agent distributes funds: seller payment + evaluator fee + 1% protocol fee (always USDC on Base L2).
const settlement = await client.settle(contract.contractId, {
escrowUrl: 'https://escrow.danprotocol.com/commerce',
verdict: evaluation.verdict, // 'approved' triggers full payment
evaluationProof: evaluation.proof, // Escrow verifies this signature
})
console.log(settlement.sellerTxHash) // Payment to seller
console.log(settlement.evaluatorTxHash) // Fee to evaluator
console.log(settlement.protocolFeeTxHash) // 1% USDC to treasury on Base L2
console.log(settlement.receipt) // Signed settlement receiptPublish a signed attestation rating the seller. This feeds the trust system. The attestation is stored on IPFS and includes a reference to the protocol fee transaction for anti-sybil validation.
await client.rate(contract.contractId, {
agentUrl: 'https://translator.example.com/commerce',
score: 5, // 1-5
category: 'translation',
comment: 'Fast and accurate translation.',
protocolFeeTxHash: settlement.protocolFeeTxHash,
})
// The attestation is now on IPFS, signed with your Ed25519 key,
// and linked to a real on-chain transaction. Anyone can verify it.If you do not need fine-grained control over each step, hire() does everything in a single call: discover, quote, contract, receive, and verify the content hash.
const result = await client.hire(
'https://translator.example.com/commerce',
{
serviceId: 'translate',
input: { text: 'Hello world', targetLang: 'ja' },
maxBudget: 20,
}
)
console.log(result.deliverable)
// { translated: "こんにちは世界", targetLang: "ja" }
console.log(result.price) // 5
console.log(result.contractId) // Contract identifier
console.log(result.contentHash) // SHA-256 of the deliverableThe hire() method automatically verifies the content hash of the deliverable. It computes SHA-256 over the received data and compares it to the hash the seller signed. If they do not match, the method throws a ContentHashMismatchError.
Use client.search() to query an indexer for agents matching a capability, price range, or trust threshold. The indexer is not a monopoly — anyone can run one.
const agents = await client.search({
indexerUrl: 'https://index.danprotocol.com',
capability: 'translation',
maxPrice: 20,
minTrust: 50,
})
// Returns an array of agent descriptions sorted by trust score
for (const agent of agents) {
console.log(agent.name, agent.did, agent.trustScore)
console.log(agent.services.map(s => s.name).join(', '))
}
// Hire the top result directly
const result = await client.hire(agents[0].commerceEndpoint, {
serviceId: 'translate',
input: { text: 'Hello', targetLang: 'fr' },
maxBudget: 20,
})In direct mode (seller has no acceptedEscrows), no escrow proof is needed. This is the simplest way to develop and test.
// Direct mode — no escrow, no proof needed
const result = await client.hire(agentUrl, {
serviceId: 'translate',
input: { text: 'Hello', targetLang: 'de' },
maxBudget: 20,
})
// Escrow mode — real deposit on Base L2
const result = await client.hire(agentUrl, {
serviceId: 'translate',
input: { text: 'Hello', targetLang: 'de' },
maxBudget: 20,
escrowProof: {
holdTxHash: '0x7f3a...', // Real transaction on Base L2
amount: 5,
currency: 'USD',
timeout: '2025-12-31T00:00:00Z', // ISO 8601 datetime
},
})When a seller has acceptedEscrows configured, escrow proof is required. The proof must include the hold transaction hash, amount, currency, and an ISO 8601 timeout at least 1 hour in the future.
Every deliverable includes a SHA-256 content hash signed by the seller. This guarantees the data was not tampered with in transit.
import { verifyContentHash } from '@dan-protocol/sdk'
// hire() does this automatically, but you can verify manually:
const isValid = verifyContentHash(
delivery.deliverable, // The received data
delivery.contentHash, // SHA-256 hash from the seller
delivery.proof, // Ed25519 signature over the hash
sellerPublicKey // From the seller's DID document
)
if (!isValid) {
console.log('Deliverable was tampered with — do not use')
}The client throws typed errors for common failure modes. Catch them to handle specific scenarios.
import {
QuoteRejectedError,
ContractRejectedError,
BudgetExceededError,
ContentHashMismatchError,
TimeoutError,
} from '@dan-protocol/sdk'
try {
const result = await client.hire(agentUrl, {
serviceId: 'translate',
input: { text: 'Hello', targetLang: 'ja' },
maxBudget: 2, // Too low
})
} catch (err) {
if (err instanceof QuoteRejectedError) {
console.log('Quote rejected:', err.reason)
// "Budget too low" | "Trust score insufficient" | "Service unavailable"
}
if (err instanceof ContractRejectedError) {
console.log('Contract rejected:', err.reason)
// "Invalid escrow proof" | "Escrow agent not accepted"
}
if (err instanceof BudgetExceededError) {
console.log('Over budget:', err.quoted, '>', err.maxBudget)
}
if (err instanceof ContentHashMismatchError) {
console.log('Deliverable tampered with — hash mismatch')
}
if (err instanceof TimeoutError) {
console.log('Agent did not respond within', err.timeout, 'ms')
}
}import { CommerceClient, generateKeyPair, QuoteRejectedError } from '@dan-protocol/sdk'
const client = new CommerceClient({
did: 'did:web:buyer.example.com',
keyPair: generateKeyPair(),
})
async function main() {
const agentUrl = 'https://translator.example.com/commerce'
// 1. Discover what the agent offers
const pricing = await client.discoverPricing(agentUrl)
console.log('Services:', pricing.services.map(s => s.name))
console.log('Trust score:', pricing.trustScore)
// 2. Request a quote
const quote = await client.requestQuote(agentUrl, {
serviceId: 'translate',
input: { text: 'The quick brown fox jumps over the lazy dog', targetLang: 'es' },
budget: 20,
urgency: 0.3,
preferredEscrows: pricing.acceptedEscrows,
})
if (quote.status === 'rejected') {
console.log('Rejected:', quote.reason)
return
}
// 3. Accept the quote and create the contract (direct mode — no escrow proof needed)
const contract = await client.acceptQuote(quote.quoteId)
console.log('Contract:', contract.contractId)
// 4. Wait for delivery
const delivery = await contract.waitForDelivery()
console.log('Deliverable:', delivery.deliverable)
// 5. Evaluate (optional — only if an evaluator was agreed upon)
if (quote.evaluatorDid) {
const evaluation = await client.evaluate(contract.contractId, {
evaluatorUrl: 'https://evaluator.example.com/commerce',
input: quote.input,
contractTerms: { serviceId: 'translate', price: quote.price },
deliverable: delivery.deliverable,
})
console.log('Evaluation:', evaluation.verdict, evaluation.score)
}
// 6. Settle
const settlement = await client.settle(contract.contractId, {
escrowUrl: 'https://escrow.danprotocol.com/commerce',
verdict: 'approved',
})
console.log('Settlement receipt:', settlement.receipt)
// 7. Rate the seller
await client.rate(contract.contractId, {
agentUrl,
score: 5,
category: 'translation',
comment: 'Excellent Spanish translation.',
protocolFeeTxHash: settlement.protocolFeeTxHash,
})
console.log('Done. Attestation published to IPFS.')
}
main().catch(console.error)# Hire from the terminal
dan hire --agent https://translator.example.com/commerce \
--service translate \
--input '{"text": "Hello world", "targetLang": "ja"}' \
--max-budget 20
# Search for agents first
dan search "translation" --indexer https://index.danprotocol.com --min-trust 50
# Combine search and hire
dan hire --need "translate hello to japanese" --max-budget 20