@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
package/examples/agent-demo.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent → Worker end-to-end demo, using MemoryStorage + a mock escrow so the
|
|
3
|
-
* script runs anywhere without an RPC or funded wallet. Shows the full flow:
|
|
4
|
-
*
|
|
5
|
-
* 1. Agent creates an encrypted task (AES-encrypt instructions, upload blob)
|
|
6
|
-
* 2. Agent assigns a worker (ECIES-wrap the AES key to worker's pubkey)
|
|
7
|
-
* 3. Worker decrypts the instructions
|
|
8
|
-
* 4. Worker submits evidence (seals a dual-recipient envelope for agent + TEE)
|
|
9
|
-
* 5. Agent decrypts the evidence
|
|
10
|
-
*
|
|
11
|
-
* Run from sdk/: npx tsx examples/agent-demo.ts
|
|
12
|
-
*
|
|
13
|
-
* For the real chain, swap MockEscrow for `new ChainClient({ network, signer }).escrow`
|
|
14
|
-
* and swap MemoryStorage for `new ZgStorage({ indexerUrl, rpcUrl, signer: ethersWallet })`.
|
|
15
|
-
*/
|
|
16
|
-
import { Agent } from '../src/agent/index.js';
|
|
17
|
-
import type { BlindEscrowClient, CreateTaskArgs } from '../src/chain/index.js';
|
|
18
|
-
import { generateKeyPair } from '../src/crypto/index.js';
|
|
19
|
-
import { EventBus, type SdkEvents } from '../src/events/index.js';
|
|
20
|
-
import { InMemoryKeyStore } from '../src/keystore/index.js';
|
|
21
|
-
import { PrivateKeySigner } from '../src/signer/index.js';
|
|
22
|
-
import { MemoryStorage } from '../src/storage/index.js';
|
|
23
|
-
import type { Address, Hex, RootHash, TaskId, TxReceiptLike } from '../src/types.js';
|
|
24
|
-
import { Worker } from '../src/worker/index.js';
|
|
25
|
-
|
|
26
|
-
// ── Mock escrow for offline demo (use real ChainClient in production) ──
|
|
27
|
-
class MockEscrow {
|
|
28
|
-
private nextId = 1n;
|
|
29
|
-
private evidenceByTask = new Map<string, RootHash>();
|
|
30
|
-
async createTask(args: CreateTaskArgs) {
|
|
31
|
-
const taskId = this.nextId++;
|
|
32
|
-
return { taskId, receipt: r('0xa') };
|
|
33
|
-
}
|
|
34
|
-
async assignWorker(_taskId: TaskId, _worker: Address) { return r('0xb'); }
|
|
35
|
-
async cancelTask(_taskId: TaskId) { return r('0xc'); }
|
|
36
|
-
async submitEvidence(taskId: TaskId, evidenceHash: Hex) {
|
|
37
|
-
this.evidenceByTask.set(taskId.toString(), evidenceHash as RootHash);
|
|
38
|
-
return r('0xd');
|
|
39
|
-
}
|
|
40
|
-
getEvidenceHash(taskId: TaskId) { return this.evidenceByTask.get(taskId.toString()); }
|
|
41
|
-
}
|
|
42
|
-
function r(prefix: string): TxReceiptLike {
|
|
43
|
-
return { hash: (prefix + 'f'.repeat(63 - prefix.length + 2)).slice(0, 66) as Hex, blockNumber: 1, gasUsed: 21000n };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// ── Shared deps ──
|
|
47
|
-
const AGENT_PK = '0x' + '1'.repeat(64);
|
|
48
|
-
const WORKER_PK = '0x' + '2'.repeat(64);
|
|
49
|
-
const USDC = '0x317227efcA18D004E12CA8046AEf7E1597458F25' as Address;
|
|
50
|
-
|
|
51
|
-
const storage = new MemoryStorage();
|
|
52
|
-
const escrow = new MockEscrow();
|
|
53
|
-
const events = new EventBus<SdkEvents>();
|
|
54
|
-
events.on('task.created', (p) => console.log(` • task.created taskId=${p.taskId} rootHash=${p.rootHash.slice(0, 18)}…`));
|
|
55
|
-
events.on('task.assigned', (p) => console.log(` • task.assigned taskId=${p.taskId} worker=${p.worker.slice(0, 10)}…`));
|
|
56
|
-
events.on('evidence.submitted', (p) => console.log(` • evidence.submitted taskId=${p.taskId} rootHash=${p.rootHash.slice(0, 18)}…`));
|
|
57
|
-
|
|
58
|
-
// ── Identities ──
|
|
59
|
-
const agentSigner = new PrivateKeySigner(AGENT_PK);
|
|
60
|
-
const workerSigner = new PrivateKeySigner(WORKER_PK);
|
|
61
|
-
const agentPubKey = await agentSigner.getPublicKey();
|
|
62
|
-
const workerPubKey = await workerSigner.getPublicKey();
|
|
63
|
-
const workerAddr = (await workerSigner.getAddress()) as Address;
|
|
64
|
-
const enclaveKeys = generateKeyPair(); // TEE pubkey — fetched from broker in production
|
|
65
|
-
|
|
66
|
-
const agent = new Agent({
|
|
67
|
-
// biome-ignore lint/suspicious/noExplicitAny: demo mock
|
|
68
|
-
escrow: escrow as any as BlindEscrowClient,
|
|
69
|
-
storage,
|
|
70
|
-
keystore: new InMemoryKeyStore(),
|
|
71
|
-
signer: agentSigner,
|
|
72
|
-
events,
|
|
73
|
-
});
|
|
74
|
-
const worker = new Worker({
|
|
75
|
-
// biome-ignore lint/suspicious/noExplicitAny: demo mock
|
|
76
|
-
escrow: escrow as any as BlindEscrowClient,
|
|
77
|
-
// biome-ignore lint/suspicious/noExplicitAny: demo: no registry needed for this path
|
|
78
|
-
registry: null as any,
|
|
79
|
-
storage,
|
|
80
|
-
signer: workerSigner,
|
|
81
|
-
events,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
// ── 1. Agent creates an encrypted task ──
|
|
85
|
-
console.log('\n[1] Agent creates encrypted task');
|
|
86
|
-
const created = await agent.createTask({
|
|
87
|
-
instructions: 'Take 3 photos of the storefront at 123 Main St, Lagos',
|
|
88
|
-
category: 'photography',
|
|
89
|
-
locationZone: 'Lagos, NG',
|
|
90
|
-
reward: { token: USDC, amount: 5_000000n },
|
|
91
|
-
deadline: new Date(Date.now() + 24 * 3600_000),
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// ── 2. Agent assigns the worker ──
|
|
95
|
-
console.log('\n[2] Agent assigns worker (wraps AES key to worker pubkey)');
|
|
96
|
-
const { wrappedKey } = await agent.assignWorker(created.taskId, workerAddr, workerPubKey);
|
|
97
|
-
|
|
98
|
-
// ── 3. Worker decrypts the instructions ──
|
|
99
|
-
console.log('\n[3] Worker decrypts instructions');
|
|
100
|
-
const instructions = await worker.decryptInstructions({
|
|
101
|
-
wrappedKey,
|
|
102
|
-
workerPrivKey: WORKER_PK,
|
|
103
|
-
taskHash: created.rootHash,
|
|
104
|
-
});
|
|
105
|
-
console.log(` → "${new TextDecoder().decode(instructions)}"`);
|
|
106
|
-
|
|
107
|
-
// ── 4. Worker submits evidence ──
|
|
108
|
-
console.log('\n[4] Worker submits evidence (dual-recipient envelope: agent + TEE)');
|
|
109
|
-
const submitted = await worker.submitEvidence({
|
|
110
|
-
taskId: created.taskId,
|
|
111
|
-
evidence: 'photo1.jpg photo2.jpg photo3.jpg (attached)',
|
|
112
|
-
agentPubKey,
|
|
113
|
-
enclavePubKey: enclaveKeys.publicKey,
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// ── 5. Agent fetches + decrypts evidence ──
|
|
117
|
-
console.log('\n[5] Agent decrypts evidence');
|
|
118
|
-
const recovered = await agent.fetchAndDecryptEvidence(submitted.rootHash, AGENT_PK);
|
|
119
|
-
console.log(` → "${new TextDecoder().decode(recovered)}"`);
|
|
120
|
-
|
|
121
|
-
console.log('\n✓ full lifecycle completed');
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Generates cross-compat crypto fixtures using Node's built-in crypto, matching
|
|
4
|
-
* backend/src/services/crypto.ts byte-for-byte. The SDK's test/crypto.compat.test.ts
|
|
5
|
-
* loads these and verifies WebCrypto-based decryption produces the same plaintext.
|
|
6
|
-
*
|
|
7
|
-
* Run: node scripts/gen-crypto-fixtures.mjs
|
|
8
|
-
* Output: test/fixtures/crypto.json (committed to the repo; regenerate only on
|
|
9
|
-
* intentional wire-format changes).
|
|
10
|
-
*/
|
|
11
|
-
import { createCipheriv, createECDH, hkdfSync, randomBytes } from 'node:crypto';
|
|
12
|
-
import { writeFileSync } from 'node:fs';
|
|
13
|
-
import { dirname, resolve } from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
|
|
16
|
-
const IV_LENGTH = 12;
|
|
17
|
-
const TAG_LENGTH = 16;
|
|
18
|
-
const KEY_LENGTH = 32;
|
|
19
|
-
const ECIES_HKDF_INFO = 'BlindMarket-ECIES-v1';
|
|
20
|
-
|
|
21
|
-
function aesEncrypt(plaintext, key) {
|
|
22
|
-
const iv = randomBytes(IV_LENGTH);
|
|
23
|
-
const cipher = createCipheriv('aes-256-gcm', key, iv, { authTagLength: TAG_LENGTH });
|
|
24
|
-
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
25
|
-
const tag = cipher.getAuthTag();
|
|
26
|
-
// [IV][tag][ciphertext] — matches backend/src/services/crypto.ts
|
|
27
|
-
return Buffer.concat([iv, tag, encrypted]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function eciesEncrypt(plaintext, recipientPubKeyBuf) {
|
|
31
|
-
const ephemeral = createECDH('secp256k1');
|
|
32
|
-
ephemeral.generateKeys();
|
|
33
|
-
const shared = ephemeral.computeSecret(recipientPubKeyBuf);
|
|
34
|
-
const aesKey = Buffer.from(hkdfSync('sha256', shared, '', ECIES_HKDF_INFO, KEY_LENGTH));
|
|
35
|
-
const aesBlob = aesEncrypt(plaintext, aesKey);
|
|
36
|
-
return Buffer.concat([ephemeral.getPublicKey(), aesBlob]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const recipient = createECDH('secp256k1');
|
|
40
|
-
recipient.generateKeys();
|
|
41
|
-
const privateKey = recipient.getPrivateKey('hex');
|
|
42
|
-
const publicKey = recipient.getPublicKey('hex', 'uncompressed');
|
|
43
|
-
|
|
44
|
-
const plaintext = 'hello cross-compat';
|
|
45
|
-
const ptBuf = Buffer.from(plaintext, 'utf-8');
|
|
46
|
-
|
|
47
|
-
// AES fixture
|
|
48
|
-
const aesKey = randomBytes(KEY_LENGTH);
|
|
49
|
-
const aesBlob = aesEncrypt(ptBuf, aesKey);
|
|
50
|
-
|
|
51
|
-
// ECIES fixture (recipient's pubkey is encoded as Buffer for computeSecret)
|
|
52
|
-
const eciesBlob = eciesEncrypt(ptBuf, Buffer.from(publicKey, 'hex'));
|
|
53
|
-
|
|
54
|
-
const fixture = {
|
|
55
|
-
_note:
|
|
56
|
-
'Generated by scripts/gen-crypto-fixtures.mjs using Node built-in crypto, matching backend/src/services/crypto.ts. If the SDK decrypts these back to the expected plaintext, wire-format compatibility is proven. Regenerate only on intentional format changes.',
|
|
57
|
-
hkdf_info: ECIES_HKDF_INFO,
|
|
58
|
-
keypair: { privateKey, publicKey },
|
|
59
|
-
plaintext,
|
|
60
|
-
aes: {
|
|
61
|
-
key: aesKey.toString('hex'),
|
|
62
|
-
blob: aesBlob.toString('hex'),
|
|
63
|
-
},
|
|
64
|
-
ecies: {
|
|
65
|
-
blob: eciesBlob.toString('hex'),
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
70
|
-
const out = resolve(__dirname, '..', 'test', 'fixtures', 'crypto.json');
|
|
71
|
-
writeFileSync(out, `${JSON.stringify(fixture, null, 2)}\n`);
|
|
72
|
-
console.log(`wrote ${out}`);
|
package/src/agent/Agent.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { BlindEscrowClient } from '../chain/index.js';
|
|
2
|
-
import {
|
|
3
|
-
aesEncrypt,
|
|
4
|
-
eciesEncrypt,
|
|
5
|
-
generateAesKey,
|
|
6
|
-
openEvidenceAsAgent,
|
|
7
|
-
} from '../crypto/index.js';
|
|
8
|
-
import { LifecycleError } from '../errors/index.js';
|
|
9
|
-
import type { EventBus, SdkEvents } from '../events/index.js';
|
|
10
|
-
import type { KeyStore } from '../keystore/index.js';
|
|
11
|
-
import type { Signer } from '../signer/index.js';
|
|
12
|
-
import type { Storage } from '../storage/index.js';
|
|
13
|
-
import type { Address, Hex, Reward, TaskId, TaskKey, TxReceiptLike } from '../types.js';
|
|
14
|
-
|
|
15
|
-
export interface AgentDeps {
|
|
16
|
-
escrow: BlindEscrowClient;
|
|
17
|
-
storage: Storage;
|
|
18
|
-
keystore: KeyStore;
|
|
19
|
-
signer: Signer;
|
|
20
|
-
events?: EventBus<SdkEvents>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface CreateTaskInput {
|
|
24
|
-
instructions: string | Uint8Array;
|
|
25
|
-
category: string;
|
|
26
|
-
locationZone: string;
|
|
27
|
-
reward: Reward & { token: Address };
|
|
28
|
-
deadline: Date | bigint;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CreateTaskResult {
|
|
32
|
-
taskId: TaskId;
|
|
33
|
-
taskKey: TaskKey;
|
|
34
|
-
rootHash: Hex;
|
|
35
|
-
txHash: Hex;
|
|
36
|
-
receipt: TxReceiptLike;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface AssignWorkerResult {
|
|
40
|
-
wrappedKey: Uint8Array;
|
|
41
|
-
receipt: TxReceiptLike;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* End-to-end agent flows. Composes storage + crypto + chain + keystore so a
|
|
46
|
-
* caller can post an encrypted bounty in one call. Key exchange with the
|
|
47
|
-
* worker is intentionally out-of-band: assignWorker() returns the wrapped
|
|
48
|
-
* AES key as bytes, and the caller is responsible for delivering it (backend
|
|
49
|
-
* API, DM, whatever). This keeps the SDK pluggable across different
|
|
50
|
-
* message-passing substrates.
|
|
51
|
-
*/
|
|
52
|
-
export class Agent {
|
|
53
|
-
constructor(private readonly deps: AgentDeps) {}
|
|
54
|
-
|
|
55
|
-
async createTask(input: CreateTaskInput): Promise<CreateTaskResult> {
|
|
56
|
-
const { storage, escrow, keystore, events } = this.deps;
|
|
57
|
-
|
|
58
|
-
const aesKey = await generateAesKey();
|
|
59
|
-
const plaintext = toBytes(input.instructions);
|
|
60
|
-
const ciphertext = await aesEncrypt(plaintext, aesKey);
|
|
61
|
-
|
|
62
|
-
const uploaded = await storage.upload(ciphertext);
|
|
63
|
-
const rootHash = uploaded.rootHash;
|
|
64
|
-
|
|
65
|
-
const deadline = typeof input.deadline === 'bigint' ? input.deadline : BigInt(Math.floor(input.deadline.getTime() / 1000));
|
|
66
|
-
|
|
67
|
-
const { taskId, receipt } = await escrow.createTask({
|
|
68
|
-
taskHash: rootHash,
|
|
69
|
-
token: input.reward.token as Address,
|
|
70
|
-
amount: input.reward.amount,
|
|
71
|
-
category: input.category,
|
|
72
|
-
locationZone: input.locationZone,
|
|
73
|
-
deadline,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
const taskKey: TaskKey = { aesKey, createdAt: new Date() };
|
|
77
|
-
await keystore.putTaskKey(taskId, taskKey);
|
|
78
|
-
|
|
79
|
-
events?.emit('task.created', { taskId, txHash: receipt.hash, rootHash });
|
|
80
|
-
return { taskId, taskKey, rootHash, txHash: receipt.hash, receipt };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Wrap the stored AES key for the chosen worker and record the assignment
|
|
85
|
-
* on-chain. The caller must deliver `wrappedKey` to the worker separately
|
|
86
|
-
* (backend API, DM, etc.). Returns the wrapped bytes and the tx receipt.
|
|
87
|
-
*/
|
|
88
|
-
async assignWorker(taskId: TaskId, worker: Address, workerPubKey: string): Promise<AssignWorkerResult> {
|
|
89
|
-
const { keystore, escrow, events } = this.deps;
|
|
90
|
-
const stored = await keystore.getTaskKey(taskId);
|
|
91
|
-
if (!stored) {
|
|
92
|
-
throw new LifecycleError('LIFECYCLE/TASK_NOT_FOUND', `no task key in keystore for task ${taskId}`);
|
|
93
|
-
}
|
|
94
|
-
const wrappedKey = await eciesEncrypt(stored.aesKey, workerPubKey);
|
|
95
|
-
const receipt = await escrow.assignWorker(taskId, worker);
|
|
96
|
-
events?.emit('task.assigned', { taskId, worker, txHash: receipt.hash });
|
|
97
|
-
return { wrappedKey, receipt };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async cancelTask(taskId: TaskId): Promise<TxReceiptLike> {
|
|
101
|
-
const { escrow, keystore, events } = this.deps;
|
|
102
|
-
const receipt = await this.deps.escrow.cancelTask(taskId);
|
|
103
|
-
void escrow;
|
|
104
|
-
await keystore.delete(taskId);
|
|
105
|
-
events?.emit('task.cancelled', { taskId, txHash: receipt.hash });
|
|
106
|
-
return receipt;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Fetch evidence by root hash, unwrap the AES key with the agent's private
|
|
111
|
-
* key (signer's key material is not accessible, so a raw private key is
|
|
112
|
-
* required), and return the decrypted evidence plaintext.
|
|
113
|
-
*/
|
|
114
|
-
async fetchAndDecryptEvidence(evidenceHash: Hex, agentPrivKey: string): Promise<Uint8Array> {
|
|
115
|
-
const envelope = await this.deps.storage.download(evidenceHash);
|
|
116
|
-
return openEvidenceAsAgent(envelope, agentPrivKey);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function toBytes(x: string | Uint8Array): Uint8Array {
|
|
121
|
-
return typeof x === 'string' ? new TextEncoder().encode(x) : x;
|
|
122
|
-
}
|
package/src/agent/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './Agent.js';
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import { ethers } from 'ethers';
|
|
2
|
-
import type { Address, Hex, TaskId, TaskStatus, TxReceiptLike } from '../types.js';
|
|
3
|
-
import { BlindEscrowAbi } from './abi/index.js';
|
|
4
|
-
import { wrapChainError } from './errors.js';
|
|
5
|
-
|
|
6
|
-
const STATUS_BY_INDEX: TaskStatus[] = ['funded', 'assigned', 'submitted', 'verified', 'completed', 'cancelled'];
|
|
7
|
-
|
|
8
|
-
export interface CreateTaskArgs {
|
|
9
|
-
taskHash: Hex;
|
|
10
|
-
token: Address;
|
|
11
|
-
amount: bigint;
|
|
12
|
-
category: string;
|
|
13
|
-
locationZone: string;
|
|
14
|
-
deadline: bigint;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface OnChainTask {
|
|
18
|
-
taskId: TaskId;
|
|
19
|
-
agent: Address;
|
|
20
|
-
worker: Address;
|
|
21
|
-
token: Address;
|
|
22
|
-
amount: bigint;
|
|
23
|
-
taskHash: Hex;
|
|
24
|
-
evidenceHash: Hex;
|
|
25
|
-
status: TaskStatus;
|
|
26
|
-
createdAt: Date;
|
|
27
|
-
deadline: Date;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Typed ethers v6 wrapper for the BlindEscrow contract. Every state-changing
|
|
32
|
-
* method awaits one confirmation and returns a TxReceiptLike; every read
|
|
33
|
-
* returns decoded values. Ethers errors are mapped to ChainError via
|
|
34
|
-
* wrapChainError().
|
|
35
|
-
*/
|
|
36
|
-
export class BlindEscrowClient {
|
|
37
|
-
readonly contract: ethers.Contract;
|
|
38
|
-
readonly address: Address;
|
|
39
|
-
|
|
40
|
-
constructor(address: Address, runner: ethers.ContractRunner) {
|
|
41
|
-
this.address = address;
|
|
42
|
-
this.contract = new ethers.Contract(address, BlindEscrowAbi, runner);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Create a task and return its id + receipt. Reads the TaskCreated event for the id. */
|
|
46
|
-
async createTask(args: CreateTaskArgs): Promise<{ taskId: TaskId; receipt: TxReceiptLike }> {
|
|
47
|
-
try {
|
|
48
|
-
const overrides: ethers.Overrides = {};
|
|
49
|
-
if (args.token === '0x0000000000000000000000000000000000000000') {
|
|
50
|
-
overrides.value = args.amount;
|
|
51
|
-
}
|
|
52
|
-
const tx = await this.contract.createTask!(
|
|
53
|
-
args.taskHash,
|
|
54
|
-
args.token,
|
|
55
|
-
args.amount,
|
|
56
|
-
args.category,
|
|
57
|
-
args.locationZone,
|
|
58
|
-
args.deadline,
|
|
59
|
-
overrides,
|
|
60
|
-
);
|
|
61
|
-
const rec = await tx.wait(1);
|
|
62
|
-
const taskId = this.extractTaskIdFromReceipt(rec);
|
|
63
|
-
return { taskId, receipt: toReceipt(rec) };
|
|
64
|
-
} catch (err) {
|
|
65
|
-
throw wrapChainError(err, 'createTask');
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async assignWorker(taskId: TaskId, worker: Address): Promise<TxReceiptLike> {
|
|
70
|
-
return this.sendTx('assignWorker', [taskId, worker]);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async submitEvidence(taskId: TaskId, evidenceHash: Hex): Promise<TxReceiptLike> {
|
|
74
|
-
return this.sendTx('submitEvidence', [taskId, evidenceHash]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async completeVerification(taskId: TaskId, passed: boolean): Promise<TxReceiptLike> {
|
|
78
|
-
return this.sendTx('completeVerification', [taskId, passed]);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async cancelTask(taskId: TaskId): Promise<TxReceiptLike> {
|
|
82
|
-
return this.sendTx('cancelTask', [taskId]);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async raiseDispute(taskId: TaskId): Promise<TxReceiptLike> {
|
|
86
|
-
return this.sendTx('raiseDispute', [taskId]);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async resolveDispute(taskId: TaskId, workerFavored: boolean): Promise<TxReceiptLike> {
|
|
90
|
-
return this.sendTx('resolveDispute', [taskId, workerFavored]);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async claimTimeout(taskId: TaskId): Promise<TxReceiptLike> {
|
|
94
|
-
return this.sendTx('claimTimeout', [taskId]);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
async getTask(taskId: TaskId): Promise<OnChainTask> {
|
|
98
|
-
try {
|
|
99
|
-
const raw = await this.contract.getTask!(taskId);
|
|
100
|
-
return decodeTask(taskId, raw);
|
|
101
|
-
} catch (err) {
|
|
102
|
-
throw wrapChainError(err, 'getTask');
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async isTaskExpired(taskId: TaskId): Promise<boolean> {
|
|
107
|
-
try {
|
|
108
|
-
return Boolean(await this.contract.isTaskExpired!(taskId));
|
|
109
|
-
} catch (err) {
|
|
110
|
-
throw wrapChainError(err, 'isTaskExpired');
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async nextTaskId(): Promise<TaskId> {
|
|
115
|
-
try {
|
|
116
|
-
return BigInt(await this.contract.nextTaskId!());
|
|
117
|
-
} catch (err) {
|
|
118
|
-
throw wrapChainError(err, 'nextTaskId');
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async feeBps(): Promise<bigint> {
|
|
123
|
-
try {
|
|
124
|
-
return BigInt(await this.contract.feeBps!());
|
|
125
|
-
} catch (err) {
|
|
126
|
-
throw wrapChainError(err, 'feeBps');
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private async sendTx(method: string, args: unknown[]): Promise<TxReceiptLike> {
|
|
131
|
-
try {
|
|
132
|
-
const fn = this.contract[method];
|
|
133
|
-
if (typeof fn !== 'function') {
|
|
134
|
-
throw new Error(`unknown contract method: ${method}`);
|
|
135
|
-
}
|
|
136
|
-
const tx = await fn(...args);
|
|
137
|
-
const rec = await tx.wait(1);
|
|
138
|
-
return toReceipt(rec);
|
|
139
|
-
} catch (err) {
|
|
140
|
-
throw wrapChainError(err, method);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
private extractTaskIdFromReceipt(receipt: ethers.ContractTransactionReceipt | null): TaskId {
|
|
145
|
-
if (!receipt) throw new Error('no receipt');
|
|
146
|
-
const iface = this.contract.interface;
|
|
147
|
-
for (const log of receipt.logs) {
|
|
148
|
-
try {
|
|
149
|
-
const parsed = iface.parseLog({ topics: log.topics as string[], data: log.data });
|
|
150
|
-
if (parsed?.name === 'TaskCreated') {
|
|
151
|
-
return BigInt(parsed.args.getValue('taskId'));
|
|
152
|
-
}
|
|
153
|
-
} catch {
|
|
154
|
-
// not our event, skip
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
throw new Error('TaskCreated event not found in receipt logs');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function toReceipt(rec: ethers.ContractTransactionReceipt | null): TxReceiptLike {
|
|
162
|
-
if (!rec) throw new Error('no receipt');
|
|
163
|
-
return {
|
|
164
|
-
hash: rec.hash as Hex,
|
|
165
|
-
blockNumber: rec.blockNumber,
|
|
166
|
-
gasUsed: rec.gasUsed,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function decodeTask(taskId: TaskId, raw: ethers.Result): OnChainTask {
|
|
171
|
-
// Tuple order from BlindEscrow.getTask(): (agent, worker, token, amount, taskHash,
|
|
172
|
-
// evidenceHash, status, createdAt, deadline) — verify against contracts/BlindEscrow.sol.
|
|
173
|
-
const arr = raw as unknown as unknown[];
|
|
174
|
-
const statusIndex = Number(arr[6]);
|
|
175
|
-
return {
|
|
176
|
-
taskId,
|
|
177
|
-
agent: arr[0] as Address,
|
|
178
|
-
worker: arr[1] as Address,
|
|
179
|
-
token: arr[2] as Address,
|
|
180
|
-
amount: BigInt(arr[3] as bigint | number),
|
|
181
|
-
taskHash: arr[4] as Hex,
|
|
182
|
-
evidenceHash: arr[5] as Hex,
|
|
183
|
-
status: STATUS_BY_INDEX[statusIndex] ?? 'funded',
|
|
184
|
-
createdAt: new Date(Number(arr[7] as bigint | number) * 1000),
|
|
185
|
-
deadline: new Date(Number(arr[8] as bigint | number) * 1000),
|
|
186
|
-
};
|
|
187
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { ethers } from 'ethers';
|
|
2
|
-
import type { Address, Hex, TaskId, TxReceiptLike } from '../types.js';
|
|
3
|
-
import { BlindReputationAbi } from './abi/index.js';
|
|
4
|
-
import { wrapChainError } from './errors.js';
|
|
5
|
-
|
|
6
|
-
export interface ReputationSnapshot {
|
|
7
|
-
tasksCompleted: bigint;
|
|
8
|
-
totalScore: bigint;
|
|
9
|
-
disputes: bigint;
|
|
10
|
-
avgScore: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class BlindReputationClient {
|
|
14
|
-
readonly contract: ethers.Contract;
|
|
15
|
-
readonly address: Address;
|
|
16
|
-
|
|
17
|
-
constructor(address: Address, runner: ethers.ContractRunner) {
|
|
18
|
-
this.address = address;
|
|
19
|
-
this.contract = new ethers.Contract(address, BlindReputationAbi, runner);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async getReputation(worker: Address): Promise<ReputationSnapshot> {
|
|
23
|
-
try {
|
|
24
|
-
const raw = (await this.contract.getReputation!(worker)) as unknown as unknown[];
|
|
25
|
-
const tasksCompleted = BigInt(raw[0] as bigint | number);
|
|
26
|
-
const totalScore = BigInt(raw[1] as bigint | number);
|
|
27
|
-
const disputes = BigInt(raw[2] as bigint | number);
|
|
28
|
-
const avgScore = tasksCompleted === 0n ? 0 : Number(totalScore) / Number(tasksCompleted);
|
|
29
|
-
return { tasksCompleted, totalScore, disputes, avgScore };
|
|
30
|
-
} catch (err) {
|
|
31
|
-
throw wrapChainError(err, 'getReputation');
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async rate(worker: Address, score: number, taskId: TaskId): Promise<TxReceiptLike> {
|
|
36
|
-
if (!Number.isInteger(score) || score < 1 || score > 5) {
|
|
37
|
-
throw wrapChainError(new Error(`score must be integer 1..5, got ${score}`), 'rate');
|
|
38
|
-
}
|
|
39
|
-
try {
|
|
40
|
-
const tx = await this.contract.rate!(worker, score, taskId);
|
|
41
|
-
const rec = await tx.wait(1);
|
|
42
|
-
return { hash: rec.hash as Hex, blockNumber: rec.blockNumber, gasUsed: rec.gasUsed };
|
|
43
|
-
} catch (err) {
|
|
44
|
-
throw wrapChainError(err, 'rate');
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async hasBeenRated(worker: Address, taskId: TaskId): Promise<boolean> {
|
|
49
|
-
try {
|
|
50
|
-
return Boolean(await this.contract.hasBeenRated!(worker, taskId));
|
|
51
|
-
} catch (err) {
|
|
52
|
-
throw wrapChainError(err, 'hasBeenRated');
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/chain/ChainClient.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { ethers } from 'ethers';
|
|
2
|
-
import type { Signer as SdkSigner } from '../signer/index.js';
|
|
3
|
-
import type { Network } from '../network/index.js';
|
|
4
|
-
import { BlindEscrowClient } from './BlindEscrowClient.js';
|
|
5
|
-
import { BlindReputationClient } from './BlindReputationClient.js';
|
|
6
|
-
import { TaskRegistryClient } from './TaskRegistryClient.js';
|
|
7
|
-
|
|
8
|
-
export interface ChainClientOptions {
|
|
9
|
-
network: Network;
|
|
10
|
-
signer?: SdkSigner;
|
|
11
|
-
/** If provided, used for reads even when signer is absent. Useful for public queries. */
|
|
12
|
-
provider?: ethers.Provider;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Bundles typed clients for all three BlindMarket contracts plus the underlying
|
|
17
|
-
* ethers runner. Reads use provider (or signer's provider); writes require a
|
|
18
|
-
* signer. To keep the SDK's Signer abstraction decoupled from ethers at the
|
|
19
|
-
* call sites, we unwrap an EthersSigner's inner ethers.Signer lazily here.
|
|
20
|
-
*/
|
|
21
|
-
export class ChainClient {
|
|
22
|
-
readonly escrow: BlindEscrowClient;
|
|
23
|
-
readonly registry: TaskRegistryClient;
|
|
24
|
-
readonly reputation: BlindReputationClient;
|
|
25
|
-
readonly provider: ethers.Provider;
|
|
26
|
-
readonly network: Network;
|
|
27
|
-
|
|
28
|
-
constructor(opts: ChainClientOptions) {
|
|
29
|
-
this.network = opts.network;
|
|
30
|
-
const provider =
|
|
31
|
-
opts.provider ??
|
|
32
|
-
(opts.signer && unwrapEthersSigner(opts.signer)?.provider) ??
|
|
33
|
-
defaultProvider(opts.network);
|
|
34
|
-
this.provider = provider;
|
|
35
|
-
|
|
36
|
-
const runner: ethers.ContractRunner = unwrapEthersSigner(opts.signer) ?? provider;
|
|
37
|
-
this.escrow = new BlindEscrowClient(opts.network.contracts.escrow, runner);
|
|
38
|
-
this.registry = new TaskRegistryClient(opts.network.contracts.registry, runner);
|
|
39
|
-
this.reputation = new BlindReputationClient(opts.network.contracts.reputation, runner);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* If the SDK Signer is an EthersSigner, pull out the inner ethers.Signer
|
|
45
|
-
* (so ethers.Contract treats it as the transaction runner). Otherwise
|
|
46
|
-
* returns undefined — writes will fail at the contract layer with a clear
|
|
47
|
-
* signer-required error, as intended.
|
|
48
|
-
*/
|
|
49
|
-
function unwrapEthersSigner(signer: SdkSigner | undefined): ethers.Signer | undefined {
|
|
50
|
-
if (!signer) return undefined;
|
|
51
|
-
const maybe = signer as unknown as { inner?: ethers.Signer };
|
|
52
|
-
return maybe.inner;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function defaultProvider(network: Network): ethers.Provider {
|
|
56
|
-
const url = network.rpc[0];
|
|
57
|
-
if (!url) throw new Error(`network ${network.name} has no RPC endpoint configured`);
|
|
58
|
-
return new ethers.JsonRpcProvider(url, { chainId: Number(network.chainId), name: network.name });
|
|
59
|
-
}
|