@blindmarket/sdk 0.7.0 → 0.9.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 +130 -0
- package/README.md +101 -8
- package/dist/chain/abi/BlindEscrow.json +154 -0
- package/dist/escrowCalls.d.ts +51 -0
- package/dist/escrowCalls.js +97 -0
- package/dist/executor/WorkerRuntime.d.ts +32 -0
- package/dist/executor/WorkerRuntime.js +120 -0
- package/dist/index.d.ts +354 -5
- package/dist/index.js +821 -159
- package/dist/network/presets.d.ts +1 -1
- package/dist/network/presets.js +1 -1
- package/dist/onchain.d.ts +9 -5
- package/dist/onchain.js +31 -6
- package/dist/posting.d.ts +63 -0
- package/dist/posting.js +168 -0
- package/dist/settlementPins.d.ts +20 -0
- package/dist/settlementPins.js +11 -0
- package/dist/tools/helpers.js +1 -1
- package/dist/types.d.ts +13 -0
- package/package.json +1 -1
|
@@ -62,6 +62,43 @@ class AcceptAbandoned extends Error {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
65
|
+
/**
|
|
66
|
+
* The unit minReward is written in: the pricing token's smallest unit, USDC
|
|
67
|
+
* with 6 decimals (CreateAgentParams.minReward), the backend's pricing unit on
|
|
68
|
+
* every chain it settles on. A reward in any other unit cannot be compared.
|
|
69
|
+
*/
|
|
70
|
+
const MIN_REWARD_UNIT = { symbol: 'USDC', decimals: 6 };
|
|
71
|
+
/**
|
|
72
|
+
* 10^12 base units is 1,000,000 USDC, no plausible floor: the backend reads a
|
|
73
|
+
* floor at or above it as the old 18-decimal units and stores it divided by
|
|
74
|
+
* 10^12, rounded up (normalizeSettlementAmount in
|
|
75
|
+
* backend/src/services/settlementUnits.ts). The runtime applies the floor the
|
|
76
|
+
* backend holds the executor to.
|
|
77
|
+
*/
|
|
78
|
+
const LEGACY_SCALE = 10n ** 12n;
|
|
79
|
+
/** A minReward as the backend holds it, in USDC base units; undefined for no floor (unset, empty, malformed or zero). */
|
|
80
|
+
function rewardFloor(raw) {
|
|
81
|
+
if (typeof raw !== 'string' || !/^\d+$/.test(raw))
|
|
82
|
+
return undefined;
|
|
83
|
+
const value = BigInt(raw);
|
|
84
|
+
const floor = value >= LEGACY_SCALE ? (value + LEGACY_SCALE - 1n) / LEGACY_SCALE : value;
|
|
85
|
+
return floor === 0n ? undefined : floor;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Whether a listing's recorded reward clears `floor`. A missing reward, a
|
|
89
|
+
* malformed one, or one in another unit never does: comparing a floor with
|
|
90
|
+
* something it cannot price would let a 1-base-unit task through.
|
|
91
|
+
*/
|
|
92
|
+
function clearsRewardFloor(meta, floor) {
|
|
93
|
+
const reward = meta?.reward;
|
|
94
|
+
if (!reward || typeof reward !== 'object')
|
|
95
|
+
return false;
|
|
96
|
+
if (reward.unit?.symbol !== MIN_REWARD_UNIT.symbol || reward.unit?.decimals !== MIN_REWARD_UNIT.decimals)
|
|
97
|
+
return false;
|
|
98
|
+
if (typeof reward.amount !== 'string' || !/^\d+$/.test(reward.amount))
|
|
99
|
+
return false;
|
|
100
|
+
return BigInt(reward.amount) >= floor;
|
|
101
|
+
}
|
|
65
102
|
/** base, 2·base, 4·base … capped. `n` is 1 for the first failure. */
|
|
66
103
|
function backoff(base, n, cap) {
|
|
67
104
|
return Math.min(base * 2 ** Math.max(0, n - 1), cap);
|
|
@@ -79,6 +116,10 @@ export class WorkerRuntime {
|
|
|
79
116
|
retries = new Map();
|
|
80
117
|
retryTimers = new Set();
|
|
81
118
|
listeners = new Set();
|
|
119
|
+
/** Set once the runtime has said it skips listings with no recorded reward. */
|
|
120
|
+
warnedNoReward = false;
|
|
121
|
+
/** The chain id each configured RPC answered, by URL, kept once it matched the backend's. */
|
|
122
|
+
rpcChainIds = new Map();
|
|
82
123
|
constructor(config) {
|
|
83
124
|
this.config = { ...DEFAULTS, ...config };
|
|
84
125
|
this.bb = new BlindMarket({ apiKey: config.apiKey, apiBase: config.apiBase });
|
|
@@ -132,6 +173,10 @@ export class WorkerRuntime {
|
|
|
132
173
|
"owner's registered public key and strand every task it accepted. To only look at tasks, call " +
|
|
133
174
|
'BlindMarket.browseA2ATasks() directly.');
|
|
134
175
|
}
|
|
176
|
+
const minReward = this.config.minReward;
|
|
177
|
+
if (minReward !== undefined && minReward !== '' && !/^\d+$/.test(minReward)) {
|
|
178
|
+
throw new Error(`[WorkerRuntime] minReward must be a whole number of the pricing token's smallest unit (USDC has 6 decimals: '1000000' is 1 USDC), not ${JSON.stringify(minReward)}.`);
|
|
179
|
+
}
|
|
135
180
|
if (this.declaredChains.length === 0) {
|
|
136
181
|
throw new Error('[WorkerRuntime] no RPC configured. Set `rpcUrls.arc` (where production posts new tasks), `rpcUrls.base` and/or `rpcUrl` (0G) to the network the backend at ' +
|
|
137
182
|
'`apiBase` settles on. There is no default: submitEvidence is signed on this RPC after the task is already ' +
|
|
@@ -317,6 +362,12 @@ export class WorkerRuntime {
|
|
|
317
362
|
// no chain) is what keeps such tasks out.
|
|
318
363
|
if (entry.meta?.chain && !this.declaredChains.includes(entry.meta.chain))
|
|
319
364
|
continue;
|
|
365
|
+
// Nor a task below this runtime's minReward: the handler run and the
|
|
366
|
+
// submitEvidence gas are the operator's, and a poster can escrow 1
|
|
367
|
+
// base unit. Newer backends also refuse such an /accept (403
|
|
368
|
+
// BELOW_MIN_REWARD); older ones apply the floor only when ranking offers.
|
|
369
|
+
if (!this.meetsFloor(entry.meta))
|
|
370
|
+
continue;
|
|
320
371
|
this.claim(taskId, state, entry.meta);
|
|
321
372
|
}
|
|
322
373
|
// Tasks that may still be held for this executor are not in the open
|
|
@@ -334,6 +385,28 @@ export class WorkerRuntime {
|
|
|
334
385
|
this.emit({ type: 'error', error: `Browse failed: ${err}` });
|
|
335
386
|
}
|
|
336
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* The floor browse applies: `minReward`, else (restore mode) the floor the
|
|
390
|
+
* executor is registered with. Undefined: no floor.
|
|
391
|
+
*/
|
|
392
|
+
get minRewardFloor() {
|
|
393
|
+
const configured = this.config.minReward;
|
|
394
|
+
return rewardFloor(configured !== undefined && configured !== '' ? configured : this.profile?.minReward);
|
|
395
|
+
}
|
|
396
|
+
/** Whether a listing clears the floor (always, without one). */
|
|
397
|
+
meetsFloor(meta) {
|
|
398
|
+
const floor = this.minRewardFloor;
|
|
399
|
+
if (floor === undefined)
|
|
400
|
+
return true;
|
|
401
|
+
if (clearsRewardFloor(meta, floor))
|
|
402
|
+
return true;
|
|
403
|
+
if (meta?.reward === undefined && !this.warnedNoReward) {
|
|
404
|
+
this.warnedNoReward = true;
|
|
405
|
+
console.warn(`[WorkerRuntime] minReward is set (${floor} USDC base units), so tasks listed without a recorded reward are skipped. ` +
|
|
406
|
+
'A backend older than the reward field lists none: unset minReward to take them.');
|
|
407
|
+
}
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
337
410
|
/** Executions holding a concurrency slot. A task waiting for a wrap or backing off holds none. */
|
|
338
411
|
inFlight() {
|
|
339
412
|
let n = 0;
|
|
@@ -371,6 +444,40 @@ export class WorkerRuntime {
|
|
|
371
444
|
return retry;
|
|
372
445
|
}
|
|
373
446
|
// ── Accept ──────────────────────────────────────────────────────────────
|
|
447
|
+
/**
|
|
448
|
+
* Why a task on `chain` must not be accepted, or null: this runtime's RPC
|
|
449
|
+
* for the chain answers another chain id than the one the backend settles
|
|
450
|
+
* it on (GET /health/settlement). A chain keeps its key when the backend
|
|
451
|
+
* moves it to another network (Arc Testnet 5042002, Arc mainnet 5042), and
|
|
452
|
+
* submitEvidence can only be signed where the task is. Nothing is held back
|
|
453
|
+
* when either side cannot be read or the backend does not list the chain:
|
|
454
|
+
* deliverResult() checks the chain again before it signs.
|
|
455
|
+
*/
|
|
456
|
+
async wrongNetwork(chain) {
|
|
457
|
+
const known = SETTLEMENT_CHAINS.find((c) => c === chain);
|
|
458
|
+
const rpc = known ? rpcFor(this.config, known) : undefined;
|
|
459
|
+
if (!rpc)
|
|
460
|
+
return null;
|
|
461
|
+
try {
|
|
462
|
+
const { chains } = await this.bb.getSettlement();
|
|
463
|
+
const listed = chains.find((c) => c.chain === chain)?.chainId;
|
|
464
|
+
if (listed === undefined || !Number.isInteger(listed))
|
|
465
|
+
return null;
|
|
466
|
+
// A match is kept: a URL's network does not change under it. Anything
|
|
467
|
+
// else is asked again next time, so one wrong answer is not kept.
|
|
468
|
+
if (this.rpcChainIds.get(rpc) === BigInt(listed))
|
|
469
|
+
return null;
|
|
470
|
+
const served = (await new ethers.JsonRpcProvider(rpc).getNetwork()).chainId;
|
|
471
|
+
if (served === BigInt(listed)) {
|
|
472
|
+
this.rpcChainIds.set(rpc, served);
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
return `the RPC for ${chain} serves chain ${served}, but the backend settles ${chain} on chain ${listed}, where this runtime could not sign its submitEvidence. Point it at chain ${listed}.`;
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
374
481
|
/**
|
|
375
482
|
* POST /accept, re-trying while the backend says the claim is still ours:
|
|
376
483
|
* 503 ASSIGNMENT_PENDING means the assign tx is broadcast but unconfirmed
|
|
@@ -516,6 +623,19 @@ export class WorkerRuntime {
|
|
|
516
623
|
if (!exec)
|
|
517
624
|
return;
|
|
518
625
|
try {
|
|
626
|
+
// An accept assigns on-chain for good, so not on a network this
|
|
627
|
+
// runtime's RPC is not on. The task is looked at again after a back-off
|
|
628
|
+
// that grows while the answer stays wrong.
|
|
629
|
+
const wrong = await this.wrongNetwork(meta?.chain);
|
|
630
|
+
if (wrong) {
|
|
631
|
+
this.executions.delete(taskId);
|
|
632
|
+
const retry = this.retryState(taskId);
|
|
633
|
+
retry.reaccept = undefined;
|
|
634
|
+
retry.failures++;
|
|
635
|
+
retry.notBefore = Date.now() + backoff(RELEASED_BACKOFF_MS, retry.failures, MAX_BACKOFF_MS);
|
|
636
|
+
this.emit({ type: 'task_failed', taskId, error: `not accepted: ${wrong}` });
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
519
639
|
// Accept task — get the rootHash + this executor's ECIES-wrapped AES
|
|
520
640
|
// key. wrappedKey is a single hex string (this caller's slice), not a
|
|
521
641
|
// Record — see acceptTask()'s doc comment in ../index.ts.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
2
|
import { ApiError } from './apiError.js';
|
|
3
|
+
import { type SettlementPin } from './settlementPins.js';
|
|
3
4
|
import type { Address, Hex, RootHash, HealthStatus, PlatformStats, OpenTask, TaskDetail, CreateTaskTx, ExecutorProfile, RegisterExecutorInput, DeployedAgentInfo, AgentWalletInfo, ReputationInfo, LeaderboardEntry, StorageUploadResult, Message, AgentSearchResult, TaskTemplate, VerifyTaskInput, A2ATaskEntry, AgentCapability, CreateAgentParams, CreateAgentResult, CreateTaskRequest } from './types.js';
|
|
4
5
|
export interface BlindMarketConfig {
|
|
5
6
|
/** Backend API base URL (default: https://api.blindmarket.xyz) */
|
|
@@ -13,6 +14,14 @@ export interface BlindMarketConfig {
|
|
|
13
14
|
* never take a key as an argument. Stays in-process; never sent to the backend.
|
|
14
15
|
*/
|
|
15
16
|
executor?: DeliverSigner;
|
|
17
|
+
/**
|
|
18
|
+
* Escrows postTask() and postTasks() may fund besides the known
|
|
19
|
+
* deployments (SETTLEMENT_PINS: Arc mainnet and Arc Testnet), for a custom
|
|
20
|
+
* or local deployment. Each names its chain id, escrow and settlement
|
|
21
|
+
* token; the backend's /health/settlement must name exactly one of them, or
|
|
22
|
+
* nothing is approved or funded (ESCROW_NOT_PINNED).
|
|
23
|
+
*/
|
|
24
|
+
trustedEscrows?: SettlementPin[];
|
|
16
25
|
}
|
|
17
26
|
export interface DeployAgentParams {
|
|
18
27
|
name: string;
|
|
@@ -141,6 +150,12 @@ export interface PostTaskParams {
|
|
|
141
150
|
targetExecutor?: Address;
|
|
142
151
|
/** Default 'global'. */
|
|
143
152
|
locationZone?: string;
|
|
153
|
+
/**
|
|
154
|
+
* A public one-liner shown on the task board, at most 500 characters. A
|
|
155
|
+
* private task's brief is sealed, so this is what the board says it is
|
|
156
|
+
* about: keep secrets out of it.
|
|
157
|
+
*/
|
|
158
|
+
routingSummary?: string;
|
|
144
159
|
}
|
|
145
160
|
export interface PostTaskOptions {
|
|
146
161
|
/**
|
|
@@ -162,6 +177,8 @@ export interface PostTaskOptions {
|
|
|
162
177
|
*/
|
|
163
178
|
onFunded?: (funding: {
|
|
164
179
|
txHash: string;
|
|
180
|
+
nonce: number;
|
|
181
|
+
raw?: string;
|
|
165
182
|
taskHash: string;
|
|
166
183
|
indexParams: IndexTaskParams;
|
|
167
184
|
}) => void | Promise<void>;
|
|
@@ -200,6 +217,168 @@ export interface IndexTaskParams {
|
|
|
200
217
|
verifierAddress?: Address;
|
|
201
218
|
requiredCapabilities?: AgentCapability[];
|
|
202
219
|
targetExecutor?: Address;
|
|
220
|
+
routingSummary?: string;
|
|
221
|
+
}
|
|
222
|
+
export interface PostTasksOptions {
|
|
223
|
+
/** As postTask(): signs on the posting chain instead of the configured executor, and must be the API key's own wallet. */
|
|
224
|
+
signer?: ethers.Signer;
|
|
225
|
+
/**
|
|
226
|
+
* The most postTasks() will lock in escrow across every row, in the
|
|
227
|
+
* token's smallest unit. Refused with AMOUNT_ABOVE_MAX before anything is sent.
|
|
228
|
+
*/
|
|
229
|
+
maxTotalRaw?: bigint | string;
|
|
230
|
+
/**
|
|
231
|
+
* Tasks per transaction on an escrow that has createTasks (SettlementChainInfo.batchCreate).
|
|
232
|
+
* Default 20, at most the chain's maxBatch and 50. On an escrow without
|
|
233
|
+
* it every task is its own transaction and this is ignored.
|
|
234
|
+
*/
|
|
235
|
+
chunkSize?: number;
|
|
236
|
+
/** Called as each row settles: posted, unlisted, failed or skipped. A throwing callback does not stop the run. */
|
|
237
|
+
onProgress?: (progress: {
|
|
238
|
+
done: number;
|
|
239
|
+
total: number;
|
|
240
|
+
result: PostTasksRowResult;
|
|
241
|
+
}) => void | Promise<void>;
|
|
242
|
+
/**
|
|
243
|
+
* Called the moment a funding transaction is broadcast, once for every row
|
|
244
|
+
* it funds. Persist `indexParams`: if this process dies before the row is
|
|
245
|
+
* listed, indexTask(indexParams) lists it without funding it again, or
|
|
246
|
+
* indexTasks() for rows whose `batch` is true, which share one transaction
|
|
247
|
+
* (the single index route refuses a receipt that funded several tasks).
|
|
248
|
+
*/
|
|
249
|
+
onFunded?: (funding: {
|
|
250
|
+
index: number;
|
|
251
|
+
txHash: string;
|
|
252
|
+
nonce: number;
|
|
253
|
+
raw?: string;
|
|
254
|
+
taskHash: string;
|
|
255
|
+
batch: boolean;
|
|
256
|
+
indexParams: IndexTaskParams;
|
|
257
|
+
}) => void | Promise<void>;
|
|
258
|
+
/** How long to wait for each transaction to confirm. Default 180000 ms. */
|
|
259
|
+
confirmTimeoutMs?: number;
|
|
260
|
+
/**
|
|
261
|
+
* Stops the run before the next row, or the next transaction of rows; a
|
|
262
|
+
* transaction already sent is always seen through to its listing. Rows not
|
|
263
|
+
* started come back 'skipped'.
|
|
264
|
+
*/
|
|
265
|
+
signal?: AbortSignal;
|
|
266
|
+
/**
|
|
267
|
+
* Backoff for a rate limit (429), a 5xx or a network error on a read, an
|
|
268
|
+
* upload, a build or a listing: `attempts` tries in all (default 5), the
|
|
269
|
+
* wait doubling from `baseDelayMs` (default 2000). A funding transaction is
|
|
270
|
+
* never sent twice.
|
|
271
|
+
*/
|
|
272
|
+
retry?: {
|
|
273
|
+
attempts?: number;
|
|
274
|
+
baseDelayMs?: number;
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
/** A row that cannot be posted as it is, from postTasks()' checks before anything is sent (ApiError INVALID_ROWS, `body.errors`). */
|
|
278
|
+
export interface PostTasksRowError {
|
|
279
|
+
/** The row's position in the array passed to postTasks(). */
|
|
280
|
+
index: number;
|
|
281
|
+
code: string;
|
|
282
|
+
message: string;
|
|
283
|
+
}
|
|
284
|
+
/** What happened to one row of postTasks(), by its position in the input. */
|
|
285
|
+
export type PostTasksRowResult =
|
|
286
|
+
/** Funded and listed. */
|
|
287
|
+
{
|
|
288
|
+
index: number;
|
|
289
|
+
status: 'posted';
|
|
290
|
+
task: PostedTask;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Funded (or sent and maybe funded, with code UNCONFIRMED) but not listed.
|
|
294
|
+
* Do not post it again: list it with indexTask(indexParams), or with
|
|
295
|
+
* indexTasks() when `batch` is true, or cancel it for a refund.
|
|
296
|
+
*/
|
|
297
|
+
| {
|
|
298
|
+
index: number;
|
|
299
|
+
status: 'unlisted';
|
|
300
|
+
taskHash: string;
|
|
301
|
+
txHash: string;
|
|
302
|
+
batch: boolean;
|
|
303
|
+
indexParams: IndexTaskParams;
|
|
304
|
+
aesKey?: string;
|
|
305
|
+
error: {
|
|
306
|
+
code?: string;
|
|
307
|
+
message: string;
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Nothing was funded for this row. */
|
|
311
|
+
| {
|
|
312
|
+
index: number;
|
|
313
|
+
status: 'failed';
|
|
314
|
+
error: {
|
|
315
|
+
code?: string;
|
|
316
|
+
message: string;
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
/** Not started: the run was aborted, or stopped at an earlier row. */
|
|
320
|
+
| {
|
|
321
|
+
index: number;
|
|
322
|
+
status: 'skipped';
|
|
323
|
+
reason: string;
|
|
324
|
+
};
|
|
325
|
+
export interface PostTasksResult {
|
|
326
|
+
chain: string;
|
|
327
|
+
chainId: number;
|
|
328
|
+
/** 'batch': createTasks, several tasks per transaction. 'single': one createTask per task. */
|
|
329
|
+
mode: 'batch' | 'single';
|
|
330
|
+
/** One per input row, in input order. */
|
|
331
|
+
results: PostTasksRowResult[];
|
|
332
|
+
posted: number;
|
|
333
|
+
unlisted: number;
|
|
334
|
+
failed: number;
|
|
335
|
+
skipped: number;
|
|
336
|
+
/**
|
|
337
|
+
* Why the run stopped before the last row, when it did: an on-chain or
|
|
338
|
+
* listing failure (so no further escrow is funded behind it), a backend
|
|
339
|
+
* that built the wrong transaction, one that stayed unreachable, or the abort signal.
|
|
340
|
+
*/
|
|
341
|
+
stopped?: {
|
|
342
|
+
index: number;
|
|
343
|
+
code?: string;
|
|
344
|
+
message: string;
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/** POST /api/v1/tasks/batch: several posts built into one createTasks transaction. */
|
|
348
|
+
export interface CreateTasksRequest {
|
|
349
|
+
token: Address;
|
|
350
|
+
tasks: Array<Omit<CreateTaskRequest, 'token'>>;
|
|
351
|
+
}
|
|
352
|
+
export interface CreateTasksTx {
|
|
353
|
+
unsignedTx: {
|
|
354
|
+
to: Address;
|
|
355
|
+
data: Hex;
|
|
356
|
+
value?: string;
|
|
357
|
+
from?: Address;
|
|
358
|
+
};
|
|
359
|
+
chain?: string;
|
|
360
|
+
chainId?: number;
|
|
361
|
+
/** The task hashes the transaction escrows, in order. */
|
|
362
|
+
taskHashes?: string[];
|
|
363
|
+
}
|
|
364
|
+
/** POST /api/v1/a2a/tasks/index-batch: list the tasks one transaction funded. */
|
|
365
|
+
export interface IndexTasksParams {
|
|
366
|
+
txHash: string;
|
|
367
|
+
isUserOp?: boolean;
|
|
368
|
+
tasks: Array<Omit<IndexTaskParams, 'txHash'>>;
|
|
369
|
+
}
|
|
370
|
+
export interface IndexTasksResult {
|
|
371
|
+
results: Array<{
|
|
372
|
+
taskHash: string;
|
|
373
|
+
onChainTaskId?: string;
|
|
374
|
+
indexed: true;
|
|
375
|
+
} | {
|
|
376
|
+
taskHash: string;
|
|
377
|
+
error: {
|
|
378
|
+
code?: string;
|
|
379
|
+
message: string;
|
|
380
|
+
};
|
|
381
|
+
}>;
|
|
203
382
|
}
|
|
204
383
|
/** A refund the client signed and sent: cancelAndRefund() or reclaimAfterTimeout(). */
|
|
205
384
|
export interface RefundResult {
|
|
@@ -208,6 +387,14 @@ export interface RefundResult {
|
|
|
208
387
|
chainId: number;
|
|
209
388
|
/** Whether the backend took the task off the market. False leaves it listed until its deadline; the refund stands either way. */
|
|
210
389
|
listingClosed: boolean;
|
|
390
|
+
/**
|
|
391
|
+
* What the transaction did, when the backend says: 'refund' returned the
|
|
392
|
+
* escrow to the poster; 'escalate' (reclaimAfterTimeout on work delivered
|
|
393
|
+
* before the deadline and never judged) sent the task for review and
|
|
394
|
+
* refunded nothing. An admin rules on it, and with no ruling within 14 days
|
|
395
|
+
* the worker is paid.
|
|
396
|
+
*/
|
|
397
|
+
outcome?: 'refund' | 'escalate';
|
|
211
398
|
}
|
|
212
399
|
export interface RefundOptions {
|
|
213
400
|
/** Signs on the task's chain instead of the configured executor. */
|
|
@@ -231,6 +418,15 @@ export interface SettlementChainInfo {
|
|
|
231
418
|
relayChain?: string | null;
|
|
232
419
|
gasSymbol?: string;
|
|
233
420
|
postable?: boolean;
|
|
421
|
+
/**
|
|
422
|
+
* Whether this chain's escrow has createTasks (several tasks in one
|
|
423
|
+
* transaction), and how many one call takes. Absent from older backends,
|
|
424
|
+
* which is the same as unsupported.
|
|
425
|
+
*/
|
|
426
|
+
batchCreate?: {
|
|
427
|
+
supported: boolean;
|
|
428
|
+
maxBatch: number;
|
|
429
|
+
};
|
|
234
430
|
}
|
|
235
431
|
/** Per-chain RPC URLs for signing `submitEvidence` — a task is escrowed on exactly one chain. */
|
|
236
432
|
export interface DeliverSigner {
|
|
@@ -263,6 +459,7 @@ export declare class BlindMarket {
|
|
|
263
459
|
private apiBase;
|
|
264
460
|
private apiKey;
|
|
265
461
|
private executor?;
|
|
462
|
+
private trustedEscrows;
|
|
266
463
|
constructor(config: BlindMarketConfig);
|
|
267
464
|
/** True when an executor signer was configured (see BlindMarketConfig.executor). */
|
|
268
465
|
get canSign(): boolean;
|
|
@@ -292,12 +489,22 @@ export declare class BlindMarket {
|
|
|
292
489
|
* generateText({ model, tools: tools(bb).vercel });
|
|
293
490
|
* ```
|
|
294
491
|
*/
|
|
492
|
+
/**
|
|
493
|
+
* `timeoutMs` gives up on a request that has not answered (ApiError
|
|
494
|
+
* TIMEOUT). `strictBody` turns a reply that is not JSON (a gateway's error
|
|
495
|
+
* page, such as Cloudflare's 524) into an ApiError carrying its HTTP status,
|
|
496
|
+
* instead of the parser's SyntaxError.
|
|
497
|
+
*/
|
|
295
498
|
private req;
|
|
296
499
|
/** Backend liveness check. */
|
|
297
500
|
health(): Promise<HealthStatus>;
|
|
298
501
|
/** Live platform counts. */
|
|
299
502
|
stats(): Promise<PlatformStats>;
|
|
300
|
-
/**
|
|
503
|
+
/**
|
|
504
|
+
* List open tasks from the legacy 0G TaskRegistry (numeric ids on the 0G
|
|
505
|
+
* escrow). Tasks escrowed on Base or Arc are not in it: browseA2ATasks()
|
|
506
|
+
* lists the work agents can take.
|
|
507
|
+
*/
|
|
301
508
|
listTasks(limit?: number): Promise<OpenTask[]>;
|
|
302
509
|
/** Get full task details (on-chain + A2A state). */
|
|
303
510
|
getTask(id: string): Promise<TaskDetail>;
|
|
@@ -327,11 +534,16 @@ export declare class BlindMarket {
|
|
|
327
534
|
/**
|
|
328
535
|
* Build an unsigned `claimTimeout` transaction (the refund of a task whose
|
|
329
536
|
* deadline passed). reclaimAfterTimeout() builds, signs and sends it for you.
|
|
537
|
+
* `outcome` says what it will do: on work delivered before the deadline and
|
|
538
|
+
* never judged, the escrow sends the task for review ('escalate') instead
|
|
539
|
+
* of refunding it, and `message` explains.
|
|
330
540
|
*/
|
|
331
541
|
claimTimeout(taskId: string, chain?: string): Promise<{
|
|
332
542
|
unsignedTx: object;
|
|
333
543
|
chain?: string;
|
|
334
544
|
chainId?: number;
|
|
545
|
+
outcome?: 'refund' | 'escalate';
|
|
546
|
+
message?: string;
|
|
335
547
|
}>;
|
|
336
548
|
/**
|
|
337
549
|
* Build an unsigned `submitEvidence` transaction.
|
|
@@ -350,6 +562,12 @@ export declare class BlindMarket {
|
|
|
350
562
|
postingChain: string | null;
|
|
351
563
|
chains: SettlementChainInfo[];
|
|
352
564
|
}>;
|
|
565
|
+
/**
|
|
566
|
+
* `chain`'s entry in /health/settlement, with its escrow: every transaction
|
|
567
|
+
* the backend builds for this client to sign must target that escrow.
|
|
568
|
+
* Throws 409 CHAIN_UNKNOWN when the backend lists no escrow for it.
|
|
569
|
+
*/
|
|
570
|
+
private settlementEntry;
|
|
353
571
|
/**
|
|
354
572
|
* Post a task end to end, from the API key's own wallet: encrypt the brief
|
|
355
573
|
* (unless public) and wrap its key to the posting chain's executors, upload
|
|
@@ -359,8 +577,10 @@ export declare class BlindMarket {
|
|
|
359
577
|
* The wallet signs locally, on the backend's posting chain (Arc on
|
|
360
578
|
* production, where gas is paid in USDC). Before anything is sent it checks
|
|
361
579
|
* the signer is the API key's owner, that its RPC is on the posting chain,
|
|
362
|
-
* that the wallet holds the amount, and that the backend built
|
|
363
|
-
*
|
|
580
|
+
* that the wallet holds the amount, and that the backend built exactly this
|
|
581
|
+
* createTask (task hash, token, amount, zone, duration) for the escrow it
|
|
582
|
+
* advertises, with no other value: 409 ESCROW_MISMATCH / TX_MISMATCH
|
|
583
|
+
* otherwise. Only the tx's to and data are signed. The funding hash goes to `onFunded` as soon as
|
|
364
584
|
* it is sent; an error after that carries it as `err.txHash`, and
|
|
365
585
|
* indexTask() lists the funded task without paying again.
|
|
366
586
|
*
|
|
@@ -371,6 +591,109 @@ export declare class BlindMarket {
|
|
|
371
591
|
* );
|
|
372
592
|
*/
|
|
373
593
|
postTask(params: PostTaskParams, opts?: PostTaskOptions): Promise<PostedTask>;
|
|
594
|
+
/**
|
|
595
|
+
* Post many tasks, from the API key's own wallet: postTask() for a list, in
|
|
596
|
+
* as few transactions as the posting chain's escrow allows.
|
|
597
|
+
*
|
|
598
|
+
* Every row is checked and its brief sealed before anything is uploaded or
|
|
599
|
+
* sent: a row the escrow or the backend would refuse throws 400
|
|
600
|
+
* INVALID_ROWS listing every such row (`err.body.errors`), with nothing
|
|
601
|
+
* sent. The wallet must hold the total, and the escrow is approved for it
|
|
602
|
+
* once, just before the first funding transaction.
|
|
603
|
+
*
|
|
604
|
+
* On an escrow with createTasks (SettlementChainInfo.batchCreate) up to
|
|
605
|
+
* `chunkSize` rows share one transaction and one listing call; otherwise
|
|
606
|
+
* each row is its own createTask, as postTask() sends it. The same checks
|
|
607
|
+
* guard every transaction: the backend's build must be exactly these tasks,
|
|
608
|
+
* for this escrow and chain, before a key signs it.
|
|
609
|
+
*
|
|
610
|
+
* A row the backend refuses before funding fails alone and the run goes on.
|
|
611
|
+
* A funding transaction that reverts or cannot be confirmed, a listing that
|
|
612
|
+
* fails, or a backend that builds the wrong transaction or stays
|
|
613
|
+
* unreachable stops the run there (`result.stopped`), so no further escrow
|
|
614
|
+
* is funded behind a problem. Nothing is ever funded twice: a funded row
|
|
615
|
+
* that is not listed comes back 'unlisted' with its `indexParams`.
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* const res = await bb.postTasks(rows, {
|
|
619
|
+
* onFunded: ({ taskHash, indexParams }) => save(taskHash, indexParams),
|
|
620
|
+
* onProgress: ({ done, total }) => console.log(`${done}/${total}`),
|
|
621
|
+
* });
|
|
622
|
+
*/
|
|
623
|
+
postTasks(rows: PostTaskParams[], opts?: PostTasksOptions): Promise<PostTasksResult>;
|
|
624
|
+
/**
|
|
625
|
+
* Build and list several posts at once (POST /api/v1/tasks/batch): one
|
|
626
|
+
* unsigned createTasks transaction for the posting chain's escrow. Only an
|
|
627
|
+
* escrow with createTasks builds it (409 BATCH_UNSUPPORTED otherwise);
|
|
628
|
+
* postTasks() calls it, and checks the transaction before signing.
|
|
629
|
+
*/
|
|
630
|
+
createTasks(params: CreateTasksRequest): Promise<CreateTasksTx>;
|
|
631
|
+
/**
|
|
632
|
+
* List every task one funding transaction created
|
|
633
|
+
* (POST /api/v1/a2a/tasks/index-batch). It works for a transaction that
|
|
634
|
+
* funded one task too. Each task comes back listed, or with its own error;
|
|
635
|
+
* a task the receipt does not hold is NOT_IN_RECEIPT. postTasks() calls it;
|
|
636
|
+
* call it yourself to finish rows it returned 'unlisted' with `batch` true.
|
|
637
|
+
*/
|
|
638
|
+
indexTasks(params: IndexTasksParams): Promise<IndexTasksResult>;
|
|
639
|
+
/**
|
|
640
|
+
* Upload several blobs to 0G Storage in one call
|
|
641
|
+
* (POST /api/v1/storage/upload-batch). Each is base64, as uploadBlob()
|
|
642
|
+
* takes it; the results come back in the same order. All or nothing.
|
|
643
|
+
*/
|
|
644
|
+
uploadBlobs(data: string[]): Promise<Array<{
|
|
645
|
+
rootHash: string;
|
|
646
|
+
txHash?: string;
|
|
647
|
+
}>>;
|
|
648
|
+
/** The posting chain, its escrow and token, and a signer checked to be the API key's own wallet on that chain. */
|
|
649
|
+
private postingContext;
|
|
650
|
+
/** Throws 402 INSUFFICIENT_BALANCE, with nothing sent, when the wallet holds less than `amount` of an ERC-20 settlement token. */
|
|
651
|
+
private assertCovers;
|
|
652
|
+
/** The executors on the posting chain a private brief can be wrapped to. */
|
|
653
|
+
private postingExecutors;
|
|
654
|
+
/**
|
|
655
|
+
* The backend's createTask, checked to be exactly this post before a key
|
|
656
|
+
* signs it: the chain and escrow checked above, the task hash, the token,
|
|
657
|
+
* the amount, the zone and the duration (a verifier commits through
|
|
658
|
+
* createTaskWithVerifier). Only its to and data are signed; the value is
|
|
659
|
+
* the amount computed here.
|
|
660
|
+
*/
|
|
661
|
+
private checkedCreateCall;
|
|
662
|
+
/** The backend's createTasks, checked to be exactly these posts, in this order, for this token, escrow and chain. */
|
|
663
|
+
private checkedCreateTasksCall;
|
|
664
|
+
private postedTask;
|
|
665
|
+
/**
|
|
666
|
+
* The escrow's ERC-20 allowance for every row still to fund, approved once,
|
|
667
|
+
* right before the first funding transaction (after that transaction's
|
|
668
|
+
* build has been checked, as postTask() approves).
|
|
669
|
+
*/
|
|
670
|
+
private approveRemaining;
|
|
671
|
+
/** Every row a transaction funds, told its hash (again with a replacement's hash if the wallet re-priced it). */
|
|
672
|
+
private notifyFunded;
|
|
673
|
+
/** One row as its own createTask, the way postTask() posts it. */
|
|
674
|
+
private postRow;
|
|
675
|
+
/** Several rows in one createTasks transaction, listed with one call. */
|
|
676
|
+
private postChunk;
|
|
677
|
+
/**
|
|
678
|
+
* Store briefs (base64) and return their root hashes, in order. Batched
|
|
679
|
+
* (upload-batch), they go UPLOAD_GROUP to a request, one request after the
|
|
680
|
+
* other; a group that fails transiently (isTransientUpload) is sent again
|
|
681
|
+
* one brief per request, each with the backoff, and a brief already stored
|
|
682
|
+
* comes back at once. Unbatched, each brief is one /storage/upload request
|
|
683
|
+
* with the backoff. A refusal (a 400) or an answer that does not add up
|
|
684
|
+
* fails at once.
|
|
685
|
+
*/
|
|
686
|
+
private uploadBriefs;
|
|
687
|
+
/** One /storage/upload-batch request, checked to answer one root hash per brief. */
|
|
688
|
+
private uploadBatchRequest;
|
|
689
|
+
/** One /storage/upload request, as postTask() stores a brief. */
|
|
690
|
+
private uploadOneRequest;
|
|
691
|
+
/**
|
|
692
|
+
* `fn`, asked again after a rate limit, a 5xx or a network error (and, for
|
|
693
|
+
* a listing, while the backend's RPC has not seen the receipt), or after
|
|
694
|
+
* what `retryable` says is worth another try.
|
|
695
|
+
*/
|
|
696
|
+
private retrying;
|
|
374
697
|
/**
|
|
375
698
|
* List a funded task on the market (`POST /api/v1/a2a/tasks/index`), from
|
|
376
699
|
* its funding transaction. Safe to call again for the same task: the
|
|
@@ -391,16 +714,33 @@ export declare class BlindMarket {
|
|
|
391
714
|
* then takes the task off the market (`POST /tasks/:id/confirm-tx`).
|
|
392
715
|
* `taskId` is the on-chain id (PostedTask.taskId); pass `chain`
|
|
393
716
|
* (PostedTask.chain) too, since ids repeat across chains.
|
|
717
|
+
*
|
|
718
|
+
* Only a zero-value `cancelTask(taskId)` on the escrow /health/settlement
|
|
719
|
+
* lists for the chain is signed (to and data only), and only on the chain
|
|
720
|
+
* you named: 409 ESCROW_MISMATCH, TX_MISMATCH, CHAIN_MISMATCH or
|
|
721
|
+
* CHAIN_UNKNOWN otherwise, with nothing sent. reclaimAfterTimeout() does
|
|
722
|
+
* the same for `claimTimeout(taskId)`.
|
|
394
723
|
*/
|
|
395
724
|
cancelAndRefund(taskId: string, opts?: RefundOptions): Promise<RefundResult>;
|
|
396
|
-
/**
|
|
725
|
+
/**
|
|
726
|
+
* Reclaim the escrow of a task whose deadline passed undelivered
|
|
727
|
+
* (claimTimeout), signed and sent. On work delivered before the deadline
|
|
728
|
+
* and never judged, the escrow sends the task for review instead and
|
|
729
|
+
* refunds nothing: the result's outcome is then 'escalate'.
|
|
730
|
+
*/
|
|
397
731
|
reclaimAfterTimeout(taskId: string, opts?: RefundOptions): Promise<RefundResult>;
|
|
732
|
+
/**
|
|
733
|
+
* Sign the refund the backend built, once it is checked to be exactly
|
|
734
|
+
* `fn(taskId)` on the escrow of the chain it names (the one the caller
|
|
735
|
+
* named, when it named one), with no value. Only its to and data are signed.
|
|
736
|
+
*/
|
|
398
737
|
private sendRefund;
|
|
399
738
|
/**
|
|
400
739
|
* Tell the backend a refund landed (`POST /api/v1/tasks/:id/confirm-tx`),
|
|
401
740
|
* which checks the receipt and takes the task off the market. Without it a
|
|
402
741
|
* refunded task keeps listing as open until its deadline. Best effort: the
|
|
403
|
-
* money has already moved, so a failure here only reports
|
|
742
|
+
* money has already moved, so a failure here only reports it not closed.
|
|
743
|
+
* A claim that sent the task for review closes nothing (escalated).
|
|
404
744
|
*/
|
|
405
745
|
private confirmRefund;
|
|
406
746
|
/** What deploying an agent costs on this backend, and how to pay it. */
|
|
@@ -642,6 +982,13 @@ export declare class BlindMarket {
|
|
|
642
982
|
* unsigned `submitEvidence` on the chain the backend names → `finalize()`.
|
|
643
983
|
* Safe to re-call on a task stranded in 'submitted': INVALID_STATE at submit
|
|
644
984
|
* and NOT_SUBMITTED_ON_CHAIN at finalize both heal through `rebroadcast()`.
|
|
985
|
+
*
|
|
986
|
+
* The executor key signs only a zero-value `submitEvidence(onChainTaskId,
|
|
987
|
+
* evidenceHash)` on the escrow /health/settlement lists for that chain,
|
|
988
|
+
* where (from /submit) evidenceHash is keccak256 of `JSON.stringify(resultData)`,
|
|
989
|
+
* over an RPC checked to serve that chain, and only its to and data.
|
|
990
|
+
* Anything else throws 409 ESCROW_MISMATCH, TX_MISMATCH, CHAIN_MISMATCH or
|
|
991
|
+
* CHAIN_UNKNOWN (or WRONG_CHAIN for the RPC) with nothing sent.
|
|
645
992
|
*/
|
|
646
993
|
deliverResult(taskId: string, resultData: Record<string, unknown>, signerOverride?: DeliverSigner): Promise<Awaited<ReturnType<BlindMarket['finalize']>> & {
|
|
647
994
|
submitTxHash?: string;
|
|
@@ -818,6 +1165,8 @@ export declare class BlindMarket {
|
|
|
818
1165
|
}
|
|
819
1166
|
export { ethers };
|
|
820
1167
|
export { ApiError };
|
|
1168
|
+
export { SETTLEMENT_PINS, isPinnedSettlement } from './settlementPins.js';
|
|
1169
|
+
export type { SettlementPin } from './settlementPins.js';
|
|
821
1170
|
export { tools, createBlindMarketTools, createTaskTools, createAgentManagementTools, createA2ATools, toLangChainTools, toVercelTools, toOpenAITools, toClaudeTools, } from './tools/index.js';
|
|
822
1171
|
export type { Tool, ToolKit, ToolDefinition } from './tools/types.js';
|
|
823
1172
|
export type { BlindMarketTools } from './tools/index.js';
|