Repository navigation
TypeScript SDK
- Introduction
- Project Structure
- Core Components
- Architecture Overview
- Detailed Component Analysis
- Dependency Analysis
- Performance Considerations
- Troubleshooting Guide
- Conclusion
- Appendices
This document explains Sigvara’s TypeScript SDK for agent identity, cryptographic authentication, reputation checks, stake management, and gate-based access control. It covers:
- Agent creation with Ed25519 keys and DID derivation
- Challenge-response authentication and signature verification
- Verifier functions for reputation and threshold validation
- Gate-based admission with nonce management and detailed refusal reasons
- Stake deposit automation
- Integration patterns for web apps, backend services, and smart contract interactions
- Troubleshooting guidance for cryptography and network issues
The SDK is modular: agent operations, verification utilities, and access control are separated into focused modules with a clean public API surface.
This page describes the SDK in 1.0.0-alpha.15, published to npm on 3 November 2026. 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.
The SDK lives under packages/sdk/src and exposes a cohesive API through its index. The main modules are:
- Public entry point that re-exports the stable API
- Agent operations for key generation, DID helpers, and challenge signing
- Verifier for on-chain reads and signature verification
- Gate for admission decisions with replay protection
- Cryptographic primitives and encoding utilities
- DID formatting and hashing
- Challenge payload generation, parsing, and expiry checks
- Stake deposit helper
- Evidence roots: leaves, trees, proofs and root matching (from 1.0.0-alpha.15)
-
sigvara-mcp, a read-only MCP trust server, indist/mcpand as a bin (from 1.0.0-alpha.15) - Contract ABIs and shared types
graph TB
Index["index.ts<br/>Public API"] --> Agent["agent.ts<br/>SigvaraAgent"]
Index --> Verifier["verifier.ts<br/>SigvaraVerifier + registerAgent"]
Index --> Gate["gate.ts<br/>SigvaraGate"]
Index --> Challenge["challenge.ts<br/>generate/sign/verify/parse"]
Index --> Did["did.ts<br/>format/parse/hash"]
Index --> Keys["keys.ts<br/>base58/hex/Ed25519 helpers"]
Index --> Stake["stake.ts<br/>depositStake"]
Verifier --> Abis["abis.ts<br/>Identity/Reputation/Staking ERC20"]
Verifier --> Types["types.ts<br/>shared types"]
Gate --> Verifier
Agent --> Challenge
Agent --> Did
Agent --> Keys
Diagram sources
- index.ts:1-67
- agent.ts
- verifier.ts
- gate.ts
- challenge.ts
- did.ts:1-28
- keys.ts:1-125
- stake.ts:1-39
- abis.ts:1-42
- types.ts
Section sources
- SigvaraAgent: Creates Ed25519 keypairs, derives DIDs and hashes, issues challenges, and signs payloads.
- SigvaraVerifier: Reads on-chain identity, reputation, and stake; verifies signatures against chain-published keys; builds DID Documents.
- SigvaraGate: Issues challenges, enforces replay protection via nonces, checks agent activity and reputation thresholds, and returns structured admission results.
- Challenge utilities: Generate v2 payloads with audience binding, sign/verify Ed25519 signatures, parse payloads, and check expiry.
- DID utilities: Format, parse, and hash DIDs consistently with on-chain logic.
- Key utilities: Base58 encode/decode, hex conversion, seed-to-keypair, bytes32 conversions, and multibase encoding for DID Documents.
- Stake helper: Approve token allowance and deposit stake in one flow.
- Payment outcomes:
signPaymentOutcomesigns, as an agent's payer, whether one payment's work was delivered, over EIP-712 typed data, andverifyPaymentOutcomechecks such a signature the way the oracle does (from 1.0.0-alpha.13).
Key responsibilities:
- Agent operations: key generation, DID derivation, challenge issuance and signing
- Verification: on-chain reads, signature verification, DID Document construction
- Access control: admission decision with clear refusal reasons and nonce management
- Utilities: cryptography, encoding, and ABI/type definitions
Section sources
- agent.ts:7-125
- verifier.ts:26-338
- gate.ts:198-384
- challenge.ts:49-165
- did.ts:8-28
- keys.ts:7-125
- stake.ts:15-39
The SDK separates concerns across modules and integrates with Ethereum contracts via ethers. Agents generate keys and DIDs, verifiers read state and verify signatures, and gates enforce policy using verified proofs and on-chain reputation.
sequenceDiagram
participant Client as "Client"
participant Agent as "SigvaraAgent"
participant Verifier as "SigvaraVerifier"
participant Gate as "SigvaraGate"
participant Chain as "On-chain Contracts"
Client->>Agent : generate({ agentAddress, chainId })
Agent-->>Client : { agent, privateKey }
Client->>Verifier : registerAgent(operatorSigner, agentAddress, pubKeyBytes32, identityAddr, proof)
Verifier->>Chain : registerAgent(...)
Chain-->>Verifier : event AgentRegistered(didHash)
Verifier-->>Client : { didHash, txHash }
Client->>Gate : challenge(agentDid)
Gate-->>Client : Challenge(payload, nonce, timestamp, expiresAt)
Client->>Agent : signChallengeFor(challenge.payload, audience)
Agent-->>Client : signatureBase58
Client->>Gate : admit(agentDid, challenge, signatureBase58)
Gate->>Verifier : verifySignature(did, payload, sig, ttl, audience)
Verifier->>Chain : getIdentity(didHash), isActive(didHash), hasMinimumStake(didHash), getTotalScore(didHash)
Chain-->>Verifier : identity, status, score
Verifier-->>Gate : verified
Gate-->>Client : AdmitResult(ok, reason, score, threshold, did)
Diagram sources
- agent.ts:43-124
- verifier.ts:353-393
- verifier.ts:73-147
- verifier.ts:242-312
- gate.ts:300-384
- challenge.ts:49-93
Responsibilities:
- Generate secure Ed25519 keypairs from random seeds
- Derive DID and DID hash consistent with on-chain logic
- Issue challenges bound to a peer DID and an audience
- Sign challenge payloads with the agent’s private key
Key behaviors:
- Static factory generates a new agent and returns the raw private key for secure storage
- DID and hash are derived deterministically from agent address and chain ID
- Challenges include prover DID, audience, nonce, and timestamp
- Signing uses Ed25519 over UTF-8 payload and returns base58-encoded signatures
-
signChallengeFor(payload, audience)signs only a v2 challenge that names this agent as the prover and names exactly the audience the caller passes, and otherwise throws and signs nothing. The audience in a challenge protects an agent from relay only if the agent checks it before signing, so the agent's blindsignChallenge(payload)is removed in 1.0.0-alpha.12 (on alpha.9 onlysignChallengeexists, so check the payload's audience yourself) - The Ed25519 secret key is held in a true private field, so
JSON.stringify(agent), a logged agent and an error reporter's snapshot do not carry it (on alpha.9 the key is an ordinary property and they do; the key of such an agent that was ever logged or serialized should be treated as exposed and rotated, see Key Management and Rotation)
classDiagram
class SigvaraAgent {
-keyPair
-_agentAddress
-_chainId
-_challengeTtl
+constructor(options)
+static generate(options)
+agentAddress
+did
+didHash
+publicKeyBytes32
+issueChallenge(peerDid, ttlSeconds)
+signChallengeFor(payload, audience)
}
Diagram sources
Section sources
Responsibilities:
- Read agent identity, status, reputation factors and total score
- Check minimum stake and threshold compliance
- Verify challenge-response signatures against on-chain public keys
- Build W3C-compliant DID Documents
- Read what a trust check needs (from 1.0.0-alpha.15): earned score, pending score, minimum stake, a queued withdrawal, the last slash proposal, the evidence root, and
verifyEvidencefor one payment
Security considerations:
- Audience binding prevents relay attacks:
expectedAudienceis required (1.0.0-alpha.10 and earlier let a caller leave it out, which skipped the check), and a v1 payload, which names no audience, is refused - Nonce freshness is enforced by TTL; integrators must track nonces if not using the gate
- Signature length and alphabet validation prevent malformed inputs from causing errors or crashes
- A challenge dated more than 60 seconds (
MAX_FUTURE_SKEW_SECONDS) ahead of the verifier's clock is refused as not fresh. Before this fix a payload dated ahead counted as fresh for as long as the date stayed ahead (alpha.9 still does) - Chain reads give up after 30 seconds (
DEFAULT_RPC_TIMEOUT_MS), andtimeoutMsonSigvaraVerifierandSigvaraGatechanges it. ethers waits five minutes by default, which is what alpha.9 does
flowchart TD
Start([verifySignature]) --> AudienceGiven{"Expected audience given?"}
AudienceGiven --> |No| ThrowMissing["Throw: expectedAudience is required"]
AudienceGiven --> |Yes| Parse["Parse challenge payload"]
Parse --> MatchDid{"Payload DID matches?"}
MatchDid --> |No| ReturnFalse["Return false"]
MatchDid --> |Yes| VersionAudience{"Version 2 and audience match?"}
VersionAudience --> |No| ReturnFalse
VersionAudience --> |Yes| ExpiryCheck["Check expiry"]
ExpiryCheck --> LengthCheck["Validate base58 signature length"]
LengthCheck --> FetchIdentity["Read identity from chain"]
FetchIdentity --> ValidIdentity{"Registered and has pubkey?"}
ValidIdentity --> |No| ReturnFalse
ValidIdentity --> |Yes| CryptoVerify["Verify Ed25519 signature"]
CryptoVerify --> Result{"Verified?"}
Result --> |Yes| True(["Return true"])
Result --> |No| False(["Return false"])
Diagram sources
Section sources
Responsibilities:
- Issue signed challenges with audience binding
- Enforce replay protection via nonce store
- Validate signatures, agent activity, and reputation thresholds
- Return structured admission results with explicit refusal reasons
- Refuse to be built with a
thresholdthat is not a finite number of 0 or more, or attlSecondsormaxScoreAgeSecondsthat is not a positive number. ANaNthreshold, for exampleNumber(process.env.X)withXunset, would otherwise admit every active agent that can prove its key - Refuse to start as
instances: 'multiple'without anonceStorewhoseconsume()is atomic, and, withprofile: 'production', untilinstancesandmaxScoreAgeSecondsare both set. Any otherprofilevalue is refused rather than ignored
Order of work in admit: the proof is checked first, the nonce is spent second, and the status, the bond held now and the score are read last. Those reads do not depend on each other and go out together, so an admit costs two round trips and not three (alpha.9 reads the status and the score one after the other, and does not read the bond). Standing is named before score. If maxScoreAgeSeconds is set, the time the score was last written is read in the same batch and a score older than the limit, or never written, is refused as stale_score after standing and before the number. Checking the proof costs one read of the agent's registered key, because the key lives on chain, so rate-limit callers where the gate is exposed.
Nonce management:
- Default in-memory store suitable for single-process deployments
- Optional atomic consume interface for distributed fleets.
atomicNonceStore(setIfAbsent)builds such a store from any atomic insert-if-absent, such as a RedisSET NX, andinstances: 'multiple'makes the gate refuse to start without one (both from 1.0.0-alpha.13).instancesdefaults to'single' - Pruning strategy bounded by issue rate and TTL
Refusal reasons:
- bad_proof: invalid signature or mismatched fields
- replayed: valid signature already spent
- not_active: agent exists but is unbonded (or bonded below the minimum stake in force now), suspended, or slashed
- stale_score: only with
maxScoreAgeSeconds: the score was written longer ago than the limit, or never - below_threshold: active and proven but score insufficient
flowchart TD
A([admit]) --> ParseSigned["Parse signed payload for nonce/timestamp"]
ParseSigned --> Verify["Verifier.verifySignature"]
Verify --> Proved{"Proof valid?"}
Proved --> |No| BadProof["Return bad_proof"]
Proved --> |Yes| ComputeExpiry["Compute expiresAt from signed timestamp + TTL"]
ComputeExpiry --> ConsumeNonce["consumeNonce(nonce, expiresAt)"]
ConsumeNonce --> Spent{"Nonce already spent?"}
Spent --> |Yes| Replayed["Return replayed"]
Spent --> |No| IsActive["Verifier.isActive and hasMinimumStake"]
IsActive --> Active{"Active and bonded?"}
Active --> |No| NotActive["Return not_active"]
Active --> |Yes| GetScore["Verifier.getTotalScore"]
GetScore --> Fresh{"Too old? Only if maxScoreAgeSeconds is set"}
Fresh --> |Yes| Stale["Return stale_score with score"]
Fresh --> |No| Threshold{"score >= threshold?"}
Threshold --> |No| Below["Return below_threshold with score"]
Threshold --> |Yes| Admit["Return ok=true with score"]
Diagram sources
Section sources
- Base58 encode/decode with strict alphabet validation
- Hex conversion with robust error reporting for invalid characters
- Seed-to-keypair derivation for Ed25519
- Bytes32 <-> public key conversions for on-chain compatibility
- Multibase encoding for DID Documents (Ed25519VerificationKey2020)
Complexity notes:
- Base58 encode/decode operate linearly in input size
- Hex conversion validates and processes each byte once
- All cryptographic operations delegate to tweetnacl
Section sources
- Parse and format DIDs following the did:sigvara scheme
- Compute DID hash matching on-chain keccak256 packing
Section sources
- v2 payloads bind prover DID, audience, nonce, and timestamp
- v1 payloads still parse, but
verifySignaturerefuses them because they name no audience - Newline-delimited format avoids ambiguity with colon-bearing fields
- Expiry checks treat unparseable payloads as expired
- Expiry checks also refuse a payload dated more than 60 seconds ahead of the verifier's clock (
MAX_FUTURE_SKEW_SECONDS), so a signature over a far-future timestamp cannot stay valid for years. Challenges a gate issues are dated by its own clock and are unaffected
Section sources
- depositStake automates ERC-20 approval shortfall and deposits stake
- Returns transaction hashes for both approval and deposit when applicable
Section sources
The SDK exhibits clear separation of concerns:
- index.ts re-exports only the public API
- agent.ts depends on keys, did, and challenge
- verifier.ts depends on abis, did, challenge, keys, and types
- gate.ts depends on verifier and challenge
- stake.ts depends on abis
- challenge.ts depends on keys and types
- did.ts depends on ethers
- keys.ts depends on tweetnacl
graph LR
Index["index.ts"] --> Agent["agent.ts"]
Index --> Verifier["verifier.ts"]
Index --> Gate["gate.ts"]
Index --> Challenge["challenge.ts"]
Index --> Did["did.ts"]
Index --> Keys["keys.ts"]
Index --> Stake["stake.ts"]
Agent --> Keys
Agent --> Did
Agent --> Challenge
Verifier --> Abis["abis.ts"]
Verifier --> Did
Verifier --> Challenge
Verifier --> Keys
Verifier --> Types["types.ts"]
Gate --> Verifier
Gate --> Challenge
Stake --> Abis
Diagram sources
- index.ts:1-67
- agent.ts
- verifier.ts
- gate.ts
- challenge.ts
- did.ts:1-28
- keys.ts:1-125
- stake.ts:1-39
- abis.ts:1-42
- types.ts
Section sources
- index.ts:1-67
- agent.ts
- verifier.ts
- gate.ts
- challenge.ts
- did.ts:1-28
- keys.ts:1-125
- stake.ts:1-39
- abis.ts:1-42
- types.ts
- Lazy contract instantiation in SigvaraVerifier reduces repeated ABI parsing overhead
- getTotalScore is used instead of full reputation tuple when only the matured score is needed, avoiding extra contract reads
- MemoryNonceStore prunes entries proportionally to expired items, keeping memory bounded by issue rate and TTL
- Signature length checks before chain reads reduce unnecessary RPC calls
- Challenge parsing treats malformed payloads as expired to fail fast
-
SigvaraGate.admitreads the status, the bond and the score together, and each chain read gives up after 30 seconds by default
Common issues and resolutions:
- Invalid base58 signature: ensure signatures are produced by signChallengeFor and transmitted unchanged; verify character set and length
- Mismatched audience: pass the same audience string to both generateChallenge and verifySignature;
verifySignaturethrows without it, and refuses a v1 payload, which names no audience - Expired challenges: ensure clients sign promptly; adjust TTL appropriately for your environment
- Replay attempts: use SigvaraGate or implement a shared nonce store with atomic consume semantics
- Unregistered or inactive agents: confirm registration and stake deposit; check status and minimum stake
- Network connectivity: verify rpcUrl and chainId; handle provider errors gracefully. A read that gets no answer fails after 30 seconds (set
timeoutMsto change it) and after ethers' default of five minutes on alpha.9 - Hex seed errors: validate hex strings and length; use SigvaraAgent.generate to avoid manual mistakes
-
signChallengeForthrows and signs nothing: the payload is not a v2 Sigvara challenge, names a different prover, or is addressed to a different audience than the one you passed -
new SigvaraGate(...)throws:thresholdis not a finite number of 0 or more, orttlSecondsormaxScoreAgeSecondsis not a positive number;instancesis neither'single'nor'multiple', or is'multiple'with anonceStorethat has noconsume(); orprofileis set to anything but'production', or is'production'whileinstancesormaxScoreAgeSecondsis missing (the message names each one missing)
Operational tips:
- For multi-instance deployments, provide a NonceStore backed by Redis or database with atomic insert-if-absent, such as
atomicNonceStoreover aSET NX, and setinstances: 'multiple'so the gate refuses to start without it. In a deployment,profile: 'production'makes the gate refuse to start untilinstancesandmaxScoreAgeSecondsare set - Log AdmitReason to distinguish cryptographic failures from policy denials
- Use buildDidDocument to publish verification methods for standard resolvers
Section sources
Sigvara’s TypeScript SDK provides a secure, modular toolkit for agent identity, cryptographic authentication, reputation evaluation, stake management, and policy-driven access control. By separating agent operations, verification, and access control, it enables clear integration patterns for web applications, backend services, and smart contract interactions while enforcing strong security properties such as audience binding, nonce replay protection, and on-chain consistency.
- Agent registration and bonding:
- Generate agent keys and derive DID
- Register agent with operator signer and agent proof
- Deposit stake to activate the agent
- Challenge-response authentication:
- Issuer creates a challenge with audience
- Agent signs the payload
- Verifier checks signature, activity, and threshold
- Gate-based admission:
- Use SigvaraGate to issue challenges and decide admission
- Handle every refusal reason distinctly (
stale_scoretoo, if you setmaxScoreAgeSeconds)
- Web application integration:
- Frontend collects challenge and signature
- Backend verifies via SigvaraGate or SigvaraVerifier
- Store nonces securely if bypassing the gate
- Backend service integration:
- Run SigvaraGate behind load balancers with a shared NonceStore
- Cache verifier instances per process
- Smart contract interactions:
- Use registerAgent and depositStake for lifecycle management
- Reference ABIs and addresses from deployment artifacts
For step-by-step examples, see the SDK README quick start and gate usage sections.
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