@blindmarket/sdk 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/README.md +72 -0
  2. package/biome.json +19 -0
  3. package/dist/BlindEscrowClient-CyPuKMge.d.cts +54 -0
  4. package/dist/BlindEscrowClient-R7iGpE-n.d.ts +54 -0
  5. package/dist/KeyStore-Be_SSpCt.d.cts +21 -0
  6. package/dist/KeyStore-k1JgYPJY.d.ts +21 -0
  7. package/dist/Signer-BnqH_qew.d.cts +38 -0
  8. package/dist/Signer-LColE0Uo.d.ts +38 -0
  9. package/dist/Storage-4QOrh7rm.d.ts +17 -0
  10. package/dist/Storage-Cf4IJRMk.d.cts +17 -0
  11. package/dist/TaskRegistryClient-Ch6lYqM_.d.ts +24 -0
  12. package/dist/TaskRegistryClient-CrQjddDc.d.cts +24 -0
  13. package/dist/aes-J-7HrHze.d.cts +17 -0
  14. package/dist/aes-J-7HrHze.d.ts +17 -0
  15. package/dist/agent/Agent.d.ts +59 -0
  16. package/dist/agent/Agent.js +73 -0
  17. package/dist/agent/index.cjs +425 -0
  18. package/dist/agent/index.cjs.map +1 -0
  19. package/dist/agent/index.d.cts +63 -0
  20. package/dist/agent/index.d.ts +1 -0
  21. package/dist/agent/index.js +1 -0
  22. package/dist/agent/index.js.map +1 -0
  23. package/dist/api/index.cjs +4 -0
  24. package/dist/api/index.cjs.map +1 -0
  25. package/dist/api/index.d.cts +2 -0
  26. package/dist/api/index.d.ts +1 -0
  27. package/dist/api/index.js +1 -0
  28. package/dist/api/index.js.map +1 -0
  29. package/dist/chain/BlindEscrowClient.d.ts +51 -0
  30. package/dist/chain/BlindEscrowClient.js +142 -0
  31. package/dist/chain/BlindReputationClient.d.ts +16 -0
  32. package/dist/chain/BlindReputationClient.js +45 -0
  33. package/dist/chain/ChainClient.d.ts +26 -0
  34. package/dist/chain/ChainClient.js +46 -0
  35. package/dist/chain/TaskRegistryClient.d.ts +21 -0
  36. package/dist/chain/TaskRegistryClient.js +70 -0
  37. package/dist/chain/abi/BlindEscrow.json +1113 -0
  38. package/dist/chain/abi/BlindReputation.json +447 -0
  39. package/dist/chain/abi/TaskRegistry.json +586 -0
  40. package/dist/chain/abi/index.d.ts +4 -0
  41. package/dist/chain/abi/index.js +4 -0
  42. package/dist/chain/errors.d.ts +8 -0
  43. package/dist/chain/errors.js +32 -0
  44. package/dist/chain/index.cjs +2484 -0
  45. package/dist/chain/index.cjs.map +1 -0
  46. package/dist/chain/index.d.cts +2305 -0
  47. package/dist/chain/index.d.ts +6 -0
  48. package/dist/chain/index.js +6 -0
  49. package/dist/chain/index.js.map +1 -0
  50. package/dist/compute/index.cjs +4 -0
  51. package/dist/compute/index.cjs.map +1 -0
  52. package/dist/compute/index.d.cts +2 -0
  53. package/dist/compute/index.d.ts +1 -0
  54. package/dist/compute/index.js +1 -0
  55. package/dist/compute/index.js.map +1 -0
  56. package/dist/crypto/aes.d.ts +15 -0
  57. package/dist/crypto/aes.js +81 -0
  58. package/dist/crypto/constants.d.ts +7 -0
  59. package/dist/crypto/constants.js +7 -0
  60. package/dist/crypto/ecies.d.ts +8 -0
  61. package/dist/crypto/ecies.js +51 -0
  62. package/dist/crypto/envelope.d.ts +35 -0
  63. package/dist/crypto/envelope.js +103 -0
  64. package/dist/crypto/hkdf.d.ts +5 -0
  65. package/dist/crypto/hkdf.js +34 -0
  66. package/dist/crypto/index.cjs +424 -0
  67. package/dist/crypto/index.cjs.map +1 -0
  68. package/dist/crypto/index.d.cts +74 -0
  69. package/dist/crypto/index.d.ts +6 -0
  70. package/dist/crypto/index.js +6 -0
  71. package/dist/crypto/index.js.map +1 -0
  72. package/dist/crypto/keys.d.ts +12 -0
  73. package/dist/crypto/keys.js +42 -0
  74. package/dist/errors/BlindBountyError.d.ts +14 -0
  75. package/dist/errors/BlindBountyError.js +30 -0
  76. package/dist/errors/codes.d.ts +19 -0
  77. package/dist/errors/codes.js +48 -0
  78. package/dist/errors/index.cjs +136 -0
  79. package/dist/errors/index.cjs.map +1 -0
  80. package/dist/errors/index.d.cts +60 -0
  81. package/dist/errors/index.d.ts +3 -0
  82. package/dist/errors/index.js +3 -0
  83. package/dist/errors/index.js.map +1 -0
  84. package/dist/errors/subclasses.d.ts +26 -0
  85. package/dist/errors/subclasses.js +41 -0
  86. package/dist/events/EventBus.d.ts +13 -0
  87. package/dist/events/EventBus.js +41 -0
  88. package/dist/events/index.cjs +41 -0
  89. package/dist/events/index.cjs.map +1 -0
  90. package/dist/events/index.d.cts +61 -0
  91. package/dist/events/index.d.ts +2 -0
  92. package/dist/events/index.js +2 -0
  93. package/dist/events/index.js.map +1 -0
  94. package/dist/events/types.d.ts +45 -0
  95. package/dist/events/types.js +1 -0
  96. package/dist/index.cjs +4 -0
  97. package/dist/index.cjs.map +1 -0
  98. package/dist/index.d.cts +2 -0
  99. package/dist/index.d.ts +129 -0
  100. package/dist/index.js +154 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/keystore/InMemoryKeyStore.d.ts +21 -0
  103. package/dist/keystore/InMemoryKeyStore.js +111 -0
  104. package/dist/keystore/KeyStore.d.ts +18 -0
  105. package/dist/keystore/KeyStore.js +1 -0
  106. package/dist/keystore/index.cjs +335 -0
  107. package/dist/keystore/index.cjs.map +1 -0
  108. package/dist/keystore/index.d.cts +23 -0
  109. package/dist/keystore/index.d.ts +2 -0
  110. package/dist/keystore/index.js +2 -0
  111. package/dist/keystore/index.js.map +1 -0
  112. package/dist/network/index.cjs +74 -0
  113. package/dist/network/index.cjs.map +1 -0
  114. package/dist/network/index.d.cts +36 -0
  115. package/dist/network/index.d.ts +3 -0
  116. package/dist/network/index.js +3 -0
  117. package/dist/network/index.js.map +1 -0
  118. package/dist/network/presets.d.ts +24 -0
  119. package/dist/network/presets.js +23 -0
  120. package/dist/network/resolve.d.ts +7 -0
  121. package/dist/network/resolve.js +17 -0
  122. package/dist/network/types.d.ts +16 -0
  123. package/dist/network/types.js +1 -0
  124. package/dist/signer/EthersSigner.d.ts +24 -0
  125. package/dist/signer/EthersSigner.js +68 -0
  126. package/dist/signer/PrivateKeySigner.d.ts +14 -0
  127. package/dist/signer/PrivateKeySigner.js +18 -0
  128. package/dist/signer/Signer.d.ts +35 -0
  129. package/dist/signer/Signer.js +1 -0
  130. package/dist/signer/index.cjs +121 -0
  131. package/dist/signer/index.cjs.map +1 -0
  132. package/dist/signer/index.d.cts +40 -0
  133. package/dist/signer/index.d.ts +3 -0
  134. package/dist/signer/index.js +3 -0
  135. package/dist/signer/index.js.map +1 -0
  136. package/dist/storage/MemoryStorage.d.ts +18 -0
  137. package/dist/storage/MemoryStorage.js +40 -0
  138. package/dist/storage/Storage.d.ts +14 -0
  139. package/dist/storage/Storage.js +1 -0
  140. package/dist/storage/ZgStorage.d.ts +24 -0
  141. package/dist/storage/ZgStorage.js +102 -0
  142. package/dist/storage/index.cjs +221 -0
  143. package/dist/storage/index.cjs.map +1 -0
  144. package/dist/storage/index.d.cts +44 -0
  145. package/dist/storage/index.d.ts +3 -0
  146. package/dist/storage/index.js +3 -0
  147. package/dist/storage/index.js.map +1 -0
  148. package/dist/testing/index.cjs +4 -0
  149. package/dist/testing/index.cjs.map +1 -0
  150. package/dist/testing/index.d.cts +2 -0
  151. package/dist/testing/index.d.ts +1 -0
  152. package/dist/testing/index.js +1 -0
  153. package/dist/testing/index.js.map +1 -0
  154. package/dist/types-BOG5Ub1z.d.cts +31 -0
  155. package/dist/types-BOG5Ub1z.d.ts +31 -0
  156. package/dist/types-V7_ZmUb-.d.cts +19 -0
  157. package/dist/types-hAsITUQ7.d.ts +19 -0
  158. package/dist/types.d.ts +58 -0
  159. package/dist/types.js +1 -0
  160. package/dist/verifier/index.cjs +4 -0
  161. package/dist/verifier/index.cjs.map +1 -0
  162. package/dist/verifier/index.d.cts +2 -0
  163. package/dist/verifier/index.d.ts +1 -0
  164. package/dist/verifier/index.js +1 -0
  165. package/dist/verifier/index.js.map +1 -0
  166. package/dist/worker/Worker.d.ts +53 -0
  167. package/dist/worker/Worker.js +53 -0
  168. package/dist/worker/index.cjs +382 -0
  169. package/dist/worker/index.cjs.map +1 -0
  170. package/dist/worker/index.d.cts +58 -0
  171. package/dist/worker/index.d.ts +1 -0
  172. package/dist/worker/index.js +1 -0
  173. package/dist/worker/index.js.map +1 -0
  174. package/examples/agent-demo.ts +121 -0
  175. package/package.json +46 -0
  176. package/scripts/gen-crypto-fixtures.mjs +72 -0
  177. package/src/agent/Agent.ts +122 -0
  178. package/src/agent/index.ts +1 -0
  179. package/src/api/index.ts +1 -0
  180. package/src/chain/BlindEscrowClient.ts +182 -0
  181. package/src/chain/BlindReputationClient.ts +55 -0
  182. package/src/chain/ChainClient.ts +59 -0
  183. package/src/chain/TaskRegistryClient.ts +82 -0
  184. package/src/chain/abi/BlindEscrow.json +1113 -0
  185. package/src/chain/abi/BlindReputation.json +447 -0
  186. package/src/chain/abi/TaskRegistry.json +586 -0
  187. package/src/chain/abi/index.ts +5 -0
  188. package/src/chain/errors.ts +43 -0
  189. package/src/chain/index.ts +6 -0
  190. package/src/compute/index.ts +1 -0
  191. package/src/crypto/aes.ts +104 -0
  192. package/src/crypto/constants.ts +7 -0
  193. package/src/crypto/ecies.ts +60 -0
  194. package/src/crypto/envelope.ts +131 -0
  195. package/src/crypto/hkdf.ts +45 -0
  196. package/src/crypto/index.ts +6 -0
  197. package/src/crypto/keys.ts +56 -0
  198. package/src/errors/BlindMarketError.ts +38 -0
  199. package/src/errors/codes.ts +67 -0
  200. package/src/errors/index.ts +3 -0
  201. package/src/errors/subclasses.ts +59 -0
  202. package/src/events/EventBus.ts +45 -0
  203. package/src/events/index.ts +2 -0
  204. package/src/events/types.ts +13 -0
  205. package/src/index.ts +222 -0
  206. package/src/keystore/InMemoryKeyStore.ts +146 -0
  207. package/src/keystore/KeyStore.ts +19 -0
  208. package/src/keystore/index.ts +2 -0
  209. package/src/network/index.ts +3 -0
  210. package/src/network/presets.ts +27 -0
  211. package/src/network/resolve.ts +19 -0
  212. package/src/network/types.ts +18 -0
  213. package/src/signer/EthersSigner.ts +82 -0
  214. package/src/signer/PrivateKeySigner.ts +19 -0
  215. package/src/signer/Signer.ts +40 -0
  216. package/src/signer/index.ts +3 -0
  217. package/src/storage/MemoryStorage.ts +49 -0
  218. package/src/storage/Storage.ts +15 -0
  219. package/src/storage/ZgStorage.ts +124 -0
  220. package/src/storage/index.ts +3 -0
  221. package/src/testing/index.ts +1 -0
  222. package/src/types.ts +76 -0
  223. package/src/verifier/index.ts +1 -0
  224. package/src/worker/Worker.ts +98 -0
  225. package/src/worker/index.ts +1 -0
  226. package/test/agent.test.ts +193 -0
  227. package/test/chain.test.ts +145 -0
  228. package/test/crypto.aes.test.ts +81 -0
  229. package/test/crypto.compat.test.ts +33 -0
  230. package/test/crypto.ecies.test.ts +64 -0
  231. package/test/envelope.test.ts +82 -0
  232. package/test/errors.test.ts +64 -0
  233. package/test/events.test.ts +59 -0
  234. package/test/fixtures/crypto.json +16 -0
  235. package/test/keystore.test.ts +103 -0
  236. package/test/network.test.ts +53 -0
  237. package/test/signer.test.ts +66 -0
  238. package/test/smoke.test.ts +7 -0
  239. package/test/storage.test.ts +55 -0
  240. package/test/worker.test.ts +181 -0
  241. package/tsconfig.build.json +1 -0
  242. package/tsconfig.json +13 -0
  243. package/tsup.config.ts +32 -0
  244. package/vitest.config.ts +17 -0
@@ -0,0 +1,182 @@
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 tx = await this.contract.createTask!(
49
+ args.taskHash,
50
+ args.token,
51
+ args.amount,
52
+ args.category,
53
+ args.locationZone,
54
+ args.deadline,
55
+ );
56
+ const rec = await tx.wait(1);
57
+ const taskId = this.extractTaskIdFromReceipt(rec);
58
+ return { taskId, receipt: toReceipt(rec) };
59
+ } catch (err) {
60
+ throw wrapChainError(err, 'createTask');
61
+ }
62
+ }
63
+
64
+ async assignWorker(taskId: TaskId, worker: Address): Promise<TxReceiptLike> {
65
+ return this.sendTx('assignWorker', [taskId, worker]);
66
+ }
67
+
68
+ async submitEvidence(taskId: TaskId, evidenceHash: Hex): Promise<TxReceiptLike> {
69
+ return this.sendTx('submitEvidence', [taskId, evidenceHash]);
70
+ }
71
+
72
+ async completeVerification(taskId: TaskId, passed: boolean): Promise<TxReceiptLike> {
73
+ return this.sendTx('completeVerification', [taskId, passed]);
74
+ }
75
+
76
+ async cancelTask(taskId: TaskId): Promise<TxReceiptLike> {
77
+ return this.sendTx('cancelTask', [taskId]);
78
+ }
79
+
80
+ async raiseDispute(taskId: TaskId): Promise<TxReceiptLike> {
81
+ return this.sendTx('raiseDispute', [taskId]);
82
+ }
83
+
84
+ async resolveDispute(taskId: TaskId, workerFavored: boolean): Promise<TxReceiptLike> {
85
+ return this.sendTx('resolveDispute', [taskId, workerFavored]);
86
+ }
87
+
88
+ async claimTimeout(taskId: TaskId): Promise<TxReceiptLike> {
89
+ return this.sendTx('claimTimeout', [taskId]);
90
+ }
91
+
92
+ async getTask(taskId: TaskId): Promise<OnChainTask> {
93
+ try {
94
+ const raw = await this.contract.getTask!(taskId);
95
+ return decodeTask(taskId, raw);
96
+ } catch (err) {
97
+ throw wrapChainError(err, 'getTask');
98
+ }
99
+ }
100
+
101
+ async isTaskExpired(taskId: TaskId): Promise<boolean> {
102
+ try {
103
+ return Boolean(await this.contract.isTaskExpired!(taskId));
104
+ } catch (err) {
105
+ throw wrapChainError(err, 'isTaskExpired');
106
+ }
107
+ }
108
+
109
+ async nextTaskId(): Promise<TaskId> {
110
+ try {
111
+ return BigInt(await this.contract.nextTaskId!());
112
+ } catch (err) {
113
+ throw wrapChainError(err, 'nextTaskId');
114
+ }
115
+ }
116
+
117
+ async feeBps(): Promise<bigint> {
118
+ try {
119
+ return BigInt(await this.contract.feeBps!());
120
+ } catch (err) {
121
+ throw wrapChainError(err, 'feeBps');
122
+ }
123
+ }
124
+
125
+ private async sendTx(method: string, args: unknown[]): Promise<TxReceiptLike> {
126
+ try {
127
+ const fn = this.contract[method];
128
+ if (typeof fn !== 'function') {
129
+ throw new Error(`unknown contract method: ${method}`);
130
+ }
131
+ const tx = await fn(...args);
132
+ const rec = await tx.wait(1);
133
+ return toReceipt(rec);
134
+ } catch (err) {
135
+ throw wrapChainError(err, method);
136
+ }
137
+ }
138
+
139
+ private extractTaskIdFromReceipt(receipt: ethers.ContractTransactionReceipt | null): TaskId {
140
+ if (!receipt) throw new Error('no receipt');
141
+ const iface = this.contract.interface;
142
+ for (const log of receipt.logs) {
143
+ try {
144
+ const parsed = iface.parseLog({ topics: log.topics as string[], data: log.data });
145
+ if (parsed?.name === 'TaskCreated') {
146
+ return BigInt(parsed.args.getValue('taskId'));
147
+ }
148
+ } catch {
149
+ // not our event, skip
150
+ }
151
+ }
152
+ throw new Error('TaskCreated event not found in receipt logs');
153
+ }
154
+ }
155
+
156
+ function toReceipt(rec: ethers.ContractTransactionReceipt | null): TxReceiptLike {
157
+ if (!rec) throw new Error('no receipt');
158
+ return {
159
+ hash: rec.hash as Hex,
160
+ blockNumber: rec.blockNumber,
161
+ gasUsed: rec.gasUsed,
162
+ };
163
+ }
164
+
165
+ function decodeTask(taskId: TaskId, raw: ethers.Result): OnChainTask {
166
+ // Tuple order from BlindEscrow.getTask(): (agent, worker, token, amount, taskHash,
167
+ // evidenceHash, status, createdAt, deadline) — verify against contracts/BlindEscrow.sol.
168
+ const arr = raw as unknown as unknown[];
169
+ const statusIndex = Number(arr[6]);
170
+ return {
171
+ taskId,
172
+ agent: arr[0] as Address,
173
+ worker: arr[1] as Address,
174
+ token: arr[2] as Address,
175
+ amount: BigInt(arr[3] as bigint | number),
176
+ taskHash: arr[4] as Hex,
177
+ evidenceHash: arr[5] as Hex,
178
+ status: STATUS_BY_INDEX[statusIndex] ?? 'funded',
179
+ createdAt: new Date(Number(arr[7] as bigint | number) * 1000),
180
+ deadline: new Date(Number(arr[8] as bigint | number) * 1000),
181
+ };
182
+ }
@@ -0,0 +1,55 @@
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
+ }
@@ -0,0 +1,59 @@
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
+ }
@@ -0,0 +1,82 @@
1
+ import { ethers } from 'ethers';
2
+ import type { Address, Hex, TaskId, TxReceiptLike } from '../types.js';
3
+ import { TaskRegistryAbi } from './abi/index.js';
4
+ import { wrapChainError } from './errors.js';
5
+
6
+ export interface OpenTaskMeta {
7
+ taskId: TaskId;
8
+ token: Address;
9
+ category: string;
10
+ locationZone: string;
11
+ reward: bigint;
12
+ createdAt: Date;
13
+ }
14
+
15
+ export class TaskRegistryClient {
16
+ readonly contract: ethers.Contract;
17
+ readonly address: Address;
18
+
19
+ constructor(address: Address, runner: ethers.ContractRunner) {
20
+ this.address = address;
21
+ this.contract = new ethers.Contract(address, TaskRegistryAbi, runner);
22
+ }
23
+
24
+ async getTaskMeta(taskId: TaskId): Promise<OpenTaskMeta> {
25
+ try {
26
+ const raw = (await this.contract.getTaskMeta!(taskId)) as unknown as unknown[];
27
+ return {
28
+ taskId,
29
+ token: raw[0] as Address,
30
+ category: raw[1] as string,
31
+ locationZone: raw[2] as string,
32
+ reward: BigInt(raw[3] as bigint | number),
33
+ createdAt: new Date(Number(raw[4] as bigint | number) * 1000),
34
+ };
35
+ } catch (err) {
36
+ throw wrapChainError(err, 'getTaskMeta');
37
+ }
38
+ }
39
+
40
+ async getOpenTasks(offset: bigint, limit: bigint): Promise<TaskId[]> {
41
+ try {
42
+ const ids = (await this.contract.getOpenTasks!(offset, limit)) as unknown as unknown[];
43
+ return ids.map((x) => BigInt(x as bigint | number));
44
+ } catch (err) {
45
+ throw wrapChainError(err, 'getOpenTasks');
46
+ }
47
+ }
48
+
49
+ async totalTasks(): Promise<bigint> {
50
+ try {
51
+ return BigInt(await this.contract.totalTasks!());
52
+ } catch (err) {
53
+ throw wrapChainError(err, 'totalTasks');
54
+ }
55
+ }
56
+
57
+ async openTaskCount(): Promise<bigint> {
58
+ try {
59
+ return BigInt(await this.contract.openTaskCount!());
60
+ } catch (err) {
61
+ throw wrapChainError(err, 'openTaskCount');
62
+ }
63
+ }
64
+
65
+ async taskExists(taskId: TaskId): Promise<boolean> {
66
+ try {
67
+ return Boolean(await this.contract.taskExists!(taskId));
68
+ } catch (err) {
69
+ throw wrapChainError(err, 'taskExists');
70
+ }
71
+ }
72
+
73
+ async closeTask(taskId: TaskId): Promise<TxReceiptLike> {
74
+ try {
75
+ const tx = await this.contract.closeTask!(taskId);
76
+ const rec = await tx.wait(1);
77
+ return { hash: rec.hash as Hex, blockNumber: rec.blockNumber, gasUsed: rec.gasUsed };
78
+ } catch (err) {
79
+ throw wrapChainError(err, 'closeTask');
80
+ }
81
+ }
82
+ }