Skip to content

Gate Access Control

RunTimeAdmin edited this page Nov 5, 2026 · 40 revisions

Gate Access Control

**Referenced Files in This Document** - [standing.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/standing.ts) - [gate.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/src/gate.ts) - [gate.test.ts](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/test/gate.test.ts) - [reputation-model.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/docs/reputation-model.md) - [README.md](https://github.com/RunTimeAdmin/Sigvara/blob/main/packages/sdk/README.md)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document explains the gate-based access control implementation in the Sigvara SDK. A gate is an authorization layer that requires an agent to prove identity, demonstrate active standing, and meet a reputation threshold before being admitted. It also manages nonces to prevent replay attacks and ensure each challenge-response pair is used exactly once.

The gate separates authentication from authorization:

  • Authentication proves that the caller controls the claimed agent identity for this specific request.
  • Authorization checks whether that agent is active, holds at least the minimum stake in force now (alpha.9 trusts the stored status), and has sufficient matured reputation.

Gates are intended for off-chain services such as web applications, APIs, or tooling that need reputation-based access control without sending an on-chain transaction for every decision. Each admit does make read calls to the chain: one for the agent's registered key, then the status, the bond held now and the score, the time the score was proposed if you set maxScoreAgeSeconds, and the stake if you set minBond.

This page describes the gate in 1.0.0-alpha.16, published to npm on 5 November 2026. New since 1.0.0-alpha.15: minBond with its below_bond refusal, challengeSecret, a spent nonce recorded for one agent and one audience, and the rule moved to standingDecision in standing.ts, which sigvara-mcp and the new SigvaraTrustCheck share. 1.0.0-alpha.15 did not change the gate. Given a threshold, its sigvara-mcp answers sigvara_check_agent by the gate's rule, without the proof, and a test runs both over 96 chain states. sigvara_check_agent has no minBond argument. New since 1.0.0-alpha.13: profile: 'production', which makes the constructor refuse to build the gate until instances and maxScoreAgeSeconds are both set. New since 1.0.0-alpha.12: atomicNonceStore, and the instances setting, with which a gate set to 'multiple' refuses to start without a nonce store with an atomic consume(). The gate did not change from 1.0.0-alpha.10 to 1.0.0-alpha.12. These changes to the gate are new since 1.0.0-alpha.9 (listed in the changelog): the constructor refusing a threshold, ttlSeconds or maxScoreAgeSeconds that is not a usable number, the timeoutMs option and the 30 second read timeout, the status, the bond held now and the score being read together, the optional maxScoreAgeSeconds limit with its stale_score refusal, and the refusal of a challenge dated in the future.

Project Structure

The gate logic lives in the SDK package under packages/sdk. The relevant files are:

  • packages/sdk/src/gate.ts: the gate class, nonce store interface, and admission flow.
  • packages/sdk/src/standing.ts: standingDecision, the rule the gate shares with sigvara-mcp and SigvaraTrustCheck, and SigvaraTrustCheck itself.
  • packages/sdk/test/gate.test.ts: tests covering admission, refusal reasons, nonce handling, concurrency, store behavior, two gates sharing one store, the instances, profile, minBond and challengeSecret settings, which agent a spent nonce belongs to, and configuration that would fail open.
  • docs/reputation-model.md: background on how reputation scores are computed, matured, and consumed.
  • packages/sdk/README.md: integration notes about audience configuration and shared nonce stores.
graph TB
Client["Client Application"] --> Gate["SigvaraGate<br/>packages/sdk/src/gate.ts"]
Gate --> Verifier["SigvaraVerifier<br/>packages/sdk/src/verifier.ts"]
Gate --> NonceStore["NonceStore<br/>In-memory or custom"]
Verifier --> Chain["On-chain Reputation Data"]
Loading

Diagram sources

Section sources

Core Components

  • SigvaraGate: the main authorization component. It issues challenges, verifies signatures, tracks nonces, checks agent activity, reads matured reputation, and returns structured admission results.
  • NonceStore: an interface for tracking spent nonces. From 1.0.0-alpha.16 the gate hands it a hash of the agent, the audience and the nonce, where it used to hand it the nonce, and nothing about a store needs to change. It supports per-process memory storage by default and can be replaced with a distributed store for multi-instance deployments. atomicNonceStore(setIfAbsent) builds such a store from any atomic insert-if-absent, such as a Redis SET NX or a database insert that does nothing on a conflict.
  • AdmitResult and AdmitReason: typed responses describing whether access was granted and why it was refused.
  • standingDecision (in standing.ts): the rule, in the order not_active, below_bond, stale_score, below_threshold. The gate, sigvara-mcp and SigvaraTrustCheck all use it, so they cannot come to different answers about one agent.
  • GateConfig: configuration for RPC connection, contract addresses, chain ID, reputation threshold, audience binding, challenge TTL, optional nonce store, how many processes serve the gate (instances), the read timeout timeoutMs, the optional score-age limit maxScoreAgeSeconds, the optional bond floor minBond, the optional challengeSecret, and the optional profile: 'production'.

Key responsibilities:

  • Issue a signed challenge containing a nonce, timestamp, audience, and DID.
  • Verify the signature against the on-chain verifier.
  • Prevent replay using a nonce store.
  • Check whether the agent is active and still holds the minimum stake in force now (alpha.9 reads the stored status only).
  • If minBond is set, refuse an agent holding less than that.
  • If maxScoreAgeSeconds is set, refuse a score proposed longer ago than that, or never.
  • Compare the matured score against the configured threshold.
  • Return a clear reason for every refusal.
  • Refuse to be built with a threshold that is not a finite number of 0 or more, a ttlSeconds or maxScoreAgeSeconds that is not a positive number, a minBond that is not a bigint or a string of digits, or a challengeSecret shorter than 32 characters.
  • Refuse to start as instances: 'multiple' without a nonce store whose consume() is atomic, and, with profile: 'production', until instances and maxScoreAgeSeconds are both set.

Section sources

Architecture Overview

The gate implements a challenge-response protocol with explicit replay protection and reputation gating.

sequenceDiagram
participant App as "Application"
participant Gate as "SigvaraGate"
participant Store as "NonceStore"
participant Verifier as "SigvaraVerifier"
participant Chain as "Reputation Contract"
App->>Gate : "challenge(did)"
Gate-->>App : "Challenge {payload, nonce, expiresAt}"
App->>App : "Agent signs payload"
App->>Gate : "admit(did, challenge, signature)"
Gate->>Gate : "Parse signed payload, and with challengeSecret check this gate issued the nonce"
Gate->>Verifier : "verifySignature(did, payload, signature, ttl, audience)"
Verifier-->>Gate : "verified or not"
alt "Invalid proof"
Gate-->>App : "{ok : false, reason : 'bad_proof'}"
else "Valid proof"
Gate->>Store : "consume(hash of agent, audience and nonce, expiresAt)"
Store-->>Gate : "allowed or already spent"
alt "Already spent"
Gate-->>App : "{ok : false, reason : 'replayed'}"
else "Allowed"
Note over Gate,Verifier : isActive, hasMinimumStake and getTotalScore are sent together
Gate->>Verifier : "isActive(did) and hasMinimumStake(did)"
Verifier-->>Gate : "active and bonded, or not"
alt "Not active, or below the minimum stake"
Gate-->>App : "{ok : false, reason : 'not_active'}"
else "Active"
Gate->>Verifier : "getTotalScore(did)"
Verifier->>Chain : "read matured score"
Chain-->>Verifier : "score"
Verifier-->>Gate : "score"
alt "Holds less than minBond, only when minBond is set"
Gate-->>App : "{ok : false, reason : 'below_bond', score}"
else "Score too old, only when maxScoreAgeSeconds is set"
Gate-->>App : "{ok : false, reason : 'stale_score', score}"
else "Fresh enough, or no limit set"
alt "Below threshold"
Gate-->>App : "{ok : false, reason : 'below_threshold', score}"
else "Meets threshold"
Gate-->>App : "{ok : true, score}"
end
end
end
end
end
Loading

Diagram sources

Detailed Component Analysis

Gate Evaluation Flow

The gate evaluates requests in a strict order designed to avoid unnecessary chain reads and to provide actionable refusal reasons. The proof is checked first, the nonce is spent second, and the status, the bond held now and the score are read last, so a caller who cannot prove an identity cannot make the gate read any of them. Those reads do not depend on each other and are sent together, and the answer still names standing before score (alpha.9 reads the status, then the score, and does not read the bond). If you set minBond, the stake is read in the same batch, and an agent holding less is refused as below_bond, after not_active and before stale_score. If you set maxScoreAgeSeconds, the time the score was proposed is read in the same batch, and a score older than the limit, or never written, is refused as stale_score before the number. If you set challengeSecret, a challenge this gate did not issue is refused as bad_proof before any read.

flowchart TD
Start(["Request received"]) --> Parse["Parse signed payload"]
Parse --> ValidPayload{"Payload valid?"}
ValidPayload --> |No| BadProof["Return 'bad_proof'"]
ValidPayload --> |Yes| Issued{"Issued here? Only if challengeSecret is set"}
Issued --> |No| BadProof
Issued --> |Yes| Verify["Verify signature and audience"]
Verify --> Verified{"Signature valid?"}
Verified --> |No| BadProof
Verified --> |Yes| Consume["Spend nonce for this agent and audience"]
Consume --> Spent{"Nonce already spent?"}
Spent --> |Yes| Replayed["Return 'replayed'"]
Spent --> |No| Active["Read status, bond held now and matured score"]
Active --> IsActive{"Active, and bonded at the current minimum?"}
IsActive --> |No| NotActive["Return 'not_active'"]
IsActive --> |Yes| Bond{"Holds less than minBond? Only if minBond is set"}
Bond --> |Yes| BelowBond["Return 'below_bond' with score"]
Bond --> |No| Fresh{"Score too old? Only if maxScoreAgeSeconds is set"}
Fresh --> |Yes| Stale["Return 'stale_score' with score"]
Fresh --> |No| Threshold{"Score >= threshold?"}
Threshold --> |No| BelowThreshold["Return 'below_threshold' with score"]
Threshold --> |Yes| Admit["Return ok with score"]
Loading

Diagram sources

Section sources

Nonce Management and Replay Prevention

Nonces are central to preventing replay attacks. The gate owns nonce lifecycle because it issued the challenge and knows which nonces are outstanding.

Important behaviors:

  • Each challenge contains a unique nonce inside the signed payload.
  • The gate spends the nonce only after successful signature verification, so invalid proofs cannot burn nonces.
  • A spent nonce is recorded for one agent and one audience. From 1.0.0-alpha.16 the gate hands its store a hash of the agent, the audience and the nonce, read from the signed bytes, so spending a nonce for one agent leaves a challenge issued to another untouched (gate.ts:397-403).
  • By default the gate remembers what was spent, not what it issued, so it accepts any challenge for its audience that the agent signed in the last ttlSeconds, including one the agent wrote itself. Set challengeSecret (from 1.0.0-alpha.16, at least 32 characters) and the gate marks each nonce it issues with a keyed hash and refuses any other with bad_proof, before any read. It keeps no state: give every instance the same secret, and they accept each other's challenges. It adds to the nonce store and does not replace it, because a challenge the gate did issue can still be presented twice (gate.ts:201-208, gate.ts:328-351).
  • The expiration time comes from the signed timestamp plus the gate’s TTL, not from a caller-supplied wrapper field.
  • The default MemoryNonceStore is process-local. For multiple instances behind a load balancer, set instances: 'multiple' and pass a shared NonceStore with an atomic consume method; the gate refuses to start as 'multiple' without one. atomicNonceStore(setIfAbsent) builds such a store from any atomic insert-if-absent, such as a Redis SET ... NX EXAT or a database insert that does nothing on a conflict, and its seen and add throw, so the gate cannot fall back to the non-atomic pair. instances defaults to 'single', so a fleet that leaves out both it and nonceStore runs on per-process stores without a warning. The SDK README shows a Redis store (one key per nonce, SET ... NX with an expiry that outlasts the challenge) and what a SQL one has to do. With only seen and add, two instances that receive one response at the same moment can both admit it.
  • Pruning removes expired entries opportunistically; it does not replace the replay check.
classDiagram
class SigvaraGate {
-verifier : SigvaraVerifier
-nonces : NonceStore
-ttl : number
-inFlight : Set~string~
+threshold : number
+audience : string
+challenge(did) Challenge
+admit(did, challenge, signature) AdmitResult
-consumeNonce(nonce, expiresAt, nowSeconds) boolean
}
class NonceStore {
<<interface>>
+seen(nonce) boolean | Promise<boolean>
+add(nonce, expiresAt) void | Promise<void>
+prune(nowSeconds) void | Promise<void>
+consume?(nonce, expiresAt) boolean | Promise<boolean>
}
class MemoryNonceStore {
-spent : Map~string,number~
+seen(nonce) boolean
+add(nonce, expiresAt) void
+prune(nowSeconds) void
+consume(nonce, expiresAt) boolean
}
SigvaraGate --> NonceStore : "uses"
MemoryNonceStore ..|> NonceStore : "implements"
Loading

Diagram sources

Section sources

Refusal Reasons and Their Meaning

The gate distinguishes six refusal reasons so callers can respond appropriately. below_bond appears only when you set minBond, and stale_score only when you set maxScoreAgeSeconds.

Reason Meaning Recommended Caller Action
bad_proof Signature verification failed, wrong agent, wrong audience, or malformed payload. Reject the request; do not treat it as a reputation problem.
replayed Valid signature but nonce already spent. Ask for a fresh challenge; investigate possible replay attempts.
not_active Agent proved itself but is not bonded, suspended, or slashed. "Bonded" is the bond held now against the minimum stake in force now, not the status stored when the agent was admitted (alpha.9 checks the stored status only). Do not suggest earning points; address standing instead.
below_bond Only with minBond: agent is active and holds the protocol's minimum, but less than this gate asks for. Do not suggest earning points; a higher score would not change it.
stale_score Only with maxScoreAgeSeconds: the score was proposed longer ago than the limit, or never. Tell the operator the oracle may have stopped writing, or the limit is too short, not that the score is low.
below_threshold Agent is active and proved itself, but matured score is below threshold. Inform the agent they need more reputation; include the current score.

Only below_threshold is about reputation. Collapsing all failures into a single boolean makes debugging impossible.

Section sources

Reputation Thresholds and Maturity

The gate compares the agent’s matured total score against the configured threshold. It uses the same value the reputation contract uses for threshold checks, avoiding disagreement with on-chain policies built on meetsThreshold. The gate also checks the bond held now against the minimum stake in force now, as SigvaraReader.isTrusted does, so the two agree for an Active agent whose bond is below the current minimum (alpha.9 checks the stored status and the score only, and admits such an agent). The Reader's isTrusted(agent, minScore, minBond) also takes a bond floor the caller chooses, and from 1.0.0-alpha.16 so does the gate. Its minBond is in the bond token's base units, as a bigint or a string of digits, and compares the same number. A number is refused when the gate is built, because a floor past what a double can hold would quietly become another floor. A floor under the protocol's minimum still needs the minimum, an amount queued for withdrawal does not count, and the extra read happens only when it is set (gate.ts:195-200, standing.ts:44-56).

Key points:

  • The gate reads the matured total score, not the raw sum of factors.
  • Raw factors may temporarily exceed the matured total while maturity ramps up.
  • The maturity mechanism prevents farm-and-cash-out attacks by limiting how fast earned reputation becomes spendable.
  • On networks where external trust and propagation are not yet reachable, the practical ceiling is lower than 100, so thresholds should be chosen carefully.
flowchart TD
Earned["Earned score finalized"] --> Matured["Matured score climbs toward earned"]
Matured --> Spendable["Spendable for gates and contracts"]
Earned --> RawFactors["Raw factor breakdown"]
RawFactors --> NotUsedByGate["Gate does not use raw sum"]
Matured --> ThresholdCheck["Gate compares matured score to threshold"]
Loading

Diagram sources

Section sources

How Old a Score May Be

maxScoreAgeSeconds reads the score's lastUpdated, the same field as SigvaraReader.isTrustedFresh and trust().scoreUpdatedAt. That is when the score now in force was proposed, not when it was finalized (src/SigvaraReputation.sol:487-490). Finalizing stays open to anyone at any time after the window, and when it is called no longer changes the score's age: a score a late finalize makes live can still go live, and it reads as old as it is. A score zeroed by a slash is dated at the slash.

A proposal goes live when the next one is made, so a healthy agent's score reaches two proposals' spacing old just before the next one lands. Choose a limit above that. On Arc testnet the oracle runs hourly and the window is 6 hours, so proposals land 6.4 to 7.4 hours apart (measured 5 November 2026), a score reaches about 15 hours old, and a day (86_400) is a sensible limit. On mainnet's daily epoch with a 6 hour window a score reaches about two days, and three days tolerates one late epoch.

Section sources

The Same Rule Without a Proof

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 there is no proof to ask for. SigvaraTrustCheck (from 1.0.0-alpha.16) applies the gate's rule to a bare address and returns { ok, agent, reason?, detail?, score? }, with not_an_address as one more reason. A failed read throws, since it is neither a yes nor a no. To know that the party you are talking to controls an agent, use the gate. TypeScript SDK has the detail, along with sigvaraX402Fetch, sigvaraTrustTool and requireTrustedAgent, which build on it.

Section sources

Practical Examples

Implementing a Custom Gate

To integrate reputation-based access control:

  1. Create a SigvaraGate with your RPC URL, contract addresses, chain ID, threshold, and a stable audience. The constructor throws if the audience is missing, the threshold is not a finite number of 0 or more, or ttlSeconds is not a positive number. A threshold taken from Number(process.env.X) with X unset is NaN: on alpha.9 that admits every active agent that can prove its key, whatever its score, and from 1.0.0-alpha.10 the constructor throws. To refuse a score nobody has refreshed, also set maxScoreAgeSeconds, more than two of your oracle's proposals apart (see How Old a Score May Be) and measured against your host's clock (off by default, one more read per admit). To ask for more bond than the protocol's minimum, set minBond. To accept only challenges this gate issued, set challengeSecret.
  2. When a user wants to perform a protected action, call challenge(did) and return the challenge object to the client.
  3. Have the agent sign challenge.payload. The agent should use signChallengeFor(payload, audience), which signs only a challenge addressed to the audience it passes (alpha.9 has only signChallenge, which signs whatever it is handed; the agent's signChallenge is removed in 1.0.0-alpha.12).
  4. Call admit(did, challenge, signature) and inspect the result.
  5. If ok is false, handle the specific reason.

For multi-instance deployments, set instances: 'multiple' and provide a shared NonceStore with an atomic consume method, such as atomicNonceStore. For a deployment, also pass profile: 'production': the constructor then refuses to build the gate until instances and maxScoreAgeSeconds are both set, and names everything missing in one error.

Section sources

Integrating With Existing Access Control Systems

Use the gate as an authorization middleware:

  • Protect routes or API endpoints by calling the gate before executing business logic.
  • Map refusal reasons to HTTP status codes or application-specific errors.
  • Cache or log decisions for observability, but do not cache admission results across challenge lifetimes.
  • Treat not_active differently from below_threshold in user-facing messaging.

Section sources

Handling Authorization Failures

  • bad_proof: reject immediately; ask the client to retry with a fresh challenge if needed.
  • replayed: reject and warn about replay attempts; require a new challenge.
  • not_active: inform the operator that the agent is not active; do not direct them to earn points.
  • below_bond (only with minBond): tell the operator the agent holds less than this gate asks for; a higher score would not change that.
  • stale_score (only with maxScoreAgeSeconds): tell the operator the oracle may have stopped writing this score, or never wrote it, or that the limit is under two proposals apart; do not tell the agent to earn points.
  • below_threshold: inform the agent that their reputation is too low; include the current score and threshold.

Section sources

Security Considerations

Nonce Expiration and Bounded State

  • Challenges expire based on the signed timestamp plus the gate’s TTL.
  • Nonces are dropped after expiry through opportunistic pruning.
  • The default in-memory store keeps state bounded by issue rate rather than uptime.

Distributed Replay Protection

  • The default store is process-local.
  • Behind a load balancer, set instances: 'multiple' and share a store with atomic consume, such as atomicNonceStore. The gate refuses to start as 'multiple' without one, and instances defaults to 'single', so a fleet has to say so.
  • Without atomic consume, two concurrent requests with the same nonce could both observe it unspent; the gate adds an in-process guard, but that does not protect across processes.

Attack Prevention

  • Invalid proofs never consume nonces, preventing denial-of-service via nonce exhaustion.
  • Audience binding prevents relay attacks where one service’s response is reused at another.
  • Using matured score prevents short-lived farms from passing gates before maturity catches up.
  • Threshold selection should account for the reachable ceiling when external trust and propagation are limited.
  • A caller who can form a well-sized signature for a registered agent makes the gate do one identity read, because the key the signature is checked against lives on chain. Nothing else is read until the proof and the nonce pass, so rate-limit callers where the gate is exposed.
  • A challenge dated more than 60 seconds ahead of the gate's clock is refused as not fresh, so a proof over a far-future timestamp cannot stay valid, and its nonce cannot sit in the store until a far-off expiry. Challenges the gate issues itself are dated by its own clock.

Section sources

Integration Patterns for Web Applications

Recommended pattern:

  1. Server-side gate instance with a stable audience.
  2. Client-side agent signs challenge payloads.
  3. Middleware enforces admission before route handlers.
  4. Clear error messages distinguish authentication failures from reputation failures.
  5. Shared nonce store for horizontally scaled deployments.
  6. Monitoring for replay attempts and repeated bad_proof failures.
flowchart TD
Request["HTTP Request"] --> Auth["Authentication Layer"]
Auth --> GateCall["SigvaraGate.admit()"]
GateCall --> Decision{"Admitted?"}
Decision --> |No| Error["Return error with reason"]
Decision --> |Yes| Handler["Business Logic Handler"]
Error --> Log["Log reason and metadata"]
Handler --> Response["Authorized Response"]
Loading

Dependency Analysis

The gate depends on:

  • SigvaraVerifier for signature verification, activity checks, and reputation queries.
  • NonceStore for replay prevention.
  • Configuration values for threshold, audience, TTL, and chain connectivity.
graph LR
Gate["SigvaraGate"] --> Verifier["SigvaraVerifier"]
Gate --> NonceStore["NonceStore"]
Verifier --> Chain["Reputation Contract"]
Gate --> Config["GateConfig"]
Loading

Diagram sources

Section sources

Performance Considerations

  • The gate reads none of the status, the bond and the score until the signature and the nonce have passed. Checking the signature costs one identity read, so a caller who can form a well-sized signature for a registered agent still makes the gate do that one read.
  • It reads the matured total score directly rather than fetching the full reputation breakdown, minimizing contract calls.
  • It reads the status, the bond and the score together (and the time the score was proposed if maxScoreAgeSeconds is set, and the stake if minBond is set), so an admit costs two round trips and not three, and each read gives up after 30 seconds by default.
  • Nonce pruning runs opportunistically and stops at the first live entry in the default store, keeping admission cost proportional to recent activity rather than total lifetime state.
  • Use a shared nonce store with atomic consume in production fleets to avoid race conditions and reduce round trips.
  • The verifier keeps to 20 calls in a JSON-RPC batch, because Arc's public RPC answers 20 and refuses the rest with a rate limit, and a gate sends up to five reads together per admission, after the one for the agent's key (verifier.ts:44-46).

Section sources

Troubleshooting Guide

Common Issues and Resolutions

  • Repeated bad_proof: Check that the client signs challenge.payload and returns the correct signature format. Ensure the audience matches the gate configuration. With challengeSecret set, check that every instance has the same secret and that the client signs a challenge this gate issued.
  • Unexpected replayed: Ensure the client requests a fresh challenge for each attempt. If using multiple server instances, configure a shared nonce store with atomic consume.
  • Agents stuck at not_active: Investigate bonding, suspension, or slashing status. Do not advise earning points as the fix.
  • Agents failing at below_bond: The gate has minBond set and the agent holds less. An amount queued for withdrawal does not count.
  • Agents failing at stale_score: The gate has maxScoreAgeSeconds set. Check that the oracle is running and proposing, and that the limit is more than two of its proposals apart. Finalizing late does not make an old score read as new.
  • Agents failing at below_threshold: Review the current score and threshold. Consider adjusting the threshold relative to the reachable reputation ceiling.
  • The constructor throws: The audience is missing, the threshold is not a finite number of 0 or more, or ttlSeconds or maxScoreAgeSeconds is not a positive number. It also throws when instances is neither 'single' nor 'multiple', when it is 'multiple' and the nonceStore has no consume(), when profile is set to anything but 'production', and when profile is 'production' while instances or maxScoreAgeSeconds is missing (the message names each one missing). It also throws on a minBond that is not a bigint or a string of digits, and on a challengeSecret shorter than 32 characters. On alpha.9 only the audience is checked.
  • admit waits a long time: The RPC has gone quiet. A read is abandoned after 30 seconds, and timeoutMs changes that. On alpha.9 ethers waits five minutes.

Debugging Tips

  • Inspect the reason field in AdmitResult; it tells you exactly which layer failed.
  • Confirm that the nonce comes from the signed payload, not a caller-modified wrapper.
  • Verify that the TTL and audience are set consistently across clients and servers.
  • Monitor nonce store growth and pruning behavior in production.

Section sources

Conclusion

The Sigvara SDK gate provides a focused, secure, and debuggable authorization layer. It combines challenge-response authentication, nonce-based replay protection, activity checks, and matured reputation threshold enforcement. By returning explicit refusal reasons and separating authentication from authorization, it enables integrators to build robust reputation-based access control for web applications and other systems.

When deploying in production, prioritize:

  • A stable audience per service.
  • A shared nonce store with atomic consume, and instances: 'multiple' so the gate will not start without it.
  • profile: 'production', so the gate will not start until instances and maxScoreAgeSeconds are set.
  • Careful threshold selection aligned with the reachable reputation ceiling.
  • Clear handling of each refusal reason in user-facing flows.

Appendices

Gate Configuration Reference

Field Description Notes
rpcUrl Ethereum RPC endpoint Used by the verifier to read on-chain data
addresses Contract addresses Identity, reputation, and staking addresses
chainId Optional chain identifier Passed to the verifier, which uses it only when it builds a DID Document; the gate's own checks do not depend on it, and the DID in a challenge already carries its chain
threshold Minimum matured score required Choose against the reachable ceiling. Must be a finite number, 0 or more
audience Stable service identifier Signed into every challenge to prevent relays
ttlSeconds Challenge validity duration Default 300 seconds if omitted. Must be a positive number
nonceStore Optional custom nonce store Required for multi-instance safety, with an atomic consume; atomicNonceStore builds one
instances How many processes serve this gate: 'single' or 'multiple' Default 'single'. With 'multiple' the constructor refuses a nonceStore without consume, which must be atomic and shared by every instance. From 1.0.0-alpha.13
timeoutMs How long one chain read may take, in milliseconds Default 30000 (alpha.9 uses ethers' five minutes)
maxScoreAgeSeconds Refuse a score proposed longer ago than this many seconds, or never, with stale_score Optional and off by default. A positive number; choose more than two of your oracle's proposals apart, such as 86_400 on Arc testnet. Measured against the gate host's clock. Costs one more read per admit
minBond Refuse an agent holding less than this with below_bond, in the bond token's base units Optional. A bigint or a string of digits; a number is refused. A floor under the protocol's minimum still needs the minimum. Compares the same number as SigvaraReader.isTrusted(agent, minScore, minBond). Costs one more read per admit. From 1.0.0-alpha.16
challengeSecret Accept only challenges this gate issued, and refuse any other with bad_proof before any read Optional. At least 32 characters, and not one a caller could learn. Keeps no state, so give every instance the same secret. From 1.0.0-alpha.16
profile 'production', or unset With 'production' the constructor refuses to build the gate until instances and maxScoreAgeSeconds are both set, and names each one missing. Any other value is refused. Unset keeps the defaults. From 1.0.0-alpha.14

Section sources

Overview

Clone this wiki locally