@blindmarket/sdk 0.6.4 → 0.8.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,8 @@ 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;
82
121
  constructor(config) {
83
122
  this.config = { ...DEFAULTS, ...config };
84
123
  this.bb = new BlindMarket({ apiKey: config.apiKey, apiBase: config.apiBase });
@@ -132,6 +171,10 @@ export class WorkerRuntime {
132
171
  "owner's registered public key and strand every task it accepted. To only look at tasks, call " +
133
172
  'BlindMarket.browseA2ATasks() directly.');
134
173
  }
174
+ const minReward = this.config.minReward;
175
+ if (minReward !== undefined && minReward !== '' && !/^\d+$/.test(minReward)) {
176
+ 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)}.`);
177
+ }
135
178
  if (this.declaredChains.length === 0) {
136
179
  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
180
  '`apiBase` settles on. There is no default: submitEvidence is signed on this RPC after the task is already ' +
@@ -317,6 +360,12 @@ export class WorkerRuntime {
317
360
  // no chain) is what keeps such tasks out.
318
361
  if (entry.meta?.chain && !this.declaredChains.includes(entry.meta.chain))
319
362
  continue;
363
+ // Nor a task below this runtime's minReward: the handler run and the
364
+ // submitEvidence gas are the operator's, and a poster can escrow 1
365
+ // base unit. Newer backends also refuse such an /accept (403
366
+ // BELOW_MIN_REWARD); older ones apply the floor only when ranking offers.
367
+ if (!this.meetsFloor(entry.meta))
368
+ continue;
320
369
  this.claim(taskId, state, entry.meta);
321
370
  }
322
371
  // Tasks that may still be held for this executor are not in the open
@@ -334,6 +383,28 @@ export class WorkerRuntime {
334
383
  this.emit({ type: 'error', error: `Browse failed: ${err}` });
335
384
  }
336
385
  }
386
+ /**
387
+ * The floor browse applies: `minReward`, else (restore mode) the floor the
388
+ * executor is registered with. Undefined: no floor.
389
+ */
390
+ get minRewardFloor() {
391
+ const configured = this.config.minReward;
392
+ return rewardFloor(configured !== undefined && configured !== '' ? configured : this.profile?.minReward);
393
+ }
394
+ /** Whether a listing clears the floor (always, without one). */
395
+ meetsFloor(meta) {
396
+ const floor = this.minRewardFloor;
397
+ if (floor === undefined)
398
+ return true;
399
+ if (clearsRewardFloor(meta, floor))
400
+ return true;
401
+ if (meta?.reward === undefined && !this.warnedNoReward) {
402
+ this.warnedNoReward = true;
403
+ console.warn(`[WorkerRuntime] minReward is set (${floor} USDC base units), so tasks listed without a recorded reward are skipped. ` +
404
+ 'A backend older than the reward field lists none: unset minReward to take them.');
405
+ }
406
+ return false;
407
+ }
337
408
  /** Executions holding a concurrency slot. A task waiting for a wrap or backing off holds none. */
338
409
  inFlight() {
339
410
  let n = 0;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { ethers } from 'ethers';
2
- import type { Address, Hex, RootHash, HealthStatus, PlatformStats, OpenTask, TaskDetail, CreateTaskTx, ExecutorProfile, RegisterExecutorInput, DeployedAgentInfo, AgentWalletInfo, ReputationInfo, LeaderboardEntry, StorageUploadResult, Message, AgentSearchResult, TaskTemplate, VerifyTaskInput, A2ATaskEntry, CreateAgentParams, CreateAgentResult, CreateTaskRequest } from './types.js';
2
+ import { ApiError } from './apiError.js';
3
+ 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';
3
4
  export interface BlindMarketConfig {
4
5
  /** Backend API base URL (default: https://api.blindmarket.xyz) */
5
6
  apiBase?: string;
@@ -16,14 +17,85 @@ export interface BlindMarketConfig {
16
17
  export interface DeployAgentParams {
17
18
  name: string;
18
19
  instructions: string;
19
- provider: 'openai' | 'anthropic' | 'groq' | 'gemini';
20
+ provider: 'openai' | 'anthropic' | 'groq' | 'gemini' | '0g-compute';
20
21
  model: string;
21
- apiKey: string;
22
- ownerAddress: string;
22
+ /** The model provider's API key. Not needed for '0g-compute', which bills the agent's own wallet. */
23
+ apiKey?: string;
24
+ /** Uncompressed secp256k1 public key, hex without 0x: the agent's private key is encrypted to it. */
23
25
  ownerPublicKey: string;
24
26
  capabilities?: string[];
25
27
  tools?: object[];
28
+ toolSecrets?: Record<string, string>;
29
+ /** Public skills to install at deploy, by slug. */
30
+ skillSlugs?: string[];
31
+ /**
32
+ * An Arc transaction that already paid the deploy fee (see getDeployFee()).
33
+ * deployAgent() then pays nothing and names this payment instead.
34
+ */
35
+ feeTxHash?: string;
36
+ /** @deprecated Ignored: the agent's owner is always the API key's wallet. */
37
+ ownerAddress?: string;
38
+ }
39
+ export interface DeployAgentOptions {
40
+ /**
41
+ * Pay the deploy fee if the backend charges one. Off by default, so
42
+ * deployAgent() never spends unless asked: without it (and without
43
+ * `feeTxHash`) a backend that charges answers DEPLOY_FEE_REQUIRED.
44
+ */
45
+ payFee?: boolean;
46
+ /**
47
+ * Signs the fee payment on the fee's chain instead of the configured
48
+ * executor (BlindMarketConfig.executor, whose rpcUrls must then name that
49
+ * chain). Must be a wallet of the API key's owner: the backend counts a fee
50
+ * from that wallet only.
51
+ */
52
+ payer?: ethers.Signer;
53
+ /**
54
+ * The most deployAgent() will pay, in the fee token's smallest unit (USDC
55
+ * has 6 decimals). Default 1_000_000 (1 USDC, today's fee). A backend that
56
+ * asks for more is refused with DEPLOY_FEE_ABOVE_MAX before anything is paid.
57
+ */
58
+ maxFeeRaw?: bigint | string;
59
+ /**
60
+ * Called with the fee transaction's hash the moment it is broadcast, before
61
+ * any wait. Persist it: if this process dies before the deploy finishes,
62
+ * pass it back as `params.feeTxHash` and nothing is paid twice. Not called
63
+ * for an AgentFactory payment, whose credit the backend keeps for you.
64
+ */
65
+ onFeePaid?: (feeTxHash: string) => void | Promise<void>;
66
+ /** How long to wait between checks while the backend confirms the payment. Default 5000 ms. */
67
+ pollIntervalMs?: number;
68
+ /** How long to wait for a payment to confirm on-chain. Default 180000 ms. */
69
+ confirmTimeoutMs?: number;
70
+ }
71
+ /** What deploying an agent costs, from GET /api/v1/agents/deploy-fee. */
72
+ export type DeployFeeTerms = {
73
+ required: false;
74
+ }
75
+ /**
76
+ * One transfer of `amountRaw` of `token` to `recipient` on chain `chainId`,
77
+ * named as feeTxHash. `factory` is the other way to pay. Backends before
78
+ * the field existed leave `chainId` out; deployAgent() will not pay those.
79
+ */
80
+ | {
81
+ required: true;
82
+ method: 'transfer';
83
+ chain: string;
84
+ chainId?: number;
85
+ token: string;
86
+ recipient: string;
87
+ amountRaw: string;
88
+ decimals: number;
89
+ factory: string | null;
26
90
  }
91
+ /** Pay through AgentFactory.deployAgent(); its event becomes a credit the next deploy spends. */
92
+ | {
93
+ required: true;
94
+ method: 'factory';
95
+ chain: string;
96
+ chainId?: number;
97
+ factory: string | null;
98
+ };
27
99
  export interface DeployedAgent {
28
100
  id: string;
29
101
  name: string;
@@ -31,15 +103,142 @@ export interface DeployedAgent {
31
103
  publicKey: string;
32
104
  inftTokenId?: number;
33
105
  status: string;
106
+ /** False when the agent was created but did not start; start it with startAgent(). */
107
+ started?: boolean;
108
+ /** The transaction that paid the deploy fee, when deployAgent() paid it or was given it. */
109
+ feeTxHash?: string;
110
+ /**
111
+ * True when `feeTxHash` had already paid for this agent, one of yours: a
112
+ * retry after a lost response returns the agent the first call created.
113
+ */
114
+ alreadyDeployed?: boolean;
115
+ }
116
+ export interface PostTaskParams {
117
+ /** The brief. Encrypted here, before it leaves this process, unless `privacy` is 'public'. */
118
+ instructions: string;
119
+ /**
120
+ * The escrow, in the settlement token's smallest unit: USDC has 6
121
+ * decimals, so '2500000' is 2.5 USDC. Paid to the worker (90%) when the
122
+ * result is verified; refundable while no one has taken the task.
123
+ */
124
+ amountRaw: string | bigint;
125
+ /** Seconds until the deadline. Default 86400 (24h). The escrow allows 1 hour to 90 days. */
126
+ durationSeconds?: number;
127
+ /**
128
+ * 'private' (default): the brief is encrypted and its key wrapped to each
129
+ * registered executor on the posting chain. 'public': the brief and the
130
+ * result are plaintext, readable by any agent.
131
+ */
132
+ privacy?: 'private' | 'public';
133
+ /** Default 'auto', with `verificationCriteria` defaulting to `{ min_length: 10, pass_threshold: 60 }`. */
134
+ verificationMode?: 'manual' | 'auto' | 'agent';
135
+ verificationCriteria?: Record<string, unknown>;
136
+ /** The designated verifier, with verificationMode 'agent'. */
137
+ verifierAddress?: Address;
138
+ /** Route to agents with these capabilities first. Empty (default) offers it to every agent. */
139
+ requiredCapabilities?: AgentCapability[];
140
+ /** Only this executor can take the task, and only it gets the brief's key. */
141
+ targetExecutor?: Address;
142
+ /** Default 'global'. */
143
+ locationZone?: string;
144
+ }
145
+ export interface PostTaskOptions {
146
+ /**
147
+ * Signs the escrow funding on the posting chain instead of the configured
148
+ * executor (BlindMarketConfig.executor, whose rpcUrls must name that chain).
149
+ * Must be the API key's owner wallet: the task is posted as that wallet.
150
+ */
151
+ signer?: ethers.Signer;
152
+ /**
153
+ * The most postTask() will lock in escrow, in the token's smallest unit.
154
+ * Refused with AMOUNT_ABOVE_MAX before anything is sent.
155
+ */
156
+ maxAmountRaw?: bigint | string;
157
+ /**
158
+ * Called the moment the funding transaction is broadcast, with its hash and
159
+ * the complete listing body. Persist `indexParams`: if this process dies
160
+ * before the task is listed, indexTask(indexParams) finishes it and the
161
+ * escrow is not funded twice.
162
+ */
163
+ onFunded?: (funding: {
164
+ txHash: string;
165
+ taskHash: string;
166
+ indexParams: IndexTaskParams;
167
+ }) => void | Promise<void>;
168
+ /** How long to wait for each transaction to confirm. Default 180000 ms. */
169
+ confirmTimeoutMs?: number;
170
+ }
171
+ export interface PostedTask {
172
+ /** The task's id on the backend (the brief's sha256 commitment). */
173
+ taskHash: string;
174
+ /** The on-chain task id, for cancelAndRefund() and reclaimAfterTimeout(). */
175
+ taskId?: string;
176
+ /** The transaction that funded the escrow. */
177
+ txHash: string;
178
+ chain: string;
179
+ chainId: number;
180
+ rootHash: string;
181
+ privacy: 'private' | 'public';
182
+ /** How many executors can decrypt the brief. 0 for a public task. */
183
+ wrappedTo: number;
184
+ /**
185
+ * The brief's AES key (hex), for a private task. Keep it to wrap the brief
186
+ * to an executor that registers later; never send it anywhere.
187
+ */
188
+ aesKey?: string;
189
+ }
190
+ /** The body of POST /api/v1/a2a/tasks/index, which lists a funded task on the market. */
191
+ export interface IndexTaskParams {
192
+ txHash: string;
193
+ taskHash: string;
194
+ rootHash?: string;
195
+ wrappedKeys?: Record<string, string>;
196
+ privacy?: 'private' | 'public';
197
+ publicBrief?: string;
198
+ verificationMode?: 'manual' | 'auto' | 'agent';
199
+ verificationCriteria?: Record<string, unknown>;
200
+ verifierAddress?: Address;
201
+ requiredCapabilities?: AgentCapability[];
202
+ targetExecutor?: Address;
203
+ }
204
+ /** A refund the client signed and sent: cancelAndRefund() or reclaimAfterTimeout(). */
205
+ export interface RefundResult {
206
+ txHash: string;
207
+ chain: string;
208
+ chainId: number;
209
+ /** Whether the backend took the task off the market. False leaves it listed until its deadline; the refund stands either way. */
210
+ listingClosed: boolean;
211
+ /**
212
+ * What the transaction did, when the backend says: 'refund' returned the
213
+ * escrow to the poster; 'escalate' (reclaimAfterTimeout on work delivered
214
+ * before the deadline and never judged) sent the task for review and
215
+ * refunded nothing. An admin rules on it, and with no ruling within 14 days
216
+ * the worker is paid.
217
+ */
218
+ outcome?: 'refund' | 'escalate';
219
+ }
220
+ export interface RefundOptions {
221
+ /** Signs on the task's chain instead of the configured executor. */
222
+ signer?: ethers.Signer;
223
+ /** The task's chain (PostedTask.chain). Task ids repeat across chains, so naming it refunds that one. */
224
+ chain?: string;
225
+ confirmTimeoutMs?: number;
34
226
  }
35
- declare class ApiError extends Error {
36
- status: number;
37
- body?: unknown | undefined;
38
- /** Backend error code (e.g. 'NEEDS_WRAP', 'NOT_SUBMITTED_ON_CHAIN'), when the envelope carried one. */
39
- code?: string | undefined;
40
- constructor(status: number, message: string, body?: unknown | undefined,
41
- /** Backend error code (e.g. 'NEEDS_WRAP', 'NOT_SUBMITTED_ON_CHAIN'), when the envelope carried one. */
42
- code?: string | undefined);
227
+ /** One settlement chain, as GET /health/settlement describes it. */
228
+ export interface SettlementChainInfo {
229
+ chain: string;
230
+ chainId: number;
231
+ tier?: string;
232
+ escrowAddress: string | null;
233
+ token: {
234
+ kind: 'native' | 'erc20';
235
+ address: string | null;
236
+ symbol: string;
237
+ decimals: number;
238
+ };
239
+ relayChain?: string | null;
240
+ gasSymbol?: string;
241
+ postable?: boolean;
43
242
  }
44
243
  /** Per-chain RPC URLs for signing `submitEvidence` — a task is escrowed on exactly one chain. */
45
244
  export interface DeliverSigner {
@@ -106,7 +305,11 @@ export declare class BlindMarket {
106
305
  health(): Promise<HealthStatus>;
107
306
  /** Live platform counts. */
108
307
  stats(): Promise<PlatformStats>;
109
- /** List open tasks (human-readable). */
308
+ /**
309
+ * List open tasks from the legacy 0G TaskRegistry (numeric ids on the 0G
310
+ * escrow). Tasks escrowed on Base or Arc are not in it: browseA2ATasks()
311
+ * lists the work agents can take.
312
+ */
110
313
  listTasks(limit?: number): Promise<OpenTask[]>;
111
314
  /** Get full task details (on-chain + A2A state). */
112
315
  getTask(id: string): Promise<TaskDetail>;
@@ -124,16 +327,28 @@ export declare class BlindMarket {
124
327
  unsignedTx: object;
125
328
  }>;
126
329
  /**
127
- * Build an unsigned `cancelTask` transaction.
330
+ * Build an unsigned `cancelTask` transaction (the refund of a task no one
331
+ * has taken). `chain`/`chainId` name where to send it. cancelAndRefund()
332
+ * builds, signs and sends it for you.
128
333
  */
129
- cancelTask(taskId: string): Promise<{
334
+ cancelTask(taskId: string, chain?: string): Promise<{
130
335
  unsignedTx: object;
336
+ chain?: string;
337
+ chainId?: number;
131
338
  }>;
132
339
  /**
133
- * Build an unsigned `claimTimeout` transaction.
340
+ * Build an unsigned `claimTimeout` transaction (the refund of a task whose
341
+ * deadline passed). reclaimAfterTimeout() builds, signs and sends it for you.
342
+ * `outcome` says what it will do: on work delivered before the deadline and
343
+ * never judged, the escrow sends the task for review ('escalate') instead
344
+ * of refunding it, and `message` explains.
134
345
  */
135
- claimTimeout(taskId: string): Promise<{
346
+ claimTimeout(taskId: string, chain?: string): Promise<{
136
347
  unsignedTx: object;
348
+ chain?: string;
349
+ chainId?: number;
350
+ outcome?: 'refund' | 'escalate';
351
+ message?: string;
137
352
  }>;
138
353
  /**
139
354
  * Build an unsigned `submitEvidence` transaction.
@@ -145,8 +360,115 @@ export declare class BlindMarket {
145
360
  unsignedTx: object;
146
361
  }>;
147
362
  /**
148
- * Deploy a new agent. The backend generates a wallet, mints an INFT,
149
- * and returns the agent descriptor.
363
+ * Where new tasks are posted and what each chain settles in
364
+ * (`GET /health/settlement`): no auth, no RPC reads on the backend.
365
+ */
366
+ getSettlement(): Promise<{
367
+ postingChain: string | null;
368
+ chains: SettlementChainInfo[];
369
+ }>;
370
+ /**
371
+ * `chain`'s entry in /health/settlement, with its escrow: every transaction
372
+ * the backend builds for this client to sign must target that escrow.
373
+ * Throws 409 CHAIN_UNKNOWN when the backend lists no escrow for it.
374
+ */
375
+ private settlementEntry;
376
+ /**
377
+ * Post a task end to end, from the API key's own wallet: encrypt the brief
378
+ * (unless public) and wrap its key to the posting chain's executors, upload
379
+ * it, build createTask, approve the escrow for the amount when the token is
380
+ * an ERC-20, fund the escrow, and list the task (`POST /a2a/tasks/index`).
381
+ *
382
+ * The wallet signs locally, on the backend's posting chain (Arc on
383
+ * production, where gas is paid in USDC). Before anything is sent it checks
384
+ * the signer is the API key's owner, that its RPC is on the posting chain,
385
+ * that the wallet holds the amount, and that the backend built exactly this
386
+ * createTask (task hash, token, amount, zone, duration) for the escrow it
387
+ * advertises, with no other value: 409 ESCROW_MISMATCH / TX_MISMATCH
388
+ * otherwise. Only the tx's to and data are signed. The funding hash goes to `onFunded` as soon as
389
+ * it is sent; an error after that carries it as `err.txHash`, and
390
+ * indexTask() lists the funded task without paying again.
391
+ *
392
+ * @example
393
+ * const task = await bb.postTask(
394
+ * { instructions: 'Summarise this paper in 5 bullets: …', amountRaw: '2000000' }, // 2 USDC
395
+ * { onFunded: ({ txHash }) => saveSomewhere(txHash) },
396
+ * );
397
+ */
398
+ postTask(params: PostTaskParams, opts?: PostTaskOptions): Promise<PostedTask>;
399
+ /**
400
+ * List a funded task on the market (`POST /api/v1/a2a/tasks/index`), from
401
+ * its funding transaction. Safe to call again for the same task: the
402
+ * backend merges a repeat from the same poster. postTask() calls it; call
403
+ * it yourself to finish a post whose funding confirmed but whose listing
404
+ * failed (the error's `body.indexParams` holds the fields).
405
+ */
406
+ indexTask(params: IndexTaskParams): Promise<{
407
+ taskHash: string;
408
+ onChainTaskId?: string;
409
+ indexed: boolean;
410
+ }>;
411
+ /** indexTask(), asking again while the backend's RPC has not seen the receipt or the backend is briefly down. */
412
+ private indexTaskPatiently;
413
+ /**
414
+ * Cancel a task no one has taken and get its escrow back: builds
415
+ * cancelTask, checks the signer is on the task's chain, signs and sends it,
416
+ * then takes the task off the market (`POST /tasks/:id/confirm-tx`).
417
+ * `taskId` is the on-chain id (PostedTask.taskId); pass `chain`
418
+ * (PostedTask.chain) too, since ids repeat across chains.
419
+ *
420
+ * Only a zero-value `cancelTask(taskId)` on the escrow /health/settlement
421
+ * lists for the chain is signed (to and data only), and only on the chain
422
+ * you named: 409 ESCROW_MISMATCH, TX_MISMATCH, CHAIN_MISMATCH or
423
+ * CHAIN_UNKNOWN otherwise, with nothing sent. reclaimAfterTimeout() does
424
+ * the same for `claimTimeout(taskId)`.
425
+ */
426
+ cancelAndRefund(taskId: string, opts?: RefundOptions): Promise<RefundResult>;
427
+ /**
428
+ * Reclaim the escrow of a task whose deadline passed undelivered
429
+ * (claimTimeout), signed and sent. On work delivered before the deadline
430
+ * and never judged, the escrow sends the task for review instead and
431
+ * refunds nothing: the result's outcome is then 'escalate'.
432
+ */
433
+ reclaimAfterTimeout(taskId: string, opts?: RefundOptions): Promise<RefundResult>;
434
+ /**
435
+ * Sign the refund the backend built, once it is checked to be exactly
436
+ * `fn(taskId)` on the escrow of the chain it names (the one the caller
437
+ * named, when it named one), with no value. Only its to and data are signed.
438
+ */
439
+ private sendRefund;
440
+ /**
441
+ * Tell the backend a refund landed (`POST /api/v1/tasks/:id/confirm-tx`),
442
+ * which checks the receipt and takes the task off the market. Without it a
443
+ * refunded task keeps listing as open until its deadline. Best effort: the
444
+ * money has already moved, so a failure here only reports it not closed.
445
+ * A claim that sent the task for review closes nothing (escalated).
446
+ */
447
+ private confirmRefund;
448
+ /** What deploying an agent costs on this backend, and how to pay it. */
449
+ getDeployFee(): Promise<DeployFeeTerms>;
450
+ /**
451
+ * Run every check POST /deploy makes before it takes a fee, with nothing
452
+ * paid or saved. Throws the same ApiError the deploy would (400 with field
453
+ * errors, 404 SKILL_NOT_FOUND, 400 INVALID_OWNER_PUBLIC_KEY). Returns false
454
+ * when the backend predates the check and nothing could be checked.
455
+ */
456
+ validateDeploy(params: DeployAgentParams): Promise<boolean>;
457
+ /**
458
+ * Deploy a new hosted agent. The backend generates its wallet, mints an
459
+ * INFT, starts it, and returns the agent descriptor.
460
+ *
461
+ * Deploying costs a fee (1 USDC on Arc on production; getDeployFee() says).
462
+ * An unspent AgentFactory credit pays first. Otherwise deployAgent() pays
463
+ * only with `{ payFee: true }`, from the configured executor wallet (set
464
+ * `rpcUrls.arc`) or `payer`, which must be the API key's owner. Before it
465
+ * pays it checks the payer's chain, the fee against `maxFeeRaw`, and the
466
+ * request itself, so nothing is paid for a deploy that would be refused.
467
+ *
468
+ * The fee's hash goes to `onFeePaid` as soon as it is sent, and onto any
469
+ * error after that (`err.feeTxHash`): retry with `params.feeTxHash` set to
470
+ * it and nothing is paid twice. A retry whose payment already created one
471
+ * of your agents returns that agent, with `alreadyDeployed: true`.
150
472
  *
151
473
  * @example
152
474
  * const agent = await bb.deployAgent({
@@ -155,12 +477,26 @@ export declare class BlindMarket {
155
477
  * provider: 'anthropic',
156
478
  * model: 'claude-sonnet-4-5',
157
479
  * apiKey: process.env.ANTHROPIC_API_KEY!,
158
- * ownerAddress: wallet.address,
159
480
  * // Uncompressed, no 0x (`wallet` is an ethers Wallet; its `publicKey` is compressed).
160
481
  * ownerPublicKey: wallet.signingKey.publicKey.slice(2),
161
- * });
482
+ * }, { payFee: true, onFeePaid: (hash) => saveSomewhere(hash) });
162
483
  */
163
- deployAgent(params: DeployAgentParams): Promise<DeployedAgent>;
484
+ deployAgent(params: DeployAgentParams, opts?: DeployAgentOptions): Promise<DeployedAgent>;
485
+ /** Deploy with a fee transaction already paid: wait for the backend to see it, and make a retry safe. */
486
+ private deployWithFee;
487
+ /** `agentId` as a DeployedAgent, when the API key's owner owns it; else null. */
488
+ private ownAgent;
489
+ /**
490
+ * An error after the fee was paid, carrying the payment: `feeTxHash` on the
491
+ * error and in its body, and the message says how to reuse it. The
492
+ * backend's code, status and envelope are kept.
493
+ */
494
+ private withFee;
495
+ private assertFeeCeiling;
496
+ /** POST /agents/deploy, asking again while the backend answers one of `retryCodes`. */
497
+ private postDeploy;
498
+ /** The configured executor as a signer on `chain`. */
499
+ private signerOn;
164
500
  /**
165
501
  * One-shot executor registration in the A2A marketplace.
166
502
  *
@@ -204,6 +540,15 @@ export declare class BlindMarket {
204
540
  * route (404 / a non-JSON 404 page) and nothing could be checked.
205
541
  */
206
542
  private assertOwnerKey;
543
+ /**
544
+ * Before a spend: throw 409 OWNER_MISMATCH unless `address` is a wallet the
545
+ * backend will credit the spend to. `exact` needs the API key's own address
546
+ * (a task is posted as that wallet); otherwise any wallet linked to it
547
+ * counts, as the deploy fee check does. Unlike assertOwnerKey this fails
548
+ * closed: money never moves on an unchecked wallet.
549
+ */
550
+ private assertSpender;
551
+ private assertFeePayer;
207
552
  /** List deployed agents, optionally filtered by owner address. */
208
553
  listAgents(ownerAddress?: string): Promise<DeployedAgentInfo[]>;
209
554
  /** Get a single deployed agent by ID. */
@@ -339,10 +684,34 @@ export declare class BlindMarket {
339
684
  * unsigned `submitEvidence` on the chain the backend names → `finalize()`.
340
685
  * Safe to re-call on a task stranded in 'submitted': INVALID_STATE at submit
341
686
  * and NOT_SUBMITTED_ON_CHAIN at finalize both heal through `rebroadcast()`.
687
+ *
688
+ * The executor key signs only a zero-value `submitEvidence(onChainTaskId,
689
+ * evidenceHash)` on the escrow /health/settlement lists for that chain,
690
+ * where (from /submit) evidenceHash is keccak256 of `JSON.stringify(resultData)`,
691
+ * over an RPC checked to serve that chain, and only its to and data.
692
+ * Anything else throws 409 ESCROW_MISMATCH, TX_MISMATCH, CHAIN_MISMATCH or
693
+ * CHAIN_UNKNOWN (or WRONG_CHAIN for the RPC) with nothing sent.
342
694
  */
343
695
  deliverResult(taskId: string, resultData: Record<string, unknown>, signerOverride?: DeliverSigner): Promise<Awaited<ReturnType<BlindMarket['finalize']>> & {
344
696
  submitTxHash?: string;
345
697
  }>;
698
+ /**
699
+ * Approve or reject the delivered result of a task you posted with
700
+ * `verificationMode: 'manual'` (`POST /api/v1/a2a/tasks/:hash/verify`).
701
+ * Approving settles the escrow to the worker (90%); rejecting fails the
702
+ * round, and the worker may resubmit before the deadline. Only the poster
703
+ * can review, and only once the task is `submitted`.
704
+ */
705
+ reviewResult(taskHash: string, review: {
706
+ passed: boolean;
707
+ reasons?: string[];
708
+ }): Promise<{
709
+ status?: string;
710
+ verificationResult?: {
711
+ passed: boolean;
712
+ reasons?: string[];
713
+ };
714
+ }>;
346
715
  /** Get tasks posted by the authenticated user. */
347
716
  getPostedTasks(): Promise<{
348
717
  tasks: A2ATaskEntry[];
@@ -378,8 +747,12 @@ export declare class BlindMarket {
378
747
  getReputation(address: Address): Promise<ReputationInfo>;
379
748
  /** Get top workers by decayed score. */
380
749
  getLeaderboard(limit?: number): Promise<LeaderboardEntry[]>;
381
- /** Upload an encrypted blob to 0G Storage. */
382
- uploadBlob(data: Hex): Promise<StorageUploadResult>;
750
+ /**
751
+ * Upload a blob to 0G Storage. `data` is the bytes as **base64**: the
752
+ * backend base64-decodes it. (The type once said Hex; a hex string sent
753
+ * here uploads the wrong bytes.)
754
+ */
755
+ uploadBlob(data: string): Promise<StorageUploadResult>;
383
756
  /**
384
757
  * Download a blob by root hash. `blob` is base64-encoded raw bytes — the
385
758
  * caller decodes (and, for encrypted tasks, decrypts) it client-side.