import type { ClientOptions, MatchRequest, MatchResponse, HealthResponse, MetricsResponse, WalletSignRequest, WalletSignResponse, RequestOptions, BlockSummary, BlockListResponse, TransactionSummary, TransactionListResponse, AddressSummary, AddressListResponse, ReceiptSummary, ReceiptListResponse, MarketplaceOffer, MarketplaceStats, MarketplaceBid, MarketplaceSession, JobSubmission, Job, JobStatus, JobResult, } from "./types"; const DEFAULT_HEADERS = { "Content-Type": "application/json", Accept: "application/json", }; export class AitbcClient { private readonly baseUrl: string; private readonly apiKey?: string; private readonly basicAuth?: ClientOptions["basicAuth"]; private readonly fetchImpl: typeof fetch; private readonly timeout?: number; constructor(options: ClientOptions) { this.baseUrl = options.baseUrl.replace(/\/$/, ""); this.apiKey = options.apiKey; this.basicAuth = options.basicAuth; this.fetchImpl = options.fetchImpl ?? fetch; this.timeout = options.timeout; } // Coordinator API Methods async match(payload: MatchRequest, options?: RequestOptions): Promise { const raw = await this.request("POST", "/v1/match", { ...options, body: JSON.stringify({ job_id: payload.jobId, requirements: payload.requirements ?? {}, hints: payload.hints ?? {}, top_k: payload.topK ?? 1, }), }); return { jobId: raw.job_id, candidates: (raw.candidates ?? []).map((candidate: any) => ({ minerId: candidate.miner_id, addr: candidate.addr, proto: candidate.proto, score: candidate.score, explain: candidate.explain, etaMs: candidate.eta_ms, price: candidate.price, })), }; } async health(options?: RequestOptions): Promise { const raw = await this.request("GET", "/v1/health", options); return { status: raw.status, db: raw.db, redis: raw.redis, minersOnline: raw.miners_online, dbError: raw.db_error ?? null, redisError: raw.redis_error ?? null, }; } async metrics(options?: RequestOptions): Promise { const response = await this.rawRequest("GET", "/metrics", options); const raw = await response.text(); return { raw }; } async sign(request: WalletSignRequest, options?: RequestOptions): Promise { return this.request("POST", `/v1/wallets/${encodeURIComponent(request.walletId)}/sign`, { ...options, body: JSON.stringify({ password: request.password, message_base64: request.messageBase64, }), }); } // Job Management Methods async submitJob(job: JobSubmission, options?: RequestOptions): Promise { return this.request("POST", "/v1/jobs", { ...options, body: JSON.stringify(job), }); } async getJob(jobId: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/jobs/${jobId}`, options); } async getJobStatus(jobId: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/jobs/${jobId}/status`, options); } async getJobResult(jobId: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/jobs/${jobId}/result`, options); } async cancelJob(jobId: string, options?: RequestOptions): Promise { await this.request("DELETE", `/v1/jobs/${jobId}`, options); } async listJobs(options?: RequestOptions): Promise<{ items: Job[]; next_offset?: string }> { return this.request<{ items: Job[]; next_offset?: string }>("GET", "/v1/jobs", options); } // Receipt Methods async getJobReceipts(jobId: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/jobs/${jobId}/receipts`, options); } async verifyReceipt(receipt: ReceiptSummary, options?: RequestOptions): Promise<{ valid: boolean }> { return this.request<{ valid: boolean }>("POST", "/v1/receipts/verify", { ...options, body: JSON.stringify(receipt), }); } // Blockchain Explorer Methods async getBlocks(options?: RequestOptions): Promise { return this.request("GET", "/v1/explorer/blocks", options); } async getBlock(height: string | number, options?: RequestOptions): Promise { return this.request("GET", `/v1/explorer/blocks/${height}`, options); } async getTransactions(options?: RequestOptions): Promise { return this.request("GET", "/v1/explorer/transactions", options); } async getTransaction(hash: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/explorer/transactions/${hash}`, options); } async getAddresses(options?: RequestOptions): Promise { return this.request("GET", "/v1/explorer/addresses", options); } async getAddress(address: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/explorer/addresses/${address}`, options); } async getReceipts(options?: RequestOptions): Promise { return this.request("GET", "/v1/explorer/receipts", options); } // Marketplace Methods async getMarketplaceStats(options?: RequestOptions): Promise { return this.request("GET", "/v1/marketplace/stats", options); } async getMarketplaceOffers(options?: RequestOptions): Promise { return this.request("GET", "/v1/marketplace/offers", options); } async getMarketplaceOffer(offerId: string, options?: RequestOptions): Promise { return this.request("GET", `/v1/marketplace/offers/${offerId}`, options); } async submitMarketplaceBid(bid: MarketplaceBid, options?: RequestOptions): Promise { await this.request("POST", "/v1/marketplace/bids", { ...options, body: JSON.stringify(bid), }); } // Authentication Methods async login(credentials: { username: string; password: string }, options?: RequestOptions): Promise { return this.request("POST", "/v1/users/login", { ...options, body: JSON.stringify(credentials), }); } async logout(options?: RequestOptions): Promise { await this.request("POST", "/v1/users/logout", options); } async request(method: string, path: string, options: RequestOptions = {}): Promise { const response = await this.rawRequest(method, path, options); const text = await response.text(); if (!response.ok) { throw new Error(`AITBC request failed (${response.status}): ${text || response.statusText}`); } return text ? (JSON.parse(text) as T) : ({} as T); } async rawRequest(method: string, path: string, options: RequestOptions = {}): Promise { const url = this.buildUrl(path, options.query); const headers = this.buildHeaders(options.headers); const controller = new AbortController(); const timeoutId = this.timeout ? setTimeout(() => controller.abort(), this.timeout) : undefined; try { return await this.fetchImpl(url, { method, signal: controller.signal, ...options, headers, }); } finally { if (timeoutId) { clearTimeout(timeoutId); } } } private buildUrl(path: string, query?: RequestOptions["query"]): string { const url = new URL(`${this.baseUrl}${path}`); if (query) { for (const [key, value] of Object.entries(query)) { if (value !== undefined) { url.searchParams.set(key, String(value)); } } } return url.toString(); } private buildHeaders(extra?: HeadersInit): HeadersInit { const headers: Record = { ...DEFAULT_HEADERS }; if (this.apiKey) { headers["X-Api-Key"] = this.apiKey; } if (this.basicAuth) { const token = btoa(`${this.basicAuth.username}:${this.basicAuth.password}`); headers["Authorization"] = `Basic ${token}`; } if (extra) { if (extra instanceof Headers) { extra.forEach((value, key) => { headers[key] = value; }); } else if (Array.isArray(extra)) { for (const [key, value] of extra) { headers[key] = value; } } else { Object.assign(headers, extra as Record); } } return headers; } }