@stellaris-lab/por-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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * End-to-end attestation pipeline.
3
+ *
4
+ * This is the orchestration layer an exchange/backend service would use: it
5
+ * normalizes a snapshot, evaluates issuer policy, generates a local proof,
6
+ * optionally verifies it locally, and submits the attestation on-chain.
7
+ */
8
+ import { InMemoryAuditSink, makeAuditLog, makeAuditStep, makeWorkflowId } from "./audit.js";
9
+ import { normalizeSnapshot, } from "./domain.js";
10
+ import { StellarisError } from "./errors.js";
11
+ import { evaluateSnapshotPolicy } from "./policy.js";
12
+ import { generateProofFromSnapshot, verifyLocal } from "./prove.js";
13
+ export class AttestationPipeline {
14
+ client;
15
+ artifacts;
16
+ signer;
17
+ policy;
18
+ auditSink;
19
+ requireLocalVerification;
20
+ constructor(options) {
21
+ this.client = options.client;
22
+ this.artifacts = options.artifacts;
23
+ this.signer = options.signer;
24
+ if (options.policy !== undefined) {
25
+ this.policy = options.policy;
26
+ }
27
+ this.auditSink = options.auditSink ?? new InMemoryAuditSink();
28
+ this.requireLocalVerification = options.requireLocalVerification ?? true;
29
+ }
30
+ async run(snapshot) {
31
+ const normalized = normalizeSnapshot(snapshot);
32
+ const workflowId = makeWorkflowId(this.signer.publicKey, normalized.periodId);
33
+ const localAudit = new InMemoryAuditSink();
34
+ const record = async (name, status, context) => {
35
+ const step = makeAuditStep(context === undefined ? { name, status } : { name, status, context });
36
+ localAudit.record(step);
37
+ await this.auditSink.record(step);
38
+ };
39
+ await record("snapshot.normalized", "accepted", {
40
+ periodId: normalized.periodId.toString(),
41
+ accountCount: normalized.accounts.length,
42
+ });
43
+ const policyReport = evaluateSnapshotPolicy(normalized, this.policy);
44
+ await record("policy.evaluated", policyReport.accepted ? "accepted" : "rejected", {
45
+ findings: policyReport.findings.map((finding) => finding.code),
46
+ reserveRatioBps: policyReport.reserveRatioBps?.toString() ?? null,
47
+ });
48
+ if (!policyReport.accepted) {
49
+ throw StellarisError.validation("snapshot rejected by policy", { workflowId, policyReport });
50
+ }
51
+ const proof = await generateProofFromSnapshot(normalized, this.artifacts);
52
+ await record("proof.generated", "accepted", {
53
+ solvent: proof.parsed.solvent,
54
+ commitment: proof.parsed.commitment,
55
+ });
56
+ if (!proof.parsed.solvent) {
57
+ await record("proof.solvency", "rejected");
58
+ throw StellarisError.validation("generated proof public signals report insolvency", { workflowId });
59
+ }
60
+ if (this.requireLocalVerification) {
61
+ if (!this.artifacts.verificationKey) {
62
+ await record("proof.local_verification", "failed", { reason: "missing_verification_key" });
63
+ throw StellarisError.configuration("local verification requires a verification key", { context: { workflowId } });
64
+ }
65
+ const verified = await verifyLocal(this.artifacts.verificationKey, proof);
66
+ await record("proof.local_verification", verified ? "accepted" : "rejected");
67
+ if (!verified) {
68
+ throw StellarisError.verification("local proof verification failed", { context: { workflowId } });
69
+ }
70
+ }
71
+ const receipt = await this.client.attest({ issuer: this.signer.publicKey, bundle: proof, signer: this.signer });
72
+ await record("contract.attest", "submitted", {
73
+ transactionHash: receipt.transactionHash,
74
+ ledger: receipt.ledger?.toString(),
75
+ });
76
+ return {
77
+ proof,
78
+ policyReport,
79
+ receipt,
80
+ auditLog: makeAuditLog({
81
+ workflowId,
82
+ issuer: this.signer.publicKey,
83
+ periodId: normalized.periodId,
84
+ deployment: this.client.deployment,
85
+ steps: localAudit.snapshot(),
86
+ publicSignals: proof.parsed,
87
+ policyReport,
88
+ receipt,
89
+ }),
90
+ };
91
+ }
92
+ }
93
+ export function createAttestationPipeline(options) {
94
+ return new AttestationPipeline(options);
95
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Policy layer for issuer snapshots.
3
+ *
4
+ * Mature protocol SDKs separate policy from mechanics: proving can be valid even
5
+ * when an issuer-level policy rejects the snapshot. This lets integrators enforce
6
+ * stricter business rules before expensive proving or transaction submission.
7
+ */
8
+ import { NormalizedReserveSnapshot } from "./domain.js";
9
+ export interface PolicyFinding {
10
+ readonly code: string;
11
+ readonly severity: "info" | "warning" | "error";
12
+ readonly message: string;
13
+ }
14
+ export interface SnapshotPolicy {
15
+ readonly minReserveRatioBps?: bigint;
16
+ readonly maxAccounts?: number;
17
+ readonly requireMetadata?: boolean;
18
+ readonly requireExternalReportUri?: boolean;
19
+ readonly allowZeroBalanceAccounts?: boolean;
20
+ }
21
+ export interface PolicyReport {
22
+ readonly accepted: boolean;
23
+ readonly reserveRatioBps: bigint | null;
24
+ readonly findings: readonly PolicyFinding[];
25
+ }
26
+ export declare const DEFAULT_SNAPSHOT_POLICY: Required<SnapshotPolicy>;
27
+ export declare function evaluateSnapshotPolicy(snapshot: NormalizedReserveSnapshot, policy?: SnapshotPolicy): PolicyReport;
package/dist/policy.js ADDED
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Policy layer for issuer snapshots.
3
+ *
4
+ * Mature protocol SDKs separate policy from mechanics: proving can be valid even
5
+ * when an issuer-level policy rejects the snapshot. This lets integrators enforce
6
+ * stricter business rules before expensive proving or transaction submission.
7
+ */
8
+ import { totalReserves } from "./domain.js";
9
+ export const DEFAULT_SNAPSHOT_POLICY = {
10
+ minReserveRatioBps: 10000n,
11
+ maxAccounts: 16,
12
+ requireMetadata: false,
13
+ requireExternalReportUri: false,
14
+ allowZeroBalanceAccounts: true,
15
+ };
16
+ export function evaluateSnapshotPolicy(snapshot, policy = DEFAULT_SNAPSHOT_POLICY) {
17
+ const effective = { ...DEFAULT_SNAPSHOT_POLICY, ...policy };
18
+ const findings = [];
19
+ const reserves = totalReserves(snapshot);
20
+ const ratio = snapshot.liabilities === 0n ? null : (reserves * 10000n) / snapshot.liabilities;
21
+ if (snapshot.accounts.length > effective.maxAccounts) {
22
+ findings.push({
23
+ code: "too_many_accounts",
24
+ severity: "error",
25
+ message: `snapshot has ${snapshot.accounts.length} accounts; max is ${effective.maxAccounts}`,
26
+ });
27
+ }
28
+ if (ratio !== null && ratio < effective.minReserveRatioBps) {
29
+ findings.push({
30
+ code: "insufficient_reserve_ratio",
31
+ severity: "error",
32
+ message: `reserve ratio ${ratio} bps below required ${effective.minReserveRatioBps} bps`,
33
+ });
34
+ }
35
+ if (!effective.allowZeroBalanceAccounts) {
36
+ const zero = snapshot.accounts.find((account) => account.balance === 0n);
37
+ if (zero) {
38
+ findings.push({
39
+ code: "zero_balance_account",
40
+ severity: "warning",
41
+ message: `account ${zero.label} has zero balance`,
42
+ });
43
+ }
44
+ }
45
+ if (effective.requireMetadata && !snapshot.metadata) {
46
+ findings.push({ code: "metadata_missing", severity: "error", message: "snapshot metadata is required" });
47
+ }
48
+ if (effective.requireExternalReportUri && !snapshot.metadata?.externalReportUri) {
49
+ findings.push({
50
+ code: "external_report_missing",
51
+ severity: "error",
52
+ message: "external report URI is required by policy",
53
+ });
54
+ }
55
+ return {
56
+ accepted: findings.every((finding) => finding.severity !== "error"),
57
+ reserveRatioBps: ratio,
58
+ findings,
59
+ };
60
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Groth16 proof generation for Stellaris.
3
+ *
4
+ * This module owns witness construction and local proof verification only. It has
5
+ * no Stellar RPC dependency and no UI/demo behavior.
6
+ */
7
+ import { ProvingBackend, WitnessInput } from "./backend.js";
8
+ import { NormalizedReserveSnapshot, ProofBundle, ProvingArtifacts, ReserveSnapshot } from "./domain.js";
9
+ export interface ReserveInput {
10
+ readonly balances: readonly bigint[];
11
+ readonly salt: bigint;
12
+ readonly liabilities: bigint;
13
+ readonly periodId: bigint;
14
+ }
15
+ export declare function validateReserveInput(input: ReserveInput): void;
16
+ export declare function buildWitnessInput(input: ReserveInput): WitnessInput;
17
+ export declare function generateProofFromInput(input: ReserveInput, artifacts: ProvingArtifacts, backend?: ProvingBackend): Promise<ProofBundle>;
18
+ export declare function generateProofFromSnapshot(snapshot: ReserveSnapshot | NormalizedReserveSnapshot, artifacts: ProvingArtifacts, backend?: ProvingBackend): Promise<ProofBundle>;
19
+ export declare function verifyLocal(verificationKey: Record<string, unknown>, bundle: ProofBundle, backend?: ProvingBackend): Promise<boolean>;
20
+ export declare function toContractArgs(bundle: ProofBundle): {
21
+ readonly proof: {
22
+ readonly a: readonly [string, string];
23
+ readonly b: readonly [readonly [string, string], readonly [string, string]];
24
+ readonly c: readonly [string, string];
25
+ };
26
+ readonly pubSignals: readonly string[];
27
+ };
package/dist/prove.js ADDED
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Groth16 proof generation for Stellaris.
3
+ *
4
+ * This module owns witness construction and local proof verification only. It has
5
+ * no Stellar RPC dependency and no UI/demo behavior.
6
+ */
7
+ import { DEFAULT_PROVING_BACKEND } from "./backend.js";
8
+ import { MAX_RESERVE, N_RESERVES } from "./constants.js";
9
+ import { normalizeSnapshot, toReserveInput, } from "./domain.js";
10
+ import { StellarisError } from "./errors.js";
11
+ export function validateReserveInput(input) {
12
+ if (input.balances.length === 0) {
13
+ throw StellarisError.validation("balances must include at least one value");
14
+ }
15
+ if (input.balances.length > N_RESERVES) {
16
+ throw StellarisError.validation(`balances length exceeds max ${N_RESERVES}`);
17
+ }
18
+ for (let i = 0; i < input.balances.length; i++) {
19
+ const balance = input.balances[i];
20
+ if (typeof balance !== "bigint" || balance < 0n || balance > MAX_RESERVE) {
21
+ throw StellarisError.validation(`balance ${i} is outside uint64 range`);
22
+ }
23
+ }
24
+ if (input.salt < 0n) {
25
+ throw StellarisError.validation("salt must be non-negative");
26
+ }
27
+ if (input.liabilities < 0n) {
28
+ throw StellarisError.validation("liabilities must be non-negative");
29
+ }
30
+ if (input.periodId < 0n) {
31
+ throw StellarisError.validation("periodId must be non-negative");
32
+ }
33
+ }
34
+ export function buildWitnessInput(input) {
35
+ validateReserveInput(input);
36
+ const padded = [...input.balances];
37
+ while (padded.length < N_RESERVES) {
38
+ padded.push(0n);
39
+ }
40
+ return {
41
+ r: padded.map((balance) => balance.toString()),
42
+ salt: input.salt.toString(),
43
+ liabilities_in: input.liabilities.toString(),
44
+ period_in: input.periodId.toString(),
45
+ };
46
+ }
47
+ export async function generateProofFromInput(input, artifacts, backend = DEFAULT_PROVING_BACKEND) {
48
+ const witnessInput = buildWitnessInput(input);
49
+ return backend.prove(witnessInput, artifacts);
50
+ }
51
+ export async function generateProofFromSnapshot(snapshot, artifacts, backend = DEFAULT_PROVING_BACKEND) {
52
+ const normalized = "accounts" in snapshot && "balances" in snapshot
53
+ ? snapshot
54
+ : normalizeSnapshot(snapshot);
55
+ return generateProofFromInput(toReserveInput(normalized), artifacts, backend);
56
+ }
57
+ export async function verifyLocal(verificationKey, bundle, backend = DEFAULT_PROVING_BACKEND) {
58
+ return backend.verify(verificationKey, bundle);
59
+ }
60
+ export function toContractArgs(bundle) {
61
+ const p = bundle.proof;
62
+ if (!p.pi_a || !p.pi_b || !p.pi_c) {
63
+ throw StellarisError.encoding("proof is missing pi_a, pi_b, or pi_c");
64
+ }
65
+ return {
66
+ proof: {
67
+ a: [p.pi_a[0], p.pi_a[1]],
68
+ b: [
69
+ [p.pi_b[0][0], p.pi_b[0][1]],
70
+ [p.pi_b[1][0], p.pi_b[1][1]],
71
+ ],
72
+ c: [p.pi_c[0], p.pi_c[1]],
73
+ },
74
+ pubSignals: [...bundle.publicSignals],
75
+ };
76
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Deterministic registry reconciliation jobs.
3
+ *
4
+ * This layer turns the registry/indexer primitives into an operational subsystem:
5
+ * each run refreshes issuer state, persists checkpoints, emits redacted audit
6
+ * steps, and classifies partial failures with bounded retry/backoff behavior.
7
+ */
8
+ import { AuditSink } from "./audit.js";
9
+ import { PublicKey } from "./domain.js";
10
+ import { FileCheckpointRepository } from "./persistence.js";
11
+ import { AttestationRegistry, RegistrySnapshot } from "./registry.js";
12
+ export type ReconciliationStatus = "succeeded" | "failed";
13
+ export interface ReconciliationTarget {
14
+ readonly issuer: PublicKey;
15
+ readonly required?: boolean;
16
+ }
17
+ export interface ReconciliationFailure {
18
+ readonly issuer: PublicKey;
19
+ readonly attempt: number;
20
+ readonly message: string;
21
+ readonly retryable: boolean;
22
+ }
23
+ export interface ReconciliationResult {
24
+ readonly runId: string;
25
+ readonly startedAt: string;
26
+ readonly finishedAt: string;
27
+ readonly status: ReconciliationStatus;
28
+ readonly attempts: number;
29
+ readonly snapshots: readonly RegistrySnapshot[];
30
+ readonly failures: readonly ReconciliationFailure[];
31
+ }
32
+ export interface BackoffPolicy {
33
+ readonly maxAttempts: number;
34
+ readonly baseDelayMs: number;
35
+ readonly maxDelayMs: number;
36
+ }
37
+ export interface ReconcilerClock {
38
+ now(): Date;
39
+ sleep(ms: number): Promise<void>;
40
+ }
41
+ export interface RegistryReconcilerOptions {
42
+ readonly registry: AttestationRegistry;
43
+ readonly targets: readonly ReconciliationTarget[];
44
+ readonly checkpoint?: FileCheckpointRepository;
45
+ readonly auditSink?: AuditSink;
46
+ readonly backoff?: Partial<BackoffPolicy>;
47
+ readonly clock?: ReconcilerClock;
48
+ }
49
+ export declare class RegistryReconciler {
50
+ private readonly registry;
51
+ private readonly targets;
52
+ private readonly checkpoint?;
53
+ private readonly auditSink;
54
+ private readonly backoff;
55
+ private readonly clock;
56
+ constructor(options: RegistryReconcilerOptions);
57
+ runOnce(): Promise<ReconciliationResult>;
58
+ runMany(count: number): Promise<readonly ReconciliationResult[]>;
59
+ private refreshWithRetry;
60
+ private audit;
61
+ }
62
+ export declare function makeRegistryReconciler(options: RegistryReconcilerOptions): RegistryReconciler;
63
+ export declare function normalizeBackoff(input?: Partial<BackoffPolicy>): BackoffPolicy;
64
+ export declare function nextDelayMs(policy: BackoffPolicy, failedAttempt: number): number;
65
+ export declare function makeRunId(now: Date): string;
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Deterministic registry reconciliation jobs.
3
+ *
4
+ * This layer turns the registry/indexer primitives into an operational subsystem:
5
+ * each run refreshes issuer state, persists checkpoints, emits redacted audit
6
+ * steps, and classifies partial failures with bounded retry/backoff behavior.
7
+ */
8
+ import { InMemoryAuditSink, makeAuditStep } from "./audit.js";
9
+ import { StellarisError } from "./errors.js";
10
+ export class RegistryReconciler {
11
+ registry;
12
+ targets;
13
+ checkpoint;
14
+ auditSink;
15
+ backoff;
16
+ clock;
17
+ constructor(options) {
18
+ if (options.targets.length === 0) {
19
+ throw StellarisError.configuration("registry reconciler requires at least one target issuer");
20
+ }
21
+ this.registry = options.registry;
22
+ this.targets = options.targets;
23
+ if (options.checkpoint !== undefined) {
24
+ this.checkpoint = options.checkpoint;
25
+ }
26
+ this.auditSink = options.auditSink ?? new InMemoryAuditSink();
27
+ this.backoff = normalizeBackoff(options.backoff);
28
+ this.clock = options.clock ?? systemClock;
29
+ }
30
+ async runOnce() {
31
+ const runId = makeRunId(this.clock.now());
32
+ const startedAt = this.clock.now().toISOString();
33
+ const snapshots = [];
34
+ const failures = [];
35
+ let attempts = 0;
36
+ await this.audit("reconciler.started", "started", { runId, targets: this.targets.length });
37
+ for (const target of this.targets) {
38
+ const outcome = await this.refreshWithRetry(target);
39
+ attempts += outcome.attempts;
40
+ if (outcome.snapshot) {
41
+ snapshots.push(outcome.snapshot);
42
+ }
43
+ failures.push(...outcome.failures);
44
+ }
45
+ const firstSnapshot = snapshots[0];
46
+ if (this.checkpoint !== undefined && firstSnapshot !== undefined) {
47
+ const records = snapshots.flatMap((snapshot) => [...snapshot.attestations]);
48
+ await this.checkpoint.saveRecords(firstSnapshot.deployment, records);
49
+ }
50
+ const requiredFailure = failures.some((failure) => {
51
+ const target = this.targets.find((item) => item.issuer === failure.issuer);
52
+ return target?.required ?? true;
53
+ });
54
+ const status = requiredFailure ? "failed" : "succeeded";
55
+ await this.audit(status === "succeeded" ? "reconciler.completed" : "reconciler.failed", status === "succeeded" ? "accepted" : "failed", {
56
+ runId,
57
+ snapshots: snapshots.length,
58
+ failures: failures.length,
59
+ });
60
+ return {
61
+ runId,
62
+ startedAt,
63
+ finishedAt: this.clock.now().toISOString(),
64
+ status,
65
+ attempts,
66
+ snapshots,
67
+ failures,
68
+ };
69
+ }
70
+ async runMany(count) {
71
+ if (!Number.isInteger(count) || count <= 0) {
72
+ throw StellarisError.validation("reconciler run count must be a positive integer", { count });
73
+ }
74
+ const results = [];
75
+ for (let i = 0; i < count; i++) {
76
+ results.push(await this.runOnce());
77
+ }
78
+ return results;
79
+ }
80
+ async refreshWithRetry(target) {
81
+ const failures = [];
82
+ for (let attempt = 1; attempt <= this.backoff.maxAttempts; attempt++) {
83
+ try {
84
+ await this.audit("issuer.refresh.started", "started", { issuer: target.issuer, attempt });
85
+ const snapshot = await this.registry.refreshIssuer(target.issuer);
86
+ await this.audit("issuer.refresh.completed", "accepted", {
87
+ issuer: target.issuer,
88
+ attempt,
89
+ periods: snapshot.periods.length,
90
+ gaps: snapshot.diagnostics.missingPeriods.length,
91
+ });
92
+ return { attempts: attempt, snapshot, failures };
93
+ }
94
+ catch (cause) {
95
+ const retryable = isRetryableFailure(cause);
96
+ const failure = makeFailure(target.issuer, attempt, cause, retryable);
97
+ failures.push(failure);
98
+ await this.audit("issuer.refresh.failed", "failed", {
99
+ issuer: target.issuer,
100
+ attempt,
101
+ retryable,
102
+ message: failure.message,
103
+ });
104
+ if (!retryable || attempt === this.backoff.maxAttempts) {
105
+ return { attempts: attempt, failures };
106
+ }
107
+ await this.clock.sleep(nextDelayMs(this.backoff, attempt));
108
+ }
109
+ }
110
+ return { attempts: this.backoff.maxAttempts, failures };
111
+ }
112
+ async audit(name, status, context) {
113
+ await this.auditSink.record(makeAuditStep({ name, status, context }));
114
+ }
115
+ }
116
+ export function makeRegistryReconciler(options) {
117
+ return new RegistryReconciler(options);
118
+ }
119
+ export function normalizeBackoff(input = {}) {
120
+ const maxAttempts = input.maxAttempts ?? 3;
121
+ const baseDelayMs = input.baseDelayMs ?? 250;
122
+ const maxDelayMs = input.maxDelayMs ?? 5_000;
123
+ if (maxAttempts <= 0 || baseDelayMs < 0 || maxDelayMs < baseDelayMs) {
124
+ throw StellarisError.configuration("invalid reconciler backoff policy", { maxAttempts, baseDelayMs, maxDelayMs });
125
+ }
126
+ return { maxAttempts, baseDelayMs, maxDelayMs };
127
+ }
128
+ export function nextDelayMs(policy, failedAttempt) {
129
+ const exponential = policy.baseDelayMs * 2 ** Math.max(0, failedAttempt - 1);
130
+ return Math.min(policy.maxDelayMs, exponential);
131
+ }
132
+ export function makeRunId(now) {
133
+ return `stellaris-reconcile-${now.toISOString()}`;
134
+ }
135
+ function makeFailure(issuer, attempt, cause, retryable) {
136
+ return {
137
+ issuer,
138
+ attempt,
139
+ retryable,
140
+ message: cause instanceof Error ? cause.message : String(cause),
141
+ };
142
+ }
143
+ function isRetryableFailure(cause) {
144
+ if (cause instanceof StellarisError) {
145
+ return cause.kind === "transport";
146
+ }
147
+ return !(cause instanceof Error);
148
+ }
149
+ const systemClock = {
150
+ now: () => new Date(),
151
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
152
+ };
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Attestation registry/indexing layer.
3
+ *
4
+ * Protocol SDKs usually expose a state-oriented subsystem in addition to direct
5
+ * RPC calls. This module reconciles contract state into a local store, detects
6
+ * period gaps/replays, and gives backend services a stable query surface without
7
+ * introducing frontend/demo code.
8
+ */
9
+ import { Attestation, ContractDeployment, PeriodId, PublicKey } from "./domain.js";
10
+ import { StellarisClient } from "./stellar.js";
11
+ export interface IndexedAttestation {
12
+ readonly attestation: Attestation;
13
+ readonly deployment: ContractDeployment;
14
+ readonly indexedAt: string;
15
+ readonly source: "contract" | "receipt" | "manual";
16
+ }
17
+ export interface RegistrySnapshot {
18
+ readonly issuer: PublicKey;
19
+ readonly deployment: ContractDeployment;
20
+ readonly indexedAt: string;
21
+ readonly periods: readonly PeriodId[];
22
+ readonly attestations: readonly IndexedAttestation[];
23
+ readonly diagnostics: RegistryDiagnostics;
24
+ }
25
+ export interface RegistryDiagnostics {
26
+ readonly duplicatePeriods: readonly PeriodId[];
27
+ readonly missingPeriods: readonly PeriodGap[];
28
+ readonly nonSolventPeriods: readonly PeriodId[];
29
+ readonly newestPeriod: PeriodId | null;
30
+ readonly oldestPeriod: PeriodId | null;
31
+ }
32
+ export interface PeriodGap {
33
+ readonly after: PeriodId;
34
+ readonly before: PeriodId;
35
+ readonly missing: readonly PeriodId[];
36
+ }
37
+ export interface AttestationStore {
38
+ put(record: IndexedAttestation): Promise<void> | void;
39
+ get(issuer: PublicKey, periodId: PeriodId): Promise<IndexedAttestation | null> | IndexedAttestation | null;
40
+ list(issuer: PublicKey): Promise<readonly IndexedAttestation[]> | readonly IndexedAttestation[];
41
+ clear?(issuer?: PublicKey): Promise<void> | void;
42
+ }
43
+ export interface RegistryRefreshOptions {
44
+ readonly source?: IndexedAttestation["source"];
45
+ readonly failOnMissingAttestation?: boolean;
46
+ }
47
+ export declare class InMemoryAttestationStore implements AttestationStore {
48
+ private readonly records;
49
+ put(record: IndexedAttestation): void;
50
+ get(issuer: PublicKey, periodId: PeriodId): IndexedAttestation | null;
51
+ list(issuer: PublicKey): readonly IndexedAttestation[];
52
+ clear(issuer?: PublicKey): void;
53
+ }
54
+ export declare class AttestationRegistry {
55
+ private readonly client;
56
+ private readonly store;
57
+ constructor(input: {
58
+ readonly client: StellarisClient;
59
+ readonly store?: AttestationStore;
60
+ });
61
+ put(record: IndexedAttestation): Promise<void>;
62
+ get(issuer: PublicKey, periodId: PeriodId): Promise<IndexedAttestation | null>;
63
+ refreshIssuer(issuer: PublicKey, options?: RegistryRefreshOptions): Promise<RegistrySnapshot>;
64
+ snapshot(issuer: PublicKey): Promise<RegistrySnapshot>;
65
+ }
66
+ export declare function indexAttestation(attestation: Attestation, deployment: ContractDeployment, source?: IndexedAttestation["source"]): IndexedAttestation;
67
+ export declare function analyzeRegistry(records: readonly IndexedAttestation[]): RegistryDiagnostics;
68
+ export declare function findPeriodGaps(periods: readonly PeriodId[]): readonly PeriodGap[];
69
+ export declare function findDuplicatePeriods(periods: readonly PeriodId[]): readonly PeriodId[];
70
+ export declare function dedupeAndSort(periods: readonly PeriodId[]): readonly PeriodId[];
71
+ export declare function comparePeriod(a: PeriodId, b: PeriodId): number;