@affix-io/sdk 2.0.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.
@@ -0,0 +1,21 @@
1
+ import type { SdkConfig } from "./types.js";
2
+ export type ApiResponse<T> = {
3
+ ok: boolean;
4
+ status: number;
5
+ data: T;
6
+ };
7
+ export declare class AffixApiClient {
8
+ private readonly config;
9
+ constructor(config: SdkConfig);
10
+ private headers;
11
+ health(): Promise<Record<string, unknown>>;
12
+ listCircuits(): Promise<Record<string, unknown>>;
13
+ prepareWitness(body: Record<string, unknown>): Promise<Record<string, unknown>>;
14
+ prove(circuitId: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
15
+ verify(circuitId: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
16
+ merkleAudit(body: Record<string, unknown>): Promise<Record<string, unknown>>;
17
+ merkleRoot(): Promise<Record<string, unknown>>;
18
+ private get;
19
+ private post;
20
+ private parse;
21
+ }
package/dist/client.js ADDED
@@ -0,0 +1,72 @@
1
+ import { requestJson } from "./http.js";
2
+ export class AffixApiClient {
3
+ config;
4
+ constructor(config) {
5
+ this.config = config;
6
+ }
7
+ headers() {
8
+ return {
9
+ Accept: "application/json",
10
+ "Content-Type": "application/json",
11
+ Authorization: `Bearer ${this.config.apiKey}`,
12
+ "X-API-Key": this.config.apiKey,
13
+ "User-Agent": "AffixIO-SDK-Web/2.0",
14
+ };
15
+ }
16
+ async health() {
17
+ return this.get("/api/health");
18
+ }
19
+ async listCircuits() {
20
+ return this.get("/v1/circuits");
21
+ }
22
+ async prepareWitness(body) {
23
+ return this.post("/v1/witness/prepare", body);
24
+ }
25
+ async prove(circuitId, body) {
26
+ return this.post(`/v1/circuits/${encodeURIComponent(circuitId)}/prove`, body);
27
+ }
28
+ async verify(circuitId, body) {
29
+ return this.post(`/v1/circuits/${encodeURIComponent(circuitId)}/verify`, body);
30
+ }
31
+ async merkleAudit(body) {
32
+ return this.post("/v1/merkle/audit", body);
33
+ }
34
+ async merkleRoot() {
35
+ return this.get("/v1/merkle/root");
36
+ }
37
+ async get(path) {
38
+ const res = await requestJson(`${this.config.apiBase}${path}`, {
39
+ method: "GET",
40
+ headers: this.headers(),
41
+ timeoutMs: this.config.timeoutMs ?? 120_000,
42
+ });
43
+ return this.parse(res);
44
+ }
45
+ async post(path, body) {
46
+ const res = await requestJson(`${this.config.apiBase}${path}`, {
47
+ method: "POST",
48
+ headers: this.headers(),
49
+ body: JSON.stringify(body),
50
+ timeoutMs: this.config.timeoutMs ?? 120_000,
51
+ });
52
+ return this.parse(res);
53
+ }
54
+ async parse(res) {
55
+ let data = {};
56
+ try {
57
+ data = (await res.json());
58
+ }
59
+ catch {
60
+ data = { error: "invalid_json" };
61
+ }
62
+ if (!res.ok) {
63
+ const message = typeof data.message === "string"
64
+ ? data.message
65
+ : typeof data.error === "string"
66
+ ? data.error
67
+ : `http_${res.status}`;
68
+ throw new Error(message);
69
+ }
70
+ return data;
71
+ }
72
+ }
@@ -0,0 +1,4 @@
1
+ import type { SdkConfig } from "./types.js";
2
+ export declare function resolveConfig(partial: Partial<SdkConfig> & {
3
+ apiKey?: string;
4
+ }): SdkConfig;
package/dist/config.js ADDED
@@ -0,0 +1,17 @@
1
+ import { loadEnv } from "./env.js";
2
+ export function resolveConfig(partial) {
3
+ loadEnv();
4
+ const apiKey = partial.apiKey ?? process.env.AFFIX_API_KEY?.trim();
5
+ if (!apiKey) {
6
+ throw new Error("api_key_required: set AFFIX_API_KEY in .env or pass apiKey to AffixSDK");
7
+ }
8
+ return {
9
+ apiBase: partial.apiBase ?? process.env.AFFIX_API_BASE ?? "https://api.affix-io.com",
10
+ apiKey,
11
+ requestAttestation: partial.requestAttestation ?? true,
12
+ sector: partial.sector,
13
+ offlineQueuePath: partial.offlineQueuePath ?? ".affix/offline-queue.json",
14
+ proofStorePath: partial.proofStorePath ?? ".affix/proofs.json",
15
+ timeoutMs: partial.timeoutMs ?? 120_000,
16
+ };
17
+ }
@@ -0,0 +1,3 @@
1
+ import type { AffixCredential } from "./types.js";
2
+ /** Noir witness inputs need field elements; hash string labels the same way the API does. */
3
+ export declare function normalizeCredential(credential: AffixCredential): AffixCredential;
@@ -0,0 +1,10 @@
1
+ import { fieldInputHex } from "./field-input.js";
2
+ /** Noir witness inputs need field elements; hash string labels the same way the API does. */
3
+ export function normalizeCredential(credential) {
4
+ return {
5
+ ...credential,
6
+ schema_id: fieldInputHex(credential.schema_id),
7
+ issuer_pubkey_hash: fieldInputHex(credential.issuer_pubkey_hash),
8
+ credential_id: fieldInputHex(credential.credential_id),
9
+ };
10
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ export declare const ENV_FILE_PATH: string;
2
+ /** Load variables from this package's .env (does not override existing process.env). */
3
+ export declare function loadEnv(): void;
4
+ export declare function maskApiKey(key: string): string;
5
+ export type EnvStatus = {
6
+ env_file: string;
7
+ env_file_exists: boolean;
8
+ api_key_set: boolean;
9
+ api_key_preview: string | null;
10
+ api_base: string;
11
+ };
12
+ export declare function envStatus(): EnvStatus;
package/dist/env.js ADDED
@@ -0,0 +1,55 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
5
+ export const ENV_FILE_PATH = join(packageRoot, ".env");
6
+ let loaded = false;
7
+ function parseEnvLine(line) {
8
+ const trimmed = line.trim();
9
+ if (!trimmed || trimmed.startsWith("#"))
10
+ return null;
11
+ const eq = trimmed.indexOf("=");
12
+ if (eq <= 0)
13
+ return null;
14
+ const key = trimmed.slice(0, eq).trim();
15
+ let value = trimmed.slice(eq + 1).trim();
16
+ if ((value.startsWith('"') && value.endsWith('"')) ||
17
+ (value.startsWith("'") && value.endsWith("'"))) {
18
+ value = value.slice(1, -1);
19
+ }
20
+ return [key, value];
21
+ }
22
+ /** Load variables from this package's .env (does not override existing process.env). */
23
+ export function loadEnv() {
24
+ if (loaded)
25
+ return;
26
+ loaded = true;
27
+ if (!existsSync(ENV_FILE_PATH))
28
+ return;
29
+ const text = readFileSync(ENV_FILE_PATH, "utf8");
30
+ for (const line of text.split("\n")) {
31
+ const parsed = parseEnvLine(line);
32
+ if (!parsed)
33
+ continue;
34
+ const [key, value] = parsed;
35
+ if (process.env[key] === undefined) {
36
+ process.env[key] = value;
37
+ }
38
+ }
39
+ }
40
+ export function maskApiKey(key) {
41
+ if (key.length <= 8)
42
+ return "****";
43
+ return `${key.slice(0, 4)}…${key.slice(-4)}`;
44
+ }
45
+ export function envStatus() {
46
+ loadEnv();
47
+ const apiKey = process.env.AFFIX_API_KEY?.trim() ?? "";
48
+ return {
49
+ env_file: ENV_FILE_PATH,
50
+ env_file_exists: existsSync(ENV_FILE_PATH),
51
+ api_key_set: apiKey.length > 0,
52
+ api_key_preview: apiKey ? maskApiKey(apiKey) : null,
53
+ api_base: process.env.AFFIX_API_BASE ?? "https://api.affix-io.com",
54
+ };
55
+ }
@@ -0,0 +1,2 @@
1
+ /** Map labels or hex to BN254 field elements (matches api.affix-io.com encoding). */
2
+ export declare function fieldInputHex(value: string | number | bigint): string;
@@ -0,0 +1,35 @@
1
+ import { createHash } from "node:crypto";
2
+ const FR_MODULUS = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
3
+ function reduceField(value) {
4
+ return ((value % FR_MODULUS) + FR_MODULUS) % FR_MODULUS;
5
+ }
6
+ function bytesToField(bytes) {
7
+ let acc = 0n;
8
+ for (const byte of bytes) {
9
+ acc = (acc << 8n) + BigInt(byte);
10
+ }
11
+ return reduceField(acc);
12
+ }
13
+ function fieldToBigInt(value) {
14
+ if (typeof value === "bigint")
15
+ return reduceField(value);
16
+ if (typeof value === "number")
17
+ return reduceField(BigInt(value));
18
+ const raw = String(value).trim();
19
+ if (!raw)
20
+ return 0n;
21
+ if (raw.startsWith("0x") || raw.startsWith("0X")) {
22
+ return reduceField(BigInt(raw));
23
+ }
24
+ if (/^[0-9]+$/.test(raw)) {
25
+ return reduceField(BigInt(raw));
26
+ }
27
+ return bytesToField(createHash("sha256").update(raw, "utf8").digest());
28
+ }
29
+ function fieldToHex(value) {
30
+ return `0x${reduceField(value).toString(16)}`;
31
+ }
32
+ /** Map labels or hex to BN254 field elements (matches api.affix-io.com encoding). */
33
+ export function fieldInputHex(value) {
34
+ return fieldToHex(fieldToBigInt(value));
35
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ export type HttpResponse = {
2
+ status: number;
3
+ ok: boolean;
4
+ json(): Promise<Record<string, unknown>>;
5
+ };
6
+ export declare function requestJson(url: string, options: {
7
+ method?: string;
8
+ headers?: Record<string, string>;
9
+ body?: string;
10
+ timeoutMs?: number;
11
+ }): Promise<HttpResponse>;
package/dist/http.js ADDED
@@ -0,0 +1,62 @@
1
+ import { readFileSync, existsSync } from "node:fs";
2
+ import http from "node:http";
3
+ import https from "node:https";
4
+ import tls from "node:tls";
5
+ import { dirname, join } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
8
+ const EXTRA_CA_PATH = join(packageRoot, "certs", "sectigo-r36.pem");
9
+ let caBundle;
10
+ function getCaBundle() {
11
+ if (caBundle)
12
+ return caBundle;
13
+ caBundle = [...tls.rootCertificates];
14
+ if (existsSync(EXTRA_CA_PATH)) {
15
+ caBundle.push(readFileSync(EXTRA_CA_PATH, "utf8"));
16
+ }
17
+ return caBundle;
18
+ }
19
+ export async function requestJson(url, options) {
20
+ const parsed = new URL(url);
21
+ const isHttps = parsed.protocol === "https:";
22
+ const lib = isHttps ? https : http;
23
+ return new Promise((resolve, reject) => {
24
+ const req = lib.request({
25
+ protocol: parsed.protocol,
26
+ hostname: parsed.hostname,
27
+ port: parsed.port || (isHttps ? 443 : 80),
28
+ path: `${parsed.pathname}${parsed.search}`,
29
+ method: options.method ?? "GET",
30
+ headers: options.headers,
31
+ ca: isHttps ? getCaBundle() : undefined,
32
+ }, (res) => {
33
+ const chunks = [];
34
+ res.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
35
+ res.on("end", () => {
36
+ const text = Buffer.concat(chunks).toString("utf8");
37
+ const status = res.statusCode ?? 0;
38
+ resolve({
39
+ status,
40
+ ok: status >= 200 && status < 300,
41
+ async json() {
42
+ try {
43
+ return JSON.parse(text);
44
+ }
45
+ catch {
46
+ return { error: "invalid_json", raw: text };
47
+ }
48
+ },
49
+ });
50
+ });
51
+ });
52
+ req.on("error", reject);
53
+ if (options.timeoutMs) {
54
+ req.setTimeout(options.timeoutMs, () => {
55
+ req.destroy(new Error("request_timeout"));
56
+ });
57
+ }
58
+ if (options.body)
59
+ req.write(options.body);
60
+ req.end();
61
+ });
62
+ }
@@ -0,0 +1,38 @@
1
+ import { AffixApiClient } from "./client.js";
2
+ import type { AffixCredential, ProveResult, SdkConfig, VerifyResult, WitnessContext, WitnessPackage } from "./types.js";
3
+ export type ProveInput = {
4
+ circuitId: string;
5
+ credential?: AffixCredential;
6
+ context?: WitnessContext;
7
+ witness?: WitnessPackage;
8
+ fields?: Record<string, string | number | boolean>;
9
+ sector?: string;
10
+ requestAttestation?: boolean;
11
+ queueOnFailure?: boolean;
12
+ };
13
+ export declare class AffixSDK {
14
+ readonly client: AffixApiClient;
15
+ private readonly queue;
16
+ private readonly store;
17
+ constructor(config?: Partial<SdkConfig> & {
18
+ apiKey?: string;
19
+ });
20
+ private readonly config;
21
+ isOnline(): Promise<boolean>;
22
+ buildWitness(circuitId: string, credential: AffixCredential, context?: Partial<WitnessContext>): Promise<WitnessPackage>;
23
+ prove(input: ProveInput): Promise<ProveResult>;
24
+ verify(circuitId: string, proof: string, requestAttestation?: boolean): Promise<VerifyResult>;
25
+ proveAndVerify(input: ProveInput): Promise<{
26
+ prove: ProveResult;
27
+ verify: VerifyResult;
28
+ }>;
29
+ flushOfflineQueue(): Promise<ProveResult[]>;
30
+ listQueuedJobs(): import("./types.js").QueuedProveJob[];
31
+ listStoredProofs(): import("./types.js").StoredProof[];
32
+ private mapProveResult;
33
+ }
34
+ export { AffixApiClient } from "./client.js";
35
+ export { ENV_FILE_PATH, envStatus, loadEnv, maskApiKey } from "./env.js";
36
+ export type { EnvStatus } from "./env.js";
37
+ export { defaultContext, prepareWitness, randomFieldHex, witnessFromInputs } from "./witness.js";
38
+ export * from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,154 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { AffixApiClient } from "./client.js";
3
+ import { resolveConfig } from "./config.js";
4
+ import { OfflineQueue } from "./offline-queue.js";
5
+ import { ProofStore } from "./proof-store.js";
6
+ import { normalizeCredential } from "./credential-normalize.js";
7
+ import { redactProveBodyForQueue } from "./queue-body.js";
8
+ import { defaultContext, prepareWitness } from "./witness.js";
9
+ export class AffixSDK {
10
+ client;
11
+ queue;
12
+ store;
13
+ constructor(config = {}) {
14
+ const resolved = resolveConfig(config);
15
+ this.client = new AffixApiClient(resolved);
16
+ this.queue = new OfflineQueue(resolved.offlineQueuePath);
17
+ this.store = new ProofStore(resolved.proofStorePath);
18
+ this.config = resolved;
19
+ }
20
+ config;
21
+ async isOnline() {
22
+ try {
23
+ const health = await this.client.health();
24
+ return health.status === "ok";
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ async buildWitness(circuitId, credential, context) {
31
+ return prepareWitness(this.client, circuitId, credential, defaultContext(context));
32
+ }
33
+ async prove(input) {
34
+ const body = {
35
+ requestAttestation: input.requestAttestation ?? this.config.requestAttestation,
36
+ sector: input.sector ?? this.config.sector,
37
+ };
38
+ if (input.witness) {
39
+ body.witness = input.witness;
40
+ }
41
+ else if (input.credential) {
42
+ body.credential = normalizeCredential(input.credential);
43
+ body.context = defaultContext(input.context);
44
+ }
45
+ else if (input.fields) {
46
+ body.fields = input.fields;
47
+ }
48
+ else {
49
+ throw new Error("prove_requires_witness_or_credential_or_fields");
50
+ }
51
+ try {
52
+ const data = await this.client.prove(input.circuitId, body);
53
+ const result = this.mapProveResult(input.circuitId, data, input.witness);
54
+ this.store.save({
55
+ proof_id: result.proof_id,
56
+ circuit_id: result.circuit_id,
57
+ proof: result.proof,
58
+ proof_digest: result.proof_digest,
59
+ valid: result.valid,
60
+ created_at: new Date().toISOString(),
61
+ synced: Boolean(result.merkle_root),
62
+ merkle_root: result.merkle_root,
63
+ });
64
+ return result;
65
+ }
66
+ catch (err) {
67
+ if (input.queueOnFailure !== false) {
68
+ this.queue.enqueue({
69
+ id: randomUUID(),
70
+ circuit_id: input.circuitId,
71
+ body: await redactProveBodyForQueue(input.circuitId, {
72
+ ...body,
73
+ circuit_id: input.circuitId,
74
+ }),
75
+ });
76
+ }
77
+ throw err;
78
+ }
79
+ }
80
+ async verify(circuitId, proof, requestAttestation = true) {
81
+ const data = await this.client.verify(circuitId, {
82
+ proof,
83
+ requestAttestation,
84
+ sector: this.config.sector,
85
+ });
86
+ return {
87
+ proof_id: String(data.proof_id ?? ""),
88
+ valid: Boolean(data.valid),
89
+ verified: Boolean(data.verified ?? data.valid),
90
+ decision: data.decision === "yes" ? "yes" : "no",
91
+ circuit_id: circuitId,
92
+ proof_digest: String(data.proof_digest ?? ""),
93
+ merkle_root: typeof data.merkle_root === "string" ? data.merkle_root : undefined,
94
+ merkle_leaf_hash: typeof data.merkle_leaf_hash === "string" ? data.merkle_leaf_hash : undefined,
95
+ attestation: data.attestation,
96
+ };
97
+ }
98
+ async proveAndVerify(input) {
99
+ const prove = await this.prove(input);
100
+ const verify = await this.verify(input.circuitId, prove.proof, input.requestAttestation ?? true);
101
+ return { prove, verify };
102
+ }
103
+ async flushOfflineQueue() {
104
+ const results = [];
105
+ for (const job of this.queue.list()) {
106
+ try {
107
+ const data = await this.client.prove(job.circuit_id, job.body);
108
+ const result = this.mapProveResult(job.circuit_id, data);
109
+ this.store.save({
110
+ proof_id: result.proof_id,
111
+ circuit_id: result.circuit_id,
112
+ proof: result.proof,
113
+ proof_digest: result.proof_digest,
114
+ valid: result.valid,
115
+ created_at: new Date().toISOString(),
116
+ synced: Boolean(result.merkle_root),
117
+ merkle_root: result.merkle_root,
118
+ });
119
+ this.queue.remove(job.id);
120
+ results.push(result);
121
+ }
122
+ catch {
123
+ this.queue.bumpAttempt(job.id);
124
+ }
125
+ }
126
+ return results;
127
+ }
128
+ listQueuedJobs() {
129
+ return this.queue.list();
130
+ }
131
+ listStoredProofs() {
132
+ return this.store.list();
133
+ }
134
+ mapProveResult(circuitId, data, witness) {
135
+ return {
136
+ proof_id: String(data.proof_id ?? ""),
137
+ circuit_id: String(data.circuit_id ?? circuitId),
138
+ proof: String(data.proof ?? ""),
139
+ valid: Boolean(data.valid),
140
+ verified: typeof data.verified === "boolean" ? data.verified : Boolean(data.valid),
141
+ decision: data.decision === "yes" ? "yes" : "no",
142
+ proof_digest: String(data.proof_digest ?? ""),
143
+ return_value: typeof data.return_value === "string" ? data.return_value : undefined,
144
+ merkle_root: typeof data.merkle_root === "string" ? data.merkle_root : undefined,
145
+ merkle_leaf_hash: typeof data.merkle_leaf_hash === "string" ? data.merkle_leaf_hash : undefined,
146
+ attestation: data.attestation,
147
+ witness,
148
+ };
149
+ }
150
+ }
151
+ export { AffixApiClient } from "./client.js";
152
+ export { ENV_FILE_PATH, envStatus, loadEnv, maskApiKey } from "./env.js";
153
+ export { defaultContext, prepareWitness, randomFieldHex, witnessFromInputs } from "./witness.js";
154
+ export * from "./types.js";
@@ -0,0 +1,11 @@
1
+ import type { QueuedProveJob } from "./types.js";
2
+ export declare class OfflineQueue {
3
+ private readonly path;
4
+ constructor(path: string);
5
+ private read;
6
+ private write;
7
+ enqueue(job: Omit<QueuedProveJob, "created_at" | "attempts">): QueuedProveJob;
8
+ list(): QueuedProveJob[];
9
+ remove(id: string): void;
10
+ bumpAttempt(id: string): void;
11
+ }
@@ -0,0 +1,52 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export class OfflineQueue {
4
+ path;
5
+ constructor(path) {
6
+ this.path = path;
7
+ }
8
+ read() {
9
+ if (!existsSync(this.path)) {
10
+ return { jobs: [] };
11
+ }
12
+ try {
13
+ const raw = JSON.parse(readFileSync(this.path, "utf8"));
14
+ return { jobs: raw.jobs ?? [] };
15
+ }
16
+ catch {
17
+ return { jobs: [] };
18
+ }
19
+ }
20
+ write(data) {
21
+ mkdirSync(dirname(this.path), { recursive: true });
22
+ writeFileSync(this.path, JSON.stringify(data, null, 2));
23
+ }
24
+ enqueue(job) {
25
+ const data = this.read();
26
+ const full = {
27
+ ...job,
28
+ created_at: new Date().toISOString(),
29
+ attempts: 0,
30
+ };
31
+ data.jobs.push(full);
32
+ this.write(data);
33
+ return full;
34
+ }
35
+ list() {
36
+ return this.read().jobs;
37
+ }
38
+ remove(id) {
39
+ const data = this.read();
40
+ data.jobs = data.jobs.filter((job) => job.id !== id);
41
+ this.write(data);
42
+ }
43
+ bumpAttempt(id) {
44
+ const data = this.read();
45
+ for (const job of data.jobs) {
46
+ if (job.id === id) {
47
+ job.attempts += 1;
48
+ }
49
+ }
50
+ this.write(data);
51
+ }
52
+ }
@@ -0,0 +1,10 @@
1
+ import type { StoredProof } from "./types.js";
2
+ export declare class ProofStore {
3
+ private readonly path;
4
+ constructor(path: string);
5
+ private read;
6
+ private write;
7
+ save(proof: StoredProof): void;
8
+ list(): StoredProof[];
9
+ markSynced(proof_id: string, merkle_root?: string): void;
10
+ }
@@ -0,0 +1,44 @@
1
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ export class ProofStore {
4
+ path;
5
+ constructor(path) {
6
+ this.path = path;
7
+ }
8
+ read() {
9
+ if (!existsSync(this.path)) {
10
+ return { proofs: [] };
11
+ }
12
+ try {
13
+ return JSON.parse(readFileSync(this.path, "utf8"));
14
+ }
15
+ catch {
16
+ return { proofs: [] };
17
+ }
18
+ }
19
+ write(data) {
20
+ mkdirSync(dirname(this.path), { recursive: true });
21
+ writeFileSync(this.path, JSON.stringify(data, null, 2));
22
+ }
23
+ save(proof) {
24
+ const data = this.read();
25
+ data.proofs.unshift(proof);
26
+ data.proofs = data.proofs.slice(0, 500);
27
+ this.write(data);
28
+ }
29
+ list() {
30
+ return this.read().proofs;
31
+ }
32
+ markSynced(proof_id, merkle_root) {
33
+ const data = this.read();
34
+ for (const proof of data.proofs) {
35
+ if (proof.proof_id === proof_id) {
36
+ proof.synced = true;
37
+ if (merkle_root) {
38
+ proof.merkle_root = merkle_root;
39
+ }
40
+ }
41
+ }
42
+ this.write(data);
43
+ }
44
+ }
@@ -0,0 +1,2 @@
1
+ /** Strip credential/context; persist witness-only bodies for offline resume. */
2
+ export declare function redactProveBodyForQueue(circuitId: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
@@ -0,0 +1,38 @@
1
+ import { defaultContext } from "./witness.js";
2
+ const WITNESS_PKG = "@affix-io/sdk-witness";
3
+ async function loadWitnessBuilder() {
4
+ try {
5
+ const mod = await import(WITNESS_PKG);
6
+ if (typeof mod.buildLocalWitness !== "function") {
7
+ throw new Error("witness_export_missing");
8
+ }
9
+ return mod.buildLocalWitness;
10
+ }
11
+ catch {
12
+ throw new Error("offline_witness_unavailable: credential-based offline queue requires the private @affix-io/sdk-witness package. " +
13
+ "Install it from AffixIO, or pass witness/fields instead of credential, or set queueOnFailure: false.");
14
+ }
15
+ }
16
+ /** Strip credential/context; persist witness-only bodies for offline resume. */
17
+ export async function redactProveBodyForQueue(circuitId, body) {
18
+ const out = {
19
+ requestAttestation: body.requestAttestation,
20
+ sector: body.sector,
21
+ circuit_id: body.circuit_id ?? circuitId,
22
+ };
23
+ if (body.witness && typeof body.witness === "object") {
24
+ out.witness = body.witness;
25
+ return out;
26
+ }
27
+ const credential = body.credential;
28
+ if (credential) {
29
+ const buildLocalWitness = await loadWitnessBuilder();
30
+ const context = defaultContext(body.context ?? {});
31
+ out.witness = buildLocalWitness(circuitId, credential, context);
32
+ return out;
33
+ }
34
+ if (body.fields) {
35
+ out.fields = body.fields;
36
+ }
37
+ return out;
38
+ }