@blindmarket/sdk 0.1.8 → 0.4.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.
Files changed (87) hide show
  1. package/dist/executor/WorkerRuntime.d.ts +21 -0
  2. package/dist/executor/WorkerRuntime.js +56 -27
  3. package/dist/index.d.ts +75 -9
  4. package/dist/index.js +52 -3
  5. package/package.json +8 -1
  6. package/biome.json +0 -19
  7. package/examples/agent-demo.ts +0 -121
  8. package/scripts/gen-crypto-fixtures.mjs +0 -72
  9. package/src/agent/Agent.ts +0 -122
  10. package/src/agent/index.ts +0 -1
  11. package/src/api/index.ts +0 -1
  12. package/src/chain/BlindEscrowClient.ts +0 -196
  13. package/src/chain/BlindReputationClient.ts +0 -60
  14. package/src/chain/ChainClient.ts +0 -65
  15. package/src/chain/IBlindMarketChain.ts +0 -109
  16. package/src/chain/TaskRegistryClient.ts +0 -91
  17. package/src/chain/abi/BlindEscrow.json +0 -1113
  18. package/src/chain/abi/BlindReputation.json +0 -447
  19. package/src/chain/abi/TaskRegistry.json +0 -586
  20. package/src/chain/abi/index.ts +0 -5
  21. package/src/chain/createChainAdapter.ts +0 -40
  22. package/src/chain/domain-types.ts +0 -92
  23. package/src/chain/errors.ts +0 -43
  24. package/src/chain/evm/EVMChainAdapter.ts +0 -212
  25. package/src/chain/evm/index.ts +0 -1
  26. package/src/chain/index.ts +0 -12
  27. package/src/compute/index.ts +0 -1
  28. package/src/crypto/aes.ts +0 -104
  29. package/src/crypto/constants.ts +0 -7
  30. package/src/crypto/ecies.ts +0 -60
  31. package/src/crypto/envelope.ts +0 -131
  32. package/src/crypto/hkdf.ts +0 -45
  33. package/src/crypto/index.ts +0 -6
  34. package/src/crypto/keys.ts +0 -56
  35. package/src/errors/BlindMarketError.ts +0 -38
  36. package/src/errors/codes.ts +0 -67
  37. package/src/errors/index.ts +0 -3
  38. package/src/errors/subclasses.ts +0 -59
  39. package/src/events/EventBus.ts +0 -45
  40. package/src/events/index.ts +0 -2
  41. package/src/events/types.ts +0 -13
  42. package/src/executor/WorkerRuntime.ts +0 -338
  43. package/src/executor/index.ts +0 -8
  44. package/src/index.ts +0 -636
  45. package/src/keystore/InMemoryKeyStore.ts +0 -146
  46. package/src/keystore/KeyStore.ts +0 -19
  47. package/src/keystore/index.ts +0 -2
  48. package/src/network/index.ts +0 -3
  49. package/src/network/presets.ts +0 -66
  50. package/src/network/resolve.ts +0 -31
  51. package/src/network/types.ts +0 -20
  52. package/src/signer/EthersSigner.ts +0 -82
  53. package/src/signer/PrivateKeySigner.ts +0 -19
  54. package/src/signer/Signer.ts +0 -40
  55. package/src/signer/index.ts +0 -3
  56. package/src/storage/MemoryStorage.ts +0 -49
  57. package/src/storage/Storage.ts +0 -15
  58. package/src/storage/ZgStorage.ts +0 -124
  59. package/src/storage/index.ts +0 -3
  60. package/src/testing/index.ts +0 -1
  61. package/src/tools/adapters.ts +0 -177
  62. package/src/tools/helpers.ts +0 -199
  63. package/src/tools/index.ts +0 -59
  64. package/src/tools/types.ts +0 -47
  65. package/src/types.ts +0 -297
  66. package/src/verifier/index.ts +0 -1
  67. package/src/worker/Worker.ts +0 -98
  68. package/src/worker/index.ts +0 -1
  69. package/test/agent.test.ts +0 -193
  70. package/test/chain.test.ts +0 -145
  71. package/test/crypto.aes.test.ts +0 -81
  72. package/test/crypto.compat.test.ts +0 -33
  73. package/test/crypto.ecies.test.ts +0 -64
  74. package/test/envelope.test.ts +0 -82
  75. package/test/errors.test.ts +0 -64
  76. package/test/events.test.ts +0 -59
  77. package/test/fixtures/crypto.json +0 -16
  78. package/test/keystore.test.ts +0 -103
  79. package/test/network.test.ts +0 -53
  80. package/test/signer.test.ts +0 -66
  81. package/test/smoke.test.ts +0 -7
  82. package/test/storage.test.ts +0 -55
  83. package/test/worker.test.ts +0 -181
  84. package/tsconfig.build.json +0 -1
  85. package/tsconfig.json +0 -14
  86. package/tsup.config.ts +0 -32
  87. package/vitest.config.ts +0 -17
@@ -1,45 +0,0 @@
1
- import { CryptoError } from '../errors/index.js';
2
- import { ECIES_HKDF_INFO } from './constants.js';
3
-
4
- function getSubtle(): SubtleCrypto {
5
- const c = (globalThis as { crypto?: Crypto }).crypto;
6
- if (!c?.subtle) {
7
- throw new CryptoError('CRYPTO/UNSUPPORTED_CIPHER', 'Web Crypto (crypto.subtle) is not available');
8
- }
9
- return c.subtle;
10
- }
11
-
12
- function toArrayBuffer(u: Uint8Array): ArrayBuffer {
13
- const ab = new ArrayBuffer(u.byteLength);
14
- new Uint8Array(ab).set(u);
15
- return ab;
16
- }
17
-
18
- /**
19
- * HKDF-SHA256. Defaults match backend: empty salt, the ECIES domain separation info,
20
- * 32-byte output length. Pass custom values for other uses.
21
- */
22
- export async function hkdfSha256(
23
- ikm: Uint8Array,
24
- length = 32,
25
- info: Uint8Array = ECIES_HKDF_INFO,
26
- salt: Uint8Array = new Uint8Array(0),
27
- ): Promise<Uint8Array> {
28
- try {
29
- const subtle = getSubtle();
30
- const baseKey = await subtle.importKey('raw', toArrayBuffer(ikm), 'HKDF', false, ['deriveBits']);
31
- const derived = await subtle.deriveBits(
32
- {
33
- name: 'HKDF',
34
- hash: 'SHA-256',
35
- info: toArrayBuffer(info),
36
- salt: toArrayBuffer(salt),
37
- },
38
- baseKey,
39
- length * 8,
40
- );
41
- return new Uint8Array(derived);
42
- } catch (cause) {
43
- throw new CryptoError('CRYPTO/INVALID_KEY', 'HKDF derivation failed', { cause });
44
- }
45
- }
@@ -1,6 +0,0 @@
1
- export * from './constants.js';
2
- export * from './aes.js';
3
- export * from './ecies.js';
4
- export * from './keys.js';
5
- export * from './envelope.js';
6
- export { hkdfSha256 } from './hkdf.js';
@@ -1,56 +0,0 @@
1
- import { ethers } from 'ethers';
2
- import { CryptoError } from '../errors/index.js';
3
-
4
- export interface KeyPair {
5
- privateKey: string;
6
- publicKey: string;
7
- }
8
-
9
- /** Fresh secp256k1 keypair. Hex strings returned un-prefixed (no 0x). */
10
- export function generateKeyPair(): KeyPair {
11
- const w = ethers.Wallet.createRandom();
12
- return {
13
- privateKey: w.privateKey.slice(2),
14
- publicKey: w.signingKey.publicKey.slice(2),
15
- };
16
- }
17
-
18
- /** Recover the uncompressed public key (un-prefixed hex) from a private key. */
19
- export function derivePublicKey(privateKey: string): string {
20
- const sk = new ethers.SigningKey(`0x${strip(privateKey)}`);
21
- return sk.publicKey.slice(2);
22
- }
23
-
24
- export function strip(hex: string): string {
25
- return hex.startsWith('0x') || hex.startsWith('0X') ? hex.slice(2) : hex;
26
- }
27
-
28
- export function assertUncompressedPubKey(pub: string): void {
29
- const s = strip(pub);
30
- if (s.length !== 130 || !/^04/i.test(s)) {
31
- throw new CryptoError(
32
- 'CRYPTO/INVALID_KEY',
33
- 'expected uncompressed secp256k1 pubkey (65 bytes hex, starts with 04)',
34
- );
35
- }
36
- }
37
-
38
- export function hexToBytes(hex: string): Uint8Array {
39
- const s = strip(hex);
40
- if (s.length % 2 !== 0) {
41
- throw new CryptoError('CRYPTO/INVALID_KEY', 'hex string has odd length');
42
- }
43
- const out = new Uint8Array(s.length / 2);
44
- for (let i = 0; i < out.length; i++) {
45
- const byte = Number.parseInt(s.substring(i * 2, i * 2 + 2), 16);
46
- if (Number.isNaN(byte)) {
47
- throw new CryptoError('CRYPTO/INVALID_KEY', `invalid hex byte at position ${i * 2}`);
48
- }
49
- out[i] = byte;
50
- }
51
- return out;
52
- }
53
-
54
- export function bytesToHex(u: Uint8Array): string {
55
- return Array.from(u, (b) => b.toString(16).padStart(2, '0')).join('');
56
- }
@@ -1,38 +0,0 @@
1
- import type { ErrorCode } from './codes.js';
2
-
3
- export interface ErrorOptions {
4
- retriable?: boolean;
5
- context?: Record<string, unknown>;
6
- cause?: unknown;
7
- }
8
-
9
- export abstract class BlindMarketError extends Error {
10
- readonly code: ErrorCode;
11
- readonly retriable: boolean;
12
- readonly context?: Record<string, unknown>;
13
- override readonly cause?: unknown;
14
-
15
- constructor(code: ErrorCode, message: string, opts: ErrorOptions = {}) {
16
- super(message);
17
- this.code = code;
18
- this.retriable = opts.retriable ?? false;
19
- if (opts.context !== undefined) this.context = opts.context;
20
- if (opts.cause !== undefined) this.cause = opts.cause;
21
- Object.defineProperty(this, 'name', {
22
- value: this.constructor.name,
23
- enumerable: false,
24
- configurable: true,
25
- writable: true,
26
- });
27
- }
28
-
29
- toJSON(): Record<string, unknown> {
30
- return {
31
- name: this.name,
32
- code: this.code,
33
- message: this.message,
34
- retriable: this.retriable,
35
- ...(this.context ? { context: this.context } : {}),
36
- };
37
- }
38
- }
@@ -1,67 +0,0 @@
1
- export const ErrorCodes = {
2
- Config: ['CONFIG/INVALID_NETWORK', 'CONFIG/MISSING_SIGNER', 'CONFIG/BAD_CONFIG'] as const,
3
- Crypto: [
4
- 'CRYPTO/INVALID_KEY',
5
- 'CRYPTO/DECRYPT_FAILED',
6
- 'CRYPTO/INTEGRITY_CHECK',
7
- 'CRYPTO/UNSUPPORTED_CIPHER',
8
- ] as const,
9
- Storage: [
10
- 'STORAGE/UPLOAD_FAILED',
11
- 'STORAGE/DOWNLOAD_FAILED',
12
- 'STORAGE/MERKLE_MISMATCH',
13
- 'STORAGE/NOT_FOUND',
14
- 'STORAGE/INDEXER_UNREACHABLE',
15
- ] as const,
16
- Chain: [
17
- 'CHAIN/TX_REVERTED',
18
- 'CHAIN/TX_TIMED_OUT',
19
- 'CHAIN/NONCE_CONFLICT',
20
- 'CHAIN/INSUFFICIENT_BALANCE',
21
- 'CHAIN/NETWORK_MISMATCH',
22
- 'CHAIN/CONTRACT_NOT_DEPLOYED',
23
- ] as const,
24
- Compute: [
25
- 'COMPUTE/BROKER_UNREACHABLE',
26
- 'COMPUTE/NO_PROVIDERS',
27
- 'COMPUTE/LEDGER_INSUFFICIENT',
28
- 'COMPUTE/TEE_ATTESTATION_INVALID',
29
- 'COMPUTE/VERIFICATION_TIMEOUT',
30
- ] as const,
31
- Api: [
32
- 'API/AUTH_REQUIRED',
33
- 'API/AUTH_EXPIRED',
34
- 'API/FORBIDDEN',
35
- 'API/RATE_LIMITED',
36
- 'API/VALIDATION',
37
- 'API/SERVER',
38
- 'API/NOT_FOUND',
39
- ] as const,
40
- Lifecycle: [
41
- 'LIFECYCLE/ILLEGAL_TRANSITION',
42
- 'LIFECYCLE/TASK_NOT_FOUND',
43
- 'LIFECYCLE/NOT_ASSIGNED',
44
- 'LIFECYCLE/ALREADY_SUBMITTED',
45
- 'LIFECYCLE/CLAIM_NOT_READY',
46
- ] as const,
47
- Timeout: ['TIMEOUT'] as const,
48
- } as const;
49
-
50
- export type ConfigCode = (typeof ErrorCodes.Config)[number];
51
- export type CryptoCode = (typeof ErrorCodes.Crypto)[number];
52
- export type StorageCode = (typeof ErrorCodes.Storage)[number];
53
- export type ChainCode = (typeof ErrorCodes.Chain)[number];
54
- export type ComputeCode = (typeof ErrorCodes.Compute)[number];
55
- export type ApiCode = (typeof ErrorCodes.Api)[number];
56
- export type LifecycleCode = (typeof ErrorCodes.Lifecycle)[number];
57
- export type TimeoutCode = (typeof ErrorCodes.Timeout)[number];
58
-
59
- export type ErrorCode =
60
- | ConfigCode
61
- | CryptoCode
62
- | StorageCode
63
- | ChainCode
64
- | ComputeCode
65
- | ApiCode
66
- | LifecycleCode
67
- | TimeoutCode;
@@ -1,3 +0,0 @@
1
- export * from './codes.js';
2
- export * from './BlindMarketError.js';
3
- export * from './subclasses.js';
@@ -1,59 +0,0 @@
1
- import { BlindMarketError, type ErrorOptions } from './BlindMarketError.js';
2
- import type {
3
- ApiCode,
4
- ChainCode,
5
- ComputeCode,
6
- ConfigCode,
7
- CryptoCode,
8
- LifecycleCode,
9
- StorageCode,
10
- TimeoutCode,
11
- } from './codes.js';
12
-
13
- export class ConfigError extends BlindMarketError {
14
- constructor(code: ConfigCode, msg: string, opts?: ErrorOptions) {
15
- super(code, msg, opts);
16
- }
17
- }
18
-
19
- export class CryptoError extends BlindMarketError {
20
- constructor(code: CryptoCode, msg: string, opts?: ErrorOptions) {
21
- super(code, msg, opts);
22
- }
23
- }
24
-
25
- export class StorageError extends BlindMarketError {
26
- constructor(code: StorageCode, msg: string, opts?: ErrorOptions) {
27
- super(code, msg, opts);
28
- }
29
- }
30
-
31
- export class ChainError extends BlindMarketError {
32
- constructor(code: ChainCode, msg: string, opts?: ErrorOptions) {
33
- super(code, msg, opts);
34
- }
35
- }
36
-
37
- export class ComputeError extends BlindMarketError {
38
- constructor(code: ComputeCode, msg: string, opts?: ErrorOptions) {
39
- super(code, msg, opts);
40
- }
41
- }
42
-
43
- export class ApiError extends BlindMarketError {
44
- constructor(code: ApiCode, msg: string, opts?: ErrorOptions) {
45
- super(code, msg, opts);
46
- }
47
- }
48
-
49
- export class LifecycleError extends BlindMarketError {
50
- constructor(code: LifecycleCode, msg: string, opts?: ErrorOptions) {
51
- super(code, msg, opts);
52
- }
53
- }
54
-
55
- export class TimeoutError extends BlindMarketError {
56
- constructor(code: TimeoutCode, msg: string, opts?: ErrorOptions) {
57
- super(code, msg, opts);
58
- }
59
- }
@@ -1,45 +0,0 @@
1
- type Handler<T> = (payload: T) => void;
2
-
3
- /**
4
- * Typed pub/sub. Listener exceptions are swallowed so one bad subscriber
5
- * cannot block others — telemetry plugins can observe failures via onError.
6
- */
7
- export class EventBus<E> {
8
- private handlers: { [K in keyof E]?: Set<Handler<E[K]>> } = {};
9
-
10
- on<K extends keyof E>(event: K, fn: Handler<E[K]>): () => void {
11
- let set = this.handlers[event];
12
- if (!set) {
13
- set = new Set<Handler<E[K]>>();
14
- this.handlers[event] = set;
15
- }
16
- set.add(fn);
17
- return () => {
18
- this.off(event, fn);
19
- };
20
- }
21
-
22
- once<K extends keyof E>(event: K, fn: Handler<E[K]>): () => void {
23
- const wrapped: Handler<E[K]> = (p) => {
24
- this.off(event, wrapped);
25
- fn(p);
26
- };
27
- return this.on(event, wrapped);
28
- }
29
-
30
- off<K extends keyof E>(event: K, fn: Handler<E[K]>): void {
31
- this.handlers[event]?.delete(fn);
32
- }
33
-
34
- emit<K extends keyof E>(event: K, payload: E[K]): void {
35
- const hs = this.handlers[event];
36
- if (!hs) return;
37
- for (const h of hs) {
38
- try {
39
- h(payload);
40
- } catch {
41
- // Isolated per-listener — prevents one bad subscriber from blocking others.
42
- }
43
- }
44
- }
45
- }
@@ -1,2 +0,0 @@
1
- export * from './EventBus.js';
2
- export * from './types.js';
@@ -1,13 +0,0 @@
1
- import type { Address, Hex, RootHash, TaskId } from '../types.js';
2
-
3
- export interface SdkEvents {
4
- 'task.created': { taskId: TaskId; txHash: Hex; rootHash: RootHash };
5
- 'task.assigned': { taskId: TaskId; worker: Address; txHash: Hex };
6
- 'task.cancelled': { taskId: TaskId; txHash: Hex };
7
- 'evidence.submitted': { taskId: TaskId; rootHash: RootHash; txHash: Hex };
8
- 'verification.done': { taskId: TaskId; passed: boolean; confidence: number };
9
- 'payment.claimed': { taskId: TaskId; txHash: Hex; amount: bigint };
10
- 'tx.sent': { hash: Hex; from: Address; to: Address };
11
- 'tx.confirmed': { hash: Hex; blockNumber: number };
12
- 'tx.failed': { hash: Hex; reason: string };
13
- }
@@ -1,338 +0,0 @@
1
- import { BlindMarket } from '../index.js';
2
- import { eciesDecrypt, aesDecrypt } from '../crypto/index.js';
3
- import type {
4
- A2ATaskState, AgentCapability, ExecutorProfile, Message,
5
- } from '../types.js';
6
-
7
- // ── Config ──────────────────────────────────────────────────────────────────
8
-
9
- export interface WorkerRuntimeConfig {
10
- apiKey: string;
11
- apiBase?: string;
12
- displayName: string;
13
- capabilities: AgentCapability[];
14
- executeTask: ExecuteTaskHandler;
15
- existingPrivateKey?: string;
16
- existingAddress?: string;
17
- existingPublicKey?: string;
18
- minReward?: string;
19
- preferredCapabilities?: AgentCapability[];
20
- browseIntervalMs?: number;
21
- watchIntervalMs?: number;
22
- maxConcurrentTasks?: number;
23
- }
24
-
25
- // ── Types ───────────────────────────────────────────────────────────────────
26
-
27
- export type ExecuteTaskHandler = (ctx: TaskContext) => Promise<Record<string, unknown>>;
28
-
29
- export interface TaskContext {
30
- taskId: string;
31
- task: A2ATaskState;
32
- instructions: string;
33
- }
34
-
35
- export interface TaskExecutionInfo {
36
- taskId: string;
37
- status: 'bidding' | 'assigned' | 'working' | 'submitted' | 'completed' | 'failed';
38
- task?: A2ATaskState;
39
- error?: string;
40
- startedAt: number;
41
- }
42
-
43
- export type WorkerRuntimeEvent =
44
- | { type: 'started' }
45
- | { type: 'stopped' }
46
- | { type: 'paused' }
47
- | { type: 'resumed' }
48
- | { type: 'registered'; profile: ExecutorProfile }
49
- | { type: 'task_found'; taskId: string; task: A2ATaskState }
50
- | { type: 'task_bidded'; taskId: string }
51
- | { type: 'task_assigned'; taskId: string }
52
- | { type: 'task_accepted'; taskId: string }
53
- | { type: 'task_working'; taskId: string }
54
- | { type: 'task_executed'; taskId: string }
55
- | { type: 'task_submitted'; taskId: string; result: Record<string, unknown> }
56
- | { type: 'task_failed'; taskId: string; error: string }
57
- | { type: 'message_received'; message: Message; count: number }
58
- | { type: 'browse_done'; found: number }
59
- | { type: 'error'; error: string };
60
-
61
- // ── Defaults ────────────────────────────────────────────────────────────────
62
-
63
- const DEFAULTS = {
64
- browseIntervalMs: 15_000,
65
- watchIntervalMs: 5_000,
66
- maxConcurrentTasks: 3,
67
- };
68
-
69
- // ── WorkerRuntime ───────────────────────────────────────────────────────────
70
-
71
- export class WorkerRuntime {
72
- private bb: BlindMarket;
73
- private config: WorkerRuntimeConfig & typeof DEFAULTS;
74
- private wallet?: { address: string; privateKey: string; publicKey: string };
75
- private profile?: ExecutorProfile;
76
- private running = false;
77
- private paused = false;
78
- private browseTimer?: ReturnType<typeof setInterval>;
79
- private watchTimers = new Map<string, ReturnType<typeof setInterval>>();
80
- private executions = new Map<string, TaskExecutionInfo>();
81
- private listeners = new Set<(event: WorkerRuntimeEvent) => void>();
82
-
83
- constructor(config: WorkerRuntimeConfig) {
84
- this.config = { ...DEFAULTS, ...config };
85
- this.bb = new BlindMarket({ apiKey: config.apiKey, apiBase: config.apiBase });
86
- }
87
-
88
- // ── Status ──────────────────────────────────────────────────────────────
89
-
90
- get isRunning(): boolean {
91
- return this.running;
92
- }
93
-
94
- get isPaused(): boolean {
95
- return this.paused;
96
- }
97
-
98
- get activeExecutions(): TaskExecutionInfo[] {
99
- return [...this.executions.values()];
100
- }
101
-
102
- /** Get own executor profile (available after start). */
103
- get executorProfile(): ExecutorProfile | undefined {
104
- return this.profile;
105
- }
106
-
107
- /** Get own wallet info (available after start). */
108
- get executorWallet(): { address: string; publicKey: string } | undefined {
109
- return this.wallet;
110
- }
111
-
112
- // ── Events ──────────────────────────────────────────────────────────────
113
-
114
- on(listener: (event: WorkerRuntimeEvent) => void): () => void {
115
- this.listeners.add(listener);
116
- return () => this.listeners.delete(listener);
117
- }
118
-
119
- private emit(event: WorkerRuntimeEvent): void {
120
- for (const l of this.listeners) l(event);
121
- }
122
-
123
- // ── Lifecycle ───────────────────────────────────────────────────────────
124
-
125
- async start(): Promise<ExecutorProfile> {
126
- if (this.running) return this.profile!;
127
-
128
- // 1. Register or restore executor
129
- if (this.config.existingPrivateKey && this.config.existingAddress && this.config.existingPublicKey) {
130
- this.wallet = {
131
- address: this.config.existingAddress,
132
- privateKey: this.config.existingPrivateKey,
133
- publicKey: this.config.existingPublicKey,
134
- };
135
- // Fetch existing profile — backend identifies by API key
136
- const result = await this.bb.getExecutorProfile();
137
- this.profile = result.agent;
138
- } else {
139
- const result = await this.bb.createAgent({
140
- displayName: this.config.displayName,
141
- capabilities: this.config.capabilities,
142
- minReward: this.config.minReward,
143
- preferredCapabilities: this.config.preferredCapabilities,
144
- });
145
- this.wallet = {
146
- address: result.wallet.address,
147
- privateKey: result.wallet.privateKey,
148
- publicKey: result.wallet.publicKey,
149
- };
150
- this.profile = result.executor;
151
- }
152
-
153
- this.emit({ type: 'registered', profile: this.profile });
154
-
155
- // 2. Start the browse loop
156
- this.running = true;
157
- this.startBrowseLoop();
158
- this.emit({ type: 'started' });
159
-
160
- return this.profile;
161
- }
162
-
163
- stop(): void {
164
- this.running = false;
165
- if (this.browseTimer) {
166
- clearInterval(this.browseTimer);
167
- this.browseTimer = undefined;
168
- }
169
- for (const t of this.watchTimers.values()) clearInterval(t);
170
- this.watchTimers.clear();
171
- this.emit({ type: 'stopped' });
172
- }
173
-
174
- pause(): void {
175
- if (!this.running || this.paused) return;
176
- this.paused = true;
177
- this.emit({ type: 'paused' });
178
- }
179
-
180
- resume(): void {
181
- if (!this.running || !this.paused) return;
182
- this.paused = false;
183
- this.emit({ type: 'resumed' });
184
- // Immediately browse instead of waiting for next interval
185
- this.browse();
186
- }
187
-
188
- // ── Browse loop ─────────────────────────────────────────────────────────
189
-
190
- private startBrowseLoop(): void {
191
- const ms = this.config.browseIntervalMs;
192
- this.browseTimer = setInterval(() => this.browse(), ms);
193
- this.browse();
194
- }
195
-
196
- private async browse(): Promise<void> {
197
- if (this.paused) return;
198
- try {
199
- const active = this.activeExecutions;
200
- const inFlight = active.filter(e => e.status === 'bidding' || e.status === 'working').length;
201
- if (inFlight >= this.config.maxConcurrentTasks) return;
202
-
203
- const result = await this.bb.browseA2ATasks({
204
- capabilities: this.config.capabilities,
205
- });
206
-
207
- this.emit({ type: 'browse_done', found: result.tasks.length });
208
-
209
- for (const task of result.tasks) {
210
- if (this.executions.has(task.taskId)) continue;
211
- if (task.status !== 'open' && task.status !== 'bidding') continue;
212
- if (task.executorAddress && task.executorAddress !== this.wallet?.address) continue;
213
-
214
- this.executions.set(task.taskId, {
215
- taskId: task.taskId,
216
- status: 'bidding',
217
- task,
218
- startedAt: Date.now(),
219
- });
220
-
221
- this.emit({ type: 'task_found', taskId: task.taskId, task });
222
-
223
- await this.bb.bidOnTask(task.taskId);
224
- this.emit({ type: 'task_bidded', taskId: task.taskId });
225
-
226
- this.watchForAssignment(task.taskId);
227
- }
228
- } catch (err) {
229
- this.emit({ type: 'error', error: `Browse failed: ${err}` });
230
- }
231
- }
232
-
233
- // ── Assignment watching ─────────────────────────────────────────────────
234
-
235
- private watchForAssignment(taskId: string): void {
236
- if (!this.running) return;
237
- const ms = this.config.watchIntervalMs;
238
- const timer = setInterval(async () => {
239
- if (!this.running || this.paused) return;
240
- try {
241
- const detail = await this.bb.getTask(taskId);
242
- const a2a = detail.a2aState;
243
- if (!a2a) return;
244
-
245
- if (a2a.status === 'assigned') {
246
- clearInterval(timer);
247
- this.watchTimers.delete(taskId);
248
- this.emit({ type: 'task_assigned', taskId });
249
- await this.executeTask(taskId, a2a);
250
- }
251
- } catch {
252
- // Retry next tick
253
- }
254
- }, ms);
255
- this.watchTimers.set(taskId, timer);
256
- }
257
-
258
- // ── Task execution ──────────────────────────────────────────────────────
259
-
260
- private async executeTask(taskId: string, a2a: A2ATaskState): Promise<void> {
261
- const exec = this.executions.get(taskId);
262
- if (!exec) return;
263
-
264
- try {
265
- exec.status = 'assigned';
266
-
267
- // Accept task — get the ECIES-wrapped AES key
268
- const acceptResult = await this.bb.acceptTask(taskId);
269
- exec.task = acceptResult.task;
270
- this.emit({ type: 'task_accepted', taskId });
271
-
272
- // Get full task detail for the storage root hash
273
- const detail = await this.bb.getTask(taskId);
274
- const taskHash = detail.taskHash;
275
-
276
- // Decrypt the wrapped key with the worker's private key
277
- let instructions = '';
278
- if (acceptResult.wrappedKey && taskHash) {
279
- const wrappedBytes = this.decodeWrappedKey(acceptResult.wrappedKey);
280
- const aesKey = await eciesDecrypt(wrappedBytes, this.wallet!.privateKey);
281
-
282
- // Download ciphertext from storage via REST API
283
- const storageResult = await this.bb.downloadBlob(taskHash);
284
- const ciphertext = this.hexToBytes(storageResult.data);
285
-
286
- // AES decrypt to get plaintext instructions
287
- const plaintext = await aesDecrypt(ciphertext, aesKey);
288
- instructions = new TextDecoder().decode(plaintext);
289
- }
290
-
291
- exec.status = 'working';
292
- this.emit({ type: 'task_working', taskId });
293
-
294
- // Call the user-provided execution handler with decrypted instructions
295
- const result = await this.config.executeTask({
296
- taskId,
297
- task: acceptResult.task,
298
- instructions,
299
- });
300
-
301
- exec.status = 'submitted';
302
- this.emit({ type: 'task_executed', taskId });
303
-
304
- // Submit result
305
- const submitResult = await this.bb.submitResult(taskId, result);
306
-
307
- exec.status = 'completed';
308
- this.emit({ type: 'task_submitted', taskId, result: submitResult });
309
- } catch (err) {
310
- exec.status = 'failed';
311
- exec.error = String(err);
312
- this.emit({ type: 'task_failed', taskId, error: String(err) });
313
- }
314
- }
315
-
316
- // ── Helpers ─────────────────────────────────────────────────────────────
317
-
318
- private decodeWrappedKey(wrappedKey: Record<string, string>): Uint8Array {
319
- // Backend returns wrapped key as hex or base64 in a record
320
- const hex = Object.values(wrappedKey)[0];
321
- if (!hex) throw new Error('Empty wrapped key');
322
- const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
323
- const bytes = new Uint8Array(clean.length / 2);
324
- for (let i = 0; i < bytes.length; i++) {
325
- bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
326
- }
327
- return bytes;
328
- }
329
-
330
- private hexToBytes(hex: string): Uint8Array {
331
- const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
332
- const bytes = new Uint8Array(clean.length / 2);
333
- for (let i = 0; i < bytes.length; i++) {
334
- bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
335
- }
336
- return bytes;
337
- }
338
- }
@@ -1,8 +0,0 @@
1
- export {
2
- WorkerRuntime,
3
- type WorkerRuntimeConfig,
4
- type ExecuteTaskHandler,
5
- type TaskContext,
6
- type TaskExecutionInfo,
7
- type WorkerRuntimeEvent,
8
- } from './WorkerRuntime.js';