@orangecheck/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +277 -0
  3. package/dist/attestation.d.mts +44 -0
  4. package/dist/attestation.d.ts +44 -0
  5. package/dist/attestation.js +579 -0
  6. package/dist/attestation.js.map +1 -0
  7. package/dist/attestation.mjs +567 -0
  8. package/dist/attestation.mjs.map +1 -0
  9. package/dist/canonical.d.mts +15 -0
  10. package/dist/canonical.d.ts +15 -0
  11. package/dist/canonical.js +109 -0
  12. package/dist/canonical.js.map +1 -0
  13. package/dist/canonical.mjs +102 -0
  14. package/dist/canonical.mjs.map +1 -0
  15. package/dist/challenge.d.mts +40 -0
  16. package/dist/challenge.d.ts +40 -0
  17. package/dist/challenge.js +169 -0
  18. package/dist/challenge.js.map +1 -0
  19. package/dist/challenge.mjs +146 -0
  20. package/dist/challenge.mjs.map +1 -0
  21. package/dist/identity.d.mts +88 -0
  22. package/dist/identity.d.ts +88 -0
  23. package/dist/identity.js +730 -0
  24. package/dist/identity.js.map +1 -0
  25. package/dist/identity.mjs +702 -0
  26. package/dist/identity.mjs.map +1 -0
  27. package/dist/index.d.mts +47 -0
  28. package/dist/index.d.ts +47 -0
  29. package/dist/index.js +2004 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/index.mjs +1930 -0
  32. package/dist/index.mjs.map +1 -0
  33. package/dist/nostr.d.mts +19 -0
  34. package/dist/nostr.d.ts +19 -0
  35. package/dist/nostr.js +395 -0
  36. package/dist/nostr.js.map +1 -0
  37. package/dist/nostr.mjs +382 -0
  38. package/dist/nostr.mjs.map +1 -0
  39. package/dist/scoring.d.mts +16 -0
  40. package/dist/scoring.d.ts +16 -0
  41. package/dist/scoring.js +75 -0
  42. package/dist/scoring.js.map +1 -0
  43. package/dist/scoring.mjs +69 -0
  44. package/dist/scoring.mjs.map +1 -0
  45. package/dist/types-BJKDroFv.d.mts +62 -0
  46. package/dist/types-BJKDroFv.d.ts +62 -0
  47. package/dist/verify.d.mts +6 -0
  48. package/dist/verify.d.ts +6 -0
  49. package/dist/verify.js +509 -0
  50. package/dist/verify.js.map +1 -0
  51. package/dist/verify.mjs +485 -0
  52. package/dist/verify.mjs.map +1 -0
  53. package/package.json +106 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 OrangeCheck
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # `@orangecheck/sdk`
2
+
3
+ **Proof of Bitcoin stake for the open web.**
4
+
5
+ A sybil-resistance primitive for forums, Nostr relays, airdrop gates, DAO votes, and any open platform that needs to filter bots without KYC. Bitcoin UTXOs are the cheapest credible signal of commitment: honest users pay nothing, attackers pay real opportunity cost.
6
+
7
+ No custody. No account. No permission. The SDK is MIT; the protocol is open forever.
8
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ yarn add @orangecheck/sdk
15
+ ```
16
+
17
+ ---
18
+
19
+ ## The 30-second integration
20
+
21
+ ### 1. Gate any action with one call
22
+
23
+ ```ts
24
+ import { check } from '@orangecheck/sdk';
25
+
26
+ // "Only let this user post if they have 100k sats unspent for 30 days."
27
+ const result = await check({
28
+ addr: 'bc1q...',
29
+ minSats: 100_000,
30
+ minDays: 30,
31
+ });
32
+
33
+ if (result.ok) {
34
+ // let them through
35
+ } else {
36
+ console.log('rejected:', result.reasons); // e.g. ['below_min_sats']
37
+ }
38
+ ```
39
+
40
+ `check()` finds the most recent attestation for the subject, verifies the Bitcoin signature, recomputes sats + days from live chain state, and compares against your thresholds. One call.
41
+
42
+ ### 2. Or call the hosted API — no SDK needed at all
43
+
44
+ ```bash
45
+ curl "https://ochk.io/api/check?addr=bc1q...&min_sats=100000&min_days=30"
46
+ # { "ok": true, "sats": 125000, "days": 47, "score": 18.2, ... }
47
+ ```
48
+
49
+ Same logic, zero dependencies. Useful from any language, any runtime, any shell script.
50
+
51
+ ### 3. Verify a raw attestation (no Nostr round-trip)
52
+
53
+ ```ts
54
+ import { verify } from '@orangecheck/sdk';
55
+
56
+ const outcome = await verify({
57
+ addr: 'bc1q...',
58
+ msg: canonicalMessage, // the exact signed text
59
+ sig: signature,
60
+ scheme: 'bip322',
61
+ });
62
+
63
+ if (outcome.ok) {
64
+ console.log(outcome.metrics); // { sats_bonded, days_unspent, score }
65
+ }
66
+ ```
67
+
68
+ Use this when you already hold the `(addr, msg, sig)` tuple — e.g., a user pasted it into your UI, or you're verifying an offline proof.
69
+
70
+ ---
71
+
72
+ ## Express / Next middleware
73
+
74
+ Want drop-in route-gating? Use [`@orangecheck/gate`](https://www.npmjs.com/package/@orangecheck/gate) (wraps this SDK):
75
+
76
+ ```ts
77
+ import { ocGate } from '@orangecheck/gate';
78
+
79
+ app.post('/post', ocGate({ minSats: 100_000, minDays: 30 }), handler);
80
+ ```
81
+
82
+ The gate expects the caller to pass `?ocAddr=...` (or a signed header) carrying their address. See the gate's README for signature schemes.
83
+
84
+ ---
85
+
86
+ ## Signed-challenge auth (proving address control)
87
+
88
+ For gates that can't trust the address source — e.g. when the client sends the address in a public header or query string — prove control first with a short-lived BIP-322 challenge. The proven address goes into a signed session cookie or JWT, and the gate reads from there.
89
+
90
+ ```ts
91
+ // --- Server: issue a challenge ---
92
+ // --- Client: ask the user's wallet to sign c.message with BIP-322 ---
93
+
94
+ // --- Server: verify ---
95
+ import { issueChallenge, verifyChallenge } from '@orangecheck/sdk';
96
+
97
+ app.get('/auth/challenge', (req, res) => {
98
+ const c = issueChallenge({
99
+ address: req.query.addr,
100
+ ttlSeconds: 300,
101
+ audience: 'https://example.com', // optional origin binding
102
+ purpose: 'login', // optional label
103
+ });
104
+ // Remember the nonce against the session so we can defeat replay on verify.
105
+ req.session.ocNonce = c.nonce;
106
+ res.json({ message: c.message, nonce: c.nonce, expiresAt: c.expiresAt });
107
+ });
108
+
109
+ app.post('/auth/verify', async (req, res) => {
110
+ const { message, signature } = req.body;
111
+ const r = await verifyChallenge({
112
+ message,
113
+ signature,
114
+ expectedNonce: req.session.ocNonce,
115
+ expectedAudience: 'https://example.com',
116
+ expectedPurpose: 'login',
117
+ });
118
+
119
+ if (!r.ok) return res.status(401).json({ reason: r.reason });
120
+
121
+ // r.address is cryptographically proven. Stash it on the session.
122
+ req.session.verifiedAddress = r.address;
123
+ res.json({ ok: true, address: r.address });
124
+ });
125
+ ```
126
+
127
+ The challenge message uses a distinct `orangecheck-auth` header and `ack` literal, so a signed auth challenge can never be confused with a reputation attestation. Pair with `@orangecheck/gate` like so:
128
+
129
+ ```ts
130
+ app.post(
131
+ '/post',
132
+ ocGate({
133
+ minSats: 100_000,
134
+ address: { from: (req) => req.session.verifiedAddress },
135
+ }),
136
+ handler
137
+ );
138
+ ```
139
+
140
+ Now the gate doesn't have to trust the client — the address it sees has been cryptographically proven earlier in the session.
141
+
142
+ ---
143
+
144
+ ## Creating an attestation (issuer side)
145
+
146
+ If you're building a wallet, a Nostr client, or a settings page where users should be able to _create_ a proof, use:
147
+
148
+ ```ts
149
+ import { buildCanonicalMessage, createAttestation, publishAttestation } from '@orangecheck/sdk';
150
+
151
+ // 1. Build the canonical message
152
+ const message = buildCanonicalMessage(
153
+ {
154
+ address: 'bc1q...',
155
+ identities: [
156
+ { protocol: 'nostr', identifier: 'npub1alice...' },
157
+ { protocol: 'github', identifier: 'alice' },
158
+ ],
159
+ },
160
+ {
161
+ bond: '1000000', // 1M sats
162
+ expires: '2027-01-15T12:00:00Z', // optional
163
+ }
164
+ );
165
+
166
+ // 2. Hand to the user's wallet (BIP-322)
167
+ const signature = await userWallet.signMessage(message);
168
+
169
+ // 3. Package into an attestation envelope
170
+ const envelope = await createAttestation({
171
+ message,
172
+ signature,
173
+ scheme: 'bip322',
174
+ address: 'bc1q...',
175
+ identities: [
176
+ { protocol: 'nostr', identifier: 'npub1alice...' },
177
+ { protocol: 'github', identifier: 'alice' },
178
+ ],
179
+ });
180
+
181
+ // 4. Optional: publish to Nostr for decentralized discovery
182
+ await publishAttestation({ envelope, npub: userNpub });
183
+
184
+ // envelope.attestation_id is the content-addressed SHA-256 of the message —
185
+ // share it as a URL, embed, or QR.
186
+ ```
187
+
188
+ ---
189
+
190
+ ## What an attestation proves
191
+
192
+ | Claim | Strength | How a verifier checks |
193
+ | ---------------------------------- | ------------------- | -------------------------------------- |
194
+ | You control address `bc1q…` | Cryptographic | BIP-322 signature verification |
195
+ | The address holds `N` sats | On-chain, trustless | Live query to mempool.space / Esplora |
196
+ | The bonded UTXO is `N` days old | On-chain, trustless | Confirmation time compared to now |
197
+ | You claim to be `@alice` on GitHub | **Self-asserted** | Out-of-band (gist, DNS TXT, tweet URL) |
198
+
199
+ `verify()` covers the first three. The fourth is up to the relying party — use `verifyIdentity()` for structured out-of-band checks on Nostr, GitHub, DNS, and Twitter.
200
+
201
+ ---
202
+
203
+ ## Identity verification (optional)
204
+
205
+ ```ts
206
+ import { verifyIdentity } from '@orangecheck/sdk';
207
+
208
+ const result = await verifyIdentity({
209
+ protocol: 'github',
210
+ identifier: 'alice',
211
+ attestationId: envelope.attestation_id,
212
+ proof: 'https://gist.github.com/alice/abc123',
213
+ });
214
+
215
+ if (result.verified) {
216
+ // the GitHub gist contains the attestation ID → handle ownership proven
217
+ }
218
+ ```
219
+
220
+ Identity bindings are self-asserted inside the signed message. They are **claims**, not proofs. Always verify out-of-band before honoring them.
221
+
222
+ ---
223
+
224
+ ## Scoring
225
+
226
+ The reference algorithm is intentionally simple:
227
+
228
+ ```
229
+ score_v0 = round( ln(1 + sats_bonded) × (1 + days_unspent / 30), 2 )
230
+ ```
231
+
232
+ This is **advisory**. Relying parties should compare `sats_bonded` and `days_unspent` against their own thresholds rather than trusting a displayed score. `check()` does exactly that.
233
+
234
+ ---
235
+
236
+ ## API surface (the short list)
237
+
238
+ ```ts
239
+ // Load-bearing — start here.
240
+ check(params: CheckParams): Promise<CheckResult>
241
+ verify(input: VerifyInput, options?: VerifyOptions): Promise<VerifyOutcome>
242
+ createAttestation(options: CreateAttestationOptions): Promise<AttestationEnvelope>
243
+
244
+ // Signed-challenge auth (proving address control).
245
+ issueChallenge(options): Challenge
246
+ verifyChallenge(options): Promise<VerifyChallengeResult>
247
+
248
+ // Building blocks.
249
+ buildCanonicalMessage(...)
250
+ generateAttestationId(msg: string): Promise<string>
251
+ publishAttestation({ envelope, npub, relays? })
252
+ discoverAttestations({ attestationId | address | identity, relays? })
253
+ verifyIdentity({ protocol, identifier, attestationId, proof? })
254
+ ```
255
+
256
+ Full type definitions ship with the package.
257
+
258
+ ---
259
+
260
+ ## Guarantees
261
+
262
+ - **No custody.** The SDK never touches user funds. It signs messages, it doesn't spend coins.
263
+ - **No telemetry.** The SDK makes network calls only to public Bitcoin explorers (mempool.space, blockstream.info) and Nostr relays you pass in. No OrangeCheck server is in the path for `verify()`.
264
+ - **Offline-verifiable.** Given `(addr, msg, sig)`, anyone — including you, sitting on an island with a Raspberry Pi — can verify.
265
+
266
+ ---
267
+
268
+ ## License
269
+
270
+ MIT. The protocol is CC-BY-4.0.
271
+
272
+ ## Links
273
+
274
+ - **Website**: https://ochk.io
275
+ - **Protocol spec**: https://ochk.io/protocol
276
+ - **API docs**: https://ochk.io/docs
277
+ - **Hosted verifier**: `https://ochk.io/api/check`
@@ -0,0 +1,44 @@
1
+ import { S as Scheme, I as IdentityBinding, A as AttestationEnvelope } from './types-BJKDroFv.mjs';
2
+
3
+ interface CreateAttestationOptions {
4
+ message: string;
5
+ signature: string;
6
+ scheme: Scheme;
7
+ address: string;
8
+ identities?: IdentityBinding[];
9
+ extensions?: Record<string, string | undefined>;
10
+ }
11
+ interface PublishAttestationOptions {
12
+ envelope: AttestationEnvelope;
13
+ npub: string;
14
+ relays?: string[];
15
+ }
16
+ interface DiscoverAttestationOptions {
17
+ attestationId?: string;
18
+ address?: string;
19
+ identity?: {
20
+ protocol: string;
21
+ identifier: string;
22
+ };
23
+ relays?: string[];
24
+ }
25
+ interface PublishResult {
26
+ success: string[];
27
+ failed: string[];
28
+ }
29
+ declare class AttestationError extends Error {
30
+ readonly code?: string | undefined;
31
+ constructor(message: string, code?: string | undefined);
32
+ }
33
+ declare function createAttestation(options: CreateAttestationOptions): Promise<AttestationEnvelope>;
34
+ declare function publishAttestation(options: PublishAttestationOptions): Promise<PublishResult>;
35
+ declare function discoverAttestations(options: DiscoverAttestationOptions): Promise<AttestationEnvelope[]>;
36
+ declare function verifyAttestationById(attestationId: string, relays?: string[]): Promise<AttestationEnvelope | null>;
37
+ declare function getAttestationsForAddress(address: string, relays?: string[]): Promise<AttestationEnvelope[]>;
38
+ declare function getAttestationsForIdentity(protocol: string, identifier: string, relays?: string[]): Promise<AttestationEnvelope[]>;
39
+ declare function parseIdentitiesFromString(identitiesStr: string): IdentityBinding[];
40
+ declare function formatIdentitiesForDisplay(identities: IdentityBinding[]): string;
41
+ declare function getVerificationUrl(attestationId: string, baseUrl?: string): string;
42
+ declare function extractAttestationIdFromUrl(url: string): string | null;
43
+
44
+ export { AttestationError, type CreateAttestationOptions, type DiscoverAttestationOptions, type PublishAttestationOptions, type PublishResult, createAttestation, discoverAttestations, extractAttestationIdFromUrl, formatIdentitiesForDisplay, getAttestationsForAddress, getAttestationsForIdentity, getVerificationUrl, parseIdentitiesFromString, publishAttestation, verifyAttestationById };
@@ -0,0 +1,44 @@
1
+ import { S as Scheme, I as IdentityBinding, A as AttestationEnvelope } from './types-BJKDroFv.js';
2
+
3
+ interface CreateAttestationOptions {
4
+ message: string;
5
+ signature: string;
6
+ scheme: Scheme;
7
+ address: string;
8
+ identities?: IdentityBinding[];
9
+ extensions?: Record<string, string | undefined>;
10
+ }
11
+ interface PublishAttestationOptions {
12
+ envelope: AttestationEnvelope;
13
+ npub: string;
14
+ relays?: string[];
15
+ }
16
+ interface DiscoverAttestationOptions {
17
+ attestationId?: string;
18
+ address?: string;
19
+ identity?: {
20
+ protocol: string;
21
+ identifier: string;
22
+ };
23
+ relays?: string[];
24
+ }
25
+ interface PublishResult {
26
+ success: string[];
27
+ failed: string[];
28
+ }
29
+ declare class AttestationError extends Error {
30
+ readonly code?: string | undefined;
31
+ constructor(message: string, code?: string | undefined);
32
+ }
33
+ declare function createAttestation(options: CreateAttestationOptions): Promise<AttestationEnvelope>;
34
+ declare function publishAttestation(options: PublishAttestationOptions): Promise<PublishResult>;
35
+ declare function discoverAttestations(options: DiscoverAttestationOptions): Promise<AttestationEnvelope[]>;
36
+ declare function verifyAttestationById(attestationId: string, relays?: string[]): Promise<AttestationEnvelope | null>;
37
+ declare function getAttestationsForAddress(address: string, relays?: string[]): Promise<AttestationEnvelope[]>;
38
+ declare function getAttestationsForIdentity(protocol: string, identifier: string, relays?: string[]): Promise<AttestationEnvelope[]>;
39
+ declare function parseIdentitiesFromString(identitiesStr: string): IdentityBinding[];
40
+ declare function formatIdentitiesForDisplay(identities: IdentityBinding[]): string;
41
+ declare function getVerificationUrl(attestationId: string, baseUrl?: string): string;
42
+ declare function extractAttestationIdFromUrl(url: string): string | null;
43
+
44
+ export { AttestationError, type CreateAttestationOptions, type DiscoverAttestationOptions, type PublishAttestationOptions, type PublishResult, createAttestation, discoverAttestations, extractAttestationIdFromUrl, formatIdentitiesForDisplay, getAttestationsForAddress, getAttestationsForIdentity, getVerificationUrl, parseIdentitiesFromString, publishAttestation, verifyAttestationById };