@orangecheck/sdk
The core TypeScript SDK. Everything else in the ecosystem imports from here.
Install
npm i @orangecheck/sdk
Zero Node-only dependencies beyond what's listed in package.json. Works in
Node 20+, Deno, Bun, and every modern browser.
The three load-bearing exports
import { check, createAttestation, verify } from '@orangecheck/sdk';
check(params)— the sybil-gate primitive. Discovers the most recent attestation for a subject, verifies it, applies thresholds.verify(input, opts?)— raw attestation verification. BIP-322 + live chain state; no Nostr lookup.createAttestation(opts)— build a signed envelope from a canonical message + signature.
check()
const result = await check({
addr: 'bc1q...', // OR id: '<attestation_id>' OR identity: { protocol, identifier }
minSats: 100_000,
minDays: 30,
relays: [...], // optional override — defaults to DEFAULT_RELAYS
verifyOptions: {}, // optional — passed through to verify()
});
if (result.ok) {
// result.sats, result.days, result.score, result.attestation_id, result.identities
}
The function fans out to multiple Nostr relays, verifies BIP-322 locally,
queries Esplora for current chain state, applies thresholds, and returns the
same shape the hosted /api/check returns. No server round-trip required.
verify()
const outcome = await verify(
{
addr: 'bc1q...',
msg: canonicalMessage,
sig: base64Signature,
scheme: 'bip322',
},
{ testMode: false }
);
// outcome.ok, outcome.codes, outcome.metrics, outcome.identities
Pure cryptographic + chain-state verification. Offline for steps 1–3; needs Esplora for steps 4–6. See Attest verification.
createAttestation()
const envelope = await createAttestation({
message: canonicalMessage,
signature: base64Signature,
scheme: 'bip322',
address: 'bc1q...',
identities: [{ protocol: 'github', identifier: 'alice' }],
});
// envelope.attestation_id, envelope.verification_url, …
Signed-challenge flow
import { issueChallenge, verifyChallenge } from '@orangecheck/sdk';
const challenge = issueChallenge({
address: 'bc1q...',
ttlSeconds: 300,
audience: 'https://example.com',
purpose: 'login',
});
// Client signs challenge.message via wallet, POSTs back.
const result = await verifyChallenge({
message: challenge.message,
signature: base64Signature,
expectedNonce: challenge.nonce,
expectedAudience: 'https://example.com',
expectedPurpose: 'login',
});
if (result.ok) {
// result.address — cryptographically proven
}
See Sign in with Bitcoin for the end-to-end session flow.
Lower-level building blocks
Re-exported for integrators who need finer control. Grouped by purpose:
Canonical message builders
import {
buildCanonicalMessage,
createAttestationEnvelope,
formatIdentities,
generateAttestationId,
parseIdentities,
} from '@orangecheck/sdk';
Nostr integration
import {
createAttestationEvent,
DEFAULT_RELAYS,
publishToRelays,
queryByAddress,
queryByAttestationId,
queryByIdentity,
} from '@orangecheck/sdk';
Discovery helpers
import {
discoverAttestations,
extractAttestationIdFromUrl,
getAttestationsForAddress,
getAttestationsForIdentity,
getVerificationUrl,
} from '@orangecheck/sdk';
Scoring
import { computeAllScores, computeScore } from '@orangecheck/sdk';
Human-readable status-code metadata
import type { StatusCode } from '@orangecheck/sdk';
import { STATUS_META } from '@orangecheck/sdk';
Types
import type {
AttestationEnvelope,
Challenge,
CheckParams,
CheckResult,
IdentityBinding,
StatusCode,
VerifyOutcome,
} from '@orangecheck/sdk';
Conformance
Ships vendored canonical-message + attestation-id + scoring + BIP-322
conformance vectors (tv01–tv23). yarn test runs them all. These are the
same vectors the Python SDK uses, which is how cross-impl byte identity is
guaranteed.
See also
- Canonical message format
- BIP-322 signing
- OC Attest — HTTP API — the server-side mirror of this SDK's shape
orangecheck(Python) — the Python twin