@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/CHANGELOG.md +7 -0
- package/README.md +85 -1
- package/dist/{chunk-3MLFNEQ5.js → chunk-HJ5D5SOE.js} +310 -37
- package/dist/chunk-HJ5D5SOE.js.map +1 -0
- package/dist/index.cjs +310 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +130 -3
- package/dist/index.d.ts +130 -3
- package/dist/index.js +1 -1
- package/dist/node.cjs +310 -34
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-3MLFNEQ5.js.map +0 -1
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-
|
|
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
|
-
|
|
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
|
|
729
|
-
`${this.baseUrl}${request.path}`,
|
|
730
|
-
|
|
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
|
|
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;
|