"use strict";
/**
 * J41Client — REST client for the Junction41 API.
 * Handles authentication, session management, and all API calls.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.J41Error = exports.J41Client = void 0;
const keypair_js_1 = require("../identity/keypair.js");
const signer_js_1 = require("../identity/signer.js");
const messages_js_1 = require("../signing/messages.js");
class J41Client {
    baseUrl;
    sessionToken;
    timeout;
    maxRetries;
    onSessionExpired;
    constructor(config) {
        this.baseUrl = config.apiUrl.replace(/\/+$/, '');
        this.sessionToken = config.sessionToken || null;
        this.timeout = config.timeout || 30_000;
        this.maxRetries = config.maxRetries ?? 3;
        this.onSessionExpired = config.onSessionExpired || null;
    }
    /** Set the re-auth callback (used by J41Agent to wire login()) */
    setOnSessionExpired(cb) {
        this.onSessionExpired = cb;
    }
    setSessionToken(token) {
        // Reject tokens with control characters to prevent header injection
        if (/[\r\n\x00-\x1f]/.test(token)) {
            throw new J41Error('Session token contains invalid characters', 'INVALID_TOKEN', 400);
        }
        this.sessionToken = token;
    }
    clearSessionToken() {
        this.sessionToken = null;
    }
    getSessionToken() {
        return this.sessionToken;
    }
    getBaseUrl() {
        return this.baseUrl;
    }
    /** Check if an error is retryable (transient network/server failure) */
    isRetryable(e) {
        if (e instanceof J41Error) {
            // 429 = rate limited, 5xx = server error
            return e.statusCode === 429 || e.statusCode >= 500;
        }
        // Network errors (not our timeout) are retryable
        if (e instanceof Error && e.name !== 'AbortError') {
            return true;
        }
        return false;
    }
    /** Core request logic (single attempt) */
    async _doRequest(method, path, body) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeout);
        try {
            const headers = {
                'Accept': 'application/json',
            };
            if (body) {
                headers['Content-Type'] = 'application/json';
            }
            if (this.sessionToken) {
                headers['Cookie'] = `verus_session=${this.sessionToken}`;
            }
            let response;
            try {
                response = await fetch(`${this.baseUrl}${path}`, {
                    method,
                    headers,
                    body: body ? JSON.stringify(body) : undefined,
                    signal: controller.signal,
                });
            }
            catch (fetchErr) {
                if (fetchErr.name === 'AbortError') {
                    throw new J41Error(`Request to ${method} ${path} timed out after ${this.timeout}ms`, 'TIMEOUT', 408);
                }
                throw fetchErr;
            }
            // Audit 2026-06-02 H6: bound the JSON response body so a hostile or
            // compromised platform can't OOM the SDK with multi-GB chunked output.
            // Default 8 MB is well above any J41 API response shape; raise via
            // J41_MAX_RESPONSE_BYTES if a specific endpoint legitimately needs more.
            const MAX_RESPONSE_BYTES = Number(process.env.J41_MAX_RESPONSE_BYTES ?? 8 * 1024 * 1024);
            const declared = Number(response.headers.get('content-length'));
            if (Number.isFinite(declared) && declared > MAX_RESPONSE_BYTES) {
                response.body?.cancel().catch(() => { });
                throw new J41Error(`Response Content-Length ${declared} exceeds cap ${MAX_RESPONSE_BYTES} for ${method} ${path}`, 'RESPONSE_TOO_LARGE', response.status);
            }
            let data;
            try {
                if (response.body) {
                    const reader = response.body.getReader();
                    const decoder = new TextDecoder('utf-8');
                    let received = 0;
                    let text = '';
                    while (true) {
                        const { value, done } = await reader.read();
                        if (done)
                            break;
                        received += value.byteLength;
                        if (received > MAX_RESPONSE_BYTES) {
                            await reader.cancel().catch(() => { });
                            throw new J41Error(`Response body exceeded cap ${MAX_RESPONSE_BYTES} for ${method} ${path}`, 'RESPONSE_TOO_LARGE', response.status);
                        }
                        text += decoder.decode(value, { stream: true });
                    }
                    text += decoder.decode();
                    data = (text ? JSON.parse(text) : {});
                }
                else {
                    data = {};
                }
            }
            catch (parseErr) {
                if (parseErr instanceof J41Error)
                    throw parseErr;
                throw new J41Error(`Non-JSON response from ${method} ${path} (HTTP ${response.status})`, 'PARSE_ERROR', response.status);
            }
            if (!response.ok) {
                // Invalidate stale session on auth errors
                if (response.status === 401 || response.status === 403) {
                    this.sessionToken = null;
                }
                const error = (data?.error ?? {});
                throw new J41Error(error.message || `HTTP ${response.status}`, error.code || 'HTTP_ERROR', response.status);
            }
            return data;
        }
        finally {
            clearTimeout(timer);
        }
    }
    /** Request with automatic retry (transient failures) and re-auth (401/403) */
    async request(method, path, body) {
        let lastError;
        for (let attempt = 0; attempt < this.maxRetries; attempt++) {
            try {
                return await this._doRequest(method, path, body);
            }
            catch (e) {
                lastError = e;
                // Auth expired — try re-auth callback once (first attempt only)
                if (e instanceof J41Error && (e.statusCode === 401 || e.statusCode === 403)) {
                    if (attempt === 0 && this.onSessionExpired) {
                        try {
                            await this.onSessionExpired();
                            continue; // retry with fresh session
                        }
                        catch {
                            throw e; // re-auth failed, throw original error
                        }
                    }
                    throw e; // no callback or already retried
                }
                // Only retry on transient errors
                if (!this.isRetryable(e) || attempt === this.maxRetries - 1) {
                    throw e;
                }
                // Exponential backoff (longer for 429)
                const baseDelay = (e instanceof J41Error && e.statusCode === 429) ? 5000 : 1000;
                const delay = baseDelay * Math.pow(2, attempt);
                await new Promise(r => setTimeout(r, delay));
            }
        }
        throw lastError;
    }
    // ------------------------------------------
    // Auth endpoints
    // ------------------------------------------
    /** Get authentication challenge for login */
    async getAuthChallenge() {
        const res = await this.request('GET', '/auth/challenge');
        if (!res.data) {
            throw new J41Error('Invalid auth challenge response: missing data', 'PARSE_ERROR', 500);
        }
        return res.data;
    }
    /**
     * Single-call authentication for bridges/frameworks (M1).
     * Handles: challenge → sign → login → set session token.
     * Bridges can use J41Client directly without J41Agent.
     *
     * @param wif - Private key in WIF format
     * @param verusId - Identity name (e.g. "myagent.agentplatform@")
     * @param network - 'verus' or 'verustest' (default: 'verustest')
     * @returns Session token string
     */
    async authenticateWithWIF(wif, verusId, network = 'verustest') {
        // Step 1: Get challenge
        const { challengeId, challenge } = await this.getAuthChallenge();
        // Step 2: Sign challenge (domain guard against a MITM'd protocol-message challenge)
        (0, messages_js_1.assertNotProtocolMessage)(challenge);
        const signature = (0, signer_js_1.signMessage)(wif, challenge, network);
        // Step 3: Login
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeout);
        let loginRes;
        try {
            loginRes = await fetch(`${this.baseUrl}/auth/login`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                signal: controller.signal,
                body: JSON.stringify({ challengeId, verusId, signature }),
            });
        }
        catch (err) {
            if (err.name === 'AbortError') {
                throw new J41Error(`Login timed out after ${this.timeout}ms`, 'TIMEOUT', 408);
            }
            throw err;
        }
        finally {
            clearTimeout(timer);
        }
        if (!loginRes.ok) {
            let errMsg = loginRes.statusText;
            try {
                const err = await loginRes.json();
                errMsg = err.error?.message || errMsg;
            }
            catch { /* non-JSON */ }
            throw new J41Error(`Login failed: ${errMsg}`, 'AUTH_FAILED', loginRes.status);
        }
        const cookies = loginRes.headers.get('set-cookie');
        const match = cookies?.match(/verus_session=([^;]+)/);
        if (!match) {
            throw new J41Error('Login succeeded but no session cookie returned', 'AUTH_FAILED', 500);
        }
        this.setSessionToken(match[1]);
        return match[1];
    }
    // ------------------------------------------
    // Transaction endpoints
    // ------------------------------------------
    /** Get chain info (public — no auth required) */
    async getChainInfo() {
        const res = await this.request('GET', '/v1/tx/info');
        return res.data;
    }
    /** Get UTXOs for authenticated identity. Optionally pass an address (R-address or i-address) to query. */
    async getUtxos(address) {
        const path = address ? `/v1/tx/utxos?address=${encodeURIComponent(address)}` : '/v1/tx/utxos';
        const res = await this.request('GET', path);
        return res.data;
    }
    /** Broadcast a signed raw transaction */
    async broadcast(rawhex) {
        const res = await this.request('POST', '/v1/tx/broadcast', { rawhex });
        return res.data;
    }
    /** Get transaction status */
    async getTxStatus(txid) {
        const res = await this.request('GET', `/v1/tx/status/${encodeURIComponent(txid)}`);
        return res.data;
    }
    // ------------------------------------------
    // Onboarding endpoints
    // ------------------------------------------
    /**
     * ONE-STEP onboarding: Create identity with a WIF key (handles all steps internally)
     *
     * @param name - Agent name (without @ suffix, e.g., 'myagent')
     * @param wif - Private key in WIF format
     * @param identityAddress - The expected i-address (for signing challenge)
     * @returns OnboardStatus when complete
     *
     * @example
     * ```typescript
     * import { J41Client, signChallenge } from '@junction41/sovagent-sdk';
     *
     * const client = new J41Client({ apiUrl: 'https://api.junction41.io' });
     * const status = await client.registerIdentity('myagent', 'Uw...', 'i42...');
     * console.log('Registered:', status.identity);
     * ```
     */
    async registerIdentity(name, wif, identityAddress, network = 'verustest') {
        // Get keypair info from WIF
        const keypair = (0, keypair_js_1.keypairFromWIF)(wif, network);
        // Validate that the WIF-derived address matches the expected identity address
        if (keypair.address !== identityAddress) {
            throw new J41Error(`WIF key derives address ${keypair.address} but expected ${identityAddress}`, 'ADDRESS_MISMATCH', 400);
        }
        // Step 1: Get challenge
        const challengeRes = await this.onboard(name, keypair.address, keypair.pubkey);
        if (!challengeRes.challenge || !challengeRes.token) {
            throw new J41Error('Invalid challenge response', 'ONBOARD_ERROR', 500);
        }
        // Step 2: Sign challenge with verifymessage-compatible signature
        (0, messages_js_1.assertNotProtocolMessage)(challengeRes.challenge);
        const signature = (0, signer_js_1.signMessage)(wif, challengeRes.challenge, network);
        // Step 3: Submit with signature
        const result = await this.onboardWithSignature(name, keypair.address, keypair.pubkey, challengeRes.challenge, challengeRes.token, signature);
        if (!result.onboardId) {
            throw new J41Error('No onboardId received', 'ONBOARD_ERROR', 500);
        }
        // Step 4: Poll until registered
        return this.pollOnboardStatus(result.onboardId);
    }
    /** Poll onboarding status until complete or failed */
    async pollOnboardStatus(onboardId, maxAttempts = 30, intervalMs = 10000) {
        for (let i = 0; i < maxAttempts; i++) {
            // Wait before polling (skip first iteration to check immediately)
            if (i > 0) {
                await new Promise(r => setTimeout(r, intervalMs));
            }
            const status = await this.onboardStatus(onboardId);
            if (status.status === 'registered') {
                return status;
            }
            if (status.status === 'failed') {
                throw new J41Error(status.error || 'Registration failed', 'ONBOARD_FAILED', 500);
            }
        }
        throw new J41Error('Registration timeout', 'ONBOARD_TIMEOUT', 504);
    }
    /** Request onboarding challenge (step 1) */
    async onboard(name, address, pubkey) {
        return this.request('POST', '/v1/onboard', { name, address, pubkey });
    }
    /** Submit onboarding with signed challenge (step 2) */
    async onboardWithSignature(name, address, pubkey, challenge, token, signature) {
        return this.request('POST', '/v1/onboard', {
            name, address, pubkey, challenge, token, signature,
        });
    }
    /** Check onboarding status */
    async onboardStatus(id) {
        return this.request('GET', `/v1/onboard/status/${encodeURIComponent(id)}`);
    }
    // ------------------------------------------
    // Agent/Service endpoints
    // ------------------------------------------
    /** Register agent profile (signed payload, requires cookie auth) */
    async registerAgent(data) {
        const res = await this.request('POST', '/v1/agents/register', data);
        return res.data;
    }
    /** Register a service (requires cookie auth) */
    async registerService(data) {
        const res = await this.request('POST', '/v1/me/services', data);
        return res.data;
    }
    /** Get jobs for authenticated identity */
    async getMyJobs(params) {
        const query = new URLSearchParams();
        if (params?.status)
            query.set('status', params.status);
        if (params?.role)
            query.set('role', params.role);
        const qs = query.toString();
        const res = await this.request('GET', `/v1/me/jobs${qs ? `?${qs}` : ''}`);
        return res;
    }
    /** Accept a job. Pass paymentAddress so the backend stores where buyer should pay. */
    async acceptJob(jobId, signature, timestamp, paymentAddress) {
        const body = { signature, timestamp };
        if (paymentAddress)
            body.paymentAddress = paymentAddress;
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/accept`, body);
        return res.data;
    }
    /** Deliver a job */
    async deliverJob(jobId, deliveryHash, signature, timestamp, deliveryMessage) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/deliver`, { deliveryHash, deliveryMessage, timestamp, signature });
        return res.data;
    }
    /** Complete a job (buyer confirms delivery) */
    async completeJob(jobId, signature, timestamp) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/complete`, { timestamp, signature });
        return res.data;
    }
    /** Get job details */
    async getJob(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}`);
        return res.data;
    }
    // ------------------------------------------
    // Session lifecycle endpoints
    // ------------------------------------------
    /** Pause a job (agent-side, signals idle timeout) */
    async pauseJob(jobId) {
        await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/pause`);
    }
    /** Submit reactivation payment (buyer-side) */
    async reactivateJob(jobId, txid) {
        await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/reactivate`, txid ? { txid } : {});
    }
    /** Get sendcurrency params for an extension payment */
    async getExtensionInvoice(jobId, amount) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/extension-invoice?amount=${amount}`);
        return res.data;
    }
    // ------------------------------------------
    // Safety endpoints
    // ------------------------------------------
    /** Register a canary token so SovGuard watches for leaks */
    async registerCanary(canary) {
        const res = await this.request('POST', '/v1/me/canary', canary);
        return res.data;
    }
    /** Set communication policy (sovguard_only | sovguard_preferred | external) */
    async setCommunicationPolicy(policy, externalChannels) {
        const res = await this.request('POST', '/v1/me/communication-policy', { policy, externalChannels });
        return res.data;
    }
    // ------------------------------------------
    // Chat endpoints
    // ------------------------------------------
    /** Get chat messages for a job */
    async getChatMessages(jobId, params) {
        const query = new URLSearchParams();
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        if (params?.since)
            query.set('since', params.since);
        const qs = query.toString();
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/messages${qs ? `?${qs}` : ''}`);
        return res;
    }
    /** Send a chat message */
    async sendChatMessage(jobId, content, signature) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/messages`, { content, signature });
        return res.data;
    }
    // ------------------------------------------
    // Job lifecycle endpoints
    // ------------------------------------------
    /** Request end of session (buyer or seller) */
    async requestEndSession(jobId, reason) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/end-session`, { reason });
        return res.data;
    }
    /** Record agent payment txid (buyer submits after sending VRSC) */
    async recordPayment(jobId, txid) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/payment`, { txid });
        return res;
    }
    /** Record platform fee txid (buyer submits after sending fee) */
    async recordPlatformFee(jobId, txid) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/platform-fee`, { txid });
        return res;
    }
    /** Cancel a job (buyer only, must be in 'requested' status) */
    async cancelJob(jobId) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/cancel`, {});
        return res.data;
    }
    /** Dispute a job (buyer or seller, signed) */
    async disputeJob(jobId, reason, signature, timestamp) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/dispute`, { reason, timestamp, signature });
        return res.data;
    }
    /** Respond to a dispute (agent/seller side) */
    async respondToDispute(jobId, options) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/dispute/respond`, options);
        return res;
    }
    /** Accept a rework offer (buyer side) */
    async acceptRework(jobId, options) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/dispute/rework-accept`, options);
        return res;
    }
    /** Get payment QR code data for a job */
    async getPaymentQr(jobId, type = 'agent') {
        const query = new URLSearchParams({ type });
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/payment-qr?${query}`);
        return res.data;
    }
    /** Get job by hash (public) */
    async getJobByHash(hash) {
        const res = await this.request('GET', `/v1/jobs/hash/${encodeURIComponent(hash)}`);
        return res.data;
    }
    /** Get jobs with unread messages */
    async getUnreadJobs() {
        const res = await this.request('GET', '/v1/me/unread-jobs');
        return res.data;
    }
    // ------------------------------------------
    // Inbox endpoints
    // ------------------------------------------
    /** Get pending inbox items */
    async getInbox(status = 'pending', limit = 20) {
        const query = new URLSearchParams({ status, limit: String(limit) });
        return this.request('GET', `/v1/me/inbox?${query}`);
    }
    /** Get a specific inbox item with full details and update command */
    async getInboxItem(id) {
        return this.request('GET', `/v1/me/inbox/${encodeURIComponent(id)}`);
    }
    /** Accept an inbox item (mark as processed, optionally record txid) */
    async acceptInboxItem(id, txid) {
        return this.request('POST', `/v1/me/inbox/${encodeURIComponent(id)}/accept`, { txid });
    }
    /** Get raw identity data from chain (for offline tx building) */
    async getIdentityRaw() {
        return this.request('GET', '/v1/me/identity/raw');
    }
    /**
     * Resolve any VerusID's primary R-addresses + multisig threshold.
     *
     * Public endpoint, no auth required. Used by dispatchers to independently verify
     * v2 canonical signatures without trusting J41's forwarding layer.
     */
    async getIdentityKeys(idOrName) {
        const res = await this.request('GET', `/v1/identity/${encodeURIComponent(idOrName)}/keys`);
        const data = res.data;
        // SECURITY (trust anchor): this endpoint decides which addresses may sign
        // for an identity — every signature check depends on it, and it is MITM-able
        // over the wire. When an operator pins the platform signer
        // (J41_PLATFORM_SIGNER = the platform's R-address), REQUIRE and verify a
        // `platformSignature` over the JCS-canonical response, so a tampered
        // primaryAddresses set can't be trusted. Unset by default → no behaviour
        // change until the backend ships signed responses (see backend report #2).
        const pinned = process.env.J41_PLATFORM_SIGNER;
        // Audit 2026-06-02 H9: on mainnet, the platform signer pin is REQUIRED —
        // letting the platform's getIdentityKeys response go unverified on mainnet
        // means a MITM/compromised platform becomes the on-chain trust anchor for
        // every downstream signature verification. On testnet the pin remains
        // optional for dev workflows. Override with J41_REQUIRE_PLATFORM_SIGNER=0
        // for legacy mainnet ops while the operator migrates (deprecated; will be
        // removed in the next major release).
        //
        // We don't have a `networkType` field on the client, so detect mainnet by
        // the base URL pattern OR the J41_NETWORK env. baseUrl test catches the
        // default production endpoint; the env is the explicit override for
        // operators on custom domains.
        const requireOnMainnet = process.env.J41_REQUIRE_PLATFORM_SIGNER !== '0';
        const looksLikeMainnet = /^https:\/\/(api\.)?junction41\.(io|com|net)/i.test(this.baseUrl)
            || process.env.J41_NETWORK === 'verus';
        if (looksLikeMainnet && requireOnMainnet && !pinned) {
            throw new J41Error('getIdentityKeys refused on mainnet: J41_PLATFORM_SIGNER is unset and the ' +
                'platform-signed response is required for trust-anchor integrity on the ' +
                'production network. Set J41_PLATFORM_SIGNER to the platform\'s R-address, ' +
                'or J41_REQUIRE_PLATFORM_SIGNER=0 to opt out (deprecated).', 'PLATFORM_SIGNER_REQUIRED', 500);
        }
        if (pinned) {
            const { platformSignature, ...signed } = data;
            if (typeof platformSignature !== 'string' || !platformSignature) {
                throw new J41Error('Identity-keys response is unsigned but a platform signer is pinned', 'KEYS_UNSIGNED', 502);
            }
            // The Verus message prefix is network-independent, so verification does
            // not depend on the network param. Signed payload = JCS-canonical of the
            // response data with platformSignature removed.
            const canonicalize = require('json-canonicalize').canonicalize;
            if (!(0, signer_js_1.verifyMessage)(canonicalize(signed), pinned, platformSignature)) {
                throw new J41Error('Identity-keys response failed platform-signature verification (possible tampering/MITM)', 'KEYS_BAD_SIGNATURE', 502);
            }
        }
        return data;
    }
    // ------------------------------------------
    // Agent Profile endpoints
    // ------------------------------------------
    /** Update agent profile (privacy tier, etc.) */
    async updateAgentProfile(data) {
        const res = await this.request('PATCH', '/v1/me/agent', data);
        return res.data;
    }
    // ------------------------------------------
    // Job Extension endpoints
    // ------------------------------------------
    /** Request a session extension (additional payment for more work) */
    async requestExtension(jobId, amount, reason) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/extensions`, { amount, reason });
        return res.data;
    }
    /** Get extensions for a job */
    async getExtensions(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/extensions`);
        return res.data;
    }
    /** Approve an extension request */
    async approveExtension(jobId, extensionId) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/extensions/${encodeURIComponent(extensionId)}/approve`, {});
        return res.data;
    }
    /** Reject an extension request */
    async rejectExtension(jobId, extensionId) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/extensions/${encodeURIComponent(extensionId)}/reject`, {});
        return res.data;
    }
    /** Request additional budget for a job */
    async requestBudget(jobId, params) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/budget-request`, params);
        return res.data;
    }
    /** Submit extension payment txids */
    async payExtension(jobId, extensionId, agentTxid, feeTxid) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/extensions/${encodeURIComponent(extensionId)}/payment`, { agentTxid, feeTxid });
        return res.data;
    }
    // ------------------------------------------
    // Attestation endpoints
    // ------------------------------------------
    /** Submit a deletion attestation */
    async submitAttestation(attestation) {
        const res = await this.request('POST', '/v1/me/attestations', attestation);
        return res.data;
    }
    /** Get attestations for an agent */
    async getAttestations(agentId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(agentId)}/attestations`);
        return res.data;
    }
    // ------------------------------------------
    // Pricing calculator endpoints
    // ------------------------------------------
    /**
     * Get a pricing recommendation from the platform calculator.
     *
     * This is a calculator (token cost × category markup × privacy multiplier),
     * not a market oracle. Method name kept for backwards compatibility — a
     * percentile-based market endpoint will land separately once mainnet has
     * enough completed-job volume to produce meaningful distributions.
     */
    async queryPricingOracle(params) {
        const query = new URLSearchParams();
        if (params.model)
            query.set('model', params.model);
        if (params.category)
            query.set('category', params.category);
        if (params.inputTokens != null)
            query.set('inputTokens', String(params.inputTokens));
        if (params.outputTokens != null)
            query.set('outputTokens', String(params.outputTokens));
        if (params.privacyTier)
            query.set('privacyTier', params.privacyTier);
        if (params.vrscUsdRate != null)
            query.set('vrscUsdRate', String(params.vrscUsdRate));
        const qs = query.toString();
        const res = await this.request('GET', `/v1/pricing/recommend${qs ? `?${qs}` : ''}`);
        return res.data;
    }
    /** Get pricing models list */
    async getPricingModels() {
        const res = await this.request('GET', '/v1/pricing/models');
        return res.data;
    }
    // ------------------------------------------
    // Job creation endpoints
    // ------------------------------------------
    /** Get the message format for creating a job request (for signing) */
    async getJobRequestMessage(params) {
        const query = new URLSearchParams();
        query.set('sellerVerusId', params.sellerVerusId);
        query.set('description', params.description);
        query.set('amount', String(params.amount));
        if (params.currency)
            query.set('currency', params.currency);
        if (params.deadline)
            query.set('deadline', params.deadline);
        if (params.timestamp != null)
            query.set('timestamp', String(params.timestamp));
        if (params.sovguardEnabled === false)
            query.set('sovguardEnabled', 'false');
        const res = await this.request('GET', `/v1/jobs/message/request?${query}`);
        return res.data;
    }
    /** Create a new job request (buyer → seller) */
    async createJob(data) {
        const res = await this.request('POST', '/v1/jobs', data);
        return res.data;
    }
    // ------------------------------------------
    // Service management endpoints
    // ------------------------------------------
    /** Browse all services (public) */
    async getServices(params) {
        const query = new URLSearchParams();
        if (params?.agentId)
            query.set('agentId', params.agentId);
        if (params?.verusId)
            query.set('verusId', params.verusId);
        if (params?.category)
            query.set('category', params.category);
        if (params?.status)
            query.set('status', params.status);
        if (params?.minPrice != null)
            query.set('minPrice', String(params.minPrice));
        if (params?.maxPrice != null)
            query.set('maxPrice', String(params.maxPrice));
        if (params?.q)
            query.set('q', params.q);
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        if (params?.sort)
            query.set('sort', params.sort);
        if (params?.order)
            query.set('order', params.order);
        const qs = query.toString();
        return this.request('GET', `/v1/services${qs ? `?${qs}` : ''}`);
    }
    /** Get service categories (public) */
    async getServiceCategories() {
        const res = await this.request('GET', '/v1/services/categories');
        return res.data;
    }
    /** Get a specific service (public) */
    async getService(serviceId) {
        const res = await this.request('GET', `/v1/services/${encodeURIComponent(serviceId)}`);
        return res.data;
    }
    /** Get an agent's services (public) */
    async getAgentServices(verusId) {
        return this.request('GET', `/v1/services/agent/${encodeURIComponent(verusId)}`);
    }
    /** List my services (authenticated) */
    async getMyServices() {
        return this.request('GET', '/v1/me/services');
    }
    /** Update a service (authenticated, owner only) */
    async updateService(serviceId, data) {
        const res = await this.request('PUT', `/v1/me/services/${encodeURIComponent(serviceId)}`, data);
        return res.data;
    }
    /** Delete a service (authenticated, owner only) */
    async deleteService(serviceId) {
        const res = await this.request('DELETE', `/v1/me/services/${encodeURIComponent(serviceId)}`);
        return res.data;
    }
    // ------------------------------------------
    // API Proxy / Access endpoints
    // ------------------------------------------
    /** Discover API endpoint providers (agents with serviceType=api-endpoint) */
    async discoverApiProvider(sellerVerusId) {
        const agent = await this.getAgent(sellerVerusId);
        const svcResp = await this.getAgentServices(sellerVerusId);
        const services = svcResp.data || [];
        const apiServices = services.filter((s) => s.serviceType === 'api-endpoint');
        return { agent, apiServices };
    }
    /** Request API access — J41 forwards this to the seller's dispatcher */
    async requestApiAccess(sellerVerusId, accessRequest) {
        const res = await this.request('POST', `/v1/proxy/access/${encodeURIComponent(sellerVerusId)}`, accessRequest);
        return res.data || res;
    }
    /** List API endpoint providers on the marketplace */
    async listApiProviders(params) {
        const query = new URLSearchParams();
        query.set('serviceType', 'api-endpoint');
        if (params?.category)
            query.set('category', params.category);
        if (params?.limit)
            query.set('limit', String(params.limit));
        if (params?.offset)
            query.set('offset', String(params.offset));
        return this.request('GET', `/v1/services?${query}`);
    }
    /**
     * Call a proxied API endpoint (helper for buyers who've already exchanged a key).
     *
     * Handles the common case: take an API key + endpoint URL obtained from a decrypted
     * AccessEnvelope, POST an OpenAI-compatible body, and return the parsed response with
     * J41 headers. Streaming responses return the raw Response object so the caller can
     * read the body stream themselves.
     *
     * Throws on HTTP >= 400 with a J41Error-style message including the X-J41-* context.
     */
    async callProxied(opts) {
        const base = opts.endpointUrl.replace(/\/$/, '');
        const path = opts.path || '/v1/chat/completions';
        const url = `${base}${path.startsWith('/') ? '' : '/'}${path}`;
        const controller = new AbortController();
        const timer = opts.timeoutMs ? setTimeout(() => controller.abort(), opts.timeoutMs) : null;
        let res;
        try {
            res = await fetch(url, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${opts.apiKey}`,
                },
                body: JSON.stringify(opts.body),
                signal: controller.signal,
            });
        }
        finally {
            if (timer)
                clearTimeout(timer);
        }
        const headers = {};
        res.headers.forEach((v, k) => { headers[k] = v; });
        const sessionId = headers['x-j41-session'];
        const creditRaw = headers['x-j41-credit-remaining'];
        const creditRemaining = creditRaw != null ? Number.parseFloat(creditRaw) : undefined;
        const model = headers['x-j41-model'];
        const isStream = opts.body.stream === true;
        let body = null;
        if (!isStream) {
            const text = await res.text();
            try {
                body = JSON.parse(text);
            }
            catch {
                body = text;
            }
        }
        if (!res.ok && !isStream) {
            const errMsg = (body && typeof body === 'object' && 'error' in body && body.error) || `HTTP ${res.status}`;
            const err = new Error(`Proxy call failed: ${typeof errMsg === 'string' ? errMsg : JSON.stringify(errMsg)} (session=${sessionId || 'n/a'}, credit=${creditRaw || 'n/a'})`);
            err.statusCode = res.status;
            err.responseHeaders = headers;
            err.responseBody = body;
            throw err;
        }
        return { ok: res.ok, status: res.status, headers, body, sessionId, creditRemaining, model, raw: res };
    }
    // ------------------------------------------
    // File sharing endpoints
    // ------------------------------------------
    /** Upload a file to a job (multipart/form-data) */
    async uploadFile(jobId, file, filename, mimeType) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeout);
        try {
            const formData = new FormData();
            const blob = file instanceof Blob ? file : new Blob([file], { type: mimeType || 'application/octet-stream' });
            formData.append('file', blob, filename);
            const headers = {};
            if (this.sessionToken) {
                headers['Cookie'] = `verus_session=${this.sessionToken}`;
            }
            const response = await fetch(`${this.baseUrl}/v1/jobs/${encodeURIComponent(jobId)}/files`, {
                method: 'POST',
                headers,
                body: formData,
                signal: controller.signal,
            });
            const data = await response.json();
            if (!response.ok) {
                if (response.status === 401 || response.status === 403)
                    this.sessionToken = null;
                const error = (data?.error ?? {});
                throw new J41Error(error.message || `HTTP ${response.status}`, error.code || 'HTTP_ERROR', response.status);
            }
            return data.data;
        }
        finally {
            clearTimeout(timer);
        }
    }
    /** List files for a job */
    async getJobFiles(jobId) {
        return this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/files`);
    }
    /** Download a file (returns raw response for streaming) */
    async downloadFile(jobId, fileId) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeout);
        try {
            const headers = {};
            if (this.sessionToken) {
                headers['Cookie'] = `verus_session=${this.sessionToken}`;
            }
            const response = await fetch(`${this.baseUrl}/v1/jobs/${encodeURIComponent(jobId)}/files/${encodeURIComponent(fileId)}`, {
                method: 'GET',
                headers,
                signal: controller.signal,
            });
            if (!response.ok) {
                if (response.status === 401 || response.status === 403)
                    this.sessionToken = null;
                let errData = {};
                try {
                    errData = await response.json();
                }
                catch { /* binary response */ }
                const error = (errData?.error ?? {});
                throw new J41Error(error.message || `HTTP ${response.status}`, error.code || 'HTTP_ERROR', response.status);
            }
            const disposition = response.headers.get('content-disposition') || '';
            const filenameMatch = disposition.match(/filename="([^"]+)"/);
            return {
                data: await response.arrayBuffer(),
                filename: filenameMatch?.[1] || 'download',
                mimeType: response.headers.get('content-type') || 'application/octet-stream',
                checksum: response.headers.get('x-checksum-sha256') || '',
            };
        }
        finally {
            clearTimeout(timer);
        }
    }
    /** Delete a file (uploader only) */
    async deleteFile(jobId, fileId) {
        const res = await this.request('DELETE', `/v1/jobs/${encodeURIComponent(jobId)}/files/${encodeURIComponent(fileId)}`);
        return res.data;
    }
    // ------------------------------------------
    // Agent discovery endpoints
    // ------------------------------------------
    /** List agents (public) */
    async getAgents(params) {
        const query = new URLSearchParams();
        if (params?.status)
            query.set('status', params.status);
        if (params?.type)
            query.set('type', params.type);
        if (params?.capability)
            query.set('capability', params.capability);
        if (params?.owner)
            query.set('owner', params.owner);
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        if (params?.sort)
            query.set('sort', params.sort);
        if (params?.order)
            query.set('order', params.order);
        const qs = query.toString();
        return this.request('GET', `/v1/agents${qs ? `?${qs}` : ''}`);
    }
    /** Get agent details (public) */
    async getAgent(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}`);
        return res.data;
    }
    /** Get agent capabilities (public) */
    async getAgentCapabilities(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/capabilities`);
        return res.data;
    }
    /** Search agents by keyword (public) */
    async searchAgents(params) {
        const query = new URLSearchParams();
        query.set('q', params.q);
        if (params.type)
            query.set('type', params.type);
        if (params.status)
            query.set('status', params.status);
        if (params.verified != null)
            query.set('verified', String(params.verified));
        if (params.limit != null)
            query.set('limit', String(params.limit));
        if (params.offset != null)
            query.set('offset', String(params.offset));
        return this.request('GET', `/v1/search?${query}`);
    }
    /** Toggle agent status (active/inactive). Requires signed payload. */
    async setAgentStatus(verusId, status, signature, timestamp, nonce) {
        const body = { status, signature, timestamp };
        if (nonce)
            body.nonce = nonce;
        const res = await this.request('POST', `/v1/agents/${encodeURIComponent(verusId)}/status`, body);
        return res.data;
    }
    // ------------------------------------------
    // Reviews & Reputation endpoints
    // ------------------------------------------
    /** Get reviews for an agent (public) */
    async getAgentReviews(verusId, params) {
        const query = new URLSearchParams();
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        if (params?.verified != null)
            query.set('verified', String(params.verified));
        const qs = query.toString();
        return this.request('GET', `/v1/reviews/agent/${encodeURIComponent(verusId)}${qs ? `?${qs}` : ''}`);
    }
    /** Get reviews left by a buyer (public) */
    async getBuyerReviews(verusId, params) {
        const query = new URLSearchParams();
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        const qs = query.toString();
        return this.request('GET', `/v1/reviews/buyer/${encodeURIComponent(verusId)}${qs ? `?${qs}` : ''}`);
    }
    /** Get review for a specific job (public) */
    async getJobReview(jobHash) {
        const res = await this.request('GET', `/v1/reviews/job/${encodeURIComponent(jobHash)}`);
        return res.data;
    }
    /** Get agent reputation score (public) */
    async getReputation(verusId, quick = false) {
        const query = quick ? '?quick=true' : '';
        const res = await this.request('GET', `/v1/reputation/${encodeURIComponent(verusId)}${query}`);
        return res.data;
    }
    /** Get top agents by reputation (public) */
    async getTopAgents(limit = 10) {
        const res = await this.request('GET', `/v1/reputation/top?limit=${limit}`);
        return res.data;
    }
    // ------------------------------------------
    // Data privacy endpoints
    // ------------------------------------------
    /** Get an agent's data policy (public) */
    async getAgentDataPolicy(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/data-policy`);
        return res.data;
    }
    /** Set my data policy (authenticated) */
    async setDataPolicy(policy) {
        const res = await this.request('PUT', '/v1/me/data-policy', policy);
        return res.data;
    }
    /** Get job data terms and attestation status */
    async getJobDataTerms(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/data-terms`);
        return res.data;
    }
    /** Get current privacy tier (standard/private/sovereign) */
    async getPrivacyTier() {
        const res = await this.request('GET', '/v1/me/privacy');
        return res.data;
    }
    /** Set privacy tier */
    async setPrivacyTier(tier) {
        const res = await this.request('POST', '/v1/me/privacy', { tier });
        return res.data;
    }
    // ------------------------------------------
    // Deletion attestation endpoints
    // ------------------------------------------
    /** Get the message to sign for a deletion attestation */
    async getDeletionAttestationMessage(jobId, timestamp) {
        const query = timestamp != null ? `?timestamp=${timestamp}` : '';
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/deletion-attestation/message${query}`);
        return res.data;
    }
    /** Submit a signed deletion attestation for a job */
    async submitDeletionAttestation(jobId, signature, timestamp) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/deletion-attestation`, { signature, timestamp });
        return res.data;
    }
    /** Get deletion attestation for a job */
    async getDeletionAttestation(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/deletion-attestation`);
        return res.data;
    }
    // ------------------------------------------
    // Content moderation endpoints
    // ------------------------------------------
    /** Get held messages for a job */
    async getHeldMessages(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/held-messages`);
        return res.data;
    }
    /** Appeal a held message */
    async appealHeldMessage(jobId, messageId, reason) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/held-messages/${encodeURIComponent(messageId)}/appeal`, { reason });
        return res.data;
    }
    /** Release a held message (buyer only) */
    async releaseHeldMessage(jobId, messageId) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/held-messages/${encodeURIComponent(messageId)}/release`, {});
        return res.data;
    }
    /** Reject a held message (buyer only) */
    async rejectHeldMessage(jobId, messageId) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/held-messages/${encodeURIComponent(messageId)}/reject`, {});
        return res.data;
    }
    /** Get hold queue statistics */
    async getHoldQueueStats() {
        const res = await this.request('GET', '/v1/hold-queue/stats');
        return res.data;
    }
    // ------------------------------------------
    // Additional safety endpoints
    // ------------------------------------------
    /** List registered canary tokens */
    async getCanaries() {
        const res = await this.request('GET', '/v1/me/canary');
        return res.canaries;
    }
    /** Delete a canary token */
    async deleteCanary(canaryId) {
        return this.request('DELETE', `/v1/me/canary/${encodeURIComponent(canaryId)}`);
    }
    /** Get communication policy */
    async getCommunicationPolicy() {
        return this.request('GET', '/v1/me/communication-policy');
    }
    // ------------------------------------------
    // Additional inbox endpoints
    // ------------------------------------------
    /** Reject an inbox item */
    async rejectInboxItem(id) {
        return this.request('POST', `/v1/me/inbox/${encodeURIComponent(id)}/reject`, {});
    }
    /** Get pending inbox count */
    async getInboxCount() {
        const res = await this.request('GET', '/v1/me/inbox/count');
        return res.data;
    }
    // ------------------------------------------
    // Alerts endpoints
    // ------------------------------------------
    /** Get my alerts */
    async getAlerts() {
        const res = await this.request('GET', '/v1/me/alerts');
        return res.data;
    }
    /** Dismiss an alert */
    async dismissAlert(alertId) {
        const res = await this.request('POST', `/v1/me/alerts/${encodeURIComponent(alertId)}/dismiss`, {});
        return res.data;
    }
    /** Report an alert */
    async reportAlert(alertId) {
        const res = await this.request('POST', `/v1/me/alerts/${encodeURIComponent(alertId)}/report`, {});
        return res.data;
    }
    // ------------------------------------------
    // Webhook endpoints
    // ------------------------------------------
    /** Register a webhook endpoint for receiving platform events */
    async registerWebhook(url, events, secret) {
        const res = await this.request('POST', '/v1/me/webhooks', { url, events, secret });
        return res.data;
    }
    /** List registered webhooks */
    async listWebhooks() {
        const res = await this.request('GET', '/v1/me/webhooks');
        return res.data;
    }
    /** Delete a webhook by ID */
    async deleteWebhook(webhookId) {
        const res = await this.request('DELETE', `/v1/me/webhooks/${encodeURIComponent(webhookId)}`);
        return res.data;
    }
    // ------------------------------------------
    // Auth session endpoints
    // ------------------------------------------
    /** Login with signed challenge (sets session token from response cookie) */
    async login(challengeId, verusId, signature) {
        const controller = new AbortController();
        const timer = setTimeout(() => controller.abort(), this.timeout);
        try {
            const response = await fetch(`${this.baseUrl}/auth/login`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
                body: JSON.stringify({ challengeId, verusId, signature }),
                signal: controller.signal,
            });
            const data = await response.json();
            if (!response.ok) {
                const error = (data?.error ?? {});
                throw new J41Error(error.message || `HTTP ${response.status}`, error.code || 'HTTP_ERROR', response.status);
            }
            // Extract session cookie from Set-Cookie header
            const setCookie = response.headers.get('set-cookie') || '';
            const sessionMatch = setCookie.match(/verus_session=([^;]+)/);
            if (sessionMatch) {
                this.setSessionToken(sessionMatch[1]);
            }
            return data.data;
        }
        finally {
            clearTimeout(timer);
        }
    }
    /** Get current session info */
    async getSession() {
        const res = await this.request('GET', '/auth/session');
        return res.data;
    }
    /** Logout (clears session) */
    async logout() {
        await this.request('POST', '/auth/logout', {});
        this.sessionToken = null;
    }
    /** Get capabilities list (public) */
    async getCapabilities() {
        const res = await this.request('GET', '/v1/capabilities');
        return res.data;
    }
    /** Health check */
    async health() {
        return this.request('GET', '/v1/health');
    }
    // ------------------------------------------
    // Trust score endpoints
    // ------------------------------------------
    /** Get trust score for an agent (public) */
    async getTrustScore(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/trust`);
        return res.data;
    }
    /** Get my trust score detail (authenticated) */
    async getMyTrust() {
        const res = await this.request('GET', '/v1/me/trust');
        return res.data;
    }
    /** Get my trust score history (authenticated) */
    async getMyTrustHistory() {
        const res = await this.request('GET', '/v1/me/trust/history');
        return res.data;
    }
    /** Get workspace session status for a job */
    async getWorkspaceStatus(jobId) {
        const res = await this.request('GET', `/v1/jailbox/${jobId}`);
        return res.data;
    }
    /**
     * Create a buyer workspace session for a job. Returns the workspace UID.
     * Uses POST /v1/jailbox/{jobId}/token (same endpoint the dashboard uses).
     * Returns 409 if workspace already exists for this job.
     */
    async initBuyerWorkspace(jobId) {
        const res = await this.request('POST', `/v1/jailbox/${encodeURIComponent(jobId)}/token`);
        return res.data;
    }
    // ------------------------------------------
    // Bounty endpoints
    // ------------------------------------------
    /** Browse open bounties (public) */
    async getBounties(params) {
        const query = new URLSearchParams();
        if (params?.category)
            query.set('category', params.category);
        if (params?.minAmount != null)
            query.set('minAmount', String(params.minAmount));
        if (params?.maxAmount != null)
            query.set('maxAmount', String(params.maxAmount));
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        const qs = query.toString();
        return this.request('GET', `/v1/bounties${qs ? `?${qs}` : ''}`);
    }
    /** Get bounty detail (public) */
    async getBounty(bountyId) {
        const res = await this.request('GET', `/v1/bounties/${encodeURIComponent(bountyId)}`);
        return res.data;
    }
    /** Post a new bounty (signed, requires auth) */
    async postBounty(data) {
        const res = await this.request('POST', '/v1/bounties', data);
        return res.data;
    }
    /** Apply to a bounty (signed, requires auth) */
    async applyToBounty(bountyId, data) {
        const res = await this.request('POST', `/v1/bounties/${encodeURIComponent(bountyId)}/apply`, data);
        return res.data;
    }
    /** Select bounty claimants (poster only, signed, requires auth) */
    async selectBountyClaimants(bountyId, data) {
        const res = await this.request('POST', `/v1/bounties/${encodeURIComponent(bountyId)}/select`, data);
        return res.data;
    }
    /** Cancel a bounty (poster only, requires auth) */
    async cancelBounty(bountyId) {
        const res = await this.request('DELETE', `/v1/bounties/${encodeURIComponent(bountyId)}`);
        return res.data;
    }
    /** Get bounties posted by or applied to by the authenticated agent */
    async getMyBounties(params) {
        const query = new URLSearchParams();
        if (params?.role)
            query.set('role', params.role);
        if (params?.status)
            query.set('status', params.status);
        if (params?.limit != null)
            query.set('limit', String(params.limit));
        if (params?.offset != null)
            query.set('offset', String(params.offset));
        const qs = query.toString();
        return this.request('GET', `/v1/me/bounties${qs ? `?${qs}` : ''}`);
    }
    // ------------------------------------------
    // Notification endpoints
    // ------------------------------------------
    /** Get notifications (authenticated) */
    async getNotifications(limit) {
        const query = new URLSearchParams();
        if (limit != null)
            query.set('limit', String(limit));
        const qs = query.toString();
        return this.request('GET', `/v1/me/notifications${qs ? `?${qs}` : ''}`);
    }
    /** Acknowledge (mark as read) notifications by IDs */
    async ackNotifications(ids) {
        const res = await this.request('POST', '/v1/me/notifications/ack', { ids });
        return res.data;
    }
    // ------------------------------------------
    // Additional dispute endpoints
    // ------------------------------------------
    /** Get dispute details for a job (participants only) */
    async getDispute(jobId) {
        const res = await this.request('GET', `/v1/jobs/${encodeURIComponent(jobId)}/dispute`);
        return res.dispute;
    }
    /** Submit refund transaction ID to complete the refund flow */
    async submitRefundTxid(jobId, txid) {
        return this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/dispute/refund-txid`, { txid });
    }
    /** Get public dispute metrics for an agent (from transparency profile) */
    async getDisputeMetrics(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/transparency`);
        return res.data?.computed?.disputes || {};
    }
    // ------------------------------------------
    // Review submission endpoints
    // ------------------------------------------
    /** Submit a signed review */
    async submitReview(data) {
        const res = await this.request('POST', '/v1/reviews', data);
        return res.data;
    }
    /** Submit a signed review for an API endpoint proxy session (no job hash — uses sessionId). */
    async submitApiSessionReview(data) {
        const res = await this.request('POST', '/v1/reviews/api-session', data);
        return res.data;
    }
    /** Get the message format that needs to be signed for a review */
    async getReviewMessage(params) {
        const query = new URLSearchParams();
        query.set('agentVerusId', params.agentVerusId);
        query.set('jobHash', params.jobHash);
        if (params.message)
            query.set('message', params.message);
        if (params.rating != null)
            query.set('rating', String(params.rating));
        if (params.timestamp != null)
            query.set('timestamp', String(params.timestamp));
        const res = await this.request('GET', `/v1/reviews/message?${query}`);
        return res.data;
    }
    // ------------------------------------------
    // Additional webhook endpoints
    // ------------------------------------------
    /** Update a webhook (partial update) */
    async updateWebhook(webhookId, data) {
        const res = await this.request('PATCH', `/v1/me/webhooks/${encodeURIComponent(webhookId)}`, data);
        return res.data;
    }
    /** Test a webhook by sending a test payload */
    async testWebhook(webhookId) {
        const res = await this.request('POST', `/v1/me/webhooks/${encodeURIComponent(webhookId)}/test`, {});
        return res.data;
    }
    // ------------------------------------------
    // Name resolution endpoints
    // ------------------------------------------
    /** Bulk resolve i-addresses to friendly names */
    async resolveNames(iAddresses) {
        const res = await this.request('POST', '/v1/resolve-names', { addresses: iAddresses });
        return res.data;
    }
    // ------------------------------------------
    // Agent refresh endpoint
    // ------------------------------------------
    /** Trigger backend to re-read agent identity from chain. No auth needed. */
    async refreshAgent(verusId) {
        const res = await this.request('POST', `/v1/agents/${encodeURIComponent(verusId)}/refresh`);
        return res.data;
    }
    // ------------------------------------------
    // Public stats endpoints
    // ------------------------------------------
    /** Get public platform statistics (no auth needed) */
    async getPublicStats() {
        const res = await this.request('GET', '/v1/public-stats');
        return res.data;
    }
    // ------------------------------------------
    // Verification & Transparency endpoints
    // ------------------------------------------
    /** Get verification status for an agent (public) */
    async getVerificationStatus(agentId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(agentId)}/verification`);
        return res.data;
    }
    /** Get transparency profile for an agent (public) */
    async getTransparencyProfile(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/transparency`);
        return res.data;
    }
    // ------------------------------------------
    // Agent update endpoints
    // ------------------------------------------
    /** Update an agent's profile data */
    async updateAgent(agentId, data) {
        const res = await this.request('POST', `/v1/agents/${encodeURIComponent(agentId)}/update`, data);
        return res.data;
    }
    // ------------------------------------------
    // Webhook delivery endpoints
    // ------------------------------------------
    /** Get delivery history for a webhook */
    async getWebhookDeliveries(webhookId, limit) {
        const query = new URLSearchParams();
        if (limit != null)
            query.set('limit', String(limit));
        const qs = query.toString();
        const res = await this.request('GET', `/v1/me/webhooks/${encodeURIComponent(webhookId)}/deliveries${qs ? `?${qs}` : ''}`);
        return res.data;
    }
    // ------------------------------------------
    // Delivery rejection endpoints
    // ------------------------------------------
    /** Reject a job delivery with a reason (buyer only) */
    async rejectDelivery(jobId, reason) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/reject-delivery`, { reason });
        return res.data;
    }
    // ------------------------------------------
    // Combined payment endpoint
    // ------------------------------------------
    /** Record a single combined payment txid (agent + fee in one tx) */
    async recordPaymentCombined(jobId, txid) {
        const res = await this.request('POST', `/v1/jobs/${encodeURIComponent(jobId)}/payment-combined`, { txid });
        return res;
    }
    // ------------------------------------------
    // Featured & Trending service endpoints
    // ------------------------------------------
    /** Get featured services (public) */
    async getFeaturedServices() {
        return this.request('GET', '/v1/services/featured');
    }
    /** Get trending services (public) */
    async getTrendingServices() {
        return this.request('GET', '/v1/services/trending');
    }
    // ------------------------------------------
    // Identity endpoints
    // ------------------------------------------
    /** Get my on-chain identity data (authenticated) */
    async getMyIdentity() {
        const res = await this.request('GET', '/v1/me/identity');
        return res.data;
    }
    // ------------------------------------------
    // Onboard retry endpoint
    // ------------------------------------------
    /** Retry a failed onboarding attempt */
    async retryOnboard(onboardId) {
        return this.request('POST', `/v1/onboard/retry/${encodeURIComponent(onboardId)}`);
    }
    // ------------------------------------------
    // Balance endpoint
    // ------------------------------------------
    /** Get the authenticated agent's on-chain balance */
    async getBalance() {
        const res = await this.request('GET', '/v1/me/balance');
        return res.data;
    }
    // ------------------------------------------
    // Agent payment address endpoint
    // ------------------------------------------
    /** Get the payment address for an agent (public) */
    async getAgentPaymentAddress(verusId) {
        const res = await this.request('GET', `/v1/agents/${encodeURIComponent(verusId)}/payment-address`);
        return res.data;
    }
    // ------------------------------------------
    // Payment verification endpoint
    // ------------------------------------------
    /** Verify a payment transaction on-chain */
    async verifyPayment(params) {
        const query = new URLSearchParams();
        query.set('txid', params.txid);
        query.set('expectedAddress', params.expectedAddress);
        query.set('expectedAmount', String(params.expectedAmount));
        query.set('currency', params.currency);
        // When supplied, the platform verifies the funding tx originated from this
        // VerusID/address and echoes the resolved sender (see backend-requests/
        // deposit-sender-verification.md). Older platforms ignore it and omit the
        // sender fields; callers must treat a missing senderVerified as "unknown".
        if (params.expectedSender)
            query.set('expectedSender', params.expectedSender);
        const res = await this.request('GET', `/v1/tx/verify-payment?${query}`);
        return res.data;
    }
    // ------------------------------------------
    // Currency endpoints
    // ------------------------------------------
    /** Get supported currencies (public) */
    async getCurrencies() {
        const res = await this.request('GET', '/v1/currencies');
        return res.data;
    }
    // ------------------------------------------
    // Earnings endpoint
    // ------------------------------------------
    /** Get my earnings summary (authenticated) */
    async getMyEarnings() {
        const res = await this.request('GET', '/v1/me/earnings');
        return res.data;
    }
    // ------------------------------------------
    // Agent name check endpoint
    // ------------------------------------------
    /** Check if an agent name is available (public) */
    async checkAgentName(name) {
        const res = await this.request('GET', `/v1/agents/check-name/${encodeURIComponent(name)}`);
        return res.data;
    }
}
exports.J41Client = J41Client;
// ------------------------------------------
// Error class
// ------------------------------------------
class J41Error extends Error {
    code;
    statusCode;
    constructor(message, code, statusCode) {
        super(message);
        this.name = 'J41Error';
        this.code = code;
        this.statusCode = statusCode;
    }
}
exports.J41Error = J41Error;
//# sourceMappingURL=index.js.map