docs / @orangecheck/sdk

@orangecheck/sdk

The core protocol SDK. Three functions cover 90% of integrations; the rest are building blocks.

yarn add @orangecheck/sdk

The three load-bearing functions

check()

Sybil-gate primitive. Discovers the most recent attestation for a subject, verifies the signature, recomputes metrics, compares against thresholds.

import { check } from '@orangecheck/sdk';

const r = await check({
  addr:    'bc1q...',
  minSats: 100_000,
  minDays: 30,
});

if (r.ok) {
  // let them through
} else {
  console.log('rejected:', r.reasons);
}

Accepts addr, id (attestation ID), or identity ({ protocol, identifier }). Returns a CheckResult.

verify()

Raw (addr, msg, sig) verification. Use when you have the envelope in hand.

import { verify } from '@orangecheck/sdk';

const outcome = await verify({
  addr:   'bc1q...',
  msg:    canonicalMessage,
  sig:    signature,
  scheme: 'bip322',
});

if (outcome.ok) {
  const { sats_bonded, days_unspent, score } = outcome.metrics!;
}

createAttestation()

Package a signed message + signature into a self-contained JSON envelope.

import {
  buildCanonicalMessage,
  createAttestation,
  publishAttestation,
} from '@orangecheck/sdk';

const message = buildCanonicalMessage(
  { address, identities },
  { bond: '1000000', expires: '2027-01-15T12:00:00Z' }
);

const signature = await userWallet.signMessage(message);

const envelope = await createAttestation({
  message, signature, scheme: 'bip322', address, identities,
});

// Optional — publish to Nostr
await publishAttestation({ envelope, npub: userNpub });

Signed-challenge auth

For gates that can't trust the address source (public headers, query strings):

import { issueChallenge, verifyChallenge } from '@orangecheck/sdk';

// Server — issue
const c = issueChallenge({
  address: userAddr,
  audience: 'https://example.com',
  purpose: 'login',
});
req.session.ocNonce = c.nonce;
res.json({ message: c.message });

// Server — verify (on POST)
const r = await verifyChallenge({
  message:       req.body.message,
  signature:     req.body.signature,
  expectedNonce: req.session.ocNonce,
});
if (r.ok) req.session.verifiedAddress = r.address;   // cryptographically proven

See Sign in with Bitcoin for the full flow.

Discovery

import {
  discoverAttestations,
  getAttestationsForAddress,
  getAttestationsForIdentity,
} from '@orangecheck/sdk';

await discoverAttestations({ attestationId: 'a3f5b8c2…' });
await getAttestationsForAddress('bc1q...');
await getAttestationsForIdentity('github', 'alice');

Identity verification

Handle ownership is self-asserted inside a signed message. Verify out-of-band:

import { verifyIdentity } from '@orangecheck/sdk';

const r = await verifyIdentity({
  protocol:      'github',
  identifier:    'alice',
  attestationId: envelope.attestation_id,
  proof:         'https://gist.github.com/alice/abc123',
});

Supported protocols: nostr, github, dns, twitter.

Public API surface

// Load-bearing
check(params: CheckParams): Promise<CheckResult>
verify(input: VerifyInput, options?: VerifyOptions): Promise<VerifyOutcome>
createAttestation(options: CreateAttestationOptions): Promise<AttestationEnvelope>

// Signed-challenge auth
issueChallenge(options): Challenge
verifyChallenge(options): Promise<VerifyChallengeResult>

// Building blocks
buildCanonicalMessage(...)
generateAttestationId(msg: string): Promise<string>
publishAttestation({ envelope, npub, relays? })
discoverAttestations({ attestationId | address | identity, relays? })
verifyIdentity({ protocol, identifier, attestationId, proof? })
computeScore(sats, days, { algorithm: 'v0' | 'tier' | 'none' })

Full types are exported — import with:

import type {
  CheckResult,
  VerifyOutcome,
  AttestationEnvelope,
  IdentityBinding,
  Challenge,
} from '@orangecheck/sdk';

Guarantees

  • No custody. Signs messages; never spends coins.
  • No telemetry. The SDK talks only to public Bitcoin explorers and Nostr relays you pass in.
  • Offline-verifiable. verify() for an envelope you already hold needs only the message, signature, address, and chain state.

Tree-shaking

Every function has a dedicated entry point:

import { check }   from '@orangecheck/sdk/check';
import { verify }  from '@orangecheck/sdk/verify';
import { issueChallenge } from '@orangecheck/sdk/challenge';
import { computeScore }   from '@orangecheck/sdk/scoring';

License

MIT.