live · mainnetoc · docs
specs · api · guides
docs / quickstart

OC Pledge — Quickstart

End-to-end: construct a pledge, sign it, publish to Nostr, verify the outcome. About five minutes if you already have an OrangeCheck attestation; ten if you need to mint one first.

1. Install

yarn add @orangecheck/pledge @orangecheck/auth-client

The SDK reaches v0.1.0-beta shortly. While it ships, the protocol spec is already frozen and 28 conformance vectors are public — any conforming implementation (yours included) will produce byte-identical canonical messages.

2. Construct a pledge

import { createPledge } from '@orangecheck/pledge';

const { envelope, canonical_message, pledge_id } = await createPledge({
    swearer: 'bc1qexampleaddress…',
    proposition: 'I will publish my Q3 results by block 920_000.',
    resolution: {
        mechanism: 'stamp_published',
        query: 'content_hash:sha256:7d…',
    },
    resolves_at: { block: 920_000 },
    expires_at: '2026-08-01T00:00:00Z',
    bond: {
        attestation_id: '<your OrangeCheck attestation id>',
        min_sats: 1_000_000,
        min_days: 90,
    },
});

The SDK refuses any combination that can't resolve to a deterministic boolean — for example a proposition paired with a non-existent mechanism, a resolves_at past expires_at, or an attestation_id the SDK can't verify exists.

3. Sign with the wallet

import { signPledge } from '@orangecheck/pledge';
import { sign } from '@orangecheck/wallet-adapter';

const sig = await sign({ address: swearer, message: canonical_message });
const signed = signPledge({ envelope, sig });

The wallet shows the signer the exact canonical message before signing — human-readable, plaintext. The user is committing to bytes they can read.

4. Publish

import { publish } from '@orangecheck/pledge';

const { event_id } = await publish(signed);
// → posted to default Nostr relays as a kind-30078 replaceable event
//   under d-tag `oc-pledge:<pledge_id>`.

5. Verify the outcome

import { resolvePledge, verify, getState } from '@orangecheck/pledge';

// At any time after `resolves_at`:
const outcome = await resolvePledge({ envelope: signed });
// → outcome envelope: { outcome: 'kept' | 'broken' | …, evidence: {…} }

const result = await verify({ envelope: signed, outcome });
// → { ok: true, state: 'kept', bond_status: 'satisfied', … }

const state = getState(signed, outcome, Date.now());
// → 'kept'

For deterministic mechanisms (chain_state, nostr_event_exists, stamp_published, http_get_hash, dns_record), every observer with access to the same public state produces the byte-identical outcome envelope — no resolver is required.

For counterparty_signs and vote_resolves, the outcome envelope must be signed by the designated resolver. Absence of that signature within expires_at resolves the pledge to expired_unresolved.

6. Show the public history

import { pledgesSworn, outcomesFor, bondInFlight } from '@orangecheck/pledge';

const ids = await pledgesSworn('bc1q…');
const outcomes = await outcomesFor('bc1q…');
const inFlight = await bondInFlight('bc1q…');

These are raw metrics. The protocol ships no aggregated score and no reference reputation function — by design. Platforms compute derived policies from raw queries; that's the composition surface.

What's next