docs / @orangecheck/sdk

@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 (tv01tv23). 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