@blindmarket/sdk 0.6.2 → 0.6.4
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 +33 -0
- package/README.md +16 -14
- package/dist/executor/WorkerRuntime.d.ts +11 -7
- package/dist/executor/WorkerRuntime.js +25 -15
- package/dist/tools/helpers.js +1 -1
- package/dist/types.d.ts +13 -8
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,39 @@
|
|
|
3
3
|
This package is 0.x: a minor version may contain breaking changes. They are
|
|
4
4
|
listed here with how to migrate.
|
|
5
5
|
|
|
6
|
+
## 0.6.4
|
|
7
|
+
|
|
8
|
+
### Fixes
|
|
9
|
+
|
|
10
|
+
**`WorkerRuntime` can take tasks on Arc.** Production posts new tasks on Arc
|
|
11
|
+
(Arc Testnet, chain 5042002) since backend #73, but `SETTLEMENT_CHAINS` was
|
|
12
|
+
`['0g', 'base']`, so a runtime never declared Arc and threw on any task whose
|
|
13
|
+
`chain` was `'arc'`. `SETTLEMENT_CHAINS` is now `['0g', 'base', 'arc']` and
|
|
14
|
+
`A2APublicTaskMeta.chain` includes `'arc'`. To claim Arc tasks set
|
|
15
|
+
`rpcUrls.arc`; a runtime without it keeps declaring only the chains it has an
|
|
16
|
+
RPC for, and says so at start. The README examples and the "no RPC
|
|
17
|
+
configured" error now name `rpcUrls.arc` first.
|
|
18
|
+
|
|
19
|
+
## 0.6.3
|
|
20
|
+
|
|
21
|
+
### Fixes
|
|
22
|
+
|
|
23
|
+
**A scheduled NEEDS_WRAP re-try is no longer refused by its own back-off.**
|
|
24
|
+
`WorkerRuntime` re-tries a task that is waiting for its key to be wrapped on a
|
|
25
|
+
timer. A timer can fire up to a millisecond before `Date.now()` reaches the
|
|
26
|
+
back-off it was set for, and the re-try then counted as too early: nothing
|
|
27
|
+
re-tried the task until the next browse (by default up to 15 s later). Nothing was lost,
|
|
28
|
+
it was only late. No API change.
|
|
29
|
+
|
|
30
|
+
### Documentation
|
|
31
|
+
|
|
32
|
+
The `supportedChains` doc comments (`RegisterExecutorInput`,
|
|
33
|
+
`ExecutorProfile`, `WorkerRuntime`, the `register_as_executor` tool, README) now
|
|
34
|
+
say what newer backends do with the list: they leave the executor out of
|
|
35
|
+
offers and refuse bids and `/accept` (409 `CHAIN_UNSUPPORTED`) for tasks on
|
|
36
|
+
chains it did not declare. Older backends only store it. No backend filters
|
|
37
|
+
browse results by it, so `WorkerRuntime` still checks a task's chain itself.
|
|
38
|
+
|
|
6
39
|
## 0.6.0
|
|
7
40
|
|
|
8
41
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ const bb = new BlindMarket({
|
|
|
197
197
|
apiKey,
|
|
198
198
|
// Optional: the API key owner's wallet + an RPC per chain your tasks settle
|
|
199
199
|
// on. Enables deliverResult() and the submit_result tool.
|
|
200
|
-
executor: { privateKey, rpcUrls: { base: 'https://sepolia.base.org' } },
|
|
200
|
+
executor: { privateKey, rpcUrls: { arc: 'https://rpc.testnet.arc.io', base: 'https://sepolia.base.org' } },
|
|
201
201
|
});
|
|
202
202
|
|
|
203
203
|
// Register as an executor. The executor ADDRESS is always the API key's owner
|
|
@@ -208,10 +208,11 @@ await bb.registerExecutor({
|
|
|
208
208
|
capabilities: ['data_processing', 'web_research'],
|
|
209
209
|
// Uncompressed, no 0x. `wallet.publicKey` is the compressed key, which is rejected.
|
|
210
210
|
publicKey: wallet.signingKey.publicKey.slice(2),
|
|
211
|
-
// Chains you can sign submitEvidence on (optional).
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
211
|
+
// Chains you can sign submitEvidence on (optional). Older backends only
|
|
212
|
+
// store it; newer ones also leave you out of offers and refuse /accept
|
|
213
|
+
// (409 CHAIN_UNSUPPORTED) on other chains. Neither filters browse results,
|
|
214
|
+
// so check entry.meta.chain before accepting (WorkerRuntime does).
|
|
215
|
+
supportedChains: ['arc', 'base'],
|
|
215
216
|
});
|
|
216
217
|
|
|
217
218
|
// Browse available tasks — entries are { meta, state }
|
|
@@ -265,16 +266,16 @@ const runtime = new WorkerRuntime({
|
|
|
265
266
|
// the owner's.
|
|
266
267
|
privateKey: process.env.EXECUTOR_PRIVATE_KEY!,
|
|
267
268
|
// REQUIRED: at least one RPC, on the network your `apiBase` settles on.
|
|
268
|
-
// There is NO default.
|
|
269
|
-
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
rpcUrls: { base: process.env.BASE_RPC_URL! },
|
|
269
|
+
// There is NO default. Production posts new tasks on Arc (Arc Testnet,
|
|
270
|
+
// https://rpc.testnet.arc.io); without `rpcUrls.arc` the runtime skips them.
|
|
271
|
+
// `base` covers older Base Sepolia tasks. `rpcUrl` is the 0G RPC only and
|
|
272
|
+
// never stands in for another chain.
|
|
273
|
+
rpcUrls: { arc: process.env.ARC_RPC_URL!, base: process.env.BASE_RPC_URL! },
|
|
273
274
|
executeTask: async ({ instructions }) => ({ output: await doTheWork(instructions) }),
|
|
274
275
|
});
|
|
275
276
|
|
|
276
277
|
await runtime.start(); // warns if a chain the SDK supports has no RPC configured
|
|
277
|
-
console.log(runtime.declaredChains); // ['
|
|
278
|
+
console.log(runtime.declaredChains); // ['base', 'arc']
|
|
278
279
|
```
|
|
279
280
|
|
|
280
281
|
**Key and RPC are mandatory.** Up to 0.5.x a runtime with no key registered a
|
|
@@ -295,9 +296,10 @@ cross-check; `existingPublicKey` is ignored (derived from the key).
|
|
|
295
296
|
|
|
296
297
|
**What keeps the runtime off a chain it cannot settle.** A task is escrowed on
|
|
297
298
|
exactly one chain and `submitEvidence` must be signed there. The runtime
|
|
298
|
-
registers the chains it has an RPC for as `supportedChains
|
|
299
|
-
|
|
300
|
-
|
|
299
|
+
registers the chains it has an RPC for as `supportedChains`. Older backends
|
|
300
|
+
only store it; newer ones also keep other chains' tasks out of its offers and
|
|
301
|
+
refuse its `/accept` on them (409 `CHAIN_UNSUPPORTED`), but no backend filters
|
|
302
|
+
browse results by it. So the runtime enforces it itself, on every backend:
|
|
301
303
|
browse skips entries whose `meta.chain` it did not declare, and after `/accept`
|
|
302
304
|
it fails the task before running your handler if the response names a chain it
|
|
303
305
|
has no RPC for (that task is already assigned — this only covers rows with no
|
|
@@ -52,7 +52,7 @@ export interface WorkerRuntimeConfig {
|
|
|
52
52
|
* DEFAULT (0.5.x defaulted to 0G testnet while `apiBase` defaults to
|
|
53
53
|
* production, so a default runtime accepted mainnet tasks and failed the
|
|
54
54
|
* chainId pin after assignment). It is 0G ONLY: it never stands in for
|
|
55
|
-
* another chain. For Base set `rpcUrls.base`. Must be the same network the
|
|
55
|
+
* another chain. For Base set `rpcUrls.base`, for Arc `rpcUrls.arc`. Must be the same network the
|
|
56
56
|
* backend at `apiBase` settles on.
|
|
57
57
|
*/
|
|
58
58
|
rpcUrl?: string;
|
|
@@ -72,7 +72,7 @@ export interface WorkerRuntimeConfig {
|
|
|
72
72
|
* Chains this runtime's CODE can sign submitEvidence on. What it registers as
|
|
73
73
|
* its `supportedChains` is the subset it also has an RPC for (declaredChains).
|
|
74
74
|
*/
|
|
75
|
-
export declare const SETTLEMENT_CHAINS: readonly ["0g", "base"];
|
|
75
|
+
export declare const SETTLEMENT_CHAINS: readonly ["0g", "base", "arc"];
|
|
76
76
|
export type SettlementChain = (typeof SETTLEMENT_CHAINS)[number];
|
|
77
77
|
export type ExecuteTaskHandler = (ctx: TaskContext) => Promise<Record<string, unknown>>;
|
|
78
78
|
export interface TaskContext {
|
|
@@ -160,8 +160,9 @@ export declare class WorkerRuntime {
|
|
|
160
160
|
get activeExecutions(): TaskExecutionInfo[];
|
|
161
161
|
/**
|
|
162
162
|
* The chains this runtime declares to the backend and claims tasks on:
|
|
163
|
-
* those its code can sign for AND it has an RPC for.
|
|
164
|
-
* list
|
|
163
|
+
* those its code can sign for AND it has an RPC for. Older backends only
|
|
164
|
+
* store the list and none filters browse results by it, so browse() and
|
|
165
|
+
* executeTask() enforce it.
|
|
165
166
|
*/
|
|
166
167
|
get declaredChains(): SettlementChain[];
|
|
167
168
|
/** Get own executor profile (available after start). */
|
|
@@ -180,8 +181,8 @@ export declare class WorkerRuntime {
|
|
|
180
181
|
* chain this runtime has no RPC for — it would be offered, accept and
|
|
181
182
|
* strand those tasks. A stored list that is a SUBSET of what the runtime
|
|
182
183
|
* can settle is left alone: an operator who registered ['base'] through
|
|
183
|
-
* the MCP or PATCH meant it.
|
|
184
|
-
*
|
|
184
|
+
* the MCP or PATCH meant it. Older backends only store the list (newer ones
|
|
185
|
+
* also filter offers and /accept by it); this runtime's own browse filter
|
|
185
186
|
* uses `declaredChains`, not the stored list — and a restore never
|
|
186
187
|
* registers otherwise, so an executor first registered by an older SDK
|
|
187
188
|
* would keep its old list.
|
|
@@ -210,7 +211,10 @@ export declare class WorkerRuntime {
|
|
|
210
211
|
private browse;
|
|
211
212
|
/** Executions holding a concurrency slot. A task waiting for a wrap or backing off holds none. */
|
|
212
213
|
private inFlight;
|
|
213
|
-
/**
|
|
214
|
+
/**
|
|
215
|
+
* Start executing `taskId` if it is not running, not backing off, and a slot
|
|
216
|
+
* is free. `dueAt`: the time a scheduled re-try counts as running at.
|
|
217
|
+
*/
|
|
214
218
|
private claim;
|
|
215
219
|
private retryState;
|
|
216
220
|
/**
|
|
@@ -5,7 +5,7 @@ import { eciesDecrypt, aesDecrypt, derivePublicKey } from '../crypto/index.js';
|
|
|
5
5
|
* Chains this runtime's CODE can sign submitEvidence on. What it registers as
|
|
6
6
|
* its `supportedChains` is the subset it also has an RPC for (declaredChains).
|
|
7
7
|
*/
|
|
8
|
-
export const SETTLEMENT_CHAINS = ['0g', 'base'];
|
|
8
|
+
export const SETTLEMENT_CHAINS = ['0g', 'base', 'arc'];
|
|
9
9
|
/**
|
|
10
10
|
* The chain the backend named for a task. Missing means 0G (backends older
|
|
11
11
|
* than the field). Any other value throws: signing it on the 0G RPC would
|
|
@@ -16,7 +16,7 @@ function settlementChain(taskId, reported) {
|
|
|
16
16
|
return '0g';
|
|
17
17
|
const known = SETTLEMENT_CHAINS.find((c) => c === reported);
|
|
18
18
|
if (!known) {
|
|
19
|
-
throw new Error(`task ${taskId} settles on "${reported}", which this runtime cannot sign for (it signs on ${SETTLEMENT_CHAINS.join('
|
|
19
|
+
throw new Error(`task ${taskId} settles on "${reported}", which this runtime cannot sign for (it signs on ${SETTLEMENT_CHAINS.join(', ')}) — update @blindmarket/sdk`);
|
|
20
20
|
}
|
|
21
21
|
return known;
|
|
22
22
|
}
|
|
@@ -95,8 +95,9 @@ export class WorkerRuntime {
|
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* The chains this runtime declares to the backend and claims tasks on:
|
|
98
|
-
* those its code can sign for AND it has an RPC for.
|
|
99
|
-
* list
|
|
98
|
+
* those its code can sign for AND it has an RPC for. Older backends only
|
|
99
|
+
* store the list and none filters browse results by it, so browse() and
|
|
100
|
+
* executeTask() enforce it.
|
|
100
101
|
*/
|
|
101
102
|
get declaredChains() {
|
|
102
103
|
return SETTLEMENT_CHAINS.filter((chain) => !!rpcFor(this.config, chain));
|
|
@@ -132,7 +133,7 @@ export class WorkerRuntime {
|
|
|
132
133
|
'BlindMarket.browseA2ATasks() directly.');
|
|
133
134
|
}
|
|
134
135
|
if (this.declaredChains.length === 0) {
|
|
135
|
-
throw new Error('[WorkerRuntime] no RPC configured. Set `
|
|
136
|
+
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 ' +
|
|
136
137
|
'`apiBase` settles on. There is no default: submitEvidence is signed on this RPC after the task is already ' +
|
|
137
138
|
'assigned, so a guessed network strands it.');
|
|
138
139
|
}
|
|
@@ -143,7 +144,7 @@ export class WorkerRuntime {
|
|
|
143
144
|
console.warn(`[WorkerRuntime] declaring chains: ${this.declaredChains.join(', ') || 'none'}. ` +
|
|
144
145
|
`No RPC for ${undeclared.join(', ')} — tasks on ${undeclared.length > 1 ? 'those chains' : 'that chain'} are skipped; ` +
|
|
145
146
|
`set ${undeclared.map((c) => `rpcUrls.${c}`).join(', ')} to claim them ` +
|
|
146
|
-
`(production posts new tasks on
|
|
147
|
+
`(production posts new tasks on Arc).`);
|
|
147
148
|
}
|
|
148
149
|
// 1. Register or restore executor
|
|
149
150
|
if (this.config.privateKey) {
|
|
@@ -191,8 +192,8 @@ export class WorkerRuntime {
|
|
|
191
192
|
* chain this runtime has no RPC for — it would be offered, accept and
|
|
192
193
|
* strand those tasks. A stored list that is a SUBSET of what the runtime
|
|
193
194
|
* can settle is left alone: an operator who registered ['base'] through
|
|
194
|
-
* the MCP or PATCH meant it.
|
|
195
|
-
*
|
|
195
|
+
* the MCP or PATCH meant it. Older backends only store the list (newer ones
|
|
196
|
+
* also filter offers and /accept by it); this runtime's own browse filter
|
|
196
197
|
* uses `declaredChains`, not the stored list — and a restore never
|
|
197
198
|
* registers otherwise, so an executor first registered by an older SDK
|
|
198
199
|
* would keep its old list.
|
|
@@ -310,9 +311,10 @@ export class WorkerRuntime {
|
|
|
310
311
|
continue;
|
|
311
312
|
// An accept assigns on-chain and cannot be released, so never claim a
|
|
312
313
|
// task browse already says is on a chain this runtime did not declare.
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
314
|
+
// No backend filters browse results by the registered
|
|
315
|
+
// supportedChains (older ones filter nothing by it), so this filter
|
|
316
|
+
// (and the post-accept check in executeTask, which covers rows with
|
|
317
|
+
// no chain) is what keeps such tasks out.
|
|
316
318
|
if (entry.meta?.chain && !this.declaredChains.includes(entry.meta.chain))
|
|
317
319
|
continue;
|
|
318
320
|
this.claim(taskId, state, entry.meta);
|
|
@@ -341,12 +343,15 @@ export class WorkerRuntime {
|
|
|
341
343
|
}
|
|
342
344
|
return n;
|
|
343
345
|
}
|
|
344
|
-
/**
|
|
345
|
-
|
|
346
|
+
/**
|
|
347
|
+
* Start executing `taskId` if it is not running, not backing off, and a slot
|
|
348
|
+
* is free. `dueAt`: the time a scheduled re-try counts as running at.
|
|
349
|
+
*/
|
|
350
|
+
claim(taskId, state, meta, dueAt = Date.now()) {
|
|
346
351
|
if (this.executions.has(taskId))
|
|
347
352
|
return false;
|
|
348
353
|
const retry = this.retries.get(taskId);
|
|
349
|
-
if (retry && retry.notBefore >
|
|
354
|
+
if (retry && retry.notBefore > dueAt)
|
|
350
355
|
return false;
|
|
351
356
|
if (this.inFlight() >= this.config.maxConcurrentTasks)
|
|
352
357
|
return false;
|
|
@@ -492,11 +497,16 @@ export class WorkerRuntime {
|
|
|
492
497
|
scheduleRetry(taskId, a2a, meta, ms) {
|
|
493
498
|
if (!this.running)
|
|
494
499
|
return;
|
|
500
|
+
// The back-off this timer ends. A timer can fire before Date.now() reaches
|
|
501
|
+
// it (they run on different clocks), and the re-try must not then be
|
|
502
|
+
// refused by its own back-off: nothing would re-try it before the next
|
|
503
|
+
// browse. A back-off set after this one still holds.
|
|
504
|
+
const due = this.retries.get(taskId)?.notBefore ?? 0;
|
|
495
505
|
const timer = setTimeout(() => {
|
|
496
506
|
this.retryTimers.delete(timer);
|
|
497
507
|
// No free slot / paused: the next browse picks it up instead.
|
|
498
508
|
if (this.running && !this.paused)
|
|
499
|
-
this.claim(taskId, a2a, meta);
|
|
509
|
+
this.claim(taskId, a2a, meta, Math.max(due, Date.now()));
|
|
500
510
|
}, ms);
|
|
501
511
|
this.retryTimers.add(timer);
|
|
502
512
|
}
|
package/dist/tools/helpers.js
CHANGED
|
@@ -61,7 +61,7 @@ export function createBlindMarketTools(bb) {
|
|
|
61
61
|
preferredCapabilities: arr('Preferred subset of capabilities (optional)', str('Capability', CAP_ENUM)),
|
|
62
62
|
// No enum: the backend validates the list, and a newer backend may accept
|
|
63
63
|
// a chain this SDK version doesn't know.
|
|
64
|
-
supportedChains: arr("Settlement chains you can sign submitEvidence on, e.g. ['
|
|
64
|
+
supportedChains: arr("Settlement chains you can sign submitEvidence on, e.g. ['base', 'arc']. Stored on your executor record; newer backends also stop offering you, and refuse your accept on, tasks on other chains. Browse results are not filtered, so check a task's chain before accepting (optional)", str('Chain slug')),
|
|
65
65
|
}, async (a) => {
|
|
66
66
|
return bb.registerExecutor(a);
|
|
67
67
|
}, ['displayName', 'capabilities', 'publicKey']),
|
package/dist/types.d.ts
CHANGED
|
@@ -168,7 +168,7 @@ export interface A2APublicTaskMeta {
|
|
|
168
168
|
requiredCapabilities?: AgentCapability[];
|
|
169
169
|
posterAddress?: string;
|
|
170
170
|
/** Which escrow holds the task. Absent on rows indexed before the field existed. */
|
|
171
|
-
chain?: 'base' | '0g';
|
|
171
|
+
chain?: 'base' | '0g' | 'arc';
|
|
172
172
|
/** Unix seconds. */
|
|
173
173
|
deadline?: number;
|
|
174
174
|
privacy?: 'public';
|
|
@@ -195,9 +195,10 @@ export interface ExecutorProfile {
|
|
|
195
195
|
minReward?: string;
|
|
196
196
|
preferredCapabilities?: AgentCapability[];
|
|
197
197
|
/** Settlement chains the executor declared at registration. `null` means it
|
|
198
|
-
* never declared any.
|
|
199
|
-
*
|
|
200
|
-
*
|
|
198
|
+
* never declared any. Older backends only store it; newer ones also filter
|
|
199
|
+
* offers, bids and /accept by it (see
|
|
200
|
+
* {@link RegisterExecutorInput.supportedChains}). Absent from backends that
|
|
201
|
+
* predate the field. */
|
|
201
202
|
supportedChains?: string[] | null;
|
|
202
203
|
registeredAt: string;
|
|
203
204
|
decayedScore?: number;
|
|
@@ -219,10 +220,14 @@ export interface RegisterExecutorInput {
|
|
|
219
220
|
minReward?: string;
|
|
220
221
|
preferredCapabilities?: AgentCapability[];
|
|
221
222
|
/** Settlement chains ('0g', 'base', …) this executor can sign
|
|
222
|
-
* `submitEvidence` on.
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
223
|
+
* `submitEvidence` on. Older backends store it on the executor record
|
|
224
|
+
* only; newer ones also leave the executor out of offers and refuse bids
|
|
225
|
+
* and /accept (409 CHAIN_UNSUPPORTED) for tasks on other chains — and for
|
|
226
|
+
* tasks indexed before chains were recorded unless it lists both '0g' and
|
|
227
|
+
* 'base'. No backend
|
|
228
|
+
* filters browse results by it, so the caller must check a task's chain
|
|
229
|
+
* (`entry.meta.chain`) before accepting — WorkerRuntime does. Backends
|
|
230
|
+
* that predate the field drop it. */
|
|
226
231
|
supportedChains?: string[];
|
|
227
232
|
}
|
|
228
233
|
/** Params for BlindMarket.createAgent() — derives the pubkey from your key + registers the executor in one call. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blindmarket/sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "BlindMarket SDK — deploy agents, assign workers, verify evidence",
|
|
5
5
|
"author": "BlindMarket Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build": "tsc",
|
|
56
|
+
"prepublishOnly": "npm run build",
|
|
56
57
|
"dev": "tsc --watch",
|
|
57
58
|
"test": "vitest run"
|
|
58
59
|
},
|