import { Address } from 'viem'; /** * IEventSource — "Where do addresses come from?" * * An event source watches on-chain events (or any other source) * and produces candidate addresses for whitelisting. * * Built-in implementations: * - RpcEventSource (viem getLogs + watchEvent) * - SubsquidEventSource (Subsquid archive + RPC) * * Custom implementations could include: * - WebhookEventSource (receives POST webhooks) * - GraphQLEventSource (GraphQL subscriptions) * - ManualEventSource (admin adds addresses via API) */ /** * Sync status of an event source. */ interface EventSourceStatus { /** Whether still catching up on historical events */ isCatchingUp: boolean; /** Last processed block number (if applicable) */ lastBlock?: bigint; /** Human-readable source type name */ sourceName: string; } /** * Interface for event sources that produce candidate addresses. * * Implementations handle both historical catch-up and live watching. * The ASP service calls start() once and receives addresses via the callback. */ interface IEventSource { /** * Start watching for events. * Calls onAddress() for each discovered candidate address. * Must handle historical catch-up before switching to live mode. * * @param onAddress - Callback invoked with each discovered address. * The callback returns a Promise so the source can * apply backpressure if the consumer is slow. */ start(onAddress: (address: `0x${string}`) => Promise): Promise; /** * Stop watching for events. * Should clean up any resources (subscriptions, connections). */ stop(): void; /** * Get the current sync status. */ getStatus(): EventSourceStatus; } /** * IMembershipGate — "Who gets whitelisted?" * * A membership gate decides whether a candidate address should be * added to the ASP's Merkle tree. It sits between the event source * and the tree — screening, approving, or rejecting addresses. * * Built-in implementations: * - AllowAllGate (approves everyone, for demos) * - SanctionsGate (blocklist-based screening) * * Custom implementations could include: * - KYCGate (calls a KYC provider API) * - ChainalysisGate (Chainalysis sanctions API) * - ManualGate (admin approval via dashboard) * - CompositeGate (chains multiple gates: sanctions AND kyc) */ /** * Statistics for a membership gate. */ interface MembershipGateStats { /** Total addresses checked */ checked: number; /** Total addresses approved */ approved: number; /** Total addresses rejected */ rejected: number; } /** * Interface for membership gates that decide who gets whitelisted. * * Gates are called for every candidate address before it's added * to the Merkle tree. They can be synchronous (blocklist lookup) * or asynchronous (API call to a KYC provider). */ interface IMembershipGate { /** * Check if an address should be whitelisted. * * @param address - The candidate address * @returns true if the address is approved for whitelisting */ approve(address: `0x${string}`): Promise; /** * Human-readable gate name (e.g., "Sanctions Screen", "KYC Verified"). */ readonly name: string; /** * Get gate statistics. */ getStats(): MembershipGateStats; } /** * ASP API Schema — Standard response types for ASP services. * * Any ASP server implementation (Express, Hono, Fastify, etc.) * should return these response shapes from the standard endpoints. * * Standard endpoints: * GET /root → ASPRootResponse * GET /proof/:address → ASPProofResponse (EIP-712 gated, 404 if not a member) * GET /status → ASPStatusResponse (global stats) * GET /status/:address → ASPAddressStatusResponse (per-address, rate-limited) * * Standard error responses: * 400 → { error: "Invalid address" } * 401 → { error: "Missing sig or timestamp" } * 403 → { error: "Invalid signature" } * 404 → { error: "Address not whitelisted" } * 429 → { error: "Rate limit exceeded" } * 500 → { error: "" } */ /** * Response for GET /root */ interface ASPRootResponse { /** Current Merkle root (decimal string) */ root: string; } /** * Response for GET /proof/:address */ interface ASPProofResponse { /** Merkle root (decimal string) */ root: string; /** Path elements / siblings (decimal strings) */ pathElements: string[]; /** Path indices (0 = left, 1 = right) */ pathIndices: number[]; } /** * Response for GET /stark-root * * Distinct from ASPRootResponse — the STARK side uses an always-hash * fixed-depth tree (AlwaysHashMerkleTree), so its root is a different * value from the SNARK-side LeanIMT root over the same membership set. * Kept as its own type so callers can't silently substitute one for * the other. */ interface ASPStarkRootResponse { /** Current STARK-side Merkle root (decimal string, M31 field) */ root: string; } /** * Response for GET /stark-proof/:address * * Always-hash, fixed-depth proof. Verifies against the same value * GET /stark-root returns, for every member in the tree (cross-leaf * root agreement). pathIndices on the wire are JSON numbers (0 or 1) * — the underlying tree uses bigint indices but they're guaranteed * binary, and JSON has no native BigInt encoding. */ interface ASPStarkProofResponse { /** Tree root (decimal string, M31 field) */ root: string; /** Leaf value the proof is for (decimal string, M31-encoded address) */ leaf: string; /** Index of the leaf in insertion order (0-based) */ leafIndex: number; /** Tree depth — fixed across every proof in this tree */ depth: number; /** Sibling at each level (decimal strings, length === depth) */ pathElements: string[]; /** Side bit at each level (0 = current is left, 1 = right; length === depth) */ pathIndices: number[]; } /** * Per-address compliance status. */ type ASPAddressStatus = 'whitelisted' | 'pending' | 'blocked' | 'unknown'; /** * Response for GET /status/:address (public, rate-limited) */ interface ASPAddressStatusResponse { /** Checksummed address */ address: string; /** Compliance status */ status: ASPAddressStatus; } /** * Response for GET /status (global) */ interface ASPStatusResponse { /** Number of whitelisted members */ memberCount: number; /** Number of blocked/rejected addresses */ blockedCount: number; /** Sanctions blocklist size (if applicable) */ blocklistSize?: number; /** Whether the event source is still catching up on history */ isCatchingUp: boolean; /** On-chain ASP ID (decimal string, null if not registered) */ aspId: string | null; /** Last published Merkle root (decimal string) */ lastPublishedRoot: string; } /** * Standard error response */ interface ASPErrorResponse { error: string; } /** * Parsed STARK proof — bigint-typed, ready for AlwaysHashMerkleTree * verification or in-circuit witness assembly. */ interface ASPStarkProofData { /** STARK tree root */ root: bigint; /** Leaf value (M31-encoded address) */ leaf: bigint; /** Index of the leaf in insertion order (0-based) */ leafIndex: number; /** Tree depth — pinned alongside the proof so verifiers don't guess */ depth: number; /** Sibling at each level; length === depth */ pathElements: bigint[]; /** Side bit at each level (0 or 1); length === depth */ pathIndices: number[]; } /** * Fetch the current STARK-side Merkle root from an ASP service. * * Throws on any non-2xx status — the root endpoint shouldn't return * 404, so we don't have a "not found" path here. Use `try/catch` to * distinguish service-unavailable from connectivity errors. */ declare function fetchStarkASPRoot(serviceUrl: string, fetchFn?: typeof fetch): Promise; /** * Fetch a STARK membership proof for `address` from an ASP service. * * Returns `null` on 404 (address not whitelisted). Throws on any other * non-2xx — including 429 (rate limited), which the caller may want to * retry with backoff. * * The returned proof is fixed-depth and always verifies against the * value `fetchStarkASPRoot` returns at the same point in time (cross- * leaf root agreement, by construction of `AlwaysHashMerkleTree`). */ declare function fetchStarkASPProof(serviceUrl: string, address: `0x${string}`, fetchFn?: typeof fetch): Promise; /** * EIP-712 domain and types for authenticated ASP API requests. * * Used by both server (verifyTypedData) and client (signTypedData). * No chainId — ASP may serve multiple chains. */ declare const ASP_EIP712_DOMAIN: { readonly name: "UPC-ASP"; readonly version: "1"; }; declare const ASP_EIP712_TYPES: { readonly ASPRequest: readonly [{ readonly name: "address"; readonly type: "address"; }, { readonly name: "timestamp"; readonly type: "uint256"; }]; }; /** Signature validity window in seconds */ declare const ASP_SIGNATURE_MAX_AGE_SECONDS = 300; /** * ASP Signature Helpers * * Non-React utilities for EIP-712 signature management. * Used by both React hooks (useASPProof) and non-React clients. */ interface CachedSignature { sig: `0x${string}`; timestamp: number; } /** * Build the EIP-712 typed data message for signing. */ declare function buildASPSignatureMessage(address: Address): { domain: { readonly name: "UPC-ASP"; readonly version: "1"; }; types: { readonly ASPRequest: readonly [{ readonly name: "address"; readonly type: "address"; }, { readonly name: "timestamp"; readonly type: "uint256"; }]; }; primaryType: "ASPRequest"; message: { address: `0x${string}`; timestamp: bigint; }; timestamp: number; }; /** * Get a cached signature if still valid, or null. */ declare function getCachedSignature(address: Address): CachedSignature | null; /** * Cache a freshly signed signature. */ declare function cacheSignature(address: Address, sig: `0x${string}`, timestamp: number): void; /** * Append signature query params to a URL. */ declare function appendSignatureParams(url: string, sig: `0x${string}`, timestamp: number): string; export { type ASPAddressStatus, type ASPAddressStatusResponse, type ASPErrorResponse, type ASPProofResponse, type ASPRootResponse, type ASPStarkProofData, type ASPStarkProofResponse, type ASPStarkRootResponse, type ASPStatusResponse, ASP_EIP712_DOMAIN, ASP_EIP712_TYPES, ASP_SIGNATURE_MAX_AGE_SECONDS, type EventSourceStatus, type IEventSource, type IMembershipGate, type MembershipGateStats, appendSignatureParams, buildASPSignatureMessage, cacheSignature, fetchStarkASPProof, fetchStarkASPRoot, getCachedSignature };