@blindmarket/sdk 0.1.7 → 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.
- package/dist/chain/BlindEscrowClient.d.ts +2 -0
- package/dist/chain/BlindEscrowClient.js +14 -6
- package/dist/chain/BlindReputationClient.d.ts +3 -0
- package/dist/chain/BlindReputationClient.js +3 -1
- package/dist/chain/ChainClient.d.ts +5 -2
- package/dist/chain/ChainClient.js +5 -0
- package/dist/chain/IBlindMarketChain.d.ts +74 -0
- package/dist/chain/TaskRegistryClient.d.ts +2 -1
- package/dist/chain/TaskRegistryClient.js +15 -7
- package/dist/chain/createChainAdapter.d.ts +25 -0
- package/dist/chain/createChainAdapter.js +24 -0
- package/dist/chain/domain-types.d.ts +77 -0
- package/dist/chain/evm/EVMChainAdapter.d.ts +56 -0
- package/dist/chain/evm/EVMChainAdapter.js +161 -0
- package/dist/chain/evm/index.d.ts +1 -0
- package/dist/chain/evm/index.js +1 -0
- package/dist/chain/index.d.ts +4 -0
- package/dist/chain/index.js +5 -0
- package/dist/executor/WorkerRuntime.d.ts +134 -0
- package/dist/executor/WorkerRuntime.js +277 -0
- package/dist/executor/index.d.ts +1 -0
- package/dist/executor/index.js +1 -0
- package/dist/index.d.ts +82 -9
- package/dist/index.js +60 -6
- package/dist/network/presets.d.ts +33 -5
- package/dist/network/presets.js +35 -8
- package/dist/network/resolve.d.ts +12 -3
- package/dist/network/resolve.js +15 -7
- package/dist/network/types.d.ts +3 -16
- package/dist/types.d.ts +1 -1
- package/package.json +8 -1
- package/biome.json +0 -19
- package/dist/BlindEscrowClient-CyPuKMge.d.cts +0 -54
- package/dist/BlindEscrowClient-R7iGpE-n.d.ts +0 -54
- package/dist/KeyStore-Be_SSpCt.d.cts +0 -21
- package/dist/KeyStore-k1JgYPJY.d.ts +0 -21
- package/dist/Signer-BnqH_qew.d.cts +0 -38
- package/dist/Signer-LColE0Uo.d.ts +0 -38
- package/dist/Storage-4QOrh7rm.d.ts +0 -17
- package/dist/Storage-Cf4IJRMk.d.cts +0 -17
- package/dist/TaskRegistryClient-Ch6lYqM_.d.ts +0 -24
- package/dist/TaskRegistryClient-CrQjddDc.d.cts +0 -24
- package/dist/aes-J-7HrHze.d.cts +0 -17
- package/dist/aes-J-7HrHze.d.ts +0 -17
- package/dist/agent/index.cjs +0 -425
- package/dist/agent/index.cjs.map +0 -1
- package/dist/agent/index.d.cts +0 -63
- package/dist/agent/index.js.map +0 -1
- package/dist/api/index.cjs +0 -4
- package/dist/api/index.cjs.map +0 -1
- package/dist/api/index.d.cts +0 -2
- package/dist/api/index.js.map +0 -1
- package/dist/chain/index.cjs +0 -2484
- package/dist/chain/index.cjs.map +0 -1
- package/dist/chain/index.d.cts +0 -2305
- package/dist/chain/index.js.map +0 -1
- package/dist/compute/index.cjs +0 -4
- package/dist/compute/index.cjs.map +0 -1
- package/dist/compute/index.d.cts +0 -2
- package/dist/compute/index.js.map +0 -1
- package/dist/crypto/index.cjs +0 -424
- package/dist/crypto/index.cjs.map +0 -1
- package/dist/crypto/index.d.cts +0 -74
- package/dist/crypto/index.js.map +0 -1
- package/dist/errors/BlindBountyError.d.ts +0 -14
- package/dist/errors/BlindBountyError.js +0 -30
- package/dist/errors/index.cjs +0 -136
- package/dist/errors/index.cjs.map +0 -1
- package/dist/errors/index.d.cts +0 -60
- package/dist/errors/index.js.map +0 -1
- package/dist/events/index.cjs +0 -41
- package/dist/events/index.cjs.map +0 -1
- package/dist/events/index.d.cts +0 -61
- package/dist/events/index.js.map +0 -1
- package/dist/index.cjs +0 -4
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2
- package/dist/index.js.map +0 -1
- package/dist/keystore/index.cjs +0 -335
- package/dist/keystore/index.cjs.map +0 -1
- package/dist/keystore/index.d.cts +0 -23
- package/dist/keystore/index.js.map +0 -1
- package/dist/mcp.d.ts +0 -23
- package/dist/mcp.js +0 -101
- package/dist/network/index.cjs +0 -74
- package/dist/network/index.cjs.map +0 -1
- package/dist/network/index.d.cts +0 -36
- package/dist/network/index.js.map +0 -1
- package/dist/signer/index.cjs +0 -121
- package/dist/signer/index.cjs.map +0 -1
- package/dist/signer/index.d.cts +0 -40
- package/dist/signer/index.js.map +0 -1
- package/dist/storage/ZgStorage.d.ts +0 -24
- package/dist/storage/ZgStorage.js +0 -102
- package/dist/storage/index.cjs +0 -221
- package/dist/storage/index.cjs.map +0 -1
- package/dist/storage/index.d.cts +0 -44
- package/dist/storage/index.js.map +0 -1
- package/dist/testing/index.cjs +0 -4
- package/dist/testing/index.cjs.map +0 -1
- package/dist/testing/index.d.cts +0 -2
- package/dist/testing/index.js.map +0 -1
- package/dist/types-BOG5Ub1z.d.cts +0 -31
- package/dist/types-BOG5Ub1z.d.ts +0 -31
- package/dist/types-V7_ZmUb-.d.cts +0 -19
- package/dist/types-hAsITUQ7.d.ts +0 -19
- package/dist/verifier/index.cjs +0 -4
- package/dist/verifier/index.cjs.map +0 -1
- package/dist/verifier/index.d.cts +0 -2
- package/dist/verifier/index.js.map +0 -1
- package/dist/worker/index.cjs +0 -382
- package/dist/worker/index.cjs.map +0 -1
- package/dist/worker/index.d.cts +0 -58
- package/dist/worker/index.js.map +0 -1
- package/examples/agent-demo.ts +0 -121
- package/scripts/gen-crypto-fixtures.mjs +0 -72
- package/src/agent/Agent.ts +0 -122
- package/src/agent/index.ts +0 -1
- package/src/chain/BlindEscrowClient.ts +0 -187
- package/src/chain/BlindReputationClient.ts +0 -55
- package/src/chain/ChainClient.ts +0 -59
- package/src/chain/TaskRegistryClient.ts +0 -82
- package/src/chain/abi/BlindEscrow.json +0 -1113
- package/src/chain/abi/BlindReputation.json +0 -447
- package/src/chain/abi/TaskRegistry.json +0 -586
- package/src/chain/abi/index.ts +0 -5
- package/src/chain/errors.ts +0 -43
- package/src/chain/index.ts +0 -6
- package/src/crypto/aes.ts +0 -104
- package/src/crypto/constants.ts +0 -7
- package/src/crypto/ecies.ts +0 -60
- package/src/crypto/envelope.ts +0 -131
- package/src/crypto/hkdf.ts +0 -45
- package/src/crypto/index.ts +0 -6
- package/src/crypto/keys.ts +0 -56
- package/src/errors/BlindMarketError.ts +0 -38
- package/src/errors/codes.ts +0 -67
- package/src/errors/index.ts +0 -3
- package/src/errors/subclasses.ts +0 -59
- package/src/events/EventBus.ts +0 -45
- package/src/events/index.ts +0 -2
- package/src/events/types.ts +0 -13
- package/src/index.ts +0 -617
- package/src/keystore/InMemoryKeyStore.ts +0 -146
- package/src/keystore/KeyStore.ts +0 -19
- package/src/keystore/index.ts +0 -2
- package/src/network/index.ts +0 -3
- package/src/network/presets.ts +0 -27
- package/src/network/resolve.ts +0 -19
- package/src/network/types.ts +0 -18
- package/src/signer/EthersSigner.ts +0 -82
- package/src/signer/PrivateKeySigner.ts +0 -19
- package/src/signer/Signer.ts +0 -40
- package/src/signer/index.ts +0 -3
- package/src/storage/MemoryStorage.ts +0 -49
- package/src/storage/Storage.ts +0 -15
- package/src/storage/ZgStorage.ts +0 -124
- package/src/storage/index.ts +0 -3
- package/src/testing/index.ts +0 -1
- package/src/tools/adapters.ts +0 -177
- package/src/tools/helpers.ts +0 -199
- package/src/tools/index.ts +0 -59
- package/src/tools/types.ts +0 -47
- package/src/types.ts +0 -296
- package/src/verifier/index.ts +0 -1
- package/src/worker/Worker.ts +0 -98
- package/src/worker/index.ts +0 -1
- package/test/agent.test.ts +0 -193
- package/test/chain.test.ts +0 -145
- package/test/crypto.aes.test.ts +0 -81
- package/test/crypto.compat.test.ts +0 -33
- package/test/crypto.ecies.test.ts +0 -64
- package/test/envelope.test.ts +0 -82
- package/test/errors.test.ts +0 -64
- package/test/events.test.ts +0 -59
- package/test/fixtures/crypto.json +0 -16
- package/test/keystore.test.ts +0 -103
- package/test/network.test.ts +0 -53
- package/test/signer.test.ts +0 -66
- package/test/smoke.test.ts +0 -7
- package/test/storage.test.ts +0 -55
- package/test/worker.test.ts +0 -181
- package/tsconfig.build.json +0 -1
- package/tsconfig.json +0 -14
- package/tsup.config.ts +0 -32
- package/vitest.config.ts +0 -17
- /package/{src/api/index.ts → dist/chain/IBlindMarketChain.js} +0 -0
- /package/{src/compute/index.ts → dist/chain/domain-types.js} +0 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { BlindMarket } from '../index.js';
|
|
2
|
+
import type { A2ATaskState, AgentCapability, ExecutorProfile, Message } from '../types.js';
|
|
3
|
+
export interface WorkerRuntimeConfig {
|
|
4
|
+
apiKey: string;
|
|
5
|
+
apiBase?: string;
|
|
6
|
+
displayName: string;
|
|
7
|
+
capabilities: AgentCapability[];
|
|
8
|
+
executeTask: ExecuteTaskHandler;
|
|
9
|
+
existingPrivateKey?: string;
|
|
10
|
+
existingAddress?: string;
|
|
11
|
+
existingPublicKey?: string;
|
|
12
|
+
minReward?: string;
|
|
13
|
+
preferredCapabilities?: AgentCapability[];
|
|
14
|
+
browseIntervalMs?: number;
|
|
15
|
+
watchIntervalMs?: number;
|
|
16
|
+
maxConcurrentTasks?: number;
|
|
17
|
+
/**
|
|
18
|
+
* RPC URL used to sign + broadcast `submitEvidence` after `submitResult()`.
|
|
19
|
+
* Defaults to the 0G testnet RPC (matches `backend/agents/worker.js`'s
|
|
20
|
+
* default). Point this at the RPC for whichever chain your tasks settle on.
|
|
21
|
+
*/
|
|
22
|
+
rpcUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
export type ExecuteTaskHandler = (ctx: TaskContext) => Promise<Record<string, unknown>>;
|
|
25
|
+
export interface TaskContext {
|
|
26
|
+
taskId: string;
|
|
27
|
+
task: A2ATaskState;
|
|
28
|
+
instructions: string;
|
|
29
|
+
}
|
|
30
|
+
export interface TaskExecutionInfo {
|
|
31
|
+
taskId: string;
|
|
32
|
+
status: 'bidding' | 'assigned' | 'working' | 'submitted' | 'completed' | 'failed';
|
|
33
|
+
task?: A2ATaskState;
|
|
34
|
+
error?: string;
|
|
35
|
+
startedAt: number;
|
|
36
|
+
}
|
|
37
|
+
export type WorkerRuntimeEvent = {
|
|
38
|
+
type: 'started';
|
|
39
|
+
} | {
|
|
40
|
+
type: 'stopped';
|
|
41
|
+
} | {
|
|
42
|
+
type: 'paused';
|
|
43
|
+
} | {
|
|
44
|
+
type: 'resumed';
|
|
45
|
+
} | {
|
|
46
|
+
type: 'registered';
|
|
47
|
+
profile: ExecutorProfile;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'task_found';
|
|
50
|
+
taskId: string;
|
|
51
|
+
task: A2ATaskState;
|
|
52
|
+
} | {
|
|
53
|
+
type: 'task_bidded';
|
|
54
|
+
taskId: string;
|
|
55
|
+
} | {
|
|
56
|
+
type: 'task_assigned';
|
|
57
|
+
taskId: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'task_accepted';
|
|
60
|
+
taskId: string;
|
|
61
|
+
} | {
|
|
62
|
+
type: 'task_working';
|
|
63
|
+
taskId: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'task_executed';
|
|
66
|
+
taskId: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: 'task_submitted';
|
|
69
|
+
taskId: string;
|
|
70
|
+
result: Record<string, unknown>;
|
|
71
|
+
} | {
|
|
72
|
+
type: 'task_finalized';
|
|
73
|
+
taskId: string;
|
|
74
|
+
finalize: Awaited<ReturnType<BlindMarket['finalize']>>;
|
|
75
|
+
} | {
|
|
76
|
+
type: 'task_failed';
|
|
77
|
+
taskId: string;
|
|
78
|
+
error: string;
|
|
79
|
+
} | {
|
|
80
|
+
type: 'message_received';
|
|
81
|
+
message: Message;
|
|
82
|
+
count: number;
|
|
83
|
+
} | {
|
|
84
|
+
type: 'browse_done';
|
|
85
|
+
found: number;
|
|
86
|
+
} | {
|
|
87
|
+
type: 'error';
|
|
88
|
+
error: string;
|
|
89
|
+
};
|
|
90
|
+
export declare class WorkerRuntime {
|
|
91
|
+
private bb;
|
|
92
|
+
private config;
|
|
93
|
+
private wallet?;
|
|
94
|
+
private profile?;
|
|
95
|
+
private running;
|
|
96
|
+
private paused;
|
|
97
|
+
private browseTimer?;
|
|
98
|
+
private watchTimers;
|
|
99
|
+
private executions;
|
|
100
|
+
private listeners;
|
|
101
|
+
constructor(config: WorkerRuntimeConfig);
|
|
102
|
+
get isRunning(): boolean;
|
|
103
|
+
get isPaused(): boolean;
|
|
104
|
+
get activeExecutions(): TaskExecutionInfo[];
|
|
105
|
+
/** Get own executor profile (available after start). */
|
|
106
|
+
get executorProfile(): ExecutorProfile | undefined;
|
|
107
|
+
/** Get own wallet info (available after start). */
|
|
108
|
+
get executorWallet(): {
|
|
109
|
+
address: string;
|
|
110
|
+
publicKey: string;
|
|
111
|
+
} | undefined;
|
|
112
|
+
on(listener: (event: WorkerRuntimeEvent) => void): () => void;
|
|
113
|
+
private emit;
|
|
114
|
+
start(): Promise<ExecutorProfile>;
|
|
115
|
+
stop(): void;
|
|
116
|
+
pause(): void;
|
|
117
|
+
resume(): void;
|
|
118
|
+
private startBrowseLoop;
|
|
119
|
+
private browse;
|
|
120
|
+
private watchForAssignment;
|
|
121
|
+
private executeTask;
|
|
122
|
+
/**
|
|
123
|
+
* Decode a wrapped-key hex string — acceptTask()'s wrappedKey field is a
|
|
124
|
+
* single hex string (this executor's ECIES-wrapped AES key), not a
|
|
125
|
+
* Record. The old code here called `Object.values` on that STRING
|
|
126
|
+
* argument (treating it as if it were a Record), which returned its
|
|
127
|
+
* first CHARACTER (e.g. "0" from "04a1b2…") — `clean.length / 2` was
|
|
128
|
+
* then 0 and every decrypt got an empty key.
|
|
129
|
+
*/
|
|
130
|
+
private decodeWrappedKey;
|
|
131
|
+
private hexToBytes;
|
|
132
|
+
/** Decode a base64 string (e.g. downloadBlob()'s `blob` field) into bytes. */
|
|
133
|
+
private base64ToBytes;
|
|
134
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import { BlindMarket } from '../index.js';
|
|
3
|
+
import { eciesDecrypt, aesDecrypt } from '../crypto/index.js';
|
|
4
|
+
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
5
|
+
const DEFAULTS = {
|
|
6
|
+
browseIntervalMs: 15_000,
|
|
7
|
+
watchIntervalMs: 5_000,
|
|
8
|
+
maxConcurrentTasks: 3,
|
|
9
|
+
rpcUrl: 'https://evmrpc-testnet.0g.ai',
|
|
10
|
+
};
|
|
11
|
+
// ── WorkerRuntime ───────────────────────────────────────────────────────────
|
|
12
|
+
export class WorkerRuntime {
|
|
13
|
+
bb;
|
|
14
|
+
config;
|
|
15
|
+
wallet;
|
|
16
|
+
profile;
|
|
17
|
+
running = false;
|
|
18
|
+
paused = false;
|
|
19
|
+
browseTimer;
|
|
20
|
+
watchTimers = new Map();
|
|
21
|
+
executions = new Map();
|
|
22
|
+
listeners = new Set();
|
|
23
|
+
constructor(config) {
|
|
24
|
+
this.config = { ...DEFAULTS, ...config };
|
|
25
|
+
this.bb = new BlindMarket({ apiKey: config.apiKey, apiBase: config.apiBase });
|
|
26
|
+
}
|
|
27
|
+
// ── Status ──────────────────────────────────────────────────────────────
|
|
28
|
+
get isRunning() {
|
|
29
|
+
return this.running;
|
|
30
|
+
}
|
|
31
|
+
get isPaused() {
|
|
32
|
+
return this.paused;
|
|
33
|
+
}
|
|
34
|
+
get activeExecutions() {
|
|
35
|
+
return [...this.executions.values()];
|
|
36
|
+
}
|
|
37
|
+
/** Get own executor profile (available after start). */
|
|
38
|
+
get executorProfile() {
|
|
39
|
+
return this.profile;
|
|
40
|
+
}
|
|
41
|
+
/** Get own wallet info (available after start). */
|
|
42
|
+
get executorWallet() {
|
|
43
|
+
return this.wallet;
|
|
44
|
+
}
|
|
45
|
+
// ── Events ──────────────────────────────────────────────────────────────
|
|
46
|
+
on(listener) {
|
|
47
|
+
this.listeners.add(listener);
|
|
48
|
+
return () => this.listeners.delete(listener);
|
|
49
|
+
}
|
|
50
|
+
emit(event) {
|
|
51
|
+
for (const l of this.listeners)
|
|
52
|
+
l(event);
|
|
53
|
+
}
|
|
54
|
+
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
55
|
+
async start() {
|
|
56
|
+
if (this.running)
|
|
57
|
+
return this.profile;
|
|
58
|
+
// 1. Register or restore executor
|
|
59
|
+
if (this.config.existingPrivateKey && this.config.existingAddress && this.config.existingPublicKey) {
|
|
60
|
+
this.wallet = {
|
|
61
|
+
address: this.config.existingAddress,
|
|
62
|
+
privateKey: this.config.existingPrivateKey,
|
|
63
|
+
publicKey: this.config.existingPublicKey,
|
|
64
|
+
};
|
|
65
|
+
// Fetch existing profile — backend identifies by API key
|
|
66
|
+
const result = await this.bb.getExecutorProfile();
|
|
67
|
+
this.profile = result.agent;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
const result = await this.bb.createAgent({
|
|
71
|
+
displayName: this.config.displayName,
|
|
72
|
+
capabilities: this.config.capabilities,
|
|
73
|
+
minReward: this.config.minReward,
|
|
74
|
+
preferredCapabilities: this.config.preferredCapabilities,
|
|
75
|
+
});
|
|
76
|
+
this.wallet = {
|
|
77
|
+
address: result.wallet.address,
|
|
78
|
+
privateKey: result.wallet.privateKey,
|
|
79
|
+
publicKey: result.wallet.publicKey,
|
|
80
|
+
};
|
|
81
|
+
this.profile = result.executor;
|
|
82
|
+
}
|
|
83
|
+
this.emit({ type: 'registered', profile: this.profile });
|
|
84
|
+
// 2. Start the browse loop
|
|
85
|
+
this.running = true;
|
|
86
|
+
this.startBrowseLoop();
|
|
87
|
+
this.emit({ type: 'started' });
|
|
88
|
+
return this.profile;
|
|
89
|
+
}
|
|
90
|
+
stop() {
|
|
91
|
+
this.running = false;
|
|
92
|
+
if (this.browseTimer) {
|
|
93
|
+
clearInterval(this.browseTimer);
|
|
94
|
+
this.browseTimer = undefined;
|
|
95
|
+
}
|
|
96
|
+
for (const t of this.watchTimers.values())
|
|
97
|
+
clearInterval(t);
|
|
98
|
+
this.watchTimers.clear();
|
|
99
|
+
this.emit({ type: 'stopped' });
|
|
100
|
+
}
|
|
101
|
+
pause() {
|
|
102
|
+
if (!this.running || this.paused)
|
|
103
|
+
return;
|
|
104
|
+
this.paused = true;
|
|
105
|
+
this.emit({ type: 'paused' });
|
|
106
|
+
}
|
|
107
|
+
resume() {
|
|
108
|
+
if (!this.running || !this.paused)
|
|
109
|
+
return;
|
|
110
|
+
this.paused = false;
|
|
111
|
+
this.emit({ type: 'resumed' });
|
|
112
|
+
// Immediately browse instead of waiting for next interval
|
|
113
|
+
this.browse();
|
|
114
|
+
}
|
|
115
|
+
// ── Browse loop ─────────────────────────────────────────────────────────
|
|
116
|
+
startBrowseLoop() {
|
|
117
|
+
const ms = this.config.browseIntervalMs;
|
|
118
|
+
this.browseTimer = setInterval(() => this.browse(), ms);
|
|
119
|
+
this.browse();
|
|
120
|
+
}
|
|
121
|
+
async browse() {
|
|
122
|
+
if (this.paused)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const active = this.activeExecutions;
|
|
126
|
+
const inFlight = active.filter(e => e.status === 'bidding' || e.status === 'working').length;
|
|
127
|
+
if (inFlight >= this.config.maxConcurrentTasks)
|
|
128
|
+
return;
|
|
129
|
+
const result = await this.bb.browseA2ATasks({
|
|
130
|
+
capabilities: this.config.capabilities,
|
|
131
|
+
});
|
|
132
|
+
this.emit({ type: 'browse_done', found: result.tasks.length });
|
|
133
|
+
for (const task of result.tasks) {
|
|
134
|
+
if (this.executions.has(task.taskId))
|
|
135
|
+
continue;
|
|
136
|
+
if (task.status !== 'open' && task.status !== 'bidding')
|
|
137
|
+
continue;
|
|
138
|
+
if (task.executorAddress && task.executorAddress !== this.wallet?.address)
|
|
139
|
+
continue;
|
|
140
|
+
this.executions.set(task.taskId, {
|
|
141
|
+
taskId: task.taskId,
|
|
142
|
+
status: 'bidding',
|
|
143
|
+
task,
|
|
144
|
+
startedAt: Date.now(),
|
|
145
|
+
});
|
|
146
|
+
this.emit({ type: 'task_found', taskId: task.taskId, task });
|
|
147
|
+
await this.bb.bidOnTask(task.taskId);
|
|
148
|
+
this.emit({ type: 'task_bidded', taskId: task.taskId });
|
|
149
|
+
this.watchForAssignment(task.taskId);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
this.emit({ type: 'error', error: `Browse failed: ${err}` });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ── Assignment watching ─────────────────────────────────────────────────
|
|
157
|
+
watchForAssignment(taskId) {
|
|
158
|
+
if (!this.running)
|
|
159
|
+
return;
|
|
160
|
+
const ms = this.config.watchIntervalMs;
|
|
161
|
+
const timer = setInterval(async () => {
|
|
162
|
+
if (!this.running || this.paused)
|
|
163
|
+
return;
|
|
164
|
+
try {
|
|
165
|
+
const detail = await this.bb.getTask(taskId);
|
|
166
|
+
const a2a = detail.a2aState;
|
|
167
|
+
if (!a2a)
|
|
168
|
+
return;
|
|
169
|
+
if (a2a.status === 'assigned') {
|
|
170
|
+
clearInterval(timer);
|
|
171
|
+
this.watchTimers.delete(taskId);
|
|
172
|
+
this.emit({ type: 'task_assigned', taskId });
|
|
173
|
+
await this.executeTask(taskId, a2a);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Retry next tick
|
|
178
|
+
}
|
|
179
|
+
}, ms);
|
|
180
|
+
this.watchTimers.set(taskId, timer);
|
|
181
|
+
}
|
|
182
|
+
// ── Task execution ──────────────────────────────────────────────────────
|
|
183
|
+
async executeTask(taskId, a2a) {
|
|
184
|
+
const exec = this.executions.get(taskId);
|
|
185
|
+
if (!exec)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
exec.status = 'assigned';
|
|
189
|
+
// Accept task — get the rootHash + this executor's ECIES-wrapped AES
|
|
190
|
+
// key. wrappedKey is a single hex string (this caller's slice), not a
|
|
191
|
+
// Record — see acceptTask()'s doc comment in ../index.ts.
|
|
192
|
+
const acceptResult = await this.bb.acceptTask(taskId);
|
|
193
|
+
exec.task = a2a;
|
|
194
|
+
this.emit({ type: 'task_accepted', taskId });
|
|
195
|
+
// Decrypt the brief. 'public' tasks carry no wrappedKey by design — the
|
|
196
|
+
// blob at rootHash is already plaintext, so skip ECIES/AES entirely.
|
|
197
|
+
// Download by acceptResult.rootHash (the 0G Storage pointer) — NOT the
|
|
198
|
+
// on-chain taskHash, which is a different value (sha256 of the
|
|
199
|
+
// ciphertext, used as the escrow's commitment) and isn't a valid
|
|
200
|
+
// storage lookup key.
|
|
201
|
+
let instructions = '';
|
|
202
|
+
if (acceptResult.rootHash) {
|
|
203
|
+
const storageResult = await this.bb.downloadBlob(acceptResult.rootHash);
|
|
204
|
+
const blobBytes = this.base64ToBytes(storageResult.blob);
|
|
205
|
+
if (acceptResult.privacy === 'public') {
|
|
206
|
+
instructions = new TextDecoder().decode(blobBytes);
|
|
207
|
+
}
|
|
208
|
+
else if (acceptResult.wrappedKey) {
|
|
209
|
+
const wrappedBytes = this.decodeWrappedKey(acceptResult.wrappedKey);
|
|
210
|
+
const aesKey = await eciesDecrypt(wrappedBytes, this.wallet.privateKey);
|
|
211
|
+
const plaintext = await aesDecrypt(blobBytes, aesKey);
|
|
212
|
+
instructions = new TextDecoder().decode(plaintext);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exec.status = 'working';
|
|
216
|
+
this.emit({ type: 'task_working', taskId });
|
|
217
|
+
// Call the user-provided execution handler with decrypted instructions
|
|
218
|
+
const result = await this.config.executeTask({
|
|
219
|
+
taskId,
|
|
220
|
+
task: a2a,
|
|
221
|
+
instructions,
|
|
222
|
+
});
|
|
223
|
+
exec.status = 'submitted';
|
|
224
|
+
this.emit({ type: 'task_executed', taskId });
|
|
225
|
+
// Submit result — the contract requires the assigned worker to sign
|
|
226
|
+
// submitEvidence personally (onlyWorker), so the backend hands back an
|
|
227
|
+
// unsigned tx instead of broadcasting it for us.
|
|
228
|
+
const submitResult = await this.bb.submitResult(taskId, result);
|
|
229
|
+
// Sign + broadcast submitEvidence with the runtime's own wallet, wait
|
|
230
|
+
// for confirmation, then tell the backend to proceed with
|
|
231
|
+
// verification. Without this the on-chain task never leaves
|
|
232
|
+
// 'Assigned' and completeVerification always reverts — the task only
|
|
233
|
+
// LOOKS complete.
|
|
234
|
+
if (submitResult.unsignedSubmitEvidence) {
|
|
235
|
+
const provider = new ethers.JsonRpcProvider(this.config.rpcUrl);
|
|
236
|
+
const signer = new ethers.Wallet(this.wallet.privateKey, provider);
|
|
237
|
+
const tx = await signer.sendTransaction(submitResult.unsignedSubmitEvidence);
|
|
238
|
+
await tx.wait();
|
|
239
|
+
}
|
|
240
|
+
const finalizeResult = await this.bb.finalize(taskId);
|
|
241
|
+
exec.status = 'completed';
|
|
242
|
+
this.emit({ type: 'task_submitted', taskId, result });
|
|
243
|
+
this.emit({ type: 'task_finalized', taskId, finalize: finalizeResult });
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
exec.status = 'failed';
|
|
247
|
+
exec.error = String(err);
|
|
248
|
+
this.emit({ type: 'task_failed', taskId, error: String(err) });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
252
|
+
/**
|
|
253
|
+
* Decode a wrapped-key hex string — acceptTask()'s wrappedKey field is a
|
|
254
|
+
* single hex string (this executor's ECIES-wrapped AES key), not a
|
|
255
|
+
* Record. The old code here called `Object.values` on that STRING
|
|
256
|
+
* argument (treating it as if it were a Record), which returned its
|
|
257
|
+
* first CHARACTER (e.g. "0" from "04a1b2…") — `clean.length / 2` was
|
|
258
|
+
* then 0 and every decrypt got an empty key.
|
|
259
|
+
*/
|
|
260
|
+
decodeWrappedKey(hex) {
|
|
261
|
+
if (!hex)
|
|
262
|
+
throw new Error('Empty wrapped key');
|
|
263
|
+
return this.hexToBytes(hex);
|
|
264
|
+
}
|
|
265
|
+
hexToBytes(hex) {
|
|
266
|
+
const clean = hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
267
|
+
const bytes = new Uint8Array(clean.length / 2);
|
|
268
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
269
|
+
bytes[i] = parseInt(clean.substring(i * 2, i * 2 + 2), 16);
|
|
270
|
+
}
|
|
271
|
+
return bytes;
|
|
272
|
+
}
|
|
273
|
+
/** Decode a base64 string (e.g. downloadBlob()'s `blob` field) into bytes. */
|
|
274
|
+
base64ToBytes(b64) {
|
|
275
|
+
return new Uint8Array(Buffer.from(b64, 'base64'));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WorkerRuntime, type WorkerRuntimeConfig, type ExecuteTaskHandler, type TaskContext, type TaskExecutionInfo, type WorkerRuntimeEvent, } from './WorkerRuntime.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WorkerRuntime, } from './WorkerRuntime.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ethers } from 'ethers';
|
|
2
|
-
import type { Address, Hex, HealthStatus, PlatformStats, OpenTask, TaskDetail, CreateTaskTx, ExecutorProfile, RegisterExecutorInput, DeployedAgentInfo, AgentWalletInfo, ReputationInfo, LeaderboardEntry, StorageUploadResult, Message, AgentSearchResult, TaskTemplate, VerifyTaskInput, A2ATaskState, CreateAgentParams, CreateAgentResult } from './types.js';
|
|
2
|
+
import type { Address, Hex, RootHash, HealthStatus, PlatformStats, OpenTask, TaskDetail, CreateTaskTx, ExecutorProfile, RegisterExecutorInput, DeployedAgentInfo, AgentWalletInfo, ReputationInfo, LeaderboardEntry, StorageUploadResult, Message, AgentSearchResult, TaskTemplate, VerifyTaskInput, A2ATaskState, CreateAgentParams, CreateAgentResult } from './types.js';
|
|
3
3
|
export interface BlindMarketConfig {
|
|
4
4
|
/** Backend API base URL (default: https://api.blindmarket.xyz) */
|
|
5
5
|
apiBase?: string;
|
|
@@ -207,15 +207,55 @@ export declare class BlindMarket {
|
|
|
207
207
|
}>;
|
|
208
208
|
/** Register intent to accept a task (bid). */
|
|
209
209
|
bidOnTask(taskId: string): Promise<void>;
|
|
210
|
-
/**
|
|
210
|
+
/**
|
|
211
|
+
* Accept a task and get the rootHash + ECIES-wrapped AES key for the
|
|
212
|
+
* caller's address. Requires executor auth.
|
|
213
|
+
*
|
|
214
|
+
* Note the shape here matches `backend/src/routes/a2a.ts`'s `/accept`
|
|
215
|
+
* handler exactly — there is no `task` field, and `wrappedKey` is a single
|
|
216
|
+
* hex string (this executor's slice), not a `Record`.
|
|
217
|
+
*/
|
|
211
218
|
acceptTask(taskId: string): Promise<{
|
|
212
|
-
|
|
213
|
-
|
|
219
|
+
taskId: string;
|
|
220
|
+
status: string;
|
|
221
|
+
rootHash?: RootHash;
|
|
222
|
+
/** ECIES-wrapped AES key, hex-encoded with no 0x prefix. Absent on public tasks or legacy tasks with no brief. */
|
|
223
|
+
wrappedKey?: string;
|
|
224
|
+
/** 'public' means the blob at rootHash is plaintext — no wrappedKey by design. Absent means private/encrypted. */
|
|
225
|
+
privacy?: 'public' | 'private';
|
|
226
|
+
alreadySettled?: boolean;
|
|
227
|
+
assignTxHash?: Hex;
|
|
214
228
|
}>;
|
|
215
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Submit result for an accepted task. Returns an unsigned `submitEvidence`
|
|
231
|
+
* transaction — the contract requires the assigned worker to sign it
|
|
232
|
+
* personally (`onlyWorker`), so the caller must sign + broadcast
|
|
233
|
+
* `unsignedSubmitEvidence` and then call `finalize()`.
|
|
234
|
+
*/
|
|
216
235
|
submitResult(taskId: string, resultData: Record<string, unknown>): Promise<{
|
|
217
|
-
|
|
218
|
-
|
|
236
|
+
taskId: string;
|
|
237
|
+
onChainTaskId?: string;
|
|
238
|
+
status: string;
|
|
239
|
+
evidenceHash?: Hex;
|
|
240
|
+
unsignedSubmitEvidence?: Record<string, unknown> | null;
|
|
241
|
+
}>;
|
|
242
|
+
/**
|
|
243
|
+
* Tell the backend your `submitEvidence` tx has confirmed on-chain so it
|
|
244
|
+
* can proceed with verification — auto-verify, or hand off to the poster
|
|
245
|
+
* / a designated verifier agent depending on the task's verificationMode.
|
|
246
|
+
* Call this after signing and broadcasting `unsignedSubmitEvidence` from
|
|
247
|
+
* `submitResult()`. Without this the escrow never settles.
|
|
248
|
+
*/
|
|
249
|
+
finalize(taskId: string): Promise<{
|
|
250
|
+
taskId: string;
|
|
251
|
+
status: string;
|
|
252
|
+
verifier?: string;
|
|
253
|
+
awaitingPosterApproval?: boolean;
|
|
254
|
+
verificationResult?: {
|
|
255
|
+
passed: boolean;
|
|
256
|
+
reasons?: string[];
|
|
257
|
+
};
|
|
258
|
+
reconciled?: boolean;
|
|
219
259
|
}>;
|
|
220
260
|
/** Get tasks posted by the authenticated user. */
|
|
221
261
|
getPostedTasks(): Promise<{
|
|
@@ -252,9 +292,15 @@ export declare class BlindMarket {
|
|
|
252
292
|
getLeaderboard(limit?: number): Promise<LeaderboardEntry[]>;
|
|
253
293
|
/** Upload an encrypted blob to 0G Storage. */
|
|
254
294
|
uploadBlob(data: Hex): Promise<StorageUploadResult>;
|
|
255
|
-
/**
|
|
295
|
+
/**
|
|
296
|
+
* Download a blob by root hash. `blob` is base64-encoded raw bytes — the
|
|
297
|
+
* caller decodes (and, for encrypted tasks, decrypts) it client-side.
|
|
298
|
+
* Matches `backend/src/routes/storage.ts`'s `GET /:rootHash` handler,
|
|
299
|
+
* which returns `{ rootHash, blob }`, not `{ data }`.
|
|
300
|
+
*/
|
|
256
301
|
downloadBlob(rootHash: Hex): Promise<{
|
|
257
|
-
|
|
302
|
+
rootHash: Hex;
|
|
303
|
+
blob: string;
|
|
258
304
|
}>;
|
|
259
305
|
/** Send a message to another user or agent. */
|
|
260
306
|
sendMessage(params: {
|
|
@@ -298,6 +344,14 @@ export declare class BlindMarket {
|
|
|
298
344
|
* Poll an agent's status at a fixed interval. Returns an unsubscribe function.
|
|
299
345
|
*/
|
|
300
346
|
watchAgent(agentId: string, callback: (agent: DeployedAgentInfo) => void, intervalMs?: number): () => void;
|
|
347
|
+
/**
|
|
348
|
+
* Canonical registration challenge signed by the agent wallet, proving
|
|
349
|
+
* control of `agentWallet`. Must stay byte-identical to
|
|
350
|
+
* `agentRegistrationMessage` in `backend/src/routes/registration.ts` — the
|
|
351
|
+
* SDK cannot import from the backend, so this is duplicated. A mismatch
|
|
352
|
+
* here silently breaks registration.
|
|
353
|
+
*/
|
|
354
|
+
private static agentRegistrationMessage;
|
|
301
355
|
/**
|
|
302
356
|
* Start a device-flow registration session. Generates a magic-link URL
|
|
303
357
|
* that the user opens in a browser to sign with their wallet.
|
|
@@ -305,6 +359,11 @@ export declare class BlindMarket {
|
|
|
305
359
|
* After registering, call `BlindMarket.pollSession(token)` to wait for
|
|
306
360
|
* the user to confirm and receive the API key.
|
|
307
361
|
*
|
|
362
|
+
* Requires proof of control of `agentWallet`: pass either `agentSigner`
|
|
363
|
+
* (anything with a `signMessage`, e.g. an ethers `Wallet`) so the SDK signs
|
|
364
|
+
* the challenge itself, or a pre-computed `agentSignature` for callers
|
|
365
|
+
* holding a remote signer. Exactly one of the two is required.
|
|
366
|
+
*
|
|
308
367
|
* @example
|
|
309
368
|
* ```ts
|
|
310
369
|
* const wallet = ethers.Wallet.createRandom();
|
|
@@ -312,6 +371,7 @@ export declare class BlindMarket {
|
|
|
312
371
|
* agentName: 'my-agent',
|
|
313
372
|
* agentWallet: wallet.address,
|
|
314
373
|
* agentPublicKey: wallet.publicKey,
|
|
374
|
+
* agentSigner: wallet,
|
|
315
375
|
* });
|
|
316
376
|
* console.log('Open', url, 'to confirm');
|
|
317
377
|
* const apiKey = await BlindMarket.pollSession(token);
|
|
@@ -322,6 +382,12 @@ export declare class BlindMarket {
|
|
|
322
382
|
agentName: string;
|
|
323
383
|
agentWallet: string;
|
|
324
384
|
agentPublicKey: string;
|
|
385
|
+
/** Signs the registration challenge proving control of agentWallet. */
|
|
386
|
+
agentSigner?: {
|
|
387
|
+
signMessage(message: string): Promise<string>;
|
|
388
|
+
};
|
|
389
|
+
/** Pre-computed alternative to agentSigner, for callers holding a remote signer. */
|
|
390
|
+
agentSignature?: string;
|
|
325
391
|
apiBase?: string;
|
|
326
392
|
}): Promise<{
|
|
327
393
|
token: string;
|
|
@@ -343,4 +409,11 @@ export { ApiError };
|
|
|
343
409
|
export { tools, createBlindMarketTools, createTaskTools, createAgentManagementTools, createA2ATools, toLangChainTools, toVercelTools, toOpenAITools, toClaudeTools, } from './tools/index.js';
|
|
344
410
|
export type { Tool, ToolKit, ToolDefinition } from './tools/types.js';
|
|
345
411
|
export type { BlindMarketTools } from './tools/index.js';
|
|
412
|
+
export * from './executor/index.js';
|
|
346
413
|
export * from './types.js';
|
|
414
|
+
export { EVMChainAdapter } from './chain/evm/index.js';
|
|
415
|
+
export { createChainAdapter } from './chain/createChainAdapter.js';
|
|
416
|
+
export type { CreateChainAdapterOptions } from './chain/createChainAdapter.js';
|
|
417
|
+
export type { IBlindMarketChain } from './chain/IBlindMarketChain.js';
|
|
418
|
+
export type { Network, EVMNetwork, BlockchainType, CreateTaskParams, DomainTask, DomainTaskMeta, DomainReputation, DomainTxReceipt, ChainSigner, } from './chain/domain-types.js';
|
|
419
|
+
export { isEVMNetwork } from './network/resolve.js';
|