@onchaindiligence/sdk 0.2.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.
package/README.md ADDED
@@ -0,0 +1,81 @@
1
+ # @onchaindiligence/sdk
2
+
3
+ A small, typed client for the [OnchainDiligence](https://onchaindiligence.com) compliance API. It hides the HTTP `402` pay-per-call flow entirely: you configure a funded account once, and every method transparently answers the payment challenge, settles on Tempo, and returns a typed, signed result.
4
+
5
+ ```bash
6
+ npm install @onchaindiligence/sdk mppx viem
7
+ ```
8
+
9
+ ## Why
10
+
11
+ OnchainDiligence charges per call over HTTP `402 Payment Required`. Standard clients like `fetch` or `axios` don't handle that challenge — you'd have to catch the 402, parse the payment requirements, sign a payment, and retry. This SDK does all of that for you, so a compliance check is a single typed call.
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { OnchainDiligence } from '@onchaindiligence/sdk'
17
+ import { privateKeyToAccount } from 'viem/accounts'
18
+
19
+ const od = new OnchainDiligence({
20
+ account: privateKeyToAccount(process.env.PAYER_KEY as `0x${string}`),
21
+ })
22
+
23
+ // Sanctions-screen a wallet
24
+ const wallet = await od.screen('0x7f268357A8c2552623316e2562D90e642bB538E5')
25
+ if (wallet.data.sanctioned) throw new Error('sanctioned address')
26
+
27
+ // OFAC name screening (fuzzy match)
28
+ const name = await od.screenName('Vladimir Putin')
29
+ console.log(name.data.hit, name.data.matches)
30
+
31
+ // UK company verification
32
+ const company = await od.verifyCompany('00000006')
33
+
34
+ // Combined diligence
35
+ const both = await od.diligence({
36
+ wallet: '0x7f26…',
37
+ company: '00000006',
38
+ })
39
+
40
+ // Anchor an attestation on Tempo, then verify it (free)
41
+ const anchored = await od.anchor(wallet.data ? wallet.attestation.signature! : '')
42
+ const status = await od.anchored(wallet.attestation.signature!)
43
+ ```
44
+
45
+ Every paid response is a `Signed<T>` — the result plus an `attestation` you can verify independently:
46
+
47
+ ```ts
48
+ {
49
+ data: { address: '0x…', sanctioned: false, /* … */ },
50
+ attestation: { signed: true, key_id: 'ed25519-…', signature: '…' }
51
+ }
52
+ ```
53
+
54
+ ## Methods
55
+
56
+ | Method | Returns | Paid |
57
+ |--------|---------|------|
58
+ | `screen(address)` | `Signed<SanctionsResult>` | yes |
59
+ | `screenName(name, { threshold? })` | `Signed<NameScreenResult>` | yes |
60
+ | `verifyCompany(number)` | `Signed<CompanyResult>` | yes |
61
+ | `diligence({ wallet?, company? })` | `Signed<DiligenceResult>` | yes |
62
+ | `anchor(signature)` | `Signed<AnchorResult>` | yes |
63
+ | `anchored(signature)` | `AnchorStatus` | free |
64
+ | `health()` | service status | free |
65
+
66
+ Errors throw `OnchainDiligenceError` with the HTTP `status` and a message.
67
+
68
+ ## Verifying an attestation
69
+
70
+ The `signature` is an Ed25519 signature over the response data plus issue metadata. Off-chain verification is straightforward with the public key at `/.well-known/attestation-key`:
71
+
72
+ ```ts
73
+ import { verify } from '@noble/ed25519'
74
+ // fetch the public key, reconstruct the signed bytes, then verify(signature, message, pubKey)
75
+ ```
76
+
77
+ **On-chain verification — read this first.** The EVM has no native Ed25519 precompile (only `ecrecover` for ECDSA), so verifying an Ed25519 signature *inside* a Solidity contract is expensive and non-trivial — it requires a full Ed25519 implementation in the contract. For most use cases, verify off-chain. If you need on-chain proof that a check happened, prefer the **anchoring** flow (`anchor()` / `anchored()`), which records the attestation hash on Tempo so a contract can check a `bytes32` rather than recover an Ed25519 signature. That's the cheaper, EVM-friendly path to on-chain verifiability.
78
+
79
+ ## License
80
+
81
+ MIT
@@ -0,0 +1,238 @@
1
+ /**
2
+ * @onchaindiligence/sdk
3
+ * ---------------------
4
+ * A small, typed client for the OnchainDiligence compliance API that hides the
5
+ * 402 "pay-per-call" dance entirely. You provide a funded account once; every
6
+ * method call transparently handles the payment challenge, settles on Tempo,
7
+ * and returns a typed, signed result.
8
+ *
9
+ * import { OnchainDiligence } from '@onchaindiligence/sdk'
10
+ * import { privateKeyToAccount } from 'viem/accounts'
11
+ *
12
+ * const od = new OnchainDiligence({
13
+ * account: privateKeyToAccount(process.env.PAYER_KEY),
14
+ * })
15
+ *
16
+ * const result = await od.screen('0x7f26…38E5')
17
+ * if (result.data.sanctioned) { ... }
18
+ *
19
+ * Under the hood this wraps `mppx/client`, which provides the payment-aware
20
+ * fetch. The SDK's job is ergonomics: clean methods, typed responses, and one
21
+ * place to configure the payer — so a developer never has to parse a 402,
22
+ * build a payment header, or retry a request by hand.
23
+ */
24
+ import type { Account } from 'viem';
25
+ export interface OnchainDiligenceOptions {
26
+ /** A viem account used to sign/settle payments (e.g. privateKeyToAccount). */
27
+ account: Account;
28
+ /** Base URL of the API. Defaults to production. */
29
+ baseUrl?: string;
30
+ }
31
+ /** The signed-attestation envelope every paid response carries. */
32
+ export interface Attestation {
33
+ signed: boolean;
34
+ key_id?: string;
35
+ algorithm?: string;
36
+ signature?: string;
37
+ issued_at?: string;
38
+ }
39
+ export interface Signed<T> {
40
+ data: T;
41
+ attestation: Attestation;
42
+ }
43
+ /** Result of locally verifying a signed attestation. */
44
+ export interface VerifyResult {
45
+ valid: boolean;
46
+ /** The key_id the signature was checked against, when available. */
47
+ keyId?: string;
48
+ /** Human-readable reason when `valid` is false. */
49
+ reason?: string;
50
+ }
51
+ /** One address's outcome within a re-screen batch. */
52
+ export interface RescreenItem {
53
+ address: string;
54
+ /** True if the screen call succeeded; false if it errored. */
55
+ ok: boolean;
56
+ /** Present when ok: whether the address is currently sanctioned. */
57
+ sanctioned?: boolean;
58
+ /** Present when ok: the full signed result (verify with verifyAttestation). */
59
+ result?: Signed<SanctionsResult>;
60
+ /** Present when ok is false: why this address failed. */
61
+ error?: string;
62
+ }
63
+ export interface RescreenOptions {
64
+ /** Max concurrent screens (default 4). Kept modest to respect rate limits. */
65
+ concurrency?: number;
66
+ /** Fired as each address resolves — useful for progress UI. */
67
+ onResult?: (item: RescreenItem) => void;
68
+ }
69
+ /** Summary of a re-screen batch: the "who is flagged now" answer. */
70
+ export interface RescreenReport {
71
+ total: number;
72
+ /** How many screens succeeded. */
73
+ screened: number;
74
+ /** How many came back sanctioned. */
75
+ flagged: number;
76
+ /** How many errored. */
77
+ errors: number;
78
+ /** The addresses that are currently sanctioned — act on these. */
79
+ flaggedAddresses: string[];
80
+ items: RescreenItem[];
81
+ }
82
+ export interface SanctionsResult {
83
+ address: string;
84
+ sanctioned: boolean;
85
+ identifications: unknown[];
86
+ source: string;
87
+ checked_at: string;
88
+ }
89
+ export interface NameScreenMatch {
90
+ ent_num: number;
91
+ matched_name: string;
92
+ matched_on: 'primary' | 'alias';
93
+ sdn_type: string | null;
94
+ program: string | null;
95
+ score: number;
96
+ }
97
+ export interface NameScreenResult {
98
+ query: string;
99
+ normalized_query: string;
100
+ hit: boolean;
101
+ matches: NameScreenMatch[];
102
+ list_date: string | null;
103
+ threshold: number;
104
+ source: string;
105
+ note: string;
106
+ }
107
+ export interface CompanyResult {
108
+ profile: {
109
+ companyNumber: string;
110
+ companyName: string;
111
+ status: string;
112
+ incorporatedOn?: string;
113
+ registeredAddress?: string;
114
+ };
115
+ pscList: unknown[];
116
+ source: string;
117
+ }
118
+ /** US public-company record from SEC EDGAR (public companies & funds only). */
119
+ export interface USCompanyResult {
120
+ source: string;
121
+ cik: string;
122
+ name: string | null;
123
+ former_names: string[];
124
+ entity_type: string | null;
125
+ sic: string | null;
126
+ sic_description: string | null;
127
+ state_of_incorporation: string | null;
128
+ tickers: string[];
129
+ exchanges: string[];
130
+ business_address: {
131
+ street1: string | null;
132
+ street2: string | null;
133
+ city: string | null;
134
+ state_or_country: string | null;
135
+ zip_code: string | null;
136
+ } | null;
137
+ latest_filing: {
138
+ form: string | null;
139
+ filing_date: string | null;
140
+ primary_document: string | null;
141
+ } | null;
142
+ /** Explicit reminder that "not found" means "not an SEC filer", not "not real". */
143
+ coverage_note: string;
144
+ checked_at?: string;
145
+ }
146
+ export interface DiligenceResult {
147
+ wallet_check?: unknown;
148
+ company_check?: unknown;
149
+ link_disclaimer: string;
150
+ checked_at: string;
151
+ }
152
+ export interface AnchorResult {
153
+ anchor_hash: string;
154
+ tx_hash: string | null;
155
+ already_anchored: boolean;
156
+ chain: string;
157
+ contract: string;
158
+ note: string;
159
+ }
160
+ export interface AnchorStatus {
161
+ anchor_hash: string;
162
+ anchored: boolean;
163
+ anchored_at: string | null;
164
+ chain: string;
165
+ contract: string;
166
+ }
167
+ export declare class OnchainDiligenceError extends Error {
168
+ status: number;
169
+ constructor(status: number, message: string);
170
+ }
171
+ export declare class OnchainDiligence {
172
+ private readonly baseUrl;
173
+ private readonly fetch;
174
+ private attestationKeyPem;
175
+ constructor(opts: OnchainDiligenceOptions);
176
+ private get;
177
+ private post;
178
+ private handle;
179
+ /** Sanctions-screen a wallet address against the Chainalysis on-chain oracle. */
180
+ screen(address: string): Promise<Signed<SanctionsResult>>;
181
+ /** Screen a person/company name against the OFAC SDN list (fuzzy match). */
182
+ screenName(name: string, opts?: {
183
+ threshold?: number;
184
+ }): Promise<Signed<NameScreenResult>>;
185
+ /** Verify a UK company by its Companies House registration number. */
186
+ verifyCompany(companyNumber: string): Promise<Signed<CompanyResult>>;
187
+ /**
188
+ * Verify a US public company via SEC EDGAR, by ticker, CIK, or name.
189
+ * Covers SEC-registered public companies and funds only — a "not found"
190
+ * (404) means "not an SEC filer", not "not a real company".
191
+ */
192
+ verifyUSCompany(query: string): Promise<Signed<USCompanyResult>>;
193
+ /** Run wallet + company checks together (independent results). */
194
+ diligence(params: {
195
+ wallet?: string;
196
+ company?: string;
197
+ }): Promise<Signed<DiligenceResult>>;
198
+ /**
199
+ * Re-screen a list of wallet addresses and report which are now flagged.
200
+ * The "watch my counterparties" primitive: store your address list, call
201
+ * this on a schedule, and act on `flaggedAddresses`.
202
+ *
203
+ * Each address is a separate paid `/screen` call, fanned out client-side
204
+ * with bounded concurrency — so the cost is the per-call sanctions price
205
+ * times the number of unique addresses, and every result carries its own
206
+ * signed attestation. Per-address failures are captured, not thrown, so one
207
+ * bad address never sinks the whole batch.
208
+ */
209
+ rescreen(addresses: string[], opts?: RescreenOptions): Promise<RescreenReport>;
210
+ /** Anchor an attestation's signature hash on Tempo (paid). */
211
+ anchor(signature: string): Promise<Signed<AnchorResult>>;
212
+ /** Check whether an attestation has been anchored on-chain (free). */
213
+ anchored(signature: string): Promise<AnchorStatus>;
214
+ /** Service health (free). */
215
+ health(): Promise<{
216
+ status: 'ok' | 'degraded';
217
+ upstreams: Record<string, string>;
218
+ attestation: string;
219
+ }>;
220
+ /**
221
+ * Verify a signed attestation locally. Fetches the server's published
222
+ * Ed25519 public key once (cached), then checks the signature over the
223
+ * canonical signing input `JSON.stringify({ data, issued_at, key_id })` —
224
+ * the exact bytes the server signs. A `valid: true` result means the data
225
+ * has not been altered since the server signed it, provable without trusting
226
+ * this SDK.
227
+ *
228
+ * Uses WebCrypto (`globalThis.crypto.subtle`) so it runs dependency-free in
229
+ * Node 18+, edge runtimes, and modern browsers.
230
+ */
231
+ verifyAttestation(signed: Signed<unknown>): Promise<VerifyResult>;
232
+ /**
233
+ * Fetch and cache the server's Ed25519 public key (PEM) from
234
+ * `/.well-known/attestation-key`. Accepts a raw PEM body or a JSON wrapper
235
+ * exposing the PEM under a common field name.
236
+ */
237
+ private getAttestationKeyPem;
238
+ }
package/dist/index.js ADDED
@@ -0,0 +1,251 @@
1
+ /**
2
+ * @onchaindiligence/sdk
3
+ * ---------------------
4
+ * A small, typed client for the OnchainDiligence compliance API that hides the
5
+ * 402 "pay-per-call" dance entirely. You provide a funded account once; every
6
+ * method call transparently handles the payment challenge, settles on Tempo,
7
+ * and returns a typed, signed result.
8
+ *
9
+ * import { OnchainDiligence } from '@onchaindiligence/sdk'
10
+ * import { privateKeyToAccount } from 'viem/accounts'
11
+ *
12
+ * const od = new OnchainDiligence({
13
+ * account: privateKeyToAccount(process.env.PAYER_KEY),
14
+ * })
15
+ *
16
+ * const result = await od.screen('0x7f26…38E5')
17
+ * if (result.data.sanctioned) { ... }
18
+ *
19
+ * Under the hood this wraps `mppx/client`, which provides the payment-aware
20
+ * fetch. The SDK's job is ergonomics: clean methods, typed responses, and one
21
+ * place to configure the payer — so a developer never has to parse a 402,
22
+ * build a payment header, or retry a request by hand.
23
+ */
24
+ import { Mppx, tempo } from 'mppx/client';
25
+ export class OnchainDiligenceError extends Error {
26
+ status;
27
+ constructor(status, message) {
28
+ super(message);
29
+ this.status = status;
30
+ this.name = 'OnchainDiligenceError';
31
+ }
32
+ }
33
+ const DEFAULT_BASE_URL = 'https://api.onchaindiligence.com';
34
+ export class OnchainDiligence {
35
+ baseUrl;
36
+ fetch;
37
+ attestationKeyPem = null;
38
+ constructor(opts) {
39
+ this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, '');
40
+ // Payment-aware fetch: transparently answers 402 challenges and retries.
41
+ const client = Mppx.create({ methods: [tempo({ account: opts.account })] });
42
+ this.fetch = client.fetch;
43
+ }
44
+ async get(path) {
45
+ const res = await this.fetch(`${this.baseUrl}${path}`);
46
+ return this.handle(res);
47
+ }
48
+ async post(path, body) {
49
+ const res = await this.fetch(`${this.baseUrl}${path}`, {
50
+ method: 'POST',
51
+ headers: { 'Content-Type': 'application/json' },
52
+ body: JSON.stringify(body),
53
+ });
54
+ return this.handle(res);
55
+ }
56
+ async handle(res) {
57
+ if (!res.ok) {
58
+ let detail = res.statusText;
59
+ try {
60
+ const j = (await res.json());
61
+ detail = j.detail || j.error || detail;
62
+ }
63
+ catch {
64
+ /* non-JSON error body */
65
+ }
66
+ throw new OnchainDiligenceError(res.status, detail);
67
+ }
68
+ return (await res.json());
69
+ }
70
+ // --- Checks (paid) -------------------------------------------------------
71
+ /** Sanctions-screen a wallet address against the Chainalysis on-chain oracle. */
72
+ screen(address) {
73
+ return this.get(`/screen/${encodeURIComponent(address)}`);
74
+ }
75
+ /** Screen a person/company name against the OFAC SDN list (fuzzy match). */
76
+ screenName(name, opts) {
77
+ const q = new URLSearchParams({ name });
78
+ if (opts?.threshold != null)
79
+ q.set('threshold', String(opts.threshold));
80
+ return this.get(`/screen-name?${q.toString()}`);
81
+ }
82
+ /** Verify a UK company by its Companies House registration number. */
83
+ verifyCompany(companyNumber) {
84
+ return this.get(`/company/${encodeURIComponent(companyNumber)}`);
85
+ }
86
+ /**
87
+ * Verify a US public company via SEC EDGAR, by ticker, CIK, or name.
88
+ * Covers SEC-registered public companies and funds only — a "not found"
89
+ * (404) means "not an SEC filer", not "not a real company".
90
+ */
91
+ verifyUSCompany(query) {
92
+ const qs = new URLSearchParams({ q: query });
93
+ return this.get(`/us-company?${qs.toString()}`);
94
+ }
95
+ /** Run wallet + company checks together (independent results). */
96
+ diligence(params) {
97
+ const q = new URLSearchParams();
98
+ if (params.wallet)
99
+ q.set('wallet', params.wallet);
100
+ if (params.company)
101
+ q.set('company', params.company);
102
+ return this.get(`/diligence?${q.toString()}`);
103
+ }
104
+ /**
105
+ * Re-screen a list of wallet addresses and report which are now flagged.
106
+ * The "watch my counterparties" primitive: store your address list, call
107
+ * this on a schedule, and act on `flaggedAddresses`.
108
+ *
109
+ * Each address is a separate paid `/screen` call, fanned out client-side
110
+ * with bounded concurrency — so the cost is the per-call sanctions price
111
+ * times the number of unique addresses, and every result carries its own
112
+ * signed attestation. Per-address failures are captured, not thrown, so one
113
+ * bad address never sinks the whole batch.
114
+ */
115
+ async rescreen(addresses, opts) {
116
+ const concurrency = Math.max(1, opts?.concurrency ?? 4);
117
+ const unique = [...new Set(addresses.map((a) => a.trim()).filter(Boolean))];
118
+ const items = new Array(unique.length);
119
+ let next = 0;
120
+ const worker = async () => {
121
+ for (let i = next++; i < unique.length; i = next++) {
122
+ const address = unique[i];
123
+ let item;
124
+ try {
125
+ const result = await this.screen(address);
126
+ item = { address, ok: true, sanctioned: result.data.sanctioned, result };
127
+ }
128
+ catch (err) {
129
+ item = { address, ok: false, error: err instanceof Error ? err.message : String(err) };
130
+ }
131
+ items[i] = item;
132
+ opts?.onResult?.(item);
133
+ }
134
+ };
135
+ await Promise.all(Array.from({ length: Math.min(concurrency, unique.length) }, () => worker()));
136
+ const flaggedAddresses = items.filter((it) => it.sanctioned).map((it) => it.address);
137
+ return {
138
+ total: unique.length,
139
+ screened: items.filter((it) => it.ok).length,
140
+ flagged: flaggedAddresses.length,
141
+ errors: items.filter((it) => !it.ok).length,
142
+ flaggedAddresses,
143
+ items,
144
+ };
145
+ }
146
+ /** Anchor an attestation's signature hash on Tempo (paid). */
147
+ anchor(signature) {
148
+ return this.post(`/anchor`, { signature });
149
+ }
150
+ // --- Free endpoints ------------------------------------------------------
151
+ /** Check whether an attestation has been anchored on-chain (free). */
152
+ anchored(signature) {
153
+ return this.get(`/anchored?signature=${encodeURIComponent(signature)}`);
154
+ }
155
+ /** Service health (free). */
156
+ health() {
157
+ return this.get(`/health`);
158
+ }
159
+ // --- Verification (local, free, no trust in this SDK or the server) -------
160
+ /**
161
+ * Verify a signed attestation locally. Fetches the server's published
162
+ * Ed25519 public key once (cached), then checks the signature over the
163
+ * canonical signing input `JSON.stringify({ data, issued_at, key_id })` —
164
+ * the exact bytes the server signs. A `valid: true` result means the data
165
+ * has not been altered since the server signed it, provable without trusting
166
+ * this SDK.
167
+ *
168
+ * Uses WebCrypto (`globalThis.crypto.subtle`) so it runs dependency-free in
169
+ * Node 18+, edge runtimes, and modern browsers.
170
+ */
171
+ async verifyAttestation(signed) {
172
+ const att = signed?.attestation;
173
+ if (!att || att.signed === false)
174
+ return { valid: false, reason: 'response is not signed' };
175
+ if (!att.signature || !att.key_id || !att.issued_at) {
176
+ return { valid: false, reason: 'attestation is missing signature, key_id, or issued_at' };
177
+ }
178
+ if (att.algorithm && att.algorithm !== 'ed25519') {
179
+ return { valid: false, reason: `unsupported algorithm: ${att.algorithm}`, keyId: att.key_id };
180
+ }
181
+ const subtle = globalThis.crypto?.subtle;
182
+ if (!subtle)
183
+ return { valid: false, reason: 'WebCrypto (crypto.subtle) is unavailable in this runtime' };
184
+ let key;
185
+ try {
186
+ const pem = await this.getAttestationKeyPem();
187
+ key = await subtle.importKey('spki', pemToDer(pem), { name: 'Ed25519' }, false, ['verify']);
188
+ }
189
+ catch (err) {
190
+ const msg = err instanceof Error ? err.message : String(err);
191
+ return { valid: false, reason: `could not load public key: ${msg}`, keyId: att.key_id };
192
+ }
193
+ const signingInput = JSON.stringify({
194
+ data: signed.data,
195
+ issued_at: att.issued_at,
196
+ key_id: att.key_id,
197
+ });
198
+ const ok = await subtle.verify('Ed25519', key, b64urlToBytes(att.signature), new TextEncoder().encode(signingInput));
199
+ return ok
200
+ ? { valid: true, keyId: att.key_id }
201
+ : { valid: false, reason: 'signature does not match', keyId: att.key_id };
202
+ }
203
+ /**
204
+ * Fetch and cache the server's Ed25519 public key (PEM) from
205
+ * `/.well-known/attestation-key`. Accepts a raw PEM body or a JSON wrapper
206
+ * exposing the PEM under a common field name.
207
+ */
208
+ async getAttestationKeyPem() {
209
+ if (this.attestationKeyPem)
210
+ return this.attestationKeyPem;
211
+ const res = await this.fetch(`${this.baseUrl}/.well-known/attestation-key`);
212
+ if (!res.ok) {
213
+ throw new OnchainDiligenceError(res.status, 'could not fetch attestation public key');
214
+ }
215
+ const text = await res.text();
216
+ let pem = text.trim();
217
+ if (!pem.includes('BEGIN PUBLIC KEY')) {
218
+ try {
219
+ const j = JSON.parse(text);
220
+ pem = (j.public_key_pem || j.publicKey || j.pem || j.key || '').trim();
221
+ }
222
+ catch {
223
+ /* not JSON — fall through to the check below */
224
+ }
225
+ }
226
+ if (!pem.includes('BEGIN PUBLIC KEY')) {
227
+ throw new Error('attestation-key endpoint did not return a PEM public key');
228
+ }
229
+ this.attestationKeyPem = pem;
230
+ return pem;
231
+ }
232
+ }
233
+ // --- Internal helpers (isomorphic: no Buffer, no Node built-ins) ------------
234
+ /** Decode a PEM (SPKI) public key to its raw DER bytes. */
235
+ function pemToDer(pem) {
236
+ const b64 = pem.replace(/-----[^-]+-----/g, '').replace(/\s+/g, '');
237
+ const bin = atob(b64);
238
+ const out = new Uint8Array(bin.length);
239
+ for (let i = 0; i < bin.length; i++)
240
+ out[i] = bin.charCodeAt(i);
241
+ return out;
242
+ }
243
+ /** Decode a base64url string to bytes. */
244
+ function b64urlToBytes(s) {
245
+ const b64 = s.replace(/-/g, '+').replace(/_/g, '/').padEnd(Math.ceil(s.length / 4) * 4, '=');
246
+ const bin = atob(b64);
247
+ const out = new Uint8Array(bin.length);
248
+ for (let i = 0; i < bin.length; i++)
249
+ out[i] = bin.charCodeAt(i);
250
+ return out;
251
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@onchaindiligence/sdk",
3
+ "version": "0.2.0",
4
+ "description": "Typed client for the OnchainDiligence compliance API — pay-per-call sanctions, OFAC name, and UK company checks, with the 402 payment flow handled for you.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "keywords": [
23
+ "compliance",
24
+ "sanctions",
25
+ "ofac",
26
+ "x402",
27
+ "402",
28
+ "mppx",
29
+ "tempo",
30
+ "agent",
31
+ "web3"
32
+ ],
33
+ "license": "MIT",
34
+ "peerDependencies": {
35
+ "mppx": "^0.7.0",
36
+ "viem": "^2.53.1"
37
+ },
38
+ "devDependencies": {
39
+ "typescript": "^5.9.3"
40
+ }
41
+ }