Files
aitbc/packages/js/aitbc-sdk/src/client.ts
AITBC System b033923756 chore: normalize file permissions across repository
- Remove executable permissions from configuration files (.editorconfig, .env.example, .gitignore)
- Remove executable permissions from documentation files (README.md, LICENSE, SECURITY.md)
- Remove executable permissions from web assets (HTML, CSS, JS files)
- Remove executable permissions from data files (JSON, SQL, YAML, requirements.txt)
- Remove executable permissions from source code files across all apps
- Add executable permissions to Python
2026-03-08 11:26:18 +01:00

269 lines
8.7 KiB
TypeScript

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<MatchResponse> {
const raw = await this.request<any>("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<HealthResponse> {
const raw = await this.request<any>("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<MetricsResponse> {
const response = await this.rawRequest("GET", "/metrics", options);
const raw = await response.text();
return { raw };
}
async sign(request: WalletSignRequest, options?: RequestOptions): Promise<WalletSignResponse> {
return this.request<WalletSignResponse>("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<Job> {
return this.request<Job>("POST", "/v1/jobs", {
...options,
body: JSON.stringify(job),
});
}
async getJob(jobId: string, options?: RequestOptions): Promise<Job> {
return this.request<Job>("GET", `/v1/jobs/${jobId}`, options);
}
async getJobStatus(jobId: string, options?: RequestOptions): Promise<JobStatus> {
return this.request<JobStatus>("GET", `/v1/jobs/${jobId}/status`, options);
}
async getJobResult(jobId: string, options?: RequestOptions): Promise<JobResult> {
return this.request<JobResult>("GET", `/v1/jobs/${jobId}/result`, options);
}
async cancelJob(jobId: string, options?: RequestOptions): Promise<void> {
await this.request<void>("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<ReceiptListResponse> {
return this.request<ReceiptListResponse>("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<BlockListResponse> {
return this.request<BlockListResponse>("GET", "/v1/explorer/blocks", options);
}
async getBlock(height: string | number, options?: RequestOptions): Promise<BlockSummary> {
return this.request<BlockSummary>("GET", `/v1/explorer/blocks/${height}`, options);
}
async getTransactions(options?: RequestOptions): Promise<TransactionListResponse> {
return this.request<TransactionListResponse>("GET", "/v1/explorer/transactions", options);
}
async getTransaction(hash: string, options?: RequestOptions): Promise<TransactionSummary> {
return this.request<TransactionSummary>("GET", `/v1/explorer/transactions/${hash}`, options);
}
async getAddresses(options?: RequestOptions): Promise<AddressListResponse> {
return this.request<AddressListResponse>("GET", "/v1/explorer/addresses", options);
}
async getAddress(address: string, options?: RequestOptions): Promise<AddressSummary> {
return this.request<AddressSummary>("GET", `/v1/explorer/addresses/${address}`, options);
}
async getReceipts(options?: RequestOptions): Promise<ReceiptListResponse> {
return this.request<ReceiptListResponse>("GET", "/v1/explorer/receipts", options);
}
// Marketplace Methods
async getMarketplaceStats(options?: RequestOptions): Promise<MarketplaceStats> {
return this.request<MarketplaceStats>("GET", "/v1/marketplace/stats", options);
}
async getMarketplaceOffers(options?: RequestOptions): Promise<MarketplaceOffer[]> {
return this.request<MarketplaceOffer[]>("GET", "/v1/marketplace/offers", options);
}
async getMarketplaceOffer(offerId: string, options?: RequestOptions): Promise<MarketplaceOffer> {
return this.request<MarketplaceOffer>("GET", `/v1/marketplace/offers/${offerId}`, options);
}
async submitMarketplaceBid(bid: MarketplaceBid, options?: RequestOptions): Promise<void> {
await this.request<void>("POST", "/v1/marketplace/bids", {
...options,
body: JSON.stringify(bid),
});
}
// Authentication Methods
async login(credentials: { username: string; password: string }, options?: RequestOptions): Promise<MarketplaceSession> {
return this.request<MarketplaceSession>("POST", "/v1/users/login", {
...options,
body: JSON.stringify(credentials),
});
}
async logout(options?: RequestOptions): Promise<void> {
await this.request<void>("POST", "/v1/users/logout", options);
}
async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {
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<Response> {
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<string, string> = { ...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<string, string>);
}
}
return headers;
}
}