Docs
Developer Docs
Actionable integration docs with real endpoints, constraints, and copy-ready snippets.
Navigate
Docs
What SIGNAL.ZERO is (in plain terms)
SIGNAL.ZERO is Proof of Continuity Infrastructure (PoCI). It gives you a simple, verifiable way to answer one question about a wallet:
Has this wallet maintained intent over time (kept showing up), in a way you can reference in code?
The innovation is not the app UI. The innovation is the Commitment Object: a wallet’s activation + continuity history that other systems can read.
How it works
- Genesis: an ERC-721 activation badge (one per wallet). Until a wallet holds Genesis, signals are rejected.
- Signals: one wallet-signed check-in per 24h window. No content. Just presence.
- Read surfaces: your app reads /status, /history, or /query/continuity and makes decisions.
What you get
- Proof of reliability (not wealth): a way to rank or gate by consistency.
- Long-term access based on behavior, not balance.
- Trust without identity: a usable signal without KYC or profiles.
- Sybil friction without KYC: not perfect sybil-proofing, but a real cost to maintaining many wallets over time.
Example: a membership wants to limit access to people who actually show up. Gate access to wallets with hasGenesis and at least 10 signals in the last 30 days.
Reliability
Uptime stance (best-effort)
SIGNAL.ZERO currently operates on a best-effort basis. There is no formal SLA published yet.
Practical guidance for production:
- Cache /status responses briefly (seconds to minutes) in your backend.
- Retry on transient failures (HTTP 5xx / network) with exponential backoff + jitter.
- Handle 429 rate_limited with backoff and request coalescing.
Concept
The problem PoCI solves
Web3 can verify ownership and transactions. It cannot reliably verify maintained intent.
Today there is no standard on-chain primitive for:
- Proof of reliability (not wealth)
- Long-term access based on behavior, not balance
- Trust without identity
- Anti-sybil mechanisms without KYC (sybil friction through time cost)
- Memberships based on consistency
- Employment / contributor credibility based on commitment
- Governance weight based on commitment, not tokens
SIGNAL.ZERO solves this by producing a commitment object that is readable and composable:Genesis + continuity history.
Your product decides the meaning. SIGNAL.ZERO gives you a standard input that can’t be faked with a single transaction.
Why we can honestly say “we started this”: we named the category (PoCI) and shipped the canonical minimal primitive.
Setup
What you need before coding
You’ll usually integrate via:(1) your backend (Partner API keys) and(2) your frontend (wallet actions).
Backend: call Partner endpoints using x-api-key. Keep this key server-side.
Frontend (optional): implement Genesis mint + signal signing/submit.
Start
Integrate in 10 minutes
1) Read continuity with the public endpoint:GET /v1/wallet/<address>/status.
2) If you need bulk evaluation, create a Partner key and call Partner endpoints from your server.
3) If you want users to check-in inside your product, implement Genesis mint +POST /api/signal.
4) Handle expected failures via the Errors & handling tab.
Security
Auth model + safe integration
There are two security surfaces:public reads andpartner reads.
Partner API keys are bearer tokens. Treat x-api-key like a production secret. Never ship it in a browser, mobile app bundle, or client-side code.
Signals are validated server-side:
- Signature must match the wallet address.
- Message format must match exactly (including day + chainId).
- Only supported chains are accepted.
- One signal per 24h window (cooldown enforced).
- Genesis gating is enforced (no Genesis, no signal).
Threat model notes
- Do not treat continuity as identity. It is a behavior signal.
- Continuity creates sybil friction, but it is not perfect sybil-proofing by itself.
- Cache read results for short windows to reduce rate-limit pressure.
Security
Auth / authz flow diagrams
These diagrams describe the two primary flows: Partner Console auth, and API key usage.
Partner Console sign-in
User Wallet
| (1) GET /api/partner/challenge
v
SIGNAL.ZERO Server -> returns message + nonce
| (2) wallet signs message
v
User Wallet
| (3) POST /api/partner/verify { address, signature }
v
SIGNAL.ZERO Server -> verifies signature, creates session cookie
|
v
Console UI is now authenticated
Partner API key usage (server-side only)
Your Backend
| (1) store x-api-key in env secret
| (2) call partner endpoints with header x-api-key
v
SIGNAL.ZERO API
| validates key + tier + rate limit + environment gating
v
JSON response / error codeNever ship x-api-key in a browser bundle. Use a backend proxy if you need client-side access.
Chains
Where SIGNAL.ZERO runs
Live deployment (current): Base Sepolia (84532).
Genesis contract (Base Sepolia):0xD1B7dBcAAeF23e28f66a43EC69f09B1962bd158C.
Production target: Base mainnet (8453) after thorough testing and auditing.
For integrators: build and validate on Base Sepolia. The API surfaces are designed to remain stable when mainnet launches.
Reliability
Data freshness + consistency model
Continuity data is derived from recorded signals. Genesis ownership is derived from on-chain ownership.
The /status endpoint is designed to be safe for gating: it consults stored state, and when needed, checks the chain to self-heal Genesis ownership.
What to expect:
- Signals are available immediately after acceptance.
- Genesis may be indexed asynchronously, but /status can confirm via chain when needed.
- For strict product decisions, treat /status as request-time truth and cache briefly.
Environment
TEST vs PROD (what changes)
Partner keys are issued with an environment. The environment determines which chain is used for gating and which endpoints are permitted.
| Environment | Default chain | Primary usage | Restrictions |
|---|---|---|---|
| TEST | Base Sepolia (84532) | Development + staging | Bulk continuity query is not available (requires PROD). |
| PROD | Base (8453) | Production integrations | Bulk continuity query requires PROD. Partner owner must hold Genesis for gated partner endpoints. |
Practical rules:
- Keep partner keys server-side only.
- Use TEST keys for integration work on Base Sepolia.
- Use PROD keys for production (Base mainnet) when available; bulk evaluation usesPOST /v1/query/continuity (PROD-only).
- Expect prod_required when attempting bulk continuity from TEST.
Limits
Quotas (and how to design for them)
Rate limits are enforced per API key and scope. Design your integration to cache, batch, and backoff.
| Tier | wallet_history | query_continuity |
|---|---|---|
| SANDBOX | 30 / min | 10 / min |
| BUILDER | 120 / min | 30 / min |
| PARTNER | 600 / min | 120 / min |
Public endpoints are also rate-limited:GET /v1/wallet/<address>/status is limited to60 requests / minute per IP. Treat public reads as shared infrastructure and cache responses.
Use
Relatable things web3 cannot do today (and PoCI enables)
These are real product problems where token balance and one-off transactions are the wrong tool.
- Long-term access based on behavior: unlock roles only after 14 active days.
- Membership based on consistency: keep communities high-quality without turning it into a paywall.
- Contributor credibility: screen applicants by maintained commitment rather than self-reported claims.
- Governance weight based on commitment: weight votes by continuity, not just tokens (your governance decides the rule).
- Sybil friction without KYC: time becomes a cost to maintaining many wallets.
Implementations: read /status for single-wallet decisions, read /history when you need auditability, and use /query/continuity for bulk eligibility.
Guarantees
What is guaranteed (and what is not)
SIGNAL.ZERO is intentionally narrow. These are the guarantees you can rely on in production logic.
- Signals are wallet-signed and verified server-side.
- One signal per 24h window (cooldown enforced).
- Genesis gating is enforced for signal writes.
- Genesis status is on-chain. When indexing lags, /status can confirm ownership via chain.
- Continuity fields are deterministic given the recorded signal history (streaks, totals, windows).
Not guaranteed:
- Identity (one human can control multiple wallets).
- Perfect sybil resistance (PoCI adds cost, it does not prove uniqueness).
Policy
API versioning and stability
APIs are versioned under /v1. Backwards-incompatible changes will ship under a new major version (for example /v2).
Compatibility guidelines:
- Do not rely on undocumented fields.
- Handle unknown fields safely (forward compatible parsing).
- Expect new error codes over time; default to safe fallback behavior.
Roadmap
What’s next (practical)
This roadmap is written to match the current build and the current integration surfaces.
Now
Base Sepolia continuity + Genesis activation + public and partner read APIs.
Next
Hardening: mainnet deployment, stronger partner tooling, and more deterministic integration patterns.
Later
SDKs and broader integrations, without increasing noise or turning continuity into content.
Base
Why SIGNAL.ZERO is Base-native
PoCI is a daily primitive. If fees are high or UX is fragile, users churn and the signal becomes meaningless.
- Low fees make daily actions realistic.
- Reliable wallet UX makes signatures and minting predictable for mainstream users.
- Base is a strong environment for identity and access primitives.
Base Sepolia is used for iteration; Base mainnet is the long-term target for production-grade continuity.
UI
Signals UI (how users check in)
Users check in once per day by signing a message with their wallet and submitting the signature.
Your UI should:build the exact message,request signature, then callPOST /api/signal.
Handle:genesis_required (show mint CTA),cooldown_active (show countdown), and signature/message errors.
Signals UI (custom)
Ask the wallet to sign the exact check-in message, then submit to POST /api/signal.
const day = new Date().toISOString().slice(0, 10);
const chainId = 84532;
const address = account.address;
const message = [
"SIGNAL.ZERO CHECK-IN",
`Address: ${address}`,
`Day: ${day}`,
`ChainId: ${chainId}`,
].join("\n");
const signature = await walletClient.signMessage({ account, message });
const res = await fetch("https://www.signalzero.ink/api/signal", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ address, chainId, message, signature }),
});
const json = await res.json();
console.log(json);GET /api/signal
Returns check-in stats (total signals, streak, last/next allowed timestamps).
curl "https://www.signalzero.ink/api/signal?address=0xYOUR_ADDRESS&chainId=8453"UI
Genesis mint UI (activation)
Genesis is the activation badge. Until a wallet has Genesis, signals are rejected withgenesis_required.
You can either:link to hosted mint(/genesis) or embed a custom mint flow in your app.
Genesis mint UI (custom)
Mint the Genesis badge contract on the supported chain from your app.
import { createPublicClient, http, isAddress } from "viem";
import { baseSepolia } from "viem/chains";
const GENESIS_BADGE_ADDRESS = process.env.NEXT_PUBLIC_GENESIS_BADGE_ADDRESS_TEST;
if (!GENESIS_BADGE_ADDRESS || !isAddress(GENESIS_BADGE_ADDRESS)) throw new Error("missing_genesis_address");
const genesisMintAbi = [
{
type: "function",
name: "publicPrice",
stateMutability: "view",
inputs: [],
outputs: [{ name: "price", type: "uint256" }],
},
{
type: "function",
name: "mintPublic",
stateMutability: "payable",
inputs: [],
outputs: [],
},
] as const;
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });
const price = await publicClient.readContract({
address: GENESIS_BADGE_ADDRESS,
abi: genesisMintAbi,
functionName: "publicPrice",
});
const { request } = await publicClient.simulateContract({
account,
address: GENESIS_BADGE_ADDRESS,
abi: genesisMintAbi,
functionName: "mintPublic",
value: price,
});
const hash = await walletClient.writeContract(request);
console.log({ hash });Partner
What the API key does
Partner keys authorize access to key-gated endpoints (for example, wallet history and bulk continuity queries).
Create a key in Console and store it on your server as an environment secret.
Common mistakes:shipping keys in frontend,calling PROD endpoints from the browser,not handling rate_limited.
Do not ship partner keys in a public frontend.
SDK
TypeScript SDK (absolute beginner)
This is the easiest way to use SIGNAL.ZERO in code. You install a small library, then call functions instead of writing raw HTTP requests.
1) Install:
npm install @signal-zero/sdk2) Pick which client you need:
SignalZeroPublicClient = no API key (safe for public reads).
SignalZeroPartnerClient = requires an API key (server-side only).
Security rule (very important): never ship your partner key in a browser bundle. Keep it on your server (environment variable / secret).
Runtime: Node 18+ (uses the built-in fetch API).
If you want UI building blocks: @signal-zero/ui is available.
Templates
Copy/paste integration templates
These examples show the safe pattern: your frontend calls your backend, and only your backend talks to SIGNAL.ZERO with a partner key.
Next.js (App Router) — server route
// app/api/signalzero/history/route.ts
import { NextResponse } from "next/server";
import { SignalZeroPartnerClient } from "@signal-zero/sdk";
export const runtime = "nodejs";
export async function GET(req: Request) {
const url = new URL(req.url);
const address = url.searchParams.get("address") ?? "";
const client = new SignalZeroPartnerClient({
baseUrl: "https://www.signalzero.ink",
apiKey: process.env.SIGNALZERO_API_KEY!,
});
const history = await client.walletHistory(address);
return NextResponse.json(history);
}Express — backend endpoint
import express from "express";
import { SignalZeroPartnerClient } from "@signal-zero/sdk";
const app = express();
app.get("/api/signalzero/history", async (req, res) => {
const address = String(req.query.address ?? "");
const client = new SignalZeroPartnerClient({
baseUrl: "https://www.signalzero.ink",
apiKey: process.env.SIGNALZERO_API_KEY!,
});
const history = await client.walletHistory(address);
res.json(history);
});
Python FastAPI — backend endpoint
import os
import requests
from fastapi import FastAPI
app = FastAPI()
@app.get("/api/signalzero/history")
def history(address: str):
res = requests.get(
"https://www.signalzero.ink/v1/wallet/" + address + "/history?limit=200",
headers={"x-api-key": os.environ["SIGNALZERO_API_KEY"]},
timeout=20,
)
res.raise_for_status()
return res.json()
TypeScript SDK (public client)
Server-safe public reads. No API key required.
import { SignalZeroPublicClient } from "@signal-zero/sdk";
const client = new SignalZeroPublicClient({ baseUrl: "https://www.signalzero.ink" });
const status = await client.walletStatus("0xYOUR_ADDRESS");
console.log(status);TypeScript SDK (partner client)
Server-side partner reads. Keep x-api-key in backend env only.
import { SignalZeroPartnerClient } from "@signal-zero/sdk";
const client = new SignalZeroPartnerClient({
baseUrl: "https://www.signalzero.ink",
apiKey: process.env.SIGNALZERO_API_KEY,
});
const history = await client.walletHistory("0xYOUR_ADDRESS");
console.log(history);SDK
Python SDK (official)
Official Python SDK for SIGNAL.ZERO.
1) Install:
pip install signalzero-sdk2) Public reads (no API key):
from signalzero_sdk import SignalZeroPublicClient
client = SignalZeroPublicClient(base_url="https://www.signalzero.ink")
status = client.wallet_status("0xYOUR_ADDRESS", chain_id=84532)
print(status)3) Partner reads (server-side only):
import os
from signalzero_sdk import SignalZeroPartnerClient
client = SignalZeroPartnerClient(
base_url="https://www.signalzero.ink",
api_key=os.environ["SIGNALZERO_API_KEY"],
)
history = client.wallet_history("0xYOUR_ADDRESS", limit=200)
print(history)Security rule (very important): never ship your partner key in a browser, mobile bundle, or client-side code.
SDK
React UI components + hooks (official)
Install:
npm install @signal-zero/uiQuickstart (read /status and render a badge):
import { useSignalZeroStatus, ContinuityBadge } from "@signal-zero/ui";
export function WalletContinuity({ address }: { address: string }) {
const { data, loading, error } = useSignalZeroStatus({
address,
chainId: 84532,
});
if (loading) return <div>Loading…</div>;
if (error) return <div>Failed: {error}</div>;
if (!data) return null;
return <ContinuityBadge status={data} />;
}The UI library is client-safe: it only uses public reads. Keep partner keys in your backend.
SDK
Any language (OpenAPI)
For any language, use the OpenAPI document:GET /openapi.json.
Think of OpenAPI like a blueprint for the API. Tools can read it and generate a client library for: Python, Java, Go, C#, and more.
You can generate clients with tools like OpenAPI Generator / Swagger Codegen. Keep partner keys on the server in every language.
# Example (OpenAPI Generator CLI)
# openapi-generator-cli generate -i https://www.signalzero.ink/openapi.json -g python -o ./signalzero_client
# openapi-generator-cli generate -i https://www.signalzero.ink/openapi.json -g typescript-fetch -o ./signalzero_ts_clientIf you are a beginner: you can ignore OpenAPI and just use the TypeScript SDK above.
OpenAPI spec
Fetch the public OpenAPI document for codegen and typed clients.
curl "https://www.signalzero.ink/openapi.json"Errors
Expected failures (and how to handle them)
genesis_required (403): wallet cannot signal (or a PROD partner key can’t be used) until Genesis is minted. If mintUrl is returned, deep link the user to mint.
cooldown_active (429): wallet already checked in within the last 24h. Show nextAllowedAt as a countdown.
invalid_signature (401): signature did not verify. Re-prompt wallet to sign and ensure the message format is exact.
invalid_message / day_mismatch / address_mismatch / chain_mismatch (400): client constructed an invalid check-in payload.
missing_api_key / invalid_api_key (401): your server is not authenticating correctly.
tier_inactive (403): plan is not active.
rate_limited (429): you exceeded limits. Implement backoff + caching.
invalid_wallets (400): partner bulk query must contain 1..500 valid addresses.
invalid_address / invalid_chain (400): bad parameters.
Endpoint
Wallet status (public)
Method: GET
Path: /v1/wallet/<address>/status
Auth: none (rate-limited).
Query: chainId (optional). Defaults to the site’s configured chain.
Returns: hasGenesis, totalSignals, streak fields, and continuityStatus.
GET /v1/wallet/<address>/status
Public. Returns the continuity snapshot for a wallet on a chain.
curl "https://www.signalzero.ink/v1/wallet/0xYOUR_ADDRESS/status?chainId=8453"Endpoint
Wallet history (partner)
Method: GET
Path: /v1/wallet/<address>/history
Auth: x-api-key (server-side only).
Query: limit (optional, 1..1000; default 200).
Returns: list of check-in day values with timestamps.
GET /v1/wallet/<address>/history
Key required (TEST or PROD). Returns recent signal days for a wallet on the key’s environment chain.
curl "https://www.signalzero.ink/v1/wallet/0xYOUR_ADDRESS/history?limit=200" \
-H "x-api-key: sz_test_..."Endpoint
Bulk continuity query (PROD key required)
Method: POST
Path: /v1/query/continuity
Auth: x-api-key (server-side only).
Body: wallets (1..500), minSignals, optional fromDay/toDay window.
Returns: per-wallet pass and snapshot.
POST /v1/query/continuity
PROD key required. Bulk evaluate wallets against a continuity window.
curl -X POST "https://www.signalzero.ink/v1/query/continuity" \
-H "content-type: application/json" \
-H "x-api-key: sz_prod_..." \
-d '{"wallets":["0xYOUR_ADDRESS"],"fromDay":"2026-01-01","toDay":"2026-01-31","minSignals":10}'Endpoint
Signals (wallet-native)
POST /api/signal submits a daily check-in.
Requirements: wallet signature + Genesis badge + supported chain + 24h cooldown.
On denial, you may receive mintUrl (for Genesis) or nextAllowedAt (for cooldown).
GET /api/signal returns stats for display (total signals, streak, last/next allowed).
GET /api/signal
Returns check-in stats (total signals, streak, last/next allowed timestamps).
curl "https://www.signalzero.ink/api/signal?address=0xYOUR_ADDRESS&chainId=8453"POST /api/signal
Wallet-native check-in. Requires Genesis. Enforces signature, chain support, and a 24h cooldown.
const day = new Date().toISOString().slice(0, 10);
const chainId = 84532;
const address = account.address;
const message = [
"SIGNAL.ZERO CHECK-IN",
`Address: ${address}`,
`Day: ${day}`,
`ChainId: ${chainId}`,
].join("\n");
const signature = await walletClient.signMessage({ account, message });
const res = await fetch("https://www.signalzero.ink/api/signal", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ address, chainId, message, signature }),
});
const json = await res.json();
console.log(json);Deterministic gating example
Read /status and gate based on hasGenesis + continuity fields.
curl "https://www.signalzero.ink/v1/wallet/0xYOUR_ADDRESS/status?chainId=84532"