@gibwork/sdk 0.0.1
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 +11 -0
- package/README.md +176 -0
- package/dist/chunk-24XHBDMZ.js +773 -0
- package/dist/chunk-24XHBDMZ.js.map +1 -0
- package/dist/index.cjs +788 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +356 -0
- package/dist/index.d.ts +356 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +870 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +14 -0
- package/dist/node.d.ts +14 -0
- package/dist/node.js +85 -0
- package/dist/node.js.map +1 -0
- package/package.json +81 -0
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto = require('crypto');
|
|
4
|
+
var sanitizeHtml = require('sanitize-html');
|
|
5
|
+
var web3_js = require('@solana/web3.js');
|
|
6
|
+
var bs58 = require('bs58');
|
|
7
|
+
var nacl = require('tweetnacl');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var sanitizeHtml__default = /*#__PURE__*/_interopDefault(sanitizeHtml);
|
|
12
|
+
var bs58__default = /*#__PURE__*/_interopDefault(bs58);
|
|
13
|
+
var nacl__default = /*#__PURE__*/_interopDefault(nacl);
|
|
14
|
+
|
|
15
|
+
// src/config.ts
|
|
16
|
+
var FALLBACK_GIBWORK_API_URL = "https://sdk.gib.work";
|
|
17
|
+
var DEFAULT_GIBWORK_API_URL = "https://sdk.gib.work"?.trim() || FALLBACK_GIBWORK_API_URL;
|
|
18
|
+
|
|
19
|
+
// src/errors/index.ts
|
|
20
|
+
var GibworkError = class extends Error {
|
|
21
|
+
name = "GibworkError";
|
|
22
|
+
};
|
|
23
|
+
var GibworkConfigurationError = class extends GibworkError {
|
|
24
|
+
name = "GibworkConfigurationError";
|
|
25
|
+
};
|
|
26
|
+
var GibworkApiError = class extends GibworkError {
|
|
27
|
+
constructor(status, body, method, path, requestId) {
|
|
28
|
+
super(`Gibwork API request failed with HTTP ${status}`);
|
|
29
|
+
this.status = status;
|
|
30
|
+
this.body = body;
|
|
31
|
+
this.method = method;
|
|
32
|
+
this.path = path;
|
|
33
|
+
this.requestId = requestId;
|
|
34
|
+
}
|
|
35
|
+
status;
|
|
36
|
+
body;
|
|
37
|
+
method;
|
|
38
|
+
path;
|
|
39
|
+
requestId;
|
|
40
|
+
name = "GibworkApiError";
|
|
41
|
+
};
|
|
42
|
+
var GibworkNetworkError = class extends GibworkError {
|
|
43
|
+
constructor(message, method, path, options) {
|
|
44
|
+
super(message, options);
|
|
45
|
+
this.method = method;
|
|
46
|
+
this.path = path;
|
|
47
|
+
}
|
|
48
|
+
method;
|
|
49
|
+
path;
|
|
50
|
+
name = "GibworkNetworkError";
|
|
51
|
+
};
|
|
52
|
+
var GibworkTimeoutError = class extends GibworkNetworkError {
|
|
53
|
+
constructor(method, path, timeoutMs, options) {
|
|
54
|
+
super(
|
|
55
|
+
`Gibwork API request timed out after ${timeoutMs}ms`,
|
|
56
|
+
method,
|
|
57
|
+
path,
|
|
58
|
+
options
|
|
59
|
+
);
|
|
60
|
+
this.timeoutMs = timeoutMs;
|
|
61
|
+
}
|
|
62
|
+
timeoutMs;
|
|
63
|
+
name = "GibworkTimeoutError";
|
|
64
|
+
};
|
|
65
|
+
var GibworkRequestAbortedError = class extends GibworkNetworkError {
|
|
66
|
+
name = "GibworkRequestAbortedError";
|
|
67
|
+
constructor(method, path, options) {
|
|
68
|
+
super("Gibwork API request was aborted", method, path, options);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
var GibworkAmbiguousSubmitError = class extends GibworkNetworkError {
|
|
72
|
+
constructor(method, path, context, options) {
|
|
73
|
+
super(
|
|
74
|
+
"The transaction submit outcome is unknown; do not submit the same intent again",
|
|
75
|
+
method,
|
|
76
|
+
path,
|
|
77
|
+
options
|
|
78
|
+
);
|
|
79
|
+
this.context = context;
|
|
80
|
+
}
|
|
81
|
+
context;
|
|
82
|
+
name = "GibworkAmbiguousSubmitError";
|
|
83
|
+
};
|
|
84
|
+
var HTML_SANITIZE_OPTIONS = {
|
|
85
|
+
allowedTags: sanitizeHtml__default.default.defaults.allowedTags.concat(["img"]),
|
|
86
|
+
allowedAttributes: {
|
|
87
|
+
...sanitizeHtml__default.default.defaults.allowedAttributes,
|
|
88
|
+
"*": ["class", "id", "style", "data-type"],
|
|
89
|
+
a: ["href", "name", "target"],
|
|
90
|
+
img: [
|
|
91
|
+
"src",
|
|
92
|
+
"srcset",
|
|
93
|
+
"alt",
|
|
94
|
+
"title",
|
|
95
|
+
"limit",
|
|
96
|
+
"width",
|
|
97
|
+
"height",
|
|
98
|
+
"loading",
|
|
99
|
+
"maxSize",
|
|
100
|
+
"fileid"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
transformTags: {
|
|
104
|
+
a: (tagName, attributes) => {
|
|
105
|
+
if (isAllowedExternalUrl(attributes.href)) {
|
|
106
|
+
return { tagName, attribs: attributes };
|
|
107
|
+
}
|
|
108
|
+
const safeAttributes = { ...attributes };
|
|
109
|
+
delete safeAttributes.href;
|
|
110
|
+
return { tagName: "span", attribs: safeAttributes };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
function sanitizeContent(content) {
|
|
115
|
+
return sanitizeHtml__default.default(content, HTML_SANITIZE_OPTIONS).trim();
|
|
116
|
+
}
|
|
117
|
+
function isAllowedExternalUrl(value) {
|
|
118
|
+
if (!value) return false;
|
|
119
|
+
const normalized = value.toLowerCase().startsWith("www.") ? `https://${value}` : value;
|
|
120
|
+
try {
|
|
121
|
+
const url = new URL(normalized);
|
|
122
|
+
return (url.protocol === "http:" || url.protocol === "https:") && Boolean(url.hostname) && !url.username && !url.password;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/auth/normalize.ts
|
|
129
|
+
function normalizeCreateTask(input, walletAddress) {
|
|
130
|
+
const normalized = {
|
|
131
|
+
walletAddress: walletAddress.trim(),
|
|
132
|
+
title: input.title.trim(),
|
|
133
|
+
content: sanitizeContent(input.content),
|
|
134
|
+
tags: input.tags.map((tag) => tag.trim()),
|
|
135
|
+
payment: {
|
|
136
|
+
mintAddress: input.payment.mintAddress.trim(),
|
|
137
|
+
amount: input.payment.amount.trim()
|
|
138
|
+
},
|
|
139
|
+
minSubmissionAmount: input.minSubmissionAmount.trim(),
|
|
140
|
+
deadline: input.deadline ?? null,
|
|
141
|
+
allowOnlyVerifiedSubmissions: input.allowOnlyVerifiedSubmissions ?? false
|
|
142
|
+
};
|
|
143
|
+
const primarySkillSlug = trimOptional(input.primarySkillSlug)?.toLowerCase();
|
|
144
|
+
if (primarySkillSlug !== void 0 && primarySkillSlug !== null) {
|
|
145
|
+
normalized.primarySkillSlug = primarySkillSlug;
|
|
146
|
+
}
|
|
147
|
+
if (input.refundFeeOverride !== void 0 && input.refundFeeOverride !== null) {
|
|
148
|
+
normalized.refundFeeOverride = input.refundFeeOverride;
|
|
149
|
+
}
|
|
150
|
+
if (input.allowOnlyDiscordGuildSubmissions !== void 0) {
|
|
151
|
+
normalized.allowOnlyDiscordGuildSubmissions = input.allowOnlyDiscordGuildSubmissions;
|
|
152
|
+
}
|
|
153
|
+
const requiredDiscordGuildId = trimOptional(input.requiredDiscordGuildId);
|
|
154
|
+
if (requiredDiscordGuildId !== void 0) {
|
|
155
|
+
normalized.requiredDiscordGuildId = requiredDiscordGuildId;
|
|
156
|
+
}
|
|
157
|
+
const requiredDiscordGuildName = trimOptional(input.requiredDiscordGuildName);
|
|
158
|
+
if (requiredDiscordGuildName !== void 0) {
|
|
159
|
+
normalized.requiredDiscordGuildName = requiredDiscordGuildName;
|
|
160
|
+
}
|
|
161
|
+
const discordGuildInvitationUrl = trimOptional(
|
|
162
|
+
input.discordGuildInvitationUrl
|
|
163
|
+
);
|
|
164
|
+
if (discordGuildInvitationUrl !== void 0) {
|
|
165
|
+
normalized.discordGuildInvitationUrl = discordGuildInvitationUrl;
|
|
166
|
+
}
|
|
167
|
+
if (input.requiredDiscordRoleIds !== void 0) {
|
|
168
|
+
const roles = input.requiredDiscordRoleIds.map((role) => role.trim()).filter(Boolean);
|
|
169
|
+
if (roles.length > 0) normalized.requiredDiscordRoleIds = roles;
|
|
170
|
+
}
|
|
171
|
+
return normalized;
|
|
172
|
+
}
|
|
173
|
+
function normalizeTaskPagination(query = {}) {
|
|
174
|
+
return {
|
|
175
|
+
page: query.page ?? 1,
|
|
176
|
+
limit: query.limit ?? 15,
|
|
177
|
+
pageAll: query.pageAll ?? false
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function normalizeSubmissionPagination(query = {}) {
|
|
181
|
+
return {
|
|
182
|
+
page: query.page ?? 1,
|
|
183
|
+
limit: query.limit ?? 15,
|
|
184
|
+
pageAll: query.pageAll ?? false,
|
|
185
|
+
status: query.status ?? null
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function normalizeApproval(input, walletAddress) {
|
|
189
|
+
const normalized = {
|
|
190
|
+
walletAddress,
|
|
191
|
+
amount: input.amount.trim()
|
|
192
|
+
};
|
|
193
|
+
if (input.rating !== void 0) normalized.rating = input.rating;
|
|
194
|
+
return normalized;
|
|
195
|
+
}
|
|
196
|
+
function normalizeRejection(reason, walletAddress) {
|
|
197
|
+
return {
|
|
198
|
+
walletAddress,
|
|
199
|
+
rejectReason: typeof reason === "string" ? reason.trim() : reason ?? null
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function normalizeComment(content, walletAddress) {
|
|
203
|
+
return {
|
|
204
|
+
walletAddress,
|
|
205
|
+
content: sanitizeContent(content)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function sha256Json(value) {
|
|
209
|
+
return crypto.createHash("sha256").update(JSON.stringify(value), "utf8").digest("hex");
|
|
210
|
+
}
|
|
211
|
+
function toQueryString(values) {
|
|
212
|
+
const query = new URLSearchParams();
|
|
213
|
+
for (const [key, value] of Object.entries(values)) {
|
|
214
|
+
if (value !== void 0 && value !== null) query.set(key, String(value));
|
|
215
|
+
}
|
|
216
|
+
return query.toString();
|
|
217
|
+
}
|
|
218
|
+
function trimOptional(value) {
|
|
219
|
+
if (typeof value !== "string") return value;
|
|
220
|
+
const trimmed = value.trim();
|
|
221
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
222
|
+
}
|
|
223
|
+
async function createWalletAuthHeaders(signer, descriptor, dependencies = {}) {
|
|
224
|
+
const values = {
|
|
225
|
+
walletAddress: signer.publicKey.toBase58(),
|
|
226
|
+
timestamp: Math.floor(
|
|
227
|
+
(dependencies.now?.() ?? Date.now()) / 1e3
|
|
228
|
+
).toString(),
|
|
229
|
+
nonce: dependencies.nonce?.() ?? crypto.randomUUID()
|
|
230
|
+
};
|
|
231
|
+
const message = buildWalletAuthMessage(descriptor, values);
|
|
232
|
+
const signature = await signer.signMessage(new TextEncoder().encode(message));
|
|
233
|
+
if (!(signature instanceof Uint8Array) || signature.length !== 64) {
|
|
234
|
+
throw new GibworkConfigurationError(
|
|
235
|
+
"WalletSigner.signMessage must return a 64-byte Ed25519 signature"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
"x-gibwork-wallet-address": values.walletAddress,
|
|
240
|
+
"x-gibwork-timestamp": values.timestamp,
|
|
241
|
+
"x-gibwork-nonce": values.nonce,
|
|
242
|
+
"x-gibwork-signature": Buffer.from(signature).toString("base64")
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function buildWalletAuthMessage(descriptor, values) {
|
|
246
|
+
return [
|
|
247
|
+
descriptor.operation,
|
|
248
|
+
`method:${descriptor.method}`,
|
|
249
|
+
`path:${descriptor.path}`,
|
|
250
|
+
...(descriptor.resourceFields ?? []).map(
|
|
251
|
+
([name, value]) => `${name}:${value}`
|
|
252
|
+
),
|
|
253
|
+
`walletAddress:${values.walletAddress}`,
|
|
254
|
+
`timestamp:${values.timestamp}`,
|
|
255
|
+
`nonce:${values.nonce}`,
|
|
256
|
+
`${descriptor.hash[0]}:${descriptor.hash[1]}`
|
|
257
|
+
].join("\n");
|
|
258
|
+
}
|
|
259
|
+
async function signPreparedTransaction(serializedTransaction, signer) {
|
|
260
|
+
if (typeof serializedTransaction !== "string" || !serializedTransaction) {
|
|
261
|
+
throw new GibworkConfigurationError(
|
|
262
|
+
"serializedTransaction must be a non-empty base64 string"
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
let transaction;
|
|
266
|
+
try {
|
|
267
|
+
transaction = web3_js.VersionedTransaction.deserialize(
|
|
268
|
+
Buffer.from(serializedTransaction, "base64")
|
|
269
|
+
);
|
|
270
|
+
} catch (cause) {
|
|
271
|
+
throw new GibworkConfigurationError(
|
|
272
|
+
"serializedTransaction is not a valid Solana VersionedTransaction",
|
|
273
|
+
{ cause }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const signed = await signer.signTransaction(transaction);
|
|
277
|
+
if (!(signed instanceof web3_js.VersionedTransaction)) {
|
|
278
|
+
throw new GibworkConfigurationError(
|
|
279
|
+
"WalletSigner.signTransaction must return a VersionedTransaction"
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
return Buffer.from(signed.serialize()).toString("base64");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// src/resources/submissions/submission-comments-resource.ts
|
|
286
|
+
var SubmissionCommentsResource = class {
|
|
287
|
+
constructor(transport, signer) {
|
|
288
|
+
this.transport = transport;
|
|
289
|
+
this.signer = signer;
|
|
290
|
+
}
|
|
291
|
+
transport;
|
|
292
|
+
signer;
|
|
293
|
+
async list(taskId, submissionId, options) {
|
|
294
|
+
const path = commentsPath(taskId, submissionId);
|
|
295
|
+
const headers = await createWalletAuthHeaders(this.signer, {
|
|
296
|
+
operation: "gibwork:view-task-submission-comments",
|
|
297
|
+
method: "GET",
|
|
298
|
+
path,
|
|
299
|
+
resourceFields: [
|
|
300
|
+
["taskId", taskId],
|
|
301
|
+
["taskSubmissionId", submissionId]
|
|
302
|
+
],
|
|
303
|
+
hash: ["queryHash", sha256Json({})]
|
|
304
|
+
});
|
|
305
|
+
return this.transport.request({
|
|
306
|
+
method: "GET",
|
|
307
|
+
path,
|
|
308
|
+
headers,
|
|
309
|
+
...options ? { options } : {}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
async create(taskId, submissionId, content, options) {
|
|
313
|
+
const path = commentsPath(taskId, submissionId);
|
|
314
|
+
const body = normalizeComment(content, this.signer.publicKey.toBase58());
|
|
315
|
+
const headers = await createWalletAuthHeaders(this.signer, {
|
|
316
|
+
operation: "gibwork:create-task-submission-comment",
|
|
317
|
+
method: "POST",
|
|
318
|
+
path,
|
|
319
|
+
resourceFields: [
|
|
320
|
+
["taskId", taskId],
|
|
321
|
+
["taskSubmissionId", submissionId]
|
|
322
|
+
],
|
|
323
|
+
hash: ["payloadHash", sha256Json(body)]
|
|
324
|
+
});
|
|
325
|
+
return this.transport.request({
|
|
326
|
+
method: "POST",
|
|
327
|
+
path,
|
|
328
|
+
headers,
|
|
329
|
+
body,
|
|
330
|
+
...options ? { options } : {}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
function commentsPath(taskId, submissionId) {
|
|
335
|
+
return `/v2/int/tasks/${encodeURIComponent(taskId)}/submission/${encodeURIComponent(submissionId)}/comments`;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/resources/submissions/submissions-resource.ts
|
|
339
|
+
var SubmissionsResource = class {
|
|
340
|
+
constructor(transport, signer) {
|
|
341
|
+
this.transport = transport;
|
|
342
|
+
this.signer = signer;
|
|
343
|
+
this.comments = new SubmissionCommentsResource(transport, signer);
|
|
344
|
+
}
|
|
345
|
+
transport;
|
|
346
|
+
signer;
|
|
347
|
+
comments;
|
|
348
|
+
async list(taskId, query = {}, options) {
|
|
349
|
+
const normalized = normalizeSubmissionPagination(query);
|
|
350
|
+
const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/submissions`;
|
|
351
|
+
const headers = await createWalletAuthHeaders(this.signer, {
|
|
352
|
+
operation: "gibwork:view-task-submissions",
|
|
353
|
+
method: "GET",
|
|
354
|
+
path,
|
|
355
|
+
resourceFields: [["taskId", taskId]],
|
|
356
|
+
hash: ["queryHash", sha256Json(normalized)]
|
|
357
|
+
});
|
|
358
|
+
const requestQuery = { ...normalized };
|
|
359
|
+
if (requestQuery.status === null) delete requestQuery.status;
|
|
360
|
+
return this.transport.request({
|
|
361
|
+
method: "GET",
|
|
362
|
+
path: `${path}?${toQueryString(requestQuery)}`,
|
|
363
|
+
headers,
|
|
364
|
+
...options ? { options } : {}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
async approve(taskId, submissionId, input, options) {
|
|
368
|
+
const prepared = await this.prepareApproval(
|
|
369
|
+
taskId,
|
|
370
|
+
submissionId,
|
|
371
|
+
input,
|
|
372
|
+
options
|
|
373
|
+
);
|
|
374
|
+
const submitPath = approvalSubmitPath(
|
|
375
|
+
taskId,
|
|
376
|
+
submissionId,
|
|
377
|
+
prepared.intentId
|
|
378
|
+
);
|
|
379
|
+
assertNotAborted(options, submitPath);
|
|
380
|
+
const signedTransaction = await signPreparedTransaction(
|
|
381
|
+
prepared.serializedTransaction,
|
|
382
|
+
this.signer
|
|
383
|
+
);
|
|
384
|
+
assertNotAborted(options, submitPath);
|
|
385
|
+
const submitted = await this.submitApproval(
|
|
386
|
+
taskId,
|
|
387
|
+
submissionId,
|
|
388
|
+
prepared.intentId,
|
|
389
|
+
signedTransaction,
|
|
390
|
+
options
|
|
391
|
+
);
|
|
392
|
+
return {
|
|
393
|
+
...submitted,
|
|
394
|
+
intentId: prepared.intentId,
|
|
395
|
+
lastValidBlockHeight: prepared.lastValidBlockHeight,
|
|
396
|
+
approvalQuote: prepared.approvalQuote
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
async reject(taskId, submissionId, reason, options) {
|
|
400
|
+
const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/submission/${encodeURIComponent(submissionId)}/reject`;
|
|
401
|
+
const body = normalizeRejection(reason, this.walletAddress);
|
|
402
|
+
const headers = await createWalletAuthHeaders(this.signer, {
|
|
403
|
+
operation: "gibwork:reject-task-submission",
|
|
404
|
+
method: "POST",
|
|
405
|
+
path,
|
|
406
|
+
resourceFields: [
|
|
407
|
+
["taskId", taskId],
|
|
408
|
+
["taskSubmissionId", submissionId]
|
|
409
|
+
],
|
|
410
|
+
hash: ["payloadHash", sha256Json(body)]
|
|
411
|
+
});
|
|
412
|
+
return this.transport.request({
|
|
413
|
+
method: "POST",
|
|
414
|
+
path,
|
|
415
|
+
headers,
|
|
416
|
+
body,
|
|
417
|
+
...options ? { options } : {}
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
async prepareApproval(taskId, submissionId, input, options) {
|
|
421
|
+
const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/submission/${encodeURIComponent(submissionId)}/pay`;
|
|
422
|
+
const body = normalizeApproval(input, this.walletAddress);
|
|
423
|
+
const headers = await createWalletAuthHeaders(this.signer, {
|
|
424
|
+
operation: "gibwork:approve-task-submission-intent",
|
|
425
|
+
method: "POST",
|
|
426
|
+
path,
|
|
427
|
+
resourceFields: [
|
|
428
|
+
["taskId", taskId],
|
|
429
|
+
["taskSubmissionId", submissionId]
|
|
430
|
+
],
|
|
431
|
+
hash: ["payloadHash", sha256Json(body)]
|
|
432
|
+
});
|
|
433
|
+
return this.transport.request({
|
|
434
|
+
method: "POST",
|
|
435
|
+
path,
|
|
436
|
+
headers,
|
|
437
|
+
body,
|
|
438
|
+
...options ? { options } : {}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async submitApproval(taskId, submissionId, intentId, signedTransaction, options) {
|
|
442
|
+
const path = approvalSubmitPath(taskId, submissionId, intentId);
|
|
443
|
+
return this.transport.request({
|
|
444
|
+
method: "POST",
|
|
445
|
+
path,
|
|
446
|
+
body: { signedTransaction },
|
|
447
|
+
...options ? { options } : {},
|
|
448
|
+
ambiguousSubmit: {
|
|
449
|
+
operation: "approve-submission",
|
|
450
|
+
taskId,
|
|
451
|
+
submissionId,
|
|
452
|
+
intentId
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
get walletAddress() {
|
|
457
|
+
return this.signer.publicKey.toBase58();
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
function approvalSubmitPath(taskId, submissionId, intentId) {
|
|
461
|
+
return `/v2/int/tasks/${encodeURIComponent(taskId)}/submission/${encodeURIComponent(submissionId)}/pay/${encodeURIComponent(intentId)}/submit`;
|
|
462
|
+
}
|
|
463
|
+
function assertNotAborted(options, path) {
|
|
464
|
+
if (options?.signal?.aborted) {
|
|
465
|
+
throw new GibworkRequestAbortedError("POST", path, {
|
|
466
|
+
cause: options.signal.reason
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// src/resources/tasks/tasks-resource.ts
|
|
472
|
+
var TasksResource = class {
|
|
473
|
+
constructor(transport, signer) {
|
|
474
|
+
this.transport = transport;
|
|
475
|
+
this.signer = signer;
|
|
476
|
+
}
|
|
477
|
+
transport;
|
|
478
|
+
signer;
|
|
479
|
+
async create(input, options) {
|
|
480
|
+
const prepared = await this.prepareCreate(input, options);
|
|
481
|
+
assertNotAborted2(options, "/v2/int/tasks");
|
|
482
|
+
const signedTransaction = await signPreparedTransaction(
|
|
483
|
+
prepared.serializedTransaction,
|
|
484
|
+
this.signer
|
|
485
|
+
);
|
|
486
|
+
assertNotAborted2(
|
|
487
|
+
options,
|
|
488
|
+
`/v2/int/tasks/${encodeURIComponent(prepared.intentId)}/submit`
|
|
489
|
+
);
|
|
490
|
+
const submitted = await this.submitCreate(
|
|
491
|
+
prepared.intentId,
|
|
492
|
+
signedTransaction,
|
|
493
|
+
options
|
|
494
|
+
);
|
|
495
|
+
return {
|
|
496
|
+
...submitted,
|
|
497
|
+
intentId: prepared.intentId,
|
|
498
|
+
lastValidBlockHeight: prepared.lastValidBlockHeight,
|
|
499
|
+
paymentQuote: prepared.paymentQuote
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
async list(query = {}, options) {
|
|
503
|
+
const normalized = normalizeTaskPagination(query);
|
|
504
|
+
const path = "/v2/int/tasks";
|
|
505
|
+
const headers = await this.auth({
|
|
506
|
+
operation: "gibwork:view-created-tasks",
|
|
507
|
+
method: "GET",
|
|
508
|
+
path,
|
|
509
|
+
hash: ["queryHash", sha256Json(normalized)]
|
|
510
|
+
});
|
|
511
|
+
return this.transport.request({
|
|
512
|
+
method: "GET",
|
|
513
|
+
path: `${path}?${toQueryString(normalized)}`,
|
|
514
|
+
headers,
|
|
515
|
+
...options ? { options } : {}
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
async refund(taskId, options) {
|
|
519
|
+
const prepared = await this.prepareRefund(taskId, options);
|
|
520
|
+
const submitPath = `/v2/int/tasks/${encodeURIComponent(taskId)}/refund/${encodeURIComponent(prepared.intentId)}/submit`;
|
|
521
|
+
assertNotAborted2(options, submitPath);
|
|
522
|
+
const signedTransaction = await signPreparedTransaction(
|
|
523
|
+
prepared.serializedTransaction,
|
|
524
|
+
this.signer
|
|
525
|
+
);
|
|
526
|
+
assertNotAborted2(options, submitPath);
|
|
527
|
+
const submitted = await this.submitRefund(
|
|
528
|
+
taskId,
|
|
529
|
+
prepared.intentId,
|
|
530
|
+
signedTransaction,
|
|
531
|
+
options
|
|
532
|
+
);
|
|
533
|
+
return {
|
|
534
|
+
...submitted,
|
|
535
|
+
intentId: prepared.intentId,
|
|
536
|
+
lastValidBlockHeight: prepared.lastValidBlockHeight,
|
|
537
|
+
refundQuote: prepared.refundQuote
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
async prepareCreate(input, options) {
|
|
541
|
+
const path = "/v2/int/tasks";
|
|
542
|
+
const body = normalizeCreateTask(input, this.walletAddress);
|
|
543
|
+
const headers = await this.auth({
|
|
544
|
+
operation: "gibwork:create-task-intent",
|
|
545
|
+
method: "POST",
|
|
546
|
+
path,
|
|
547
|
+
hash: ["payloadHash", sha256Json(body)]
|
|
548
|
+
});
|
|
549
|
+
return this.transport.request({
|
|
550
|
+
method: "POST",
|
|
551
|
+
path,
|
|
552
|
+
headers,
|
|
553
|
+
body,
|
|
554
|
+
...options ? { options } : {}
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async submitCreate(intentId, signedTransaction, options) {
|
|
558
|
+
const path = `/v2/int/tasks/${encodeURIComponent(intentId)}/submit`;
|
|
559
|
+
return this.transport.request({
|
|
560
|
+
method: "POST",
|
|
561
|
+
path,
|
|
562
|
+
body: { signedTransaction },
|
|
563
|
+
...options ? { options } : {},
|
|
564
|
+
ambiguousSubmit: {
|
|
565
|
+
operation: "create-task",
|
|
566
|
+
intentId
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
async prepareRefund(taskId, options) {
|
|
571
|
+
const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/refund`;
|
|
572
|
+
const body = { walletAddress: this.walletAddress };
|
|
573
|
+
const headers = await this.auth({
|
|
574
|
+
operation: "gibwork:refund-task-intent",
|
|
575
|
+
method: "POST",
|
|
576
|
+
path,
|
|
577
|
+
resourceFields: [["taskId", taskId]],
|
|
578
|
+
hash: ["payloadHash", sha256Json(body)]
|
|
579
|
+
});
|
|
580
|
+
return this.transport.request({
|
|
581
|
+
method: "POST",
|
|
582
|
+
path,
|
|
583
|
+
headers,
|
|
584
|
+
body,
|
|
585
|
+
...options ? { options } : {}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async submitRefund(taskId, intentId, signedTransaction, options) {
|
|
589
|
+
const path = `/v2/int/tasks/${encodeURIComponent(taskId)}/refund/${encodeURIComponent(intentId)}/submit`;
|
|
590
|
+
return this.transport.request({
|
|
591
|
+
method: "POST",
|
|
592
|
+
path,
|
|
593
|
+
body: { signedTransaction },
|
|
594
|
+
...options ? { options } : {},
|
|
595
|
+
ambiguousSubmit: {
|
|
596
|
+
operation: "refund-task",
|
|
597
|
+
taskId,
|
|
598
|
+
intentId
|
|
599
|
+
}
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
get walletAddress() {
|
|
603
|
+
return this.signer.publicKey.toBase58();
|
|
604
|
+
}
|
|
605
|
+
auth(descriptor) {
|
|
606
|
+
return createWalletAuthHeaders(this.signer, descriptor);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
function assertNotAborted2(options, path) {
|
|
610
|
+
if (options?.signal?.aborted) {
|
|
611
|
+
throw new GibworkRequestAbortedError("POST", path, {
|
|
612
|
+
cause: options.signal.reason
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// src/transport/http-transport.ts
|
|
618
|
+
var HttpTransport = class {
|
|
619
|
+
baseUrl;
|
|
620
|
+
fetchImplementation;
|
|
621
|
+
timeoutMs;
|
|
622
|
+
constructor(options) {
|
|
623
|
+
this.baseUrl = normalizeBaseUrl(options.baseUrl);
|
|
624
|
+
this.fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
625
|
+
this.timeoutMs = options.timeoutMs ?? 6e4;
|
|
626
|
+
if (typeof this.fetchImplementation !== "function") {
|
|
627
|
+
throw new GibworkConfigurationError(
|
|
628
|
+
"A fetch implementation is required in this environment"
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) {
|
|
632
|
+
throw new GibworkConfigurationError(
|
|
633
|
+
"timeoutMs must be a positive number"
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
async request(request) {
|
|
638
|
+
const controller = new AbortController();
|
|
639
|
+
const callerSignal = request.options?.signal;
|
|
640
|
+
let timedOut = false;
|
|
641
|
+
if (callerSignal?.aborted) {
|
|
642
|
+
throw new GibworkRequestAbortedError(request.method, request.path, {
|
|
643
|
+
cause: callerSignal.reason
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const abortFromCaller = () => controller.abort(callerSignal?.reason);
|
|
647
|
+
callerSignal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
648
|
+
const timeout = setTimeout(() => {
|
|
649
|
+
timedOut = true;
|
|
650
|
+
controller.abort();
|
|
651
|
+
}, this.timeoutMs);
|
|
652
|
+
const headers = {
|
|
653
|
+
accept: "application/json",
|
|
654
|
+
...request.headers
|
|
655
|
+
};
|
|
656
|
+
const init = {
|
|
657
|
+
method: request.method,
|
|
658
|
+
headers,
|
|
659
|
+
signal: controller.signal
|
|
660
|
+
};
|
|
661
|
+
if (request.body !== void 0) {
|
|
662
|
+
headers["content-type"] = "application/json";
|
|
663
|
+
init.body = JSON.stringify(request.body);
|
|
664
|
+
}
|
|
665
|
+
let response;
|
|
666
|
+
try {
|
|
667
|
+
response = await this.fetchImplementation(
|
|
668
|
+
`${this.baseUrl}${request.path}`,
|
|
669
|
+
init
|
|
670
|
+
);
|
|
671
|
+
} catch (cause) {
|
|
672
|
+
if (request.ambiguousSubmit) {
|
|
673
|
+
throw new GibworkAmbiguousSubmitError(
|
|
674
|
+
request.method,
|
|
675
|
+
request.path,
|
|
676
|
+
request.ambiguousSubmit,
|
|
677
|
+
{ cause }
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
if (timedOut) {
|
|
681
|
+
throw new GibworkTimeoutError(
|
|
682
|
+
request.method,
|
|
683
|
+
request.path,
|
|
684
|
+
this.timeoutMs,
|
|
685
|
+
{ cause }
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (callerSignal?.aborted) {
|
|
689
|
+
throw new GibworkRequestAbortedError(request.method, request.path, {
|
|
690
|
+
cause
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
throw new GibworkNetworkError(
|
|
694
|
+
"Could not reach the Gibwork API",
|
|
695
|
+
request.method,
|
|
696
|
+
request.path,
|
|
697
|
+
{ cause }
|
|
698
|
+
);
|
|
699
|
+
} finally {
|
|
700
|
+
clearTimeout(timeout);
|
|
701
|
+
callerSignal?.removeEventListener("abort", abortFromCaller);
|
|
702
|
+
}
|
|
703
|
+
const body = await parseResponseBody(response);
|
|
704
|
+
if (!response.ok) {
|
|
705
|
+
const requestId = response.headers.get("x-request-id") ?? void 0;
|
|
706
|
+
const apiError = new GibworkApiError(
|
|
707
|
+
response.status,
|
|
708
|
+
body,
|
|
709
|
+
request.method,
|
|
710
|
+
request.path,
|
|
711
|
+
requestId
|
|
712
|
+
);
|
|
713
|
+
if (request.ambiguousSubmit && response.status >= 500) {
|
|
714
|
+
throw new GibworkAmbiguousSubmitError(
|
|
715
|
+
request.method,
|
|
716
|
+
request.path,
|
|
717
|
+
request.ambiguousSubmit,
|
|
718
|
+
{ cause: apiError }
|
|
719
|
+
);
|
|
720
|
+
}
|
|
721
|
+
throw apiError;
|
|
722
|
+
}
|
|
723
|
+
return body;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
function normalizeBaseUrl(value) {
|
|
727
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
728
|
+
throw new GibworkConfigurationError("baseUrl is required");
|
|
729
|
+
}
|
|
730
|
+
let url;
|
|
731
|
+
try {
|
|
732
|
+
url = new URL(value.trim());
|
|
733
|
+
} catch (cause) {
|
|
734
|
+
throw new GibworkConfigurationError("baseUrl must be a valid URL", {
|
|
735
|
+
cause
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
739
|
+
throw new GibworkConfigurationError("baseUrl must use http or https");
|
|
740
|
+
}
|
|
741
|
+
if (url.search || url.hash) {
|
|
742
|
+
throw new GibworkConfigurationError(
|
|
743
|
+
"baseUrl must not include a query string or fragment"
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
return url.toString().replace(/\/+$/, "");
|
|
747
|
+
}
|
|
748
|
+
async function parseResponseBody(response) {
|
|
749
|
+
const text = await response.text();
|
|
750
|
+
if (!text) return void 0;
|
|
751
|
+
try {
|
|
752
|
+
return JSON.parse(text);
|
|
753
|
+
} catch {
|
|
754
|
+
return text;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/client/gibwork-client.ts
|
|
759
|
+
var GibworkClient = class {
|
|
760
|
+
tasks;
|
|
761
|
+
submissions;
|
|
762
|
+
constructor(options) {
|
|
763
|
+
assertSigner(options.signer);
|
|
764
|
+
const transport = new HttpTransport({
|
|
765
|
+
baseUrl: options.baseUrl ?? DEFAULT_GIBWORK_API_URL,
|
|
766
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
767
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
|
|
768
|
+
});
|
|
769
|
+
this.tasks = new TasksResource(transport, options.signer);
|
|
770
|
+
this.submissions = new SubmissionsResource(transport, options.signer);
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
function assertSigner(signer) {
|
|
774
|
+
if (!signer || typeof signer.publicKey?.toBase58 !== "function" || typeof signer.signMessage !== "function" || typeof signer.signTransaction !== "function") {
|
|
775
|
+
throw new GibworkConfigurationError(
|
|
776
|
+
"signer must provide publicKey, signMessage, and signTransaction"
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function createKeypairSigner(privateKey) {
|
|
781
|
+
const keypair = keypairFromPrivateKey(privateKey);
|
|
782
|
+
return {
|
|
783
|
+
publicKey: keypair.publicKey,
|
|
784
|
+
async signMessage(message) {
|
|
785
|
+
return nacl__default.default.sign.detached(message, keypair.secretKey);
|
|
786
|
+
},
|
|
787
|
+
async signTransaction(transaction) {
|
|
788
|
+
transaction.sign([keypair]);
|
|
789
|
+
return transaction;
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function keypairFromPrivateKey(privateKey) {
|
|
794
|
+
const bytes = decodePrivateKey(privateKey);
|
|
795
|
+
try {
|
|
796
|
+
if (bytes.length === 32) return web3_js.Keypair.fromSeed(bytes);
|
|
797
|
+
if (bytes.length === 64) return web3_js.Keypair.fromSecretKey(bytes);
|
|
798
|
+
} catch {
|
|
799
|
+
throw new GibworkConfigurationError("SOLANA_PRIVATE_KEY is invalid");
|
|
800
|
+
}
|
|
801
|
+
throw new GibworkConfigurationError(
|
|
802
|
+
"SOLANA_PRIVATE_KEY must contain a 32-byte seed or 64-byte secret key"
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
function decodePrivateKey(privateKey) {
|
|
806
|
+
if (privateKey instanceof Uint8Array) return new Uint8Array(privateKey);
|
|
807
|
+
if (Array.isArray(privateKey)) return validateByteArray(privateKey);
|
|
808
|
+
if (typeof privateKey !== "string") {
|
|
809
|
+
throw new GibworkConfigurationError("SOLANA_PRIVATE_KEY is required");
|
|
810
|
+
}
|
|
811
|
+
const value = privateKey.trim();
|
|
812
|
+
if (!value) {
|
|
813
|
+
throw new GibworkConfigurationError("SOLANA_PRIVATE_KEY is required");
|
|
814
|
+
}
|
|
815
|
+
if (value.startsWith("[")) {
|
|
816
|
+
try {
|
|
817
|
+
const parsed = JSON.parse(value);
|
|
818
|
+
if (!Array.isArray(parsed)) throw new Error("Expected a JSON array");
|
|
819
|
+
return validateByteArray(parsed);
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (error instanceof GibworkConfigurationError) throw error;
|
|
822
|
+
throw new GibworkConfigurationError(
|
|
823
|
+
"SOLANA_PRIVATE_KEY must be a valid JSON byte array or base58 string"
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
try {
|
|
828
|
+
return bs58__default.default.decode(value);
|
|
829
|
+
} catch {
|
|
830
|
+
throw new GibworkConfigurationError(
|
|
831
|
+
"SOLANA_PRIVATE_KEY must be a valid JSON byte array or base58 string"
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
function validateByteArray(values) {
|
|
836
|
+
if (!values.every(
|
|
837
|
+
(value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 255
|
|
838
|
+
)) {
|
|
839
|
+
throw new GibworkConfigurationError(
|
|
840
|
+
"SOLANA_PRIVATE_KEY JSON values must be integers between 0 and 255"
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
return Uint8Array.from(values);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// src/node.ts
|
|
847
|
+
function createGibworkClient(options) {
|
|
848
|
+
return new GibworkClient({
|
|
849
|
+
signer: createKeypairSigner(options.privateKey),
|
|
850
|
+
...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
|
|
851
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
852
|
+
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
exports.DEFAULT_GIBWORK_API_URL = DEFAULT_GIBWORK_API_URL;
|
|
857
|
+
exports.GibworkAmbiguousSubmitError = GibworkAmbiguousSubmitError;
|
|
858
|
+
exports.GibworkApiError = GibworkApiError;
|
|
859
|
+
exports.GibworkClient = GibworkClient;
|
|
860
|
+
exports.GibworkConfigurationError = GibworkConfigurationError;
|
|
861
|
+
exports.GibworkError = GibworkError;
|
|
862
|
+
exports.GibworkNetworkError = GibworkNetworkError;
|
|
863
|
+
exports.GibworkRequestAbortedError = GibworkRequestAbortedError;
|
|
864
|
+
exports.GibworkTimeoutError = GibworkTimeoutError;
|
|
865
|
+
exports.createGibworkClient = createGibworkClient;
|
|
866
|
+
exports.createKeypairSigner = createKeypairSigner;
|
|
867
|
+
exports.keypairFromPrivateKey = keypairFromPrivateKey;
|
|
868
|
+
exports.signPreparedTransaction = signPreparedTransaction;
|
|
869
|
+
//# sourceMappingURL=node.cjs.map
|
|
870
|
+
//# sourceMappingURL=node.cjs.map
|