@gibwork/sdk 0.0.6 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -18,8 +18,9 @@ declare class GibworkApiError extends GibworkError {
18
18
  readonly method: string;
19
19
  readonly path: string;
20
20
  readonly requestId?: string | undefined;
21
+ readonly retryAfter?: string | undefined;
21
22
  readonly name: string;
22
- constructor(status: number, body: unknown, method: string, path: string, requestId?: string | undefined);
23
+ constructor(status: number, body: unknown, method: string, path: string, requestId?: string | undefined, retryAfter?: string | undefined);
23
24
  }
24
25
  declare class GibworkNetworkError extends GibworkError {
25
26
  readonly method: string;
@@ -36,18 +37,46 @@ declare class GibworkRequestAbortedError extends GibworkNetworkError {
36
37
  readonly name: string;
37
38
  constructor(method: string, path: string, options?: ErrorOptions);
38
39
  }
39
- type SubmitOperation = 'create-task' | 'refund-task' | 'approve-submission';
40
+ type SubmitOperation = 'create-task' | 'refund-task' | 'approve-submission' | 'create-submission';
40
41
  interface AmbiguousSubmitContext {
41
42
  operation: SubmitOperation;
42
43
  taskId?: string;
43
44
  submissionId?: string;
44
45
  intentId: string;
46
+ paymentAttemptId?: string;
47
+ idempotencyKey?: string;
48
+ walletAddress?: string;
49
+ environment?: 'stage' | 'prod';
50
+ apiUrl?: string;
45
51
  }
46
52
  declare class GibworkAmbiguousSubmitError extends GibworkNetworkError {
47
53
  readonly context: AmbiguousSubmitContext;
48
54
  readonly name: string;
49
55
  constructor(method: string, path: string, context: AmbiguousSubmitContext, options?: ErrorOptions);
50
56
  }
57
+ declare class GibworkValidationError extends GibworkError {
58
+ readonly name: string;
59
+ }
60
+ declare class GibworkProtocolError extends GibworkError {
61
+ readonly name: string;
62
+ }
63
+ interface SubmissionRecoveryContext {
64
+ taskId: string;
65
+ idempotencyKey: string;
66
+ walletAddress: string;
67
+ environment: 'stage' | 'prod';
68
+ apiUrl: string;
69
+ phase: 'prepare' | 'sign' | 'submit';
70
+ intentId?: string;
71
+ paymentAttemptId?: string;
72
+ submissionId?: string;
73
+ }
74
+ /** Retains identifiers when creation stops, including before any payment is sent. */
75
+ declare class GibworkSubmissionOperationError extends GibworkError {
76
+ readonly context: SubmissionRecoveryContext;
77
+ readonly name: string;
78
+ constructor(context: SubmissionRecoveryContext, options?: ErrorOptions);
79
+ }
51
80
 
52
81
  interface RequestOptions {
53
82
  signal?: AbortSignal;
@@ -325,6 +354,88 @@ interface GibworkApiErrorBody {
325
354
  error?: string;
326
355
  [key: string]: unknown;
327
356
  }
357
+ interface AvailableTasksQuery {
358
+ page?: number;
359
+ limit?: number;
360
+ }
361
+ interface AvailableTask {
362
+ id: string;
363
+ slug: string;
364
+ title: string;
365
+ content: string;
366
+ requirements: string | null;
367
+ tags: string[];
368
+ primarySkillId: string | null;
369
+ createdAt: string;
370
+ deadline: string | null;
371
+ status: 'CREATED';
372
+ isOpen: boolean;
373
+ asset: AvailableTaskAsset | null;
374
+ minSubmissionAmount: string | null;
375
+ totalSubmissions: number;
376
+ maxSubmissions: number | null;
377
+ standardSubmissionSlotsRemaining: number | null;
378
+ requiresPremium: boolean;
379
+ participationRequirements: AvailableTaskParticipationRequirements;
380
+ }
381
+ type AvailableTaskPage = Paginated<AvailableTask>;
382
+ interface CreateSubmissionInput {
383
+ content: string;
384
+ referral?: string | null;
385
+ mediaIds?: string[] | null;
386
+ /** Reuse for retries of this exact task and submission payload. */
387
+ idempotencyKey: string;
388
+ }
389
+ interface SubmitSubmissionInput {
390
+ paymentAttemptId: string;
391
+ signedTransaction: string;
392
+ }
393
+ type TaskSubmissionIntentStatus = 'pending' | 'submitted' | 'fulfilled' | 'failed' | 'expired' | 'requires_review';
394
+ interface TaskSubmissionIntent {
395
+ intentId: string;
396
+ taskId: string;
397
+ /** Allocated in advance; exists as a submission only when fulfilled. */
398
+ taskSubmissionId: string;
399
+ paymentAttemptId: string;
400
+ status: TaskSubmissionIntentStatus;
401
+ txHash: string | null;
402
+ expiresAt: string;
403
+ lastValidBlockHeight: number;
404
+ fee: TaskSubmissionFee;
405
+ reviewReason?: string;
406
+ /** Only returned by prepare for a pending intent, including pending retries. */
407
+ serializedTransaction?: string;
408
+ }
409
+ interface TaskSubmissionFee {
410
+ amount: string;
411
+ amountBaseUnits: string;
412
+ totalDebit: string;
413
+ symbol: 'USDC';
414
+ mintAddress: string;
415
+ decimals: number;
416
+ destinationAddress: string;
417
+ }
418
+ interface AvailableTaskAsset {
419
+ mintAddress: string;
420
+ symbol: string;
421
+ imageUrl: string;
422
+ decimals: number;
423
+ /** Original reward pool in base units, not the remaining balance. */
424
+ amount: string;
425
+ }
426
+ interface AvailableTaskParticipationRequirements {
427
+ allowOnlyVerifiedSubmissions: boolean;
428
+ allowOnlyVerifiedTwitterAccountSubmissions: boolean;
429
+ minTwitterFollowers: number;
430
+ minTweetLikes: number;
431
+ minTweetViews: number;
432
+ isTwitterTask: boolean;
433
+ allowOnlyDiscordGuildSubmissions: boolean;
434
+ requiredDiscordGuildId: string | null;
435
+ requiredDiscordGuildName: string | null;
436
+ discordGuildInvitationUrl: string | null;
437
+ requiredDiscordRoleIds: string[];
438
+ }
328
439
 
329
440
  type FetchImplementation = typeof fetch;
330
441
  interface HttpTransportOptions {
@@ -340,6 +451,7 @@ interface HttpRequest {
340
451
  body?: unknown;
341
452
  options?: RequestOptions;
342
453
  ambiguousSubmit?: AmbiguousSubmitContext;
454
+ validateResponse?: (body: unknown) => void;
343
455
  }
344
456
  declare class HttpTransport {
345
457
  private readonly baseUrl;
@@ -347,6 +459,11 @@ declare class HttpTransport {
347
459
  private readonly production;
348
460
  private readonly timeoutMs;
349
461
  constructor(options: HttpTransportOptions);
462
+ /** Recovery location without URL credentials. Retains reverse-proxy path prefixes. */
463
+ get recoveryLocation(): {
464
+ apiUrl: string;
465
+ environment: 'stage' | 'prod';
466
+ };
350
467
  request<T>(request: HttpRequest): Promise<T>;
351
468
  }
352
469
 
@@ -363,6 +480,14 @@ declare class SubmissionsResource {
363
480
  private readonly signer;
364
481
  readonly comments: SubmissionCommentsResource;
365
482
  constructor(transport: HttpTransport, signer: WalletSigner);
483
+ /** Creates work through a participation fee; only fulfilled confirms creation. */
484
+ create(taskId: string, input: CreateSubmissionInput, options?: RequestOptions): Promise<TaskSubmissionIntent>;
485
+ /** Reuse the exact input and idempotency key after an interrupted prepare. */
486
+ prepareCreate(taskId: string, input: CreateSubmissionInput, options?: RequestOptions): Promise<TaskSubmissionIntent>;
487
+ /** Never automatically retries. Recover an uncertain payment with getIntent. */
488
+ submitCreate(taskId: string, intentId: string, input: SubmitSubmissionInput, options?: RequestOptions): Promise<TaskSubmissionIntent>;
489
+ /** Reads/reconciles creation using the original wallet; does not report review/payout. */
490
+ getIntent(taskId: string, intentId: string, options?: RequestOptions): Promise<TaskSubmissionIntent>;
366
491
  list(taskId: string, query?: SubmissionPaginationQuery, options?: RequestOptions): Promise<Paginated<TaskSubmission>>;
367
492
  get(taskId: string, submissionId: string, options?: RequestOptions): Promise<WalletTaskSubmissionDetails>;
368
493
  approve(taskId: string, submissionId: string, input: ApproveSubmissionInput, options?: RequestOptions): Promise<ApproveSubmissionResult>;
@@ -378,6 +503,8 @@ declare class TasksResource {
378
503
  constructor(transport: HttpTransport, signer: WalletSigner);
379
504
  create(input: CreateTaskInput, options?: RequestOptions): Promise<CreateTaskResult>;
380
505
  list(query?: PaginationQuery, options?: RequestOptions): Promise<Paginated<WalletTaskSummary>>;
506
+ /** Discover tasks across creators. Eligibility is checked when preparing work. */
507
+ listAvailable(query?: AvailableTasksQuery, options?: RequestOptions): Promise<AvailableTaskPage>;
381
508
  update(taskId: string, input: UpdateTaskInput, options?: RequestOptions): Promise<UpdatedTask>;
382
509
  refund(taskId: string, options?: RequestOptions): Promise<RefundTaskResult>;
383
510
  prepareCreate(input: CreateTaskInput, options?: RequestOptions): Promise<PreparedTaskIntent>;
@@ -407,4 +534,4 @@ declare const DEFAULT_GIBWORK_API_URL: string;
407
534
 
408
535
  declare function signPreparedTransaction(serializedTransaction: string, signer: WalletSigner): Promise<string>;
409
536
 
410
- export { type AmbiguousSubmitContext, type ApprovalQuote, type ApprovalSubmitResult, type ApproveSubmissionInput, type ApproveSubmissionResult, type CreateTaskInput, type CreateTaskResult, DEFAULT_GIBWORK_API_URL, type FeeQuote, type FetchImplementation, GibworkAmbiguousSubmitError, GibworkApiError, type GibworkApiErrorBody, GibworkClient, type GibworkClientOptions, GibworkConfigurationError, GibworkError, GibworkNetworkError, GibworkRequestAbortedError, GibworkTimeoutError, type MediaItem, type Membership, type Paginated, type PaginationQuery, type PaymentQuote, type PreparedApprovalIntent, type PreparedRefundIntent, type PreparedTaskIntent, type PublicProfileReference, type RefundQuote, type RefundSubmitResult, type RefundTaskResult, type RejectSubmissionResult, type RequestOptions, type SubmissionAsset, type SubmissionComment, type SubmissionPaginationQuery, type SubmissionStatus, type SubmitOperation, type TaskSubmission, type TaskSubmissionStatus, type TaskSubmitResult, type TokenQuote, type UpdateTaskInput, type UpdatedTask, type UserSummary, type WalletSigner, type WalletSubmissionComment, type WalletSubmissionUser, type WalletTaskAsset, type WalletTaskSubmissionDetails, type WalletTaskSummary, signPreparedTransaction };
537
+ export { type AmbiguousSubmitContext, type ApprovalQuote, type ApprovalSubmitResult, type ApproveSubmissionInput, type ApproveSubmissionResult, type AvailableTask, type AvailableTaskAsset, type AvailableTaskPage, type AvailableTaskParticipationRequirements, type AvailableTasksQuery, type CreateSubmissionInput, type CreateTaskInput, type CreateTaskResult, DEFAULT_GIBWORK_API_URL, type FeeQuote, type FetchImplementation, GibworkAmbiguousSubmitError, GibworkApiError, type GibworkApiErrorBody, GibworkClient, type GibworkClientOptions, GibworkConfigurationError, GibworkError, GibworkNetworkError, GibworkProtocolError, GibworkRequestAbortedError, GibworkSubmissionOperationError, GibworkTimeoutError, GibworkValidationError, type MediaItem, type Membership, type Paginated, type PaginationQuery, type PaymentQuote, type PreparedApprovalIntent, type PreparedRefundIntent, type PreparedTaskIntent, type PublicProfileReference, type RefundQuote, type RefundSubmitResult, type RefundTaskResult, type RejectSubmissionResult, type RequestOptions, type SubmissionAsset, type SubmissionComment, type SubmissionPaginationQuery, type SubmissionRecoveryContext, type SubmissionStatus, type SubmitOperation, type SubmitSubmissionInput, type TaskSubmission, type TaskSubmissionFee, type TaskSubmissionIntent, type TaskSubmissionIntentStatus, type TaskSubmissionStatus, type TaskSubmitResult, type TokenQuote, type UpdateTaskInput, type UpdatedTask, type UserSummary, type WalletSigner, type WalletSubmissionComment, type WalletSubmissionUser, type WalletTaskAsset, type WalletTaskSubmissionDetails, type WalletTaskSummary, signPreparedTransaction };
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- export { DEFAULT_GIBWORK_API_URL, GibworkAmbiguousSubmitError, GibworkApiError, GibworkClient, GibworkConfigurationError, GibworkError, GibworkNetworkError, GibworkRequestAbortedError, GibworkTimeoutError, signPreparedTransaction } from './chunk-3MLFNEQ5.js';
1
+ export { DEFAULT_GIBWORK_API_URL, GibworkAmbiguousSubmitError, GibworkApiError, GibworkClient, GibworkConfigurationError, GibworkError, GibworkNetworkError, GibworkProtocolError, GibworkRequestAbortedError, GibworkSubmissionOperationError, GibworkTimeoutError, GibworkValidationError, signPreparedTransaction } from './chunk-HJ5D5SOE.js';
2
2
  //# sourceMappingURL=index.js.map
3
3
  //# sourceMappingURL=index.js.map
package/dist/node.cjs CHANGED
@@ -24,19 +24,21 @@ var GibworkConfigurationError = class extends GibworkError {
24
24
  name = "GibworkConfigurationError";
25
25
  };
26
26
  var GibworkApiError = class extends GibworkError {
27
- constructor(status, body, method, path, requestId) {
27
+ constructor(status, body, method, path, requestId, retryAfter) {
28
28
  super(`Gibwork API request failed with HTTP ${status}`);
29
29
  this.status = status;
30
30
  this.body = body;
31
31
  this.method = method;
32
32
  this.path = path;
33
33
  this.requestId = requestId;
34
+ this.retryAfter = retryAfter;
34
35
  }
35
36
  status;
36
37
  body;
37
38
  method;
38
39
  path;
39
40
  requestId;
41
+ retryAfter;
40
42
  name = "GibworkApiError";
41
43
  };
42
44
  var GibworkNetworkError = class extends GibworkError {
@@ -81,6 +83,51 @@ var GibworkAmbiguousSubmitError = class extends GibworkNetworkError {
81
83
  context;
82
84
  name = "GibworkAmbiguousSubmitError";
83
85
  };
86
+ var GibworkValidationError = class extends GibworkError {
87
+ name = "GibworkValidationError";
88
+ };
89
+ var GibworkProtocolError = class extends GibworkError {
90
+ name = "GibworkProtocolError";
91
+ };
92
+ var GibworkSubmissionOperationError = class extends GibworkError {
93
+ constructor(context, options) {
94
+ super(
95
+ "Submission creation interrupted; recover using the original input/key or intent",
96
+ options
97
+ );
98
+ this.context = context;
99
+ }
100
+ context;
101
+ name = "GibworkSubmissionOperationError";
102
+ };
103
+
104
+ // src/auth/validation.ts
105
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
106
+ var UUID_V4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
107
+ function isRecord(value) {
108
+ return typeof value === "object" && value !== null && !Array.isArray(value);
109
+ }
110
+ function assertObjectKeys(value, keys) {
111
+ if (!isRecord(value) || Object.keys(value).some((key) => !keys.includes(key))) {
112
+ throw new GibworkValidationError(
113
+ `Expected an object containing only: ${keys.join(", ")}`
114
+ );
115
+ }
116
+ }
117
+ function isUuid(value, v4 = false) {
118
+ return typeof value === "string" && (v4 ? UUID_V4 : UUID).test(value);
119
+ }
120
+ function assertUuid(value, name, v4 = false) {
121
+ if (!isUuid(value, v4))
122
+ throw new GibworkValidationError(`${name} must be a UUID${v4 ? "v4" : ""}`);
123
+ }
124
+ function assertText(value, name, min, max) {
125
+ if (typeof value !== "string" || Array.from(value).length < min || Array.from(value).length > max) {
126
+ throw new GibworkValidationError(
127
+ `${name} must contain ${min}\u2013${max} characters`
128
+ );
129
+ }
130
+ }
84
131
  var HTML_SANITIZE_OPTIONS = {
85
132
  allowedTags: sanitizeHtml__default.default.defaults.allowedTags.concat(["img"]),
86
133
  allowedAttributes: {
@@ -235,6 +282,42 @@ function trimOptional(value) {
235
282
  const trimmed = value.trim();
236
283
  return trimmed === "" ? void 0 : trimmed;
237
284
  }
285
+ function normalizeAvailableTasksQuery(query = {}) {
286
+ assertObjectKeys(query, ["page", "limit"]);
287
+ const page = query.page === void 0 ? 1 : query.page;
288
+ const limit = query.limit === void 0 ? 15 : query.limit;
289
+ if (!Number.isSafeInteger(page) || page < 1 || !Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger((page - 1) * limit)) {
290
+ throw new GibworkValidationError(
291
+ "page must be a positive safe integer, limit must be 1\u2013100, and the offset must be safe"
292
+ );
293
+ }
294
+ return { page, limit };
295
+ }
296
+ function normalizeCreateSubmission(input, walletAddress) {
297
+ assertObjectKeys(input, [
298
+ "content",
299
+ "referral",
300
+ "mediaIds",
301
+ "idempotencyKey"
302
+ ]);
303
+ assertText(input.content, "content", 1, 5e4);
304
+ if (input.referral != null) assertText(input.referral, "referral", 0, 200);
305
+ assertUuid(input.idempotencyKey, "idempotencyKey", true);
306
+ const mediaIds = input.mediaIds ?? [];
307
+ if (!Array.isArray(mediaIds) || mediaIds.length > 10 || new Set(mediaIds).size !== mediaIds.length) {
308
+ throw new GibworkValidationError(
309
+ "mediaIds must contain at most ten distinct UUIDv4 values"
310
+ );
311
+ }
312
+ for (const id of mediaIds) assertUuid(id, "mediaIds", true);
313
+ return {
314
+ walletAddress,
315
+ content: input.content,
316
+ referral: input.referral ?? null,
317
+ mediaIds: [...mediaIds],
318
+ idempotencyKey: input.idempotencyKey
319
+ };
320
+ }
238
321
  async function createWalletAuthHeaders(signer, descriptor, dependencies = {}) {
239
322
  const values = {
240
323
  walletAddress: signer.publicKey.toBase58(),
@@ -271,6 +354,30 @@ function buildWalletAuthMessage(descriptor, values) {
271
354
  `${descriptor.hash[0]}:${descriptor.hash[1]}`
272
355
  ].join("\n");
273
356
  }
357
+
358
+ // src/resources/submissions/submission-intent.ts
359
+ function submissionIntentPath(taskId, intentId) {
360
+ assertUuid(taskId, "taskId");
361
+ if (intentId !== void 0) assertUuid(intentId, "intentId");
362
+ return `/v2/int/tasks/${taskId}/submission-intents${intentId === void 0 ? "" : `/${intentId}`}`;
363
+ }
364
+ var statuses = [
365
+ "pending",
366
+ "submitted",
367
+ "fulfilled",
368
+ "failed",
369
+ "expired",
370
+ "requires_review"
371
+ ];
372
+ var nonempty = (value) => typeof value === "string" && value.length > 0;
373
+ var decimal = (value) => typeof value === "string" && /^\d+(\.\d+)?$/.test(value);
374
+ var natural = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
375
+ var sameId = (value, expected) => value.toLowerCase() === expected.toLowerCase();
376
+ function validateSubmissionIntent(value, taskId, intentId, paymentAttemptId) {
377
+ if (!isRecord(value) || !isUuid(value.intentId) || !isUuid(value.taskId) || !isUuid(value.taskSubmissionId) || !isUuid(value.paymentAttemptId, true) || !sameId(value.taskId, taskId) || intentId !== void 0 && !sameId(value.intentId, intentId) || paymentAttemptId !== void 0 && !sameId(value.paymentAttemptId, paymentAttemptId) || typeof value.status !== "string" || !statuses.includes(value.status) || !(value.txHash === null || nonempty(value.txHash)) || !nonempty(value.expiresAt) || !Number.isFinite(Date.parse(value.expiresAt)) || !natural(value.lastValidBlockHeight) || value.reviewReason !== void 0 && typeof value.reviewReason !== "string" || value.serializedTransaction !== void 0 && !nonempty(value.serializedTransaction) || !isRecord(value.fee) || !decimal(value.fee.amount) || !decimal(value.fee.totalDebit) || typeof value.fee.amountBaseUnits !== "string" || !/^\d+$/.test(value.fee.amountBaseUnits) || value.fee.symbol !== "USDC" || !natural(value.fee.decimals) || !nonempty(value.fee.mintAddress) || !nonempty(value.fee.destinationAddress)) {
378
+ throw new GibworkProtocolError("Invalid submission intent response");
379
+ }
380
+ }
274
381
  async function signPreparedTransaction(serializedTransaction, signer) {
275
382
  if (typeof serializedTransaction !== "string" || !serializedTransaction) {
276
383
  throw new GibworkConfigurationError(
@@ -360,6 +467,142 @@ var SubmissionsResource = class {
360
467
  transport;
361
468
  signer;
362
469
  comments;
470
+ /** Creates work through a participation fee; only fulfilled confirms creation. */
471
+ async create(taskId, input, options) {
472
+ const path = submissionIntentPath(taskId);
473
+ const { walletAddress, ...snapshot } = normalizeCreateSubmission(
474
+ input,
475
+ this.walletAddress
476
+ );
477
+ let context = {
478
+ taskId,
479
+ idempotencyKey: snapshot.idempotencyKey,
480
+ walletAddress,
481
+ ...this.transport.recoveryLocation,
482
+ phase: "prepare"
483
+ };
484
+ try {
485
+ assertNotAborted(options, path);
486
+ const prepared = await this.prepareCreate(taskId, snapshot, options);
487
+ context = {
488
+ ...context,
489
+ intentId: prepared.intentId,
490
+ paymentAttemptId: prepared.paymentAttemptId,
491
+ submissionId: prepared.taskSubmissionId,
492
+ phase: "sign"
493
+ };
494
+ if (prepared.status !== "pending") return prepared;
495
+ if (!prepared.serializedTransaction)
496
+ throw new GibworkProtocolError(
497
+ "Pending prepare response is missing its transaction"
498
+ );
499
+ assertNotAborted(options, path);
500
+ const signedTransaction = await signPreparedTransaction(
501
+ prepared.serializedTransaction,
502
+ this.signer
503
+ );
504
+ assertNotAborted(options, path);
505
+ context = { ...context, phase: "submit" };
506
+ return await this.submitCreate(
507
+ taskId,
508
+ prepared.intentId,
509
+ { paymentAttemptId: prepared.paymentAttemptId, signedTransaction },
510
+ options
511
+ );
512
+ } catch (cause) {
513
+ if (cause instanceof GibworkAmbiguousSubmitError) {
514
+ throw new GibworkAmbiguousSubmitError(
515
+ cause.method,
516
+ cause.path,
517
+ {
518
+ ...cause.context,
519
+ ...context,
520
+ operation: "create-submission",
521
+ intentId: cause.context.intentId
522
+ },
523
+ { cause }
524
+ );
525
+ }
526
+ throw new GibworkSubmissionOperationError(context, { cause });
527
+ }
528
+ }
529
+ /** Reuse the exact input and idempotency key after an interrupted prepare. */
530
+ async prepareCreate(taskId, input, options) {
531
+ const path = submissionIntentPath(taskId);
532
+ const body = normalizeCreateSubmission(input, this.walletAddress);
533
+ assertNotAborted(options, path);
534
+ const headers = await createWalletAuthHeaders(this.signer, {
535
+ operation: "gibwork:create-task-submission-intent",
536
+ method: "POST",
537
+ path,
538
+ resourceFields: [["taskId", taskId]],
539
+ hash: ["payloadHash", sha256Json(body)]
540
+ });
541
+ return this.transport.request({
542
+ method: "POST",
543
+ path,
544
+ headers,
545
+ body,
546
+ ...options ? { options } : {},
547
+ validateResponse: (value) => validateSubmissionIntent(value, taskId)
548
+ });
549
+ }
550
+ /** Never automatically retries. Recover an uncertain payment with getIntent. */
551
+ async submitCreate(taskId, intentId, input, options) {
552
+ const path = `${submissionIntentPath(taskId, intentId)}/submit`;
553
+ assertObjectKeys(input, ["paymentAttemptId", "signedTransaction"]);
554
+ assertUuid(input.paymentAttemptId, "paymentAttemptId", true);
555
+ assertText(input.signedTransaction, "signedTransaction", 1, 1e4);
556
+ const body = {
557
+ paymentAttemptId: input.paymentAttemptId,
558
+ signedTransaction: input.signedTransaction
559
+ };
560
+ return this.transport.request({
561
+ method: "POST",
562
+ path,
563
+ body,
564
+ ...options ? { options } : {},
565
+ ambiguousSubmit: {
566
+ operation: "create-submission",
567
+ taskId,
568
+ intentId,
569
+ paymentAttemptId: body.paymentAttemptId,
570
+ walletAddress: this.walletAddress,
571
+ ...this.transport.recoveryLocation
572
+ },
573
+ validateResponse: (value) => validateSubmissionIntent(
574
+ value,
575
+ taskId,
576
+ intentId,
577
+ body.paymentAttemptId
578
+ )
579
+ });
580
+ }
581
+ /** Reads/reconciles creation using the original wallet; does not report review/payout. */
582
+ async getIntent(taskId, intentId, options) {
583
+ const path = submissionIntentPath(taskId, intentId);
584
+ if (options?.signal?.aborted)
585
+ throw new GibworkRequestAbortedError("GET", path, {
586
+ cause: options.signal.reason
587
+ });
588
+ const headers = await createWalletAuthHeaders(this.signer, {
589
+ operation: "gibwork:view-task-submission-intent",
590
+ method: "GET",
591
+ path,
592
+ resourceFields: [
593
+ ["taskId", taskId],
594
+ ["intentId", intentId]
595
+ ],
596
+ hash: ["queryHash", sha256Json({})]
597
+ });
598
+ return this.transport.request({
599
+ method: "GET",
600
+ path,
601
+ headers,
602
+ ...options ? { options } : {},
603
+ validateResponse: (value) => validateSubmissionIntent(value, taskId, intentId)
604
+ });
605
+ }
363
606
  async list(taskId, query = {}, options) {
364
607
  const normalized = normalizeSubmissionPagination(query);
365
608
  const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/submissions`;
@@ -549,6 +792,23 @@ var TasksResource = class {
549
792
  ...options ? { options } : {}
550
793
  });
551
794
  }
795
+ /** Discover tasks across creators. Eligibility is checked when preparing work. */
796
+ async listAvailable(query = {}, options) {
797
+ const normalized = normalizeAvailableTasksQuery(query);
798
+ const path = "/v2/int/tasks/available";
799
+ const headers = await this.auth({
800
+ operation: "gibwork:view-available-tasks",
801
+ method: "GET",
802
+ path,
803
+ hash: ["queryHash", sha256Json(normalized)]
804
+ });
805
+ return this.transport.request({
806
+ method: "GET",
807
+ path: `${path}?${toQueryString(normalized)}`,
808
+ headers,
809
+ ...options ? { options } : {}
810
+ });
811
+ }
552
812
  async update(taskId, input, options) {
553
813
  const path = `/v2/int/tasks/${encodeURIComponent(taskId)}`;
554
814
  const body = normalizeUpdateTask(input, this.walletAddress);
@@ -694,6 +954,16 @@ var HttpTransport = class {
694
954
  );
695
955
  }
696
956
  }
957
+ /** Recovery location without URL credentials. Retains reverse-proxy path prefixes. */
958
+ get recoveryLocation() {
959
+ const url = new URL(this.baseUrl);
960
+ url.username = "";
961
+ url.password = "";
962
+ return {
963
+ apiUrl: url.toString().replace(/\/+$/, ""),
964
+ environment: this.production ? "prod" : "stage"
965
+ };
966
+ }
697
967
  async request(request) {
698
968
  const controller = new AbortController();
699
969
  const callerSignal = request.options?.signal;
@@ -703,12 +973,6 @@ var HttpTransport = class {
703
973
  cause: callerSignal.reason
704
974
  });
705
975
  }
706
- const abortFromCaller = () => controller.abort(callerSignal?.reason);
707
- callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
708
- const timeout = setTimeout(() => {
709
- timedOut = true;
710
- controller.abort();
711
- }, this.timeoutMs);
712
976
  const headers = {
713
977
  accept: "application/json",
714
978
  ...request.headers,
@@ -723,13 +987,40 @@ var HttpTransport = class {
723
987
  headers["content-type"] = "application/json";
724
988
  init.body = JSON.stringify(request.body);
725
989
  }
726
- let response;
990
+ const abortFromCaller = () => controller.abort(callerSignal?.reason);
991
+ callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
992
+ let rejectOnAbort = () => {
993
+ };
994
+ const aborted = new Promise((_, reject) => {
995
+ rejectOnAbort = () => reject(controller.signal.reason);
996
+ controller.signal.addEventListener("abort", rejectOnAbort, {
997
+ once: true
998
+ });
999
+ });
1000
+ const timeout = setTimeout(() => {
1001
+ timedOut = true;
1002
+ controller.abort();
1003
+ }, this.timeoutMs);
727
1004
  try {
728
- response = await this.fetchImplementation(
729
- `${this.baseUrl}${request.path}`,
730
- init
731
- );
1005
+ const response = await Promise.race([
1006
+ this.fetchImplementation(`${this.baseUrl}${request.path}`, init),
1007
+ aborted
1008
+ ]);
1009
+ const body = await Promise.race([parseResponseBody(response), aborted]);
1010
+ if (!response.ok) {
1011
+ throw new GibworkApiError(
1012
+ response.status,
1013
+ body,
1014
+ request.method,
1015
+ request.path,
1016
+ response.headers.get("x-request-id") ?? void 0,
1017
+ response.headers.get("retry-after") ?? void 0
1018
+ );
1019
+ }
1020
+ request.validateResponse?.(body);
1021
+ return body;
732
1022
  } catch (cause) {
1023
+ if (cause instanceof GibworkApiError && cause.status < 500) throw cause;
733
1024
  if (request.ambiguousSubmit) {
734
1025
  throw new GibworkAmbiguousSubmitError(
735
1026
  request.method,
@@ -738,6 +1029,8 @@ var HttpTransport = class {
738
1029
  { cause }
739
1030
  );
740
1031
  }
1032
+ if (cause instanceof GibworkApiError || cause instanceof GibworkProtocolError)
1033
+ throw cause;
741
1034
  if (timedOut) {
742
1035
  throw new GibworkTimeoutError(
743
1036
  request.method,
@@ -752,7 +1045,7 @@ var HttpTransport = class {
752
1045
  });
753
1046
  }
754
1047
  throw new GibworkNetworkError(
755
- "Could not reach the Gibwork API",
1048
+ "Could not read the Gibwork API response",
756
1049
  request.method,
757
1050
  request.path,
758
1051
  { cause }
@@ -760,28 +1053,8 @@ var HttpTransport = class {
760
1053
  } finally {
761
1054
  clearTimeout(timeout);
762
1055
  callerSignal?.removeEventListener("abort", abortFromCaller);
1056
+ controller.signal.removeEventListener("abort", rejectOnAbort);
763
1057
  }
764
- const body = await parseResponseBody(response);
765
- if (!response.ok) {
766
- const requestId = response.headers.get("x-request-id") ?? void 0;
767
- const apiError = new GibworkApiError(
768
- response.status,
769
- body,
770
- request.method,
771
- request.path,
772
- requestId
773
- );
774
- if (request.ambiguousSubmit && response.status >= 500) {
775
- throw new GibworkAmbiguousSubmitError(
776
- request.method,
777
- request.path,
778
- request.ambiguousSubmit,
779
- { cause: apiError }
780
- );
781
- }
782
- throw apiError;
783
- }
784
- return body;
785
1058
  }
786
1059
  };
787
1060
  function normalizeBaseUrl(value) {
@@ -923,8 +1196,11 @@ exports.GibworkClient = GibworkClient;
923
1196
  exports.GibworkConfigurationError = GibworkConfigurationError;
924
1197
  exports.GibworkError = GibworkError;
925
1198
  exports.GibworkNetworkError = GibworkNetworkError;
1199
+ exports.GibworkProtocolError = GibworkProtocolError;
926
1200
  exports.GibworkRequestAbortedError = GibworkRequestAbortedError;
1201
+ exports.GibworkSubmissionOperationError = GibworkSubmissionOperationError;
927
1202
  exports.GibworkTimeoutError = GibworkTimeoutError;
1203
+ exports.GibworkValidationError = GibworkValidationError;
928
1204
  exports.createGibworkClient = createGibworkClient;
929
1205
  exports.createKeypairSigner = createKeypairSigner;
930
1206
  exports.keypairFromPrivateKey = keypairFromPrivateKey;