diff --git a/packages/js/aitbc-sdk/src/client.ts b/packages/js/aitbc-sdk/src/client.ts index ca2a96da..88a42263 100644 --- a/packages/js/aitbc-sdk/src/client.ts +++ b/packages/js/aitbc-sdk/src/client.ts @@ -46,7 +46,10 @@ export class AitbcClient { } // Coordinator API Methods - async match(payload: MatchRequest, options?: RequestOptions): Promise { + async match( + payload: MatchRequest, + options?: RequestOptions, + ): Promise { const raw = await this.request("POST", "/v1/match", { ...options, body: JSON.stringify({ @@ -88,14 +91,21 @@ export class AitbcClient { 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, - }), - }); + 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 @@ -110,11 +120,17 @@ export class AitbcClient { return this.request("GET", `/v1/jobs/${jobId}`, options); } - async getJobStatus(jobId: string, options?: RequestOptions): Promise { + async getJobStatus( + jobId: string, + options?: RequestOptions, + ): Promise { return this.request("GET", `/v1/jobs/${jobId}/status`, options); } - async getJobResult(jobId: string, options?: RequestOptions): Promise { + async getJobResult( + jobId: string, + options?: RequestOptions, + ): Promise { return this.request("GET", `/v1/jobs/${jobId}/result`, options); } @@ -122,16 +138,32 @@ export class AitbcClient { 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); + 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 getJobReceipts( + jobId: string, + options?: RequestOptions, + ): Promise { + return this.request( + "GET", + `/v1/jobs/${jobId}/receipts`, + options, + ); } - async verifyReceipt(receipt: ReceiptSummary, options?: RequestOptions): Promise<{ valid: boolean }> { + async verifyReceipt( + receipt: ReceiptSummary, + options?: RequestOptions, + ): Promise<{ valid: boolean }> { return this.request<{ valid: boolean }>("POST", "/v1/receipts/verify", { ...options, body: JSON.stringify(receipt), @@ -140,47 +172,108 @@ export class AitbcClient { // Blockchain Explorer Methods async getBlocks(options?: RequestOptions): Promise { - return this.request("GET", "/v1/explorer/blocks", options); + 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 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 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 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); + 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 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); + return this.request( + "GET", + "/v1/explorer/receipts", + options, + ); } // Marketplace Methods - async getMarketplaceStats(options?: RequestOptions): Promise { - return this.request("GET", "/v1/marketplace/stats", options); + 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 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 getMarketplaceOffer( + offerId: string, + options?: RequestOptions, + ): Promise { + return this.request( + "GET", + `/v1/marketplace/offers/${offerId}`, + options, + ); } - async submitMarketplaceBid(bid: MarketplaceBid, options?: RequestOptions): Promise { + async submitMarketplaceBid( + bid: MarketplaceBid, + options?: RequestOptions, + ): Promise { await this.request("POST", "/v1/marketplace/bids", { ...options, body: JSON.stringify(bid), @@ -188,7 +281,10 @@ export class AitbcClient { } // Authentication Methods - async login(credentials: { username: string; password: string }, options?: RequestOptions): Promise { + async login( + credentials: { username: string; password: string }, + options?: RequestOptions, + ): Promise { return this.request("POST", "/v1/users/login", { ...options, body: JSON.stringify(credentials), @@ -199,21 +295,33 @@ export class AitbcClient { await this.request("POST", "/v1/users/logout", options); } - async request(method: string, path: string, options: RequestOptions = {}): Promise { + 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}`); + 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 { + 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; + const timeoutId = this.timeout + ? setTimeout(() => controller.abort(), this.timeout) + : undefined; try { return await this.fetchImpl(url, { @@ -247,7 +355,9 @@ export class AitbcClient { headers["X-Api-Key"] = this.apiKey; } if (this.basicAuth) { - const token = btoa(`${this.basicAuth.username}:${this.basicAuth.password}`); + const token = btoa( + `${this.basicAuth.username}:${this.basicAuth.password}`, + ); headers["Authorization"] = `Basic ${token}`; } if (extra) { diff --git a/packages/js/aitbc-sdk/src/receipts.test.ts b/packages/js/aitbc-sdk/src/receipts.test.ts index 84787026..0e06a3df 100644 --- a/packages/js/aitbc-sdk/src/receipts.test.ts +++ b/packages/js/aitbc-sdk/src/receipts.test.ts @@ -11,7 +11,9 @@ class ThrowingClient { describe("ReceiptService signature verification", () => { const { publicKey, privateKey } = generateKeyPairSync("ed25519"); - const publicKeyPem = publicKey.export({ type: "spki", format: "pem" }).toString(); + const publicKeyPem = publicKey + .export({ type: "spki", format: "pem" }) + .toString(); const baseReceipt: ReceiptSummary = { receiptId: "r1", diff --git a/packages/js/aitbc-sdk/src/receipts.ts b/packages/js/aitbc-sdk/src/receipts.ts index 1caf1dbf..f8b4bbd7 100644 --- a/packages/js/aitbc-sdk/src/receipts.ts +++ b/packages/js/aitbc-sdk/src/receipts.ts @@ -1,5 +1,9 @@ import { verify as verifySignature, createPublicKey } from "crypto"; -import type { ReceiptListResponse, ReceiptSummary, RequestOptions } from "./types"; +import type { + ReceiptListResponse, + ReceiptSummary, + RequestOptions, +} from "./types"; import { AitbcClient } from "./client"; export interface PaginatedReceipts { @@ -57,19 +61,19 @@ export class ReceiptService { while (attempt <= this.maxRetries) { try { - const data = await this.client.request( - "GET", - `/v1/jobs/${jobId}/receipts`, - { - ...options, - query: { - cursor, - limit, - }, - } - ); + const data = await this.client.request< + ReceiptListResponse & { next_cursor?: string } + >("GET", `/v1/jobs/${jobId}/receipts`, { + ...options, + query: { + cursor, + limit, + }, + }); return { - items: (data.items ?? []).filter((r) => !r.jobId || r.jobId === jobId), + items: (data.items ?? []).filter( + (r) => !r.jobId || r.jobId === jobId, + ), nextCursor: data.next_cursor ?? (data as any).nextCursor ?? null, }; } catch (err) { @@ -83,7 +87,10 @@ export class ReceiptService { throw lastError ?? new Error("Failed to fetch receipts"); } - async validateReceipt(receipt: ReceiptSummary, options?: ReceiptValidationOptions): Promise { + async validateReceipt( + receipt: ReceiptSummary, + options?: ReceiptValidationOptions, + ): Promise { // Placeholder for full cryptographic verification: delegate to coordinator API const { minerVerifier, @@ -94,14 +101,13 @@ export class ReceiptService { ...requestOptions } = options ?? {}; try { - const data = await this.client.request<{ valid: boolean; reason?: string }>( - "POST", - "/v1/receipts/verify", - { - ...requestOptions, - body: JSON.stringify(this.buildVerificationPayload(receipt)), - } - ); + const data = await this.client.request<{ + valid: boolean; + reason?: string; + }>("POST", "/v1/receipts/verify", { + ...requestOptions, + body: JSON.stringify(this.buildVerificationPayload(receipt)), + }); return { valid: !!data.valid, reason: data.reason }; } catch (err) { // Fallback to local checks if API unavailable @@ -111,9 +117,11 @@ export class ReceiptService { coordinatorVerifier, minerPublicKeyPem ?? this.minerPublicKeyPem, coordinatorPublicKeyPem ?? this.coordinatorPublicKeyPem, - signatureAlgorithm ?? this.signatureAlgorithm ?? "ed25519" + signatureAlgorithm ?? this.signatureAlgorithm ?? "ed25519", ); - return local.valid ? local : { valid: false, reason: (err as Error).message }; + return local.valid + ? local + : { valid: false, reason: (err as Error).message }; } } @@ -132,7 +140,9 @@ export class ReceiptService { async validateLocally( receipt: ReceiptSummary, minerVerifier?: (receipt: ReceiptSummary) => Promise | boolean, - coordinatorVerifier?: (receipt: ReceiptSummary) => Promise | boolean, + coordinatorVerifier?: ( + receipt: ReceiptSummary, + ) => Promise | boolean, minerPublicKeyPem?: string, coordinatorPublicKeyPem?: string, signatureAlgorithm: string = "ed25519", @@ -158,22 +168,35 @@ export class ReceiptService { const ok = await minerVerifier(receipt); if (!ok) return { valid: false, reason: "miner signature invalid" }; } else if (minerPublicKeyPem) { - const ok = this.verifyWithCrypto(minerSig, minerPublicKeyPem, payloadForSig, signatureAlgorithm); + const ok = this.verifyWithCrypto( + minerSig, + minerPublicKeyPem, + payloadForSig, + signatureAlgorithm, + ); if (!ok) return { valid: false, reason: "miner signature invalid" }; } if (coordinatorVerifier) { const ok = await coordinatorVerifier(receipt); if (!ok) return { valid: false, reason: "coordinator signature invalid" }; } else if (coordinatorPublicKeyPem) { - const ok = this.verifyWithCrypto(coordinatorSig, coordinatorPublicKeyPem, payloadForSig, signatureAlgorithm); + const ok = this.verifyWithCrypto( + coordinatorSig, + coordinatorPublicKeyPem, + payloadForSig, + signatureAlgorithm, + ); if (!ok) return { valid: false, reason: "coordinator signature invalid" }; } return { valid: true }; } - private sanitizePayload(payload: Record): Record { + private sanitizePayload( + payload: Record, + ): Record { // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { signature, minerSignature, coordinatorSignature, ...rest } = payload ?? {}; + const { signature, minerSignature, coordinatorSignature, ...rest } = + payload ?? {}; return rest; } @@ -192,7 +215,12 @@ export class ReceiptService { return verifySignature(null, message, key, sig); } if (alg.toLowerCase() === "secp256k1") { - return verifySignature("sha256", message, { key, dsaEncoding: "ieee-p1363" }, sig); + return verifySignature( + "sha256", + message, + { key, dsaEncoding: "ieee-p1363" }, + sig, + ); } if (alg.toLowerCase().startsWith("rsa")) { return verifySignature("sha256", message, key, sig);