Repository navigation
Integration Patterns and Examples
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
This document explains how to integrate the Sigvara SDK into web applications, backend services, and smart contract workflows. It focuses on practical patterns: agent registration flows, reputation checks in user interfaces, gate-based authorization, real-time updates, event handling, and state synchronization. It also provides performance guidance for batching, caching, and connection pooling, plus troubleshooting for common integration issues such as network connectivity problems, signature verification failures, and gas estimation errors.
The SDK exposes agent identity, challenge-response authentication, reputation reads, stake operations, and a gate that combines proof and standing into a single admission decision. The example MCP server demonstrates a production-style gated tool flow using the SDK’s gate.
SDK behaviour on this page is that of 1.0.0-alpha.16, published to npm on 5 November 2026. New since 1.0.0-alpha.15: SigvaraTrustCheck, which asks the gate's question about a bare address, with no proof; sigvaraX402Fetch, which checks the seller before an x402 payment is signed; sigvaraTrustTool and requireTrustedAgent, for LangChain and its like; minBond on SigvaraGate, with its below_bond refusal; challengeSecret on SigvaraGate, which accepts only challenges the gate issued, with generateChallenge taking an optional fourth argument that makes the nonce; a spent nonce recorded for one agent and one audience, as a hash a NonceStore is handed where it was handed the nonce; and SigvaraVerifier keeping to 20 calls in a batch. New since 1.0.0-alpha.14: sigvara-mcp, a read-only MCP trust server for an agent's MCP host (see MCP Gated Tools), the evidence-root functions evidenceLeaf, evidenceRoot and matchEvidenceRoot, and reads on SigvaraVerifier for a trust check, verifyEvidence and getEvidenceRoot among them. New since 1.0.0-alpha.13: profile: 'production' on SigvaraGate, which makes the constructor refuse to build the gate until instances and maxScoreAgeSeconds are both set. New since 1.0.0-alpha.12: atomicNonceStore; the gate's instances setting, with which a gate set to 'multiple' refuses to start without a nonce store with an atomic consume(); and signPaymentOutcome and verifyPaymentOutcome, which sign and check a payer's statement of whether one payment's work was delivered. New since 1.0.0-alpha.11: the agent's signChallenge is removed, so a call does not compile in TypeScript and throws in JavaScript, and signChallengeFor is the way to sign. New since 1.0.0-alpha.10: verifySignature requires the expected audience (see the changelog). These items are new since 1.0.0-alpha.9 (listed in the changelog): signChallengeFor, the 30 second read timeout, the parallel status and score read, the gate also checking the bond held now, the optional maxScoreAgeSeconds limit and its stale_score refusal, the refusal of a challenge dated in the future, the checks on a gate's threshold, ttlSeconds and maxScoreAgeSeconds, and the agent keeping its secret key in a private field.
At a high level, the repository contains:
- A TypeScript SDK under packages/sdk with modules for agents, verifiers, gates, challenges, staking, types, and ABIs.
- An MCP-gated tool example showing end-to-end gating over JSON-RPC.
- Documentation including quickstart and AI framework integration guides.
- Oracle and site utilities used by the broader ecosystem.
graph TB
subgraph "SDK"
IDX["index.ts"]
AGT["agent.ts"]
VER["verifier.ts"]
GAT["gate.ts"]
CHA["challenge.ts"]
STK["stake.ts"]
TYP["types.ts"]
ABI["abis.ts"]
end
subgraph "Examples"
MCP["mcp-gated-tool/server.mjs"]
end
subgraph "Docs"
QS["docs/quickstart.md"]
SDK_README["packages/sdk/README.md"]
end
IDX --> AGT
IDX --> VER
IDX --> GAT
IDX --> CHA
IDX --> STK
IDX --> TYP
IDX --> ABI
MCP --> GAT
MCP --> VER
QS --> IDX
SDK_README --> IDX
Diagram sources
- packages/sdk/src/index.ts:1-72
- examples/mcp-gated-tool/server.mjs:46-58
- docs/quickstart.md
- packages/sdk/README.md
Section sources
- packages/sdk/src/index.ts:1-72
- packages/sdk/README.md
- docs/quickstart.md
- examples/mcp-gated-tool/server.mjs:1-182
- SigvaraAgent: Generates Ed25519 key material, derives DID and didHash, and signs challenges.
- SigvaraVerifier: Reads identity, reputation, and stake from contracts; verifies signatures against on-chain public keys.
- SigvaraGate: Issues challenges, enforces replay protection via nonces, and admits or refuses based on proof, status, and threshold.
- SigvaraTrustCheck: Asks the gate's question about a bare address, with no proof, for deciding whether to pay or hire it.
- Challenge utilities: Generate, parse, sign, verify, and expire-check challenge payloads (v1 and v2).
- Staking helpers: Approve and deposit stake in one call.
- Types and ABIs: Shared interfaces and minimal contract ABIs for Identity, Reputation, Staking, and ERC-20.
Key responsibilities:
- Agent side: create identity, sign challenges, expose DID.
- Verifier side: read chain state, validate signatures, check reputation thresholds.
- Gate side: combine proof and standing into a single admission decision with clear refusal reasons.
- Paying side: check the address the money goes to before signing anything.
Section sources
- packages/sdk/src/agent.ts
- packages/sdk/src/verifier.ts
- packages/sdk/src/gate.ts
- packages/sdk/src/challenge.ts
- packages/sdk/src/stake.ts:1-39
- packages/sdk/src/types.ts
- packages/sdk/src/abis.ts:1-42
The typical integration involves three roles:
- Operator: registers an agent and posts a bond to activate it.
- Agent: proves control of its DID by signing challenges.
- Verifier/Gate: validates proofs, checks on-chain status and reputation, and decides whether to admit work.
sequenceDiagram
participant Client as "Client / UI"
participant Server as "Backend Service"
participant Gate as "SigvaraGate"
participant Verifier as "SigvaraVerifier"
participant Chain as "On-chain Contracts"
Client->>Server : Request gated action
Server->>Gate : challenge(did)
Gate-->>Server : {payload, nonce, timestamp, expiresAt}
Server-->>Client : Return challenge payload
Client->>Server : submitSignature(did, payload, signature)
Server->>Gate : admit(did, challenge, signature)
Gate->>Verifier : verifySignature(...)
Verifier->>Chain : getIdentity(...)
Chain-->>Verifier : identity data
Verifier-->>Gate : verified?
alt Verified
Gate->>Verifier : isActive(...) and hasMinimumStake(...)
Verifier->>Chain : isActive(...), hasMinimumStake(...)
Chain-->>Verifier : booleans
Verifier-->>Gate : active and bonded?
alt Active and bonded
Gate->>Verifier : getTotalScore(...)
Verifier->>Chain : getTotalScore(...)
Chain-->>Verifier : score
Verifier-->>Gate : score
alt Score >= threshold
Gate-->>Server : admit(true, score)
Server-->>Client : Action allowed
else Below threshold
Gate-->>Server : admit(false, reason=below_threshold, score)
Server-->>Client : Refused with reason
end
else Not active, or below the minimum stake
Gate-->>Server : admit(false, reason=not_active)
Server-->>Client : Refused with reason
end
else Not verified
Gate-->>Server : admit(false, reason=bad_proof)
Server-->>Client : Refused with reason
end
Diagram sources
- packages/sdk/src/gate.ts:328-438
- packages/sdk/src/verifier.ts:234-314
- packages/sdk/src/verifier.ts:75-149
- examples/mcp-gated-tool/server.mjs:124-157
Operator-side flow:
- Generate an Ed25519 keypair and derive the agent’s DID and didHash.
- Register the agent on-chain with a proof that the agent address controls itself.
- Deposit stake to move the agent from PendingBond to Active.
flowchart TD
Start(["Start"]) --> Gen["Generate Ed25519 keypair<br/>Compute DID and didHash"]
Gen --> Reg["Call registerAgent(signer, agentAddress, pubKeyBytes32, identityAddress, proof)"]
Reg --> WaitReg{"Registration receipt?"}
WaitReg --> |No| ErrorReg["Handle error"]
WaitReg --> |Yes| Stake["Call depositStake(signer, didHash, amount, stakingAddress)"]
Stake --> WaitStake{"Deposit receipt?"}
WaitStake --> |No| ErrorStake["Handle error"]
WaitStake --> |Yes| Active["Agent becomes Active once the deposit reaches minimumStake"]
ErrorReg --> End(["End"])
ErrorStake --> End
Active --> End
Diagram sources
Section sources
Recommended pattern:
- On the client, display a “Connect” step that obtains the agent’s DID and shows current reputation.
- For gated actions, request a challenge from your backend, have the agent sign it, and send back the signature.
- Use the backend’s gate to decide admission and return a clear reason if refused.
Implementation notes:
- Keep RPC calls on the backend to avoid exposing private keys.
- Cache reputation reads per session to reduce RPC load.
- Show human-readable refusal reasons to users (e.g., “Your agent is not active” vs “Reputation below threshold”).
No code snippets are included here; see the MCP example for a complete flow and the SDK README for API usage.
Section sources
Pattern:
- Create a single SigvaraGate instance per service process with a stable audience and threshold.
- Expose two endpoints:
- GET /challenge?did=... returns a fresh challenge payload.
- POST /admit sends did, challenge, and signature to gate.admit().
- Persist outstanding challenges per session, each under its payload, and enforce strict matching between issued and submitted challenges.
Security considerations:
- Never trust a challenge payload supplied by the caller; always compare against the one you issued.
challengeSecreton the gate does this with no shared state: it accepts only challenges it issued, and refuses any other withbad_proofbefore any read. - Use a shared NonceStore (e.g., Redis) when running multiple instances to prevent cross-process replays, and set
instances: 'multiple': the gate then refuses to start unless the store has aconsume, which must be atomic.atomicNonceStorebuilds such a store from a RedisSET NX. In production,profile: 'production'makes the gate refuse to start untilinstancesandmaxScoreAgeSecondsare set.
Section sources
- examples/mcp-gated-tool/server.mjs:46-58
- examples/mcp-gated-tool/server.mjs:124-157
- packages/sdk/src/gate.ts:164-300
- Identity: registerAgent requires a signed digest binding agent address, operator, and public key bytes32.
- Reputation:
meetsThresholdcompares the spendable score and nothing else, so a Suspended agent passes it, and a threshold of 0 is met by an address that was never registered. Use getTotalScore for local gating decisions andSigvaraReader.isTrustedfor an on-chain gate (see below). - Staking: depositStake handles the ERC-20 approval and the deposit in one helper call. It sends the approval only when the allowance is short, so a call is one or two transactions, not one atomic step.
On-chain consumers: a contract that must decide whether to deal with an agent can call SigvaraReader, a stateless, non-upgradeable contract that reads the three registries through the interfaces in src/interfaces/ISigvara.sol.
-
isTrusted(agent, minScore)is true when the agent is Active, the bond it holds now meets the staking contract's current minimum, and its spendable score is at leastminScore. It takes the agent's address, so there is no didHash to derive. -
isTrusted(agent, minScore, minBond)also requires a bond held now of at leastminBond, in the bond token's units, for a consumer with more at stake than the minimum covers. It can only ask for more: aminBondunder the current minimum still needs the minimum, and an amount queued for withdrawal does not count (src/SigvaraReader.sol:82-85, src/interfaces/ISigvara.sol:180-184).SigvaraGatetakes the same floor asminBond, from 1.0.0-alpha.16. -
isTrustedFresh(agent, minScore, maxAgeSeconds)isisTrustedplus a limit on how long ago the score was proposed, because nothing on chain ages a score. It counts from the proposal and not the finalize, so finalizing a score late does not make it read as new. Choose a limit above two of your oracle's proposals apart: a score reaches about 15 hours old on Arc testnet, where a day is a sensible limit, and about two days on mainnet's daily epoch, where three days tolerates one late epoch (docs/integration.md:103-124). AmaxAgeSecondsof 0 can never be met by a finalized score, because a window lies between a proposal and its finalize.trust(agent)returns the whole standing as one struct, and itsscoreUpdatedAtis when the score now in force was proposed. -
SigvaraGatechecks Active status, the bond held now against the current minimum, and the spendable score, asisTrusteddoes, and has an optional limit on the score's age,maxScoreAgeSeconds, asisTrustedFreshdoes, and an optional bond floor,minBond, asisTrusted(agent, minScore, minBond)does (alpha.9 checks the stored status and the score only). - The Reader is deployed on Arc testnet at
0x2921b4eE879CdF5793FA273BbDc4E7dF01ABcef8(deployments/5042002.json:9), block 70760219, tagarc-testnet-2026-11-04b. sigvara.xyz/integrate has the call from a service and from a contract. To point at other registries, a consumer deploys its own withscript/DeployReader.s.sol. The worked example isexamples/gated-marketplace/GatedMarketplace.sol, and the guide isdocs/integration.md.
classDiagram
class SigvaraAgent {
+generate(options)
+issueChallenge(peerDid, ttlSeconds)
+signChallengeFor(payload, audience)
+did
+didHash
+publicKeyBytes32
}
class SigvaraVerifier {
+getIdentity(did)
+isActive(did)
+getReputation(did)
+getTotalScore(did)
+getScoreUpdatedAt(did)
+meetsThreshold(did, threshold)
+verifySignature(did, payload, signature, maxAgeSeconds, expectedAudience)
+buildDidDocument(did)
}
class SigvaraGate {
+challenge(did)
+admit(did, challenge, signature)
+threshold
+audience
}
SigvaraGate --> SigvaraVerifier : "uses"
SigvaraAgent --> SigvaraVerifier : "peer verification"
Diagram sources
- packages/sdk/src/agent.ts:7-125
- packages/sdk/src/verifier.ts:26-340
- packages/sdk/src/gate.ts:218-438
Section sources
A gate asks a caller to prove who it is. To decide whether to pay or hire an address, what matters is who the money goes to, so SigvaraTrustCheck (from 1.0.0-alpha.16) asks the same question with no proof, by the same rule.
- An x402 client: wrap the
fetchunderneath it, aswrapFetchWithPayment(sigvaraX402Fetch(fetch, trust), client). EverypayTooffered must have standing, or the request rejects withX402SellerRefusedand the client signs nothing. A 402 it cannot read is refused. It checks who is paid, not that work will be delivered, and a seller paid anywhere but the agent's registered address reads as having no standing. - An agent framework:
sigvaraTrustTool(tool, trust)gives a model a tool to call, andrequireTrustedAgent(fn, trust, agentFrom)makes a tool of yours refuse to run for an agent without standing, whatever the model decided. - A read that fails throws, with URLs removed, since it is neither a yes nor a no. Whatever the check guards should not go ahead.
TypeScript SDK has the detail, and sigvara.xyz/integrate has a card on it.
Section sources
- packages/sdk/README.md:291-358
- packages/sdk/src/standing.ts:92-153
- packages/sdk/src/x402.ts:70-93
- packages/sdk/src/tools.ts:37-82
- After registration and stake deposit, the oracle detects events and begins tracking the agent.
- UIs should poll or subscribe to relevant events (e.g., AgentRegistered, status changes) and update cached reputation values.
- When displaying reputation, prefer the matured total score for consistency with on-chain threshold checks.
Operational tips:
- Debounce UI updates to avoid excessive re-renders.
- Invalidate caches on event receipts.
- Surface meaningful messages when scores are still building.
- The matured score also moves with time alone, so no event announces some changes. Poll for the current number and use events (such as
ReputationUpdatedandAgentStatusUpdated) to learn when to look again.
Section sources
- Frontend holds only the DID and displays reputation; it never stores private keys.
- Backend owns SigvaraGate and performs all chain reads and signature verification.
- Session-scoped storage keeps each issued challenge under its payload to ensure strict challenge binding, so a second request about one DID does not replace the first's, and drops one only when its proof holds.
flowchart TD
FE["Frontend"] --> BE["Backend"]
BE --> CHAIN["On-chain"]
FE --> |Request challenge| BE
BE --> |Return payload| FE
FE --> |Submit signature| BE
BE --> |Verify + Gate| BE
BE --> |Admit/Refuse| FE
The SDK composes small, focused modules:
- index.ts re-exports the public surface.
- agent.ts depends on keys, did, and challenge modules.
- verifier.ts depends on abis, did, challenge, and keys.
- gate.ts depends on verifier and challenge.
- stake.ts depends on abis.
- types.ts defines shared interfaces.
graph LR
IDX["index.ts"] --> AGT["agent.ts"]
IDX --> VER["verifier.ts"]
IDX --> GAT["gate.ts"]
IDX --> CHA["challenge.ts"]
IDX --> STK["stake.ts"]
IDX --> TYP["types.ts"]
IDX --> ABI["abis.ts"]
AGT --> CHA
AGT --> TYP
VER --> ABI
VER --> CHA
VER --> TYP
GAT --> VER
GAT --> CHA
GAT --> TYP
STK --> ABI
Diagram sources
- packages/sdk/src/index.ts:1-72
- packages/sdk/src/agent.ts
- packages/sdk/src/verifier.ts
- packages/sdk/src/gate.ts
- packages/sdk/src/challenge.ts
- packages/sdk/src/stake.ts:1-39
- packages/sdk/src/types.ts
- packages/sdk/src/abis.ts:1-42
Section sources
- packages/sdk/src/index.ts:1-72
- packages/sdk/src/agent.ts
- packages/sdk/src/verifier.ts
- packages/sdk/src/gate.ts
- packages/sdk/src/challenge.ts
- packages/sdk/src/stake.ts:1-39
- packages/sdk/src/types.ts
- packages/sdk/src/abis.ts:1-42
- Batching operations:
- Combine related reads (e.g., getReputation and getTotalScore) where possible.
- In verifier.getReputation, both tuple and total are fetched concurrently to minimize latency.
- SigvaraVerifier keeps to 20 calls in a JSON-RPC batch, because Arc's public RPC answers 20 and refuses the rest with a rate limit (packages/sdk/src/verifier.ts:44-46).
- Caching strategies:
- Cache identity and reputation per DID for the duration of a session or short TTL window.
- Use response caching at the oracle layer to reduce repeated heavy computations.
- Connection pooling:
- Reuse ethers.JsonRpcProvider instances rather than creating new ones per request.
- Configure provider timeouts and retries appropriately for your RPC endpoint. The verifier and the gate abandon a read after 30 seconds by default, and
timeoutMschanges that (alpha.9 waits the five minutes ethers allows). - The gate reads the status, the bond and the score together, so an admit costs two round trips and not three.
- Nonce store efficiency:
- Prefer atomic consume(nonce, expiresAt) for distributed deployments to avoid race conditions. The gate hands it a hash of agent, audience and nonce, so a spent nonce counts for one agent and one audience.
- Prune expired nonces opportunistically to keep memory bounded.
Practical references:
- Concurrent reads in reputation retrieval.
- MemoryNonceStore pruning optimized by insertion order.
- Response cache with byte budget and LRU eviction.
Section sources
Common issues and resolutions:
-
Network connectivity problems:
- Symptoms: timeouts, failed RPC calls, inability to read identity or reputation.
- Actions: verify RPC URL and chainId; add retry/backoff; check node health; confirm correct network. A read that gets no answer fails after 30 seconds, and after five minutes on alpha.9.
-
Signature verification failures:
- Causes: wrong audience, stale challenge, malformed base58 signature, mismatched DID in payload.
- Actions: ensure generateChallenge includes a stable audience; pass the same audience to verifySignature; validate signature length bounds; handle invalid base58 gracefully.
-
Gas estimation errors:
- Causes: insufficient balance, incorrect token decimals, missing approvals.
- Actions: pre-check allowance; approve exact shortfall; ensure signer has enough native currency for gas; inspect transaction receipts for revert reasons.
-
Replay attacks:
- Cause: reusing a valid signature across processes without shared nonce store.
- Actions: implement NonceStore.consume for distributed fleets; ensure challenge.payload matches the one you issued; drop old nonces past TTL.
-
Misleading refusal reasons:
- Cause: collapsing bad_proof, replayed, not_active, below_bond (if you set
minBond), stale_score (if you setmaxScoreAgeSeconds) and below_threshold into a generic false. - Actions: propagate specific reasons to callers so they can provide actionable feedback.
- Cause: collapsing bad_proof, replayed, not_active, below_bond (if you set
-
Oracle delays:
- Cause: first score appears low until oracle runs an epoch and score matures.
- Actions: inform users that reputation builds over time; do not treat zero score as permanent failure immediately after bonding.
Section sources
- packages/sdk/src/verifier.ts:234-314
- packages/sdk/src/challenge.ts:49-77
- packages/sdk/src/gate.ts:328-438
- packages/sdk/src/stake.ts:9-39
- docs/quickstart.md:113-138
Sigvara integrates identity, reputation, and accountability into modern applications through a small, composable SDK. Use SigvaraAgent for identity and signing, SigvaraVerifier for chain reads and signature verification, and SigvaraGate for robust admission decisions. Follow the patterns shown here for web apps, backend services, and smart contract interactions. Apply batching, caching, and connection reuse to optimize performance, and use the troubleshooting guide to resolve common integration issues quickly.
- SigvaraAgent.generate: create agent and private key.
- SigvaraAgent.issueChallenge: produce a signed challenge payload.
- SigvaraAgent.signChallenge: removed in 1.0.0-alpha.12. It signed a received payload, whatever it said.
- SigvaraAgent.signChallengeFor: sign a received payload only if it is a v2 challenge naming this agent and the audience you pass.
- SigvaraVerifier.getIdentity: fetch identity and status.
- SigvaraVerifier.verifySignature: validate challenge signature and audience.
- SigvaraVerifier.meetsThreshold: on-chain threshold check.
- SigvaraGate.challenge: issue a challenge bound to your audience.
- SigvaraGate.admit: admit or refuse with detailed reasons.
- registerAgent: register an agent with proof of control.
- depositStake: approve and deposit stake in one call.
- atomicNonceStore: build a NonceStore whose consume() is atomic from any insert-if-absent, for a gate that runs as several processes.
- signPaymentOutcome and verifyPaymentOutcome: sign, as an agent's payer, whether one payment's work was delivered, and check such a signature as the oracle does.
- SigvaraTrustCheck.check: the gate's question about a bare address, with no proof.
- sigvaraX402Fetch: goes underneath an x402 client, and refuses a seller without standing before anything is signed.
- sigvaraTrustTool and requireTrustedAgent: a tool the model can call, and a function of yours that runs only for an agent with standing.
Section sources
- Agent Registration and Lifecycle
- Contract Overview and Architecture
- Economic Security Model and Incentives
- Evidence Verification and Merkle Proofs
- Key Management and Rotation
- Maturity Mechanism and Score Release
- Proposal Workflow and Challenge System
- Score Storage and Data Model
- SigvaraIdentity Contract
- SigvaraOracleBond Contract
- SigvaraReputation Contract
- SigvaraStaking Contract
- Slashing Lifecycle and Dispute Resolution
- Stake Management Functions
- Status Management and Control
- Token and Fee Contracts
- Unbonding Period and Withdrawal Security
- Anti-Manipulation and Security Measures
- Community Flag Processing
- Evidence Sources and Verification
- External Trust Integration
- Merkle Proof System
- Oracle Score Computation
- Payment-Backed Attestations
- Scoring Model and Formulas
- Agent Creation and Key Management
- Agent Operations
- Agent Registration Workflow
- Challenge-Response Authentication
- Gate Access Control
- Integration Patterns and Examples
- SDK Overview and Setup
- Verifier Functions