/**
 * VoyagerDataOps — thin wrapper over Starknet JSON-RPC (free tier).
 *
 * Uses the Nethermind free RPC endpoint (no API key required).
 * Rate-limited via configurable inter-call delay.
 * No indexed REST API — pure JSON-RPC v0.7.
 */
import { z } from "zod";
const DEFAULT_RPC_URL = "https://free-rpc.nethermind.io/mainnet-juno/";
const DEFAULT_RATE_LIMIT_MS = 100;
const DEFAULT_CHUNK_SIZE = 1000;
// ─── Zod schemas (strict boundary validation) ────────────────────────────────
const RpcResponseSchema = z.object({
    jsonrpc: z.literal("2.0"),
    id: z.number(),
    result: z.unknown().optional(),
    error: z
        .object({ code: z.number(), message: z.string() })
        .optional(),
});
const EventSchema = z.object({
    transaction_hash: z.string(),
    block_number: z.number(),
    keys: z.array(z.string()),
    data: z.array(z.string()),
});
const GetEventsResultSchema = z.object({
    events: z.array(EventSchema),
    continuation_token: z.string().optional(),
});
// ─── VoyagerDataOps ──────────────────────────────────────────────────────────
export class VoyagerDataOps {
    rpcUrl;
    rateLimitMs;
    chunkSize;
    lastCallAt = 0;
    requestId = 0;
    constructor(config = {}) {
        this.rpcUrl = config.rpcUrl ?? DEFAULT_RPC_URL;
        this.rateLimitMs = config.rateLimitMs ?? DEFAULT_RATE_LIMIT_MS;
        this.chunkSize = config.chunkSize ?? DEFAULT_CHUNK_SIZE;
    }
    /**
     * Extract unique wallet addresses that interacted with a protocol contract.
     * Uses starknet_getEvents; the first element of event.data is the caller.
     *
     * @param contractAddress - Protocol contract (e.g. zkLend main contract)
     * @param eventSelector   - Event key selector (hex string, e.g. Deposit event)
     * @param limit           - Max wallets to return. Default: 100.
     */
    async getProtocolWallets(contractAddress, eventSelector, limit = 100) {
        const wallets = new Set();
        let continuationToken;
        while (wallets.size < limit) {
            const result = await this.getEvents({
                address: contractAddress,
                keys: [[eventSelector]],
                chunk_size: Math.min(this.chunkSize, limit * 2),
                continuation_token: continuationToken,
            });
            for (const event of result.events) {
                const caller = event.data[0];
                if (caller)
                    wallets.add(caller);
                if (wallets.size >= limit)
                    break;
            }
            continuationToken = result.continuation_token;
            if (!continuationToken)
                break;
        }
        return Array.from(wallets).slice(0, limit);
    }
    /**
     * Get events emitted by a specific wallet address for a given contract.
     */
    async getWalletEvents(walletAddress, contractAddress, eventSelector) {
        const result = await this.getEvents({
            address: contractAddress,
            keys: [[eventSelector]],
            chunk_size: this.chunkSize,
        });
        return result.events
            .filter((e) => e.data[0] === walletAddress)
            .map((e) => ({
            walletAddress,
            txHash: e.transaction_hash,
            blockNumber: e.block_number,
            eventData: e.data,
        }));
    }
    /**
     * Verify RPC connectivity and return current block number + latency.
     */
    async healthCheck() {
        const t0 = Date.now();
        const res = await this.rpcCall("starknet_blockNumber", []);
        return { blockNumber: res, latencyMs: Date.now() - t0 };
    }
    // ─── Private helpers ───────────────────────────────────────────────────────
    async getEvents(params) {
        const raw = await this.rpcCall("starknet_getEvents", [params]);
        return GetEventsResultSchema.parse(raw);
    }
    async rpcCall(method, params) {
        await this.throttle();
        const id = ++this.requestId;
        const body = JSON.stringify({ jsonrpc: "2.0", method, params, id });
        const response = await fetch(this.rpcUrl, {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body,
        });
        if (!response.ok) {
            throw new VoyagerRpcError(`HTTP ${response.status} from ${this.rpcUrl}`, response.status);
        }
        const raw = await response.json();
        const parsed = RpcResponseSchema.parse(raw);
        if (parsed.error) {
            throw new VoyagerRpcError(parsed.error.message, parsed.error.code);
        }
        return parsed.result;
    }
    async throttle() {
        const now = Date.now();
        const elapsed = now - this.lastCallAt;
        if (elapsed < this.rateLimitMs) {
            await sleep(this.rateLimitMs - elapsed);
        }
        this.lastCallAt = Date.now();
    }
}
// ─── Error ────────────────────────────────────────────────────────────────────
export class VoyagerRpcError extends Error {
    code;
    constructor(message, code) {
        super(`VoyagerRpcError(${code}): ${message}`);
        this.code = code;
        this.name = "VoyagerRpcError";
    }
}
// ─── Helpers ─────────────────────────────────────────────────────────────────
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
//# sourceMappingURL=voyager.js.map