style: apply Prettier formatting
Some checks failed
JavaScript SDK Tests / test-js-sdk (push) Successful in 16s
package-tests / test-python-packages (map[name:aitbc-agent-sdk path:packages/py/aitbc-agent-sdk python_version:3.13]) (push) Failing after 9s
package-tests / test-python-packages (map[name:aitbc-core path:packages/py/aitbc-core python_version:3.13]) (push) Failing after 9s
package-tests / test-python-packages (map[name:aitbc-crypto path:packages/py/aitbc-crypto python_version:3.13]) (push) Successful in 8s
package-tests / test-python-packages (map[name:aitbc-sdk path:packages/py/aitbc-sdk python_version:3.13]) (push) Successful in 13s
package-tests / test-javascript-packages (map[name:aitbc-sdk node_version:24 path:packages/js/aitbc-sdk]) (push) Successful in 10s
package-tests / cross-language-compatibility (push) Has been skipped
package-tests / package-integration-tests (push) Has been skipped
security-scanning / audit (push) Successful in 1m22s
integration-tests / test-service-integration (push) Successful in 3m22s

This commit is contained in:
2026-03-28 08:47:41 +01:00
parent b35aa25243
commit 074e5fbfad
3 changed files with 211 additions and 71 deletions

View File

@@ -46,7 +46,10 @@ export class AitbcClient {
} }
// Coordinator API Methods // Coordinator API Methods
async match(payload: MatchRequest, options?: RequestOptions): Promise<MatchResponse> { async match(
payload: MatchRequest,
options?: RequestOptions,
): Promise<MatchResponse> {
const raw = await this.request<any>("POST", "/v1/match", { const raw = await this.request<any>("POST", "/v1/match", {
...options, ...options,
body: JSON.stringify({ body: JSON.stringify({
@@ -88,14 +91,21 @@ export class AitbcClient {
return { raw }; return { raw };
} }
async sign(request: WalletSignRequest, options?: RequestOptions): Promise<WalletSignResponse> { async sign(
return this.request<WalletSignResponse>("POST", `/v1/wallets/${encodeURIComponent(request.walletId)}/sign`, { request: WalletSignRequest,
...options, options?: RequestOptions,
body: JSON.stringify({ ): Promise<WalletSignResponse> {
password: request.password, return this.request<WalletSignResponse>(
message_base64: request.messageBase64, "POST",
}), `/v1/wallets/${encodeURIComponent(request.walletId)}/sign`,
}); {
...options,
body: JSON.stringify({
password: request.password,
message_base64: request.messageBase64,
}),
},
);
} }
// Job Management Methods // Job Management Methods
@@ -110,11 +120,17 @@ export class AitbcClient {
return this.request<Job>("GET", `/v1/jobs/${jobId}`, options); return this.request<Job>("GET", `/v1/jobs/${jobId}`, options);
} }
async getJobStatus(jobId: string, options?: RequestOptions): Promise<JobStatus> { async getJobStatus(
jobId: string,
options?: RequestOptions,
): Promise<JobStatus> {
return this.request<JobStatus>("GET", `/v1/jobs/${jobId}/status`, options); return this.request<JobStatus>("GET", `/v1/jobs/${jobId}/status`, options);
} }
async getJobResult(jobId: string, options?: RequestOptions): Promise<JobResult> { async getJobResult(
jobId: string,
options?: RequestOptions,
): Promise<JobResult> {
return this.request<JobResult>("GET", `/v1/jobs/${jobId}/result`, options); return this.request<JobResult>("GET", `/v1/jobs/${jobId}/result`, options);
} }
@@ -122,16 +138,32 @@ export class AitbcClient {
await this.request<void>("DELETE", `/v1/jobs/${jobId}`, options); await this.request<void>("DELETE", `/v1/jobs/${jobId}`, options);
} }
async listJobs(options?: RequestOptions): Promise<{ items: Job[]; next_offset?: string }> { async listJobs(
return this.request<{ items: Job[]; next_offset?: string }>("GET", "/v1/jobs", options); options?: RequestOptions,
): Promise<{ items: Job[]; next_offset?: string }> {
return this.request<{ items: Job[]; next_offset?: string }>(
"GET",
"/v1/jobs",
options,
);
} }
// Receipt Methods // Receipt Methods
async getJobReceipts(jobId: string, options?: RequestOptions): Promise<ReceiptListResponse> { async getJobReceipts(
return this.request<ReceiptListResponse>("GET", `/v1/jobs/${jobId}/receipts`, options); 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 }> { async verifyReceipt(
receipt: ReceiptSummary,
options?: RequestOptions,
): Promise<{ valid: boolean }> {
return this.request<{ valid: boolean }>("POST", "/v1/receipts/verify", { return this.request<{ valid: boolean }>("POST", "/v1/receipts/verify", {
...options, ...options,
body: JSON.stringify(receipt), body: JSON.stringify(receipt),
@@ -140,47 +172,108 @@ export class AitbcClient {
// Blockchain Explorer Methods // Blockchain Explorer Methods
async getBlocks(options?: RequestOptions): Promise<BlockListResponse> { async getBlocks(options?: RequestOptions): Promise<BlockListResponse> {
return this.request<BlockListResponse>("GET", "/v1/explorer/blocks", options); return this.request<BlockListResponse>(
"GET",
"/v1/explorer/blocks",
options,
);
} }
async getBlock(height: string | number, options?: RequestOptions): Promise<BlockSummary> { async getBlock(
return this.request<BlockSummary>("GET", `/v1/explorer/blocks/${height}`, options); height: string | number,
options?: RequestOptions,
): Promise<BlockSummary> {
return this.request<BlockSummary>(
"GET",
`/v1/explorer/blocks/${height}`,
options,
);
} }
async getTransactions(options?: RequestOptions): Promise<TransactionListResponse> { async getTransactions(
return this.request<TransactionListResponse>("GET", "/v1/explorer/transactions", options); options?: RequestOptions,
): Promise<TransactionListResponse> {
return this.request<TransactionListResponse>(
"GET",
"/v1/explorer/transactions",
options,
);
} }
async getTransaction(hash: string, options?: RequestOptions): Promise<TransactionSummary> { async getTransaction(
return this.request<TransactionSummary>("GET", `/v1/explorer/transactions/${hash}`, options); hash: string,
options?: RequestOptions,
): Promise<TransactionSummary> {
return this.request<TransactionSummary>(
"GET",
`/v1/explorer/transactions/${hash}`,
options,
);
} }
async getAddresses(options?: RequestOptions): Promise<AddressListResponse> { async getAddresses(options?: RequestOptions): Promise<AddressListResponse> {
return this.request<AddressListResponse>("GET", "/v1/explorer/addresses", options); return this.request<AddressListResponse>(
"GET",
"/v1/explorer/addresses",
options,
);
} }
async getAddress(address: string, options?: RequestOptions): Promise<AddressSummary> { async getAddress(
return this.request<AddressSummary>("GET", `/v1/explorer/addresses/${address}`, options); address: string,
options?: RequestOptions,
): Promise<AddressSummary> {
return this.request<AddressSummary>(
"GET",
`/v1/explorer/addresses/${address}`,
options,
);
} }
async getReceipts(options?: RequestOptions): Promise<ReceiptListResponse> { async getReceipts(options?: RequestOptions): Promise<ReceiptListResponse> {
return this.request<ReceiptListResponse>("GET", "/v1/explorer/receipts", options); return this.request<ReceiptListResponse>(
"GET",
"/v1/explorer/receipts",
options,
);
} }
// Marketplace Methods // Marketplace Methods
async getMarketplaceStats(options?: RequestOptions): Promise<MarketplaceStats> { async getMarketplaceStats(
return this.request<MarketplaceStats>("GET", "/v1/marketplace/stats", options); options?: RequestOptions,
): Promise<MarketplaceStats> {
return this.request<MarketplaceStats>(
"GET",
"/v1/marketplace/stats",
options,
);
} }
async getMarketplaceOffers(options?: RequestOptions): Promise<MarketplaceOffer[]> { async getMarketplaceOffers(
return this.request<MarketplaceOffer[]>("GET", "/v1/marketplace/offers", options); options?: RequestOptions,
): Promise<MarketplaceOffer[]> {
return this.request<MarketplaceOffer[]>(
"GET",
"/v1/marketplace/offers",
options,
);
} }
async getMarketplaceOffer(offerId: string, options?: RequestOptions): Promise<MarketplaceOffer> { async getMarketplaceOffer(
return this.request<MarketplaceOffer>("GET", `/v1/marketplace/offers/${offerId}`, options); offerId: string,
options?: RequestOptions,
): Promise<MarketplaceOffer> {
return this.request<MarketplaceOffer>(
"GET",
`/v1/marketplace/offers/${offerId}`,
options,
);
} }
async submitMarketplaceBid(bid: MarketplaceBid, options?: RequestOptions): Promise<void> { async submitMarketplaceBid(
bid: MarketplaceBid,
options?: RequestOptions,
): Promise<void> {
await this.request<void>("POST", "/v1/marketplace/bids", { await this.request<void>("POST", "/v1/marketplace/bids", {
...options, ...options,
body: JSON.stringify(bid), body: JSON.stringify(bid),
@@ -188,7 +281,10 @@ export class AitbcClient {
} }
// Authentication Methods // Authentication Methods
async login(credentials: { username: string; password: string }, options?: RequestOptions): Promise<MarketplaceSession> { async login(
credentials: { username: string; password: string },
options?: RequestOptions,
): Promise<MarketplaceSession> {
return this.request<MarketplaceSession>("POST", "/v1/users/login", { return this.request<MarketplaceSession>("POST", "/v1/users/login", {
...options, ...options,
body: JSON.stringify(credentials), body: JSON.stringify(credentials),
@@ -199,21 +295,33 @@ export class AitbcClient {
await this.request<void>("POST", "/v1/users/logout", options); await this.request<void>("POST", "/v1/users/logout", options);
} }
async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> { async request<T>(
method: string,
path: string,
options: RequestOptions = {},
): Promise<T> {
const response = await this.rawRequest(method, path, options); const response = await this.rawRequest(method, path, options);
const text = await response.text(); const text = await response.text();
if (!response.ok) { 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); return text ? (JSON.parse(text) as T) : ({} as T);
} }
async rawRequest(method: string, path: string, options: RequestOptions = {}): Promise<Response> { async rawRequest(
method: string,
path: string,
options: RequestOptions = {},
): Promise<Response> {
const url = this.buildUrl(path, options.query); const url = this.buildUrl(path, options.query);
const headers = this.buildHeaders(options.headers); const headers = this.buildHeaders(options.headers);
const controller = new AbortController(); 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 { try {
return await this.fetchImpl(url, { return await this.fetchImpl(url, {
@@ -247,7 +355,9 @@ export class AitbcClient {
headers["X-Api-Key"] = this.apiKey; headers["X-Api-Key"] = this.apiKey;
} }
if (this.basicAuth) { 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}`; headers["Authorization"] = `Basic ${token}`;
} }
if (extra) { if (extra) {

View File

@@ -11,7 +11,9 @@ class ThrowingClient {
describe("ReceiptService signature verification", () => { describe("ReceiptService signature verification", () => {
const { publicKey, privateKey } = generateKeyPairSync("ed25519"); 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 = { const baseReceipt: ReceiptSummary = {
receiptId: "r1", receiptId: "r1",

View File

@@ -1,5 +1,9 @@
import { verify as verifySignature, createPublicKey } from "crypto"; 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"; import { AitbcClient } from "./client";
export interface PaginatedReceipts { export interface PaginatedReceipts {
@@ -57,19 +61,19 @@ export class ReceiptService {
while (attempt <= this.maxRetries) { while (attempt <= this.maxRetries) {
try { try {
const data = await this.client.request<ReceiptListResponse & { next_cursor?: string }>( const data = await this.client.request<
"GET", ReceiptListResponse & { next_cursor?: string }
`/v1/jobs/${jobId}/receipts`, >("GET", `/v1/jobs/${jobId}/receipts`, {
{ ...options,
...options, query: {
query: { cursor,
cursor, limit,
limit, },
}, });
}
);
return { 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, nextCursor: data.next_cursor ?? (data as any).nextCursor ?? null,
}; };
} catch (err) { } catch (err) {
@@ -83,7 +87,10 @@ export class ReceiptService {
throw lastError ?? new Error("Failed to fetch receipts"); throw lastError ?? new Error("Failed to fetch receipts");
} }
async validateReceipt(receipt: ReceiptSummary, options?: ReceiptValidationOptions): Promise<ReceiptValidationResult> { async validateReceipt(
receipt: ReceiptSummary,
options?: ReceiptValidationOptions,
): Promise<ReceiptValidationResult> {
// Placeholder for full cryptographic verification: delegate to coordinator API // Placeholder for full cryptographic verification: delegate to coordinator API
const { const {
minerVerifier, minerVerifier,
@@ -94,14 +101,13 @@ export class ReceiptService {
...requestOptions ...requestOptions
} = options ?? {}; } = options ?? {};
try { try {
const data = await this.client.request<{ valid: boolean; reason?: string }>( const data = await this.client.request<{
"POST", valid: boolean;
"/v1/receipts/verify", reason?: string;
{ }>("POST", "/v1/receipts/verify", {
...requestOptions, ...requestOptions,
body: JSON.stringify(this.buildVerificationPayload(receipt)), body: JSON.stringify(this.buildVerificationPayload(receipt)),
} });
);
return { valid: !!data.valid, reason: data.reason }; return { valid: !!data.valid, reason: data.reason };
} catch (err) { } catch (err) {
// Fallback to local checks if API unavailable // Fallback to local checks if API unavailable
@@ -111,9 +117,11 @@ export class ReceiptService {
coordinatorVerifier, coordinatorVerifier,
minerPublicKeyPem ?? this.minerPublicKeyPem, minerPublicKeyPem ?? this.minerPublicKeyPem,
coordinatorPublicKeyPem ?? this.coordinatorPublicKeyPem, 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( async validateLocally(
receipt: ReceiptSummary, receipt: ReceiptSummary,
minerVerifier?: (receipt: ReceiptSummary) => Promise<boolean> | boolean, minerVerifier?: (receipt: ReceiptSummary) => Promise<boolean> | boolean,
coordinatorVerifier?: (receipt: ReceiptSummary) => Promise<boolean> | boolean, coordinatorVerifier?: (
receipt: ReceiptSummary,
) => Promise<boolean> | boolean,
minerPublicKeyPem?: string, minerPublicKeyPem?: string,
coordinatorPublicKeyPem?: string, coordinatorPublicKeyPem?: string,
signatureAlgorithm: string = "ed25519", signatureAlgorithm: string = "ed25519",
@@ -158,22 +168,35 @@ export class ReceiptService {
const ok = await minerVerifier(receipt); const ok = await minerVerifier(receipt);
if (!ok) return { valid: false, reason: "miner signature invalid" }; if (!ok) return { valid: false, reason: "miner signature invalid" };
} else if (minerPublicKeyPem) { } 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 (!ok) return { valid: false, reason: "miner signature invalid" };
} }
if (coordinatorVerifier) { if (coordinatorVerifier) {
const ok = await coordinatorVerifier(receipt); const ok = await coordinatorVerifier(receipt);
if (!ok) return { valid: false, reason: "coordinator signature invalid" }; if (!ok) return { valid: false, reason: "coordinator signature invalid" };
} else if (coordinatorPublicKeyPem) { } 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" }; if (!ok) return { valid: false, reason: "coordinator signature invalid" };
} }
return { valid: true }; return { valid: true };
} }
private sanitizePayload(payload: Record<string, unknown>): Record<string, unknown> { private sanitizePayload(
payload: Record<string, unknown>,
): Record<string, unknown> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const { signature, minerSignature, coordinatorSignature, ...rest } = payload ?? {}; const { signature, minerSignature, coordinatorSignature, ...rest } =
payload ?? {};
return rest; return rest;
} }
@@ -192,7 +215,12 @@ export class ReceiptService {
return verifySignature(null, message, key, sig); return verifySignature(null, message, key, sig);
} }
if (alg.toLowerCase() === "secp256k1") { 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")) { if (alg.toLowerCase().startsWith("rsa")) {
return verifySignature("sha256", message, key, sig); return verifySignature("sha256", message, key, sig);