@blindmarket/sdk 0.1.6 → 0.1.8
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/README.md +242 -29
- package/dist/chain/BlindEscrowClient.d.ts +2 -0
- package/dist/chain/BlindEscrowClient.js +19 -7
- package/dist/chain/BlindReputationClient.d.ts +3 -0
- package/dist/chain/BlindReputationClient.js +3 -1
- package/dist/chain/ChainClient.d.ts +6 -3
- package/dist/chain/ChainClient.js +6 -1
- package/dist/chain/IBlindMarketChain.d.ts +74 -0
- package/dist/chain/IBlindMarketChain.js +1 -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/domain-types.js +1 -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/crypto/constants.js +1 -1
- package/dist/errors/{BlindBountyError.d.ts → BlindMarketError.d.ts} +1 -1
- package/dist/errors/{BlindBountyError.js → BlindMarketError.js} +1 -1
- package/dist/errors/index.d.ts +1 -1
- package/dist/errors/index.js +1 -1
- package/dist/errors/subclasses.d.ts +9 -9
- package/dist/errors/subclasses.js +9 -9
- package/dist/executor/WorkerRuntime.d.ts +113 -0
- package/dist/executor/WorkerRuntime.js +248 -0
- package/dist/executor/index.d.ts +1 -0
- package/dist/executor/index.js +1 -0
- package/dist/index.d.ts +292 -22
- package/dist/index.js +413 -23
- package/dist/keystore/InMemoryKeyStore.js +2 -2
- 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/signer/EthersSigner.js +1 -1
- package/dist/storage/index.d.ts +0 -1
- package/dist/storage/index.js +1 -1
- package/dist/tools/adapters.d.ts +99 -0
- package/dist/tools/adapters.js +129 -0
- package/dist/tools/helpers.d.ts +9 -0
- package/dist/tools/helpers.js +169 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +32 -0
- package/dist/tools/types.d.ts +44 -0
- package/dist/tools/types.js +1 -0
- package/dist/types.d.ts +202 -1
- package/dist/types.js +24 -1
- package/package.json +13 -2
- package/src/chain/BlindEscrowClient.ts +20 -6
- package/src/chain/BlindReputationClient.ts +6 -1
- package/src/chain/ChainClient.ts +9 -3
- package/src/chain/IBlindMarketChain.ts +109 -0
- package/src/chain/TaskRegistryClient.ts +17 -8
- package/src/chain/createChainAdapter.ts +40 -0
- package/src/chain/domain-types.ts +92 -0
- package/src/chain/evm/EVMChainAdapter.ts +212 -0
- package/src/chain/evm/index.ts +1 -0
- package/src/chain/index.ts +6 -0
- package/src/executor/WorkerRuntime.ts +338 -0
- package/src/executor/index.ts +8 -0
- package/src/index.ts +546 -36
- package/src/network/presets.ts +47 -8
- package/src/network/resolve.ts +20 -8
- package/src/network/types.ts +18 -16
- package/src/signer/index.ts +1 -1
- package/src/storage/index.ts +1 -1
- package/src/tools/adapters.ts +177 -0
- package/src/tools/helpers.ts +199 -0
- package/src/tools/index.ts +59 -0
- package/src/tools/types.ts +47 -0
- package/src/types.ts +222 -1
- package/tsconfig.json +2 -1
- 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/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/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
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Address, Hex, TaskId, TaskStatus } from '../types.js';
|
|
2
|
+
|
|
3
|
+
// ── Chain-agnostic domain types ───────────────────────────────────────────
|
|
4
|
+
// These types are the BlindMarket domain model, independent of any specific
|
|
5
|
+
// blockchain. Each chain adapter translates between these and its native
|
|
6
|
+
// contract types.
|
|
7
|
+
|
|
8
|
+
export type BlockchainType = 'evm';
|
|
9
|
+
|
|
10
|
+
/** Base fields shared by all chain network configs. */
|
|
11
|
+
export interface NetworkBase {
|
|
12
|
+
name: string;
|
|
13
|
+
chainType: BlockchainType;
|
|
14
|
+
rpc: string[];
|
|
15
|
+
explorer?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** EVM-compatible chain network config (0G, Ethereum, Polygon, etc.). */
|
|
19
|
+
export interface EVMNetwork extends NetworkBase {
|
|
20
|
+
chainType: 'evm';
|
|
21
|
+
chainId: number;
|
|
22
|
+
contracts: {
|
|
23
|
+
escrow: Address;
|
|
24
|
+
registry: Address;
|
|
25
|
+
reputation: Address;
|
|
26
|
+
usdc: Address;
|
|
27
|
+
};
|
|
28
|
+
indexer?: string;
|
|
29
|
+
broker?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Union of all supported chain network configs. */
|
|
33
|
+
export type Network = EVMNetwork;
|
|
34
|
+
|
|
35
|
+
/** Parameters for creating a task on any chain. */
|
|
36
|
+
export interface CreateTaskParams {
|
|
37
|
+
taskHash: Hex;
|
|
38
|
+
token: Address | string;
|
|
39
|
+
amount: bigint;
|
|
40
|
+
category: string;
|
|
41
|
+
locationZone: string;
|
|
42
|
+
deadline: bigint;
|
|
43
|
+
/** Per-task verifier address (agent-verification mode). Optional. */
|
|
44
|
+
verifierAgent?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Decoded on-chain task representation (chain-agnostic). */
|
|
48
|
+
export interface DomainTask {
|
|
49
|
+
taskId: TaskId;
|
|
50
|
+
agent: string;
|
|
51
|
+
worker: string;
|
|
52
|
+
token: string;
|
|
53
|
+
amount: bigint;
|
|
54
|
+
taskHash: Hex;
|
|
55
|
+
evidenceHash: Hex;
|
|
56
|
+
status: TaskStatus;
|
|
57
|
+
category: string;
|
|
58
|
+
locationZone: string;
|
|
59
|
+
createdAt: Date;
|
|
60
|
+
deadline: Date;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Task metadata used for discovery / listing. */
|
|
64
|
+
export interface DomainTaskMeta {
|
|
65
|
+
taskId: TaskId;
|
|
66
|
+
agent: string;
|
|
67
|
+
category: string;
|
|
68
|
+
locationZone: string;
|
|
69
|
+
reward: bigint;
|
|
70
|
+
createdAt: Date;
|
|
71
|
+
isOpen: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** On-chain reputation snapshot (chain-agnostic). */
|
|
75
|
+
export interface DomainReputation {
|
|
76
|
+
tasksCompleted: bigint;
|
|
77
|
+
totalScore: bigint;
|
|
78
|
+
disputes: bigint;
|
|
79
|
+
avgScore: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Transaction receipt (chain-agnostic). */
|
|
83
|
+
export interface DomainTxReceipt {
|
|
84
|
+
hash: string;
|
|
85
|
+
blockNumber?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Signer abstraction that works across chains. */
|
|
89
|
+
export interface ChainSigner {
|
|
90
|
+
getAddress(): Promise<string>;
|
|
91
|
+
getChainType(): BlockchainType;
|
|
92
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
import type { Address, Hex } from '../../types.js';
|
|
3
|
+
import { ChainClient, type ChainClientOptions } from '../ChainClient.js';
|
|
4
|
+
import { resolveNetwork } from '../../network/resolve.js';
|
|
5
|
+
import type { EthersSigner } from '../../signer/EthersSigner.js';
|
|
6
|
+
import type { Signer } from '../../signer/Signer.js';
|
|
7
|
+
import type {
|
|
8
|
+
CreateTaskParams,
|
|
9
|
+
DomainReputation,
|
|
10
|
+
DomainTask,
|
|
11
|
+
DomainTaskMeta,
|
|
12
|
+
DomainTxReceipt,
|
|
13
|
+
EVMNetwork,
|
|
14
|
+
} from '../domain-types.js';
|
|
15
|
+
import type { IBlindMarketChain } from '../IBlindMarketChain.js';
|
|
16
|
+
import type { OpenTaskMeta } from '../TaskRegistryClient.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* EVM-compatible implementation of IBlindMarketChain.
|
|
20
|
+
*
|
|
21
|
+
* Wraps the existing ChainClient (which bundles BlindEscrowClient,
|
|
22
|
+
* TaskRegistryClient, and BlindReputationClient) and translates domain
|
|
23
|
+
* operations into ethers v6 contract calls on EVM chains (0G, Ethereum, etc.).
|
|
24
|
+
*/
|
|
25
|
+
export class EVMChainAdapter implements IBlindMarketChain {
|
|
26
|
+
readonly network: EVMNetwork;
|
|
27
|
+
private client: ChainClient;
|
|
28
|
+
private signer: EthersSigner | undefined;
|
|
29
|
+
|
|
30
|
+
constructor(opts: ChainClientOptions) {
|
|
31
|
+
const resolved = resolveNetwork(opts.network);
|
|
32
|
+
if (resolved.chainType !== 'evm') {
|
|
33
|
+
throw new Error(`EVMChainAdapter requires an EVM network, got ${resolved.chainType}`);
|
|
34
|
+
}
|
|
35
|
+
this.network = resolved;
|
|
36
|
+
this.client = new ChainClient(opts);
|
|
37
|
+
this.signer = opts.signer as EthersSigner | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getAddress(): Promise<string> {
|
|
41
|
+
if (!this.signer) throw new Error('No signer configured');
|
|
42
|
+
return this.signer.getAddress();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ── Task lifecycle ──────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
async createTask(params: CreateTaskParams): Promise<{ taskId: bigint; receipt: DomainTxReceipt }> {
|
|
48
|
+
const result = await this.client.escrow.createTask({
|
|
49
|
+
taskHash: params.taskHash,
|
|
50
|
+
token: params.token as Address,
|
|
51
|
+
amount: params.amount,
|
|
52
|
+
category: params.category,
|
|
53
|
+
locationZone: params.locationZone,
|
|
54
|
+
deadline: params.deadline,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
taskId: result.taskId,
|
|
58
|
+
receipt: {
|
|
59
|
+
hash: result.receipt.hash,
|
|
60
|
+
blockNumber: result.receipt.blockNumber,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async assignWorker(taskId: bigint, worker: string): Promise<DomainTxReceipt> {
|
|
66
|
+
const receipt = await this.client.escrow.assignWorker(taskId, worker as Address);
|
|
67
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async submitEvidence(taskId: bigint, evidenceHash: string): Promise<DomainTxReceipt> {
|
|
71
|
+
const receipt = await this.client.escrow.submitEvidence(taskId, evidenceHash as Hex);
|
|
72
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async completeVerification(taskId: bigint, passed: boolean): Promise<DomainTxReceipt> {
|
|
76
|
+
const receipt = await this.client.escrow.completeVerification(taskId, passed);
|
|
77
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async cancelTask(taskId: bigint): Promise<DomainTxReceipt> {
|
|
81
|
+
const receipt = await this.client.escrow.cancelTask(taskId);
|
|
82
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async claimTimeout(taskId: bigint): Promise<DomainTxReceipt> {
|
|
86
|
+
const receipt = await this.client.escrow.claimTimeout(taskId);
|
|
87
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async raiseDispute(taskId: bigint): Promise<DomainTxReceipt> {
|
|
91
|
+
const receipt = await this.client.escrow.raiseDispute(taskId);
|
|
92
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async resolveDispute(taskId: bigint, workerFavored: boolean): Promise<DomainTxReceipt> {
|
|
96
|
+
const receipt = await this.client.escrow.resolveDispute(taskId, workerFavored);
|
|
97
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async getTask(taskId: bigint): Promise<DomainTask> {
|
|
101
|
+
const onChain = await this.client.escrow.getTask(taskId);
|
|
102
|
+
return {
|
|
103
|
+
...onChain,
|
|
104
|
+
category: '', // OnChainTask doesn't decode these contract fields;
|
|
105
|
+
locationZone: '', // they are present in the Solidity Task struct.
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async isTaskExpired(taskId: bigint): Promise<boolean> {
|
|
110
|
+
return this.client.escrow.isTaskExpired(taskId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Task discovery ──────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
async publishTask(taskId: bigint, _meta: Omit<DomainTaskMeta, 'taskId'> & { taskId?: bigint }): Promise<DomainTxReceipt> {
|
|
116
|
+
// The EVM contracts publish tasks via the escrow's TaskCreated event
|
|
117
|
+
// (handled by the backend indexer). This is a no-op at the adapter level
|
|
118
|
+
// for EVM — the registry is updated by the escrow contract internally.
|
|
119
|
+
// Return a stub receipt for interface compatibility.
|
|
120
|
+
return { hash: '0x0', blockNumber: 0 };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async closeTask(taskId: bigint): Promise<DomainTxReceipt> {
|
|
124
|
+
const receipt = await this.client.registry.closeTask(taskId);
|
|
125
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async getOpenTasks(offset: bigint, limit: bigint): Promise<DomainTaskMeta[]> {
|
|
129
|
+
const taskIds = await this.client.registry.getOpenTasks(offset, limit);
|
|
130
|
+
const metas = await Promise.all(
|
|
131
|
+
taskIds.map(async (tid: bigint) => {
|
|
132
|
+
const meta = await this.client.registry.getTaskMeta(tid);
|
|
133
|
+
return {
|
|
134
|
+
taskId: tid,
|
|
135
|
+
agent: '' as Address, // EVM TaskRegistry stores agent; use meta directly
|
|
136
|
+
category: meta.category,
|
|
137
|
+
locationZone: meta.locationZone,
|
|
138
|
+
reward: meta.reward,
|
|
139
|
+
createdAt: meta.createdAt,
|
|
140
|
+
isOpen: true,
|
|
141
|
+
} as DomainTaskMeta;
|
|
142
|
+
}),
|
|
143
|
+
);
|
|
144
|
+
return metas;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async openTaskCount(): Promise<bigint> {
|
|
148
|
+
return this.client.registry.openTaskCount();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async totalTasks(): Promise<bigint> {
|
|
152
|
+
return this.client.registry.totalTasks();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Reputation ──────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
async rateWorker(worker: string, score: number, taskId: bigint): Promise<DomainTxReceipt> {
|
|
158
|
+
const receipt = await this.client.reputation.rate(worker as Address, score, taskId);
|
|
159
|
+
return { hash: receipt.hash, blockNumber: receipt.blockNumber };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async recordDispute(worker: string, taskId: bigint): Promise<DomainTxReceipt> {
|
|
163
|
+
// EVM BlindReputation doesn't expose recordDispute as a standalone client
|
|
164
|
+
// method in the SDK — the escrow calls it internally. For external callers
|
|
165
|
+
// we throw. The escrow contract handles dispute recording via its own path.
|
|
166
|
+
throw new Error('recordDispute is handled internally by BlindEscrow on EVM chains');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async getReputation(worker: string): Promise<DomainReputation> {
|
|
170
|
+
return this.client.reputation.getReputation(worker as Address);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async hasBeenRated(worker: string, taskId: bigint): Promise<boolean> {
|
|
174
|
+
return this.client.reputation.hasBeenRated(worker as Address, taskId);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Agent identity ──────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
async mintAgent(params: {
|
|
180
|
+
to: string;
|
|
181
|
+
encryptedURI: string;
|
|
182
|
+
metadataHash: string;
|
|
183
|
+
}): Promise<{ tokenId: bigint; receipt: DomainTxReceipt }> {
|
|
184
|
+
// INFT minting is handled by the backend (services/chain.ts → inft.mint()).
|
|
185
|
+
// The SDK doesn't expose a direct INFT client. For direct EVM calls,
|
|
186
|
+
// consumers can use the raw ethers.Contract via the unwrapped signer.
|
|
187
|
+
throw new Error('INFT minting is backend-managed on EVM chains. Use the REST API or raw ethers.Contract.');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async getEncryptedURI(_tokenId: bigint): Promise<string> {
|
|
191
|
+
throw new Error('INFT URI reading is backend-managed on EVM chains. Use the REST API or raw ethers.Contract.');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Queries ─────────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
async getBalance(address: string): Promise<bigint> {
|
|
197
|
+
return this.client.provider.getBalance(address);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ── Accessors for direct ethers access (advanced use) ───────────────────
|
|
201
|
+
|
|
202
|
+
/** Get the underlying ethers Provider for raw queries. */
|
|
203
|
+
getProvider(): ethers.Provider {
|
|
204
|
+
return this.client.provider;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Get the underlying ethers Signer (if configured). */
|
|
208
|
+
getEthersSigner(): ethers.Signer | undefined {
|
|
209
|
+
if (!this.signer) return undefined;
|
|
210
|
+
return (this.signer as unknown as { inner?: ethers.Signer }).inner;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { EVMChainAdapter } from './EVMChainAdapter.js';
|
package/src/chain/index.ts
CHANGED
|
@@ -4,3 +4,9 @@ export * from './BlindEscrowClient.js';
|
|
|
4
4
|
export * from './TaskRegistryClient.js';
|
|
5
5
|
export * from './BlindReputationClient.js';
|
|
6
6
|
export * from './ChainClient.js';
|
|
7
|
+
|
|
8
|
+
// Chain-agnostic domain interface (EVM only — 0G Chain)
|
|
9
|
+
export * from './IBlindMarketChain.js';
|
|
10
|
+
export * from './domain-types.js';
|
|
11
|
+
export * from './createChainAdapter.js';
|
|
12
|
+
export * from './evm/index.js';
|
|
@@ -0,0 +1,338 @@
|
|
|
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
|
+
}
|