@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.
@@ -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
- /** List open tasks (human-readable). */
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 the tx for
363
- * the escrow it advertises. The funding hash goes to `onFunded` as soon as
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
- /** Reclaim the escrow of a task whose deadline passed undelivered (claimTimeout), signed and sent. */
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 false.
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';